Merge branch 'main' of http://121.37.111.42:3000/zhengsl/WholeProcessPlatform
@ -14,11 +14,22 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
public class MybitsPlusConfig {
|
||||
|
||||
// @Bean
|
||||
// public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
// MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
|
||||
// mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
// return mybatisPlusInterceptor;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 分页插件配置(Oracle 兼容)
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
|
||||
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
return mybatisPlusInterceptor;
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 添加分页拦截器,指定数据库类型为 Oracle
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.ORACLE));
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -90,9 +90,11 @@ public class SysMenuController {
|
||||
@PostMapping("/permissionAssignment")
|
||||
@Operation(summary = "获取分配权限(不含按钮)")
|
||||
@ResponseBody
|
||||
public List<Map<String, Object>> permissionAssignment(String roleId) {
|
||||
|
||||
return sysMenuService.permissionAssignment(roleId);
|
||||
public List<Map<String, Object>> permissionAssignment(String code, String roleId) {
|
||||
if (StrUtil.isBlank(code)) {
|
||||
code = "1";
|
||||
}
|
||||
return sysMenuService.permissionAssignment(code,roleId);
|
||||
}
|
||||
|
||||
/**********************************
|
||||
|
||||
@ -115,6 +115,9 @@ public class SysOrganizationController {
|
||||
if (StrUtil.isEmpty(sysOrganization.getIsvaild())) {
|
||||
sysOrganization.setIsvaild("1");
|
||||
}
|
||||
if("".equals(sysOrganization.getId())){
|
||||
sysOrganization.setId(null);
|
||||
}
|
||||
//填写 当前用户名称
|
||||
sysOrganization.setLastmodifier(userService.getUsername());
|
||||
//填写 当前日期
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.yfd.platform.system.controller;
|
||||
|
||||
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.update.UpdateWrapper;
|
||||
import com.yfd.platform.annotation.Log;
|
||||
@ -46,15 +47,8 @@ public class SysRoleController {
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "查询所有角色")
|
||||
@ResponseBody
|
||||
public List<SysRole> list(String rolename) {
|
||||
QueryWrapper<SysRole> queryWrapper = new QueryWrapper<>();
|
||||
if (StrUtil.isNotEmpty(rolename)) {
|
||||
//根据角色名称模糊查询
|
||||
queryWrapper.like("rolename", rolename);
|
||||
}
|
||||
//根据角色级别,角色编号 正序排序
|
||||
queryWrapper.ne("level", "1").orderByAsc("level", "lastmodifydate");
|
||||
return roleService.list(queryWrapper);
|
||||
public List<SysRole> list(@RequestParam(required = false) String rolename) {
|
||||
return roleService.selectRoleList(rolename);
|
||||
}
|
||||
|
||||
/***********************************
|
||||
|
||||
@ -64,7 +64,7 @@ public class UserController {
|
||||
public ResponseResult queryUsers(String orgid,
|
||||
String username, Page<SysUser> page) {
|
||||
|
||||
Page<Map<String, Object>> mapPage = userService.queryUsers(orgid,
|
||||
Page<SysUser> mapPage = userService.queryUsers(orgid,
|
||||
username, page);
|
||||
return ResponseResult.successData(mapPage);
|
||||
}
|
||||
|
||||
@ -41,6 +41,7 @@ public class SysRole implements Serializable {
|
||||
/**
|
||||
* 1-超级管理员 2-单位管理员 3-普通用户
|
||||
*/
|
||||
@TableField("\"LEVEL\"")
|
||||
private String level;
|
||||
|
||||
/**
|
||||
|
||||
@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -113,4 +114,7 @@ public class SysUser implements Serializable {
|
||||
* 备用3
|
||||
*/
|
||||
private String custom3;
|
||||
|
||||
@TableField(exist = false)
|
||||
List<SysRole> roles;
|
||||
}
|
||||
|
||||
@ -101,4 +101,12 @@ public interface SysRoleMapper extends BaseMapper<SysRole> {
|
||||
* 返回值说明:角色 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);
|
||||
|
||||
Page<Map<String, Object>> queryUsers(String orgid,
|
||||
Page<SysUser> queryUsers(String orgid,
|
||||
String username,
|
||||
Page<SysUser> page);
|
||||
|
||||
|
||||
@ -95,7 +95,7 @@ public interface ISysMenuService extends IService<SysMenu> {
|
||||
* isdisplay 是否显示
|
||||
* 返回值说明: 菜单结构树集合
|
||||
***********************************/
|
||||
List<Map<String, Object>> permissionAssignment(String roleId);
|
||||
List<Map<String, Object>> permissionAssignment(String code,String roleId);
|
||||
|
||||
String uploadIcon(MultipartFile icon) throws FileNotFoundException;
|
||||
}
|
||||
|
||||
@ -63,4 +63,6 @@ public interface ISysRoleService extends IService<SysRole> {
|
||||
* 返回值说明: 是否分配成功
|
||||
***********************************/
|
||||
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);
|
||||
|
||||
//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批量删除用户
|
||||
|
||||
@ -451,18 +451,19 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
|
||||
* 返回值说明: 菜单结构树集合
|
||||
***********************************/
|
||||
@Override
|
||||
public List<Map<String, Object>> permissionAssignment(String roleId) {
|
||||
public List<Map<String, Object>> permissionAssignment(String code,String roleId) {
|
||||
|
||||
String code = sysMenuMapper.getSystemCodeById(roleId);
|
||||
if (code == null) {
|
||||
code = "1";
|
||||
}
|
||||
// String code = sysMenuMapper.getSystemCodeById(roleId);
|
||||
// if (code == null) {
|
||||
// code = "1";
|
||||
// }
|
||||
LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(SysMenu::getSystemcode, code).select(SysMenu::getId,
|
||||
SysMenu::getParentid, SysMenu::getName).orderByAsc
|
||||
(SysMenu::getOrderno);
|
||||
List<Map<String, Object>> listAll =
|
||||
List<Map<String, Object>> mapList =
|
||||
sysMenuMapper.selectMaps(queryWrapper);
|
||||
List<Map<String, Object>> listAll = ObjectConverterUtil.convertMapFieldsToEntityFormat(SysMenu.class, mapList);
|
||||
List<String> listRole =
|
||||
sysMenuMapper.selectMenuByRoleId(roleId);
|
||||
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.service.ISysOrganizationService;
|
||||
import com.yfd.platform.system.service.IUserService;
|
||||
import com.yfd.platform.utils.ObjectConverterUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
@ -128,12 +129,15 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
|
||||
QueryWrapper<SysOrganization> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("parentid", parentid); //根据上级id 查询
|
||||
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 =
|
||||
child(listMap.get(i).get("id").toString()); //循环获取下一子集
|
||||
listMap.get(i).put("childList", childList); //添加新列 子集
|
||||
child(mapList.get(i).get("id").toString()); //循环获取下一子集
|
||||
mapList.get(i).put("childList", childList); //添加新列 子集
|
||||
}
|
||||
return mapList;
|
||||
}
|
||||
return listMap;
|
||||
}
|
||||
@ -161,10 +165,12 @@ public class SysOrganizationServiceImpl extends ServiceImpl<SysOrganizationMappe
|
||||
queryWrapper.eq("parentid", "0");
|
||||
}
|
||||
List<Object> max = this.listObjs(queryWrapper);
|
||||
//判断查询是否存在 存在转换成int类型并给codeMax替换值
|
||||
if (max.size() > 0) {
|
||||
codeMax =
|
||||
Integer.parseInt(max.get(0).toString().substring(max.get(0).toString().length() - 2));
|
||||
//判断查询是否存在 存在转换成 int 类型并给 codeMax 替换值
|
||||
if (!max.isEmpty() && max.getFirst() != null) {
|
||||
String maxValue = max.getFirst().toString();
|
||||
if (maxValue.length() >= 2) {
|
||||
codeMax = Integer.parseInt(maxValue.substring(maxValue.length() - 2));
|
||||
}
|
||||
}
|
||||
//2位数字编号
|
||||
DecimalFormat df = new DecimalFormat("00");
|
||||
|
||||
@ -49,8 +49,14 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
||||
QueryWrapper<SysRole> queryWrapper = new QueryWrapper<>();
|
||||
List<Object> max = this.listObjs(queryWrapper.select("MAX(rolecode) " +
|
||||
"rolecode"));// 查询最大的编号
|
||||
if (max.size() > 0) {
|
||||
codeMax = Integer.parseInt(max.get(0).toString());//判断查询是否存在
|
||||
// 存在转换成 int 类型并给 codeMax 替换值
|
||||
if (!max.isEmpty() && max.getFirst() != null) {
|
||||
try {
|
||||
codeMax = Integer.parseInt(max.getFirst().toString());
|
||||
} catch (NumberFormatException e) {
|
||||
// 如果转换失败,保持默认值 0
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
// 存在转换成int类型并给codeMax替换值
|
||||
String code = df.format(codeMax + 1); // 最大编号累加
|
||||
@ -160,4 +166,9 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
||||
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.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
@ -224,46 +225,79 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
|
||||
*roleids 角色id
|
||||
* 返回值说明: 是否更新成功
|
||||
************************************/
|
||||
@Override
|
||||
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);
|
||||
// ... existing code ...
|
||||
|
||||
} else {
|
||||
//根据用户id 删除该用户角色关联
|
||||
ok = ok && sysUserMapper.delRoleUsersByUserid(sysUser.getId());
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
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("msg", "用户信息修改成功!");
|
||||
} else {
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("更新用户信息失败", e);
|
||||
result.put("status", "error");
|
||||
result.put("msg", "用户信息修改失败!");
|
||||
result.put("msg", "操作失败:" + e.getMessage());
|
||||
throw e; // 抛出异常,触发事务回滚
|
||||
}
|
||||
|
||||
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
|
||||
public Map getOneById(String id) {
|
||||
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
|
||||
@ -476,20 +510,16 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public Page<Map<String, Object>> queryUsers(String orgid,
|
||||
public Page<SysUser> queryUsers(String orgid,
|
||||
String username,
|
||||
Page<SysUser> page) {
|
||||
Page<Map<String, Object>> mapPage = sysUserMapper.queryUsers(orgid,
|
||||
Page<SysUser> mapPage = sysUserMapper.queryUsers(orgid,
|
||||
username, page);
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
List<Map<String, Object>> records = mapPage.getRecords();
|
||||
for (Map<String, Object> record : records) {
|
||||
String id = (String) record.get("id");
|
||||
;mapPage.getRecords().forEach(record -> {
|
||||
String id = record.getId();
|
||||
List<SysRole> sysRoles = sysRoleMapper.getRoleByUserId(id);
|
||||
record.put("roles", sysRoles);
|
||||
list.add(record);
|
||||
}
|
||||
mapPage.setRecords(list);
|
||||
record.setRoles(sysRoles);
|
||||
});
|
||||
return mapPage;
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
DISTINCT
|
||||
m.id,
|
||||
m.parentid,
|
||||
m.`name`,
|
||||
m.name,
|
||||
m.opturl,
|
||||
m.icon,
|
||||
m.orderno,
|
||||
|
||||
@ -91,7 +91,15 @@
|
||||
<!--根据用户id获取角色信息-->
|
||||
<select id="getRoleByUserId"
|
||||
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>
|
||||
|
||||
<!--根据角色id获取用户id-->
|
||||
@ -111,6 +119,20 @@
|
||||
FROM sys_role_users
|
||||
WHERE userid = #{id}
|
||||
</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除外)-->
|
||||
<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}
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
|
||||
<!--用户分配角色 系统角色用户对照新增数据-->
|
||||
<insert id="addUserRoles">
|
||||
insert into sys_role_users value (#{id},#{roleid},#{userid})
|
||||
insert into sys_role_users values (#{id},#{roleid},#{userid})
|
||||
</insert>
|
||||
|
||||
<!--根据用户id 和角色id 查询 系统角色用户对照表-->
|
||||
@ -47,11 +47,13 @@
|
||||
select roleid from sys_role_users where userid=#{userid}
|
||||
</select>
|
||||
|
||||
<!--根据用户表id查询角色表最大级别-->
|
||||
<!--根据用户表 id 查询角色表最大级别-->
|
||||
<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 id="queryUsers" resultType="java.util.Map">
|
||||
<select id="queryUsers" resultType="com.yfd.platform.system.domain.SysUser">
|
||||
SELECT DISTINCT
|
||||
u.id,
|
||||
u.usertype,
|
||||
@ -62,7 +64,7 @@
|
||||
u.phone,
|
||||
u.avatar,
|
||||
u.orgid,
|
||||
u.`status`,
|
||||
u.status,
|
||||
u.lastmodifier,
|
||||
u.lastmodifydate
|
||||
FROM
|
||||
@ -73,7 +75,7 @@
|
||||
and u.orgid = #{orgid}
|
||||
</if>
|
||||
<if test="username != null">
|
||||
and u.username like concat('%', #{username},'%')
|
||||
and u.username LIKE '%' || #{username} || '%'
|
||||
</if>
|
||||
ORDER BY u.lastmodifydate DESC
|
||||
</select>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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="description" content="NewFrameWork2023-WEB" />
|
||||
<meta name="keywords" content="NewFrameWork2023-WEB" />
|
||||
|
||||
@ -10,13 +10,16 @@
|
||||
"prettier": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@types/js-cookie": "^3.0.2",
|
||||
"@vueuse/core": "^9.1.1",
|
||||
"@wangeditor/editor": "^5.0.0",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.2.0",
|
||||
"better-scroll": "^2.4.2",
|
||||
"dayjs": "^1.11.20",
|
||||
"default-passive-events": "^2.0.0",
|
||||
"echarts": "^5.2.2",
|
||||
"element-plus": "^2.2.27",
|
||||
@ -63,6 +66,5 @@
|
||||
},
|
||||
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
|
||||
"author": "有来开源组织",
|
||||
"license": "MIT",
|
||||
"__npminstall_done": false
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
|
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">
|
||||
import { ElConfigProvider } from 'element-plus';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
const appStore = useAppStore();
|
||||
import { ElConfigProvider } from 'element-plus';
|
||||
import { useAppStore,usetTheme } from '@/store/modules/app';
|
||||
const appStore = useAppStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-config-provider :locale="appStore.locale" :size="appStore.size">
|
||||
<router-view />
|
||||
<a-config-provider :theme="usetTheme">
|
||||
<router-view />
|
||||
</a-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 { useSettingsStore } from '@/store/modules/settings';
|
||||
|
||||
// 图标依赖
|
||||
import { Close, Setting } from '@element-plus/icons-vue';
|
||||
import { ElColorPicker } from 'element-plus';
|
||||
|
||||
// const settingsStore = useSettingsStore();
|
||||
|
||||
const show = ref(false);
|
||||
|
||||
defineProps({
|
||||
@ -32,21 +29,6 @@ watch(show, value => {
|
||||
});
|
||||
|
||||
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);
|
||||
@ -92,7 +74,6 @@ onBeforeUnmount(() => {
|
||||
.showRightPanel {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: calc(100% - 15px);
|
||||
}
|
||||
</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>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import { useSettingsStore } from '@/store/modules/settings';
|
||||
// const settingsStore = useSettingsStore();
|
||||
</script>
|
||||
|
||||
<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: {
|
||||
title: '公司开发平台框架',
|
||||
title: '水电水利建设项目全过程环境管理信息平台',
|
||||
username: '用户名',
|
||||
rulesUsername: '请输入用户名',
|
||||
rulesUsername: '用户账号/身份证号/手机号 不能为空',
|
||||
password: '密码',
|
||||
rulesPassword: '请输入密码',
|
||||
rulesPassword: '密码 不能为空',
|
||||
rulesPasswordPlace: '密码不能少于6位',
|
||||
login: '登 录',
|
||||
code: '请输入验证码',
|
||||
|
||||
@ -1,49 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView';
|
||||
import { useRoute, } from 'vue-router';
|
||||
import { computed } from "vue";
|
||||
import { useTagsViewStore } from "@/store/modules/tagsView";
|
||||
import { useRoute } from "vue-router";
|
||||
import GisView from "@/components/gis/GisView.vue";
|
||||
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
|
||||
|
||||
const router = useRoute();
|
||||
const routeKey = computed(() => router.path + Math.random());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="app-main">
|
||||
<router-view v-slot="{ Component, route }" :key="routeKey">
|
||||
<transition name="router-fade" mode="out-in">
|
||||
<keep-alive :include="tagsViewStore.cachedViews">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
<GisView />
|
||||
<div class="gi-panels">
|
||||
<router-view v-slot="{ Component, route }" :key="routeKey">
|
||||
<transition name="router-fade" mode="out-in">
|
||||
<keep-alive :include="tagsViewStore.cachedViews">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/variables.module.scss" as *;
|
||||
.app-main {
|
||||
min-height: calc(100vh - 114px);
|
||||
min-height: calc(100vh - $layout-header-height - $locationbar-height);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: #f0f2f5;
|
||||
padding: 0 16px 16px;
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
.hasTagsView {
|
||||
.app-main {
|
||||
/* 84 = navbar + tags-view = 50 + 34 */
|
||||
min-height: calc(100vh - 114px);
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
padding-top: 84px;
|
||||
}
|
||||
.gi-panels {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,46 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { getToken } from '@/utils/auth';
|
||||
|
||||
import Screenfull from '@/components/Screenfull/index.vue';
|
||||
import SizeSelect from '@/components/SizeSelect/index.vue';
|
||||
import News from './news.vue';
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { getToken } from "@/utils/auth";
|
||||
import { UserOutlined, LogoutOutlined } from "@ant-design/icons-vue";
|
||||
// 国际化
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
// import LangSelect from '@/components/LangSelect/index.vue';
|
||||
import MixNav from './Sidebar/MixNav.vue';
|
||||
// import { CaretBottom } from '@element-plus/icons-vue';
|
||||
import Logo from './Sidebar/Logo.vue';
|
||||
import Sidebar from "./Sidebar/index.vue";
|
||||
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
import Cookies from 'js-cookie';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useTagsViewStore } from "@/store/modules/tagsView";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import Cookies from "js-cookie";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
const url = import.meta.env.VITE_APP_BASE_API;
|
||||
const username = Cookies.get('username');
|
||||
const settingsStore = useSettingsStore();
|
||||
const { sidebarLogo } = storeToRefs(settingsStore);
|
||||
const isCollapse = computed(() => !appStore.sidebar.opened);
|
||||
const username = Cookies.get("username");
|
||||
|
||||
const appStore = useAppStore();
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const querystr = ref('');
|
||||
const dialogVisible = ref(false)
|
||||
const device = computed(() => appStore.device);
|
||||
const news = ref()
|
||||
|
||||
function logout() {
|
||||
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
}).then(() => {
|
||||
userStore
|
||||
.logout()
|
||||
@ -52,114 +39,128 @@ function logout() {
|
||||
});
|
||||
});
|
||||
}
|
||||
function querystrChange() {
|
||||
}
|
||||
const badgeval = ref(0)
|
||||
const isbadge = ref(true)
|
||||
var source = new EventSource(url+ `/sse/connect/` + getToken(),);
|
||||
const badgeval = ref(0);
|
||||
const isbadge = ref(true);
|
||||
var source = new EventSource(url + `/sse/connect/` + getToken());
|
||||
onMounted(() => {
|
||||
if ("EventSource" in window) {
|
||||
source.onmessage = function(e) {
|
||||
if(e.data>0) {
|
||||
badgeval.value = e.data
|
||||
isbadge.value = false
|
||||
news.value.init()
|
||||
source.onmessage = function (e) {
|
||||
if (e.data > 0) {
|
||||
badgeval.value = e.data;
|
||||
isbadge.value = false;
|
||||
} else {
|
||||
isbadge.value = true
|
||||
isbadge.value = true;
|
||||
}
|
||||
};
|
||||
source.onopen = function(e) {
|
||||
};
|
||||
source.onerror = function(e:any) {
|
||||
source.onopen = function (e) {};
|
||||
source.onerror = function (e: any) {
|
||||
if (e.readyState == EventSource.CLOSED) {
|
||||
} else {
|
||||
}
|
||||
};
|
||||
} else {
|
||||
};
|
||||
})
|
||||
onBeforeUnmount(()=>{
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
source.close();
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="navbar">
|
||||
<logo v-if="sidebarLogo" :collapse="isCollapse" />
|
||||
<!-- <mix-nav v-if="device !== 'mobile' && settingsStore.layout === 'mix'" /> -->
|
||||
<div v-if="settingsStore.layout === 'left'" class="flex justify-start">
|
||||
<div class="flex justify-center items-center">
|
||||
<!--全屏 -->
|
||||
<div v-if="device === 'desktop'" style="position: relative;">
|
||||
<el-input class="keywords" v-model="querystr" placeholder="请输入搜索关键字" clearable
|
||||
@change="querystrChange"></el-input>
|
||||
<img src="@/assets/MenuIcon/top_ss.png" alt="" style="position: absolute;right: 30px;top: 10px;">
|
||||
</div>
|
||||
<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>
|
||||
<a-layout-header class="header">
|
||||
<transition class="bg-white-800">
|
||||
<a
|
||||
href="/"
|
||||
class="h-[50px] min-w-[350px] flex items-center justify-center text-white"
|
||||
>
|
||||
<h1 class="text-blank font-bold fontSize-16">{{ t("login.title") }}</h1></a
|
||||
>
|
||||
</transition>
|
||||
<Sidebar />
|
||||
|
||||
<el-tooltip content="个人中心" effect="dark" placement="bottom">
|
||||
<div class="cursor-pointer flex justify-center items-center pl-[10px]"
|
||||
:style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }" @click="$router.push('/personalCenter')">{{
|
||||
username }}</div>
|
||||
</el-tooltip>
|
||||
|
||||
<div class="flex justify-center items-center cursor-pointer pr-[12px]"
|
||||
:style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }" @click="logout"><el-divider
|
||||
direction="vertical" style="padding-right:5px" />{{ $t('navbar.logout') }}</div>
|
||||
<News ref="news" v-model:dialog-visible="dialogVisible" />
|
||||
</div>
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight">
|
||||
<a-space class="username">
|
||||
<div>
|
||||
<span class="icon">
|
||||
<UserOutlined />
|
||||
</span>
|
||||
<span class="text">{{ username }}</span>
|
||||
</div>
|
||||
</a-space>
|
||||
<template #overlay>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/variables.module.scss" as *;
|
||||
.navbar {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 60px;
|
||||
height: 110px;
|
||||
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;
|
||||
}
|
||||
|
||||
.keywords {
|
||||
width: 240px;
|
||||
height: 34px;
|
||||
margin-right: 20px;
|
||||
.header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
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) {
|
||||
padding-left: 15px;
|
||||
border-radius: 40px;
|
||||
div {
|
||||
display: flex;
|
||||
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;
|
||||
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">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ref, onBeforeMount, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
import SidebarItem from './SidebarItem.vue';
|
||||
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();
|
||||
import { usePermissionStore } from "@/store/modules/permission";
|
||||
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 router = useRouter();
|
||||
|
||||
const isCollapse = computed(() => !appStore.sidebar.opened);
|
||||
|
||||
const activeMenu = computed<string>(() => {
|
||||
const { meta, path } = route;
|
||||
if (meta?.activeMenu) {
|
||||
return meta.activeMenu as string;
|
||||
// 主菜单切换
|
||||
const handleTabChange = (key: string) => {
|
||||
let data = menus.value.find((item: any) => item.id === key);
|
||||
subMenus.value = data.children;
|
||||
activeKey.value = key;
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div :class="{ 'has-logo': sidebarLogo }">
|
||||
<el-scrollbar>
|
||||
<el-menu :default-active="activeMenu" :collapse="isCollapse" :background-color="variables.menuBg"
|
||||
:text-color="variables.menuText" :active-text-color="variables.menuActiveText" :unique-opened="false"
|
||||
:collapse-transition="false" mode="vertical" >
|
||||
<sidebar-item v-for="route in permissionStore.routes" :item="route" :key="route.opturl"
|
||||
:is-collapse="isCollapse" />
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
<a-tabs
|
||||
class="sidebar-container"
|
||||
v-model:activeKey="activeKey"
|
||||
@change="handleTabChange"
|
||||
size="small"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="route in menus"
|
||||
: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>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/styles/variables.module' as variables;
|
||||
</style>
|
||||
@use "@/styles/variables.module.scss" as *;
|
||||
|
||||
.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 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">
|
||||
import { computed, watchEffect } from 'vue';
|
||||
import { useWindowSize } from '@vueuse/core';
|
||||
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 { computed } from "vue";
|
||||
import { AppMain, Navbar } from "./components/index";
|
||||
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
|
||||
const { width } = useWindowSize();
|
||||
|
||||
/**
|
||||
* 响应式布局容器固定宽度
|
||||
*
|
||||
* 大屏(>=1200px)
|
||||
* 中屏(>=992px)
|
||||
* 小屏(>=768px)
|
||||
*/
|
||||
const WIDTH = 992;
|
||||
import { useAppStore } from "@/store/modules/app";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||
const showTagsView = computed(() => settingsStore.tagsView);
|
||||
const showSettings = computed(() => settingsStore.showSettings);
|
||||
|
||||
const classObj = computed(() => ({
|
||||
hideSidebar: !appStore.sidebar.opened,
|
||||
openSidebar: appStore.sidebar.opened,
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div :class="classObj" class="app-wrapper">
|
||||
<!-- 手机设备 && 侧边栏 → 显示遮罩层 -->
|
||||
<div
|
||||
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" />
|
||||
|
||||
<navbar />
|
||||
<div class="main-container">
|
||||
<!--主页面-->
|
||||
<app-main />
|
||||
|
||||
<!-- 设置面板 -->
|
||||
<RightPanel v-if="showSettings">
|
||||
<settings />
|
||||
</RightPanel>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/styles/mixin.scss' as mixin;
|
||||
@use '@/styles/variables.module.scss' as variables;
|
||||
|
||||
.app-wrapper {
|
||||
// @include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.drawer-bg {
|
||||
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>
|
||||
</style>
|
||||
|
||||
@ -8,6 +8,8 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import Pagination from '@/components/Pagination/index.vue';
|
||||
import '@/permission';
|
||||
|
||||
import Antd from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/reset.css' // Ant Design 全局样式重置
|
||||
// 引入svg注册脚本
|
||||
import 'virtual:svg-icons-register';
|
||||
|
||||
@ -39,6 +41,7 @@ app
|
||||
.component('Pagination', Pagination)
|
||||
.use(router)
|
||||
.use(ElementPlus)
|
||||
.use(Antd)
|
||||
.use(WujieVue)
|
||||
.use(i18n)
|
||||
.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/nprogress.css';
|
||||
NProgress.configure({ showSpinner: false }); // 进度条
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
const permissionStore = usePermissionStoreHook();
|
||||
|
||||
// 白名单路由
|
||||
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) => {
|
||||
NProgress.start();
|
||||
const userStore = useUserStoreHook();
|
||||
|
||||
if (userStore.Token) {
|
||||
// 登录成功,跳转到首页
|
||||
if (to.path === '/login') {
|
||||
@ -26,9 +41,19 @@ router.beforeEach(async (to, from, next) => {
|
||||
NProgress.done();
|
||||
} else {
|
||||
const hasGetUserInfo = userStore.roles.length > 0;
|
||||
// const hasGetUserInfo = true;
|
||||
|
||||
if (hasGetUserInfo) {
|
||||
// 已获取用户信息,检查路由匹配
|
||||
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');
|
||||
} else {
|
||||
next();
|
||||
@ -36,15 +61,25 @@ router.beforeEach(async (to, from, next) => {
|
||||
} else {
|
||||
try {
|
||||
const { roles } = await userStore.getInfo();
|
||||
const accessRoutes: RouteRecordRaw[] =
|
||||
await permissionStore.generateRoutes(roles);
|
||||
|
||||
const accessRoutes: RouteRecordRaw[] = await permissionStore.generateRoutes(roles);
|
||||
|
||||
accessRoutes.forEach((route: any) => {
|
||||
router.addRoute(route);
|
||||
});
|
||||
|
||||
// 关键:如果是根路径,加载完路由后跳转到第一个可用路由
|
||||
if (to.path === '/') {
|
||||
const firstRoute = findFirstAvailableRoute(accessRoutes);
|
||||
if (firstRoute) {
|
||||
next(firstRoute);
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
next({ ...to, replace: true });
|
||||
} catch (error) {
|
||||
// 移除 token 并跳转登录页
|
||||
console.log(error);
|
||||
await userStore.resetToken();
|
||||
next(`/login?redirect=${to.path}`);
|
||||
NProgress.done();
|
||||
@ -64,4 +99,4 @@ router.beforeEach(async (to, from, next) => {
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
});
|
||||
@ -26,48 +26,11 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/error-page/404.vue'),
|
||||
meta: { hidden: true }
|
||||
},
|
||||
|
||||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
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' }
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
path: '/401',
|
||||
component: () => import('@/views/error-page/401.vue'),
|
||||
meta: { hidden: true }
|
||||
},
|
||||
];
|
||||
|
||||
// 创建路由
|
||||
|
||||
@ -10,7 +10,7 @@ interface DefaultSettings {
|
||||
}
|
||||
|
||||
const defaultSettings: DefaultSettings = {
|
||||
title: '公司开发平台框架',
|
||||
title: '水电水利建设项目全过程环境管理信息平台',
|
||||
showSettings: false,
|
||||
tagsView: true,
|
||||
fixedHeader: true,
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
getSidebarStatus,
|
||||
setSidebarStatus,
|
||||
getSize,
|
||||
setSize,
|
||||
@ -24,7 +23,12 @@ export enum SizeType {
|
||||
large,
|
||||
small
|
||||
}
|
||||
|
||||
export const usetTheme = {
|
||||
token: {
|
||||
colorPrimary: '#1890ff',
|
||||
borderRadius: 2,
|
||||
},
|
||||
};
|
||||
// setup
|
||||
export const useAppStore = defineStore('app', () => {
|
||||
// state
|
||||
@ -32,7 +36,6 @@ export const useAppStore = defineStore('app', () => {
|
||||
const size = ref(getSize() || 'default');
|
||||
const language = ref(getLanguage());
|
||||
const sidebar = reactive({
|
||||
opened: getSidebarStatus() !== 'closed',
|
||||
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) {
|
||||
size.value = val;
|
||||
@ -87,11 +64,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
language,
|
||||
locale,
|
||||
size,
|
||||
toggleDevice,
|
||||
changeSize,
|
||||
changeLanguage,
|
||||
toggleSidebar,
|
||||
closeSideBar,
|
||||
openSideBar
|
||||
};
|
||||
});
|
||||
|
||||
@ -8,24 +8,18 @@ import { ref } from 'vue';
|
||||
const modules = import.meta.glob('../../views/**/**.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 res: RouteRecordRaw[] = [];
|
||||
routes.forEach(route => {
|
||||
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)) {
|
||||
tmp.path = tmp.opturl;
|
||||
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-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;
|
||||
}
|
||||