Merge branch 'main' of http://121.37.111.42:3000/zhengsl/WholeProcessPlatform
@ -14,11 +14,22 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class MybitsPlusConfig {
|
public class MybitsPlusConfig {
|
||||||
|
|
||||||
|
// @Bean
|
||||||
|
// public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||||
|
// MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
|
||||||
|
// mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||||
|
// return mybatisPlusInterceptor;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页插件配置(Oracle 兼容)
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||||
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
|
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||||
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
// 添加分页拦截器,指定数据库类型为 Oracle
|
||||||
return mybatisPlusInterceptor;
|
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.ORACLE));
|
||||||
|
return interceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,9 +90,11 @@ public class SysMenuController {
|
|||||||
@PostMapping("/permissionAssignment")
|
@PostMapping("/permissionAssignment")
|
||||||
@Operation(summary = "获取分配权限(不含按钮)")
|
@Operation(summary = "获取分配权限(不含按钮)")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public List<Map<String, Object>> permissionAssignment(String roleId) {
|
public List<Map<String, Object>> permissionAssignment(String code, String roleId) {
|
||||||
|
if (StrUtil.isBlank(code)) {
|
||||||
return sysMenuService.permissionAssignment(roleId);
|
code = "1";
|
||||||
|
}
|
||||||
|
return sysMenuService.permissionAssignment(code,roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************
|
/**********************************
|
||||||
|
|||||||
@ -115,6 +115,9 @@ public class SysOrganizationController {
|
|||||||
if (StrUtil.isEmpty(sysOrganization.getIsvaild())) {
|
if (StrUtil.isEmpty(sysOrganization.getIsvaild())) {
|
||||||
sysOrganization.setIsvaild("1");
|
sysOrganization.setIsvaild("1");
|
||||||
}
|
}
|
||||||
|
if("".equals(sysOrganization.getId())){
|
||||||
|
sysOrganization.setId(null);
|
||||||
|
}
|
||||||
//填写 当前用户名称
|
//填写 当前用户名称
|
||||||
sysOrganization.setLastmodifier(userService.getUsername());
|
sysOrganization.setLastmodifier(userService.getUsername());
|
||||||
//填写 当前日期
|
//填写 当前日期
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.yfd.platform.system.controller;
|
package com.yfd.platform.system.controller;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.yfd.platform.annotation.Log;
|
import com.yfd.platform.annotation.Log;
|
||||||
@ -46,15 +47,8 @@ public class SysRoleController {
|
|||||||
@PostMapping("/list")
|
@PostMapping("/list")
|
||||||
@Operation(summary = "查询所有角色")
|
@Operation(summary = "查询所有角色")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public List<SysRole> list(String rolename) {
|
public List<SysRole> list(@RequestParam(required = false) String rolename) {
|
||||||
QueryWrapper<SysRole> queryWrapper = new QueryWrapper<>();
|
return roleService.selectRoleList(rolename);
|
||||||
if (StrUtil.isNotEmpty(rolename)) {
|
|
||||||
//根据角色名称模糊查询
|
|
||||||
queryWrapper.like("rolename", rolename);
|
|
||||||
}
|
|
||||||
//根据角色级别,角色编号 正序排序
|
|
||||||
queryWrapper.ne("level", "1").orderByAsc("level", "lastmodifydate");
|
|
||||||
return roleService.list(queryWrapper);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/***********************************
|
/***********************************
|
||||||
|
|||||||
@ -64,7 +64,7 @@ public class UserController {
|
|||||||
public ResponseResult queryUsers(String orgid,
|
public ResponseResult queryUsers(String orgid,
|
||||||
String username, Page<SysUser> page) {
|
String username, Page<SysUser> page) {
|
||||||
|
|
||||||
Page<Map<String, Object>> mapPage = userService.queryUsers(orgid,
|
Page<SysUser> mapPage = userService.queryUsers(orgid,
|
||||||
username, page);
|
username, page);
|
||||||
return ResponseResult.successData(mapPage);
|
return ResponseResult.successData(mapPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ public class SysRole implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 1-超级管理员 2-单位管理员 3-普通用户
|
* 1-超级管理员 2-单位管理员 3-普通用户
|
||||||
*/
|
*/
|
||||||
|
@TableField("\"LEVEL\"")
|
||||||
private String level;
|
private String level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@ -113,4 +114,7 @@ public class SysUser implements Serializable {
|
|||||||
* 备用3
|
* 备用3
|
||||||
*/
|
*/
|
||||||
private String custom3;
|
private String custom3;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
List<SysRole> roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,4 +101,12 @@ public interface SysRoleMapper extends BaseMapper<SysRole> {
|
|||||||
* 返回值说明:角色 ID 列表
|
* 返回值说明:角色 ID 列表
|
||||||
***********************************/
|
***********************************/
|
||||||
List<String> getRoleIdsByUserId(String id);
|
List<String> getRoleIdsByUserId(String id);
|
||||||
|
|
||||||
|
/**********************************
|
||||||
|
* 用途说明:查询角色列表(Oracle 兼容)
|
||||||
|
* 参数说明:rolename - 角色名称
|
||||||
|
* 返回值说明:角色列表
|
||||||
|
***********************************/
|
||||||
|
List<SysRole> selectRoleList(@Param("rolename") String rolename);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,7 +81,7 @@ public interface SysUserMapper extends BaseMapper<SysUser> {
|
|||||||
************************************/
|
************************************/
|
||||||
boolean delInRoleUsersByUserid(@Param("userid") String userid,@Param("roleids")String[] roleids);
|
boolean delInRoleUsersByUserid(@Param("userid") String userid,@Param("roleids")String[] roleids);
|
||||||
|
|
||||||
Page<Map<String, Object>> queryUsers(String orgid,
|
Page<SysUser> queryUsers(String orgid,
|
||||||
String username,
|
String username,
|
||||||
Page<SysUser> page);
|
Page<SysUser> page);
|
||||||
|
|
||||||
|
|||||||
@ -95,7 +95,7 @@ public interface ISysMenuService extends IService<SysMenu> {
|
|||||||
* isdisplay 是否显示
|
* isdisplay 是否显示
|
||||||
* 返回值说明: 菜单结构树集合
|
* 返回值说明: 菜单结构树集合
|
||||||
***********************************/
|
***********************************/
|
||||||
List<Map<String, Object>> permissionAssignment(String roleId);
|
List<Map<String, Object>> permissionAssignment(String code,String roleId);
|
||||||
|
|
||||||
String uploadIcon(MultipartFile icon) throws FileNotFoundException;
|
String uploadIcon(MultipartFile icon) throws FileNotFoundException;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,4 +63,6 @@ public interface ISysRoleService extends IService<SysRole> {
|
|||||||
* 返回值说明: 是否分配成功
|
* 返回值说明: 是否分配成功
|
||||||
***********************************/
|
***********************************/
|
||||||
boolean setMenuById(String id, String menuIds);
|
boolean setMenuById(String id, String menuIds);
|
||||||
|
|
||||||
|
List<SysRole> selectRoleList(String rolename);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,7 +128,7 @@ public interface IUserService extends IService<SysUser> {
|
|||||||
boolean addUserRoles(String roleid, String userid);
|
boolean addUserRoles(String roleid, String userid);
|
||||||
|
|
||||||
//Page<SysUser> queryUsers(String orgid, String username, Page<SysUser> page);
|
//Page<SysUser> queryUsers(String orgid, String username, Page<SysUser> page);
|
||||||
Page<Map<String,Object>> queryUsers(String orgid, String username, Page<SysUser> page);
|
Page<SysUser> queryUsers(String orgid, String username, Page<SysUser> page);
|
||||||
|
|
||||||
/***********************************
|
/***********************************
|
||||||
* 用途说明:根据ID批量删除用户
|
* 用途说明:根据ID批量删除用户
|
||||||
|
|||||||
@ -451,18 +451,19 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
|
|||||||
* 返回值说明: 菜单结构树集合
|
* 返回值说明: 菜单结构树集合
|
||||||
***********************************/
|
***********************************/
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> permissionAssignment(String roleId) {
|
public List<Map<String, Object>> permissionAssignment(String code,String roleId) {
|
||||||
|
|
||||||
String code = sysMenuMapper.getSystemCodeById(roleId);
|
// String code = sysMenuMapper.getSystemCodeById(roleId);
|
||||||
if (code == null) {
|
// if (code == null) {
|
||||||
code = "1";
|
// code = "1";
|
||||||
}
|
// }
|
||||||
LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(SysMenu::getSystemcode, code).select(SysMenu::getId,
|
queryWrapper.eq(SysMenu::getSystemcode, code).select(SysMenu::getId,
|
||||||
SysMenu::getParentid, SysMenu::getName).orderByAsc
|
SysMenu::getParentid, SysMenu::getName).orderByAsc
|
||||||
(SysMenu::getOrderno);
|
(SysMenu::getOrderno);
|
||||||
List<Map<String, Object>> listAll =
|
List<Map<String, Object>> mapList =
|
||||||
sysMenuMapper.selectMaps(queryWrapper);
|
sysMenuMapper.selectMaps(queryWrapper);
|
||||||
|
List<Map<String, Object>> listAll = ObjectConverterUtil.convertMapFieldsToEntityFormat(SysMenu.class, mapList);
|
||||||
List<String> listRole =
|
List<String> listRole =
|
||||||
sysMenuMapper.selectMenuByRoleId(roleId);
|
sysMenuMapper.selectMenuByRoleId(roleId);
|
||||||
for (Map<String, Object> map : listAll) {
|
for (Map<String, Object> map : listAll) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import com.yfd.platform.system.mapper.SysOrganizationMapper;
|
|||||||
import com.yfd.platform.system.mapper.SysRoleMapper;
|
import com.yfd.platform.system.mapper.SysRoleMapper;
|
||||||
import com.yfd.platform.system.service.ISysOrganizationService;
|
import com.yfd.platform.system.service.ISysOrganizationService;
|
||||||
import com.yfd.platform.system.service.IUserService;
|
import com.yfd.platform.system.service.IUserService;
|
||||||
|
import com.yfd.platform.utils.ObjectConverterUtil;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@ -128,12 +129,15 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
|
|||||||
QueryWrapper<SysOrganization> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<SysOrganization> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq("parentid", parentid); //根据上级id 查询
|
queryWrapper.eq("parentid", parentid); //根据上级id 查询
|
||||||
listMap = this.listMaps(queryWrapper.orderByAsc("orgcode"));
|
listMap = this.listMaps(queryWrapper.orderByAsc("orgcode"));
|
||||||
if (!listMap.isEmpty()) { //判断是否存在子集
|
|
||||||
for (int i = 0; i < listMap.size(); i++) { //遍历表数据
|
if (!listMap.isEmpty()) {
|
||||||
|
List<Map<String, Object>> mapList = ObjectConverterUtil.convertMapFieldsToEntityFormat(SysOrganization.class, listMap);//判断是否存在子集
|
||||||
|
for (int i = 0; i < mapList.size(); i++) { //遍历表数据
|
||||||
List<Map<String, Object>> childList =
|
List<Map<String, Object>> childList =
|
||||||
child(listMap.get(i).get("id").toString()); //循环获取下一子集
|
child(mapList.get(i).get("id").toString()); //循环获取下一子集
|
||||||
listMap.get(i).put("childList", childList); //添加新列 子集
|
mapList.get(i).put("childList", childList); //添加新列 子集
|
||||||
}
|
}
|
||||||
|
return mapList;
|
||||||
}
|
}
|
||||||
return listMap;
|
return listMap;
|
||||||
}
|
}
|
||||||
@ -161,10 +165,12 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
|
|||||||
queryWrapper.eq("parentid", "0");
|
queryWrapper.eq("parentid", "0");
|
||||||
}
|
}
|
||||||
List<Object> max = this.listObjs(queryWrapper);
|
List<Object> max = this.listObjs(queryWrapper);
|
||||||
//判断查询是否存在 存在转换成int类型并给codeMax替换值
|
//判断查询是否存在 存在转换成 int 类型并给 codeMax 替换值
|
||||||
if (max.size() > 0) {
|
if (!max.isEmpty() && max.getFirst() != null) {
|
||||||
codeMax =
|
String maxValue = max.getFirst().toString();
|
||||||
Integer.parseInt(max.get(0).toString().substring(max.get(0).toString().length() - 2));
|
if (maxValue.length() >= 2) {
|
||||||
|
codeMax = Integer.parseInt(maxValue.substring(maxValue.length() - 2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//2位数字编号
|
//2位数字编号
|
||||||
DecimalFormat df = new DecimalFormat("00");
|
DecimalFormat df = new DecimalFormat("00");
|
||||||
|
|||||||
@ -49,8 +49,14 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
|||||||
QueryWrapper<SysRole> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<SysRole> queryWrapper = new QueryWrapper<>();
|
||||||
List<Object> max = this.listObjs(queryWrapper.select("MAX(rolecode) " +
|
List<Object> max = this.listObjs(queryWrapper.select("MAX(rolecode) " +
|
||||||
"rolecode"));// 查询最大的编号
|
"rolecode"));// 查询最大的编号
|
||||||
if (max.size() > 0) {
|
// 存在转换成 int 类型并给 codeMax 替换值
|
||||||
codeMax = Integer.parseInt(max.get(0).toString());//判断查询是否存在
|
if (!max.isEmpty() && max.getFirst() != null) {
|
||||||
|
try {
|
||||||
|
codeMax = Integer.parseInt(max.getFirst().toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 如果转换失败,保持默认值 0
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 存在转换成int类型并给codeMax替换值
|
// 存在转换成int类型并给codeMax替换值
|
||||||
String code = df.format(codeMax + 1); // 最大编号累加
|
String code = df.format(codeMax + 1); // 最大编号累加
|
||||||
@ -160,4 +166,9 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SysRole> selectRoleList(String rolename) {
|
||||||
|
return roleMapper.selectRoleList(rolename);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@ -224,46 +225,79 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
|
|||||||
*roleids 角色id
|
*roleids 角色id
|
||||||
* 返回值说明: 是否更新成功
|
* 返回值说明: 是否更新成功
|
||||||
************************************/
|
************************************/
|
||||||
@Override
|
// ... existing code ...
|
||||||
public Map updateById(SysUser sysUser, String roleids) {
|
|
||||||
//返回信息
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
//获取当前用户 最近修改者替换
|
|
||||||
sysUser.setLastmodifier(getUsername());
|
|
||||||
//获取当前时间 最近修改日期替换
|
|
||||||
sysUser.setLastmodifydate(new Timestamp(System.currentTimeMillis()));
|
|
||||||
//根据修改
|
|
||||||
boolean ok = this.updateById(sysUser);
|
|
||||||
if (ok) {
|
|
||||||
if (StrUtil.isNotEmpty(roleids)) {
|
|
||||||
String[] roles = roleids.split(",");
|
|
||||||
List<String> list = sysUserMapper.getRoleid(sysUser.getId());
|
|
||||||
for (String role : roles) {
|
|
||||||
if (!list.contains(role)) {
|
|
||||||
//系统生成id
|
|
||||||
String id = IdUtil.fastSimpleUUID();
|
|
||||||
//新增sys_role_users表数据
|
|
||||||
ok = ok && sysUserMapper.addUserRoles(id, role,
|
|
||||||
sysUser.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//删除不包含的角色
|
|
||||||
sysUserMapper.delInRoleUsersByUserid(sysUser.getId(), roles);
|
|
||||||
|
|
||||||
} else {
|
@Override
|
||||||
//根据用户id 删除该用户角色关联
|
@Transactional(rollbackFor = Exception.class)
|
||||||
ok = ok && sysUserMapper.delRoleUsersByUserid(sysUser.getId());
|
public Map updateById(SysUser sysUser, String roleids) {
|
||||||
|
Map<String, String> result = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 设置修改信息
|
||||||
|
String currentUsername = getUsername();
|
||||||
|
Timestamp currentTime = new Timestamp(System.currentTimeMillis());
|
||||||
|
sysUser.setLastmodifier(currentUsername);
|
||||||
|
sysUser.setLastmodifydate(currentTime);
|
||||||
|
|
||||||
|
// 更新用户信息
|
||||||
|
boolean ok = this.updateById(sysUser);
|
||||||
|
if (!ok) {
|
||||||
|
result.put("status", "error");
|
||||||
|
result.put("msg", "用户信息修改失败!");
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理角色分配
|
||||||
|
String userId = sysUser.getId();
|
||||||
|
if (StrUtil.isNotEmpty(roleids)) {
|
||||||
|
handleUserRoles(userId, roleids);
|
||||||
|
} else {
|
||||||
|
// 清空所有角色
|
||||||
|
sysUserMapper.delRoleUsersByUserid(userId);
|
||||||
|
}
|
||||||
|
|
||||||
result.put("status", "sucess");
|
result.put("status", "sucess");
|
||||||
result.put("msg", "用户信息修改成功!");
|
result.put("msg", "用户信息修改成功!");
|
||||||
} else {
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新用户信息失败", e);
|
||||||
result.put("status", "error");
|
result.put("status", "error");
|
||||||
result.put("msg", "用户信息修改失败!");
|
result.put("msg", "操作失败:" + e.getMessage());
|
||||||
|
throw e; // 抛出异常,触发事务回滚
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理用户角色分配(增量更新)
|
||||||
|
* @param userId 用户 ID
|
||||||
|
* @param roleIds 角色 ID 字符串(逗号分隔)
|
||||||
|
*/
|
||||||
|
private void handleUserRoles(String userId, String roleIds) {
|
||||||
|
// 获取用户当前角色
|
||||||
|
List<String> currentRoles = sysUserMapper.getRoleid(userId);
|
||||||
|
Set<String> currentRoleSet = new HashSet<>(currentRoles != null ? currentRoles : Collections.emptyList());
|
||||||
|
|
||||||
|
// 解析新角色列表
|
||||||
|
String[] newRoles = roleIds.split(",");
|
||||||
|
Set<String> newRoleSet = new HashSet<>(Arrays.asList(newRoles));
|
||||||
|
|
||||||
|
// 需要新增的角色(新角色 - 当前角色)
|
||||||
|
for (String roleId : newRoles) {
|
||||||
|
if (!currentRoleSet.contains(roleId)) {
|
||||||
|
String id = IdUtil.fastSimpleUUID();
|
||||||
|
sysUserMapper.addUserRoles(id, roleId, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 需要删除的角色(当前角色 - 新角色)
|
||||||
|
sysUserMapper.delInRoleUsersByUserid(userId, newRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... existing code ...
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map getOneById(String id) {
|
public Map getOneById(String id) {
|
||||||
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
|
||||||
@ -476,20 +510,16 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<Map<String, Object>> queryUsers(String orgid,
|
public Page<SysUser> queryUsers(String orgid,
|
||||||
String username,
|
String username,
|
||||||
Page<SysUser> page) {
|
Page<SysUser> page) {
|
||||||
Page<Map<String, Object>> mapPage = sysUserMapper.queryUsers(orgid,
|
Page<SysUser> mapPage = sysUserMapper.queryUsers(orgid,
|
||||||
username, page);
|
username, page);
|
||||||
List<Map<String, Object>> list = new ArrayList<>();
|
;mapPage.getRecords().forEach(record -> {
|
||||||
List<Map<String, Object>> records = mapPage.getRecords();
|
String id = record.getId();
|
||||||
for (Map<String, Object> record : records) {
|
|
||||||
String id = (String) record.get("id");
|
|
||||||
List<SysRole> sysRoles = sysRoleMapper.getRoleByUserId(id);
|
List<SysRole> sysRoles = sysRoleMapper.getRoleByUserId(id);
|
||||||
record.put("roles", sysRoles);
|
record.setRoles(sysRoles);
|
||||||
list.add(record);
|
});
|
||||||
}
|
|
||||||
mapPage.setRecords(list);
|
|
||||||
return mapPage;
|
return mapPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
DISTINCT
|
DISTINCT
|
||||||
m.id,
|
m.id,
|
||||||
m.parentid,
|
m.parentid,
|
||||||
m.`name`,
|
m.name,
|
||||||
m.opturl,
|
m.opturl,
|
||||||
m.icon,
|
m.icon,
|
||||||
m.orderno,
|
m.orderno,
|
||||||
|
|||||||
@ -91,7 +91,15 @@
|
|||||||
<!--根据用户id获取角色信息-->
|
<!--根据用户id获取角色信息-->
|
||||||
<select id="getRoleByUserId"
|
<select id="getRoleByUserId"
|
||||||
resultType="com.yfd.platform.system.domain.SysRole">
|
resultType="com.yfd.platform.system.domain.SysRole">
|
||||||
SELECT r.id,r.rolename,r.orgscope FROM sys_role_users ru INNER JOIN sys_role r ON ru.roleid =r.id WHERE ru.userid =#{id}
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.rolename,
|
||||||
|
r.orgscope
|
||||||
|
FROM
|
||||||
|
sys_role_users ru
|
||||||
|
INNER JOIN sys_role r ON ru.roleid = r.id
|
||||||
|
WHERE
|
||||||
|
ru.userid =#{id}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!--根据角色id获取用户id-->
|
<!--根据角色id获取用户id-->
|
||||||
@ -111,6 +119,20 @@
|
|||||||
FROM sys_role_users
|
FROM sys_role_users
|
||||||
WHERE userid = #{id}
|
WHERE userid = #{id}
|
||||||
</select>
|
</select>
|
||||||
|
<!--查询角色列表(Oracle 兼容版)-->
|
||||||
|
<select id="selectRoleList" resultType="com.yfd.platform.system.domain.SysRole">
|
||||||
|
SELECT r.id, r.rolecode, r.rolename, r."LEVEL", r.description,
|
||||||
|
r.orgscope, r.optscope, r.busscope, r.isvaild,
|
||||||
|
r.lastmodifier, r.lastmodifydate, r.custom1, r.custom2, r.custom3
|
||||||
|
FROM sys_role r
|
||||||
|
<where>
|
||||||
|
<if test="rolename != null and rolename != ''">
|
||||||
|
AND r.rolename LIKE '%' || #{rolename} || '%'
|
||||||
|
</if>
|
||||||
|
AND r."LEVEL" != '1'
|
||||||
|
</where>
|
||||||
|
ORDER BY r."LEVEL" ASC, lastmodifydate ASC
|
||||||
|
</select>
|
||||||
<!--根据 角色id和用户id 删除系统角色用户对照 (admin除外)-->
|
<!--根据 角色id和用户id 删除系统角色用户对照 (admin除外)-->
|
||||||
<delete id="deleteRoleUsers">
|
<delete id="deleteRoleUsers">
|
||||||
delete from sys_role_users where userid !=(select u.id from sys_user u where u.account="admin") and roleid=#{roleid} and userid=#{urserid}
|
delete from sys_role_users where userid !=(select u.id from sys_user u where u.account="admin") and roleid=#{roleid} and userid=#{urserid}
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
<!--用户分配角色 系统角色用户对照新增数据-->
|
<!--用户分配角色 系统角色用户对照新增数据-->
|
||||||
<insert id="addUserRoles">
|
<insert id="addUserRoles">
|
||||||
insert into sys_role_users value (#{id},#{roleid},#{userid})
|
insert into sys_role_users values (#{id},#{roleid},#{userid})
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<!--根据用户id 和角色id 查询 系统角色用户对照表-->
|
<!--根据用户id 和角色id 查询 系统角色用户对照表-->
|
||||||
@ -47,11 +47,13 @@
|
|||||||
select roleid from sys_role_users where userid=#{userid}
|
select roleid from sys_role_users where userid=#{userid}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!--根据用户表id查询角色表最大级别-->
|
<!--根据用户表 id 查询角色表最大级别-->
|
||||||
<select id="getMaxLevel" resultType="String">
|
<select id="getMaxLevel" resultType="String">
|
||||||
select min(level) from sys_role where id in (select roleid from sys_role_users where userid=#{userid})
|
SELECT MIN(r."LEVEL")
|
||||||
|
FROM sys_role r
|
||||||
|
WHERE r.id IN ( SELECT roleid FROM sys_role_users WHERE userid = #{userId} )
|
||||||
</select>
|
</select>
|
||||||
<select id="queryUsers" resultType="java.util.Map">
|
<select id="queryUsers" resultType="com.yfd.platform.system.domain.SysUser">
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
u.id,
|
u.id,
|
||||||
u.usertype,
|
u.usertype,
|
||||||
@ -62,7 +64,7 @@
|
|||||||
u.phone,
|
u.phone,
|
||||||
u.avatar,
|
u.avatar,
|
||||||
u.orgid,
|
u.orgid,
|
||||||
u.`status`,
|
u.status,
|
||||||
u.lastmodifier,
|
u.lastmodifier,
|
||||||
u.lastmodifydate
|
u.lastmodifydate
|
||||||
FROM
|
FROM
|
||||||
@ -73,7 +75,7 @@
|
|||||||
and u.orgid = #{orgid}
|
and u.orgid = #{orgid}
|
||||||
</if>
|
</if>
|
||||||
<if test="username != null">
|
<if test="username != null">
|
||||||
and u.username like concat('%', #{username},'%')
|
and u.username LIKE '%' || #{username} || '%'
|
||||||
</if>
|
</if>
|
||||||
ORDER BY u.lastmodifydate DESC
|
ORDER BY u.lastmodifydate DESC
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/logo.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="NewFrameWork2023-WEB" />
|
<meta name="description" content="NewFrameWork2023-WEB" />
|
||||||
<meta name="keywords" content="NewFrameWork2023-WEB" />
|
<meta name="keywords" content="NewFrameWork2023-WEB" />
|
||||||
|
|||||||
@ -10,13 +10,16 @@
|
|||||||
"prettier": "prettier --write ."
|
"prettier": "prettier --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
"@element-plus/icons-vue": "^2.0.10",
|
"@element-plus/icons-vue": "^2.0.10",
|
||||||
"@types/js-cookie": "^3.0.2",
|
"@types/js-cookie": "^3.0.2",
|
||||||
"@vueuse/core": "^9.1.1",
|
"@vueuse/core": "^9.1.1",
|
||||||
"@wangeditor/editor": "^5.0.0",
|
"@wangeditor/editor": "^5.0.0",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
|
"ant-design-vue": "^4.2.6",
|
||||||
"axios": "^1.2.0",
|
"axios": "^1.2.0",
|
||||||
"better-scroll": "^2.4.2",
|
"better-scroll": "^2.4.2",
|
||||||
|
"dayjs": "^1.11.20",
|
||||||
"default-passive-events": "^2.0.0",
|
"default-passive-events": "^2.0.0",
|
||||||
"echarts": "^5.2.2",
|
"echarts": "^5.2.2",
|
||||||
"element-plus": "^2.2.27",
|
"element-plus": "^2.2.27",
|
||||||
@ -63,6 +66,5 @@
|
|||||||
},
|
},
|
||||||
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
|
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
|
||||||
"author": "有来开源组织",
|
"author": "有来开源组织",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"__npminstall_done": false
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.2 KiB |
BIN
frontend/public/logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElConfigProvider } from 'element-plus';
|
import { ElConfigProvider } from 'element-plus';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore,usetTheme } from '@/store/modules/app';
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-config-provider :locale="appStore.locale" :size="appStore.size">
|
<el-config-provider :locale="appStore.locale" :size="appStore.size">
|
||||||
<router-view />
|
<a-config-provider :theme="usetTheme">
|
||||||
|
<router-view />
|
||||||
|
</a-config-provider>
|
||||||
</el-config-provider>
|
</el-config-provider>
|
||||||
</template>
|
</template>
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import request from '@/utils/request';
|
|
||||||
|
|
||||||
export function getMessageList(params:any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/message/getMessageList',
|
|
||||||
method: 'get',
|
|
||||||
params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export function setMessageStatus(data:any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/message/setMessageStatus?id=' + data,
|
|
||||||
method: 'post'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export function setAllMessageStatus() {
|
|
||||||
return request({
|
|
||||||
url: '/system/message/setAllMessageStatus',
|
|
||||||
method: 'post'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export function deleteMessageById(data:any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/message/deleteMessageById?id=' + data,
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
import request from '@/utils/request';
|
|
||||||
|
|
||||||
//获取表格内容
|
|
||||||
export function getTaskList(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/getQuartzJobList',
|
|
||||||
method: 'get',
|
|
||||||
params: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//新增表格内容
|
|
||||||
export function addTaskList(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/addQuartzJob',
|
|
||||||
method: 'post',
|
|
||||||
data: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//删除定时任务
|
|
||||||
export function delTaskList(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/deleteQuartzJob',
|
|
||||||
method: 'post',
|
|
||||||
params: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//修改定时任务
|
|
||||||
export function updataTaskList(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/updateQuartzJob',
|
|
||||||
method: 'post',
|
|
||||||
data: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//定时任务是否有效
|
|
||||||
export function setTaskList(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/setQuartzStatus',
|
|
||||||
method: 'post',
|
|
||||||
params: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//拖拽
|
|
||||||
export function changeItemOrder(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/changeDictOrder',
|
|
||||||
method: 'post',
|
|
||||||
params: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
BIN
frontend/src/assets/components/arrow-down.png
Normal file
|
After Width: | Height: | Size: 238 B |
BIN
frontend/src/assets/components/arrow-left.png
Normal file
|
After Width: | Height: | Size: 205 B |
BIN
frontend/src/assets/components/arrow-right.png
Normal file
|
After Width: | Height: | Size: 215 B |
BIN
frontend/src/assets/components/arrow-up.png
Normal file
|
After Width: | Height: | Size: 231 B |
1
frontend/src/assets/components/bg-toggle.e1dabcf3.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="16" height="88" xmlns="http://www.w3.org/2000/svg"><path d="M16 0a22.397 22.397 0 0 1-8.71 12.793l-2.265 1.618A12 12 0 0 0 0 24.175v39.65a12 12 0 0 0 5.025 9.764l2.265 1.618c4.2 3 7.23 7.356 8.588 12.325L16 88V0Z" fill="#E5EDF3" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 268 B |
1
frontend/src/assets/components/fgx.svg
Normal file
|
After Width: | Height: | Size: 24 KiB |
1
frontend/src/assets/icons/menuActiveBg.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="106" height="51" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-32.8%" y="-233.3%" width="165.6%" height="566.7%" filterUnits="objectBoundingBox" id="a"><feGaussianBlur stdDeviation="7" in="SourceGraphic"/></filter><filter x="-23.7%" y="-289.3%" width="147.4%" height="678.6%" filterUnits="objectBoundingBox" id="b"><feGaussianBlur stdDeviation="3" in="SourceGraphic"/></filter><filter x="-4.7%" y="-300%" width="109.4%" height="700%" filterUnits="objectBoundingBox" id="d"><feGaussianBlur stdDeviation="1" in="SourceGraphic"/></filter><linearGradient x1="100%" y1="50%" x2="0%" y2="50%" id="c"><stop stop-color="#37FDF7" stop-opacity="0" offset="0%"/><stop stop-color="#37FDF7" offset="51.347%"/><stop stop-color="#37FDF7" stop-opacity="0" offset="100%"/></linearGradient></defs><g transform="translate(21 21)" fill="none" fill-rule="evenodd"><ellipse fill="#36FCF6" filter="url(#a)" cx="32" cy="4.5" rx="32" ry="4.5"/><ellipse fill="#11FFF2" filter="url(#b)" cx="32" cy="2" rx="19" ry="1.556"/><path fill="url(#c)" filter="url(#d)" d="M0 1h64v1H0z"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 171 B |
BIN
frontend/src/assets/images/login-bg.jpg
Normal file
|
After Width: | Height: | Size: 673 KiB |
BIN
frontend/src/assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
frontend/src/assets/images/map-dixingtu.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
frontend/src/assets/images/map-shiliangtu.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
frontend/src/assets/images/map-yingxiangtu.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
frontend/src/assets/images/nineSections-dixing.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
frontend/src/assets/images/nineSections-shiliang.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
frontend/src/assets/images/nineSections-yingxiang.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 569 B |
|
Before Width: | Height: | Size: 409 B |
|
Before Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
107
frontend/src/components/BaseLayerSwitcher/index.vue
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<div class="baselayer-switcher">
|
||||||
|
<div
|
||||||
|
class="switcher-item"
|
||||||
|
v-for="item in data"
|
||||||
|
:key="item.name"
|
||||||
|
:class="{ active: item.name === activeLayer }"
|
||||||
|
@click="activeLayer = item.name"
|
||||||
|
>
|
||||||
|
<img :src="item.img" alt="" />
|
||||||
|
<div class="label">{{ item.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="nineSectionsImg">
|
||||||
|
<img :src="nineSectionsImg" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onMounted } from "vue";
|
||||||
|
import mapShiliangtu from "@/assets/images/map-shiliangtu.png";
|
||||||
|
import mapDixingtu from "@/assets/images/map-dixingtu.png";
|
||||||
|
import mapYingxiangtu from "@/assets/images/map-yingxiangtu.png";
|
||||||
|
import nineSectionsShiliang from "@/assets/images/nineSections-shiliang.png";
|
||||||
|
import nineSectionsDixing from "@/assets/images/nineSections-dixing.png";
|
||||||
|
import nineSectionsYingxiang from "@/assets/images/nineSections-yingxiang.png";
|
||||||
|
const data = ref([
|
||||||
|
{ name: "矢量", img: mapShiliangtu },
|
||||||
|
{ name: "地形", img: mapDixingtu },
|
||||||
|
{ name: "影像", img: mapYingxiangtu },
|
||||||
|
]);
|
||||||
|
const nineSectionsImg = ref(nineSectionsShiliang);
|
||||||
|
const nineSectionsData = ref([
|
||||||
|
{ name: "矢量", img: nineSectionsShiliang },
|
||||||
|
{ name: "地形", img: nineSectionsDixing },
|
||||||
|
{ name: "影像", img: nineSectionsYingxiang },
|
||||||
|
]);
|
||||||
|
const activeLayer = ref("矢量");
|
||||||
|
|
||||||
|
watch(activeLayer, (val) => {
|
||||||
|
nineSectionsImg.value =
|
||||||
|
nineSectionsData.value.find((item) => item.name === val)?.img || "";
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.baselayer-switcher {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 480px;
|
||||||
|
z-index: 200;
|
||||||
|
.switcher-item {
|
||||||
|
background: #d8d8d8;
|
||||||
|
border-radius: 2px;
|
||||||
|
width: 85px;
|
||||||
|
height: 60px;
|
||||||
|
display: none;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 2px 3px;
|
||||||
|
background-color: #00000059;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 2px 0 0 2px / 2px 0px 0px 2px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid #3a7098;
|
||||||
|
.label {
|
||||||
|
background-color: #005293;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.switcher-item:not(:first-child) {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
.active {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid #3a7098;
|
||||||
|
.label {
|
||||||
|
background-color: #005293;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.switcher-item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.nineSectionsImg {
|
||||||
|
position: absolute;
|
||||||
|
right: 56px;
|
||||||
|
bottom: 92px;
|
||||||
|
border: 1px solid grey;
|
||||||
|
pointer-events: none;
|
||||||
|
img {
|
||||||
|
max-width: 100px;
|
||||||
|
width: 100px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,105 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-breadcrumb
|
|
||||||
separator-class="el-icon-arrow-right"
|
|
||||||
class="h-[50px] flex items-center"
|
|
||||||
>
|
|
||||||
<transition-group name="breadcrumb">
|
|
||||||
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
|
|
||||||
<span
|
|
||||||
v-if="
|
|
||||||
item.redirect === 'noredirect' || index === breadcrumbs.length - 1
|
|
||||||
"
|
|
||||||
class="text-[#97a8be]"
|
|
||||||
>{{ generateTitle(item.meta.title) }}</span
|
|
||||||
>
|
|
||||||
<a v-else @click.prevent="handleLink(item)">
|
|
||||||
{{ generateTitle(item.meta.title) }}
|
|
||||||
</a>
|
|
||||||
</el-breadcrumb-item>
|
|
||||||
</transition-group>
|
|
||||||
</el-breadcrumb>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onBeforeMount, ref, watch } from 'vue';
|
|
||||||
import { useRoute, RouteLocationMatched } from 'vue-router';
|
|
||||||
import { compile } from 'path-to-regexp';
|
|
||||||
import router from '@/router';
|
|
||||||
import { generateTitle } from '@/utils/i18n';
|
|
||||||
|
|
||||||
const currentRoute = useRoute();
|
|
||||||
const pathCompile = (path: string) => {
|
|
||||||
const { params } = currentRoute;
|
|
||||||
const toPath = compile(path);
|
|
||||||
return toPath(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
const breadcrumbs = ref([] as Array<RouteLocationMatched>);
|
|
||||||
|
|
||||||
function getBreadcrumb() {
|
|
||||||
let matched = currentRoute.matched.filter(
|
|
||||||
item => item.meta && item.meta.title
|
|
||||||
);
|
|
||||||
const first = matched[0];
|
|
||||||
if (!isDashboard(first)) {
|
|
||||||
matched = [
|
|
||||||
{ path: '/dashboard', meta: { title: 'dashboard' } } as any
|
|
||||||
].concat(matched);
|
|
||||||
}
|
|
||||||
breadcrumbs.value = matched.filter(item => {
|
|
||||||
return item.meta && item.meta.title && item.meta.breadcrumb !== false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDashboard(route: RouteLocationMatched) {
|
|
||||||
const name = route && route.name;
|
|
||||||
if (!name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
name.toString().trim().toLocaleLowerCase() ===
|
|
||||||
'Dashboard'.toLocaleLowerCase()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLink(item: any) {
|
|
||||||
const { redirect, path } = item;
|
|
||||||
if (redirect) {
|
|
||||||
router.push(redirect).catch(err => {
|
|
||||||
console.warn(err);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
router.push(pathCompile(path)).catch(err => {
|
|
||||||
console.warn(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => currentRoute.path,
|
|
||||||
path => {
|
|
||||||
if (path.startsWith('/redirect/')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getBreadcrumb();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
|
||||||
getBreadcrumb();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.app-breadcrumb.el-breadcrumb {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 50px;
|
|
||||||
margin-left: 8px;
|
|
||||||
|
|
||||||
.no-redirect {
|
|
||||||
color: #97a8be;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a
|
|
||||||
href="https://github.com/hxrui"
|
|
||||||
target="_blank"
|
|
||||||
class="github-corner"
|
|
||||||
aria-label="View source on Github"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
width="80"
|
|
||||||
height="80"
|
|
||||||
viewBox="0 0 250 250"
|
|
||||||
style="fill: #40c9c6; color: #fff"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
|
|
||||||
<path
|
|
||||||
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
|
|
||||||
fill="currentColor"
|
|
||||||
style="transform-origin: 130px 106px"
|
|
||||||
class="octo-arm"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
|
|
||||||
fill="currentColor"
|
|
||||||
class="octo-body"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.github-corner:hover .octo-arm {
|
|
||||||
animation: octocat-wave 560ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes octocat-wave {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
transform: rotate(0);
|
|
||||||
}
|
|
||||||
20%,
|
|
||||||
60% {
|
|
||||||
transform: rotate(-25deg);
|
|
||||||
}
|
|
||||||
40%,
|
|
||||||
80% {
|
|
||||||
transform: rotate(10deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
.github-corner:hover .octo-arm {
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
.github-corner .octo-arm {
|
|
||||||
animation: octocat-wave 560ms ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
@click="toggleClick"
|
|
||||||
class="px-[15px] hover:bg-gray-50 cursor-pointer h-[50px] leading-[50px] text-center fixed bottom-2 "
|
|
||||||
style="z-index:1005;display: flex;display: -webkit-flex; justify-content: center; align-items: center; -webkit-justify-content: center; -webkit-align-items: center;"
|
|
||||||
:style="appStore.sidebar.opened?'left:80px': 'left:2px'"
|
|
||||||
>
|
|
||||||
<img v-if="isActive" src="@/assets/MenuIcon/dh_sq.png" alt="">
|
|
||||||
<img v-else src="@/assets/MenuIcon/dh_sq1.png" alt="">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
const appStore = useAppStore();
|
|
||||||
defineProps({
|
|
||||||
isActive: {
|
|
||||||
required: true,
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(['toggleClick']);
|
|
||||||
|
|
||||||
function toggleClick() {
|
|
||||||
emit('toggleClick');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.hamburger {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
fill: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hamburger.is-active {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
109
frontend/src/components/RightDrawer/index.vue
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="rightContentDrawer">
|
||||||
|
<div @click="handleToggle" class="drawerController1" v-if="!drawerOpen">
|
||||||
|
<img src="../../assets/components/arrow-left.png" alt="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 使用 Vue 的 Transition 组件控制动画 -->
|
||||||
|
<transition name="drawer-slide">
|
||||||
|
<a-drawer
|
||||||
|
:get-container="false"
|
||||||
|
:style="{ position: 'relative' }"
|
||||||
|
v-model:open="drawerOpen"
|
||||||
|
:mask="false"
|
||||||
|
placement="right"
|
||||||
|
width="450"
|
||||||
|
:closable="false"
|
||||||
|
:headerStyle="{ color: '#FAFCFE' }">
|
||||||
|
<div @click="handleToggle" class="drawerController">
|
||||||
|
<img src="../../assets/components/arrow-right.png" alt="">
|
||||||
|
</div>
|
||||||
|
<div style="padding:16px 8px 0;" class="text_she">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</a-drawer>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineOptions } from 'vue';
|
||||||
|
|
||||||
|
// 定义组件名 (便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'rightDrawer'
|
||||||
|
});
|
||||||
|
const drawerOpen = ref(true);
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
drawerOpen.value = !drawerOpen.value;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.rightContentDrawer {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.drawerController1 {
|
||||||
|
width: 18px;
|
||||||
|
position: absolute;
|
||||||
|
right: -2px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
height: 88px;
|
||||||
|
line-height: 88px;
|
||||||
|
top: 45%;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-image: url(../../assets/components/bg-toggle.e1dabcf3.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ant-drawer {
|
||||||
|
margin: 5px 0px;
|
||||||
|
|
||||||
|
.ant-drawer-content{
|
||||||
|
overflow:visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-drawer-content-wrapper {
|
||||||
|
border: 2px solid #c5d6e2 !important;
|
||||||
|
box-shadow: 3px 3px 3px 9px #e5edf3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-drawer-body {
|
||||||
|
padding: 0px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawerController {
|
||||||
|
width: 18px;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
height: 88px;
|
||||||
|
line-height: 88px;
|
||||||
|
top: 45%;
|
||||||
|
left: -18px;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-image: url(../../assets/components/bg-toggle.e1dabcf3.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.text_she{
|
||||||
|
font-size: 14px;
|
||||||
|
color:#262626;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -3,14 +3,11 @@ import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|||||||
|
|
||||||
import { addClass, removeClass } from '@/utils/index';
|
import { addClass, removeClass } from '@/utils/index';
|
||||||
|
|
||||||
// import { useSettingsStore } from '@/store/modules/settings';
|
|
||||||
|
|
||||||
// 图标依赖
|
// 图标依赖
|
||||||
import { Close, Setting } from '@element-plus/icons-vue';
|
import { Close, Setting } from '@element-plus/icons-vue';
|
||||||
import { ElColorPicker } from 'element-plus';
|
import { ElColorPicker } from 'element-plus';
|
||||||
|
|
||||||
// const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
@ -32,21 +29,6 @@ watch(show, value => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function addEventClick() {
|
function addEventClick() {
|
||||||
window.addEventListener('click', closeSidebar, { passive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSidebar(evt: any) {
|
|
||||||
// 主题选择点击不关闭
|
|
||||||
let parent = evt.target.closest('.theme-picker-dropdown');
|
|
||||||
if (parent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent = evt.target.closest('.right-panel');
|
|
||||||
if (!parent) {
|
|
||||||
show.value = false;
|
|
||||||
window.removeEventListener('click', closeSidebar);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rightPanel = ref(ElColorPicker);
|
const rightPanel = ref(ElColorPicker);
|
||||||
@ -92,7 +74,6 @@ onBeforeUnmount(() => {
|
|||||||
.showRightPanel {
|
.showRightPanel {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: calc(100% - 15px);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
264
frontend/src/components/SidePanelItem/index.vue
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="qgc-side-pannel-item">
|
||||||
|
<div class="qgc_title">
|
||||||
|
<div class="title_left">
|
||||||
|
<span>{{ title }}</span>
|
||||||
|
<span v-if="prompt.show" class="title_icon">
|
||||||
|
<a-tooltip placement="top" :title="prompt.value" :get-popup-container="getPopupContainer">
|
||||||
|
<QuestionCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<span v-if="clickprompt.show" class="title_icon">
|
||||||
|
<a-tooltip placement="top" trigger="click" :title="clickprompt.value"
|
||||||
|
:get-popup-container="getPopupContainer">
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="title_right">
|
||||||
|
<div v-if="select.show">
|
||||||
|
<a-select v-model:value="selectValue" show-search placeholder="请选择" style="width: 142px"
|
||||||
|
:options="select.options" :filter-option="filterOption" @focus="handleFocus" @blur="handleBlur"
|
||||||
|
@change="handleChange"></a-select>
|
||||||
|
</div>
|
||||||
|
<div v-if="shrink" class="title_shrink" @click="isExpand = !isExpand">
|
||||||
|
<img v-if="isExpand" src="@/assets/components/arrow-up.png" alt="">
|
||||||
|
<img v-else src="@/assets/components/arrow-down.png" alt="">
|
||||||
|
</div>
|
||||||
|
<div v-if="moreSelect.show">
|
||||||
|
<a-tree-select v-model:value="moreSelectValue" show-search style="width: 170px"
|
||||||
|
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="Please select"
|
||||||
|
allow-clear tree-default-expand-all :tree-data="moreSelect.options"
|
||||||
|
tree-node-filter-prop="label">
|
||||||
|
</a-tree-select>
|
||||||
|
</div>
|
||||||
|
<div v-if="datetimePicker.show">
|
||||||
|
<!-- 添加 locale 属性来设置语言 -->
|
||||||
|
<a-date-picker v-model:value="datetimeValue" show-time
|
||||||
|
:format="datetimePicker.format !== null ? datetimePicker.format : undefined"
|
||||||
|
:picker="datetimePicker.picker" placeholder="请选择时间" style="width: 180px"
|
||||||
|
@change="handleDateTimeChange" :locale="locale" />
|
||||||
|
<!-- 修改为 locale 变量 -->
|
||||||
|
</div>
|
||||||
|
<div v-if="scopeDate.show">
|
||||||
|
<a-range-picker v-model:value="scopeDateValue" :locale="locale" :picker="scopeDate.picker"
|
||||||
|
:format="'YYYY-MM-DD'" :range-separator="' 至 '" style="width: 200px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<slot v-if="isExpand" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import {
|
||||||
|
QuestionCircleOutlined,
|
||||||
|
InfoCircleOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import type { SelectProps } from 'ant-design-vue';
|
||||||
|
// 引入中文语言包
|
||||||
|
import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
||||||
|
// 导入 dayjs
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import 'dayjs/locale/zh-cn'; // 必须引入
|
||||||
|
dayjs.locale('zh-cn'); // 设置全局语言
|
||||||
|
console.log(dayjs().format('MMMM'));
|
||||||
|
const locale = ref(zhCN);
|
||||||
|
|
||||||
|
// 定义类型接口
|
||||||
|
interface PromptConfig {
|
||||||
|
show: boolean;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SelectConfig {
|
||||||
|
picker: any;
|
||||||
|
format: any;
|
||||||
|
show: boolean;
|
||||||
|
value: string | undefined;
|
||||||
|
options: SelectProps['options'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'SidePanelItem'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义props
|
||||||
|
const props = defineProps({
|
||||||
|
title: { // 标题
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
shrink: { // 是否显示收缩
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
prompt: { // 浮动提示
|
||||||
|
type: Object as () => PromptConfig,
|
||||||
|
default: () => ({
|
||||||
|
show: false,
|
||||||
|
value: '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clickprompt: { // 点击提示
|
||||||
|
type: Object as () => PromptConfig,
|
||||||
|
default: () => ({
|
||||||
|
show: false,
|
||||||
|
value: '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
select: { // 选择框
|
||||||
|
type: Object as () => SelectConfig,
|
||||||
|
default: () => ({
|
||||||
|
show: false,
|
||||||
|
value: undefined,
|
||||||
|
options: []
|
||||||
|
})
|
||||||
|
},
|
||||||
|
moreSelect: {//树选择框
|
||||||
|
type: Object as () => SelectConfig,
|
||||||
|
default: () => ({
|
||||||
|
show: false,
|
||||||
|
value: undefined,
|
||||||
|
options: []
|
||||||
|
})
|
||||||
|
},
|
||||||
|
datetimePicker: { // 时间选择框
|
||||||
|
type: Object as () => SelectConfig,
|
||||||
|
default: () => ({
|
||||||
|
show: false,
|
||||||
|
value: undefined,
|
||||||
|
format: null, //YYYY-MM-DD HH
|
||||||
|
picker: 'date' //date | week | month | quarter | year
|
||||||
|
})
|
||||||
|
},
|
||||||
|
scopeDate: { // 时间选择框
|
||||||
|
type: Object as () => SelectConfig,
|
||||||
|
default: () => ({
|
||||||
|
show: false,
|
||||||
|
value: undefined,
|
||||||
|
picker: 'month' //date | week | month | quarter | year
|
||||||
|
})
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isExpand = ref(true);
|
||||||
|
const selectValue = ref(props.select.value)
|
||||||
|
const moreSelectValue = ref(props.select.value)
|
||||||
|
const datetimeValue = ref<Dayjs | null>(props.datetimePicker.value ? dayjs(props.datetimePicker.value) : null);
|
||||||
|
const scopeDateValue = ref(props.scopeDate.value);
|
||||||
|
// // 定义 locale 变量
|
||||||
|
// const locale = zhCN;
|
||||||
|
|
||||||
|
// console.log(locale, "zhCN");
|
||||||
|
// 下拉选择框事件处理
|
||||||
|
const handleChange = (value: string) => {
|
||||||
|
console.log(`selected ${value}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
console.log('blur');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFocus = () => {
|
||||||
|
console.log('focus');
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterOption = (input: string, option?: { value: string }) => {
|
||||||
|
if (!option) return false;
|
||||||
|
return option.value.toLowerCase().includes(input.toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
// 文字提示容器
|
||||||
|
const getPopupContainer = (trigger: HTMLElement) => {
|
||||||
|
return trigger.parentElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
//时间选择框事件处理
|
||||||
|
const handleDateTimeChange = (date: any | null, dateString: string) => {
|
||||||
|
console.log('Selected DateTime:', date, dateString);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
console.log(props.select);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.qgc-side-pannel-item {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.qgc_title {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #e5edf3;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #2f6b98;
|
||||||
|
line-height: 36px;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 8px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
.title_shrink {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title_left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.title_icon {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title_right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qgc_title:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
left: 0;
|
||||||
|
width: 2px;
|
||||||
|
background-color: #005293;
|
||||||
|
top: 2px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
padding: 16px 0 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
height: calc(100% - 36px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-indent: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -16,8 +16,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// import { useSettingsStore } from '@/store/modules/settings';
|
|
||||||
// const settingsStore = useSettingsStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
141
frontend/src/components/developStatusChart/index.vue
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="basic_body">
|
||||||
|
<div ref="chartContainer" class="chart-container"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'developStatusChart'
|
||||||
|
});
|
||||||
|
|
||||||
|
const chartContainer = ref<HTMLDivElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
if (chartContainer.value) {
|
||||||
|
initChart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartContainer.value) return;
|
||||||
|
|
||||||
|
chartInstance = echarts.init(chartContainer.value);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{a} <br/>{b}: {c} ({d}%)',
|
||||||
|
// position: function (point, params, dom, rect, size) {
|
||||||
|
|
||||||
|
// // 固定在图表右侧显示,再往右挪一点
|
||||||
|
// return [size.viewSize.width - 120, point[1]];
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
bottom: '5%',
|
||||||
|
right: '5%',
|
||||||
|
orient: 'vertical', // 垂直排列
|
||||||
|
data: ['已建', '在建'],
|
||||||
|
itemWidth: 14,
|
||||||
|
itemHeight: 14,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 0
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 11
|
||||||
|
},
|
||||||
|
itemGap: 12 // 图例项之间的间距
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '建设状态',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['80%', '95%'], // 外环
|
||||||
|
center: ['35%', '50%'], // 整体向下移动一点
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 0,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 1
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false // 不显示外侧标签
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false // 不显示引导线
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
{ value: 80, name: '已建', itemStyle: { color: '#4CAF50' } },
|
||||||
|
{ value: 20, name: '在建', itemStyle: { color: '#2196F3' } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '中心圆',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['0%', '55%'], // 中心圆
|
||||||
|
center: ['35%', '50%'], // 与外环保持一致,整体向下移动
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 0,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 0
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
{ value: 80, name: '已建', itemStyle: { color: '#4CAF50' } },
|
||||||
|
{ value: 20, name: '在建', itemStyle: { color: '#2196F3' } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
|
||||||
|
// 监听窗口大小变化,重新调整图表大小
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.basic_body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 185px; // 容器宽度
|
||||||
|
height: 100px; // 容器高度
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
90
frontend/src/components/engEnvironmentData/index.vue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<!-- engEnvironmentData/index.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="eng-environment-data">
|
||||||
|
<div class="title">监测数据接入情况</div>
|
||||||
|
|
||||||
|
<div class="data-list">
|
||||||
|
<div class="data-item" v-for="item in dataList" :key="item.label">
|
||||||
|
<div class="item-content">
|
||||||
|
<span class="color-bar" :style="{ backgroundColor: item.color }"></span>
|
||||||
|
<span class="label">{{ item.label }}</span>
|
||||||
|
<span class="value" style=" color: #2f6b98 ">{{ item.value }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
|
||||||
|
// 定义组件名
|
||||||
|
defineOptions({
|
||||||
|
name: 'EngEnvironmentData'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 数据列表
|
||||||
|
const dataList = ref([
|
||||||
|
{
|
||||||
|
label: '大中型已建在建电站',
|
||||||
|
value: '707',
|
||||||
|
color: '#00b894'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '已接入电站运行数据',
|
||||||
|
value: '452',
|
||||||
|
color: '#0984e3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '已开展全过程监测工作',
|
||||||
|
value: '42',
|
||||||
|
color: '#6c5ce7'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.eng-environment-data {
|
||||||
|
padding: 0px 5px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-list {
|
||||||
|
.data-item {
|
||||||
|
border: 1px solid #edf2f8;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.item-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 4px;
|
||||||
|
|
||||||
|
.color-bar {
|
||||||
|
width: 1.6px;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
46
frontend/src/components/gis/GisView.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gis-view">
|
||||||
|
<div id="mapContainer" />
|
||||||
|
<!-- 地图图例 -->
|
||||||
|
<!-- tabType="{baseType[0]?.tagType}"
|
||||||
|
legendData="{legendData}"
|
||||||
|
legendDataMap="{legendDataMap}"
|
||||||
|
setLegendDataMap="{updateLegendDataMap}"
|
||||||
|
dvtpType="{dvtpType}"
|
||||||
|
mapList="{mapList}"
|
||||||
|
loading="{loading}"
|
||||||
|
pointData="{pointData}" -->
|
||||||
|
<MapLegend />
|
||||||
|
<!-- 地图筛选器 -->
|
||||||
|
<MapFilter />
|
||||||
|
<!-- 地图控制器 -->
|
||||||
|
<MapController />
|
||||||
|
<!-- 基础图层切换器 -->
|
||||||
|
<BaseLayerSwitcher />
|
||||||
|
<!-- <MapFilter inverse={true} searchList={mapSearchList} pointData={pointData} fish={fish}
|
||||||
|
wqElementsList={wqElementsList} className={'map-filter'} initialValues={{ timeRange: searchTimeRange, dvtp: dvtpType, year: yearTime }}
|
||||||
|
getFormRef={(ref: any) => (mapFilterFormRef.current = ref)}
|
||||||
|
fetchPointData={fetchPointData} /> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MapLegend from "@/components/mapLegend/index.vue";
|
||||||
|
import MapFilter from "@/components/mapFilter/index.vue";
|
||||||
|
import MapController from "@/components/mapController/index.vue";
|
||||||
|
import BaseLayerSwitcher from "@/components/baseLayerSwitcher/index.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.gis-view {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
#mapContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
103
frontend/src/components/mapController/index.vue
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<div class="map-controller">
|
||||||
|
<div class="map-controller-group">
|
||||||
|
<div class="map-controller-item" v-for="item in controllers" :key="item.key">
|
||||||
|
<a-tooltip :title="item.name" placement="left">
|
||||||
|
<i class="icon iconfont" :class="'icon-' + item.icon"></i>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
const isFullScreen = ref(false);
|
||||||
|
const mapType = ref("2D");
|
||||||
|
const controllers = ref([
|
||||||
|
{
|
||||||
|
name: "全屏",
|
||||||
|
key: "fullScreen",
|
||||||
|
icon: isFullScreen.value ? "exitFullScreen" : "fullScreen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "定位",
|
||||||
|
key: "positioning",
|
||||||
|
icon: "iconGlobal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "放大",
|
||||||
|
key: "zoomIn",
|
||||||
|
icon: "zoomIn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "缩小",
|
||||||
|
key: "zoomOut",
|
||||||
|
icon: "zoomOut",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "3D",
|
||||||
|
key: "dim",
|
||||||
|
icon: mapType.value === "2D" ? "a-3D" : "a-2D",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "图层",
|
||||||
|
// 树形图层组件
|
||||||
|
key: "layerController",
|
||||||
|
icon: "layer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "下载",
|
||||||
|
key: "screenShot",
|
||||||
|
icon: "downLoad",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "梯级",
|
||||||
|
key: "TJ",
|
||||||
|
icon: "tiji",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "倾斜摄影",
|
||||||
|
key: "OSBGController",
|
||||||
|
icon: "obliquePhotography",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "三维漫游",
|
||||||
|
key: "threedRoam",
|
||||||
|
icon: "roaming",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.map-controller {
|
||||||
|
position: absolute;
|
||||||
|
right: 480px;
|
||||||
|
bottom: 114px;
|
||||||
|
z-index: 10;
|
||||||
|
.map-controller-group {
|
||||||
|
box-shadow: 0 1px 2px #00000026;
|
||||||
|
background-color: #fff;
|
||||||
|
border: none;
|
||||||
|
.map-controller-item {
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
color: #000;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
.iconfont {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: #005292;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map-controller-group:not(:first-child) {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
75
frontend/src/components/mapFilter/index.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<div class="map-filter-container">
|
||||||
|
<div class="toolbar">
|
||||||
|
<a-form :model="formModel" :rules="rules" ref="formRef">
|
||||||
|
<a-row :gutter="10">
|
||||||
|
<a-col>
|
||||||
|
<a-form-item label="" name="siteRangePicker">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formModel.siteRangePicker"
|
||||||
|
placeholder="装机容量"
|
||||||
|
style="width: 120px"
|
||||||
|
>
|
||||||
|
<a-select-option v-for="item in siteRangePicker" :key="item.value">{{
|
||||||
|
item.label
|
||||||
|
}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col>
|
||||||
|
<a-form-item label="" name="siteRangePicker">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formModel.siteRangePicker"
|
||||||
|
placeholder="请输入关键字检索"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
|
||||||
|
const siteRangePicker = [
|
||||||
|
{ label: "0-10", value: "0-10" },
|
||||||
|
{ label: "10-20", value: "10-20" },
|
||||||
|
{ label: "20-30", value: "20-30" },
|
||||||
|
{ label: "30-40", value: "30-40" },
|
||||||
|
{ label: "40-50", value: "40-50" },
|
||||||
|
{ label: "50-60", value: "50-60" },
|
||||||
|
];
|
||||||
|
const formModel = ref({
|
||||||
|
siteRangePicker: [],
|
||||||
|
});
|
||||||
|
const rules = ref({
|
||||||
|
siteRangePicker: [{ required: true, message: "请选择装机容量" }],
|
||||||
|
});
|
||||||
|
const formRef = ref<any>(null);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.map-filter-container {
|
||||||
|
position: absolute;
|
||||||
|
left: 220px;
|
||||||
|
top: 15px;
|
||||||
|
z-index: 99;
|
||||||
|
padding: 6px 0px;
|
||||||
|
background: #e5edf3;
|
||||||
|
border: none;
|
||||||
|
:deep(.ant-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
:deep(.ant-row) {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
114
frontend/src/components/mapLegend/index.vue
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mapLegendView">
|
||||||
|
<div class="legendTitle">
|
||||||
|
图例
|
||||||
|
<span class="legendBtn" @click="isOpen = !isOpen">
|
||||||
|
<i class="icon iconfont" :class="isOpen ? 'icon-fold' : 'icon-unFold'"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="legendContent" v-show="isOpen">
|
||||||
|
<a-spin :spinning="data === 0">
|
||||||
|
<div class="legendGroup" v-for="i in data">
|
||||||
|
<div class="groupTitle">工程</div>
|
||||||
|
<div class="groupContent">
|
||||||
|
<div class="legendItem" v-for="j in 10" :key="j">
|
||||||
|
<div class="legendIcon smallIcon"></div>
|
||||||
|
<div class="legendIconTitle">
|
||||||
|
大型水电站-已建
|
||||||
|
<!-- <span
|
||||||
|
><br />
|
||||||
|
2.、装机容量(万kW)
|
||||||
|
</span> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
const data = ref(0);
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
data.value = 10;
|
||||||
|
}, 1000);
|
||||||
|
console.log(data.value);
|
||||||
|
});
|
||||||
|
const isOpen = ref(true);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mapLegendView {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
min-width: 72px;
|
||||||
|
margin: 24px 0 16px 16px;
|
||||||
|
border: 1px solid #ccdae7;
|
||||||
|
padding: 6px;
|
||||||
|
z-index: 9;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
.legendTitle {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
.legendBtn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.legendContent {
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 450px;
|
||||||
|
max-height: 392px;
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: scroll;
|
||||||
|
border-top: 1px solid #eeeeee;
|
||||||
|
overflow-y: hidden;
|
||||||
|
// &::-webkit-scrollbar {
|
||||||
|
// width: 0;
|
||||||
|
// }
|
||||||
|
.legendGroup {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 0 4px;
|
||||||
|
.groupTitle {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.groupContent {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
.gray {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
color: #00000073;
|
||||||
|
}
|
||||||
|
.legendItem {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 10px;
|
||||||
|
padding: 4px 0;
|
||||||
|
min-height: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
.legendIcon {
|
||||||
|
width: 22px !important;
|
||||||
|
height: 22px !important;
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
.legendIconTitle {
|
||||||
|
cursor: pointer;
|
||||||
|
flex: 1 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -6,11 +6,11 @@ export default {
|
|||||||
},
|
},
|
||||||
// 登录页面国际化
|
// 登录页面国际化
|
||||||
login: {
|
login: {
|
||||||
title: '公司开发平台框架',
|
title: '水电水利建设项目全过程环境管理信息平台',
|
||||||
username: '用户名',
|
username: '用户名',
|
||||||
rulesUsername: '请输入用户名',
|
rulesUsername: '用户账号/身份证号/手机号 不能为空',
|
||||||
password: '密码',
|
password: '密码',
|
||||||
rulesPassword: '请输入密码',
|
rulesPassword: '密码 不能为空',
|
||||||
rulesPasswordPlace: '密码不能少于6位',
|
rulesPasswordPlace: '密码不能少于6位',
|
||||||
login: '登 录',
|
login: '登 录',
|
||||||
code: '请输入验证码',
|
code: '请输入验证码',
|
||||||
|
|||||||
@ -1,49 +1,44 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue"
|
import { computed } from "vue";
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView';
|
import { useTagsViewStore } from "@/store/modules/tagsView";
|
||||||
import { useRoute, } from 'vue-router';
|
import { useRoute } from "vue-router";
|
||||||
|
import GisView from "@/components/gis/GisView.vue";
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
const tagsViewStore = useTagsViewStore();
|
||||||
|
|
||||||
const router = useRoute();
|
const router = useRoute();
|
||||||
const routeKey = computed(() => router.path + Math.random());
|
const routeKey = computed(() => router.path + Math.random());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="app-main">
|
<section class="app-main">
|
||||||
<router-view v-slot="{ Component, route }" :key="routeKey">
|
<GisView />
|
||||||
<transition name="router-fade" mode="out-in">
|
<div class="gi-panels">
|
||||||
<keep-alive :include="tagsViewStore.cachedViews">
|
<router-view v-slot="{ Component, route }" :key="routeKey">
|
||||||
<component :is="Component" :key="route.fullPath" />
|
<transition name="router-fade" mode="out-in">
|
||||||
</keep-alive>
|
<keep-alive :include="tagsViewStore.cachedViews">
|
||||||
</transition>
|
<component :is="Component" :key="route.fullPath" />
|
||||||
</router-view>
|
</keep-alive>
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/variables.module.scss" as *;
|
||||||
.app-main {
|
.app-main {
|
||||||
min-height: calc(100vh - 114px);
|
min-height: calc(100vh - $layout-header-height - $locationbar-height);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #f0f2f5;
|
background-color: #ffffff;
|
||||||
padding: 0 16px 16px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
.gi-panels {
|
||||||
.fixed-header + .app-main {
|
position: absolute;
|
||||||
padding-top: 50px;
|
width: 100%;
|
||||||
}
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
.hasTagsView {
|
|
||||||
.app-main {
|
|
||||||
/* 84 = navbar + tags-view = 50 + 34 */
|
|
||||||
min-height: calc(100vh - 114px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header + .app-main {
|
|
||||||
padding-top: 84px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,46 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, onMounted, onBeforeUnmount } from 'vue';
|
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from "element-plus";
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from "@/utils/auth";
|
||||||
|
import { UserOutlined, LogoutOutlined } from "@ant-design/icons-vue";
|
||||||
import Screenfull from '@/components/Screenfull/index.vue';
|
// 国际化
|
||||||
import SizeSelect from '@/components/SizeSelect/index.vue';
|
import { useI18n } from "vue-i18n";
|
||||||
import News from './news.vue';
|
const { t } = useI18n();
|
||||||
// import LangSelect from '@/components/LangSelect/index.vue';
|
// import LangSelect from '@/components/LangSelect/index.vue';
|
||||||
import MixNav from './Sidebar/MixNav.vue';
|
import Sidebar from "./Sidebar/index.vue";
|
||||||
// import { CaretBottom } from '@element-plus/icons-vue';
|
|
||||||
import Logo from './Sidebar/Logo.vue';
|
|
||||||
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useTagsViewStore } from "@/store/modules/tagsView";
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView';
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import Cookies from "js-cookie";
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
import { storeToRefs } from "pinia";
|
||||||
import Cookies from 'js-cookie';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
|
|
||||||
const url = import.meta.env.VITE_APP_BASE_API;
|
const url = import.meta.env.VITE_APP_BASE_API;
|
||||||
const username = Cookies.get('username');
|
const username = Cookies.get("username");
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const { sidebarLogo } = storeToRefs(settingsStore);
|
|
||||||
const isCollapse = computed(() => !appStore.sidebar.opened);
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
const tagsViewStore = useTagsViewStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const querystr = ref('');
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
const device = computed(() => appStore.device);
|
|
||||||
const news = ref()
|
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: "取消",
|
||||||
type: 'warning'
|
type: "warning",
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
userStore
|
userStore
|
||||||
.logout()
|
.logout()
|
||||||
@ -52,114 +39,128 @@ function logout() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function querystrChange() {
|
const badgeval = ref(0);
|
||||||
}
|
const isbadge = ref(true);
|
||||||
const badgeval = ref(0)
|
var source = new EventSource(url + `/sse/connect/` + getToken());
|
||||||
const isbadge = ref(true)
|
|
||||||
var source = new EventSource(url+ `/sse/connect/` + getToken(),);
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if ("EventSource" in window) {
|
if ("EventSource" in window) {
|
||||||
source.onmessage = function(e) {
|
source.onmessage = function (e) {
|
||||||
if(e.data>0) {
|
if (e.data > 0) {
|
||||||
badgeval.value = e.data
|
badgeval.value = e.data;
|
||||||
isbadge.value = false
|
isbadge.value = false;
|
||||||
news.value.init()
|
|
||||||
} else {
|
} else {
|
||||||
isbadge.value = true
|
isbadge.value = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
source.onopen = function(e) {
|
source.onopen = function (e) {};
|
||||||
};
|
source.onerror = function (e: any) {
|
||||||
source.onerror = function(e:any) {
|
|
||||||
if (e.readyState == EventSource.CLOSED) {
|
if (e.readyState == EventSource.CLOSED) {
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
};
|
}
|
||||||
})
|
});
|
||||||
onBeforeUnmount(()=>{
|
onBeforeUnmount(() => {
|
||||||
source.close();
|
source.close();
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="navbar">
|
<div class="navbar">
|
||||||
<logo v-if="sidebarLogo" :collapse="isCollapse" />
|
<a-layout-header class="header">
|
||||||
<!-- <mix-nav v-if="device !== 'mobile' && settingsStore.layout === 'mix'" /> -->
|
<transition class="bg-white-800">
|
||||||
<div v-if="settingsStore.layout === 'left'" class="flex justify-start">
|
<a
|
||||||
<div class="flex justify-center items-center">
|
href="/"
|
||||||
<!--全屏 -->
|
class="h-[50px] min-w-[350px] flex items-center justify-center text-white"
|
||||||
<div v-if="device === 'desktop'" style="position: relative;">
|
>
|
||||||
<el-input class="keywords" v-model="querystr" placeholder="请输入搜索关键字" clearable
|
<h1 class="text-blank font-bold fontSize-16">{{ t("login.title") }}</h1></a
|
||||||
@change="querystrChange"></el-input>
|
>
|
||||||
<img src="@/assets/MenuIcon/top_ss.png" alt="" style="position: absolute;right: 30px;top: 10px;">
|
</transition>
|
||||||
</div>
|
<Sidebar />
|
||||||
<div class="flex items-center cursor-pointer" style="margin-right:15px;margin-top: 6px;">
|
|
||||||
<el-badge :value="badgeval" :hidden="isbadge" :max="10">
|
|
||||||
<el-icon style="font-size: 22px;" @click="dialogVisible = true,news.init()"><Bell /></el-icon>
|
|
||||||
</el-badge>
|
|
||||||
</div>
|
|
||||||
<!-- 布局大小 -->
|
|
||||||
<el-tooltip content="字号大小" effect="dark" placement="bottom">
|
|
||||||
<size-select />
|
|
||||||
</el-tooltip>
|
|
||||||
<!-- 全屏 -->
|
|
||||||
<el-tooltip content="全屏缩放" effect="dark" placement="bottom">
|
|
||||||
<screenfull id="screenfull" />
|
|
||||||
</el-tooltip>
|
|
||||||
<!--语言选择-->
|
|
||||||
<!-- <lang-select /> -->
|
|
||||||
</div>
|
|
||||||
<!-- 头像 -->
|
|
||||||
<div style="heigth:100%;display: flex;display: -webkit-flex;align-items: center; -webkit-align-items: center; ">
|
|
||||||
<div class="heighta"></div>
|
|
||||||
<img v-if="userStore.avatar!== ''" :src="url + '/avatar/' + userStore.avatar + '?imageView2/1/w/80/h/80'"
|
|
||||||
class="w-[30px] h-[30px] rounded-lg rounded-full" style="border-radius: 50%;" />
|
|
||||||
<img v-else src="@/assets/MenuIcon/top_tx.png" alt="" class="w-[30px] h-[30px] rounded-lg rounded-full" style="border-radius: 50%;">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-tooltip content="个人中心" effect="dark" placement="bottom">
|
<a-dropdown :trigger="['click']" placement="bottomRight">
|
||||||
<div class="cursor-pointer flex justify-center items-center pl-[10px]"
|
<a-space class="username">
|
||||||
:style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }" @click="$router.push('/personalCenter')">{{
|
<div>
|
||||||
username }}</div>
|
<span class="icon">
|
||||||
</el-tooltip>
|
<UserOutlined />
|
||||||
|
</span>
|
||||||
<div class="flex justify-center items-center cursor-pointer pr-[12px]"
|
<span class="text">{{ username }}</span>
|
||||||
:style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }" @click="logout"><el-divider
|
</div>
|
||||||
direction="vertical" style="padding-right:5px" />{{ $t('navbar.logout') }}</div>
|
</a-space>
|
||||||
<News ref="news" v-model:dialog-visible="dialogVisible" />
|
<template #overlay>
|
||||||
</div>
|
<a-menu>
|
||||||
|
<a-menu-item key="changePassword">
|
||||||
|
<UserOutlined />
|
||||||
|
修改密码
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-divider />
|
||||||
|
<a-menu-item key="logout" @click="logout">
|
||||||
|
<LogoutOutlined />
|
||||||
|
退出登录
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-layout-header>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/variables.module.scss" as *;
|
||||||
.navbar {
|
.navbar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 60px;
|
height: 110px;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
-webkit-justify-content: space-between;
|
|
||||||
box-shadow: 0px 0px 10px rgb(219 225 236);
|
|
||||||
z-index: 98;
|
z-index: 98;
|
||||||
}
|
|
||||||
|
|
||||||
.keywords {
|
.header {
|
||||||
width: 240px;
|
width: 100%;
|
||||||
height: 34px;
|
display: flex;
|
||||||
margin-right: 20px;
|
align-items: center;
|
||||||
|
background-color: #005293;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||||
|
height: $layout-header-height;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
.username {
|
||||||
|
height: $layout-header-height;
|
||||||
|
line-height: $layout-header-height;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
:deep(.el-input__wrapper) {
|
div {
|
||||||
padding-left: 15px;
|
display: flex;
|
||||||
border-radius: 40px;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
text-align: center;
|
||||||
|
color: $main-menu-color;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 18px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: $main-menu-color;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.heighta{
|
|
||||||
border-left:1px solid #dcdfe6 ;
|
.heighta {
|
||||||
|
border-left: 1px solid #dcdfe6;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,198 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { Sunny, Moon } from '@element-plus/icons-vue';
|
|
||||||
|
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
|
||||||
|
|
||||||
import { useDark, useToggle } from '@vueuse/core';
|
|
||||||
import { ElDivider, ElSwitch, ElTooltip } from 'element-plus';
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const isDark = useDark();
|
|
||||||
|
|
||||||
function toggleTheme() {
|
|
||||||
const isDark = useDark();
|
|
||||||
useToggle(isDark);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
window.document.body.setAttribute('layout', settingsStore.layout);
|
|
||||||
});
|
|
||||||
|
|
||||||
function changeLayout(layout: string) {
|
|
||||||
settingsStore.changeSetting({ key: 'layout', value: layout });
|
|
||||||
window.document.body.setAttribute('layout', settingsStore.layout);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="settings-container">
|
|
||||||
<h3 class="text-base font-bold">项目配置</h3>
|
|
||||||
<el-divider />
|
|
||||||
<div class="drawer-item">
|
|
||||||
<span>开启 Tags-View</span>
|
|
||||||
<el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-item">
|
|
||||||
<span>固定 Header</span>
|
|
||||||
<el-switch v-model="settingsStore.fixedHeader" class="drawer-switch" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-item">
|
|
||||||
<span>侧边栏 Logo</span>
|
|
||||||
<el-switch v-model="settingsStore.sidebarLogo" class="drawer-switch" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-divider>主题</el-divider>
|
|
||||||
|
|
||||||
<div class="flex justify-center" @click.stop>
|
|
||||||
<el-switch
|
|
||||||
v-model="isDark"
|
|
||||||
inline-prompt
|
|
||||||
@change="toggleTheme"
|
|
||||||
:active-icon="Sunny"
|
|
||||||
:inactive-icon="Moon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-divider>导航栏布局</el-divider>
|
|
||||||
|
|
||||||
<ul class="layout">
|
|
||||||
<el-tooltip content="左侧模式" placement="bottom">
|
|
||||||
<li
|
|
||||||
:class="
|
|
||||||
'layout-item layout-left ' +
|
|
||||||
(settingsStore.layout == 'left' ? 'is-active' : '')
|
|
||||||
"
|
|
||||||
@click="changeLayout('left')"
|
|
||||||
>
|
|
||||||
<div />
|
|
||||||
<div />
|
|
||||||
</li>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="顶部模式" placement="bottom">
|
|
||||||
<li
|
|
||||||
:class="
|
|
||||||
'layout-item layout-top ' +
|
|
||||||
(settingsStore.layout == 'top' ? 'is-active' : '')
|
|
||||||
"
|
|
||||||
@click="changeLayout('top')"
|
|
||||||
>
|
|
||||||
<div />
|
|
||||||
<div />
|
|
||||||
</li>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="混合模式" placement="bottom">
|
|
||||||
<li
|
|
||||||
:class="
|
|
||||||
'layout-item layout-mix ' +
|
|
||||||
(settingsStore.layout == 'mix' ? 'is-active' : '')
|
|
||||||
"
|
|
||||||
@click="changeLayout('mix')"
|
|
||||||
>
|
|
||||||
<div />
|
|
||||||
<div />
|
|
||||||
</li>
|
|
||||||
</el-tooltip>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.settings-container {
|
|
||||||
padding: 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
.drawer-title {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
color: rgba(0, 0, 0, 0.85);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-item {
|
|
||||||
color: rgba(0, 0, 0, 0.65);
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 12px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-switch {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout {
|
|
||||||
display: flex;
|
|
||||||
display: -webkit-flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-around;
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&-item {
|
|
||||||
width: 18%;
|
|
||||||
height: 45px;
|
|
||||||
background: #f0f2f5;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
&-item.is-active {
|
|
||||||
border: 2px solid var(--el-color-primary);
|
|
||||||
}
|
|
||||||
&-left {
|
|
||||||
div {
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 30%;
|
|
||||||
height: 100%;
|
|
||||||
background: #1b2a47;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
width: 70%;
|
|
||||||
height: 30%;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 0 1px #888;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-top {
|
|
||||||
div {
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 100%;
|
|
||||||
height: 30%;
|
|
||||||
background: #1b2a47;
|
|
||||||
box-shadow: 0 0 1px #888;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-mix {
|
|
||||||
div {
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 100%;
|
|
||||||
height: 30%;
|
|
||||||
background: #1b2a47;
|
|
||||||
box-shadow: 0 0 1px #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
width: 30%;
|
|
||||||
height: 70%;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
background: #1b2a47;
|
|
||||||
box-shadow: 0 0 1px #888;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { isExternal } from '@/utils/validate';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
const sidebar = computed(() => appStore.sidebar);
|
|
||||||
const device = computed(() => appStore.device);
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
to: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
function push() {
|
|
||||||
if (device.value === 'mobile' && sidebar.value.opened == true) {
|
|
||||||
appStore.closeSideBar(false);
|
|
||||||
}
|
|
||||||
router.push(props.to).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
|
|
||||||
<slot />
|
|
||||||
</a>
|
|
||||||
<div v-else @click="push">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
|
|
||||||
// 国际化
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
const { t } = useI18n();
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
collapse: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const logo = ref<string>(
|
|
||||||
new URL(`../../../assets/logo.png`, import.meta.url).href
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<transition class="ml-5 bg-white-800">
|
|
||||||
<router-link
|
|
||||||
key="collapse"
|
|
||||||
class="h-[50px] flex items-center justify-center"
|
|
||||||
to="/dashboard"
|
|
||||||
>
|
|
||||||
<img :src="logo" class="w-5 h-5" />
|
|
||||||
<h1 class="ml-3 text-blank font-bold" :style="{fontSize: appStore.size === 'default' ? '20px' : '22px'}">{{t('login.title')}}</h1>
|
|
||||||
</router-link>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
@ -1,159 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, onMounted } from 'vue';
|
|
||||||
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
|
||||||
import {
|
|
||||||
ElDropdown,
|
|
||||||
ElDropdownItem,
|
|
||||||
ElDropdownMenu,
|
|
||||||
ElMenu,
|
|
||||||
ElMessageBox,
|
|
||||||
ElTooltip
|
|
||||||
} from 'element-plus';
|
|
||||||
|
|
||||||
import Screenfull from '@/components/Screenfull/index.vue';
|
|
||||||
import SizeSelect from '@/components/SizeSelect/index.vue';
|
|
||||||
import LangSelect from '@/components/LangSelect/index.vue';
|
|
||||||
import { CaretBottom } from '@element-plus/icons-vue';
|
|
||||||
|
|
||||||
import SidebarItem from './SidebarItem.vue';
|
|
||||||
import variables from '@/styles/variables.module.scss';
|
|
||||||
|
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView';
|
|
||||||
import { useUserStore } from '@/store/modules/user';
|
|
||||||
import { usePermissionStore } from '@/store/modules/permission';
|
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const permissionStore = usePermissionStore();
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
const routes = [] as any[];
|
|
||||||
onMounted(() => {
|
|
||||||
permissionStore.routes.forEach(item => {
|
|
||||||
const { ...newItem } = item;
|
|
||||||
routes.push(newItem);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const activeMenu = computed<string>(() => {
|
|
||||||
const { meta, path } = route;
|
|
||||||
if (meta?.activeMenu) {
|
|
||||||
return meta.activeMenu as string;
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
});
|
|
||||||
|
|
||||||
function logout() {
|
|
||||||
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
userStore
|
|
||||||
.logout()
|
|
||||||
.then(() => {
|
|
||||||
tagsViewStore.delAllViews();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
router.push(`/login?redirect=${route.fullPath}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="horizontal-header">
|
|
||||||
<el-menu
|
|
||||||
class="horizontal-header-menu"
|
|
||||||
:default-active="activeMenu"
|
|
||||||
:background-color="variables.menuBg"
|
|
||||||
:text-color="variables.menuText"
|
|
||||||
:active-text-color="variables.menuActiveText"
|
|
||||||
mode="horizontal"
|
|
||||||
>
|
|
||||||
<sidebar-item
|
|
||||||
v-for="route in routes"
|
|
||||||
:item="route"
|
|
||||||
:key="route.path"
|
|
||||||
:base-path="route.path"
|
|
||||||
/>
|
|
||||||
</el-menu>
|
|
||||||
|
|
||||||
<div class="horizontal-header-right">
|
|
||||||
<!--全屏 -->
|
|
||||||
<screenfull id="screenfull" />
|
|
||||||
|
|
||||||
<!-- 布局大小 -->
|
|
||||||
<el-tooltip content="布局大小" effect="dark" placement="bottom">
|
|
||||||
<size-select />
|
|
||||||
</el-tooltip>
|
|
||||||
123
|
|
||||||
<!--语言选择-->
|
|
||||||
<lang-select />
|
|
||||||
|
|
||||||
<el-dropdown trigger="click">
|
|
||||||
<div class="flex justify-center items-center pr-[20px]">
|
|
||||||
<img
|
|
||||||
:src="userStore.avatar + '?imageView2/1/w/80/h/80'"
|
|
||||||
class="w-[40px] h-[40px] rounded-lg"
|
|
||||||
/>
|
|
||||||
<CaretBottom class="w-3 h-3" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<router-link to="/">
|
|
||||||
<el-dropdown-item>{{ $t('navbar.dashboard') }}</el-dropdown-item>
|
|
||||||
</router-link>
|
|
||||||
<a target="_blank" href="https://github.com/hxrui">
|
|
||||||
<el-dropdown-item>Github</el-dropdown-item>
|
|
||||||
</a>
|
|
||||||
<a target="_blank" href="https://gitee.com/haoxr">
|
|
||||||
<el-dropdown-item>{{ $t('navbar.gitee') }}</el-dropdown-item>
|
|
||||||
</a>
|
|
||||||
<a target="_blank" href="https://www.cnblogs.com/haoxianrui/">
|
|
||||||
<el-dropdown-item>{{ $t('navbar.document') }}</el-dropdown-item>
|
|
||||||
</a>
|
|
||||||
<el-dropdown-item divided @click="logout">
|
|
||||||
{{ $t('navbar.logout') }}
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@use '@/styles/variables.module' as variables;
|
|
||||||
|
|
||||||
.horizontal-header {
|
|
||||||
display: flex;
|
|
||||||
display: -webkit-flex;
|
|
||||||
width: 100%;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
-webkit-justify-content: space-around;
|
|
||||||
background: #001529;
|
|
||||||
|
|
||||||
&-menu {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-right {
|
|
||||||
display: flex;
|
|
||||||
display: -webkit-flex;
|
|
||||||
min-width: 340px;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
-webkit-justify-content: flex-end;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted } from "vue";
|
|
||||||
import { isExternal } from "@/utils/validate";
|
|
||||||
import AppLink from "./Link.vue";
|
|
||||||
import { generateTitle } from "@/utils/i18n";
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const url = import.meta.env.VITE_APP_BASE_API;
|
|
||||||
defineProps({
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
isNest: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
// basePath: {
|
|
||||||
// type: String,
|
|
||||||
// required: true,
|
|
||||||
// },
|
|
||||||
});
|
|
||||||
|
|
||||||
const onlyOneChild = ref();
|
|
||||||
|
|
||||||
function hasOneShowingChild(children = [] as any, parent: any) {
|
|
||||||
if (!children) {
|
|
||||||
children = [];
|
|
||||||
}
|
|
||||||
const showingChildren = children.filter((item: any) => {
|
|
||||||
if (item.meta && item.meta.hidden) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// Temp set(will be used if only has one showing child)
|
|
||||||
onlyOneChild.value = item;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// When there is only one child router, the child router is displayed by default
|
|
||||||
if (showingChildren.length === 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show parent if there are no child router to display
|
|
||||||
if (showingChildren.length === 0) {
|
|
||||||
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolvePath(routePath: any) {
|
|
||||||
// if (isExternal(props.basePath)) {
|
|
||||||
// return props.basePath;
|
|
||||||
// }
|
|
||||||
if (routePath.islink == 0) {
|
|
||||||
if (isExternal(routePath.opturl)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return routePath.opturl;
|
|
||||||
}
|
|
||||||
onMounted(() => {
|
|
||||||
generateTitle
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div v-if="!item.meta || !item.meta.hidden || item.isdisplay == '1'">
|
|
||||||
<template v-if="
|
|
||||||
hasOneShowingChild(item.children, item) &&
|
|
||||||
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
|
|
||||||
(!item.meta || !item.meta.alwaysShow)
|
|
||||||
">
|
|
||||||
<app-link v-if="onlyOneChild.meta || item.isdisplay == '1'" :to="resolvePath(onlyOneChild)">
|
|
||||||
<el-menu-item :index="resolvePath(onlyOneChild)" :class="{ 'submenu-title-noDropdown': !isNest }"
|
|
||||||
style="height: 44px;">
|
|
||||||
<img v-if="onlyOneChild.icon && onlyOneChild.name == '首页'" src="@/assets/MenuIcon/dh_sy.png"
|
|
||||||
style="margin-right:10px;width: 16px">
|
|
||||||
<img v-if="onlyOneChild.icon && onlyOneChild.name !== '首页'" :src="url + '/menu/' + item.icon"
|
|
||||||
style="margin-right:10px;width: 16px">
|
|
||||||
<template #title>
|
|
||||||
<span :style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }">{{ generateTitle(onlyOneChild.name)
|
|
||||||
}}</span>
|
|
||||||
</template>
|
|
||||||
</el-menu-item>
|
|
||||||
</app-link>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-sub-menu v-else :index="resolvePath(item)" telested>
|
|
||||||
<template #title>
|
|
||||||
<div style="height: 44px; display: flex;display: -webkit-flex; align-items: center;-webkit-align-items: center;justify-content: space-around;">
|
|
||||||
<img v-if="item.icon" :icon-class="item.icon" :src="url + '/menu/' + item.icon" alt=""
|
|
||||||
style="margin-right:10px;width: 16px; " class="management">
|
|
||||||
<span :style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }" style="display: block;" >{{
|
|
||||||
generateTitle(item.name) }}</span>
|
|
||||||
</div>
|
|
||||||
<!-- <svg-icon v-if="item.icon" :icon-class="item.icon" :style="{fontSize: appStore.size === 'default' ? '14px' : '16px'}" /> -->
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<sidebar-item v-for="child in item.children" :key="child.opturl" :item="child" :is-nest="true"
|
|
||||||
:base-path="resolvePath(child)" />
|
|
||||||
</el-sub-menu>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style scoped>
|
|
||||||
:deep(.el-sub-menu__title) {
|
|
||||||
height: 44px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-icon) {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
:deep(.el-tooltip__trigger span) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
:deep(.el-tooltip__trigger .management) {
|
|
||||||
margin-left:20px !important ;
|
|
||||||
}
|
|
||||||
:deep(.el-menu-tooltip__trigger){
|
|
||||||
width: 105% !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@ -1,47 +1,173 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { ref, onBeforeMount, onMounted } from "vue";
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
import SidebarItem from './SidebarItem.vue';
|
import { usePermissionStore } from "@/store/modules/permission";
|
||||||
import variables from '@/styles/variables.module.scss';
|
|
||||||
|
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
|
||||||
import { usePermissionStore } from '@/store/modules/permission';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const permissionStore = usePermissionStore();
|
const permissionStore = usePermissionStore();
|
||||||
|
const menus: any = ref([]); // 主菜单列表
|
||||||
|
const subMenus: any = ref([]); // 子菜单列表
|
||||||
|
const activeKey = ref(""); // 主菜单选中项
|
||||||
|
const subActiveKey = ref(""); // 子菜单选中项
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
const { sidebarLogo } = storeToRefs(settingsStore);
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const isCollapse = computed(() => !appStore.sidebar.opened);
|
// 主菜单切换
|
||||||
|
const handleTabChange = (key: string) => {
|
||||||
const activeMenu = computed<string>(() => {
|
let data = menus.value.find((item: any) => item.id === key);
|
||||||
const { meta, path } = route;
|
subMenus.value = data.children;
|
||||||
if (meta?.activeMenu) {
|
activeKey.value = key;
|
||||||
return meta.activeMenu as string;
|
if (data.children.length > 0) {
|
||||||
|
subActiveKey.value = data.children[0].id;
|
||||||
|
let subData = subMenus.value.find((item: any) => item.id === subActiveKey.value);
|
||||||
|
router.push(subData.path || subData.opturl);
|
||||||
|
} else {
|
||||||
|
subActiveKey.value = "";
|
||||||
}
|
}
|
||||||
return path;
|
};
|
||||||
});
|
// 子菜单切换
|
||||||
|
const handleSubTabChange = (key: string) => {
|
||||||
|
subActiveKey.value = key;
|
||||||
|
let data = subMenus.value.find((item: any) => item.id === key);
|
||||||
|
router.push(data.path || data.opturl);
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
console.log(route)
|
||||||
|
console.log(route)
|
||||||
|
console.log(permissionStore.routes)
|
||||||
|
permissionStore.routes.map((item: any) => {
|
||||||
|
if (item.meta?.hidden) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
if (item?.parentid == null) {
|
||||||
|
menus.value.push(item?.children?.[0]);
|
||||||
|
} else {
|
||||||
|
menus.value.push(item);
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
item.children.map((child: any) => {
|
||||||
|
if (child.path === route.path) {
|
||||||
|
activeKey.value = item.id;
|
||||||
|
subMenus.value = item.children;
|
||||||
|
subActiveKey.value = child.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
onMounted(() => {});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="{ 'has-logo': sidebarLogo }">
|
<a-tabs
|
||||||
<el-scrollbar>
|
class="sidebar-container"
|
||||||
<el-menu :default-active="activeMenu" :collapse="isCollapse" :background-color="variables.menuBg"
|
v-model:activeKey="activeKey"
|
||||||
:text-color="variables.menuText" :active-text-color="variables.menuActiveText" :unique-opened="false"
|
@change="handleTabChange"
|
||||||
:collapse-transition="false" mode="vertical" >
|
size="small"
|
||||||
<sidebar-item v-for="route in permissionStore.routes" :item="route" :key="route.opturl"
|
>
|
||||||
:is-collapse="isCollapse" />
|
<a-tab-pane
|
||||||
</el-menu>
|
v-for="route in menus"
|
||||||
</el-scrollbar>
|
:key="route.id"
|
||||||
|
:tab="route.meta.title"
|
||||||
|
></a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
<div class="sub-menus">
|
||||||
|
<a-tabs v-model:activeKey="subActiveKey" @change="handleSubTabChange" size="small">
|
||||||
|
<a-tab-pane
|
||||||
|
v-for="route in subMenus"
|
||||||
|
:key="route.id"
|
||||||
|
:tab="route.meta.title"
|
||||||
|
></a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@use '@/styles/variables.module' as variables;
|
@use "@/styles/variables.module.scss" as *;
|
||||||
</style>
|
|
||||||
|
.sidebar-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: hidden !important;
|
||||||
|
margin: 0 30px;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-nav) {
|
||||||
|
height: $layout-header-height;
|
||||||
|
margin-bottom: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-nav-operations {
|
||||||
|
color: $main-menu-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-tab) {
|
||||||
|
padding: 0 20px;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
color: $main-menu-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-tab-active) {
|
||||||
|
color: $main-menu-color-active !important;
|
||||||
|
background: $main-menu-bg-color-active;
|
||||||
|
|
||||||
|
.ant-tabs-tab-btn {
|
||||||
|
color: $main-menu-color-active !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-tab:hover) {
|
||||||
|
color: $main-menu-color-active;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-ink-bar) {
|
||||||
|
visibility: visible;
|
||||||
|
border: none;
|
||||||
|
background: url("@/assets/icons/menuActiveBg.svg") no-repeat center 30px;
|
||||||
|
height: 51px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-menus {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: $layout-header-height;
|
||||||
|
height: $locationbar-height;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
z-index: 100;
|
||||||
|
padding-left: 12px;
|
||||||
|
|
||||||
|
:deep(.ant-tabs-nav) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.ant-tabs-tab {
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-ink-bar {
|
||||||
|
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAxMS8wMy8yMrqNQAoAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAOklEQVQImWNgQAchkx3QhZhQeEFTExj+MexnCJoyH1mYEUUBwz8kScYFDOtyEhGKMBSgKmTErQChEAA6FRM7O0rIOgAAAABJRU5ErkJggg==")
|
||||||
|
no-repeat center bottom;
|
||||||
|
border-bottom: 2px solid #005293;
|
||||||
|
height: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-tab-active {
|
||||||
|
.ant-tabs-tab-btn {
|
||||||
|
color: #2f6b98 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,130 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
ref,
|
|
||||||
computed,
|
|
||||||
onMounted,
|
|
||||||
onBeforeUnmount,
|
|
||||||
getCurrentInstance
|
|
||||||
} from 'vue';
|
|
||||||
import { useTagsViewStore, TagView } from '@/store/modules/tagsView';
|
|
||||||
|
|
||||||
const tagAndTagSpacing = ref(4);
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
|
|
||||||
const emits = defineEmits(['scroll']);
|
|
||||||
const emitScroll = () => {
|
|
||||||
emits('scroll');
|
|
||||||
};
|
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
|
||||||
|
|
||||||
const scrollWrapper = computed(
|
|
||||||
() => proxy?.$refs.scrollContainer.$refs.wrapRef
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
scrollWrapper.value.addEventListener('scroll', emitScroll, true);
|
|
||||||
});
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
scrollWrapper.value.removeEventListener('scroll', emitScroll);
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleScroll(e: WheelEvent) {
|
|
||||||
const eventDelta = (e as any).wheelDelta || -e.deltaY * 40;
|
|
||||||
scrollWrapper.value.scrollLeft =
|
|
||||||
scrollWrapper.value.scrollLeft + eventDelta / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveToTarget(currentTag: TagView) {
|
|
||||||
const $container = proxy.$refs.scrollContainer.$el;
|
|
||||||
const $containerWidth = $container.offsetWidth;
|
|
||||||
const $scrollWrapper = scrollWrapper.value;
|
|
||||||
|
|
||||||
let firstTag = null;
|
|
||||||
let lastTag = null;
|
|
||||||
|
|
||||||
// find first tag and last tag
|
|
||||||
if (tagsViewStore.visitedViews.length > 0) {
|
|
||||||
firstTag = tagsViewStore.visitedViews[0];
|
|
||||||
lastTag = tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstTag === currentTag) {
|
|
||||||
$scrollWrapper.scrollLeft = 0;
|
|
||||||
} else if (lastTag === currentTag) {
|
|
||||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
|
|
||||||
} else {
|
|
||||||
const tagListDom = document.getElementsByClassName('tags-item');
|
|
||||||
const currentIndex = tagsViewStore.visitedViews.findIndex(
|
|
||||||
item => item === currentTag
|
|
||||||
);
|
|
||||||
let prevTag = null;
|
|
||||||
let nextTag = null;
|
|
||||||
for (const k in tagListDom) {
|
|
||||||
if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
|
|
||||||
if (
|
|
||||||
(tagListDom[k] as any).dataset.path ===
|
|
||||||
tagsViewStore.visitedViews[currentIndex - 1].path
|
|
||||||
) {
|
|
||||||
prevTag = tagListDom[k];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(tagListDom[k] as any).dataset.path ===
|
|
||||||
tagsViewStore.visitedViews[currentIndex + 1].path
|
|
||||||
) {
|
|
||||||
nextTag = tagListDom[k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the tag's offsetLeft after of nextTag
|
|
||||||
const afterNextTagOffsetLeft =
|
|
||||||
(nextTag as any).offsetLeft +
|
|
||||||
(nextTag as any).offsetWidth +
|
|
||||||
tagAndTagSpacing.value;
|
|
||||||
|
|
||||||
// the tag's offsetLeft before of prevTag
|
|
||||||
const beforePrevTagOffsetLeft =
|
|
||||||
(prevTag as any).offsetLeft - tagAndTagSpacing.value;
|
|
||||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
|
||||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth;
|
|
||||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
|
||||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
moveToTarget
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<el-scrollbar
|
|
||||||
ref="scrollContainer"
|
|
||||||
:vertical="false"
|
|
||||||
class="scroll-container"
|
|
||||||
@wheel.prevent="handleScroll"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</el-scrollbar>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.scroll-container {
|
|
||||||
.el-scrollbar__bar {
|
|
||||||
bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-scrollbar__wrap {
|
|
||||||
height: 49px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-container {
|
|
||||||
white-space: nowrap;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,356 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
getCurrentInstance,
|
|
||||||
nextTick,
|
|
||||||
ref,
|
|
||||||
watch,
|
|
||||||
onMounted,
|
|
||||||
ComponentInternalInstance
|
|
||||||
} from 'vue';
|
|
||||||
|
|
||||||
import path from 'path-browserify';
|
|
||||||
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
import ScrollPane from './ScrollPane.vue';
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
|
||||||
import { generateTitle } from '@/utils/i18n';
|
|
||||||
|
|
||||||
import { usePermissionStore } from '@/store/modules/permission';
|
|
||||||
import { useTagsViewStore, TagView } from '@/store/modules/tagsView';
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
const permissionStore = usePermissionStore();
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const visible = ref(false);
|
|
||||||
const selectedTag = ref({});
|
|
||||||
const scrollPaneRef = ref();
|
|
||||||
const left = ref(0);
|
|
||||||
const top = ref(0);
|
|
||||||
const affixTags = ref<TagView[]>([]);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
route,
|
|
||||||
() => {
|
|
||||||
addTags();
|
|
||||||
moveToCurrentTag();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
//初始化立即执行
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(visible, value => {
|
|
||||||
if (value) {
|
|
||||||
document.body.addEventListener('click', closeMenu);
|
|
||||||
} else {
|
|
||||||
document.body.removeEventListener('click', closeMenu);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function filterAffixTags(routes: any[], basePath = '/') {
|
|
||||||
let tags: TagView[] = [];
|
|
||||||
routes.forEach(route => {
|
|
||||||
if (route.meta && route.meta.affix) {
|
|
||||||
const tagPath = path.resolve(basePath, route.path);
|
|
||||||
tags.push({
|
|
||||||
fullPath: tagPath,
|
|
||||||
path: tagPath,
|
|
||||||
name: route.name,
|
|
||||||
meta: { ...route.meta }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.children) {
|
|
||||||
const childTags = filterAffixTags(route.children, route.path);
|
|
||||||
if (childTags.length >= 1) {
|
|
||||||
tags = tags.concat(childTags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initTags() {
|
|
||||||
const tags = filterAffixTags(permissionStore.routes);
|
|
||||||
affixTags.value = tags;
|
|
||||||
for (const tag of tags) {
|
|
||||||
// Must have tag name
|
|
||||||
if ((tag as TagView).name) {
|
|
||||||
tagsViewStore.addVisitedView(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addTags() {
|
|
||||||
if (route.name) {
|
|
||||||
tagsViewStore.addView(route);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveToCurrentTag() {
|
|
||||||
nextTick(() => {
|
|
||||||
for (const r of tagsViewStore.visitedViews) {
|
|
||||||
if (r.path === route.path) {
|
|
||||||
scrollPaneRef.value.moveToTarget(r);
|
|
||||||
// when query is different then update
|
|
||||||
if (r.fullPath !== route.fullPath) {
|
|
||||||
tagsViewStore.updateVisitedView(route);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isActive(tag: TagView) {
|
|
||||||
return tag.path === route.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAffix(tag: TagView) {
|
|
||||||
return tag.meta && tag.meta.affix;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFirstView() {
|
|
||||||
try {
|
|
||||||
return (
|
|
||||||
(selectedTag.value as TagView).fullPath ===
|
|
||||||
tagsViewStore.visitedViews[1].fullPath ||
|
|
||||||
(selectedTag.value as TagView).fullPath === '/index'
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLastView() {
|
|
||||||
try {
|
|
||||||
return (
|
|
||||||
(selectedTag.value as TagView).fullPath ===
|
|
||||||
tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1].fullPath
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshSelectedTag(view: TagView) {
|
|
||||||
tagsViewStore.delCachedView(view);
|
|
||||||
const { fullPath } = view;
|
|
||||||
nextTick(() => {
|
|
||||||
router.replace({ path: '/redirect' + fullPath }).catch(err => {
|
|
||||||
console.warn(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toLastView(visitedViews: TagView[], view?: any) {
|
|
||||||
const latestView = visitedViews.slice(-1)[0];
|
|
||||||
if (latestView && latestView.fullPath) {
|
|
||||||
router.push(latestView.fullPath);
|
|
||||||
} else {
|
|
||||||
// now the default is to redirect to the home page if there is no tags-view,
|
|
||||||
// you can adjust it according to your needs.
|
|
||||||
if (view.name === 'Dashboard') {
|
|
||||||
// to reload home page
|
|
||||||
router.replace({ path: '/redirect' + view.fullPath });
|
|
||||||
} else {
|
|
||||||
router.push('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSelectedTag(view: TagView) {
|
|
||||||
tagsViewStore.delView(view).then((res: any) => {
|
|
||||||
if (isActive(view)) {
|
|
||||||
toLastView(res.visitedViews, view);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeLeftTags() {
|
|
||||||
tagsViewStore.delLeftViews(selectedTag.value).then((res: any) => {
|
|
||||||
if (
|
|
||||||
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
|
||||||
) {
|
|
||||||
toLastView(res.visitedViews);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function closeRightTags() {
|
|
||||||
tagsViewStore.delRightViews(selectedTag.value).then((res: any) => {
|
|
||||||
if (
|
|
||||||
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
|
||||||
) {
|
|
||||||
toLastView(res.visitedViews);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeOtherTags() {
|
|
||||||
router.push(selectedTag.value);
|
|
||||||
tagsViewStore.delOtherViews(selectedTag.value).then(() => {
|
|
||||||
moveToCurrentTag();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeAllTags(view: TagView) {
|
|
||||||
tagsViewStore.delAllViews().then((res: any) => {
|
|
||||||
toLastView(res.visitedViews, view);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openMenu(tag: TagView, e: any) {
|
|
||||||
const menuMinWidth = 105;
|
|
||||||
const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
|
|
||||||
const offsetWidth = proxy?.$el.offsetWidth; // container width
|
|
||||||
const maxLeft = offsetWidth - menuMinWidth; // left boundary
|
|
||||||
const l = e.clientX - offsetLeft + 15; // 15: margin right
|
|
||||||
|
|
||||||
if (l > maxLeft) {
|
|
||||||
left.value = maxLeft;
|
|
||||||
} else {
|
|
||||||
left.value = l;
|
|
||||||
}
|
|
||||||
top.value = e.layerY + 10;
|
|
||||||
visible.value = true;
|
|
||||||
selectedTag.value = tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeMenu() {
|
|
||||||
visible.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleScroll() {
|
|
||||||
closeMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
initTags();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="w-full h-[54px] py-[10px]" style="background:#f0f2f5">
|
|
||||||
<scroll-pane ref="scrollPaneRef" class="tags-container" @scroll="handleScroll">
|
|
||||||
<router-link v-for="tag in tagsViewStore.visitedViews" :key="tag.path" :data-path="tag.path"
|
|
||||||
:class="isActive(tag) ? 'active' : ''" :to="{ path: tag.path, query: tag.query }" class="tags-item"
|
|
||||||
:style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }"
|
|
||||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''" @contextmenu.prevent="openMenu(tag, $event)">
|
|
||||||
{{ generateTitle(tag.name) }}
|
|
||||||
<span v-if="!isAffix(tag)" class="tags-item-remove" @click.prevent.stop="closeSelectedTag(tag)" >
|
|
||||||
<svg-icon icon-class="close" />
|
|
||||||
</span>
|
|
||||||
</router-link>
|
|
||||||
</scroll-pane>
|
|
||||||
|
|
||||||
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="tags-item-menu">
|
|
||||||
<li @click="refreshSelectedTag(selectedTag)">
|
|
||||||
<svg-icon icon-class="refresh" />
|
|
||||||
刷新
|
|
||||||
</li>
|
|
||||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
|
|
||||||
<svg-icon icon-class="close" />
|
|
||||||
关闭
|
|
||||||
</li>
|
|
||||||
<!-- <li @click="closeOtherTags">
|
|
||||||
<svg-icon icon-class="close_other" />
|
|
||||||
关闭其它
|
|
||||||
</li> -->
|
|
||||||
<li v-if="!isFirstView()" @click="closeLeftTags">
|
|
||||||
<svg-icon icon-class="close_left" />
|
|
||||||
关闭左侧
|
|
||||||
</li>
|
|
||||||
<li v-if="!isLastView()" @click="closeRightTags">
|
|
||||||
<svg-icon icon-class="close_right" />
|
|
||||||
关闭右侧
|
|
||||||
</li>
|
|
||||||
<li @click="closeAllTags(selectedTag)">
|
|
||||||
<svg-icon icon-class="close_all" />
|
|
||||||
关闭所有
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.tags-container {
|
|
||||||
|
|
||||||
.tags-item {
|
|
||||||
display: inline-block;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid #d8dce5;
|
|
||||||
background: #ffffff;
|
|
||||||
padding: 4px 10px;
|
|
||||||
margin: 0 0 0 5px;
|
|
||||||
border-radius: 2px;
|
|
||||||
box-shadow: 0px 0px 10px rgb(219 225 236);
|
|
||||||
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: var(--el-color-primary-light-9);
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
border-color: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-remove {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
line-height: 10px;
|
|
||||||
// text-align: center;
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 50%;
|
|
||||||
&:hover {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #ee7b7b;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-scrollbar__view) {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
display: -webkit-flex;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags-item-menu {
|
|
||||||
background: #fff;
|
|
||||||
z-index: 99;
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
|
|
||||||
|
|
||||||
li {
|
|
||||||
padding: 8px 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #eee;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,4 +1,2 @@
|
|||||||
export { default as Navbar } from './Navbar.vue';
|
export { default as Navbar } from './Navbar.vue';
|
||||||
export { default as AppMain } from './AppMain.vue';
|
export { default as AppMain } from './AppMain.vue';
|
||||||
export { default as Settings } from './Settings/index.vue';
|
|
||||||
export { default as TagsView } from './TagsView/index.vue';
|
|
||||||
|
|||||||
@ -1,257 +0,0 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
||||||
import { getMessageList, setMessageStatus, setAllMessageStatus, deleteMessageById } from '@/api/message/index'
|
|
||||||
import Page from '@/components/Pagination/page.vue'
|
|
||||||
const emit = defineEmits(['update:dialogVisible'])
|
|
||||||
const props = defineProps({
|
|
||||||
dialogVisible: {
|
|
||||||
type: Boolean
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const active = ref('0')
|
|
||||||
const loading = ref(false)
|
|
||||||
const tableData = ref([])
|
|
||||||
const tableDataSel = ref([])
|
|
||||||
const isLookOver = ref(false)
|
|
||||||
const info = ref({
|
|
||||||
title: '',
|
|
||||||
createtime: '',
|
|
||||||
content: ''
|
|
||||||
})
|
|
||||||
const queryParams = ref({
|
|
||||||
current: 1,
|
|
||||||
size: 10,
|
|
||||||
title: '',
|
|
||||||
type: '',
|
|
||||||
status: active.value,
|
|
||||||
startDate: '',
|
|
||||||
endDate: ''
|
|
||||||
});
|
|
||||||
const createtime = ref('')
|
|
||||||
const total = ref()
|
|
||||||
const handleClose = () => {
|
|
||||||
emit(
|
|
||||||
'update:dialogVisible',
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function handleSelectionChange(val: any) {
|
|
||||||
tableDataSel.value = val;
|
|
||||||
}
|
|
||||||
function reset() {
|
|
||||||
queryParams.value.title = ''
|
|
||||||
queryParams.value.type = ''
|
|
||||||
queryParams.value.startDate = ''
|
|
||||||
queryParams.value.endDate = ''
|
|
||||||
createtime.value = ''
|
|
||||||
init()
|
|
||||||
}
|
|
||||||
function init() {
|
|
||||||
loading.value = true
|
|
||||||
queryParams.value.status = active.value
|
|
||||||
if(createtime.value) {
|
|
||||||
queryParams.value.startDate = createtime.value[0]
|
|
||||||
queryParams.value.endDate = createtime.value[1]
|
|
||||||
}
|
|
||||||
if(Number(active.value) == 0) {
|
|
||||||
queryParams.value.title = ''
|
|
||||||
queryParams.value.type = ''
|
|
||||||
createtime.value = ''
|
|
||||||
}
|
|
||||||
getMessageList(queryParams.value).then((result) => {
|
|
||||||
loading.value = false
|
|
||||||
tableData.value = result.data.records
|
|
||||||
total.value = result.data.total
|
|
||||||
queryParams.value.size = result.data.size;
|
|
||||||
queryParams.value.current = result.data.current
|
|
||||||
}).catch(() => {
|
|
||||||
loading.value = false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function open(row: any) {
|
|
||||||
const id = [row.id]
|
|
||||||
setMessageStatus(id.join(',')).then(() => {
|
|
||||||
init()
|
|
||||||
})
|
|
||||||
isLookOver.value = true
|
|
||||||
info.value = JSON.parse(JSON.stringify(row))
|
|
||||||
}
|
|
||||||
function del(row:any) {
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
'确定删除此消息吗',
|
|
||||||
'删除提示',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then( async () => {
|
|
||||||
const id = [] as any[]
|
|
||||||
if(row.id == null) {
|
|
||||||
tableDataSel.value.forEach((element:any) => {
|
|
||||||
id.push(element.id)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
id.push(row.id)
|
|
||||||
}
|
|
||||||
deleteMessageById(id.join(',')).then(() => {
|
|
||||||
ElMessage({
|
|
||||||
message: "删除成功",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
init()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function tabClick() {
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
'确定将全部消息标记为已阅吗?',
|
|
||||||
'提示信息',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then( async () => {
|
|
||||||
setAllMessageStatus().then(() => {
|
|
||||||
ElMessage({
|
|
||||||
message: "已读成功",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
init()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
defineExpose({
|
|
||||||
init
|
|
||||||
});
|
|
||||||
onMounted(() => {
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<el-dialog class="dialog" draggable v-model="props.dialogVisible" width="1100" top="3vh" append-to-body
|
|
||||||
:before-close="handleClose" :show-close="!isLookOver">
|
|
||||||
<el-tabs v-show="!isLookOver" v-model="active" @tab-change="init" type="border-card">
|
|
||||||
<el-tab-pane label="最新消息">
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="历史消息">
|
|
||||||
<div class="flex justify-between mb-4">
|
|
||||||
<div class="flex items-center ">
|
|
||||||
<el-input v-model="queryParams.title" placeholder="请输入消息标题" clearable style="width: 200px;margin-right:10px" @keyup.enter="init" />
|
|
||||||
<el-select v-model="queryParams.type" clearable default-first-option placeholder="消息类型" style="width: 200px;margin-right:10px" @change="init">
|
|
||||||
<el-option label="定时任务" value="1" />
|
|
||||||
<el-option label="工作流触发" value="2" />
|
|
||||||
<el-option label="人工任务" value="3" />
|
|
||||||
</el-select>
|
|
||||||
<el-date-picker
|
|
||||||
v-model="createtime"
|
|
||||||
type="daterange"
|
|
||||||
range-separator="-"
|
|
||||||
start-placeholder="开始时间"
|
|
||||||
end-placeholder="结束时间"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
@change="init"
|
|
||||||
/>
|
|
||||||
<el-button type="primary" style="margin-left: 10px;" @click="init">搜索</el-button>
|
|
||||||
<el-button @click="reset">重置</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button :disabled="tableDataSel.length==0" type="primary" @click="del">
|
|
||||||
<span>删除</span>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-table v-loading="loading" :data="tableData" style="width: 100%; margin-bottom: 20px;overflow:hidden;" height="400px"
|
|
||||||
row-key="id" border @selection-change="handleSelectionChange" default-expand-all
|
|
||||||
:header-cell-style="{ background: 'rgb(250 250 250)', height: '50px' }">
|
|
||||||
<el-table-column v-if="active == '1'" type="selection" align="center" width="50" />
|
|
||||||
<el-table-column type="index" label="序号" align="center" width="70" />
|
|
||||||
<el-table-column prop="title" label="消息标题">
|
|
||||||
<template #default="scope">
|
|
||||||
<span class="title" @click="open(scope.row)">{{ scope.row.title }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="type" label="消息类型" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ scope.row.type == '1' ? '定时任务' : (scope.row.type == '2' ? '工作流触发' : '人工触发') }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="senderName" label="发送者" width="120"></el-table-column>
|
|
||||||
<el-table-column prop="createtime" label="通知时间" width="180"></el-table-column>
|
|
||||||
<el-table-column label="状态" width="180">
|
|
||||||
<template #default="scope">
|
|
||||||
<span :style="{color:scope.row.status == '2'?'rgb(55, 196, 122)':scope.row.status == '9'?'rgb(255, 153, 0)':''}">{{ scope.row.status != '1'?'●':''}} {{ scope.row.status == '1' ? '初始创建' : (scope.row.status == '2' ? '消息已阅' : '消息过期') }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="active == '1'" fixed="right" align="center" label="操作" width="80">
|
|
||||||
<template #default="scope">
|
|
||||||
<img src="@/assets/MenuIcon/lbcz_sc.png" alt="" @click="del(scope.row)"
|
|
||||||
title="删除" style="cursor: pointer;margin-left:10px">
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<div class="flex justify-between items-center mb-4">
|
|
||||||
<Page :total="total" v-model:size="queryParams.size" v-model:current="queryParams.current" @pagination="init()" ></Page>
|
|
||||||
<el-button v-if="active == '0'" type="primary" @click="tabClick">
|
|
||||||
<span>全部已读</span>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</el-tabs>
|
|
||||||
<div v-show="isLookOver" class="p-6">
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="font-black" style="color: #000;font-size: 20px;">{{ info.title }}</span>
|
|
||||||
<el-button type="primary" plain @click="isLookOver = false">
|
|
||||||
返回
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
<div style="height:50px;line-height: 50px;border-bottom: 1px solid #e4e4e4;">
|
|
||||||
<span style="color: rgb(148, 148, 148);font-size:14px;">{{ info.createtime }}</span>
|
|
||||||
</div>
|
|
||||||
<div style="color:rgb(80, 80, 80);font-size: 14px;padding-top:15px;overflow:auto">
|
|
||||||
{{ info.content }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
<style lang="scss">
|
|
||||||
.title{
|
|
||||||
color:rgb(64, 158, 255);
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover{
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dialog {
|
|
||||||
height: 500px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-tabs__content,
|
|
||||||
.el-tabs--border-card {
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-tabs__item {
|
|
||||||
height: 50px !important;
|
|
||||||
line-height: 50px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__headerbtn {
|
|
||||||
z-index: 100;
|
|
||||||
top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__header {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__body {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,134 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, watchEffect } from 'vue';
|
import { computed } from "vue";
|
||||||
import { useWindowSize } from '@vueuse/core';
|
import { AppMain, Navbar } from "./components/index";
|
||||||
import { AppMain, Navbar, Settings, TagsView } from './components/index';
|
|
||||||
import Sidebar from './components/Sidebar/index.vue';
|
|
||||||
import RightPanel from '@/components/RightPanel/index.vue';
|
|
||||||
import Hamburger from '@/components/Hamburger/index.vue';
|
|
||||||
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from "@/store/modules/app";
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
|
||||||
|
|
||||||
const { width } = useWindowSize();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 响应式布局容器固定宽度
|
|
||||||
*
|
|
||||||
* 大屏(>=1200px)
|
|
||||||
* 中屏(>=992px)
|
|
||||||
* 小屏(>=768px)
|
|
||||||
*/
|
|
||||||
const WIDTH = 992;
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
|
||||||
const showTagsView = computed(() => settingsStore.tagsView);
|
|
||||||
const showSettings = computed(() => settingsStore.showSettings);
|
|
||||||
|
|
||||||
const classObj = computed(() => ({
|
const classObj = computed(() => ({
|
||||||
hideSidebar: !appStore.sidebar.opened,
|
|
||||||
openSidebar: appStore.sidebar.opened,
|
|
||||||
withoutAnimation: appStore.sidebar.withoutAnimation,
|
withoutAnimation: appStore.sidebar.withoutAnimation,
|
||||||
mobile: appStore.device === 'mobile'
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (width.value < WIDTH) {
|
|
||||||
appStore.toggleDevice('mobile');
|
|
||||||
appStore.closeSideBar(true);
|
|
||||||
} else {
|
|
||||||
appStore.toggleDevice('desktop');
|
|
||||||
|
|
||||||
if (width.value >= 1200) {
|
|
||||||
//大屏
|
|
||||||
appStore.openSideBar(true);
|
|
||||||
} else {
|
|
||||||
appStore.closeSideBar(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleOutsideClick() {
|
|
||||||
appStore.closeSideBar(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSideBar() {
|
|
||||||
appStore.toggleSidebar(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="classObj" class="app-wrapper">
|
<div :class="classObj" class="app-wrapper">
|
||||||
<!-- 手机设备 && 侧边栏 → 显示遮罩层 -->
|
<navbar />
|
||||||
<div
|
<div class="main-container">
|
||||||
class="drawer-bg"
|
|
||||||
v-if="classObj.mobile && classObj.openSidebar"
|
|
||||||
@click="handleOutsideClick"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<Sidebar class="sidebar-container" />
|
|
||||||
<navbar :class="{ 'fixed-header': fixedHeader }" />
|
|
||||||
<div v-show="fixedHeader" style="height:60px"></div>
|
|
||||||
<hamburger
|
|
||||||
:is-active="appStore.sidebar.opened"
|
|
||||||
@toggleClick="toggleSideBar"
|
|
||||||
/>
|
|
||||||
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
|
||||||
<tags-view v-if="showTagsView" />
|
|
||||||
|
|
||||||
<!--主页面-->
|
<!--主页面-->
|
||||||
<app-main />
|
<app-main />
|
||||||
|
|
||||||
<!-- 设置面板 -->
|
|
||||||
<RightPanel v-if="showSettings">
|
|
||||||
<settings />
|
|
||||||
</RightPanel>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@use '@/styles/mixin.scss' as mixin;
|
@use '@/styles/mixin.scss' as mixin;
|
||||||
@use '@/styles/variables.module.scss' as variables;
|
|
||||||
|
|
||||||
.app-wrapper {
|
.app-wrapper {
|
||||||
// @include clearfix;
|
// @include clearfix;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&.mobile.openSidebar {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.drawer-bg {
|
</style>
|
||||||
background: #000;
|
|
||||||
opacity: 0.3;
|
|
||||||
width: 100%;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 98;
|
|
||||||
width: 100%;
|
|
||||||
transition: width 0.28s;
|
|
||||||
background: #ffffff;
|
|
||||||
}
|
|
||||||
.hideSidebar .fixed-header {
|
|
||||||
// width: calc(100% - 54px);
|
|
||||||
width:100%;
|
|
||||||
}
|
|
||||||
.mobile .fixed-header {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
|||||||
import Pagination from '@/components/Pagination/index.vue';
|
import Pagination from '@/components/Pagination/index.vue';
|
||||||
import '@/permission';
|
import '@/permission';
|
||||||
|
|
||||||
|
import Antd from 'ant-design-vue'
|
||||||
|
import 'ant-design-vue/dist/reset.css' // Ant Design 全局样式重置
|
||||||
// 引入svg注册脚本
|
// 引入svg注册脚本
|
||||||
import 'virtual:svg-icons-register';
|
import 'virtual:svg-icons-register';
|
||||||
|
|
||||||
@ -39,6 +41,7 @@ app
|
|||||||
.component('Pagination', Pagination)
|
.component('Pagination', Pagination)
|
||||||
.use(router)
|
.use(router)
|
||||||
.use(ElementPlus)
|
.use(ElementPlus)
|
||||||
|
.use(Antd)
|
||||||
.use(WujieVue)
|
.use(WujieVue)
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.mount('#app');
|
.mount('#app');
|
||||||
|
|||||||
162
frontend/src/modules/dianxingcuoshijieshao/index.vue
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="carousel-container">
|
||||||
|
<SidePanelItem title="典型设施介绍">
|
||||||
|
<div v-if="carouselData.length > 0" class="carousel-wrapper">
|
||||||
|
<a-carousel v-model:current="currentIndex" autoplay class="tech-carousel"
|
||||||
|
:dot-style="{ bottom: '0px' }">
|
||||||
|
<div v-for="(item, index) in carouselData" :key="index" class="carousel-item">
|
||||||
|
<div class="image-container">
|
||||||
|
<img :src="item.image" :alt="item.title" class="carousel-image" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-carousel>
|
||||||
|
|
||||||
|
<!-- 文字描述区域 -->
|
||||||
|
<div v-if="carouselData[currentIndex]" class="description-container">
|
||||||
|
<p class="item-description">{{ carouselData[currentIndex].description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<a-empty />
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'dianxingcuoshijieshao'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当前轮播索引
|
||||||
|
const currentIndex = ref(0);
|
||||||
|
|
||||||
|
// 轮播图数据
|
||||||
|
const carouselData = ref([
|
||||||
|
{
|
||||||
|
title: '叠梁门',
|
||||||
|
description: '叠梁门是一种用于调节水库流量和控制水位的设施。在低温季节,可以通过控制叠梁门的开闭程度来调节流量,减少低温水体的进...',
|
||||||
|
image: 'https://img.shetu66.com/2024/02/19/170835076078361368.png' // 替换为实际图片路径
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '环保设施',
|
||||||
|
description: '现代化环保设施,采用先进技术,有效处理工业废水废气,实现达标排放,保护生态环境...',
|
||||||
|
image: 'https://img.shetu66.com/2024/02/19/170835076078361368.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '智能监控',
|
||||||
|
description: '24 小时智能监控系统,实时监测设备运行状态,确保设施安全稳定运行...',
|
||||||
|
image: 'https://img.shetu66.com/2024/02/19/170835076078361368.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '水处理系统',
|
||||||
|
description: '高效水处理系统,通过多级过滤和生物处理,实现水资源循环利用...',
|
||||||
|
image: 'https://img.shetu66.com/2024/02/19/170835076078361368.png'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
// 可以在这里加载实际的图片数据
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.carousel-container {
|
||||||
|
width: 415px;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
:deep(.ant-carousel) {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.slick-slide {
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slick-dots {
|
||||||
|
bottom: 0px !important;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
margin: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background-color: #005293;
|
||||||
|
opacity: 0.3;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.slick-active button {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
opacity: 1;
|
||||||
|
background-color: #005293;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #f5f0e1; // 米黄色背景
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-image {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
object-fit: contain; // 保持图片比例,完整显示
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-container {
|
||||||
|
padding: 12px 0 0 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f1f1f;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-description {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #595959;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.8;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
134
frontend/src/modules/huanbaoMod/index.vue
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<SidePanelItem title="环保设施情况">
|
||||||
|
<div class="facility-grid">
|
||||||
|
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
||||||
|
<div class="facility-icon">
|
||||||
|
<component :is="facility.icon" />
|
||||||
|
</div>
|
||||||
|
<div class="facility-info">
|
||||||
|
<div class="facility-name">{{ facility.name }}</div>
|
||||||
|
<div> <span class="facility-count">{{ facility.count }}</span> 个</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import {
|
||||||
|
// DropletOutlined,
|
||||||
|
// ThermometerOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
FileTextOutlined,
|
||||||
|
// FishOutlined,
|
||||||
|
EnvironmentOutlined,
|
||||||
|
HeartOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'huanbaoMod'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设施数据
|
||||||
|
const facilities = ref([
|
||||||
|
{
|
||||||
|
name: '生态流量泄放设施',
|
||||||
|
count: 145,
|
||||||
|
// icon: DropletOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '低温水减缓设施',
|
||||||
|
count: 24,
|
||||||
|
// icon: ThermometerOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '栖息地',
|
||||||
|
count: 142,
|
||||||
|
icon: HomeOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '过鱼设施',
|
||||||
|
count: 60,
|
||||||
|
icon: FileTextOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '鱼类增殖站',
|
||||||
|
count: 69,
|
||||||
|
// icon: FishOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '珍稀植物园',
|
||||||
|
count: 41,
|
||||||
|
icon: EnvironmentOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '动物救助站',
|
||||||
|
count: 4,
|
||||||
|
icon: HeartOutlined
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.facility-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
margin-right: 8px;
|
||||||
|
background: #2f6b98;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-name {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
// font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-count {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #2f6b98;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
133
frontend/src/modules/huanbaozdjcgzkzQK/index.vue
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<SidePanelItem title="环保自动监测工作开展情况">
|
||||||
|
<div class="facility-grid">
|
||||||
|
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
||||||
|
<div class="facility-icon">
|
||||||
|
<component :is="facility.icon" />
|
||||||
|
</div>
|
||||||
|
<div class="facility-info">
|
||||||
|
<div class="facility-name">{{ facility.name }}</div>
|
||||||
|
<div> <span class="facility-count">{{ facility.count }}</span> 个</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import {
|
||||||
|
// DropletOutlined,
|
||||||
|
// ThermometerOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
FileTextOutlined,
|
||||||
|
// FishOutlined,
|
||||||
|
EnvironmentOutlined,
|
||||||
|
HeartOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'huanbaoMod'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设施数据
|
||||||
|
const facilities = ref([
|
||||||
|
{
|
||||||
|
name: '生态流量泄放设施',
|
||||||
|
count: 145,
|
||||||
|
// icon: DropletOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '低温水减缓设施',
|
||||||
|
count: 24,
|
||||||
|
// icon: ThermometerOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '栖息地',
|
||||||
|
count: 142,
|
||||||
|
icon: HomeOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '过鱼设施',
|
||||||
|
count: 60,
|
||||||
|
icon: FileTextOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '鱼类增殖站',
|
||||||
|
count: 69,
|
||||||
|
// icon: FishOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '珍稀植物园',
|
||||||
|
count: 41,
|
||||||
|
icon: EnvironmentOutlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '动物救助站',
|
||||||
|
count: 4,
|
||||||
|
icon: HeartOutlined
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.facility-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
margin-right: 8px;
|
||||||
|
background: #2f6b98;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-name {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
// font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-count {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #2f6b98;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
35
frontend/src/modules/jidiInfoMod/index.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<SidePanelItem title="基本情况介绍" :shrink="true">
|
||||||
|
<p v-if="title_text" class="basic_body1">{{ title_text }}</p>
|
||||||
|
<div v-else class="zanwushujv"> <a-empty /></div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'jidiInfoMod'
|
||||||
|
});
|
||||||
|
const title_text = ref('我国水能资源丰富,主要集中在金沙江、长江上游、雅砻江、黄河上游、大渡河、南盘江-红水河、乌江和西南诸河等流域,总技术可开发量约3.81亿kW,占全国技术可开发量的55.5%。截至2023年年底,雅砻江、金沙江、大渡河、乌江、长江上游、南盘江-红水河等流域已建和在建比例超80%,水能资源开发程度较高,剩余待开发水利资源主要集中在西南诸河,发展潜力巨大。')
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.zanwushujv {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basic_body1 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
142
frontend/src/modules/jidiSelectorMod.vue
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
const loading = ref(false);
|
||||||
|
const isOpen = ref(true);
|
||||||
|
const jidiData = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "当前全部",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "水电基地2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "水电基地3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "水电基地4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "水电基地5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "水电基地6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "水电基地7",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: "水电基地8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: "水电基地9",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: "水电基地10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: "水电基地11",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const jidiDataNum = ref(9);
|
||||||
|
const selectedItem: any = ref(1);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="jidiSelectorMod">
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<div
|
||||||
|
class="qgc-dropdown-select"
|
||||||
|
@mouseenter="jidiDataNum = jidiData.length"
|
||||||
|
@mouseleave="jidiDataNum = 9"
|
||||||
|
>
|
||||||
|
<div class="title" @click="isOpen = !isOpen">
|
||||||
|
<div>水电基地</div>
|
||||||
|
<div style="padding-right: 5px;" :style="{ transform: !isOpen ? 'rotate(180deg)' : 'rotate(0deg)' }">
|
||||||
|
<i class="icon iconfont icon-topOutline"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isOpen"
|
||||||
|
class="item"
|
||||||
|
v-for="i in jidiData.slice(0, jidiDataNum)"
|
||||||
|
:class="{ selected: selectedItem === i.id }"
|
||||||
|
@click="selectedItem = i.id"
|
||||||
|
>
|
||||||
|
<i class="icon iconfont icon-hydroPower"></i>
|
||||||
|
<span style="margin-left: 10px">{{ i.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use "@/styles/variables.module.scss" as *;
|
||||||
|
.jidiSelectorMod {
|
||||||
|
width: 175px;
|
||||||
|
max-height: 941px;
|
||||||
|
border: 1px solid #cedce8;
|
||||||
|
border-radius: 1px;
|
||||||
|
background-color: #e5edf3;
|
||||||
|
padding: 4px;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 1px;
|
||||||
|
margin: 16px 0 0 16px;
|
||||||
|
z-index: 99;
|
||||||
|
pointer-events: auto;
|
||||||
|
.qgc-dropdown-select {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 941px;
|
||||||
|
overflow: auto;
|
||||||
|
.title {
|
||||||
|
height: 26.14px;
|
||||||
|
padding: 0 2px;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
color: $primary-title-color;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.icon-topOutline {
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
scale: 0.6;
|
||||||
|
position: relative;
|
||||||
|
top: -3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
margin: 4px 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #acc4d6;
|
||||||
|
line-height: 30px;
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: #fff;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover,
|
||||||
|
&.selected {
|
||||||
|
background-color: #2f6b98;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
190
frontend/src/modules/shengtaidabiaoMod/index.vue
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<SidePanelItem title="生态流量达标情况" :clickprompt="obj">
|
||||||
|
<div class="body_topOne">
|
||||||
|
<a-radio-group v-model:value="mode">
|
||||||
|
<a-radio-button value="top">逐时</a-radio-button>
|
||||||
|
<a-radio-button value="left">日</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<div class="title_text">≥95%座数/总座数</div>
|
||||||
|
</div>
|
||||||
|
<a-spin :spinning="spinning">
|
||||||
|
<div>
|
||||||
|
<div v-for="el in datalist">
|
||||||
|
<div :key='el.key'>
|
||||||
|
<div class="boy_one">
|
||||||
|
<div style="flex: 1; white-space: nowrap;margin-right: 5px;">{{ el.name }}</div>
|
||||||
|
<img style="flex: 1;" src="@/assets/components/fgx.svg" alt="">
|
||||||
|
</div>
|
||||||
|
<div class="body_zhu">
|
||||||
|
<div class="body_biao">
|
||||||
|
<div v-for="value in allArr">
|
||||||
|
<div :key="value" class="nei_body">
|
||||||
|
<div v-if="value < (el?.key == null ? 1 : el.qecTCnt == 0 ? 0 : el.qecCnt / el.qecTCnt) * 20"
|
||||||
|
class="little_body" :style="{ backgroundColor: el.color }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="body_text">
|
||||||
|
{{ el.qecCnt }}/{{ el.qecTCnt }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
|
||||||
|
|
||||||
|
</SidePanelItem>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
|
||||||
|
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'shengtaidabiaoMod'
|
||||||
|
});
|
||||||
|
const obj =
|
||||||
|
{
|
||||||
|
show: true,
|
||||||
|
value: '1、统计电站范围:接入过生态流量数据的电站,2、当来水不足时,生态流量不小于入库流量判定为达标,3、“≥95%座数”表示统计电站范围内时段达标率大于等于95%的电站数量',
|
||||||
|
}
|
||||||
|
const mode = ref('top');
|
||||||
|
const dataArr: any = ref([
|
||||||
|
{
|
||||||
|
name: '生态环境部门要求',
|
||||||
|
qecCnt: '41', // 座数
|
||||||
|
qecTCnt: '55', //总座数
|
||||||
|
type: 1
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '水利部门要求',
|
||||||
|
qecCnt: '41', // 座数
|
||||||
|
qecTCnt: '55', //总座数
|
||||||
|
type: 2
|
||||||
|
}
|
||||||
|
])
|
||||||
|
const list: any = ref(
|
||||||
|
[
|
||||||
|
{ name: '生态环境部门要求', type: 1, color: '#77C300', key: 'qecInterval' },
|
||||||
|
{ name: '水利部门要求', type: 2, color: '#56C3E3', key: 'mwrInterval' },
|
||||||
|
{ name: '多年平均流量 10%', type: 3, color: '#F76B01', key: 'avqInterval' },
|
||||||
|
{ name: '无生态流量要求', type: 4, color: '#B4B4B4', key: null },
|
||||||
|
]
|
||||||
|
)
|
||||||
|
const datalist: any = ref([])
|
||||||
|
const allArr: any = ref(Array.from({ length: 20 }, (_, i) => i))
|
||||||
|
const spinning = ref(false)
|
||||||
|
// 根据 type 将 list 中的数据合并到 dataArr 中
|
||||||
|
const setStyle = () => {
|
||||||
|
if (dataArr.value.length == 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
dataArr.value.forEach((item: any) => {
|
||||||
|
// 在 list 中找到 type 匹配的数据
|
||||||
|
const matched = list.value.find((listItem: any) => listItem.type === item.type)
|
||||||
|
if (matched) {
|
||||||
|
// 合并数据,dataArr 的属性优先
|
||||||
|
datalist.value.push({
|
||||||
|
...matched,
|
||||||
|
...item,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(datalist.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
setStyle()
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.body_topOne {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.title_text {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.boy_one {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 5px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body_zhu {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.body_biao {
|
||||||
|
width: 100%;
|
||||||
|
height: 52px;
|
||||||
|
border: 1px solid #ccdae7;
|
||||||
|
border-radius: 2px;
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.nei_body {
|
||||||
|
width: 12px;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid #ccdae7;
|
||||||
|
padding: 2px;
|
||||||
|
margin-left: 4px;
|
||||||
|
|
||||||
|
.little_body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body_text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 90px;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-left: 6px;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-group {
|
||||||
|
// border: 3px solid #2f6b98 !important;
|
||||||
|
// border-radius: 10px !important;
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked {
|
||||||
|
border: 1px solid #2f6b98 !important;
|
||||||
|
background-color: #2f6b98 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper {
|
||||||
|
border: 2px solid #2f6b98 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked :before {
|
||||||
|
background-color: #2f6b98 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
215
frontend/src/modules/shengtaidabiaoTwoMod/index.vue
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<SidePanelItem title="生态流量达标情况" :clickprompt="obj">
|
||||||
|
<div class="body_topOne">
|
||||||
|
<a-radio-group v-model:value="mode">
|
||||||
|
<a-radio-button value="top">按基地</a-radio-button>
|
||||||
|
<a-radio-button value="left">按调节性能</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<a-spin :spinning="spinning">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, watch } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'shengtaidabiaoMod'
|
||||||
|
});
|
||||||
|
const obj =
|
||||||
|
{
|
||||||
|
show: true,
|
||||||
|
value: '1、统计电站范围:接入过生态流量数据的电站,2、当来水不足时,生态流量不小于入库流量判定为达标,3、"≥95% 座数"表示统计电站范围内时段达标率大于等于 95% 的电站数量',
|
||||||
|
}
|
||||||
|
const mode = ref('top');
|
||||||
|
const spinning = ref(false)
|
||||||
|
const chartRef = ref<HTMLElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const categoryData = [
|
||||||
|
'其他', '闽浙赣', '澜沧江干流', '东北', '南盘江·红水河',
|
||||||
|
'黄河中游干流', '黄河上游干流', '湘西', '长江上游干流',
|
||||||
|
'乌江干流', '大渡河干流', '雅砻江干流', '金沙江干流'
|
||||||
|
];
|
||||||
|
const currentData = Array(13).fill(0).map(() => Math.random() * 5 + 86);
|
||||||
|
const lastYearData = Array(13).fill(0).map(() => Math.random() * 5 + 86);
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
|
||||||
|
// 如果实例存在,先销毁
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
},
|
||||||
|
formatter: function(params: any) {
|
||||||
|
let result = params[0].name + '<br/>';
|
||||||
|
params.forEach((param: any) => {
|
||||||
|
const percentage = param.value.toFixed(2) + '%';
|
||||||
|
result += param.marker + param.seriesName + ' <b>' + percentage + '</b><br/>';
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['当前', '去年同期'],
|
||||||
|
top: 0,
|
||||||
|
left: 'center',
|
||||||
|
itemWidth: 20,
|
||||||
|
itemHeight: 10,
|
||||||
|
textStyle: {
|
||||||
|
color: '#666'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
top: '50',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: 0,
|
||||||
|
filterMode: 'empty',
|
||||||
|
zoomOnMouseWheel: true,
|
||||||
|
moveOnMouseMove: false,
|
||||||
|
moveOnMouseWheel: true,
|
||||||
|
start: 0,
|
||||||
|
end: 100,
|
||||||
|
minValueSpan: 0,
|
||||||
|
maxValueSpan: 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
xAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 80,
|
||||||
|
max: 100,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E8E8E8',
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666',
|
||||||
|
formatter: '{value}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: categoryData,
|
||||||
|
inverse: true,
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666',
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 0,
|
||||||
|
rotate: 0,
|
||||||
|
margin: 10
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#666'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#666'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '当前',
|
||||||
|
type: 'bar',
|
||||||
|
data: currentData,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#5470C6'
|
||||||
|
},
|
||||||
|
barWidth: 12,
|
||||||
|
barGap: '30%'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '去年同期',
|
||||||
|
type: 'bar',
|
||||||
|
data: lastYearData,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#91CC75'
|
||||||
|
},
|
||||||
|
barWidth: 12,
|
||||||
|
barGap: '30%'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
initChart();
|
||||||
|
|
||||||
|
// 监听窗口大小变化
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听 mode 变化
|
||||||
|
watch(mode, () => {
|
||||||
|
// 这里可以根据 mode 的值重新加载数据
|
||||||
|
initChart();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ant-radio-group {
|
||||||
|
// border: 3px solid #2f6b98 !important;
|
||||||
|
// border-radius: 10px !important;
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked {
|
||||||
|
border: 1px solid #2f6b98 !important;
|
||||||
|
background-color: #2f6b98 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper {
|
||||||
|
border: 2px solid #2f6b98 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked :before {
|
||||||
|
background-color: #2f6b98 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
297
frontend/src/modules/shuidianhuangjingjieruMod/DataTable.vue
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
<!-- DataTable.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="data-table-container">
|
||||||
|
<a-table :columns="columns" :data-source="tableData" :pagination="false" size="middle" :customRow="customRow"
|
||||||
|
class="custom-table">
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
|
||||||
|
|
||||||
|
// 定义组件名
|
||||||
|
defineOptions({
|
||||||
|
name: 'DataTable'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const columns: ColumnsType = [
|
||||||
|
{
|
||||||
|
title: '基地名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
fixed: 'left',
|
||||||
|
// width: 65,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '装机容量 (万 kW)',
|
||||||
|
key: 'capacity',
|
||||||
|
align: 'center',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: '总计',
|
||||||
|
dataIndex: 'total',
|
||||||
|
key: 'total',
|
||||||
|
width: 65,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '已建',
|
||||||
|
dataIndex: 'built',
|
||||||
|
key: 'built',
|
||||||
|
width: 65,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '在建',
|
||||||
|
dataIndex: 'building',
|
||||||
|
key: 'building',
|
||||||
|
width: 65,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '未建',
|
||||||
|
dataIndex: 'unbuilt',
|
||||||
|
key: 'unbuilt',
|
||||||
|
width: 65,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableData = ref([
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
name: '金沙江干流',
|
||||||
|
total: '7952.00',
|
||||||
|
built: '6370.00',
|
||||||
|
building: '902.00',
|
||||||
|
unbuilt: '680.00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
name: '雅砻江干流',
|
||||||
|
total: '2773.65',
|
||||||
|
built: '1920.00',
|
||||||
|
building: '492.00',
|
||||||
|
unbuilt: '361.65'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
name: '大渡河干流',
|
||||||
|
total: '2689.65',
|
||||||
|
built: '1925.65',
|
||||||
|
building: '534.00',
|
||||||
|
unbuilt: '230.00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
name: '乌江干流',
|
||||||
|
total: '1181.36',
|
||||||
|
built: '1133.36',
|
||||||
|
building: '48.00',
|
||||||
|
unbuilt: '0.00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5',
|
||||||
|
name: '长江上游干流',
|
||||||
|
total: '3212.65',
|
||||||
|
built: '2523.65',
|
||||||
|
building: '0.00',
|
||||||
|
unbuilt: '689.00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '6',
|
||||||
|
name: '湘西',
|
||||||
|
total: '1054.32',
|
||||||
|
built: '959.30',
|
||||||
|
building: '38.52',
|
||||||
|
unbuilt: '56.65'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '7',
|
||||||
|
name: '黄河上游干流',
|
||||||
|
total: '2794.59',
|
||||||
|
built: '1749.89',
|
||||||
|
building: '0.00',
|
||||||
|
unbuilt: '1044.65'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '8',
|
||||||
|
name: '黄河中游干流',
|
||||||
|
total: '835.65',
|
||||||
|
built: '401.65',
|
||||||
|
building: '0.00',
|
||||||
|
unbuilt: '434.00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '9',
|
||||||
|
name: '南盘江 - 红水河',
|
||||||
|
total: '1271.00',
|
||||||
|
built: '1271.00',
|
||||||
|
building: '0.00',
|
||||||
|
unbuilt: '0.00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '10',
|
||||||
|
name: '东北',
|
||||||
|
total: '1331.95',
|
||||||
|
built: '749.05',
|
||||||
|
building: '0.00',
|
||||||
|
unbuilt: '582.90'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '11',
|
||||||
|
name: '澜沧江干流',
|
||||||
|
total: '2535.00',
|
||||||
|
built: '2275.00',
|
||||||
|
building: '260.00',
|
||||||
|
unbuilt: '0.00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '12',
|
||||||
|
name: '怒江干流',
|
||||||
|
total: '3138.00',
|
||||||
|
built: '0.00',
|
||||||
|
building: '102.00',
|
||||||
|
unbuilt: '3036.00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '13',
|
||||||
|
name: '闽浙赣',
|
||||||
|
total: '962.08',
|
||||||
|
built: '920.68',
|
||||||
|
building: '0.00',
|
||||||
|
unbuilt: '41.40'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '14',
|
||||||
|
name: '其他',
|
||||||
|
total: '7460.57',
|
||||||
|
built: '7273.27',
|
||||||
|
building: '121.90',
|
||||||
|
unbuilt: '65.40'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '15',
|
||||||
|
name: '总计',
|
||||||
|
total: '7460.57',
|
||||||
|
built: '7273.27',
|
||||||
|
building: '121.90',
|
||||||
|
unbuilt: '65.40'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 自定义行样式
|
||||||
|
const customRow = (record: any, index: number) => {
|
||||||
|
return {
|
||||||
|
style: {
|
||||||
|
backgroundColor: index % 2 === 1 ? '#fafafa' : '#ffffff'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.data-table-container {
|
||||||
|
padding: 0;
|
||||||
|
background: #fff;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-table {
|
||||||
|
:deep(.ant-table) {
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
|
||||||
|
.ant-table-thead {
|
||||||
|
>tr {
|
||||||
|
>th {
|
||||||
|
background-color: #e5eff8 !important;
|
||||||
|
color: #2f6b98;
|
||||||
|
// font-weight: 600;
|
||||||
|
border: 1px solid #e8e8e8 !important;
|
||||||
|
padding: 6px 8px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
background-color: #e5eff8 !important;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
>th {
|
||||||
|
background-color: #e5eff8 !important;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
background-color: #e5eff8 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-tbody {
|
||||||
|
>tr {
|
||||||
|
>td {
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
padding: 6px 8px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
text-align: left;
|
||||||
|
// font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e6f7ff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-footer {
|
||||||
|
padding: 0;
|
||||||
|
background-color: #fafafa;
|
||||||
|
|
||||||
|
.table-footer {
|
||||||
|
.footer-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 8px;
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
background-color: #fafafa;
|
||||||
|
|
||||||
|
.footer-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
// font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
flex: 0 0 140px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除默认的 footer 单元格样式
|
||||||
|
.ant-table-footer::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
53
frontend/src/modules/shuidianhuangjingjieruMod/index.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="basic_body">
|
||||||
|
<SidePanelItem title="大中型水电开发及环境监测数据接入情况" :prompt="prompt">
|
||||||
|
<div>
|
||||||
|
<div class="body_top">
|
||||||
|
<!-- {/* 水电开发情况图表 */} -->
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<div>水电开发情况</div>
|
||||||
|
<div style="color: #757575;font-size: 12px;line-height: 16px;">图释:数量(内)/装机容量(外)</div>
|
||||||
|
<div>
|
||||||
|
<DevelopStatusChart />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center" }} -->
|
||||||
|
<div style="flex: 1;display: flex; flex-direction: column; align-items: center;">
|
||||||
|
<EngEnvironmentData />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DataTable />
|
||||||
|
</div>
|
||||||
|
<!-- <div v-else class="zanwushujv"> <a-empty /></div> -->
|
||||||
|
</SidePanelItem>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import DevelopStatusChart from "@/components/developStatusChart/index.vue"
|
||||||
|
import EngEnvironmentData from "@/components/engEnvironmentData/index.vue"
|
||||||
|
import DataTable from "./DataTable.vue"
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'shuidianhuangjingjieruMod'
|
||||||
|
});
|
||||||
|
const prompt = ref({
|
||||||
|
show: true,
|
||||||
|
value: '统计大中型已建,在建水电站',
|
||||||
|
|
||||||
|
})
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.body_top {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -5,20 +5,35 @@ import { usePermissionStoreHook } from '@/store/modules/permission';
|
|||||||
|
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
NProgress.configure({ showSpinner: false }); // 进度条
|
NProgress.configure({ showSpinner: false });
|
||||||
|
|
||||||
const permissionStore = usePermissionStoreHook();
|
const permissionStore = usePermissionStoreHook();
|
||||||
|
|
||||||
// 白名单路由
|
// 白名单路由
|
||||||
const whiteList = ['/login'];
|
const whiteList = ['/login'];
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
// 查找第一个可用路由
|
||||||
whiteList.push('/process/antd-demo');
|
function findFirstAvailableRoute(routes: RouteRecordRaw[]): string | undefined {
|
||||||
|
for (const route of routes) {
|
||||||
|
if (route.meta?.hidden) continue;
|
||||||
|
|
||||||
|
if (route.children?.length > 0) {
|
||||||
|
const child = route.children[0];
|
||||||
|
// 优先使用 opturl 或 path
|
||||||
|
const targetPath = child.opturl || child.path;
|
||||||
|
return targetPath?.startsWith('/') ? targetPath : `/${targetPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPath = route.opturl || route.path;
|
||||||
|
return targetPath;
|
||||||
|
}
|
||||||
|
return '/404';
|
||||||
}
|
}
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
NProgress.start();
|
NProgress.start();
|
||||||
const userStore = useUserStoreHook();
|
const userStore = useUserStoreHook();
|
||||||
|
|
||||||
if (userStore.Token) {
|
if (userStore.Token) {
|
||||||
// 登录成功,跳转到首页
|
// 登录成功,跳转到首页
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
@ -26,9 +41,19 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
NProgress.done();
|
NProgress.done();
|
||||||
} else {
|
} else {
|
||||||
const hasGetUserInfo = userStore.roles.length > 0;
|
const hasGetUserInfo = userStore.roles.length > 0;
|
||||||
// const hasGetUserInfo = true;
|
|
||||||
if (hasGetUserInfo) {
|
if (hasGetUserInfo) {
|
||||||
|
// 已获取用户信息,检查路由匹配
|
||||||
if (to.matched.length === 0) {
|
if (to.matched.length === 0) {
|
||||||
|
// 路由未匹配,可能是访问根路径
|
||||||
|
if (to.path === '/') {
|
||||||
|
const firstRoute = findFirstAvailableRoute(permissionStore.routes);
|
||||||
|
if (firstRoute) {
|
||||||
|
next(firstRoute);
|
||||||
|
NProgress.done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
from.name ? next({ name: from.name as any }) : next('/401');
|
from.name ? next({ name: from.name as any }) : next('/401');
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
@ -36,15 +61,25 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const { roles } = await userStore.getInfo();
|
const { roles } = await userStore.getInfo();
|
||||||
const accessRoutes: RouteRecordRaw[] =
|
const accessRoutes: RouteRecordRaw[] = await permissionStore.generateRoutes(roles);
|
||||||
await permissionStore.generateRoutes(roles);
|
|
||||||
|
|
||||||
accessRoutes.forEach((route: any) => {
|
accessRoutes.forEach((route: any) => {
|
||||||
router.addRoute(route);
|
router.addRoute(route);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 关键:如果是根路径,加载完路由后跳转到第一个可用路由
|
||||||
|
if (to.path === '/') {
|
||||||
|
const firstRoute = findFirstAvailableRoute(accessRoutes);
|
||||||
|
if (firstRoute) {
|
||||||
|
next(firstRoute);
|
||||||
|
NProgress.done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
next({ ...to, replace: true });
|
next({ ...to, replace: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 移除 token 并跳转登录页
|
console.log(error);
|
||||||
await userStore.resetToken();
|
await userStore.resetToken();
|
||||||
next(`/login?redirect=${to.path}`);
|
next(`/login?redirect=${to.path}`);
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
@ -64,4 +99,4 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
|
|
||||||
router.afterEach(() => {
|
router.afterEach(() => {
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
});
|
});
|
||||||
@ -26,48 +26,11 @@ export const constantRoutes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/error-page/404.vue'),
|
component: () => import('@/views/error-page/404.vue'),
|
||||||
meta: { hidden: true }
|
meta: { hidden: true }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
{
|
path: '/401',
|
||||||
path: '/',
|
component: () => import('@/views/error-page/401.vue'),
|
||||||
component: Layout,
|
meta: { hidden: true }
|
||||||
redirect: '/dashboard',
|
},
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'dashboard',
|
|
||||||
opturl: '/dashboard',
|
|
||||||
component: () => import('@/views/dashboard/index.vue'),
|
|
||||||
name: '首页',
|
|
||||||
icon: 'homepage',
|
|
||||||
meta: { title: 'dashboard', icon: 'homepage', affix: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'personalCenter',
|
|
||||||
component: () => import('@/views/system/user/personalCenter.vue'),
|
|
||||||
name: '个人中心',
|
|
||||||
meta: { title: 'personalCenter',hidden: true, icon: 'personalCenter' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '401',
|
|
||||||
component: () => import('@/views/error-page/401.vue'),
|
|
||||||
meta: { hidden: true }
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/process',
|
|
||||||
opturl: '/process',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/process/antd-demo',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'antd-demo',
|
|
||||||
opturl: '/process/antd-demo',
|
|
||||||
component: () => import('@/views/process/antd-demo.vue'),
|
|
||||||
name: 'AntD Demo',
|
|
||||||
meta: { title: 'AntD Demo' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// 创建路由
|
// 创建路由
|
||||||
|
|||||||
@ -10,7 +10,7 @@ interface DefaultSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultSettings: DefaultSettings = {
|
const defaultSettings: DefaultSettings = {
|
||||||
title: '公司开发平台框架',
|
title: '水电水利建设项目全过程环境管理信息平台',
|
||||||
showSettings: false,
|
showSettings: false,
|
||||||
tagsView: true,
|
tagsView: true,
|
||||||
fixedHeader: true,
|
fixedHeader: true,
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
getSidebarStatus,
|
|
||||||
setSidebarStatus,
|
setSidebarStatus,
|
||||||
getSize,
|
getSize,
|
||||||
setSize,
|
setSize,
|
||||||
@ -24,7 +23,12 @@ export enum SizeType {
|
|||||||
large,
|
large,
|
||||||
small
|
small
|
||||||
}
|
}
|
||||||
|
export const usetTheme = {
|
||||||
|
token: {
|
||||||
|
colorPrimary: '#1890ff',
|
||||||
|
borderRadius: 2,
|
||||||
|
},
|
||||||
|
};
|
||||||
// setup
|
// setup
|
||||||
export const useAppStore = defineStore('app', () => {
|
export const useAppStore = defineStore('app', () => {
|
||||||
// state
|
// state
|
||||||
@ -32,7 +36,6 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
const size = ref(getSize() || 'default');
|
const size = ref(getSize() || 'default');
|
||||||
const language = ref(getLanguage());
|
const language = ref(getLanguage());
|
||||||
const sidebar = reactive({
|
const sidebar = reactive({
|
||||||
opened: getSidebarStatus() !== 'closed',
|
|
||||||
withoutAnimation: false
|
withoutAnimation: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,32 +47,6 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// actions
|
|
||||||
function toggleSidebar(withoutAnimation: boolean) {
|
|
||||||
sidebar.opened = !sidebar.opened;
|
|
||||||
sidebar.withoutAnimation = withoutAnimation;
|
|
||||||
if (sidebar.opened) {
|
|
||||||
setSidebarStatus('opened');
|
|
||||||
} else {
|
|
||||||
setSidebarStatus('closed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSideBar(withoutAnimation: boolean) {
|
|
||||||
sidebar.opened = false;
|
|
||||||
sidebar.withoutAnimation = withoutAnimation;
|
|
||||||
setSidebarStatus('closed');
|
|
||||||
}
|
|
||||||
|
|
||||||
function openSideBar(withoutAnimation: boolean) {
|
|
||||||
sidebar.opened = true;
|
|
||||||
sidebar.withoutAnimation = withoutAnimation;
|
|
||||||
setSidebarStatus('opened');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleDevice(val: string) {
|
|
||||||
device.value = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeSize(val: string) {
|
function changeSize(val: string) {
|
||||||
size.value = val;
|
size.value = val;
|
||||||
@ -87,11 +64,7 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
language,
|
language,
|
||||||
locale,
|
locale,
|
||||||
size,
|
size,
|
||||||
toggleDevice,
|
|
||||||
changeSize,
|
changeSize,
|
||||||
changeLanguage,
|
changeLanguage,
|
||||||
toggleSidebar,
|
|
||||||
closeSideBar,
|
|
||||||
openSideBar
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,24 +8,18 @@ import { ref } from 'vue';
|
|||||||
const modules = import.meta.glob('../../views/**/**.vue');
|
const modules = import.meta.glob('../../views/**/**.vue');
|
||||||
export const Layout = () => import('@/layout/index.vue');
|
export const Layout = () => import('@/layout/index.vue');
|
||||||
|
|
||||||
// const hasPermission = (roles: string[], route: RouteRecordRaw) => {
|
|
||||||
// if (route.meta && route.meta.roles) {
|
|
||||||
// if (roles.includes('ROOT')) {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// return roles.some(role => {
|
|
||||||
// if (route.meta?.roles !== undefined) {
|
|
||||||
// return (route.meta.roles as string[]).includes(role);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// return false;
|
|
||||||
// };
|
|
||||||
|
|
||||||
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
||||||
const res: RouteRecordRaw[] = [];
|
const res: RouteRecordRaw[] = [];
|
||||||
routes.forEach(route => {
|
routes.forEach(route => {
|
||||||
const tmp = { ...route } as any;
|
const tmp = { ...route } as any;
|
||||||
|
// ✅ 保存原始名称到 meta,用于菜单显示
|
||||||
|
tmp.meta = {
|
||||||
|
...tmp.meta,
|
||||||
|
title: tmp.name || tmp.menuName, // 原始名称用于显示
|
||||||
|
};
|
||||||
|
// ✅ name 使用路径生成唯一值
|
||||||
|
tmp.name = tmp.path || tmp.opturl;
|
||||||
|
|
||||||
// if (hasPermission(roles, tmp)) {
|
// if (hasPermission(roles, tmp)) {
|
||||||
tmp.path = tmp.opturl;
|
tmp.path = tmp.opturl;
|
||||||
if (tmp.type == '0') {
|
if (tmp.type == '0') {
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import defaultSettings from '../../settings';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useStorage } from '@vueuse/core';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主题类型
|
|
||||||
*/
|
|
||||||
export enum ThemeType {
|
|
||||||
light,
|
|
||||||
dark
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 布局类型
|
|
||||||
*/
|
|
||||||
export enum LayoutType {
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
mix
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSettingsStore = defineStore('setting', () => {
|
|
||||||
// state
|
|
||||||
const showSettings = ref<boolean>(defaultSettings.showSettings);
|
|
||||||
const tagsView = useStorage<boolean>('tagsView', defaultSettings.tagsView);
|
|
||||||
const fixedHeader = ref<boolean>(defaultSettings.fixedHeader);
|
|
||||||
const sidebarLogo = ref<boolean>(defaultSettings.sidebarLogo);
|
|
||||||
|
|
||||||
const layout = useStorage<string>('layout', defaultSettings.layout);
|
|
||||||
|
|
||||||
// actions
|
|
||||||
function changeSetting(param: { key: string; value: any }) {
|
|
||||||
const { key, value } = param;
|
|
||||||
switch (key) {
|
|
||||||
case 'showSettings':
|
|
||||||
showSettings.value = value;
|
|
||||||
break;
|
|
||||||
case 'fixedHeader':
|
|
||||||
fixedHeader.value = value;
|
|
||||||
break;
|
|
||||||
case 'tagsView':
|
|
||||||
tagsView.value = value;
|
|
||||||
break;
|
|
||||||
case 'sidevarLogo':
|
|
||||||
sidebarLogo.value = value;
|
|
||||||
break;
|
|
||||||
case 'layout':
|
|
||||||
layout.value = value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
showSettings,
|
|
||||||
tagsView,
|
|
||||||
fixedHeader,
|
|
||||||
sidebarLogo,
|
|
||||||
layout,
|
|
||||||
changeSetting
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@ -4,56 +4,4 @@
|
|||||||
--el-color-primary-dark: #0d84ff;
|
--el-color-primary-dark: #0d84ff;
|
||||||
// --el-font-size-base: 16px !important;
|
// --el-font-size-base: 16px !important;
|
||||||
}
|
}
|
||||||
.el-button--large, .el-input--large, .el-table--large, .el-form--large, .el-select__tags-text{
|
|
||||||
font-size: 16px !important;
|
|
||||||
.el-form-item__label{
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
--el-font-size-base: 16px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 覆盖 element-plus 的样式
|
|
||||||
.el-breadcrumb__inner,
|
|
||||||
.el-breadcrumb__inner a {
|
|
||||||
font-weight: 400 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-upload {
|
|
||||||
input[type='file'] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-upload__input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dropdown
|
|
||||||
.el-dropdown-menu {
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// to fix el-date-picker css style
|
|
||||||
.el-range-separator {
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选中行背景色值
|
|
||||||
.el-table__body tr.current-row td {
|
|
||||||
background-color: #e1f3d8b5 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// card 的header统一高度
|
|
||||||
.el-card__header {
|
|
||||||
height: 60px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表格表头和表体未对齐
|
|
||||||
.el-table__header col[name='gutter'] {
|
|
||||||
display: table-cell !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
539
frontend/src/styles/iconfont/demo.css
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
/* Logo 字体 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "iconfont logo";
|
||||||
|
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
|
||||||
|
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
|
||||||
|
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
|
||||||
|
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
|
||||||
|
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-family: "iconfont logo";
|
||||||
|
font-size: 160px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tabs */
|
||||||
|
.nav-tabs {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-more {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 42px;
|
||||||
|
line-height: 42px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabs {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabs li {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#tabs .active {
|
||||||
|
border-bottom-color: #f00;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-container .content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面布局 */
|
||||||
|
.main {
|
||||||
|
padding: 30px 100px;
|
||||||
|
width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .logo {
|
||||||
|
color: #333;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
line-height: 1;
|
||||||
|
height: 110px;
|
||||||
|
margin-top: -50px;
|
||||||
|
overflow: hidden;
|
||||||
|
*zoom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .logo a {
|
||||||
|
font-size: 160px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helps {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helps pre {
|
||||||
|
padding: 20px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: solid 1px #e7e1cd;
|
||||||
|
background-color: #fffdef;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists {
|
||||||
|
width: 100% !important;
|
||||||
|
overflow: hidden;
|
||||||
|
*zoom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists li {
|
||||||
|
width: 100px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-right: 20px;
|
||||||
|
text-align: center;
|
||||||
|
list-style: none !important;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists li .code-name {
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists .icon {
|
||||||
|
display: block;
|
||||||
|
height: 100px;
|
||||||
|
line-height: 100px;
|
||||||
|
font-size: 42px;
|
||||||
|
margin: 10px auto;
|
||||||
|
color: #333;
|
||||||
|
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
|
||||||
|
-moz-transition: font-size 0.25s linear, width 0.25s linear;
|
||||||
|
transition: font-size 0.25s linear, width 0.25s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists .icon:hover {
|
||||||
|
font-size: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists .svg-icon {
|
||||||
|
/* 通过设置 font-size 来改变图标大小 */
|
||||||
|
width: 1em;
|
||||||
|
/* 图标和文字相邻时,垂直对齐 */
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
/* 通过设置 color 来改变 SVG 的颜色/fill */
|
||||||
|
fill: currentColor;
|
||||||
|
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
|
||||||
|
normalize.css 中也包含这行 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists li .name,
|
||||||
|
.icon_lists li .code-name {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* markdown 样式 */
|
||||||
|
.markdown {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown img {
|
||||||
|
vertical-align: middle;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1 {
|
||||||
|
color: #404040;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 40px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h2,
|
||||||
|
.markdown h3,
|
||||||
|
.markdown h4,
|
||||||
|
.markdown h5,
|
||||||
|
.markdown h6 {
|
||||||
|
color: #404040;
|
||||||
|
margin: 1.6em 0 0.6em 0;
|
||||||
|
font-weight: 500;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h2 {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h4 {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h5 {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h6 {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown hr {
|
||||||
|
height: 1px;
|
||||||
|
border: 0;
|
||||||
|
background: #e9e9e9;
|
||||||
|
margin: 16px 0;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown p {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>p,
|
||||||
|
.markdown>blockquote,
|
||||||
|
.markdown>.highlight,
|
||||||
|
.markdown>ol,
|
||||||
|
.markdown>ul {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown ul>li {
|
||||||
|
list-style: circle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>ul li,
|
||||||
|
.markdown blockquote ul>li {
|
||||||
|
margin-left: 20px;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>ul li p,
|
||||||
|
.markdown>ol li p {
|
||||||
|
margin: 0.6em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown ol>li {
|
||||||
|
list-style: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>ol li,
|
||||||
|
.markdown blockquote ol>li {
|
||||||
|
margin-left: 20px;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown code {
|
||||||
|
margin: 0 3px;
|
||||||
|
padding: 0 5px;
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown strong,
|
||||||
|
.markdown b {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0px;
|
||||||
|
empty-cells: show;
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
width: 95%;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table th {
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table th,
|
||||||
|
.markdown>table td {
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
padding: 8px 16px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table th {
|
||||||
|
background: #F7F7F7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown blockquote {
|
||||||
|
font-size: 90%;
|
||||||
|
color: #999;
|
||||||
|
border-left: 4px solid #e9e9e9;
|
||||||
|
padding-left: 0.8em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown blockquote p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown .anchor {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown .waiting {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1:hover .anchor,
|
||||||
|
.markdown h2:hover .anchor,
|
||||||
|
.markdown h3:hover .anchor,
|
||||||
|
.markdown h4:hover .anchor,
|
||||||
|
.markdown h5:hover .anchor,
|
||||||
|
.markdown h6:hover .anchor {
|
||||||
|
opacity: 1;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>br,
|
||||||
|
.markdown>p>br {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
display: block;
|
||||||
|
background: white;
|
||||||
|
padding: 0.5em;
|
||||||
|
color: #333333;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-meta {
|
||||||
|
color: #969896;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-strong,
|
||||||
|
.hljs-emphasis,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #df5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag,
|
||||||
|
.hljs-type {
|
||||||
|
color: #a71d5d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-attribute {
|
||||||
|
color: #0086b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-section,
|
||||||
|
.hljs-name {
|
||||||
|
color: #63a35c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-tag {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-attr,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-selector-attr,
|
||||||
|
.hljs-selector-pseudo {
|
||||||
|
color: #795da3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-addition {
|
||||||
|
color: #55a532;
|
||||||
|
background-color: #eaffea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-deletion {
|
||||||
|
color: #bd2c00;
|
||||||
|
background-color: #ffecec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 代码高亮 */
|
||||||
|
/* PrismJS 1.15.0
|
||||||
|
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
||||||
|
/**
|
||||||
|
* prism.js default theme for JavaScript, CSS and HTML
|
||||||
|
* Based on dabblet (http://dabblet.com)
|
||||||
|
* @author Lea Verou
|
||||||
|
*/
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: black;
|
||||||
|
background: none;
|
||||||
|
text-shadow: 0 1px white;
|
||||||
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]::-moz-selection,
|
||||||
|
pre[class*="language-"] ::-moz-selection,
|
||||||
|
code[class*="language-"]::-moz-selection,
|
||||||
|
code[class*="language-"] ::-moz-selection {
|
||||||
|
text-shadow: none;
|
||||||
|
background: #b3d4fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]::selection,
|
||||||
|
pre[class*="language-"] ::selection,
|
||||||
|
code[class*="language-"]::selection,
|
||||||
|
code[class*="language-"] ::selection {
|
||||||
|
text-shadow: none;
|
||||||
|
background: #b3d4fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
pre[class*="language-"] {
|
||||||
|
padding: 1em;
|
||||||
|
margin: .5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre)>code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
background: #f5f2f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
:not(pre)>code[class*="language-"] {
|
||||||
|
padding: .1em;
|
||||||
|
border-radius: .3em;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
color: slategray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property,
|
||||||
|
.token.tag,
|
||||||
|
.token.boolean,
|
||||||
|
.token.number,
|
||||||
|
.token.constant,
|
||||||
|
.token.symbol,
|
||||||
|
.token.deleted {
|
||||||
|
color: #905;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector,
|
||||||
|
.token.attr-name,
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.builtin,
|
||||||
|
.token.inserted {
|
||||||
|
color: #690;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator,
|
||||||
|
.token.entity,
|
||||||
|
.token.url,
|
||||||
|
.language-css .token.string,
|
||||||
|
.style .token.string {
|
||||||
|
color: #9a6e3a;
|
||||||
|
background: hsla(0, 0%, 100%, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.atrule,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.keyword {
|
||||||
|
color: #07a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.function,
|
||||||
|
.token.class-name {
|
||||||
|
color: #DD4A68;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.regex,
|
||||||
|
.token.important,
|
||||||
|
.token.variable {
|
||||||
|
color: #e90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.important,
|
||||||
|
.token.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||