This commit is contained in:
root 2026-04-01 15:49:37 +08:00
commit 0a39c7fa4d
150 changed files with 20351 additions and 5218 deletions

View File

@ -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;
}
}

View File

@ -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);
}
/**********************************

View File

@ -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());
//填写 当前日期

View File

@ -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);
}
/***********************************

View File

@ -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);
}

View File

@ -41,6 +41,7 @@ public class SysRole implements Serializable {
/**
* 1-超级管理员 2-单位管理员 3-普通用户
*/
@TableField("\"LEVEL\"")
private String level;
/**

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}

View File

@ -63,4 +63,6 @@ public interface ISysRoleService extends IService<SysRole> {
* 返回值说明: 是否分配成功
***********************************/
boolean setMenuById(String id, String menuIds);
List<SysRole> selectRoleList(String rolename);
}

View File

@ -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批量删除用户

View File

@ -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) {

View File

@ -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");

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -33,7 +33,7 @@
DISTINCT
m.id,
m.parentid,
m.`name`,
m.name,
m.opturl,
m.icon,
m.orderno,

View File

@ -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}

View File

@ -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>

View File

@ -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" />

View File

@ -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"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

BIN
frontend/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -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>

View File

@ -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
});
}

View File

@ -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
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

View 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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>

View 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>

View File

@ -16,8 +16,6 @@
</template>
<script setup lang="ts">
// import { useSettingsStore } from '@/store/modules/settings';
// const settingsStore = useSettingsStore();
</script>
<style>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -6,11 +6,11 @@ export default {
},
// 登录页面国际化
login: {
title: '公司开发平台框架',
title: '水电水利建设项目全过程环境管理信息平台',
username: '用户名',
rulesUsername: '请输入用户名',
rulesUsername: '用户账号/身份证号/手机号 不能为空',
password: '密码',
rulesPassword: '请输入密码',
rulesPassword: '密码 不能为空',
rulesPasswordPlace: '密码不能少于6位',
login: '登 录',
code: '请输入验证码',

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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';

View File

@ -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>

View File

@ -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>

View File

@ -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');

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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 + '&nbsp;&nbsp;<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>

View 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>

View 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>

View File

@ -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();
});
});

View File

@ -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 }
},
];
// 创建路由

View File

@ -10,7 +10,7 @@ interface DefaultSettings {
}
const defaultSettings: DefaultSettings = {
title: '公司开发平台框架',
title: '水电水利建设项目全过程环境管理信息平台',
showSettings: false,
tagsView: true,
fixedHeader: true,

View File

@ -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
};
});

View File

@ -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') {

View File

@ -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
};
});

View File

@ -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;
}

View 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;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More