160 Commits

Author SHA1 Message Date
guanj
3ffb11defa 调整台账 2026-04-03 14:47:36 +08:00
guanj
0b9aafc1b5 联调文件管理页面 2026-04-02 09:08:57 +08:00
guanj
762965b1e4 联调设备文件 2026-03-30 09:03:53 +08:00
guanj
a30379ab01 绘制 运维版本管理页面 2026-03-19 11:29:26 +08:00
dk
9f1fbf93cd 新增实时运维页面 2026-03-18 21:06:48 +08:00
dk
b5fc946ce2 测试修改提交 2026-03-17 14:32:14 +08:00
guanj
1171d37a86 添加工程树 2026-03-06 09:36:42 +08:00
guanj
3fdb41c468 修改测试bug 2026-02-04 09:35:24 +08:00
guanj
dd0dab7643 添加工程信息管理 页面 2026-02-02 13:56:50 +08:00
guanj
cf4291ed9a 修改报表 2026-01-28 10:16:05 +08:00
guanj
46124f0ea5 修改全局报表功能 2026-01-27 16:32:33 +08:00
guanj
def48e9c84 修改测试bug 2026-01-23 09:24:13 +08:00
guanj
823d5f4475 修改问题 2026-01-20 14:39:13 +08:00
guanj
c09e6f54dd 修改测试问题 2026-01-16 15:54:16 +08:00
guanj
5ceb9be9e2 修改测试问题 2026-01-15 15:59:13 +08:00
guanj
054d84778b 优化表格 2026-01-14 13:30:23 +08:00
guanj
63433aa6dc 云平台自测问题修改 2026-01-13 14:27:23 +08:00
guanj
e9d7231a75 修改测试用例1 2026-01-12 11:06:54 +08:00
guanj
08afdddc51 修改数据来源 2026-01-08 20:08:26 +08:00
guanj
e21ae50e51 修改数据来源 2026-01-08 19:51:43 +08:00
guanj
4cbd2e43cb 修改告警级别 2026-01-08 19:20:32 +08:00
guanj
4c9b677e81 修改监测点列表 2026-01-08 14:09:43 +08:00
guanj
0affb17e3a 修改监测列表页面 2026-01-08 13:48:40 +08:00
guanj
c2d0faea08 修改在线设备 2026-01-08 11:33:40 +08:00
guanj
0d155c8680 优化页面 2026-01-08 11:32:01 +08:00
guanj
3db01fe32d 修改驾驶舱组件重复绑定问题 2026-01-08 10:08:51 +08:00
guanj
545e3836d1 修改测试问题 2026-01-07 21:01:28 +08:00
guanj
02a95c1dcd 修改测试bug,优化页面 2026-01-07 13:14:26 +08:00
guanj
7a81c008c3 修改组件页面 2026-01-06 15:42:33 +08:00
guanj
5d3d16f8ec 修改测试bug 2026-01-06 11:35:11 +08:00
guanj
d25f16bcc7 添加系统绑的功能 2026-01-05 16:34:42 +08:00
guanj
75987c0c6f 修改测试问题 2026-01-05 11:31:50 +08:00
guanj
a765cdf9ee 修改测试bug 优化页面 2026-01-04 14:55:31 +08:00
guanj
cc0f8bc8b6 优化驾驶舱页面 2025-12-20 23:44:46 +08:00
guanj
7e4db9d4cd 修改菜单 2025-12-17 17:41:35 +08:00
sjl
67e2fa57d0 在线设备录入校验 2025-12-17 16:47:11 +08:00
stt
ad1fc11e61 删除打印 2025-12-10 13:33:06 +08:00
stt
6824864db2 没有波形图的时候显示暂无波形 2025-12-10 13:21:48 +08:00
stt
37ed693cea 实时数据页面样式修改 2025-12-10 10:30:32 +08:00
stt
0419af8e50 指标越限程度宽度修改 2025-12-09 15:21:42 +08:00
stt
5268b93dd0 监测点管理页面 2025-12-09 14:56:33 +08:00
guanj
4e6bd55089 修改报表样式 2025-12-09 13:58:37 +08:00
stt
4e0db29ab1 复位按钮隐藏 2025-12-09 11:36:52 +08:00
stt
9b0fd76f48 修改"下一个"按钮的状态 2025-12-08 15:23:38 +08:00
stt
f92b07c555 修改"下一个"按钮的状态 2025-12-08 14:21:27 +08:00
guanj
a77db278ac 修改驾驶舱时间问题 2025-12-08 13:30:46 +08:00
stt
51a0ae49a9 zoom缩放导致echarts偏移问题 2025-12-08 11:39:39 +08:00
stt
814e9917d6 弹框显示越限和不越限,不显示数值 2025-12-08 10:55:24 +08:00
stt
21f1c41196 宽度调整 2025-12-08 10:35:31 +08:00
stt
c188446e76 样式修改 2025-12-08 10:32:11 +08:00
stt
e2a5d084a5 下拉框添加filterable属性 2025-12-08 09:42:08 +08:00
stt
4ae27a9d6d 所以下拉框加filterable属性 2025-12-08 09:31:16 +08:00
stt
7783569f91 不越限样式调整 2025-12-08 09:01:00 +08:00
stt
f1ac67070f 指标越限明显样式调整 2025-12-08 08:55:36 +08:00
stt
77a9a2adfc Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-12-08 08:54:48 +08:00
stt
accc1f30f6 暂态事件样式调整 2025-12-08 08:54:44 +08:00
guanj
94649b3348 微调 2025-12-08 08:37:07 +08:00
stt
e3de350dc5 指标拟合图y轴展示修改 2025-12-05 16:18:48 +08:00
stt
4963dd495a 样式修改 2025-12-05 16:06:39 +08:00
stt
40fa6eba20 暂降方向统计页面联调 2025-12-05 14:55:32 +08:00
stt
f32934e0e6 Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-12-05 11:05:08 +08:00
stt
460962cead 删除多余代码 2025-12-05 11:05:05 +08:00
guanj
fa75fc2923 联调实时数据 2025-12-05 10:44:35 +08:00
stt
f953b560c7 暂态电能质量分析时间修改 2025-12-04 20:27:05 +08:00
stt
8f3426eb1f 稳态治理效果分析时间修改 2025-12-04 20:08:37 +08:00
stt
c2a2a4afd6 稳态电能质量分析时间修改 2025-12-04 19:47:31 +08:00
stt
d2357d4ad2 稳态电能质量分析时间修改 2025-12-04 19:11:21 +08:00
stt
1b23355134 实时数据页面提交 2025-12-04 16:29:46 +08:00
guanj
ce9caa8729 Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-12-04 15:25:31 +08:00
guanj
2d0349c1b6 微调 2025-12-04 15:25:22 +08:00
stt
8355fc6aed Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-12-04 14:51:25 +08:00
stt
23bc2d8f05 组件查询时间加必填校验 2025-12-04 14:51:21 +08:00
guanj
43caddffa3 修改 echart样式 2025-12-04 10:33:48 +08:00
stt
3accaf3079 日期下拉默认修改 2025-12-04 10:30:19 +08:00
guanj
5687367602 Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-12-04 09:37:58 +08:00
guanj
b8ee530557 修改驾驶舱zoom缩放问题 2025-12-04 09:37:38 +08:00
stt
0518127792 公共时间修改 2025-12-03 16:30:42 +08:00
guanj
5db43cd4b1 微调 2025-12-03 15:37:08 +08:00
guanj
bf0657cbbc 在线设备录入添加参数
修改组件管理时间线配置
2025-12-03 14:56:57 +08:00
stt
bcb1535d4d 日历只月的时候调接口 2025-12-03 13:26:03 +08:00
stt
aa07112605 敏感用户管理加单位 2025-12-03 09:05:13 +08:00
guanj
c09bea9e04 修改波形样式 2025-12-02 16:03:04 +08:00
stt
22cd6088a3 敏感用户列表联调 2025-12-02 15:55:20 +08:00
stt
faf7ba98a6 区分项目 2025-12-01 15:04:44 +08:00
guanj
b90f70c72d Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-12-01 15:02:50 +08:00
guanj
d38b426527 添加项目区分 2025-12-01 15:02:33 +08:00
stt
79a246cd87 接口路径修改 2025-12-01 14:15:16 +08:00
stt
58ee36c6a5 接口路径修改 2025-12-01 09:33:12 +08:00
stt
515dcfe76c 联调修改 2025-11-28 16:33:32 +08:00
stt
031fab286b 电压暂升的时候显示/ 2025-11-28 14:34:16 +08:00
stt
58bc269940 F47曲线详情,暂态事件明细详情联调 2025-11-28 14:06:33 +08:00
stt
80182cdc6f 治理报告联调接口 2025-11-28 11:22:33 +08:00
stt
8ce2968bee 修改样式 2025-11-27 16:21:52 +08:00
guanj
1a146afcd7 修改 波形样式 2025-11-27 15:45:42 +08:00
guanj
800ec7f0cf 修改波形样式 2025-11-27 15:25:33 +08:00
stt
e824f4823a rem改成px 2025-11-27 15:08:04 +08:00
stt
2476d2401e 加上YPT的代码 2025-11-27 15:04:32 +08:00
stt
f043b6dc1a F47曲线不可容忍事件也有小圆圈 2025-11-27 15:00:35 +08:00
stt
09bf34700a 趋势对比线条颜色修改 2025-11-27 14:58:06 +08:00
stt
f32673c92a 去掉小圆点 2025-11-27 14:47:04 +08:00
stt
2c7b5a8583 波形图标题修改 2025-11-27 14:38:30 +08:00
stt
3745d91a9d 指标拟合图没有数据的时候也要展示x,y轴 2025-11-27 14:36:27 +08:00
guanj
9117a6e3c6 修改接口名称 2025-11-26 16:25:26 +08:00
stt
e759f443d3 查看波形图联调修改 2025-11-26 15:37:56 +08:00
stt
af3f9fe607 接口路径修改 2025-11-26 14:33:23 +08:00
stt
d97e97f51c 接口路径修改 2025-11-26 14:31:34 +08:00
stt
5e1a628d53 接口路径修改 2025-11-26 14:20:19 +08:00
stt
acc5e93731 波形图去掉背景色,文字颜色改成黑色 2025-11-26 13:45:14 +08:00
stt
93586255fc 接口路径调整 2025-11-26 13:11:56 +08:00
stt
cf51ba9ff0 日历表格调整 2025-11-26 09:31:32 +08:00
stt
67d9aaf958 暂态事件概率分布联调修改 2025-11-25 16:12:20 +08:00
stt
f1439e0464 Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-11-25 15:39:20 +08:00
stt
29c88b56dc 暂态事件概率分布联调 2025-11-25 15:39:17 +08:00
guanj
0c71b2ac32 修改报表绑定指标 2025-11-25 15:15:38 +08:00
stt
f916721b6a 报表修改撤销 2025-11-25 14:35:38 +08:00
stt
f2d02ff7f5 暂态事件明细页面联调 2025-11-25 14:22:30 +08:00
stt
2de28c0067 暂态事件明细只能选择月份 2025-11-25 13:44:26 +08:00
stt
09326b287e 暂态事件明细页面联调 2025-11-25 11:38:34 +08:00
stt
d365932648 F47曲线联调修改 2025-11-25 11:00:35 +08:00
stt
925c9c6f15 暂态事件统计页面联调 2025-11-25 10:14:50 +08:00
stt
b0df1157ad 敏感负荷列表联调 2025-11-25 09:30:49 +08:00
stt
d450383cfd 加重复请求的接口 2025-11-24 15:15:36 +08:00
stt
e603ba9f8c 删除监测点列表的缓存 2025-11-24 15:12:24 +08:00
stt
704f735744 报表修改 2025-11-24 14:53:07 +08:00
stt
0249ccac48 报表修改 2025-11-24 14:43:13 +08:00
stt
e49e34df6d 全屏默认值小于当前时间,不禁用下一步按钮 2025-11-24 13:25:55 +08:00
stt
042798c6a7 趋势对比联调修改 2025-11-24 10:18:38 +08:00
stt
3827490cd1 监测点列表联调 2025-11-21 10:42:20 +08:00
stt
da7cb2cb90 取值字段修改 2025-11-20 10:59:30 +08:00
stt
36e182a99a 电网侧指标越限统计联调 2025-11-20 10:58:20 +08:00
stt
05e0287407 指标越限概率分布联调 2025-11-19 14:07:26 +08:00
stt
7a68a4114b 指标拟合图联调 2025-11-18 15:31:26 +08:00
guanj
ddc8f81196 微调 2025-11-18 14:54:52 +08:00
guanj
5bed340320 修改指标越限明细样式 2025-11-18 14:23:06 +08:00
stt
2048da5e52 指标拟合图联调 2025-11-18 14:21:31 +08:00
stt
4184d35854 Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-11-18 11:34:01 +08:00
stt
59b56d4a19 样式修改 2025-11-18 11:33:58 +08:00
sjl
0a6bd2e453 实时数据一二次值单位 2025-11-17 15:44:20 +08:00
stt
60e9685b7e 取值修改 2025-11-17 10:36:14 +08:00
stt
d8433d8d9c Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-11-17 09:51:36 +08:00
stt
0f090666e9 指标越限程度取值修改 2025-11-17 09:51:31 +08:00
sjl
1d849bcff9 Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-11-14 16:19:47 +08:00
sjl
e9b09e2193 正式用户权限分配 2025-11-14 16:19:18 +08:00
stt
f66dd649c7 保留两个小数 2025-11-14 16:18:38 +08:00
stt
af05b9c810 指标越限明细联调 2025-11-14 16:06:30 +08:00
guanj
0af955d05c 提交代码 2025-11-14 15:00:10 +08:00
stt
49dcf440ff 时间取值修改 2025-11-14 14:09:34 +08:00
stt
d6bfd8b958 修改时间传值 2025-11-14 13:42:26 +08:00
stt
ea6aed9b99 缓存修改,里面的时间切换不做缓存 2025-11-14 13:24:21 +08:00
stt
cd565c03ca 加上清除逻辑 2025-11-14 11:18:54 +08:00
stt
f82ef923ae 样式修改 2025-11-14 10:33:34 +08:00
guanj
4c2ed4aade 修改页面告警 2025-11-14 09:51:18 +08:00
stt
8cd3e14922 样式调整 2025-11-14 09:50:25 +08:00
stt
5580d0618e Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-11-14 09:34:42 +08:00
stt
c1ebcfed6f 指标越限程度联调 2025-11-14 09:34:39 +08:00
guanj
6303bd1e2c 修改测试问题 2025-11-14 09:33:35 +08:00
stt
d9efb37083 波形文件 2025-11-13 14:11:55 +08:00
stt
e271a3be06 联调修改 2025-11-13 14:11:26 +08:00
stt
078488a842 监测点详情 趋势图数据 2025-11-13 08:49:44 +08:00
stt
384ea90acd 监测点列表详情 2025-11-12 09:51:28 +08:00
221 changed files with 38865 additions and 23564 deletions

3
.env.ypt Normal file
View File

@@ -0,0 +1,3 @@
# 云平台
NODE_ENV = ypt
VITE_NAME="ypt"

View File

@@ -1,2 +1,3 @@
NODE_ENV = zl # 治理
VITE_NAME="zl" NODE_ENV = zl
VITE_NAME="zl"

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ dist-ssr
*.sln *.sln
*.sw? *.sw?
pnpm-lock.yaml pnpm-lock.yaml
#test

View File

@@ -6,8 +6,10 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"dev:zl": "vite --mode zl", "dev:zl": "vite --mode zl",
"dev:ypt": "vite --mode ypt",
"build": "vite build", "build": "vite build",
"build:zl": "vite build --mode zl", "build:zl": "vite build --mode zl",
"build:ypt": "vite build --mode ypt",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {

View File

@@ -1,144 +1,152 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 装置基础数据和模板数据 // 设备基础数据和模板数据
export function getDeviceData(deviceId: string, type: string, lineId: string) { export function getDeviceData(deviceId: string, type: string, lineId: string) {
let form = new FormData() let form = new FormData()
form.append('deviceId', deviceId) form.append('deviceId', deviceId)
form.append('lineId', lineId) form.append('lineId', lineId)
form.append('type', type) form.append('type', type)
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/deviceData', url: '/cs-device-boot/EquipmentDelivery/deviceData',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
}, },
data: form data: form
}) })
} }
//获取趋势数据、暂态数据、实时数据 //获取趋势数据、暂态数据、实时数据
export function getTabsDataByType(data: any) { export function getTabsDataByType(data: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/deviceDataByType', url: '/cs-device-boot/csGroup/deviceDataByType',
method: 'POST', method: 'POST',
data data
}) })
} }
/**** 获取基础实施数据 ****/ /**** 获取基础实施数据 ****/
export function getBasicRealData(id: any) { export function getBasicRealData(id: any) {
return createAxios({ return createAxios({
url: `/cs-harmonic-boot/realData/getBaseRealData?lineId=${id}`, url: `/cs-harmonic-boot/realData/getBaseRealData?lineId=${id}`,
method: 'POST' method: 'POST'
}) })
} }
/**** 获取谐波实时数据 ****/ /**** 获取谐波实时数据 ****/
export function getHarmRealData(id: any, target: any) { export function getHarmRealData(id: any, target: any) {
return createAxios({ return createAxios({
url: `/cs-harmonic-boot/realData/getHarmRealData?lineId=${id}&target=${target}`, url: `/cs-harmonic-boot/realData/getHarmRealData?lineId=${id}&target=${target}`,
method: 'POST' method: 'POST'
}) })
} }
/**** 获取国标限值 ****/ /**** 获取国标限值 ****/
export function getOverLimitData(id: any) { export function getOverLimitData(id: any) {
return createAxios({ return createAxios({
url: `/cs-device-boot/csline/getOverLimitData?id=${id}`, url: `/cs-device-boot/csline/getOverLimitData?id=${id}`,
method: 'POST' method: 'POST'
}) })
} }
//获取实时数据列表数据 //获取实时数据列表数据
export function getRealTimeTableList() { export function getRealTimeTableList() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getGroupPortableStatistical', url: '/cs-device-boot/csGroup/getGroupPortableStatistical',
method: 'GET' method: 'GET'
}) })
} }
//离线数据导入 //离线数据导入
export function uploadOffLineDataFile(data: any) { export function uploadOffLineDataFile(data: any) {
return createAxios({ return createAxios({
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
}, },
url: '/cs-device-boot/portableOfflLog/importEquipment', url: '/cs-device-boot/portableOfflLog/importEquipment',
method: 'POST', method: 'POST',
data data
}) })
} }
//查询实时数据中实时趋势中指标分组 //查询实时数据中实时趋势中指标分组
export function getDeviceTrendDataGroup() { export function getDeviceTrendDataGroup() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getDeviceTrendDataGroup', url: '/cs-device-boot/csGroup/getDeviceTrendDataGroup',
method: 'GET' method: 'GET'
}) })
} }
//根据指标分组查询实时数据中实时趋势 //根据指标分组查询实时数据中实时趋势
export function getDeviceTrendData(query: any) { export function getDeviceTrendData(query: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getDeviceTrendData', url: '/cs-device-boot/csGroup/getDeviceTrendData',
method: 'GET', method: 'GET',
params: query params: query
}) })
} }
//查询实时数据-谐波频谱-稳态指标 //查询实时数据-谐波频谱-稳态指标
export function getGroupPortableStatistical() { export function getGroupPortableStatistical() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getGroupPortableStatistical', url: '/cs-device-boot/csGroup/getGroupPortableStatistical',
method: 'GET' method: 'GET'
}) })
} }
//查询实时数据-谐波频谱 //查询实时数据-谐波频谱
export function getDeviceHarmonicSpectrumData(data: any) { export function getDeviceHarmonicSpectrumData(data: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getDeviceHarmonicSpectrumData', url: '/cs-device-boot/csGroup/getDeviceHarmonicSpectrumData',
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
//获取指标类型-谐波频谱 //获取指标类型-谐波频谱
export function queryDictType(data?: any) { export function queryDictType(data?: any) {
return createAxios({ return createAxios({
url: '/system-boot/dictTree/queryDictType', url: '/system-boot/dictTree/queryDictType',
method: 'GET', method: 'GET',
params: data params: data
}) })
} }
//根据监测点id获取监测点详情 //根据监测点id获取监测点详情
export function getById(data?: any) { export function getById(data?: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csline/getById', url: '/cs-device-boot/csline/getById',
method: 'POST', method: 'POST',
params: data params: data
}) })
} }
//测试项日志修改 //测试项日志修改
export function updateRecordData(data?: any) { export function updateRecordData(data?: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/wlRecord/updateRecordData', url: '/cs-device-boot/wlRecord/updateRecordData',
method: 'POST', method: 'POST',
data data
}) })
} }
//模块数据 //模块数据
export function allModelData(data?: any) { export function allModelData(data?: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/data/allModelData', url: '/cs-harmonic-boot/data/allModelData',
method: 'POST', method: 'POST',
data data
}) })
} }
//刷新状态 //刷新状态
export function getModuleState(data?: any) { export function getModuleState(data?: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/data/getModuleState', url: '/cs-harmonic-boot/data/getModuleState',
method: 'POST', method: 'POST',
params: data params: data
}) })
} }
//获取运行取数
export function getRawData(data?: any) {
return createAxios({
url: '/cs-device-boot/pqsCommunicate/getRawData',
method: 'POST',
data
})
}

View File

@@ -1,4 +1,4 @@
import createAxios from "@/utils/request"; import createAxios from '@/utils/request'
//根据Id获取台账信息 //根据Id获取台账信息
export function getInfoById(id: any) { export function getInfoById(id: any) {
@@ -11,7 +11,6 @@ export function getInfoById(id: any) {
}) })
} }
//工程查询通过id获取 //工程查询通过id获取
export function getEngineerById(id: any) { export function getEngineerById(id: any) {
let form = new FormData() let form = new FormData()
@@ -23,7 +22,6 @@ export function getEngineerById(id: any) {
}) })
} }
//项目查询通过id获取 //项目查询通过id获取
export function getProjectById(id: any) { export function getProjectById(id: any) {
let form = new FormData() let form = new FormData()
@@ -53,7 +51,7 @@ export function getById(id: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csline/getById', url: '/cs-device-boot/csline/getById',
method: 'POST', method: 'POST',
data: form data: form
}) })
} }
@@ -75,13 +73,15 @@ export function addLedger(data: any) {
} }
//修改-删除项目 //修改-删除项目
export function deleteProject(id: any,name:any,area:any,description:any,status:any) { export function deleteProject(id: any, name: any, area: any, description: any, status: any, sort: any, topoIds: any) {
let form = new FormData() let form = new FormData()
form.append('id', id) form.append('id', id)
form.append('name', name) form.append('name', name)
form.append('area', area) form.append('area', area)
form.append('description', description) form.append('description', description)
form.append('status', status) form.append('status', status)
form.append('sort', sort)
form.append('topoIds', topoIds)
return createAxios({ return createAxios({
url: '/cs-device-boot/project/auditAppProject', url: '/cs-device-boot/project/auditAppProject',
method: 'post', method: 'post',
@@ -105,7 +105,7 @@ export const deleteLine = (id: any) => {
let form = new FormData() let form = new FormData()
form.append('id', id) form.append('id', id)
return createAxios({ return createAxios({
url: '/cs-device-boot/csline/delCldLine', url: '/cs-device-boot/csline/delCldLine',
method: 'POST', method: 'POST',
data: form data: form
}) })
@@ -120,7 +120,6 @@ export function updateEquipment(data: any) {
}) })
} }
//修改监测点 //修改监测点
export function updateLine(data: any) { export function updateLine(data: any) {
return createAxios({ return createAxios({
@@ -134,8 +133,7 @@ export function updateLine(data: any) {
export function pushLog() { export function pushLog() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csTerminalLogs/pushCldInfo', url: '/cs-device-boot/csTerminalLogs/pushCldInfo',
method: 'post', method: 'post'
}) })
} }
@@ -143,7 +141,6 @@ export function pushLog() {
export function queryPushResult() { export function queryPushResult() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csTerminalReply/queryData', url: '/cs-device-boot/csTerminalReply/queryData',
method: 'post', method: 'post'
}) })
} }

View File

@@ -1,86 +1,86 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 查询分组 // 查询分组
export function getGroup(dataSet: string) { export function getGroup(dataSet: string) {
let form = new FormData() let form = new FormData()
form.append('dataSet', dataSet) form.append('dataSet', dataSet)
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getGroup', url: '/cs-device-boot/csGroup/getGroup',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
}, },
data: form data: form
}) })
} }
// 装置分组实时数据 // 设备分组实时数据
export function deviceHisData(data: any) { export function deviceHisData(data: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/deviceHistoryData', url: '/cs-device-boot/csGroup/deviceHistoryData',
method: 'POST', method: 'POST',
data: Object.assign( data: Object.assign(
{ {
endTime: '', endTime: '',
id: '', id: '',
lineId: '', lineId: '',
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
startTime: '' startTime: ''
}, },
data data
) )
}) })
} }
// 装置分组历史数据 // 设备分组历史数据
export function deviceRtData(data: any) { export function deviceRtData(data: any) {
let form = new FormData() let form = new FormData()
form.append('id', data.id) form.append('id', data.id)
form.append('lineId', data.lineId) form.append('lineId', data.lineId)
form.append('pageNum', data.pageNum) form.append('pageNum', data.pageNum)
form.append('pageSize', data.pageSize) form.append('pageSize', data.pageSize)
form.append('searchValue', data.searchValue) form.append('searchValue', data.searchValue)
form.append('dataLevel', data.dataLevel) form.append('dataLevel', data.dataLevel)
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/deviceRtData', url: '/cs-device-boot/csGroup/deviceRtData',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
}, },
data: form data: form
}) })
} }
// 装置分组历史数据 // 设备分组历史数据
export function realTimeData(data: any) { export function realTimeData(data: any) {
let form = new FormData() let form = new FormData()
form.append('id', data.id) form.append('id', data.id)
form.append('lineId', data.lineId) form.append('lineId', data.lineId)
form.append('pageNum', data.pageNum) form.append('pageNum', data.pageNum)
form.append('pageSize', data.pageSize) form.append('pageSize', data.pageSize)
form.append('searchValue', data.searchValue) form.append('searchValue', data.searchValue)
form.append('targetType', data.targetType) form.append('targetType', data.targetType)
form.append('dataLevel', data.dataLevel) form.append('dataLevel', data.dataLevel)
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/data/realTimeData', url: '/cs-harmonic-boot/data/realTimeData',
method: 'POST', method: 'POST',
data data
}) })
} }
// 设备监控-》测试项数据 // 设备监控-》测试项数据
export function getTestData(data: any) { export function getTestData(data: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/data/getTestData', url: '/cs-harmonic-boot/data/getTestData',
method: 'POST', method: 'POST',
data data
}) })
} }
// 设备监控-删除装置测试项 // 设备监控-删除设备测试项
export function deleteItem(data: any) { export function deleteItem(data: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/wlRecord/deleteItem', url: '/cs-device-boot/wlRecord/deleteItem',
method: 'POST', method: 'POST',
params: data params: data
}) })
} }

View File

@@ -1,17 +1,26 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 设备列表 // 设备列表
export function getDeviceTree() { export function getDeviceTree(params?: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csLedger/deviceTree', url: '/cs-device-boot/csLedger/deviceTree',
method: 'POST' method: 'POST',
params
}) })
} }
// 监测点列表 // 监测点列表
export function getLineTree() { export function getLineTree(params?: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csLedger/lineTree', url: '/cs-device-boot/csLedger/lineTree',
method: 'POST',
params
})
}
// 监测点列表治理
export function objTree() {
return createAxios({
url: '/cs-device-boot/csLedger/objTree',
method: 'POST' method: 'POST'
}) })
} }
@@ -24,4 +33,11 @@ export function getCldTree() {
method: 'POST' method: 'POST'
}) })
} }
//报表树
export function lineTree() {
return createAxios({
url: '/cs-device-boot/csLedger/lineTree',
method: 'POST'
})
}

View File

@@ -1,41 +1,90 @@
import request from '@/utils/request' import request from '@/utils/request'
// 新增程序版本 // 新增程序版本
export const addEdData = (data) => { export const addEdData = data => {
return request({ return request({
url: '/cs-device-boot/edData/addEdData', url: '/cs-device-boot/edData/addEdData',
method: 'post', method: 'post',
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, },
data: data, data: data
}) })
} }
export const auditEdData = (data) => { export const auditEdData = data => {
return request({ return request({
url: '/cs-device-boot/edData/auditEdData', url: '/cs-device-boot/edData/auditEdData',
method: 'post', method: 'post',
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, },
data: data, data: data
}) })
} }
// 修改-删除工程 // 修改-删除工程
export const auditEngineering = (data:any)=> { export const auditEngineering = (data: any) => {
return request({ return request({
url: '/cs-device-boot/engineering/auditEngineering', url: '/cs-device-boot/engineering/auditEngineering',
method: 'post', method: 'post',
data: data, data: data
}) })
} }
// 修改项目 // 修改项目
export const updateProject = (data:any) => { export const updateProject = (data: any) => {
return request({ return request({
url: '/cs-device-boot/project/updateProject', url: '/cs-device-boot/project/updateProject',
method: 'post', method: 'post',
data: data, data: data
}) })
} }
// 新增工程
export const addEngineering = (data: any) => {
return request({
url: '/cs-device-boot/engineeringProjectRelation/addEngineering',
method: 'post',
data: data
})
}
// 修改工程
export const updateEngineering = (data: any) => {
return request({
url: '/cs-device-boot/engineeringProjectRelation/updateEngineering',
method: 'post',
data: data
})
}
// 刪除工程
export const deleteEngineering = (data: any) => {
return request({
url: '/cs-device-boot/engineeringProjectRelation/deleteEngineering',
method: 'post',
params: data
})
}
// 刪除項目
export const deleteProject = (data: any) => {
return request({
url: '/cs-device-boot/engineeringProjectRelation/deleteProject',
method: 'post',
params: data
})
}
// 新增项目
export const addProject = (data: any) => {
return request({
url: '/cs-device-boot/engineeringProjectRelation/addProject',
method: 'post',
data: data
})
}
// 修改项目
export const updateProjects = (data: any) => {
return request({
url: '/cs-device-boot/engineeringProjectRelation/updateProject',
method: 'post',
data: data
})
}

View File

@@ -1,93 +1,140 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 设备文件根目录查询 // 设备文件根目录查询
export function getDeviceRootPath(nDid) { export function getDeviceRootPath(nDid) {
return createAxios({ return createAxios({
url: '/cs-device-boot/deviceFile/askDeviceRootPath?nDid=' + nDid, url: '/cs-device-boot/deviceFile/askDeviceRootPath?nDid=' + nDid,
method: 'POST' method: 'POST'
}) })
} }
// 设备文件-目录信息询问 // 设备文件-目录信息询问
export function getFileServiceFileOrDir(data) { export function getFileServiceFileOrDir(data) {
return createAxios({ return createAxios({
url: `cs-device-boot/deviceFile/askDeviceFileOrDir?nDid=${data.nDid}&name=${data.name}&type=${data.type}`, url: `cs-device-boot/deviceFile/askDeviceFileOrDir?nDid=${data.nDid}&name=${data.name}&type=${data.type}`,
method: 'POST' method: 'POST'
}) })
} }
// 监测设备-目录信息询问
//设备文件下载 export function listDir(data) {
export function downLoadDeviceFile(data) { return createAxios({
return createAxios({ url: `/zl-event-boot/file/listDir`,
url: `/cs-device-boot/deviceFile/downloadFile?nDid=${data.nDid}&name=${data.name}&fileCheck=${data.fileCheck}&size=${data.size}`, method: 'POST',
method: 'POST' data: data
}) })
} }
// 下载文件
//获取下载文件的文件路径地址 export function downloadFileFromFrontr(data: any) {
export function downLoadDeviceFilePath(obj) { return createAxios({
let form = new FormData() url: `/zl-event-boot/file/downloadFileFromFront`,
form.append('name', obj.name) method: 'POST',
form.append('nDid', obj.nDid) data: data,
return createAxios({ responseType: 'blob'
url: `/cs-device-boot/deviceFile/getDownloadFilePath`, })
method: 'POST', }
headers: { // 删除文件
'Content-Type': 'application/x-www-form-urlencoded' export function deleteCld(data: any) {
}, return createAxios({
data: form url: `/zl-event-boot/file/delete`,
}) method: 'POST',
} data: data
//装置重启 })
export function reStartDevice(data) { }
return createAxios({ // 新建文件
url: `/cs-device-boot/EquipmentDelivery/rebootDevice?nDid=${data.nDid}`, export function mkdir(data: any) {
method: 'POST' return createAxios({
}) url: `/zl-event-boot/file/mkdir`,
} method: 'POST',
data: data
//上传文件至装置 })
export function uploadDeviceFile(data) { }
let form = new FormData() // 上传文件
form.append('file', data.file) export function uploadFileToFront(obj: any) {
form.append('filePath', data.filePath) let form = new FormData()
form.append('id', data.id) form.append('file', obj.file)
return createAxios({ form.append('devId', obj.devId)
url: `/access-boot/analyzeModel/uploadDevFile`, form.append('dirPath', obj.dirPath)
method: 'POST', return createAxios({
headers: { url: `/zl-event-boot/file/uploadFileToFront`,
'Content-Type': 'application/x-www-form-urlencoded' method: 'POST',
}, headers: {
data: form 'Content-Type': 'application/x-www-form-urlencoded'
}) },
} data: form
})
//新建文件夹目录 }
export function addDeviceDir(data) { //设备文件下载
let form = new FormData() export function downLoadDeviceFile(data) {
form.append('nDid', data.nDid) return createAxios({
form.append('path', data.path) url: `/cs-device-boot/deviceFile/downloadFile?nDid=${data.nDid}&name=${data.name}&fileCheck=${data.fileCheck}&size=${data.size}`,
return createAxios({ method: 'POST'
url: `/access-boot/askDeviceData/createFolder`, })
method: 'POST', }
headers: {
'Content-Type': 'application/x-www-form-urlencoded' //获取下载文件的文件路径地址
}, export function downLoadDeviceFilePath(obj) {
data: form let form = new FormData()
}) form.append('name', obj.name)
} form.append('nDid', obj.nDid)
return createAxios({
//删除文件/文件夹 url: `/cs-device-boot/deviceFile/getDownloadFilePath`,
export function delDeviceDir(data) { method: 'POST',
let form = new FormData() headers: {
form.append('nDid', data.nDid) 'Content-Type': 'application/x-www-form-urlencoded'
form.append('path', data.path) },
return createAxios({ data: form
url: `/access-boot/askDeviceData/deleteFolder`, })
method: 'POST', }
headers: { //设备重启
'Content-Type': 'application/x-www-form-urlencoded' export function reStartDevice(data) {
}, return createAxios({
data: form url: `/cs-device-boot/EquipmentDelivery/rebootDevice?nDid=${data.nDid}`,
}) method: 'POST'
} })
}
//上传文件至设备
export function uploadDeviceFile(data) {
let form = new FormData()
form.append('file', data.file)
form.append('filePath', data.filePath)
form.append('id', data.id)
return createAxios({
url: `/access-boot/analyzeModel/uploadDevFile`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: form
})
}
//新建文件夹目录
export function addDeviceDir(data) {
let form = new FormData()
form.append('nDid', data.nDid)
form.append('path', data.path)
return createAxios({
url: `/access-boot/askDeviceData/createFolder`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: form
})
}
//删除文件/文件夹
export function delDeviceDir(data) {
let form = new FormData()
form.append('nDid', data.nDid)
form.append('path', data.path)
return createAxios({
url: `/access-boot/askDeviceData/deleteFolder`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: form
})
}

View File

@@ -0,0 +1,28 @@
import request from '@/utils/request'
// 新增敏感用户
export function saveUser(data: any) {
return request({
url: '/cs-harmonic-boot/pqSensitiveUser/save',
method: 'post',
data: data
})
}
// 修改敏感用户
export function updateUser(data: any) {
return request({
url: '/cs-harmonic-boot/pqSensitiveUser/update',
method: 'post',
data: data
})
}
// 删除敏感用户
export function deleteUser(data: any) {
return request({
url: '/cs-harmonic-boot/pqSensitiveUser/delete',
method: 'post',
data: data
})
}

View File

@@ -53,3 +53,25 @@ export const removeMarketData = (data: any) => {
params: data params: data
}) })
} }
/**
* 获取用户未绑定的在运的便携式设备
*/
export const queryRunPortableDevByUseId = (data: any) => {
return createAxios({
url: '/cs-device-boot/EquipmentDelivery/getRunPortableDev',
method: 'post',
params: data
})
}
/**
* 获取用户未绑定的工程信息
*/
export const queryUnlinkEngineeringByUseId = (data: any) => {
return createAxios({
url: '/cs-device-boot/engineering/getUnlinkedEngineering',
method: 'post',
params: data
})
}

View File

@@ -1,22 +1,59 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
import { genFileId, ElMessage, ElNotification } from 'element-plus'
// 查询设备数据趋势 // 查询设备数据趋势
export function getDeviceDataTrend(data: any) { export function getDeviceDataTrend(data: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/datatrend/querydatatrend', url: '/cs-harmonic-boot/datatrend/querydatatrend',
method: 'POST', method: 'POST',
data data
}) })
} }
// 波形下载
export function getFileZip(params: any) {
// 波形下载 return createAxios({
export function getFileZip(params: any) { url: '/cs-harmonic-boot/event/getFileZip',
return createAxios({ method: 'get',
url: '/cs-harmonic-boot/event/getFileZip', params,
method: 'get', responseType: 'blob'
params, })
responseType: 'blob' }
})
} export function exportModel(data: any) {
return createAxios({
url: '/cs-harmonic-boot/exportmodel/exportModel',
method: 'post',
data: data,
responseType: 'blob'
}).then(async res => {
let load: any = await readJsonBlob(res)
if (load.code) {
if (load.data.code == 'A0011') {
ElMessage.warning('下载失败!')
} else {
ElMessage.warning(load.data.message)
}
} else {
return res
}
})
}
async function readJsonBlob(blob) {
try {
// 1. Blob.text() 读取二进制 → 直接转为 字符串(自动处理编码)
const jsonStr = await blob.text()
// 2. JSON.parse 解析字符串 → 得到可用的 JS 对象/数组
const jsonData = JSON.parse(jsonStr)
// 3. 拿到数据,后续随便用
return {
code: true,
data: jsonData
}
} catch (err) {
return {
code: false,
data: {}
}
// console.error('解析Blob的JSON数据失败', err)
}
}

View File

@@ -1,50 +1,67 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
//新增组态项目 //新增组态项目
export function add(data: any) { export function add(data: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/csconfiguration/add', url: '/cs-harmonic-boot/csconfiguration/add',
method: 'post', method: 'post',
data data
}) })
} }
//组态项目分页查询 //组态项目分页查询
export function coFqueryPage(data: any) { export function coFqueryPage(data: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/csconfiguration/queryPage', url: '/cs-harmonic-boot/csconfiguration/queryPage',
method: 'post', method: 'post',
data data
}) })
} }
//修改组态项目 //修改组态项目
export function audit(data: any) { export function audit(data: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/csconfiguration/audit', url: '/cs-harmonic-boot/csconfiguration/audit',
method: 'post', method: 'post',
data data
}) })
} }
//组态页面分页查询 //组态页面分页查询
export function queryPageData(data: any) { export function queryPageData(data: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/cspage/queryPage', url: '/cs-harmonic-boot/cspage/queryPage',
method: 'post', method: 'post',
data data
}) })
} }
//查询工程列表 //查询工程列表
export function deviceTree(data: any) { export function deviceTree(data: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csLedger/deviceTree', url: '/cs-device-boot/csLedger/deviceTree',
method: 'post', method: 'post',
data data
}) })
} }
//三层设备树(项目层根节点为治理设备和便携式设备组态) //三层设备树(项目层根节点为治理设备和便携式设备组态)
export function getztProjectTree() { export function getztProjectTree() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csLedger/getztProjectTree', url: '/cs-device-boot/csLedger/getztProjectTree',
method: 'post', method: 'post',
}) })
} }
//根据用户id获取组件信息
export function getByUserId(data: any) {
return createAxios({
url: '/cs-harmonic-boot/cspage/getByUserId',
method: 'post',
params: data
})
}
//c保存组态界面与用户的关系
export function savePageIdWithUser(data: any) {
return createAxios({
url: '/cs-harmonic-boot/cspage/savePageIdWithUser',
method: 'post',
params: data
})
}

View File

@@ -1,27 +1,43 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 获取设备补召页面数据 // 获取设备补召页面数据
export function getMakeUpData(data: any) { export function getMakeUpData(data: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/offlineDataUpload/makeUpData?lineId='+data, url: '/cs-harmonic-boot/offlineDataUpload/makeUpData?lineId=' + data,
method: 'POST' method: 'POST'
}) })
} }
//查询装置目录-文件 //查询设备目录-文件
export function getAskDirOrFile(data: any) { export function getAskDirOrFile(data: any) {
return createAxios({ return createAxios({
url: `/cs-harmonic-boot/offlineDataUpload/askDirOrFile?fileType=${data.fileType}&nDid=${data.nDid}&path=${data.path}&prjName=${data.prjName}`, url: `/cs-harmonic-boot/offlineDataUpload/askDirOrFile?fileType=${data.fileType}&nDid=${data.nDid}&path=${data.path}&prjName=${data.prjName}`,
method: 'POST' method: 'POST'
}) })
} }
//设备补召操作 //设备补召操作
// 获取设备补召页面数据 // 获取设备补召页面数据
export function offlineDataUploadMakeUp(data: any) { export function offlineDataUploadMakeUp(data: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/offlineDataUpload/makeUp', url: '/cs-harmonic-boot/offlineDataUpload/makeUp',
method: 'POST', method: 'POST',
data data
}) })
} }
//设备补召操作
// 根据id集合获取敏感负荷用户列表
export function getListByIds() {
return createAxios({
url: '/cs-harmonic-boot/pqSensitiveUser/getListByIds',
method: 'POST'
})
}
// 根据id集合获取敏感负荷用户列表
export function getList(data) {
return createAxios({
url: '/cs-harmonic-boot/pqSensitiveUser/getList',
method: 'POST',
data
})
}

View File

@@ -1,98 +1,105 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 新增出厂设备 // 新增出厂设备
export const addEquipmentDelivery = (data: any) => { export const addEquipmentDelivery = (data: any) => {
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/addEquipmentDelivery', url: '/cs-device-boot/EquipmentDelivery/addEquipmentDelivery',
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
// 删除出厂设备 // 删除出厂设备
export const deleteEquipmentDelivery = (id: any) => { export const deleteEquipmentDelivery = (id: any) => {
let form = new FormData() let form = new FormData()
form.append('id', id) form.append('id', id)
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/AuditEquipmentDelivery', url: '/cs-device-boot/EquipmentDelivery/AuditEquipmentDelivery',
method: 'POST', method: 'POST',
data: form data: form
}) })
} }
// 恢复出厂设置 // 恢复出厂设置
export const resetEquipmentDelivery = (id: any) => { export const resetEquipmentDelivery = (id: any) => {
let form = new FormData() let form = new FormData()
form.append('nDid', id) form.append('nDid', id)
return createAxios({ return createAxios({
url: '/access-boot/device/resetFactory', url: '/access-boot/device/resetFactory',
method: 'POST', method: 'POST',
data: form data: form
}) })
} }
// 编辑出厂设备 // 编辑出厂设备
export const editEquipmentDelivery = (data: any) => { export const editEquipmentDelivery = (data: any) => {
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/updateEquipmentDelivery', url: '/cs-device-boot/EquipmentDelivery/updateEquipmentDelivery',
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
// 上传拓扑图 // 上传拓扑图
export const uploadTopo = (file: any) => { export const uploadTopo = (file: any) => {
let form = new FormData() let form = new FormData()
form.append('file', file) form.append('file', file)
return createAxios({ return createAxios({
url: '/cs-device-boot/topologyTemplate/uploadImage', url: '/cs-device-boot/topologyTemplate/uploadImage',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
}, },
data: form data: form
}) })
} }
// 批量导入设备 // 批量导入设备
export const batchImportDevice = (file: any) => { export const batchImportDevice = (file: any) => {
let form = new FormData() let form = new FormData()
form.append('file', file) form.append('file', file)
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/importEquipment', url: '/cs-device-boot/EquipmentDelivery/importEquipment',
method: 'POST', method: 'POST',
responseType: 'blob', responseType: 'blob',
data: form data: form
}) })
} }
// 直连设备注册接入 // 直连设备注册接入
export const governDeviceRegister = (data: any) => { export const governDeviceRegister = (data: any) => {
return createAxios({ return createAxios({
url: `/access-boot/device/register?nDid=${data.nDid}&type=${data.type}`, url: `/access-boot/device/register?nDid=${data.nDid}&type=${data.type}`,
method: 'POST' method: 'POST'
}) })
} }
// 便携式设备注册 // 便携式设备注册
export const portableDeviceRegister = (params: any) => { export const portableDeviceRegister = (params: any) => {
return createAxios({ return createAxios({
url: `/access-boot/device/wlRegister`, url: `/access-boot/device/wlRegister`,
method: 'POST', method: 'POST',
params params
}) })
} }
// 便携式设备接入 // 便携式设备接入
export const portableDeviceAccess = (data: any) => { export const portableDeviceAccess = (data: any) => {
return createAxios({ return createAxios({
url: `/access-boot/device/wlAccess?nDid=${data.nDid}`, url: `/access-boot/device/wlAccess?nDid=${data.nDid}`,
method: 'POST', method: 'POST'
}) })
} }
// 下载模版 // 下载模版
export function getExcelTemplate() { export function getExcelTemplate() {
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/getExcelTemplate', url: '/cs-device-boot/EquipmentDelivery/getExcelTemplate',
method: 'get', method: 'get',
responseType: 'blob' responseType: 'blob'
}) })
} }
// 查询工程信息列表
export function engineeringProject() {
return createAxios({
url: '/cs-device-boot/engineeringProjectRelation/list',
method: 'post'
})
}

View File

@@ -1,20 +1,30 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 更新问题状态 // 更新问题状态
export function auditFeedBack(data: any) { export function auditFeedBack(data: any) {
return createAxios({ return createAxios({
url: '/cs-system-boot/feedback/auditFeedBack', url: '/cs-system-boot/feedback/auditFeedBack',
method: 'post', method: 'post',
params: data params: data
}) })
} }
//下载文件 //下载文件
export function downLoadFile(filePath: any) { export function downLoadFile(filePath: any) {
return createAxios({ return createAxios({
url: '/system-boot/file/download', url: '/system-boot/file/download',
method: 'get', method: 'get',
responseType: 'blob', responseType: 'blob',
params: { filePath: filePath } params: { filePath: filePath }
}) })
}
//获取文件的一个短期url
export function getFileUrl(filePath: any) {
return createAxios({
url: '/system-boot/file/getFileUrl',
method: 'get',
// responseType: 'blob',
params: { filePath: filePath }
})
} }

View File

@@ -0,0 +1,29 @@
import createAxios from '@/utils/request'
// * 根据用户id查询工程和便携式设备
export const queryDevByUseId = (data: any) => {
return createAxios({
url: '/cs-system-boot/wlUser/selectDevByUserId',
method: 'POST',
params: data
})
}
// * 用户绑定工程和便携式设备
export const add = (data: any) => {
return createAxios({
url: '/cs-system-boot/wlUser/addUserDev',
method: 'POST',
data: data
})
}
/**
* 用户取消绑定工程和便携式设备
*/
export const removeUserDev = (data: any) => {
return createAxios({
url: '/cs-system-boot/wlUser/deleteUserDev',
method: 'post',
data: data
})
}

View File

@@ -1,10 +1,299 @@
import request from '@/utils/request' import request from '@/utils/request'
import { genFileId, ElMessage, ElNotification } from 'element-plus'
// 主要监测点列表查询 // 主要监测点列表查询>>分页
export function mainLineList(data: any) { export function mainLineList(data: any) {
return request({ return request({
url: '/harmonic-boot/mainLine/list', url: '/cs-harmonic-boot/mainLine/list',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 主要监测点指标越限详情
export function statLimitRateDetails(data: any) {
return request({
url: '/cs-harmonic-boot/mainLine/statLimitRateDetails',
method: 'post',
data: data
})
}
// 查询监测点列表=全部>>不分页
export function cslineList(data: any) {
return request({
url: '/cs-device-boot/csline/list',
method: 'post',
data: data
})
}
// 监测点详情 趋势图数据
export function trendData(data: any) {
return request({
url: '/cs-device-boot/csGroup/trendData',
method: 'post',
data: data
})
}
// 每日越限占比统计
export function totalLimitStatisticsDetails(data: any) {
return request({
url: '/cs-harmonic-boot/totalLimitStatistics/details',
method: 'post',
data: data
})
}
// 总体指标越限统计列表
export function totalLimitStatisticsList(data: any) {
return request({
url: '/cs-harmonic-boot/totalLimitStatistics/list',
method: 'post',
data: data
})
}
// 总体指标越限统计数据
export function totalLimitStatisticsData(data: any) {
return request({
url: '/cs-harmonic-boot/totalLimitStatistics/data',
method: 'post',
data: data
})
}
// 指标越限程度数据
export function limitExtentData(data: any) {
return request({
url: '/cs-harmonic-boot/limitRateDetailD/limitExtentData',
method: 'post',
data: data
})
}
// 指标日趋势图数据
export function limitExtentDayData(data: any) {
return request({
url: '/cs-harmonic-boot/limitRateDetailD/limitExtentDayData',
method: 'post',
data: data
})
}
// 指标越限明细日历数据
export function limitCalendarData(data: any) {
return request({
url: '/cs-harmonic-boot/limitRateDetailD/limitCalendarData',
method: 'post',
data: data
})
}
//指标拟合图数据
export function fittingData(data: any) {
return request({
url: '/cs-device-boot/csGroup/fittingData',
method: 'post',
data: data
})
}
//指标越限时间概率分布
export function limitTimeProbabilityData(data: any) {
return request({
url: '/cs-harmonic-boot/limitRateDetailD/limitTimeProbabilityData',
method: 'post',
data: data
})
}
//指标越限程度概率分布
export function limitProbabilityData(data: any) {
return request({
url: '/cs-harmonic-boot/limitRateDetailD/limitProbabilityData',
method: 'post',
data: data
})
}
// 电网侧指标越限统计列表
export function gridSideLimitStatisticsList(data: any) {
return request({
url: '/cs-harmonic-boot/gridSideLimitStatistics/list',
method: 'post',
data: data
})
}
// 电网侧指标越限统计数据
export function gridSideLimitStatisticsData(data: any) {
return request({
url: '/cs-harmonic-boot/gridSideLimitStatistics/data',
method: 'post',
data: data
})
}
// 敏感负荷用户监测点列表
export function governLineList(data: any) {
return request({
url: '/cs-device-boot/csline/getSensitiveUserLineList',
method: 'post',
data: data
})
}
// 根据id集合获取敏感负荷用户列表
export function getListByIds(data: any) {
return request({
url: '/cs-harmonic-boot/pqSensitiveUser/getListByIds',
method: 'post',
data: data
})
}
// 上传治理报告
export function uploadReport(data: any) {
return request({
url: '/cs-device-boot/csline/uploadReport',
method: 'post',
data: data
})
}
// 获取治理报告链接
export function getReportUrl(data: any) {
return request({
url: '/cs-device-boot/csline/getReportUrl',
method: 'post',
params: data
})
}
// 查询监测对象电网侧和负载侧监测点指标趋势对比数据
export function sensitiveUserTrendData(data: any) {
return request({
url: '/cs-device-boot/csGroup/sensitiveUserTrendData',
method: 'post',
data: data
})
}
// 获取敏感负荷用户列表
export function getList(data: any) {
return request({
url: '/cs-harmonic-boot/pqSensitiveUser/getList',
method: 'post',
data: data
})
}
// F47曲线
export function f47Curve(data: any) {
return request({
url: '/cs-harmonic-boot/csevent/f47Curve',
method: 'post',
data: data
})
}
// 获取电压暂态表及密度坐标图
export function getEventCoords(data: any) {
return request({
url: '/cs-harmonic-boot/csevent/getEventCoords',
method: 'post',
data: data
})
}
// 日历暂降事件详情
export function getEventDate(data: any) {
return request({
url: '/cs-harmonic-boot/csevent/getEventDate',
method: 'post',
data: data
})
}
// 暂降类型分类统计Echart
export function netEventEcharts(data: any) {
return request({
url: '/cs-harmonic-boot/csevent/netEventEcharts',
method: 'post',
data: data
})
}
// 暂降类型分类统计表格
export function netEventTable(data: any) {
return request({
url: '/cs-harmonic-boot/csevent/netEventTable',
method: 'post',
data: data
})
}
// 分页查询暂降事件
export function pageEvent(data: any) {
return request({
url: '/cs-harmonic-boot/event/pageEvent',
method: 'post',
data: data
})
}
// 暂态事件波形分析
export function analyseWave(data: any) {
return request({
url: '/cs-harmonic-boot/event/analyseWave',
method: 'get',
params: data
})
}
// 暂态监测点下拉框接口
export function getSimpleLine() {
return request({
url: '/cs-device-boot/csline/getSimpleLine',
method: 'get'
})
}
export function getLineExport(data: any) {
return request({
url: '/cs-harmonic-boot/eventReport/getLineExport',
method: 'post',
data: data,
responseType: 'blob'
}).then(async res => {
let load: any = await readJsonBlob(res)
if (load.code) {
if (load.data.code == 'A0011') {
ElMessage.warning('下载失败!')
} else {
ElMessage.warning(load.data.message)
}
} else {
return res
}
})
}
async function readJsonBlob(blob) {
try {
// 1. Blob.text() 读取二进制 → 直接转为 字符串(自动处理编码)
const jsonStr = await blob.text()
// 2. JSON.parse 解析字符串 → 得到可用的 JS 对象/数组
const jsonData = JSON.parse(jsonStr)
// 3. 拿到数据,后续随便用
return {
code: true,
data: jsonData
}
} catch (err) {
return {
code: false,
data: {}
}
// console.error('解析Blob的JSON数据失败', err)
}
}

View File

@@ -3,14 +3,15 @@ import createAxios from '@/utils/request'
// 获取参数指标 // 获取参数指标
export function getIndex() { export function getIndex() {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/reportChooseTree', // url: '/harmonic-boot/customReport/reportChooseTree',
url: '/cs-harmonic-boot/customReport/reportChooseTree',
method: 'get' method: 'get'
}) })
} }
//、查询数据激活报表模板 //、查询数据激活报表模板
export function updateTemplateActive(data) { export function updateTemplateActive(data) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/updateTemplateActive', url: '/cs-harmonic-boot/customReport/updateTemplateActive',
method: 'post', method: 'post',
data data
}) })
@@ -19,7 +20,8 @@ export function updateTemplateActive(data) {
//获取报表模板 //部门树查询 //获取报表模板 //部门树查询
export function getTemplateList(data:any) { export function getTemplateList(data:any) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/getTemplateList', // url: '/harmonic-boot/customReport/getTemplateList',
url: '/cs-harmonic-boot/customReport/getTemplateList',
// url:'/api3/harmonic-boot/customReport/getTemplateList', // url:'/api3/harmonic-boot/customReport/getTemplateList',
method: 'post', method: 'post',
data data
@@ -28,7 +30,7 @@ export function getTemplateList(data:any) {
//删除报表模板 //删除报表模板
export function delTemplate(data:any) { export function delTemplate(data:any) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/delTemplate', url: '/cs-harmonic-boot/customReport/delTemplate',
method: 'post', method: 'post',
data data
}) })
@@ -37,7 +39,7 @@ export function delTemplate(data:any) {
//修改获取数据 //修改获取数据
export function getCustomReportTemplateById(params) { export function getCustomReportTemplateById(params) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/getCustomReportTemplateById', url: '/cs-harmonic-boot/customReport/getCustomReportTemplateById',
method: 'get', method: 'get',
params params
}) })
@@ -46,7 +48,7 @@ export function getCustomReportTemplateById(params) {
//修改获取数据 //修改获取数据
export function viewCustomReportTemplateById(params) { export function viewCustomReportTemplateById(params) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/viewCustomReportTemplateById', url: '/cs-harmonic-boot/customReport/viewCustomReportTemplateById',
method: 'get', method: 'get',
params params
}) })
@@ -54,16 +56,17 @@ export function viewCustomReportTemplateById(params) {
//修改模板 //修改模板
export function dateTemplateup(data) { export function dateTemplateup(data) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/updateTemplate', url: '/cs-harmonic-boot/customReport/updateTemplate',
method: 'POST', method: 'POST',
data data
}) })
} }
//新增报表模板 //新增报表模板
export function addTemplate(data) { export function addTemplate(data:any) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/addTemplate', // url: '/harmonic-boot/customReport/addTemplate',
url: '/cs-harmonic-boot/customReport/addTemplate',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -71,7 +74,7 @@ export function addTemplate(data) {
//模板对应指标替换 //模板对应指标替换
export function getCustomReport(data: any) { export function getCustomReport(data: any) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/getCustomReport', url: '/cs-harmonic-boot/customReport/getCustomReport',
method: 'POST', method: 'POST',
data data
}) })
@@ -79,7 +82,7 @@ export function getCustomReport(data: any) {
//绑定模板 //绑定模板
export function updateBindTemplate(data) { export function updateBindTemplate(data) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/updateBindTemplate', url: '/cs-harmonic-boot/customReport/updateBindTemplate',
method: 'post', method: 'post',
data data
}) })
@@ -87,7 +90,7 @@ export function updateBindTemplate(data) {
//根据模板ID查询数据 //根据模板ID查询数据
export function getDataByTempId(params) { export function getDataByTempId(params) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/getDataByTempId', url: '/cs-harmonic-boot/customReport/getDataByTempId',
method: 'get', method: 'get',
params params
}) })
@@ -95,11 +98,19 @@ export function getDataByTempId(params) {
//根据部门查询模板 //根据部门查询模板
export function getTemplateByDept(params) { export function getTemplateByDept(params) {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/getTemplateByDept', url: '/cs-harmonic-boot/customReport/getTemplateByDept',
method: 'get', method: 'get',
params params
}) })
} }
// 获取模版
export function querySysExcel(params) {
return createAxios({
url: '/cs-harmonic-boot/sysExcel/querySysExcel',
method: 'post',
params
})
}
//资源管理 查询数据 //资源管理 查询数据
export function queryData(data) { export function queryData(data) {
return createAxios({ return createAxios({
@@ -144,7 +155,7 @@ export function updateFile(data) {
//合格率报告 //合格率报告
export function pageTable(data) { export function pageTable(data) {
return createAxios({ return createAxios({
url: '/harmonic-boot/qualifiedReport/pageTable', url: '/cs-harmonic-boot/qualifiedReport/pageTable',
method: 'post', method: 'post',
data data
}) })
@@ -152,14 +163,56 @@ export function pageTable(data) {
//合格率报告 //合格率报告
export function targetLimitChooseTree() { export function targetLimitChooseTree() {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/targetLimitChooseTree', // url: '/harmonic-boot/customReport/targetLimitChooseTree',
url: '/cs-harmonic-boot/customReport/targetLimitChooseTree',
method: 'get' method: 'get'
}) })
} }
//监测点指标 //监测点指标
export function terminalChooseTree() { export function terminalChooseTree() {
return createAxios({ return createAxios({
url: '/harmonic-boot/customReport/terminalChooseTree', // url: '/harmonic-boot/customReport/terminalChooseTree',
url: '/cs-harmonic-boot/customReport/terminalChooseTree',
method: 'get' method: 'get'
}) })
} }
//新增模版
export function addSysExcel(data:any) {
return createAxios({
url: '/cs-harmonic-boot/sysExcel/addSysExcel',
method: 'post',
data
})
}
//修改模版
export function updateSysExcel(data:any) {
return createAxios({
url: '/cs-harmonic-boot/sysExcel/updateSysExcel',
method: 'post',
data
})
}
//删除模版
export function deleteSysExcel(params:any) {
return createAxios({
url: '/cs-harmonic-boot/sysExcel/deleteSysExcel',
method: 'post',
params
})
}
//查詢綁定
export function queryList(params:any) {
return createAxios({
url: '/cs-harmonic-boot/sysExcelRelation/queryList',
method: 'post',
params
})
}
//綁定
export function bandRelation(data:any) {
return createAxios({
url: '/cs-harmonic-boot/sysExcelRelation/bandRelation',
method: 'post',
data
})
}

View File

@@ -1,118 +1,126 @@
import request from '@/utils/request' import request from '@/utils/request'
// 新增字典数据 // 新增字典数据
export const addCsDictData = (data: any) => { export const addCsDictData = (data: any) => {
return request({ return request({
url: '/system-boot/csDictData/add', url: '/system-boot/csDictData/add',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 查询字典数据 // 查询字典数据
export const queryCsDictDataPage = (data: any) => { export const queryCsDictDataPage = (data: any) => {
return request({ return request({
url: '/system-boot/csDictData/list', url: '/system-boot/csDictData/list',
method: 'post', method: 'post',
data: Object.assign( data: Object.assign(
{ {
orderBy: '', orderBy: '',
pageNum: 0, pageNum: 0,
pageSize: 0, pageSize: 0,
searchBeginTime: '', searchBeginTime: '',
searchEndTime: '', searchEndTime: '',
searchState: 0, searchState: 0,
searchValue: '', searchValue: '',
dataType: '', dataType: '',
sortBy: '' sortBy: ''
}, },
data data
) )
}) })
} }
//删除字典数据 //删除字典数据
export const delCsDictData = (id: string) => { export const delCsDictData = (id: string) => {
let form = new FormData() let form = new FormData()
form.append('id', id) form.append('id', id)
return request({ return request({
url: '/system-boot/csDictData/delete', url: '/system-boot/csDictData/delete',
method: 'post', method: 'post',
data: form, data: form,
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
} }
}) })
} }
// 修改字典数据 // 修改字典数据
export const updateCsDictData = (data: any) => { export const updateCsDictData = (data: any) => {
return request({ return request({
url: '/system-boot/csDictData/update', url: '/system-boot/csDictData/update',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 执行算法 // 执行算法
export const timerRun = (params: any) => { export const timerRun = (params: any) => {
return request({ return request({
url: '/system-boot/timer/run', url: '/system-boot/timer/run',
method: 'GET', method: 'GET',
params params
}) })
} }
// 任务表达式 // 任务表达式
export const getActionClasses = () => { export const getActionClasses = () => {
return request({ return request({
url: '/system-boot/timer/getActionClasses', url: '/system-boot/timer/getActionClasses',
method: 'GET' method: 'GET'
}) })
} }
// 新增任务 // 新增任务
export const addTimer = (data: any) => { export const addTimer = (data: any) => {
return request({ return request({
url: '/system-boot/timer/add', url: '/system-boot/timer/add',
method: 'POST', method: 'POST',
data data
}) })
} }
// 修改任务 // 修改任务
export const updateTimer = (data: any) => { export const updateTimer = (data: any) => {
return request({ return request({
url: '/system-boot/timer/update', url: '/system-boot/timer/update',
method: 'POST', method: 'POST',
data data
}) })
} }
// 补招配置 // 补招配置
export const runTimer = (data: any) => { export const runTimer = (data: any) => {
return request({ return request({
url: '/system-boot/timer/run', url: '/system-boot/timer/run',
method: 'GET', method: 'GET',
params: data params: data
}) })
} }
// 删除任务 // 删除任务
export const deleteTimer = (data: any) => { export const deleteTimer = (data: any) => {
return request({ return request({
url: '/system-boot/timer/delete', url: '/system-boot/timer/delete',
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
// 关闭任务 // 关闭任务
export const stop = (params: any) => { export const stop = (params: any) => {
return request({ return request({
url: '/system-boot/timer/stop', url: '/system-boot/timer/stop',
method: 'get', method: 'get',
params params
}) })
} }
// 启动任务 // 启动任务
export const start = (params: any) => { export const start = (params: any) => {
return request({ return request({
url: '/system-boot/timer/start', url: '/system-boot/timer/start',
method: 'get', method: 'get',
params params
}) })
}
// 查询监测对象类型
export const getDicDataByTypeCode = (params: any) => {
return request({
url: '/system-boot/dictData/getDicDataByTypeCode',
method: 'get',
params
})
} }

View File

@@ -54,6 +54,14 @@ export const activatePage = (params: any) => {
params params
}) })
} }
// 全局的驾驶舱页面
export const scopePage = (params: any) => {
return createAxios({
url: '/system-boot/dashboard/scopePage',
method: 'post',
params
})
}
// 查询激活的驾驶舱页面 // 查询激活的驾驶舱页面
export const queryActivatePage = () => { export const queryActivatePage = () => {
return createAxios({ return createAxios({

View File

@@ -88,8 +88,9 @@ export const updateStatistical = (data: any) => {
// 单位绑定 // 单位绑定
export function codeDicTree(data: any) { export function codeDicTree(data: any) {
return createAxios({ return createAxios({
url: '/system-boot/dictTree/codeDicTree', // url: '/system-boot/dictTree/codeDicTree',
method: 'get', url: '/system-boot/dictTree/queryByCodeList',
method: 'post',
params: data params: data
}) })
} }

View File

@@ -0,0 +1,11 @@
import request from '@/utils/request'
/**
* 获取移动端、便携式正式用户列表
* @returns {AxiosPromise}
*/
export const getFormalUserList = () => {
return request({
url: '/user-boot/user/getFormalUserList',
method: 'post'
})
}

View File

@@ -1,21 +1,33 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
export function getFunctionsByRoleIndex(data) { export function getFunctionsByRoleIndex(data) {
return createAxios({ return createAxios({
url: '/user-boot/roleFunction/getFunctionsByRoleIndex', url: '/user-boot/roleFunction/getFunctionsByRoleIndex',
method: 'post', method: 'post',
params: data params: data
}) })
} }
export function updateRoleMenu(data:any) { export function updateRoleMenu(data: any) {
return createAxios({ return createAxios({
url: '/user-boot/function/assignFunctionByRoleIndexes', url: '/user-boot/function/assignFunctionByRoleIndexes',
method: 'post', method: 'post',
data: data data: data
// params: roleIndex,functionIndexList })
// data:{ }
// roleIndex,functionIndexList // 新增角色与系统关系
// } export function systemAdd(data: any) {
}) return createAxios({
} url: '/user-boot/sysRoleSystem/add',
method: 'post',
data: data
})
}
// 根据角色id获取系统信息
export function getSystemByRoleId(params: any) {
return createAxios({
url: '/user-boot/sysRoleSystem/getSystemByRoleId',
method: 'get',
params
})
}

BIN
src/assets/img/jss.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

View File

@@ -1,7 +1,14 @@
<template> <template>
<div> <div>
<!--F47曲线 --> <!--F47曲线 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
ref="TableHeaderRef"
:showReset="false"
:timeKeyList="prop.timeKey"
@selectChange="selectChange"
datePicker
v-if="fullscreen"
></TableHeader>
<el-descriptions class="mt2" direction="vertical" :column="4" border> <el-descriptions class="mt2" direction="vertical" :column="4" border>
<el-descriptions-item align="center" label="名称">{{ data.name }}</el-descriptions-item> <el-descriptions-item align="center" label="名称">{{ data.name }}</el-descriptions-item>
<el-descriptions-item align="center" label="事件总数">{{ data.gs }}</el-descriptions-item> <el-descriptions-item align="center" label="事件总数">{{ data.gs }}</el-descriptions-item>
@@ -9,15 +16,23 @@
<el-descriptions-item align="center" label="不可容忍">{{ data.bkrr }}</el-descriptions-item> <el-descriptions-item align="center" label="不可容忍">{{ data.bkrr }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<my-echart <my-echart
v-loading="tableStore.table.loading"
ref="chartRef" ref="chartRef"
class="tall" class="tall"
:options="echartList" :options="echartList"
:style="{ width: prop.width, height: `calc(${prop.height} - 80px - ${headerHeight}px + ${fullscreen ? 0 : 56}px)` }" :style="{
width: prop.width,
height: `calc(${prop.height} - 80px - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}"
@chart-click="handleChartClick" @chart-click="handleChartClick"
/> />
<el-dialog v-model="isWaveCharts" draggable title="瞬时/RMS波形" append-to-body width="70%"> <el-dialog v-model="isWaveCharts" v-if="isWaveCharts" draggable :title="dialogTitle" append-to-body width="70%">
<waveFormAnalysis v-loading="loading" v-if="isWaveCharts" ref="waveFormAnalysisRef" <waveFormAnalysis
@handleHideCharts="isWaveCharts = false" :wp="wp" /> v-loading="loading"
ref="waveFormAnalysisRef"
@handleHideCharts="isWaveCharts = false"
:wp="wp"
/>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@@ -25,35 +40,34 @@
import { ref, onMounted, provide, reactive, watch, h } from 'vue' import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { ElMessage, ElMessageBox } from 'element-plus' import waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue';
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router' import { analyseWave } from '@/api/common'
import { useTimeCacheStore } from '@/stores/timeCache' import { ElMessage } from 'element-plus'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const TableHeaderRef = ref()
const headerHeight = ref(57) const headerHeight = ref(57)
const route = useRoute() const dialogTitle = ref('波形分析')
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
@@ -69,7 +83,7 @@ const fullscreen = computed(() => {
} }
}) })
const echartList = ref({}) const echartList = ref()
const chartRef = ref() const chartRef = ref()
// 波形 // 波形
@@ -79,7 +93,10 @@ const loading = ref(false)
const wp = ref({}) const wp = ref({})
const OverLimitDetailsRef = ref() const boxoList: any = ref({})
const waveFormAnalysisRef: any = ref(null)
const data = reactive({ const data = reactive({
name: '事件个数', name: '事件个数',
gs: 0, gs: 0,
@@ -87,46 +104,25 @@ const data = reactive({
bkrr: 0 bkrr: 0
}) })
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree', url: '/cs-harmonic-boot/csevent/f47Curve',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
}, },
loadCallback: () => { loadCallback: () => {
let res = { const gongData = gongfunction(tableStore.table.data)
data: { totalNumberOfEvents: 0, voltageToleranceCurveDataList: [] } data.gs = tableStore.table.data.length
} data.krr = gongData.pointF.length
data.bkrr = gongData.pointFun.length
const gongData = gongfunction(res.data.voltageToleranceCurveDataList)
data.gs = res.data.voltageToleranceCurveDataList.length
data.krr = gongData.pointI.length
data.bkrr = gongData.pointIun.length
console.log(gongData)
echartList.value = { echartList.value = {
// backgroundColor: "#f9f9f9", //地图背景色深蓝
title: { title: {
text: `F47曲线` text: `F47曲线`
}, },
legend: { legend: {
// data: ['上限', '下限', '可容忍事件', '不可容忍事件'],
data: ['可容忍事件', '不可容忍事件'], data: ['可容忍事件', '不可容忍事件'],
itemWidth: 10, itemWidth: 10,
itemHeight: 10, itemHeight: 10,
@@ -151,8 +147,9 @@ const tableStore: any = new TableStore({
backgroundColor: 'rgba(0,0,0,0.55)', backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0, borderWidth: 0,
formatter: function (a: any) { formatter: function (a: any) {
var relVal = '' var relVal = `<strong>${a.seriesName}</strong><br/>`
relVal = "<font style='color:" + "'>发生时间:" + a.value[2] + '</font><br/>'
relVal += "<font style='color:" + "'>发生时间:" + a.value[2] + '</font><br/>'
relVal += "<font style='color:" + "'>持续时间:" + a.value[0] + 's</font><br/>' relVal += "<font style='color:" + "'>持续时间:" + a.value[0] + 's</font><br/>'
relVal += "<font style='color:" + "'>特征幅值:" + a.value[1].toFixed(2) + '%</font>' relVal += "<font style='color:" + "'>特征幅值:" + a.value[1].toFixed(2) + '%</font>'
return relVal return relVal
@@ -173,11 +170,16 @@ const tableStore: any = new TableStore({
yAxis: [ yAxis: [
{ {
type: 'value', type: 'value',
max: function (value: any) { // max: function (value: any) {
return value.max + 20 // return value.max + 20
// },
max: function (value) {
// 先取原始最大值+20再向上取整到最近的10的倍数确保刻度够用且规整
return Math.ceil((value.max + 20) / 10) * 10
}, },
splitNumber: 10, // splitNumber: 10,
minInterval: 0.1, // interval: 10,
// minInterval: 10,
name: '%' name: '%'
} }
], ],
@@ -208,36 +210,28 @@ const tableStore: any = new TableStore({
type: 'scatter', type: 'scatter',
symbol: 'circle', symbol: 'circle',
symbolSize: 8, symbolSize: 8,
// data: gongData.pointF, data: gongData.pointF,
data: [ // data: [
[0.2, 10, '2023-01-01 10:00:00'], // [0.2, 10, '2023-01-01 10:00:00'],
[0.4, 50, '2023-01-01 11:00:00'] // [0.4, 50, '2023-01-01 11:00:00']
], // ],
legendSymbol: 'circle', legendSymbol: 'circle'
emphasis: {
focus: 'series', // tooltip: {
itemStyle: { // show: true,
borderColor: '#fff', // trigger: 'item',
borderWidth: 2, // formatter: function (params: any) {
shadowBlur: 10, // return `<strong>可容忍事件</strong><br/>
shadowColor: 'rgba(0, 0, 0, 0.5)' // 持续时间: ${params.value[0]}s<br/>
} // 特征幅值: ${params.value[1].toFixed(2)}%<br/>
}, // 发生时间: ${params.value[2] || 'N/A'}`
tooltip: { // }
show: true, // }
trigger: 'item',
formatter: function (params: any) {
return `<strong>可容忍事件</strong><br/>
持续时间: ${params.value[0]}s<br/>
特征幅值: ${params.value[1].toFixed(2)}%<br/>
发生时间: ${params.value[2] || 'N/A'}`
}
}
}, },
{ {
name: '不可容忍事件', name: '不可容忍事件',
type: 'scatter', type: 'scatter',
symbol: 'rect', symbol: 'circle',
symbolSize: 8, symbolSize: 8,
data: gongData.pointFun, data: gongData.pointFun,
legendSymbol: 'rect' legendSymbol: 'rect'
@@ -252,6 +246,25 @@ const tableRef = ref()
provide('tableRef', tableRef) provide('tableRef', tableRef)
provide('tableStore', tableStore) provide('tableStore', tableStore)
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
function gongfunction(arr: any) { function gongfunction(arr: any) {
let standI = 0 let standI = 0
let unstandI = 0 let unstandI = 0
@@ -271,8 +284,9 @@ function gongfunction(arr: any) {
let yy = arr[i].eventValue let yy = arr[i].eventValue
let time = arr[i].time let time = arr[i].time
let eventId = arr[i].eventId let eventId = arr[i].eventId
let lineName = arr[i].lineName
// let index =arr[i].eventDetailIndex; // let index =arr[i].eventDetailIndex;
point = [xx, yy, time, eventId] point = [xx, yy, time, eventId, lineName]
if (xx <= 0.003) { if (xx <= 0.003) {
let line = 0 let line = 0
@@ -419,22 +433,55 @@ onMounted(() => {
// 点击事件处理函数 // 点击事件处理函数
const handleChartClick = (params: any) => { const handleChartClick = (params: any) => {
if (params.seriesName === '可容忍事件') { if (params.seriesName === '可容忍事件') {
// 处理可容忍事件点击 // 处理可容忍事件点击
ElMessage.info(`点击了可容忍事件: 持续时间${params.value[0]}s, 幅值${params.value[1].toFixed(2)}%`) dialogTitle.value = '可容忍事件波形分析'
handleTolerableEventClick(params) handleTolerableEventClick(params)
} else if (params.seriesName === '不可容忍事件') { } else if (params.seriesName === '不可容忍事件') {
dialogTitle.value = '不可容忍事件波形分析'
// 处理不可容忍事件点击 // 处理不可容忍事件点击
ElMessage.info(`点击了不可容忍事件: 持续时间${params.value[0]}s, 幅值${params.value[1].toFixed(2)}%`) // ElMessage.info(`点击了不可容忍事件: 持续时间${params.value[0]}s, 幅值${params.value[1].toFixed(2)}%`)
handleIntolerableEventClick(params) handleTolerableEventClick(params)
} }
} }
// 可容忍事件点击处理函数 // 可容忍事件点击处理函数
const handleTolerableEventClick = (params: any) => { const handleTolerableEventClick = async (row: any) => {
console.log('可容忍事件详情:', params) loading.value = true
isWaveCharts.value = true nextTick(() => {
if (waveFormAnalysisRef.value) {
//waveFormAnalysisRef.value.setHeight(false, 360)
// waveFormAnalysisRef.value.setHeight(999, 130, 1.6666666)
}
})
const messageInstance = ElMessage.info(`正在加载,请稍等...`)
await analyseWave(row.value[3]) //eventId
.then(res => {
row.loading1 = false
if (res != undefined) {
boxoList.value = {
persistTime: row.value[0], //持续时间
featureAmplitude: (row.value[1] / 100).toFixed(2), //残余电压
startTime: row.value[2], //时间
lineName: row.value[4] //监测点名称
}
boxoList.value.systemType = 'YPT'
wp.value = res.data
}
isWaveCharts.value = true
loading.value = false
})
.catch(() => {
messageInstance.close()
row.loading1 = false
loading.value = false
})
nextTick(() => {
waveFormAnalysisRef.value && waveFormAnalysisRef.value.setHeight(999, 130, 1.6666666)
waveFormAnalysisRef.value && waveFormAnalysisRef.value.getWpData(wp.value, boxoList.value, true)
})
} }
// 不可容忍事件点击处理函数 // 不可容忍事件点击处理函数
@@ -449,12 +496,17 @@ watch(
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() // 当外部时间值变化时,更新表格的时间参数
if (newVal && (!oldVal || newVal[0] !== oldVal[0] || newVal[1] !== oldVal[1])) {
tableStore.table.params.searchBeginTime = newVal[0]
tableStore.table.params.searchEndTime = newVal[1]
tableStore.index()
}
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )

View File

@@ -0,0 +1,308 @@
<template>
<div>
<!--暂态越限时间分布 -->
<TableHeader
ref="TableHeaderRef"
:timeKeyList="prop.timeKey"
:showReset="false"
@selectChange="selectChange"
datePicker
v-if="fullscreen"
></TableHeader>
<my-echart
class="tall"
:options="echartList1"
:style="{
width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}"
/>
<!-- <my-echart
class="mt10"
:options="echartList1"
:style="{
width: prop.width,
height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`
}"
/> -->
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch } from 'vue'
import TableStore from '@/utils/tableStore'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useConfig } from '@/stores/config'
import TableHeader from '@/components/table/header/index.vue'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({
w: { type: [String, Number] },
h: { type: [String, Number] },
width: { type: [String, Number] },
height: { type: [String, Number] },
timeKey: { type: Array as () => string[] },
timeValue: { type: Object },
interval: { type: Number }
})
const TableHeaderRef = ref()
const headerHeight = ref(57)
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
if (datePickerValue && datePickerValue.timeValue) {
// 更新时间参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const config = useConfig()
const echartList = ref({})
const echartList1 = ref({})
const processDataForChart = (rawData: any[]) => {
// 将后端返回的扁平数据转换为 ECharts 需要的三维坐标格式 [x, y, z]
const chartData = rawData.map(item => [item.x, item.y, item.z])
return chartData
}
const tableStore: any = new TableStore({
url: '/cs-harmonic-boot/csevent/getEventCoords',
method: 'POST',
showPage: false,
column: [],
beforeSearchFun: () => {
setTime()
},
loadCallback: () => {
const processedData = processDataForChart(tableStore.table.data.innerList || [])
const trendList = tableStore.table.data.trendList || []
const xlist = tableStore.table.data.xlist || []
// 处理趋势图数据
const seriesData = trendList.map((item: any) => {
// 根据接口返回的name字段确定系列名称和颜色
let name = ''
let color = ''
switch (item.name) {
case 'Evt_Sys_DipStr':
name = '电压暂降'
color = '#FFBF00'
break
case 'Evt_Sys_IntrStr':
name = '电压中断'
color = '#FF9100'
break
case 'Evt_Sys_SwlStr':
name = '电压暂升'
color = config.layout.elementUiPrimary[0]
break
default:
name = item.name
color = '#000000'
}
return {
name: name,
type: 'line',
showSymbol: false,
color: color,
data: item.trendList?.map((value: number, index: number) => [xlist[index], value]) || []
}
})
// 获取x轴和y轴的标签值
const xLabels = [
'0-10%',
'10%-20%',
'20%-30%',
'30%-40%',
'40%-50%',
'50%-60%',
'60%-70%',
'70%-80%',
'80%-90%',
'90%-100%'
]
const yLabels = ['0-0.01s', '0.01s-0.1s', '0.1s-1s', '1s-10s', '10s']
echartList.value = {
options: {
xAxis: null,
yAxis: null,
dataZoom: null,
backgroundColor: '#fff',
tooltip: {
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter: function (params: any) {
var tips = ''
tips += '持续时间: ' + yLabels[params.value[1]] + '</br>'
tips += '特征幅值: ' + xLabels[params.value[0]] + '</br>'
tips += '事件次数: ' + params.value[2] + '</br>'
return tips
}
},
title: {
text: '暂态事件概率分布',
x: 'center'
},
visualMap: {
max: 500,
show: false,
inRange: {
color: ['#313695', '#00BB00', '#ff8000', '#a50026']
}
},
xAxis3D: {
type: 'category',
name: '特征幅值',
data: xLabels,
nameGap: 40
},
yAxis3D: {
type: 'category',
name: '持续时间',
data: yLabels,
nameGap: 40,
splitLine: {
lineStyle: {
type: 'dashed',
opacity: 0.5
}
}
},
zAxis3D: {
type: 'value',
minInterval: 10,
name: '暂态事件次数',
nameGap: 30
},
grid3D: {
viewControl: {
projection: 'perspective',
distance: 260
},
boxWidth: 200,
boxDepth: 80,
light: {
main: {
intensity: 1.2
},
ambient: {
intensity: 0.3
}
}
},
series: [
{
type: 'bar3D',
data: processedData,
shading: 'realistic',
label: {
show: false,
textStyle: {
fontSize: 16,
borderWidth: 1
}
}
}
]
}
}
echartList1.value = {
title: {
text: '暂态越限时间概率分布'
},
xAxis: {
type: 'category',
data: xlist,
axisLabel: {
formatter: '{value}'
}
},
yAxis: {
name: '次'
},
grid: {
left: '10px',
right: '20px'
},
series: seriesData
}
}
})
const tableRef = ref()
provide('tableRef', tableRef)
provide('tableStore', tableStore)
onMounted(() => {
tableStore.index()
})
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue,
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true
}
)
</script>
<style lang="scss" scoped></style>

View File

@@ -1,44 +1,50 @@
<template> <template>
<div> <div>
<!--暂降方向统计 --> <!--暂降方向统计 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
<my-echart class="tall" :options="echartList" :style="{ width: prop.width, height: `calc(${prop.height} )` }" /> ref="TableHeaderRef"
:showReset="false"
@selectChange="selectChange"
datePicker
:timeKeyList="prop.timeKey"
v-if="fullscreen"
></TableHeader>
<my-echart
v-loading="tableStore.table.loading"
class="tall"
:options="echartList"
:style="{ width: prop.width, height: `calc(${prop.height} )` }"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue' import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { useDictData } from '@/stores/dictData'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router' import { getTime } from '@/utils/formatTime'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const route = useRoute() const TableHeaderRef = ref()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
@@ -54,98 +60,99 @@ const fullscreen = computed(() => {
} }
}) })
const data = [ const echartList = ref({})
{
name: '来自电网',
value: 4
},
{
name: '来自负荷',
value: 41
}
]
const echartList = ref({
title: {},
tooltip: { // const data = [
trigger: 'item' // {
}, // name: '来自电网',
legend: { // value: 4
orient: 'vertical', // },
top: 'center', // {
right: '5%', // name: '来自负荷',
formatter: function (e: any) { // value: 41
return e + ' ' + data.filter(item => item.name == e)[0].value + '次' // }
} // ]
},
xAxis: {
show: false
},
yAxis: {
show: false
},
grid: {
left: '10px',
right: '20px'
},
options: {
dataZoom: null,
title: [
{
text: '暂降方向统计',
left: 'center'
},
{
text: data[0].value + data[1].value + '次',
left: 'center',
top: 'center'
}
],
series: [
{
type: 'pie',
center: 'center',
radius: ['55%', '75%'],
label: {
show: false,
position: 'outside',
textStyle: {
//数值样式
}
},
name: '事件统计',
data: data
}
]
}
})
const OverLimitDetailsRef = ref() const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree', url: '/cs-harmonic-boot/csevent/getEventDirectionData',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime },
loadCallback: () => {
if (!tableStore.table.data || !Array.isArray(tableStore.table.data)) {
return []
}
const chartData = ref(
tableStore.table.data.map((item: any) => ({
name: item.source === 'load' ? '来自负荷' : '来自电网',
value: item.times
}))
)
if (fullscreen.value) { const total = chartData.value.reduce((sum: any, item: any) => sum + item.value, 0)
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) { echartList.value = {
beginTime = cached.timeValue[0] title: {},
endTime = cached.timeValue[1]
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
top: '50',
right: '10',
formatter: function (name: string) {
const item = chartData.value.find((i: any) => i.name === name)
return item ? `${name} ${item.value}` : name
}
},
xAxis: {
show: false
},
yAxis: {
show: false
},
grid: {
left: '10px',
right: '20px'
},
options: {
dataZoom: null,
title: [
{
text: '暂降方向统计',
left: 'center'
},
{
text: total + '次',
left: 'center',
top: 'center'
}
],
series: [
{
type: 'pie',
center: 'center',
radius: ['55%', '75%'],
label: {
show: false,
position: 'outside',
textStyle: {
//数值样式
}
},
name: '事件统计',
data: chartData.value
}
]
} }
} }
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {}
}) })
const tableRef = ref() const tableRef = ref()
@@ -153,10 +160,28 @@ provide('tableRef', tableRef)
provide('tableStore', tableStore) provide('tableStore', tableStore)
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') { if (column.field != 'name') {
console.log(row)
OverLimitDetailsRef.value.open(row) OverLimitDetailsRef.value.open(row)
} }
} }
@@ -171,12 +196,12 @@ watch(
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )

View File

@@ -1,11 +1,9 @@
<template> <template>
<!-- 指标日趋势图 --> <!-- 指标日趋势图 -->
<el-dialog draggable title="指标日趋势图" v-model="dialogVisible" append-to-body width="70%"> <el-dialog draggable :title="dialogTitle" v-model="dialogVisible" append-to-body width="70%">
<my-echart <div :style="pageHeight">
class="tall" <my-echart class="tall" :options="echartList" style="width: 100%" />
:options="echartList" </div>
style="width: 98%; height: 320px"
/>
</el-dialog> </el-dialog>
</template> </template>
@@ -13,77 +11,138 @@
import { ref, onMounted, provide, reactive, watch, h } from 'vue' import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { mainHeight } from '@/utils/layout'
import { limitExtentDayData } from '@/api/harmonic-boot/cockpit/cockpit'
import { yMethod } from '@/utils/echartMethod'
const prop = defineProps({ const prop = defineProps({
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object }
}) })
const pageHeight = mainHeight(0, 2)
const dialogVisible: any = ref(false) const dialogVisible: any = ref(false)
const dialogTitle = ref('')
const config = useConfig() const config = useConfig()
const echartList = ref({ const dialogText = ref('')
title: {
text: '指标日趋势图', const echartList = ref()
},
xAxis: { const init = () => {
type: 'category', echartList.value = {
data: [ title: {
'00:00', '01:00', '02:00', '03:00', '04:00', '05:00', text: dialogText.value
'06:00', '07:00', '08:00', '09:00', '10:00', '11:00', },
'12:00', '13:00', '14:00', '15:00', '16:00', '17:00', legend: {
'18:00', '19:00', '20:00', '21:00', '22:00', '23:00' itemWidth: 20,
], itemHeight: 20,
axisLabel: { itemStyle: { opacity: 0 }, //去圆点
interval: 1 right: 70
} },
}, tooltip: {
yAxis: { trigger: 'axis',
type: 'value', formatter: (params: any) => {
name: '数值' if (!params || params.length === 0) return ''
},
grid: { // 使用第一个项目的轴标签作为时间标题
left: '10px', let tooltipText = params[0].axisValueLabel + '<br/>'
right: '20px',
containLabel: true // 遍历所有项目并累加到tooltipText中
}, params.forEach((item: any) => {
options: { // 将数值格式化为保留两位小数
series: [ const formattedValue = Math.round(item.value[1] * 100) / 100
{ tooltipText += `${item.marker} ${item.seriesName}: ${formattedValue}<br/>`
name: '指标值', })
type: 'line',
showSymbol: true, return tooltipText
smooth: true, }
data: [ },
10, 12, 15, 18, 22, 28, xAxis: {
35, 45, 60, 75, 88, 95, type: 'time',
100, 98, 90, 80, 70, 60, axisLabel: {
50, 40, 30, 25, 20, 15 formatter: {
], day: '{MM}-{dd}',
itemStyle: { month: '{MM}',
normal: { year: '{yyyy}'
color: config.layout.elementUiPrimary[0]
}
},
areaStyle: {
opacity: 0.3
} }
} }
] },
yAxis: {},
options: {
series: []
}
} }
}) }
const initData = async (row: any) => {
limitExtentDayData({ code: row.code, lineId: row.lineId, time: row.time }).then(res => {
if (res.data && res.data.length > 0) {
// 重新初始化图表
init()
let [min, max] = yMethod(res.data.map((item: any) => item.value.split(',')).flat())
onMounted(() => { // 从第一条数据中提取时间作为x轴数据
})
// 定义相位颜色映射
const phaseColors: any = {
A: '#DAA520',
B: '#2E8B57',
C: '#A52a2a'
}
// 处理每条相位数据
const seriesData = res.data
.filter(item => item.valueType == 'max')
.sort((a, b) => {
return a.phasic.localeCompare(b.phasic)
})
.map((item: any) => {
const xAxisData = item.time.split(',')
const values = xAxisData.map((time: string, index: number) => {
// 将传入的日期与时间拼接成完整的时间字符串
const fullTime = `${row.time} ${time}`
const value = parseFloat(item.value.split(',')[index]) || 0
return [fullTime, value]
})
return {
name: `${item.phasic}`,
type: 'line',
showSymbol: false,
smooth: true,
data: values,
itemStyle: {
normal: {
// 根据相位设置对应颜色
color: phaseColors[item.phasic] || config.layout.elementUiPrimary[0]
}
}
}
})
echartList.value.yAxis.max = max
echartList.value.yAxis.min = min
// 更新图表配置
echartList.value.options.series = seriesData
// 注意:使用时间轴时不需要设置 xAxis.data
}
})
}
onMounted(() => {})
const open = async (row: any) => { const open = async (row: any) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = row.name + '日趋势图'
dialogText.value = `监测点名称:${row.lineName} 越限时间:${row.time} 指标名称:${row.name}`
nextTick(() => {
initData(row)
})
} }
defineExpose({ open }) defineExpose({ open })

View File

@@ -1,15 +1,28 @@
<template> <template>
<div> <div>
<!--指标越限程度 --> <!--指标越限程度 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
ref="TableHeaderRef"
:showReset="false"
@selectChange="selectChange"
datePicker
:timeKeyList="prop.timeKey"
v-if="fullscreen"
></TableHeader>
<my-echart <my-echart
class="tall" class="tall"
:options="echartList" :options="echartList"
:style="{ width: prop.width, height: `calc(${prop.height} / 2 )` }" :style="{ width: prop.width, height: `calc(${prop.height} / 2 )` }"
/> />
<Table ref="tableRef" @cell-click="cellClickEvent" :height="`calc(${prop.height} / 2 - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`" isGroup></Table> <Table
ref="tableRef"
@cell-click="cellClickEvent"
:height="`calc(${prop.height} / 2 - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`"
isGroup
></Table>
<!-- 指标日趋势图 --> <!-- 指标日趋势图 -->
<DailyTrendChart ref="dailyTrendChartRef" /> <HarmonicRatio ref="harmonicRatioRef" v-if="dialogFlag" @close="onHarmonicRatioClose" :showIndex="false" />
<!-- <DailyTrendChart v-if="dialogTrendChart" ref="dailyTrendChartRef" @close="dialogTrendChart = false" /> -->
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -19,32 +32,33 @@ import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime' import { getTimeOfTheMonth } from '@/utils/formatTime'
import { ElMessage, ElMessageBox } from 'element-plus'
import DailyTrendChart from '@/components/cockpit/exceedanceLevel/components/dailyTrendChart.vue' import DailyTrendChart from '@/components/cockpit/exceedanceLevel/components/dailyTrendChart.vue'
import { useRoute } from 'vue-router' import { getTime } from '@/utils/formatTime'
import { useTimeCacheStore } from '@/stores/timeCache' import HarmonicRatio from '@/components/cockpit/overLimitStatistics/components/harmonicRatio.vue'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const TableHeaderRef = ref()
const route = useRoute() const headerHeight = ref(57)
const timeCacheStore = useTimeCacheStore() const harmonicRatioRef: any = ref(null)
const dialogTrendChart = ref(false)
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
// 计算是否全屏展示 // 计算是否全屏展示
@@ -59,49 +73,11 @@ const fullscreen = computed(() => {
} }
}) })
const echartList = ref({ const echartList = ref()
title: {
text: '指标越限严重度'
},
xAxis: {
// name: '(区域)',
data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
},
yAxis: {
name: '%', // 给X轴加单位
interval: 20
},
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '越限占比',
data: [0, 7.5, 36, 0, 80],
barMaxWidth: 30
// label: {
// show: true,
// position: 'top',
// textStyle: {
// //数值样式
// color: '#000'
// },
// fontSize: 12
// }
}
]
}
})
const dailyTrendChartRef = ref() const dailyTrendChartRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree', url: '/cs-harmonic-boot/limitRateDetailD/limitExtentData',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
@@ -123,95 +99,81 @@ const tableStore: any = new TableStore({
{ {
title: '越限最大值', title: '越限最大值',
field: 'type', field: 'maxValue',
minWidth: '60', minWidth: '70',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type}</span>` const extentValue =
row.maxValue !== null && row.maxValue !== undefined && row.maxValue !== ''
? Math.floor(row.maxValue * 100) / 100
: '/'
return `<span style='cursor: pointer;text-decoration: underline;'>${extentValue}</span>`
} }
}, },
{ {
title: '国标限值', title: '国标限值',
field: 'type1', field: 'internationalValue',
minWidth: '60' minWidth: '60'
}, },
{ {
title: '越限程度(%)', title: '越限程度(%)',
field: 'type2', field: 'extent',
minWidth: '60' minWidth: '70',
formatter: (row: any) => {
return Math.floor(row.cellValue * 100) / 100
}
}, },
{ {
title: '发生日期', title: '越限时间',
field: 'type3', field: 'time',
minWidth: '100' minWidth: '60',
formatter: (row: any) => {
return row.cellValue || '/'
}
}, },
{ {
title: '越限最高监测点', title: '越限最高监测点',
field: 'type4', field: 'lineName',
minWidth: '90' minWidth: '90',
formatter: (row: any) => {
return row.cellValue || '/'
}
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.data = [ // 定义 x 轴标签顺序
{
name: '闪变',
type: '0.0',
type1: '2.0',
type2: '0.0',
type3: '/',
type4: '/'
},
{
name: '谐波电压',
type: '1.72',
type1: '1.6',
type2: '7.5',
type3: '2025-03-09',
type4: '10kV1#电动机'
},
{
name: '谐波电流',
type: '27.2',
type1: '20.0',
type2: '36.0',
type3: '2025-03-16',
type4: '380V电焊机(治理前)'
},
{
name: '电压偏差',
type: '0.0',
type1: '2.0',
type2: '0.0',
type3: '/',
type4: '/'
},
{
name: '三相不平衡',
type: '3.6',
type1: '2.0',
type2: '80.0',
type3: '2025-03-01',
type4: '380V电焊机(治理前)'
}
]
echartList.value = {
title: {
text: '指标越限严重度'
},
xAxis: {
data: tableStore.table.data.map((item: any) => item.name)
},
yAxis: {
name: '%' // 给X轴加单位
// interval: 20
},
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
type: 'bar',
name: '越限占比',
data: tableStore.table.data.map((item: any) => Math.floor(item.extent * 100) / 100),
barMaxWidth: 30
}
]
}
}
} }
}) })
@@ -219,15 +181,62 @@ const tableRef = ref()
provide('tableRef', tableRef) provide('tableRef', tableRef)
provide('tableStore', tableStore) provide('tableStore', tableStore)
const codeMap = [
{ key: '闪变', code: 'flickerOvertime' },
{ key: '电压偏差', code: 'voltageDevOvertime' },
{ key: '三相', code: 'ubalanceOvertime' },
{ key: '谐波电压', code: 'uharm' },
{ key: '谐波电流', code: 'iharm' },
];
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') { dialogTrendChart.value = true
console.log(row)
dailyTrendChartRef.value.open(row) if (column.field == 'maxValue' ) {
if(row.lineId==null){
ElMessage.info('暂无越限监测点!')
}else{
nextTick(() => {
// dailyTrendChartRef.value.open(row)
dialogFlag.value = true
nextTick(() => {
const code = codeMap.find(item => row.name.includes(item.key))?.code || '';
harmonicRatioRef.value.openDialog(row,code,column.title.replace(/次/g, ""))
})
})
}
} }
} }
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
const dialogFlag=ref(false)
// 谐波弹窗关闭时的回调
const onHarmonicRatioClose = () => {
dialogFlag.value = false
// 重新打开指标越限详情弹窗
}
onMounted(() => { onMounted(() => {
tableStore.index() tableStore.index()
}) })
@@ -238,12 +247,12 @@ watch(
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +1,21 @@
<template> <template>
<div> <div>
<!--治理效果报表 --> <!--治理效果报表 -->
<TableHeader :showReset="false" datePicker @selectChange="selectChange" v-if="fullscreen"> <TableHeader :showReset="false" :timeKeyList="prop.timeKey" ref="TableHeaderRef" datePicker @selectChange="selectChange" v-if="fullscreen">
<template v-slot:select> <template v-slot:select>
<el-form-item label="治理对象"> <el-form-item label="模板策略">
<el-select <el-select filterable v-model="tableStore.table.params.tempId" placeholder="请选择模板策略" clearable>
v-model="tableStore.table.params.power" <el-option v-for="item in templateList" :key="item.id" :label="item.excelName" :value="item.id" />
placeholder="请选择治理对象" </el-select>
clearable </el-form-item>
style="width: 130px" <el-form-item label="监测对象">
> <el-select filterable v-model="tableStore.table.params.sensitiveUserId" placeholder="请选择监测对象" clearable>
<el-option <el-option v-for="item in idList" :key="item.id" :label="item.name" :value="item.id" />
v-for="item in powerList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</template> </template>
<template v-slot:operation> <template v-slot:operation>
<el-button @click="downloadExcel" class="" type="primary" icon="el-icon-Download">导出excel</el-button> <el-button @click="downloadExcel" class="" type="primary" icon="el-icon-Download">导出</el-button>
</template> </template>
</TableHeader> </TableHeader>
<div style="display: flex"> <div style="display: flex">
@@ -35,95 +30,71 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h, computed } from 'vue' import { ref, onMounted, provide, reactive, watch, h, computed, nextTick } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import { exportExcel } from '@/views/govern/reportForms/export.js' import { exportExcel } from '@/views/govern/reportForms/export.js'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime' import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useConfig } from '@/stores/config' import { getListByIds } from '@/api/harmonic-boot/cockpit/cockpit'
import Json from './index.json' import { getTime } from '@/utils/formatTime'
import { useRoute } from 'vue-router' import { ElMessage } from 'element-plus'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const config = useConfig()
const powerList: any = ref([
{
label: '1#变压器',
value: '1'
},
{
label: '2#变压器',
value: '2'
}
])
const route = useRoute() const TableHeaderRef = ref()
const timeCacheStore = useTimeCacheStore()
const tableStore: any = new TableStore({ // 报表模板列表
url: '/user-boot/role/selectRoleDetail?id=0', const templateList = ref()
method: 'POST',
showPage: false, // 监测对象
exportName: '主要监测点列表', const idList = ref()
column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) { // 监测对象
const cached = timeCacheStore.getCache(route.path) const initListByIds = () => {
if (cached && cached.timeValue) { getListByIds({}).then((res: any) => {
beginTime = cached.timeValue[0] if (res.data.length > 0) {
endTime = cached.timeValue[1] idList.value = res.data
if (!tableStore.table.params.sensitiveUserId && idList.value?.length > 0) {
tableStore.table.params.sensitiveUserId = idList.value[0].id
} }
templateListData()
} }
})
}
// 如果缓存中没有则使用默认值 const templateListData = () => {
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0] querySysExcel({}).then(res => {
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1] templateList.value = res.data.filter(item => item.excelType == 4)
}, if (!tableStore.table.params.tempId && templateList.value?.length > 0) {
loadCallback: () => {} tableStore.table.params.tempId = templateList.value[0].id
}) }
nextTick(() => {
const tableRef = ref() tableStore.index()
provide('tableRef', tableRef) })
tableStore.table.params.power = '1' })
}
provide('tableStore', tableStore)
// 下载表格 // 下载表格
const downloadExcel = () => { const downloadExcel = () => {
exportExcel(luckysheet.getAllSheets(), '治理效果报表') exportExcel(luckysheet.getAllSheets(), '治理效果报表')
} }
onMounted(() => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: Json
})
tableStore.index() onMounted(() => {
initListByIds()
}) })
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
@@ -138,6 +109,65 @@ const fullscreen = computed(() => {
return false return false
} }
}) })
const tableStore: any = new TableStore({
url: '/cs-harmonic-boot/customReport/getSensitiveUserReport',
method: 'POST',
showPage: false,
exportName: '治理效果报表',
column: [],
beforeSearchFun: () => {
setTime()
// if (!tableStore.table.params.sensitiveUserId && idList.value?.length > 0) {
// tableStore.table.params.sensitiveUserId = idList.value[0].id
// }
// if (!tableStore.table.params.tempId && templateList.value?.length > 0) {
// tableStore.table.params.tempId = templateList.value[0].id
// }
// if( !tableStore.table.params.tempId){
// return ElMessage.warning('请选择模板')
// }
},
loadCallback: () => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: tableStore.table.data
})
}
})
const tableRef = ref()
provide('tableRef', tableRef)
provide('tableStore', tableStore)
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
tableStore.table.params.startTime = time[0]
tableStore.table.params.endTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
watch( watch(
() => prop.timeKey, () => prop.timeKey,
val => { val => {
@@ -145,19 +175,17 @@ watch(
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )
const addMenu = () => {}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-select) { // :deep(.el-select) {
min-width: 80px; // min-width: 80px;
} // }
</style> </style>

View File

@@ -0,0 +1,717 @@
<template>
<el-dialog draggable title="趋势图" v-model="dialogVisible" append-to-body width="70%">
<!-- 总体指标占比详情谐波含有率 -->
<div>
<TableHeader ref="tableHeaderRef" :showSearch="false" @selectChange="selectChange">
<template v-slot:select>
<el-form-item>
<DatePicker ref="datePickerRef"></DatePicker>
</el-form-item>
<el-form-item label="统计指标" label-width="80px">
<el-select
multiple
:multiple-limit="2"
collapse-tags
collapse-tags-tooltip
v-model="searchForm.index"
placeholder="请选择统计指标"
@change="onIndexChange($event)"
filterable
>
<el-option
v-for="item in indexOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-radio-group v-model="searchForm.dataLevel" @change="init()">
<el-radio-button label="一次值" value="Primary" />
<el-radio-button label="二次值" value="Secondary" />
</el-radio-group>
</el-form-item>
<el-form-item label="统计类型">
<el-select
style="min-width: 90px !important"
placeholder="请选择"
v-model="searchForm.valueType"
filterable
>
<el-option value="max" label="最大值"></el-option>
<el-option value="min" label="最小值"></el-option>
<el-option value="avg" label="平均值"></el-option>
<el-option value="cp95" label="CP95"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<div
class="history_count"
v-for="(item, index) in countData"
:key="index"
v-show="item.countOptions.length != 0"
>
<span class="mr12">
{{ item.name.includes('次数') ? item.name : item.name + '谐波次数' }}
</span>
<el-select
v-model="item.count"
@change="onCountChange($event, index)"
placeholder="请选择谐波次数"
style="width: 100px"
class="mr20"
filterable
>
<el-option
v-for="vv in item.countOptions"
:key="vv"
:label="item.name.includes('间谐波') ? vv - 0.5 : vv"
:value="vv"
></el-option>
</el-select>
</div>
</el-form-item>
</template>
<template #operation>
<el-button type="primary" icon="el-icon-Search" @click="init()">查询</el-button>
<el-button :type="timeControl ? 'primary' : ''" icon="el-icon-Sort" @click="setTimeControl">
缺失数据
</el-button>
</template>
</TableHeader>
</div>
<div class="history_chart" :style="pageHeight" v-loading="loading">
<MyEchart ref="historyChart" :options="echartsData" v-if="showEchart" />
<el-empty :style="pageHeight" v-else description="暂无数据" />
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { mainHeight } from '@/utils/layout'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { ref, onMounted, watch } from 'vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useDictData } from '@/stores/dictData'
import { queryStatistical } from '@/api/system-boot/csstatisticalset'
import { yMethod, exportCSV, completeTimeSeries } from '@/utils/echartMethod'
import TableHeader from '@/components/table/header/index.vue'
import { trendData } from '@/api/harmonic-boot/cockpit/cockpit'
import DatePicker from '@/components/form/datePicker/index.vue'
import { color } from '@/components/echarts/color'
import { ElMessage } from 'element-plus'
const dictData = useDictData()
defineOptions({
// name: 'govern/device/control'
})
const props = defineProps({
TrendList: {
type: Array
}
})
const dialogVisible: any = ref(false)
// console.log("🚀 ~ props:", props.TrendList)
const showEchart = ref(true)
const num = ref(0)
const timeControl = ref(false)
//值类型
const pageHeight = ref(mainHeight(57 * 1.6, 1.6))
const loading = ref(true)
const searchForm: any = ref({})
const tableHeaderRef = ref()
const typeOptions = [
{
name: '平均值',
id: 'avg'
},
{
name: '最大值',
id: 'max'
},
{
name: '最小值',
id: 'min'
},
{
name: 'CP95值',
id: 'cp95'
}
]
searchForm.value = {
index: [],
type: typeOptions[0].id,
count: '',
searchBeginTime: '',
searchEndTime: '',
dataLevel: 'Primary',
valueType: 'avg'
}
//统计指标
const indexOptions: any = ref([])
//谐波次数
const countOptions: any = ref([])
// Harmonic_Type
// portable-harmonic
const legendDictList: any = ref([])
const initCode = (field: string, title: string) => {
queryByCode('gridSide_exceedTheLimit').then(res => {
queryCsDictTree(res.data.id).then(item => {
//排序
indexOptions.value = item.data.sort((a: any, b: any) => {
return a.sort - b.sort
})
let codeKey = field.includes('flickerOvertime')
? '闪变'
: field.includes('uharm')
? '谐波电压'
: field.includes('iharm')
? '谐波电流'
: field.includes('voltageDevOvertime')
? '电压偏差'
: field.includes('ubalanceOvertime')
? '不平衡'
: ''
// const titleMap: Record<string, number> = {
// flickerOvertime: 0,
// uaberranceOvertime: 3,
// ubalanceOvertime: 4,
// freqDevOvertime: 5
// }
// let defaultIndex = 0 // 默认值
let defaultIndex = indexOptions.value.findIndex((item: any) => item.name.includes(codeKey)) || 0
// if (field in titleMap) {
// defaultIndex = titleMap[field]
// } else if (field.includes('uharm')) {
// defaultIndex = indexOptions.value.findIndex((item: any) => item.code === 'uharm')
// } else if (field.includes('iharm')) {
// defaultIndex = indexOptions.value.findIndex((item: any) => item.code === 'iharm')
// }
searchForm.value.index[0] = indexOptions.value[defaultIndex].id
})
queryStatistical(res.data.id).then(vv => {
legendDictList.value = vv.data
indexOptions.value.map((item: any, index: any) => {
if (!countDataCopy.value[index]) {
countDataCopy.value[index] = {
index: item.id,
countOptions: [],
count: [],
name: indexOptions.value.find((vv: any) => {
return vv.id == item.id
})?.name
}
}
legendDictList.value?.selectedList?.map((vv: any, vvs: any) => {
//查找相等的指标
if (item.id == vv.dataType) {
vv.eleEpdPqdVOS.map((kk: any, kks: any) => {
if (kk.harmStart && kk.harmEnd) {
range(0, 0, 0)
if (kk.showName.includes('间谐波电压')) {
countDataCopy.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1).map(
(item: any) => {
return item - 0.5
}
)
} else {
countDataCopy.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1)
}
if (title && countDataCopy.value[index].countOptions.includes(Number(title))) {
countDataCopy.value[index].count = Number(title)
} else if (title && countDataCopy.value[index].countOptions.includes(title)) {
countDataCopy.value[index].count = title
} else if (
!countDataCopy.value[index].count ||
countDataCopy.value[index].count.length == 0
) {
// 只有当count为空时才设置默认值
countDataCopy.value[index].count = countDataCopy.value[index].countOptions[0]
}
}
})
}
})
})
nextTick(() => {
formatCountOptions()
})
init()
})
})
}
const chartsList = ref<any>([])
const chartTitle: any = ref('')
const echartsData = ref<any>(null)
//加载echarts图表
//历史趋势数据
const historyDataList: any = ref([])
const range = (start: any, end: any, step: any) => {
return Array.from({ length: (end - start) / step + 1 }, (_, i) => start + i * step)
}
//获取请求趋势数据参数
const trendRequestData = ref()
const getTrendRequest = (val: any) => {
trendRequestData.value = val
// init()
}
//初始化趋势图
const headerRef = ref()
const datePickerRef = ref()
const lineStyle = [{ type: 'solid' }, { type: 'dashed' }, { type: 'dotted' }]
const init = async () => {
loading.value = true
// 选择指标的时候切换legend内容和data数据
let list: any = []
legendDictList.value?.selectedList?.map((item: any) => {
searchForm.value.index.map((vv: any) => {
if (item.dataType == vv) {
list.push(item.eleEpdPqdVOS)
}
})
})
//颜色数组
const colorList = color
//选择的指标使用方法处理
formatCountOptions()
//查询历史趋势
historyDataList.value = []
chartTitle.value = ''
searchForm.value.index.map((item: any, indexs: any) => {
indexOptions.value.map((vv: any) => {
if (vv.id == item) {
chartTitle.value += indexs == searchForm.value.index.length - 1 ? vv.name : vv.name + '/'
}
})
})
let lists: any = []
let frequencys: any = null
countData.value.map((item: any, index: any) => {
if (item.name.includes('谐波')) {
frequencys = item.count
} else {
frequencys = ''
}
lists[index] = {
statisticalId: item.index,
frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : ''
}
})
let obj = {
//...trendRequestData.value,
lineId: trendRequestData.value.lineId,
list: lists,
dataLevel: searchForm.value.dataLevel,
valueType: searchForm.value.valueType,
searchBeginTime: datePickerRef.value && datePickerRef.value.timeValue[0],
searchEndTime: datePickerRef.value && datePickerRef.value.timeValue[1]
}
if (searchForm.value.index.length == 0) {
ElMessage.warning('请选择统计指标')
loading.value = false
return
}
if (obj.list.length != 0) {
try {
showEchart.value = true
await trendData(obj)
.then((res: any) => {
if (res.code == 'A0000') {
if (res.data.length == 0) {
loading.value = false
showEchart.value = false
return
}
historyDataList.value = res.data
chartsList.value = JSON.parse(JSON.stringify(res.data))
loading.value = false
setEchart()
}
})
.catch(error => {
loading.value = false
})
} catch (error) {
loading.value = false
}
}
}
const setEchart = () => {
loading.value = true
echartsData.value = {}
//icon图标替换legend图例
// y轴单位数组
let unitList: any = []
let groupedData = chartsList.value.reduce((acc: any, item: any) => {
let key = ''
if (item.phase == null) {
key = item.unit
} else {
key = item.anotherName
}
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
let result = Object.values(groupedData)
if (chartsList.value.length > 0) {
unitList = result.map((item: any) => {
return item[0].unit
})
}
echartsData.value = {
legend: {
itemWidth: 20,
itemHeight: 20,
itemStyle: { opacity: 0 }, //去圆点
type: 'scroll', // 开启滚动分页
// orient: 'vertical', // 垂直排列
top: 5,
right: 70
// width: 550,
// height: 50
},
grid: {
top: '80px'
},
tooltip: {
axisPointer: {
type: 'cross',
label: {
color: '#fff',
fontSize: 16
}
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter(params: any) {
const xname = params[0].value[0]
let str = `${xname}<br>`
params.forEach((el: any, index: any) => {
let marker = ''
if (el.value[3] == 'dashed') {
for (let i = 0; i < 3; i++) {
marker += `<span style="display:inline-block;border: 2px ${el.color} solid;margin-right:5px;width:10px;height:0px;background-color:#ffffff00;"></span>`
}
} else {
marker = `<span style="display:inline-block;border: 2px ${el.color} ${el.value[3]};margin-right:5px;width:40px;height:0px;background-color:#ffffff00;"></span>`
}
let unit = el.value[2] ? el.value[2] : ''
str += `${marker}${el.seriesName.split('(')[0]}${el.value[1]}${unit}
<br>`
})
return str
}
},
color: ['#DAA520', '#2E8B57', '#A52a2a', ...color],
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: [{}],
toolbox: {
featureProps: {
myTool1: {
show: true,
title: '下载csv',
icon: 'path://M588.8 551.253333V512H352v39.253333h236.373333z m0 78.933334v-39.253334H352v39.253334h236.373333z m136.533333 78.933333V334.933333l-157.866666-157.866666H273.066667A59.306667 59.306667 0 0 0 213.333333 236.373333v551.253334a59.306667 59.306667 0 0 0 59.306667 59.306666h274.773333v42.666667H853.333333v-180.48zM568.746667 234.666667l100.266666 100.693333h-81.066666a20.053333 20.053333 0 0 1-19.626667-20.053333z m-20.48 573.013333H273.066667a19.2 19.2 0 0 1-17.493334-19.626667V236.373333a19.2 19.2 0 0 1 19.626667-19.626666h256v98.133333a58.88 58.88 0 0 0 58.88 59.306667h96.426667v334.933333h-98.133334v-39.68H352v39.68h196.266667z m100.266666 23.04a37.973333 37.973333 0 0 1-32 15.786667 38.826667 38.826667 0 0 1-32.426666-15.786667 53.76 53.76 0 0 1-10.24-32.853333 42.666667 42.666667 0 0 1 42.666666-47.786667 35.84 35.84 0 0 1 37.546667 29.866667h-12.8a23.893333 23.893333 0 0 0-24.746667-19.2c-17.066667 0-29.013333 14.08-29.013333 35.84s11.52 37.546667 28.586667 37.546666a26.453333 26.453333 0 0 0 26.453333-25.6h12.8a39.253333 39.253333 0 0 1-7.253333 22.186667z m59.733334 15.786667a35.84 35.84 0 0 1-40.106667-34.56H682.666667a23.893333 23.893333 0 0 0 26.88 23.04c12.8 0 22.613333-6.4 22.613333-15.786667s-4.266667-11.52-14.506667-13.653333l-21.333333-5.12c-17.066667-4.266667-24.32-11.52-24.32-23.893334s12.8-26.453333 34.133333-26.453333a31.573333 31.573333 0 0 1 35.413334 30.293333h-13.653334a19.626667 19.626667 0 0 0-22.613333-18.773333c-12.8 0-20.48 5.12-20.48 12.8s5.12 11.093333 17.066667 13.653333l14.933333 2.986667a42.666667 42.666667 0 0 1 20.906667 8.96 23.893333 23.893333 0 0 1 7.68 17.92c-0.426667 17.066667-14.506667 28.16-37.12 28.16z m88.746666 0h-14.506666l-32.426667-92.16h14.08l19.626667 59.733333 6.4 20.053333c0-9.386667 3.413333-12.8 5.546666-20.053333l19.2-59.733333h14.08z',
onclick: e => {
// console.log("🚀 ~ init ~ echartsData.value:", echartsData.value.options.series.map(item => item.data))
let list = echartsData.value.options.series?.map((item: any) => item.data)
let dataList = list[0]?.map((item: any, index: any) => {
let value = [item[0], item[1]]
list.forEach((item1: any, index1: any) => {
if (index1 > 0) {
value.push(item1 && item1[index] ? item1[index][1] : null)
}
})
return value
})
exportCSV(
echartsData.value.options.series.map((item: any) => item.name),
dataList,
'监测点指标趋势.csv'
)
}
}
}
},
options: {
series: []
}
}
// console.log("🚀 ~ unitList.forEach ~ unitList:", unitList)
if (chartsList.value.length > 0) {
let yData: any = []
echartsData.value.yAxis = []
let setList = [...new Set(unitList)]
setList.forEach((item: any, index: any) => {
if (index > 2) {
echartsData.value.grid.right = (index - 1) * 80
}
yData.push([])
let right = {
position: 'right',
offset: (index - 1) * 80
}
// console.log("🚀 ~ unitList.forEach ~ right.index:", index)
echartsData.value.yAxis.push({
name: item,
yAxisIndex: index,
splitNumber: 5,
minInterval: 1,
splitLine: {
show: false
},
...(index > 0 ? right : null)
})
})
// console.log("🚀 ~ result.forEach ~ result:", result)
// '电压负序分量', '电压正序分量', '电压零序分量'
let ABCName = [
...new Set(
chartsList.value.map((item: any) => {
return item.anotherName == '电压负序分量'
? '电压不平衡'
: item.anotherName == '电压正序分量'
? '电压不平衡'
: item.anotherName == '电压零序分量'
? '电压不平衡'
: item.anotherName
})
)
]
// console.log("🚀 ~ .then ~ ABCName:", ABCName)
result.forEach((item: any, index: any) => {
let yMethodList: any = []
let ABCList = Object.values(
item.reduce((acc, item) => {
let key = ''
if (item.phase == null) {
key = item.anotherName
} else {
key = item.phase
}
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
)
// console.log("🚀 ~ ABCList.forEach ~ ABCList:", ABCList)
ABCList.forEach((kk: any) => {
let colorName = kk[0].phase?.charAt(0).toUpperCase()
let lineS = ABCName.findIndex(
item =>
item ===
(kk[0].anotherName == '电压负序分量'
? '电压不平衡'
: kk[0].anotherName == '电压正序分量'
? '电压不平衡'
: kk[0].anotherName == '电压零序分量'
? '电压不平衡'
: kk[0].anotherName)
)
let seriesList: any = []
kk.forEach((cc: any) => {
if (cc.statisticalData !== null) {
yData[setList.indexOf(kk[0].unit)].push(cc.statisticalData?.toFixed(2))
}
seriesList.push([cc.time, cc.statisticalData?.toFixed(2), cc.unit, lineStyle[lineS].type])
})
// console.log(kk);
echartsData.value.options.series.push({
name: kk[0].phase ? kk[0].phase + '相' + kk[0].anotherName : kk[0].anotherName,
type: 'line',
smooth: true,
color:
colorName == 'A' ? '#DAA520' : colorName == 'B' ? '#2E8B57' : colorName == 'C' ? '#A52a2a' : '',
symbol: 'none',
// data: seriesList,
data: timeControl.value ? completeTimeSeries(seriesList) : seriesList,
lineStyle: lineStyle[lineS],
yAxisIndex: setList.indexOf(kk[0].unit)
})
})
})
yData.forEach((item: any, index: any) => {
let [min, max] = yMethod(item)
echartsData.value.yAxis[index].min = min
echartsData.value.yAxis[index].max = max
})
// console.log("🚀 ~ result.forEach ~ echartsData.value:", echartsData.value)
}
loading.value = false
}
const setTimeControl = () => {
timeControl.value = !timeControl.value
setEchart()
}
const selectChange = (flag: boolean, height: any) => {
pageHeight.value = mainHeight(height * 1.6, 1.6)
}
//导出
const historyChart = ref()
const countData: any = ref([])
const countDataCopy: any = ref([])
//根据选择的指标处理谐波次数
const formatCountOptions = () => {
countData.value = []
if (searchForm.value.index.length != 0) {
searchForm.value.index.forEach((item: any, index: any) => {
countDataCopy.value.forEach((vv: any, vvs: any) => {
if (vv.index == item) {
countData.value.push(vv)
}
})
})
countData.value.map((item: any, key: any) => {
if (item.name.includes('间谐波电压')) {
item.name = '间谐波电压次数'
} else if (item.name.includes('谐波电流')) {
item.name = '谐波电流次数'
} else if (item.name.includes('谐波电压')) {
item.name = '谐波电压次数'
}
})
}
// setTimeout(() => {
// tableHeaderRef.value.computedSearchRow()
// }, 500)
}
// 判断下拉框是否存在
const onCountChange = (val: any, index: any) => {
if (val.length == 0) {
countData.value[index].count = countData.value[index].countOptions[0]
}
}
const flag = ref(true)
const onIndexChange = (val: any) => {
num.value += 1
let pp: any = []
indexOptions.value.forEach((item: any) => {
const filteredResult = val.filter(vv => item.id == vv)
if (filteredResult.length > 0) {
pp.push(filteredResult[0])
}
})
searchForm.value.index = pp
flag.value = true
formatCountOptions()
}
watch(
() => searchForm.value.index,
(val: any, oldval: any) => {},
{
deep: true,
immediate: true
}
)
const openDialog = async (row: any, field: any, title: any) => {
dialogVisible.value = true
trendRequestData.value = row
nextTick(() => {
// 默认当天
datePickerRef.value.setInterval(5)
datePickerRef.value.timeValue = [row.time, row.time]
initCode(field, title)
})
}
defineExpose({ getTrendRequest, openDialog })
</script>
<style lang="scss" scoped>
.history_header {
display: flex;
// flex-wrap: wrap;
#history_select {
width: 100%;
display: flex;
// justify-content: flex-start;
// overflow-x: auto;
height: auto;
flex-wrap: wrap;
.el-form-item {
flex: none !important;
// max-width: 380px;
}
.el-select {
margin-right: 10px;
}
}
// #history_select::-webkit-scrollbar {
// width: 0 !important;
// display: none !important;
// }
.history_searchBtn {
flex: 1;
display: flex;
justify-content: flex-end;
margin-top: 3px;
}
}
.history_chart {
width: 100%;
// flex: 1;
margin-top: 10px;
}
.history_count {
.el-select {
min-width: 100px;
}
}
</style>

View File

@@ -0,0 +1,192 @@
<template>
<div>
<!-- 指标越限详情 -->
<el-dialog draggable title="指标越限详情" v-model="dialogVisible" append-to-body width="70%">
<TableHeader datePicker showExport :showReset="false" ref="tableHeaderRef">
<template v-slot:select>
<el-form-item label="监测点">
<el-select
v-model="tableStore.table.params.lineId"
placeholder="请选择监测点"
style="width: 150px"
filterable
>
<el-option
v-for="item in options"
:key="item.lineId"
:label="item.lineName"
:value="item.lineId"
/>
</el-select>
</el-form-item>
</template>
</TableHeader>
<Table ref="tableRef" @cell-click="cellClickEvent" isGroup :height="height"></Table>
</el-dialog>
<!-- 谐波电流谐波电压占有率 -->
<HarmonicRatio ref="harmonicRatioRef" v-if="dialogFlag" @close="onHarmonicRatioClose" />
</div>
</template>
<script setup lang="ts">
import { ref, provide,nextTick } from 'vue'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout'
import HarmonicRatio from '@/components/cockpit/gridSideStatistics/components/harmonicRatio.vue'
import { cslineList } from '@/api/harmonic-boot/cockpit/cockpit'
const dialogVisible: any = ref(false)
const harmonicRatioRef: any = ref(null)
const options = ref()
const height = mainHeight(0, 2).height as any
const tableHeaderRef = ref()
const dialogFlag = ref(false)
const loop50 = (key: string) => {
let list: any[] = []
for (let i = 2; i < 26; i++) {
list.push({
title: i + '次',
field: key + i + 'Overtime',
width: '60',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row[key + i + 'Overtime']}</span>`
}
})
}
return list
}
const tableStore: any = new TableStore({
url: '/cs-harmonic-boot/totalLimitStatistics/details',
method: 'POST',
publicHeight: 30,
showPage: false,
exportName: '每日越限占比统计',
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '日期',
field: 'time',
width: '150',
sortable: true
},
{
title: '名称',
field: 'lineName',
width: '150'
},
{
title: '长时闪变越限(%)',
field: 'flickerOvertime',
width: '90',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
}
},
{
title: '电压偏差越限(%)',
field: 'voltageDevOvertime',
width: '100',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
}
},
{
title: '三相不平衡度越限(%)',
field: 'ubalanceOvertime',
width: '100',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>`
}
},
{
title: '谐波电压越限(%)',
children: loop50('uharm')
},
{
title: '谐波电流越限(%)',
children: loop50('iharm')
},
// {
// title: '频率偏差越限(%)',
// field: 'freqDevOvertime',
// width: '100',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>`
// }
// }
],
beforeSearchFun: () => {
},
loadCallback: () => {
}
})
provide('tableStore', tableStore)
tableStore.table.params.sortBy = ''
tableStore.table.params.orderBy = ''
const open = async (row: any,searchBeginTime:any,searchEndTime:any,interval:any,list:any) => {
dialogVisible.value = true
options.value = list
// initCSlineList()
tableStore.table.params.lineId = row.lineId
nextTick(() => {
tableHeaderRef.value.setInterval(interval)
setTimeout(() => {
tableHeaderRef.value.setTimeInterval([searchBeginTime, searchEndTime])
tableStore.table.params.searchBeginTime =searchBeginTime
tableStore.table.params.searchEndTime = searchEndTime
tableStore.index()
},100)
})
}
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name' && column.field != 'time') {
dialogFlag.value = true
dialogVisible.value = false
nextTick(() => {
harmonicRatioRef.value.openDialog(row,column.field,column.title.replace(/次/g, ""))
})
}
}
// 谐波弹窗关闭时的回调
const onHarmonicRatioClose = () => {
dialogFlag.value = false
// 重新打开指标越限详情弹窗
nextTick(() => {
dialogVisible.value = true
})
}
const initCSlineList = async () => {
const res = await cslineList({})
options.value = res.data
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@@ -1,19 +1,26 @@
<template> <template>
<div> <div>
<!--电网侧指标越限统计 --> <!--电网侧指标越限统计 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
:showReset="false"
ref="TableHeaderRef"
@selectChange="selectChange"
datePicker
:timeKeyList="prop.timeKey"
v-if="fullscreen"
></TableHeader>
<my-echart <my-echart
class="tall" class="tall"
:options="echartList" :options="echartList"
:style="{ :style="{
width: prop.width, width: prop.width,
height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )` height: `calc(${prop.height} / 2 )`
}" }"
/> />
<Table <Table
ref="tableRef" ref="tableRef"
@cell-click="cellClickEvent" @cell-click="cellClickEvent"
:height="`calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`" :height="`calc(${prop.height} / 2 - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`"
isGroup isGroup
></Table> ></Table>
<!-- 指标越限详情 --> <!-- 指标越限详情 -->
@@ -24,35 +31,35 @@
import { ref, onMounted, provide, reactive, watch, h } from 'vue' import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue' import Table from '@/components/table/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import OverLimitDetails from '@/components/cockpit/listOfMainMonitoringPoints/components/overLimitDetails.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router' import MyEchart from '@/components/echarts/MyEchart.vue'
import { useTimeCacheStore } from '@/stores/timeCache' import OverLimitDetails from '@/components/cockpit/gridSideStatistics/components/overLimitDetails.vue'
import { gridSideLimitStatisticsData } from '@/api/harmonic-boot/cockpit/cockpit'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const route = useRoute() const echartList = ref({})
const timeCacheStore = useTimeCacheStore()
const TableHeaderRef = ref()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
@@ -67,49 +74,59 @@ const fullscreen = computed(() => {
return false return false
} }
}) })
const echartList = ref({
title: {
text: '指标越限占比'
},
xAxis: { const initEcharts = () => {
// name: '(区域)', gridSideLimitStatisticsData({
data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡'] searchBeginTime: tableStore.table.params.searchBeginTime,
}, searchEndTime: tableStore.table.params.searchEndTime
}).then((res: any) => {
const dataArray = [res.data.flicker, res.data.uharm, res.data.iharm, res.data.voltageDev, res.data.ubalance]
echartList.value = {
title: {
text: '指标越限占比'
},
yAxis: { xAxis: {
name: '%', // 给X轴加单位 // name: '(区域)',
interval: 20 data: ['长时闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
}, },
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '越限占比',
data: [0, 45, 22, 0, 70],
barMaxWidth: 30
// label: { yAxis: {
// show: true, name: '%', // 给X轴加单位
// position: 'top', interval: 20
// textStyle: { },
// //数值样式 grid: {
// color: '#000' left: '10px',
// }, right: '20px'
// fontSize: 12 },
// } options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '越限占比',
data: dataArray,
barMaxWidth: 30
// label: {
// show: true,
// position: 'top',
// textStyle: {
// //数值样式
// color: '#000'
// },
// fontSize: 12
// }
}
]
} }
] }
} })
}) }
const OverLimitDetailsRef = ref() const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree', url: '/cs-harmonic-boot/gridSideLimitStatistics/list',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
@@ -125,90 +142,67 @@ const tableStore: any = new TableStore({
}, },
{ {
title: '名称', title: '名称',
field: 'name', field: 'lineName',
minWidth: '90' minWidth: '90'
}, },
{ {
title: '越限占比(%)', title: '越限占比(%)',
children: [ children: [
{ {
title: '闪变', title: '长时闪变',
field: 'type', field: 'flicker',
minWidth: '70', minWidth: '70',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>`
} }
}, },
{ {
title: '谐波电压', title: '谐波电压',
field: 'type1', field: 'uharm',
minWidth: '80', minWidth: '80',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type1}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.uharm}</span>`
} }
}, },
{ {
title: '谐波电流', title: '谐波电流',
field: 'type2', field: 'iharm',
minWidth: '80', minWidth: '80',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type2}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.iharm}</span>`
} }
}, },
{ {
title: '电压偏差', title: '电压偏差',
field: 'type3', field: 'voltageDev',
minWidth: '80', minWidth: '80',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type3}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.voltageDev}</span>`
} }
}, },
{ {
title: '三相不平衡', title: '三相不平衡',
field: 'type4', field: 'ubalance',
minWidth: '90', minWidth: '90',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type4}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalance}</span>`
} }
} }
] ]
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime tableStore.table.params.interval = TableHeaderRef.value?.datePickerRef?.interval || 3
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.data = [
{
name: '1#变压器电网侧',
type: '0',
type1: '45',
type2: '22',
type3: '0',
type4: '70'
}
]
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
initEcharts()
} }
}) })
@@ -219,28 +213,51 @@ provide('tableStore', tableStore)
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') { OverLimitDetailsRef.value.open(
console.log(row) row,
OverLimitDetailsRef.value.open(row) tableStore.table.params.searchBeginTime || prop.timeValue?.[0],
} tableStore.table.params.searchEndTime || prop.timeValue?.[1],
tableStore.table.params.interval || prop.interval,
tableStore.table.data
)
} }
onMounted(() => { onMounted(() => {
tableStore.index() tableStore.index()
}) })
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
watch( watch(
() => prop.timeKey, () => prop.timeKey,
val => { val => {
tableStore.index() tableStore.index()
initEcharts()
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )

View File

@@ -0,0 +1,480 @@
<template>
<div>
<!--指标越限时间分布
-->
<TableHeader
:showReset="false"
:timeKeyList="prop.timeKey"
ref="TableHeaderRef"
@selectChange="selectChange"
datePicker
v-if="fullscreen"
>
<template v-slot:select>
<el-form-item label="监测点">
<el-select size="small" filterable v-model="tableStore.table.params.lineId">
<el-option
v-for="item in lineList"
:key="item.lineId"
:label="item.name"
:value="item.lineId"
/>
</el-select>
</el-form-item>
</template>
</TableHeader>
<div v-loading="tableStore.table.loading">
<my-echart
class="tall"
v-if="lineShow"
:options="echartList1"
:style="{
width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}"
/>
<el-empty
v-else
description="暂无监测点"
:style="{
width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}"
/>
<!-- <my-echart
class="mt10"
:options="echartList1"
:style="{
width: prop.width,
height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`
}"
/> -->
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import TableHeader from '@/components/table/header/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { limitProbabilityData, cslineList } from '@/api/harmonic-boot/cockpit/cockpit'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({
w: { type: [String, Number] },
h: { type: [String, Number] },
width: { type: [String, Number] },
height: { type: [String, Number] },
timeKey: { type: Array as () => string[] },
timeValue: { type: Object },
interval: { type: Number }
})
// const options = ref(JSON.parse(window.localStorage.getItem('lineIdList') || '[]'))
const lineList = ref()
const headerHeight = ref(57)
const TableHeaderRef = ref()
const lineShow = ref(true)
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
if (datePickerValue && datePickerValue.timeValue) {
// 更新时间参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const echartList = ref()
const echartList1 = ref()
const probabilityData = ref()
const initLineList = async () => {
cslineList({}).then(res => {
if (res.data.length == 0) {
lineShow.value = false
return (tableStore.table.loading = false)
}
lineShow.value = true
lineList.value = res.data
tableStore.table.params.lineId = lineList.value[0].lineId
tableStore.index()
})
}
// 越限程度概率分布
const initProbabilityData = () => {
// 只有当 lineList 已加载且有数据时才设置默认 lineId
if (!tableStore.table.params.lineId && lineList.value && lineList.value.length > 0) {
tableStore.table.params.lineId = lineList.value[0].lineId
}
const params = {
searchBeginTime: tableStore.table.params.searchBeginTime || prop.timeValue?.[0],
searchEndTime: tableStore.table.params.searchEndTime || prop.timeValue?.[1],
lineId: tableStore.table.params.lineId
}
limitProbabilityData(params).then((res: any) => {
probabilityData.value = res.data
// 处理接口返回的数据,转换为图表所需格式
if (res.data && Array.isArray(res.data)) {
// 定义指标类型顺序
const indicatorOrder = ['长时闪变', '谐波电压', '谐波电流', '电压偏差', '三相电压不平衡度', '频率偏差']
// 按照指定顺序排序数据
const sortedData = [...res.data].sort((a, b) => {
return indicatorOrder.indexOf(a.indexName) - indicatorOrder.indexOf(b.indexName)
})
// 构造 series 数据
const seriesData: any = []
let maxValue: any = 0 // 用于存储数据中的最大值
// 遍历每个越限程度区间0-20%, 20-40%, 40-60%, 60-80%, 80-100%
for (let xIndex = 0; xIndex < 5; xIndex++) {
// 遍历每个指标类型
sortedData.forEach((item, yIndex) => {
// 从 extentGrades 中获取对应区间的值
const extentGrade = item.extentGrades[xIndex]
const value = extentGrade ? (Object.values(extentGrade)[0] as number) : 0
seriesData.push([xIndex, yIndex, value])
// 更新最大值
if (value > maxValue) {
maxValue = value
}
})
}
// 计算 z 轴最大值(最大值加 5
const zAxisMax = Math.ceil(maxValue) + 5
// 构造 yAxis 数据(指标类型名称)
const yAxisData = sortedData.map(item => item.indexName)
echartList.value = {
title: {
text: '指标越限概率分布'
},
options: {
backgroundColor: '#fff',
tooltip: {
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter: function (params: any) {
var yIndex = params.value[1] //获取y轴索引
var tips = ''
tips += '指标类型: ' + yAxisData[yIndex] + '</br>'
tips += '越限程度: ' + params.seriesName + '</br>'
tips += '越限天数: ' + params.value[2] + '</br>'
return tips
}
},
// 移除或隐藏 visualMap 组件
visualMap: {
show: false, // 设置为 false 隐藏右侧颜色条
min: 0,
// max: 100,
max: zAxisMax, // 使用计算出的最大值加5
inRange: {
color: ['#313695', '#00BB00', '#ff8000', '#d73027', '#a50026']
}
},
// 添加 legend 配置并设置为不显示
legend: {
show: false // 隐藏图例
},
xAxis3D: {
type: 'category',
name: '越限程度',
nameLocation: 'middle',
nameGap: 50,
data: ['0-20%', '20-40%', '40-60%', '60-80%', '80-100%']
},
yAxis3D: {
type: 'category',
name: '指标类型',
nameLocation: 'middle',
nameGap: 50,
data: yAxisData,
splitLine: {
lineStyle: {
type: 'dashed',
opacity: 0.5
}
}
},
zAxis3D: {
type: 'value',
name: '越限天数',
nameLocation: 'middle',
nameGap: 30,
minInterval: 10
// max: 100
},
grid3D: {
viewControl: {
projection: 'perspective',
distance: 260,
rotateSensitivity: 10,
zoomSensitivity: 2
},
boxWidth: 150,
boxDepth: 100,
boxHeight: 100,
light: {
main: {
intensity: 1.2
},
ambient: {
intensity: 0.4
}
}
},
series: [
{
type: 'bar3D',
name: '0-20%',
data: seriesData.filter((item: any) => item[0] === 0),
shading: 'realistic',
label: {
show: false
},
itemStyle: {
opacity: 0.9
},
emphasis: {
label: {
show: true,
textStyle: {
fontSize: 14,
color: '#000'
}
},
itemStyle: {
color: '#ff8000'
}
}
},
{
type: 'bar3D',
name: '20-40%',
data: seriesData.filter((item: any) => item[0] === 1),
shading: 'realistic',
label: {
show: false
},
itemStyle: {
opacity: 0.9
},
emphasis: {
label: {
show: true,
textStyle: {
fontSize: 14,
color: '#000'
}
},
itemStyle: {
color: '#ff8000'
}
}
},
{
type: 'bar3D',
name: '40-60%',
data: seriesData.filter((item: any) => item[0] === 2),
shading: 'realistic',
label: {
show: false
},
itemStyle: {
opacity: 0.9
},
emphasis: {
label: {
show: true,
textStyle: {
fontSize: 14,
color: '#000'
}
},
itemStyle: {
color: '#ff8000'
}
}
},
{
type: 'bar3D',
name: '60-80%',
data: seriesData.filter((item: any) => item[0] === 3),
shading: 'realistic',
label: {
show: false
},
itemStyle: {
opacity: 0.9
},
emphasis: {
label: {
show: true,
textStyle: {
fontSize: 14,
color: '#000'
}
},
itemStyle: {
color: '#ff8000'
}
}
},
{
type: 'bar3D',
name: '80-100%',
data: seriesData.filter((item: any) => item[0] === 4),
shading: 'realistic',
label: {
show: false
},
itemStyle: {
opacity: 0.9
},
emphasis: {
label: {
show: true,
textStyle: {
fontSize: 14,
color: '#000'
}
},
itemStyle: {
color: '#ff8000'
}
}
}
]
}
}
}
})
}
const tableStore: any = new TableStore({
url: '/cs-harmonic-boot/limitRateDetailD/limitTimeProbabilityData',
method: 'POST',
showPage: false,
column: [],
beforeSearchFun: () => {
setTime()
// 只有当 lineList 已加载且有数据时才设置默认 lineId
if (!tableStore.table.params.lineId && lineList.value && lineList.value.length > 0) {
tableStore.table.params.lineId = lineList.value[0].lineId
}
},
loadCallback: () => {
// 处理返回的数据,将其转换为图表所需格式
const indexNames: any = [...new Set(tableStore.table.data.map((item: any) => item.indexName))]
const timePeriods = [...new Set(tableStore.table.data.map((item: any) => item.timePeriod))]
// 构建系列数据
const seriesData = indexNames.map((indexName: string) => {
const dataIndex = tableStore.table.data.filter((item: any) => item.indexName === indexName)
return {
name: indexName,
type: 'line',
symbol: 'none',
data: dataIndex.map((item: any) => [item.timePeriod, item.times])
}
})
echartList1.value = {
title: {
text: '指标越限时间概率分布'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: indexNames
},
xAxis: {
type: 'category',
name: '时间段',
data: timePeriods
},
yAxis: {
type: 'value'
// name: '次数'
},
series: seriesData
}
initProbabilityData()
}
})
provide('tableStore', tableStore)
onMounted(() => {
initLineList()
})
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue,
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

View File

@@ -1,26 +1,38 @@
<template> <template>
<div> <div>
<!--指标越限明细 --> <!--指标越限明细 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
<el-calendar v-model="value" :style="{ height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`, overflow: 'auto' }"> :showReset="false"
ref="TableHeaderRef"
@selectChange="selectChange"
datePicker
v-if="fullscreen"
:timeKeyList="prop.timeKey"
></TableHeader>
<el-calendar
v-model="value"
:style="{
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`,
overflow: 'auto'
}"
v-loading="tableStore.table.loading"
>
<template #date-cell="{ data }"> <template #date-cell="{ data }">
<div style="height: 100%; padding: 8px" :style="{ background: setBackground(data.day) }"> <div
style="padding: 8px"
:style="{
background: setBackground(data.day),
height: `calc((${prop.height} - 100px - ${headerHeight}px + ${fullscreen ? 0 : 56}px) / 5 )`
}"
>
<p :class="data.isSelected ? 'is-selected' : ''"> <p :class="data.isSelected ? 'is-selected' : ''">
{{ data.day.split('-').slice(2).join('-') }} {{ data.day.split('-').slice(2).join('-') }}
</p> </p>
<el-tooltip <el-tooltip effect="dark" placement="top" :hide-after="0" v-if="getTextForDate(data.day)">
effect="dark"
placement="top"
:hide-after="0"
v-if="list?.filter(item => item.time == data.day)[0]?.text || false"
>
<template #content> <template #content>
<span v-html="list?.filter(item => item.time == data.day)[0]?.text || ''"></span> <span v-html="getTextForDate(data.day)"></span>
</template> </template>
<div <div class="details" v-html="fullscreen ? getTextForDate(data.day) : '有越限'"></div>
:style="{ height: `calc(${prop.height} / 5 - 47px)`, overflow: 'auto' }"
v-html="list?.filter(item => item.time == data.day)[0]?.text || ''"
></div>
</el-tooltip> </el-tooltip>
</div> </div>
</template> </template>
@@ -31,25 +43,24 @@
import { ref, onMounted, provide, reactive, watch, h } from 'vue' import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { dayjs } from 'element-plus' import { dayjs } from 'element-plus'
import { getTime } from '@/utils/formatTime'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const route = useRoute() const list = ref()
const timeCacheStore = useTimeCacheStore()
const TableHeaderRef = ref()
dayjs.en.weekStart = 1 //设置日历的周起始日为星期一 dayjs.en.weekStart = 1 //设置日历的周起始日为星期一
const value = ref(new Date()) const value = ref(new Date())
@@ -57,11 +68,10 @@ const value = ref(new Date())
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
@@ -77,132 +87,116 @@ const fullscreen = computed(() => {
} }
}) })
const list = ref([ const getTextForDate = (date: string) => {
{ const item = list.value?.find((item: any) => item.time === date)
time: '2025-11-01', return item ? item.text : ''
key: 81, }
text: '3次谐波越限<br/>5次谐波越限<br/>三相不平衡越限'
},
{
time: '2025-10-31',
key: 81,
text: '3次谐波越限<br/>5次谐波越限<br/>三相不平衡越限'
},
{
time: '2025-11-08',
key: 20,
text: '3次谐波越限<br/>5次谐波越限<br/>三相不平衡越限'
},
{
time: '2025-11-16',
key: 20,
text: '3次谐波越限<br/>5次谐波越限<br/>三相不平衡越限'
},
{
time: '2025-11-23',
key: 20,
text: '3次谐波越限<br/>5次谐波越限<br/>三相不平衡越限'
},
{
time: '2025-11-04',
key: 0,
text: ''
},
{
time: '2025-10-05',
key: 0,
text: ''
}
])
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree', url: '/cs-harmonic-boot/limitRateDetailD/limitCalendarData',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.data = [] value.value = tableStore.table.params.searchBeginTime
if (tableStore.table.data && tableStore.table.data.length > 0) {
list.value = tableStore.table.data.map((item: any) => {
// 将 items 数组转换为带换行的文本
const text = item.items && item.items.length > 0 ? item.items.join('<br/>') : ''
return {
time: item.time,
key: item.status || 0,
text: text
}
})
} else {
list.value = []
}
} }
}) })
const tableRef = ref([])
const setBackground = (value: string) => { const setBackground = (value: string) => {
let data = [] const data = list.value?.find((item: any) => item.time === value)
data = list.value?.filter(item => item.time == value)
if (data && data?.length > 0) { if (data) {
if (data[0].key < 10) { // 根据 status 值返回对应的颜色
return '#33996690' switch (data.key) {
} else if (data[0].key < 80) { case 0: // 无越限
return '#FFCC3390' return '#33996690'
} else if (data[0].key <= 100) { case 1: // 一般越限
return '#Ff660090' return '#FFCC3390'
case 2: // 严重越限
return '#Ff660090'
default:
return '#fff' // 默认白色背景
} }
} }
return '#fff' return '#fff' // 默认白色背景
} }
provide('tableRef', tableRef)
provide('tableStore', tableStore) provide('tableStore', tableStore)
onMounted(() => { onMounted(() => {
tableStore.index() nextTick(() => {
})
watch(
() => prop.timeKey,
val => {
tableStore.index() tableStore.index()
})
})
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
} }
) }
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )
const addMenu = () => {}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-calendar) { :deep(.el-calendar) {
.el-calendar__header { .el-calendar__button-group {
display: none; display: none;
} }
.el-calendar__body { .el-calendar__body {
padding: 0px !important; padding: 0px !important;
height: 100%; height: calc(100% - 46px);
.el-calendar-table { .el-calendar-table {
height: 100%; height: 100%;
} }
} }
.el-calendar-day { .el-calendar-day {
// height: calc(912px / 5 );
height: 100%; height: 100%;
padding: 0px; padding: 0px;
overflow: hidden; overflow: hidden;
.details {
height: calc(100% - 20px);
overflow-y: auto;
}
} }
.el-calendar-table__row { .el-calendar-table__row {
.next { .next {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,716 @@
<template>
<el-dialog draggable title="趋势图" v-model="dialogVisible" append-to-body width="70%">
<!-- 总体指标占比详情谐波含有率 -->
<div>
<TableHeader ref="tableHeaderRef" :showSearch="false" @selectChange="selectChange">
<template v-slot:select>
<el-form-item>
<DatePicker ref="datePickerRef"></DatePicker>
</el-form-item>
<el-form-item label="统计指标" label-width="80px">
<el-select
multiple
:multiple-limit="2"
collapse-tags
collapse-tags-tooltip
v-model="searchForm.index"
placeholder="请选择统计指标"
@change="onIndexChange($event)"
filterable
>
<el-option
v-for="item in indexOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-radio-group v-model="searchForm.dataLevel" @change="init()">
<el-radio-button label="一次值" value="Primary" />
<el-radio-button label="二次值" value="Secondary" />
</el-radio-group>
</el-form-item>
<el-form-item label="统计类型">
<el-select
style="min-width: 120px !important"
placeholder="请选择"
v-model="searchForm.valueType"
filterable
>
<el-option value="max" label="最大值"></el-option>
<el-option value="min" label="最小值"></el-option>
<el-option value="avg" label="平均值"></el-option>
<el-option value="cp95" label="cp95"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<div
class="history_count"
v-for="(item, index) in countData"
:key="index"
v-show="item.countOptions.length != 0"
>
<span class="mr12">
{{ item.name.includes('次数') ? item.name : item.name + '谐波次数' }}
</span>
<el-select
v-model="item.count"
@change="onCountChange($event, index)"
placeholder="请选择谐波次数"
style="width: 100px"
class="mr20"
filterable
>
<el-option
v-for="vv in item.countOptions"
:key="vv"
:label="item.name.includes('间谐波') ? vv - 0.5 : vv"
:value="vv"
></el-option>
</el-select>
</div>
</el-form-item>
</template>
<template #operation>
<el-button type="primary" icon="el-icon-Search" @click="init()">查询</el-button>
<el-button :type="timeControl ? 'primary' : ''" icon="el-icon-Sort" @click="setTimeControl">
缺失数据
</el-button>
</template>
</TableHeader>
</div>
<div class="history_chart" :style="pageHeight" v-loading="loading">
<MyEchart ref="historyChart" :options="echartsData" v-if="showEchart" />
<el-empty :style="pageHeight" v-else description="暂无数据" />
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { mainHeight } from '@/utils/layout'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { ref, onMounted, watch } from 'vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useDictData } from '@/stores/dictData'
import { queryStatistical } from '@/api/system-boot/csstatisticalset'
import { yMethod, exportCSV, completeTimeSeries } from '@/utils/echartMethod'
import TableHeader from '@/components/table/header/index.vue'
import { trendData } from '@/api/harmonic-boot/cockpit/cockpit'
import DatePicker from '@/components/form/datePicker/index.vue'
import { color } from '@/components/echarts/color'
import { ElMessage } from 'element-plus'
const dictData = useDictData()
defineOptions({
// name: 'govern/device/control'
})
const props = defineProps({
TrendList: {
type: Array
}
})
const dialogVisible: any = ref(false)
// console.log("🚀 ~ props:", props.TrendList)
const showEchart = ref(true)
const num = ref(0)
const timeControl = ref(false)
//值类型
const pageHeight = ref(mainHeight(57 * 1.6, 1.6))
const loading = ref(true)
const searchForm: any = ref({})
const tableHeaderRef = ref()
const typeOptions = [
{
name: '平均值',
id: 'avg'
},
{
name: '最大值',
id: 'max'
},
{
name: '最小值',
id: 'min'
},
{
name: 'CP95值',
id: 'cp95'
}
]
searchForm.value = {
index: [],
type: typeOptions[0].id,
count: '',
searchBeginTime: '',
searchEndTime: '',
dataLevel: 'Primary',
valueType: 'avg'
}
//统计指标
const indexOptions: any = ref([])
//谐波次数
const countOptions: any = ref([])
// Harmonic_Type
// portable-harmonic
const legendDictList: any = ref([])
const initCode = (field: string, title: string) => {
queryByCode('gridSide_exceedTheLimit').then(res => {
queryCsDictTree(res.data.id).then(item => {
//排序
indexOptions.value = item.data.sort((a: any, b: any) => {
return a.sort - b.sort
})
// const titleMap: Record<string, number> = {
// flickerOvertime: 0,
// uaberranceOvertime: 3,
// ubalanceOvertime: 4,
// freqDevOvertime: 5
// }
// let defaultIndex = 0 // 默认值
// if (field in titleMap) {
// defaultIndex = titleMap[field]
// } else if (field.includes('uharm')) {
// defaultIndex = 1
// } else if (field.includes('iharm')) {
// defaultIndex = 2
// }
let codeKey = field.includes('flickerOvertime')
? '闪变'
: field.includes('uharm')
? '谐波电压'
: field.includes('iharm')
? '谐波电流'
: field.includes('voltageDevOvertime')
? '电压偏差'
: field.includes('ubalanceOvertime')
? '不平衡'
: ''
let defaultIndex = indexOptions.value.findIndex((item: any) => item.name.includes(codeKey)) || 0
searchForm.value.index[0] = indexOptions.value[defaultIndex].id
})
queryStatistical(res.data.id).then(vv => {
legendDictList.value = vv.data
indexOptions.value.map((item: any, index: any) => {
if (!countDataCopy.value[index]) {
countDataCopy.value[index] = {
index: item.id,
countOptions: [],
count: [],
name: indexOptions.value.find((vv: any) => {
return vv.id == item.id
})?.name
}
}
legendDictList.value?.selectedList?.map((vv: any, vvs: any) => {
//查找相等的指标
if (item.id == vv.dataType) {
vv.eleEpdPqdVOS.map((kk: any, kks: any) => {
if (kk.harmStart && kk.harmEnd) {
range(0, 0, 0)
if (kk.showName == '间谐波电压含有率') {
countDataCopy.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1).map(
(item: any) => {
return item - 0.5
}
)
} else {
countDataCopy.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1)
}
if (title && countDataCopy.value[index].countOptions.includes(Number(title))) {
countDataCopy.value[index].count = Number(title)
} else if (title && countDataCopy.value[index].countOptions.includes(title)) {
countDataCopy.value[index].count = title
} else if (
!countDataCopy.value[index].count ||
countDataCopy.value[index].count.length == 0
) {
// 只有当count为空时才设置默认值
countDataCopy.value[index].count = countDataCopy.value[index].countOptions[0]
}
}
})
}
})
})
nextTick(() => {
formatCountOptions()
})
init()
})
})
}
const chartsList = ref<any>([])
const chartTitle: any = ref('')
const echartsData = ref<any>(null)
//加载echarts图表
//历史趋势数据
const historyDataList: any = ref([])
const range = (start: any, end: any, step: any) => {
return Array.from({ length: (end - start) / step + 1 }, (_, i) => start + i * step)
}
//获取请求趋势数据参数
const trendRequestData = ref()
const getTrendRequest = (val: any) => {
trendRequestData.value = val
// init()
}
//初始化趋势图
const headerRef = ref()
const datePickerRef = ref()
const lineStyle = [{ type: 'solid' }, { type: 'dashed' }, { type: 'dotted' }]
const init = async () => {
loading.value = true
// 选择指标的时候切换legend内容和data数据
let list: any = []
legendDictList.value?.selectedList?.map((item: any) => {
searchForm.value.index.map((vv: any) => {
if (item.dataType == vv) {
list.push(item.eleEpdPqdVOS)
}
})
})
//颜色数组
const colorList = color
//选择的指标使用方法处理
formatCountOptions()
//查询历史趋势
historyDataList.value = []
chartTitle.value = ''
searchForm.value.index.map((item: any, indexs: any) => {
indexOptions.value.map((vv: any) => {
if (vv.id == item) {
chartTitle.value += indexs == searchForm.value.index.length - 1 ? vv.name : vv.name + '/'
}
})
})
let lists: any = []
let frequencys: any = null
countData.value.map((item: any, index: any) => {
if (item.name.includes('谐波含有率')) {
frequencys = item.count
} else {
frequencys = ''
}
lists[index] = {
statisticalId: item.index,
frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : ''
}
})
let obj = {
//...trendRequestData.value,
lineId: trendRequestData.value.lineId,
list: lists,
dataLevel: searchForm.value.dataLevel,
valueType: searchForm.value.valueType,
searchBeginTime: datePickerRef.value && datePickerRef.value.timeValue[0],
searchEndTime: datePickerRef.value && datePickerRef.value.timeValue[1]
}
if (searchForm.value.index.length == 0) {
ElMessage.warning('请选择统计指标')
loading.value = false
return
}
if (obj.list.length != 0) {
try {
showEchart.value = true
await trendData(obj)
.then((res: any) => {
if (res.code == 'A0000') {
if (res.data.length == 0) {
loading.value = false
showEchart.value = false
return
}
historyDataList.value = res.data
chartsList.value = JSON.parse(JSON.stringify(res.data))
loading.value = false
setEchart()
}
})
.catch(error => {
loading.value = false
})
} catch (error) {
loading.value = false
}
}
}
const setEchart = () => {
loading.value = true
echartsData.value = {}
//icon图标替换legend图例
// y轴单位数组
let unitList: any = []
let groupedData = chartsList.value.reduce((acc: any, item: any) => {
let key = ''
if (item.phase == null) {
key = item.unit
} else {
key = item.anotherName
}
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
let result = Object.values(groupedData)
if (chartsList.value.length > 0) {
unitList = result.map((item: any) => {
return item[0].unit
})
}
echartsData.value = {
legend: {
itemWidth: 20,
itemHeight: 20,
itemStyle: { opacity: 0 }, //去圆点
type: 'scroll', // 开启滚动分页
// orient: 'vertical', // 垂直排列
top: 5,
right: 70
// width: 550,
// height: 50
},
grid: {
top: '80px'
},
tooltip: {
axisPointer: {
type: 'cross',
label: {
color: '#fff',
fontSize: 16
}
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter(params: any) {
const xname = params[0].value[0]
let str = `${xname}<br>`
params.forEach((el: any, index: any) => {
let marker = ''
if (el.value[3] == 'dashed') {
for (let i = 0; i < 3; i++) {
marker += `<span style="display:inline-block;border: 2px ${el.color} solid;margin-right:5px;width:10px;height:0px;background-color:#ffffff00;"></span>`
}
} else {
marker = `<span style="display:inline-block;border: 2px ${el.color} ${el.value[3]};margin-right:5px;width:40px;height:0px;background-color:#ffffff00;"></span>`
}
let unit = el.value[2] ? el.value[2] : ''
str += `${marker}${el.seriesName.split('(')[0]}${el.value[1]}${unit}
<br>`
})
return str
}
},
color: ['#DAA520', '#2E8B57', '#A52a2a', ...color],
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: [{}],
toolbox: {
featureProps: {
myTool1: {
show: true,
title: '下载csv',
icon: 'path://M588.8 551.253333V512H352v39.253333h236.373333z m0 78.933334v-39.253334H352v39.253334h236.373333z m136.533333 78.933333V334.933333l-157.866666-157.866666H273.066667A59.306667 59.306667 0 0 0 213.333333 236.373333v551.253334a59.306667 59.306667 0 0 0 59.306667 59.306666h274.773333v42.666667H853.333333v-180.48zM568.746667 234.666667l100.266666 100.693333h-81.066666a20.053333 20.053333 0 0 1-19.626667-20.053333z m-20.48 573.013333H273.066667a19.2 19.2 0 0 1-17.493334-19.626667V236.373333a19.2 19.2 0 0 1 19.626667-19.626666h256v98.133333a58.88 58.88 0 0 0 58.88 59.306667h96.426667v334.933333h-98.133334v-39.68H352v39.68h196.266667z m100.266666 23.04a37.973333 37.973333 0 0 1-32 15.786667 38.826667 38.826667 0 0 1-32.426666-15.786667 53.76 53.76 0 0 1-10.24-32.853333 42.666667 42.666667 0 0 1 42.666666-47.786667 35.84 35.84 0 0 1 37.546667 29.866667h-12.8a23.893333 23.893333 0 0 0-24.746667-19.2c-17.066667 0-29.013333 14.08-29.013333 35.84s11.52 37.546667 28.586667 37.546666a26.453333 26.453333 0 0 0 26.453333-25.6h12.8a39.253333 39.253333 0 0 1-7.253333 22.186667z m59.733334 15.786667a35.84 35.84 0 0 1-40.106667-34.56H682.666667a23.893333 23.893333 0 0 0 26.88 23.04c12.8 0 22.613333-6.4 22.613333-15.786667s-4.266667-11.52-14.506667-13.653333l-21.333333-5.12c-17.066667-4.266667-24.32-11.52-24.32-23.893334s12.8-26.453333 34.133333-26.453333a31.573333 31.573333 0 0 1 35.413334 30.293333h-13.653334a19.626667 19.626667 0 0 0-22.613333-18.773333c-12.8 0-20.48 5.12-20.48 12.8s5.12 11.093333 17.066667 13.653333l14.933333 2.986667a42.666667 42.666667 0 0 1 20.906667 8.96 23.893333 23.893333 0 0 1 7.68 17.92c-0.426667 17.066667-14.506667 28.16-37.12 28.16z m88.746666 0h-14.506666l-32.426667-92.16h14.08l19.626667 59.733333 6.4 20.053333c0-9.386667 3.413333-12.8 5.546666-20.053333l19.2-59.733333h14.08z',
onclick: e => {
// console.log("🚀 ~ init ~ echartsData.value:", echartsData.value.options.series.map(item => item.data))
let list = echartsData.value.options.series?.map((item: any) => item.data)
let dataList = list[0]?.map((item: any, index: any) => {
let value = [item[0], item[1]]
list.forEach((item1: any, index1: any) => {
if (index1 > 0) {
value.push(item1 && item1[index] ? item1[index][1] : null)
}
})
return value
})
exportCSV(
echartsData.value.options.series.map((item: any) => item.name),
dataList,
'历史趋势.csv'
)
}
}
}
},
options: {
series: []
}
}
// console.log("🚀 ~ unitList.forEach ~ unitList:", unitList)
if (chartsList.value.length > 0) {
let yData: any = []
echartsData.value.yAxis = []
let setList = [...new Set(unitList)]
setList.forEach((item: any, index: any) => {
if (index > 2) {
echartsData.value.grid.right = (index - 1) * 80
}
yData.push([])
let right = {
position: 'right',
offset: (index - 1) * 80
}
// console.log("🚀 ~ unitList.forEach ~ right.index:", index)
echartsData.value.yAxis.push({
name: item,
yAxisIndex: index,
splitNumber: 5,
minInterval: 1,
splitLine: {
show: false
},
...(index > 0 ? right : null)
})
})
// console.log("🚀 ~ result.forEach ~ result:", result)
// '电压负序分量', '电压正序分量', '电压零序分量'
let ABCName = [
...new Set(
chartsList.value.map((item: any) => {
return item.anotherName == '电压负序分量'
? '电压不平衡'
: item.anotherName == '电压正序分量'
? '电压不平衡'
: item.anotherName == '电压零序分量'
? '电压不平衡'
: item.anotherName
})
)
]
// console.log("🚀 ~ .then ~ ABCName:", ABCName)
result.forEach((item: any, index: any) => {
let yMethodList: any = []
let ABCList = Object.values(
item.reduce((acc, item) => {
let key = ''
if (item.phase == null) {
key = item.anotherName
} else {
key = item.phase
}
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
)
// console.log("🚀 ~ ABCList.forEach ~ ABCList:", ABCList)
ABCList.forEach((kk: any) => {
let colorName = kk[0].phase?.charAt(0).toUpperCase()
let lineS = ABCName.findIndex(
item =>
item ===
(kk[0].anotherName == '电压负序分量'
? '电压不平衡'
: kk[0].anotherName == '电压正序分量'
? '电压不平衡'
: kk[0].anotherName == '电压零序分量'
? '电压不平衡'
: kk[0].anotherName)
)
let seriesList: any = []
kk.forEach((cc: any) => {
if (cc.statisticalData !== null) {
yData[setList.indexOf(kk[0].unit)].push(cc.statisticalData?.toFixed(2))
}
seriesList.push([cc.time, cc.statisticalData?.toFixed(2), cc.unit, lineStyle[lineS].type])
})
// console.log(kk);
echartsData.value.options.series.push({
name: kk[0].phase ? kk[0].phase + '相' + kk[0].anotherName : kk[0].anotherName,
type: 'line',
smooth: true,
color:
colorName == 'A' ? '#DAA520' : colorName == 'B' ? '#2E8B57' : colorName == 'C' ? '#A52a2a' : '',
symbol: 'none',
// data: seriesList,
data: timeControl.value ? completeTimeSeries(seriesList) : seriesList,
lineStyle: lineStyle[lineS],
yAxisIndex: setList.indexOf(kk[0].unit)
})
})
})
yData.forEach((item: any, index: any) => {
let [min, max] = yMethod(item)
echartsData.value.yAxis[index].min = min
echartsData.value.yAxis[index].max = max
})
// console.log("🚀 ~ result.forEach ~ echartsData.value:", echartsData.value)
}
loading.value = false
}
const setTimeControl = () => {
timeControl.value = !timeControl.value
setEchart()
}
const selectChange = (flag: boolean, height: any) => {
pageHeight.value = mainHeight(height * 1.6, 1.6)
}
//导出
const historyChart = ref()
const countData: any = ref([])
const countDataCopy: any = ref([])
//根据选择的指标处理谐波次数
const formatCountOptions = () => {
countData.value = []
if (searchForm.value.index.length != 0) {
searchForm.value.index.forEach((item: any, index: any) => {
countDataCopy.value.forEach((vv: any, vvs: any) => {
if (vv.index == item) {
countData.value.push(vv)
}
})
})
countData.value.map((item: any, key: any) => {
if (item.name == '谐波电流有效值') {
item.name = '谐波电流次数'
} else if (item.name == '谐波电压含有率') {
item.name = '谐波电压次数'
} else if (item.name == '间谐波电压含有率') {
item.name = '间谐波电压次数'
}
})
}
// setTimeout(() => {
// tableHeaderRef.value.computedSearchRow()
// }, 500)
}
// 判断下拉框是否存在
const onCountChange = (val: any, index: any) => {
if (val.length == 0) {
countData.value[index].count = countData.value[index].countOptions[0]
}
}
const flag = ref(true)
const onIndexChange = (val: any) => {
num.value += 1
let pp: any = []
indexOptions.value.forEach((item: any) => {
const filteredResult = val.filter(vv => item.id == vv)
if (filteredResult.length > 0) {
pp.push(filteredResult[0])
}
})
searchForm.value.index = pp
flag.value = true
formatCountOptions()
}
watch(
() => searchForm.value.index,
(val: any, oldval: any) => {},
{
deep: true,
immediate: true
}
)
const openDialog = async (row: any, field: any, title: any) => {
dialogVisible.value = true
trendRequestData.value = row
nextTick(() => {
// 默认当天
datePickerRef.value.setInterval(5)
datePickerRef.value.timeValue = [row.time, row.time]
initCode(field, title)
})
}
defineExpose({ getTrendRequest, openDialog })
</script>
<style lang="scss" scoped>
.history_header {
display: flex;
// flex-wrap: wrap;
#history_select {
width: 100%;
display: flex;
// justify-content: flex-start;
// overflow-x: auto;
height: auto;
flex-wrap: wrap;
.el-form-item {
flex: none !important;
// max-width: 380px;
}
.el-select {
margin-right: 10px;
}
}
// #history_select::-webkit-scrollbar {
// width: 0 !important;
// display: none !important;
// }
.history_searchBtn {
flex: 1;
display: flex;
justify-content: flex-end;
margin-top: 3px;
}
}
.history_chart {
width: 100%;
// flex: 1;
margin-top: 10px;
}
.history_count {
.el-select {
min-width: 100px;
}
}
</style>

View File

@@ -1,20 +1,21 @@
<template> <template>
<div> <div>
<!-- 综合评估详情 --> <!-- 指标越限详情 -->
<el-dialog draggable title="指标合格率统计" v-model="dialogVisible" append-to-body width="70%"> <el-dialog draggable title="指标越限详情" v-model="dialogVisible" append-to-body width="70%">
<TableHeader datePicker showExport :showReset="false"> <TableHeader datePicker showExport :showReset="false" ref="tableHeaderRef">
<template v-slot:select> <template v-slot:select>
<el-form-item label="监测点名称"> <el-form-item label="监测点">
<el-select <el-select
v-model="tableStore.table.params.searchValue" v-model="tableStore.table.params.lineId"
placeholder="请选择监测点名称" placeholder="请选择监测点"
style="width: 150px" style="width: 150px"
filterable
> >
<el-option <el-option
v-for="item in options" v-for="item in options"
:key="item.value" :key="item.lineId"
:label="item.label" :label="item.lineName"
:value="item.value" :value="item.lineId"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -23,43 +24,43 @@
<Table ref="tableRef" @cell-click="cellClickEvent" isGroup :height="height"></Table> <Table ref="tableRef" @cell-click="cellClickEvent" isGroup :height="height"></Table>
</el-dialog> </el-dialog>
<!-- 谐波电流谐波电压占有率 --> <!-- 谐波电流谐波电压占有率 -->
<HarmonicRatio ref="harmonicRatioRef" /> <HarmonicRatio ref="harmonicRatioRef" @close="onHarmonicRatioClose" v-if="dialogFlag" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, provide } from 'vue' import { ref, provide,nextTick } from 'vue'
import Table from '@/components/table/index.vue' import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout' import { mainHeight } from '@/utils/layout'
import HarmonicRatio from '@/components/cockpit/listOfMainMonitoringPoints/components/harmonicRatio.vue' import HarmonicRatio from '@/components/cockpit/indicatorFittingChart/components/harmonicRatio.vue'
import { cslineList } from '@/api/harmonic-boot/cockpit/cockpit'
const dialogVisible: any = ref(false) const dialogVisible: any = ref(false)
const harmonicRatioRef: any = ref(null) const harmonicRatioRef: any = ref(null)
const options = [
{ const dialogFlag = ref(false)
value: '35kV进线',
label: '35kV进线' const options = ref()
}
]
const height = mainHeight(0, 2).height as any const height = mainHeight(0, 2).height as any
const tableHeaderRef = ref()
const loop50 = (key: string) => { const loop50 = (key: string) => {
let list: any[] = [] let list: any[] = []
for (let i = 2; i < 51; i++) { for (let i = 2; i < 26; i++) {
list.push({ list.push({
title: i + '次', title: i + '次',
// field: key + i, field: key + i + 'Overtime',
field: 'flicker', width: '60',
width: '80', render: 'customTemplate',
// render: 'customTemplate', customTemplate: (row: any) => {
// customTemplate: (row: any) => { return `<span style='cursor: pointer;text-decoration: underline;'>${row[key + i + 'Overtime']}</span>`
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>` }
// }
}) })
} }
return list return list
} }
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0', url: '/cs-harmonic-boot/mainLine/statLimitRateDetails',
method: 'POST', method: 'POST',
publicHeight: 30, publicHeight: 30,
showPage: false, showPage: false,
@@ -76,82 +77,111 @@ const tableStore: any = new TableStore({
{ {
title: '日期', title: '日期',
field: 'time', field: 'time',
width: '150' width: '150',
sortable: true
}, },
{ {
title: '名称', title: '名称',
field: 'name', field: 'lineName',
width: '150' width: '150'
}, },
{ {
title: '闪变越限(分钟)', title: '越限(分钟)',
field: 'flicker', field: 'flickerOvertime',
width: '80', width: '90',
// render: 'customTemplate', render: 'customTemplate',
// customTemplate: (row: any) => { customTemplate: (row: any) => {
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
// } }
}, }, {
{ title: '电压偏差越限(分钟)',
title: '谐波电压越限(分钟)', field: 'uaberranceOvertime',
children: loop50('voltage') width: '100',
}, render: 'customTemplate',
{ customTemplate: (row: any) => {
title: '谐波电流越限(分钟)', return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
children: loop50('harmonicCurrent') }
}, },
{ {
title: '三相不平衡度越限(分钟)', title: '三相不平衡度越限(分钟)',
field: 'flicker', field: 'ubalanceOvertime',
width: '100' width: '100',
}, render: 'customTemplate',
{ customTemplate: (row: any) => {
title: '电压偏差越限(分钟)', return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>`
field: 'flicker',
width: '100'
},
{
title: '频率偏差越限(分钟)',
field: 'flicker',
width: '100'
}
],
beforeSearchFun: () => {},
loadCallback: () => {
tableStore.table.data = [
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
} }
] },
{
title: '谐波电压越限(分钟)',
children: loop50('uharm')
},
{
title: '谐波电流越限(分钟)',
children: loop50('iharm')
},
// {
// title: '频率偏差越限(分钟)',
// field: 'freqDevOvertime',
// width: '100',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>`
// }
// }
],
beforeSearchFun: () => {
},
loadCallback: () => {
} }
}) })
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore) provide('tableStore', tableStore)
const open = async (row: any) => { tableStore.table.params.sortBy = ''
tableStore.table.params.orderBy = ''
const open = async (row: any,searchBeginTime:any,searchEndTime:any,data:any=[]) => {
dialogVisible.value = true dialogVisible.value = true
tableStore.index() // initCSlineList()
options.value = data
tableStore.table.params.lineId = row.lineId
nextTick(() => {
tableHeaderRef.value.setTimeInterval([searchBeginTime, searchEndTime])
tableStore.table.params.searchBeginTime =searchBeginTime
tableStore.table.params.searchEndTime = searchEndTime
tableStore.index()
})
} }
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name' && column.field != 'time') { if (column.field != 'name' && column.field != 'time') {
harmonicRatioRef.value.openDialog(row) dialogFlag.value = true
dialogVisible.value = false
nextTick(() => {
harmonicRatioRef.value.openDialog(row,column.field,column.title.replace(/次/g, ""))
})
} }
} }
// 谐波弹窗关闭时的回调
const onHarmonicRatioClose = () => {
dialogFlag.value = false
// 重新打开指标越限详情弹窗
nextTick(() => {
dialogVisible.value = true
})
}
const initCSlineList = async () => {
// const res = await cslineList({})
// options.value = res.data
}
defineExpose({ open }) defineExpose({ open })
</script> </script>

View File

@@ -3,29 +3,29 @@
<!--主要监测点列表 --> <!--主要监测点列表 -->
<TableHeader <TableHeader
:showReset="false" :showReset="false"
:timeKeyList="prop.timeKey"
@selectChange="selectChange" @selectChange="selectChange"
v-if="fullscreen" v-if="fullscreen"
datePicker
:initialInterval="getInitialInterval()" ref="TableHeaderRef"
:initialTimeValue="getInitialTimeValue()"
> >
<template v-slot:select> <template v-slot:select>
<el-form-item label="关键"> <el-form-item label="关键字筛选">
<el-input v-model="tableStore.table.params.keywords" clearable placeholder="请输关键字" /> <el-input v-model="tableStore.table.params.keywords" clearable placeholder="请输入监测点名称" />
</el-form-item> </el-form-item>
</template> </template>
</TableHeader> </TableHeader>
<Table <Table
ref="tableRef" ref="tableRef"
@cell-click="cellClickEvent" @cell-click="cellClickEvent"
:height="`calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`" :height="`calc(${prop.height} - ${headerHeight}px + ${fullscreen ? -58 : 56}px )`"
></Table> ></Table>
<!-- 指标越限详情 --> <!-- 指标越限详情 -->
<OverLimitDetails ref="OverLimitDetailsRef" /> <OverLimitDetails ref="OverLimitDetailsRef" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue' import { ref, onMounted, provide, reactive, watch, nextTick } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue' import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
@@ -35,12 +35,13 @@ import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache' import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const OverLimitDetailsRef = ref() const OverLimitDetailsRef = ref()
const headerHeight = ref(57) const headerHeight = ref(57)
@@ -48,16 +49,7 @@ const headerHeight = ref(57)
const route = useRoute() const route = useRoute()
const timeCacheStore = useTimeCacheStore() const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const TableHeaderRef = ref()
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示 // 计算是否全屏展示
const fullscreen = computed(() => { const fullscreen = computed(() => {
@@ -71,11 +63,20 @@ const fullscreen = computed(() => {
} }
}) })
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// if (datePickerValue && datePickerValue.timeValue) {
// // 更新时间参数
// tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
// tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
// }
}
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/harmonic-boot/mainLine/list', url: '/cs-harmonic-boot/mainLine/list',
method: 'POST', method: 'POST',
showPage: false, showPage: fullscreen.value ? true : false,
exportName: '主要监测点列表', exportName: '主要监测点列表',
column: [ column: [
{ {
@@ -99,32 +100,24 @@ const tableStore: any = new TableStore({
{ {
title: '监测对象类型', title: '监测对象类型',
field: 'objType', field: 'objType',
minWidth: '90' minWidth: '90',
formatter: (row: any) => {
return row.cellValue || '/'
}
}, },
{ {
title: '是否治理', title: '是否治理',
field: 'govern', field: 'govern',
minWidth: '70' minWidth: '80',
formatter: (row: any) => {
return row.cellValue || '/'
}
}, },
{ title: '主要存在的电能质量问题', field: 'problems', minWidth: '150' } { title: '主要存在的电能质量问题', field: 'problems', minWidth: '150', showOverflow: true }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
@@ -139,49 +132,48 @@ provide('tableStore', tableStore)
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
if (column.field == 'lineName') { if (column.field == 'lineName') {
console.log(row)
OverLimitDetailsRef.value.open(row) let time = getTimeOfTheMonth('3');
OverLimitDetailsRef.value.open(
row,
time[0],
time[1],
tableStore.table.data
)
} }
} }
// 获取缓存的初始值 const setTime = () => {
const getInitialInterval = (): 3 => { // const time = getTime(
if (fullscreen.value) { // (TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
const cached = timeCacheStore.getCache(route.path); // prop.timeKey,
if (cached && cached.interval !== undefined) { // fullscreen.value
return cached.interval as 3; // 强制断言为 3 或根据实际类型调整 // ? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
} // : prop.timeValue
} // )
return 3; // 明确返回字面量类型 3
}; // if (Array.isArray(time)) {
// 外部总的时间值 // tableStore.table.params.searchBeginTime = time[0]
const getInitialTimeValue = () => { // tableStore.table.params.searchEndTime = time[1]
if (fullscreen.value) { // // TableHeaderRef.value?.setInterval(time[2] - 0)
const cached = timeCacheStore.getCache(route.path) // // TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
if (cached && cached.timeValue) { // } else {
return cached.timeValue // console.warn('获取时间失败time 不是一个有效数组')
} // }
}
return prop.timeValue // 使用传入的默认值
} }
// 在组件挂载时设置缓存值到 DatePicker // 在组件挂载时设置缓存值到 DatePicker
onMounted(() => { onMounted(() => {
tableStore.index() tableStore.index()
}) })
watch( watch(
() => prop.timeKey, () => prop.timeValue,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )

View File

@@ -82,9 +82,9 @@ import { getTimeOfTheMonth } from '@/utils/formatTime'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
const prop = defineProps({ const prop = defineProps({
width: { type: String }, width: { type: [String, Number]},
height: { type: String }, height: { type: [String, Number]},
timeKey: { type: String }, timeKey: { type: [String, Number]},
timeValue: { type: Object } timeValue: { type: Object }
}) })

View File

@@ -69,19 +69,11 @@ const tableStore: any = new TableStore({
width: '150' width: '150'
}, },
{ {
title: '闪变越限(分钟)', title: '长时闪变越限(分钟)',
field: 'flicker', field: 'flicker',
width: '80' width: '80'
}, },
{ {
title: '谐波电压越限(分钟)',
children: loop50('voltage')
},
{
title: '谐波电流越限(分钟)',
children: loop50('harmonicCurrent')
},
{
title: '三相不平衡度越限(分钟)', title: '三相不平衡度越限(分钟)',
field: 'flicker', field: 'flicker',
width: '100' width: '100'
@@ -95,30 +87,20 @@ const tableStore: any = new TableStore({
title: '频率偏差越限(分钟)', title: '频率偏差越限(分钟)',
field: 'flicker', field: 'flicker',
width: '100' width: '100'
} },
{
title: '谐波电压越限(分钟)',
children: loop50('voltage')
},
{
title: '谐波电流越限(分钟)',
children: loop50('harmonicCurrent')
},
], ],
beforeSearchFun: () => {}, beforeSearchFun: () => {},
loadCallback: () => { loadCallback: () => {
tableStore.table.data = [
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0',
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0',
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0',
},
]
} }
}) })

View File

@@ -84,7 +84,7 @@ const tableStore: any = new TableStore({
width: '150' width: '150'
}, },
{ {
title: '闪变越限(分钟)', title: '长时闪变越限(分钟)',
field: 'flicker', field: 'flicker',
width: '80', width: '80',
render: 'customTemplate', render: 'customTemplate',
@@ -92,15 +92,7 @@ const tableStore: any = new TableStore({
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>`
} }
}, },
{ {
title: '谐波电压越限(分钟)',
children: loop50('voltage')
},
{
title: '谐波电流越限(分钟)',
children: loop50('harmonicCurrent')
},
{
title: '三相不平衡度越限(分钟)', title: '三相不平衡度越限(分钟)',
field: 'flicker', field: 'flicker',
width: '100' width: '100'
@@ -114,7 +106,16 @@ const tableStore: any = new TableStore({
title: '频率偏差越限(分钟)', title: '频率偏差越限(分钟)',
field: 'flicker', field: 'flicker',
width: '100' width: '100'
} },
{
title: '谐波电压越限(分钟)',
children: loop50('voltage')
},
{
title: '谐波电流越限(分钟)',
children: loop50('harmonicCurrent')
},
], ],
beforeSearchFun: () => {}, beforeSearchFun: () => {},
loadCallback: () => { loadCallback: () => {

View File

@@ -1,101 +1,129 @@
<template> <template>
<div> <div>
<!--指标拟合图 --> <!--指标拟合图 -->
<TableHeader :showReset="false" datePicker @selectChange="selectChange" v-if="fullscreen"> <TableHeader
datePicker
@selectChange="selectChange"
v-if="fullscreen"
ref="TableHeaderRef"
:timeKeyList="prop.timeKey"
>
<template v-slot:select> <template v-slot:select>
<el-form-item label="监测点">
<el-select filterable v-model="tableStore.table.params.lineId" placeholder="请选择监测点" clearable>
<el-option
v-for="item in lineList"
:key="item.lineId"
:label="item.name"
:value="item.lineId"
/>
</el-select>
</el-form-item>
<el-form-item label="用户功率"> <el-form-item label="用户功率">
<el-select <el-select
filterable
v-model="tableStore.table.params.power" v-model="tableStore.table.params.power"
placeholder="请选择用户功率" placeholder="请选择用户功率"
clearable clearable
style="width: 130px"
> >
<el-option <el-option v-for="item in powerList" :key="item.id" :label="item.name" :value="item.id" />
v-for="item in powerList" </el-select>
:key="item.value" </el-form-item>
:label="item.label" <el-form-item label="统计类型">
:value="item.value" <el-select
/> style="min-width: 120px !important"
placeholder="请选择"
v-model="tableStore.table.params.valueType"
filterable
>
<el-option value="max" label="最大值"></el-option>
<el-option value="min" label="最小值"></el-option>
<el-option value="avg" label="平均值"></el-option>
<el-option value="cp95" label="cp95"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="电能质量指标"> <el-form-item label="电能质量指标">
<el-select <el-select
filterable
v-model="tableStore.table.params.indicator" v-model="tableStore.table.params.indicator"
placeholder="请选择电能质量指标" placeholder="请选择电能质量指标"
clearable clearable
style="width: 130px"
> >
<el-option <el-option v-for="item in indicatorList" :key="item.id" :label="item.name" :value="item.id" />
v-for="item in indicatorList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="越限情况"> <el-form-item>
<el-select <div v-if="shouldShowHarmonicCount()" style="display: flex; color: var(--el-text-color-regular)">
v-model="tableStore.table.params.exceedingTheLimit" <span style="width: 160px">{{ getHarmonicTypeName() }}谐波次数</span>
placeholder="请选择越限情况" <el-select
clearable v-model="tableStore.table.params.harmonicCount"
style="width: 90px" placeholder="请选择谐波次数"
> style="min-width: 80px !important"
<el-option filterable
v-for="item in exceedingTheLimitList" >
:key="item.value" <el-option
:label="item.label" v-for="num in harmonicCountOptions"
:value="item.value" :key="num"
/> :label="num"
</el-select> :value="num"
></el-option>
</el-select>
</div>
</el-form-item> </el-form-item>
</template> </template>
</TableHeader> </TableHeader>
<my-echart <div v-loading="tableStore.table.loading">
class="tall" <my-echart
:options="echartList" class="tall"
:style="{ v-if="lineShow"
width: prop.width, :options="echartList"
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )` :style="{
}" width: prop.width,
/> height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`
}"
/>
<el-empty
v-else
description="暂无监测点"
:style="{
width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}"
/>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h, computed } from 'vue' import { ref, onMounted, provide, reactive, watch, h, computed, nextTick } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { useRoute } from 'vue-router' import { cslineList, fittingData } from '@/api/harmonic-boot/cockpit/cockpit'
import { useTimeCacheStore } from '@/stores/timeCache' import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { ElMessage } from 'element-plus'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const TableHeaderRef = ref()
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const config = useConfig() const config = useConfig()
const powerList: any = ref([
{ const lineList: any = ref()
label: '三相总有功功率',
value: '1' const powerList: any = ref()
},
{ const chartsList = ref<any>([])
label: '三相总无功功率', const lineShow = ref(true)
value: '2'
}
])
// 计算是否全屏展示 // 计算是否全屏展示
const fullscreen = computed(() => { const fullscreen = computed(() => {
const w = Number(prop.w) const w = Number(prop.w)
@@ -117,191 +145,351 @@ const exceedingTheLimitList: any = ref([
value: '0' value: '0'
} }
]) ])
const indicatorList: any = ref([
{
label: '谐波电压总畸变率',
value: '1'
},
{
label: '各次谐波电压',
value: '2'
},
{
label: '各次谐波电压',
value: '3'
},
{
label: '三相电压不平衡',
value: '4'
}
])
const echartList = ref({
title: {
text: '谐波电压总畸变率越限与功率负荷曲线拟合图'
},
xAxis: { const indicatorList = ref()
type: 'time',
axisLabel: { const initLineList = async () => {
formatter: { cslineList({}).then(res => {
day: '{MM}-{dd}', if (res.data.length == 0) {
month: '{MM}', lineShow.value = false
year: '{yyyy}' return (tableStore.table.loading = false)
}
} }
}, lineShow.value = true
lineList.value = res.data
tableStore.table.params.lineId = lineList.value[0].lineId
initCode()
})
}
yAxis: [{}, {}], const echartList = ref()
grid: {
left: '10px', const headerHeight = ref(57)
right: '20px' const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
}, headerHeight.value = height
options: { if (datePickerValue && datePickerValue.timeValue) {
series: [ // 更新时间参数
{ tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
// name: '暂降次数', tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
type: 'bar', }
name: '有功功率', }
data: [
['2025-10-16 07:00:00', 10], const setEchart = () => {
['2025-10-16 07:15:00', 10], // 获取当前选择的功率和指标名称
['2025-10-16 07:30:00', 10], const powerName = powerList.value?.find((item: any) => item.id === tableStore.table.params.power)?.name || '功率'
['2025-10-16 07:45:00', 10], const indicatorName =
['2025-10-16 08:00:00', 30], indicatorList.value?.find((item: any) => item.id === tableStore.table.params.indicator)?.name || '电能质量指标'
['2025-10-16 08:15:00', 50],
['2025-10-16 08:30:00', 60], echartList.value = {
['2025-10-16 08:45:00', 70], title: {
['2025-10-16 09:00:00', 100], text: `${indicatorName}${powerName}负荷曲线拟合图`
['2025-10-16 09:15:00', 120], },
['2025-10-16 09:30:00', 130], tooltip: {
['2025-10-16 09:45:00', 140], trigger: 'axis',
['2025-10-16 10:00:00', 160], formatter: function (params: any) {
['2025-10-16 10:15:00', 160], let result = params[0].axisValueLabel
['2025-10-16 10:30:00', 130], params.forEach((item: any) => {
['2025-10-16 10:45:00', 120], if (item.seriesName === indicatorName) {
['2025-10-16 11:00:00', 140], // 对于电能质量指标格式化Y轴值显示
['2025-10-16 11:15:00', 80], let valueText = ''
['2025-10-16 11:30:00', 70], if (item.value[1] == 0) {
['2025-10-16 11:45:00', 90], valueText = '不越限'
['2025-10-16 12:00:00', 60], } else if (item.value[1] == 1) {
['2025-10-16 12:15:00', 60], valueText = '越限'
['2025-10-16 12:30:00', 60], } else {
['2025-10-16 12:45:00', 60] valueText = item.value[1]
], }
itemStyle: { result += `<br/>${item.marker}${item.seriesName}: ${valueText}`
normal: { } else {
//这里是颜色 // 对于功率数据,正常显示数值
color: function (params: any) { result += `<br/>${item.marker}${item.seriesName}: ${item.value[1]} ${item.value[2]}`
if (params.value[1] == 0 || params.value[1] == 3.14159) { }
return '#ccc' })
} else { return result
return config.layout.elementUiPrimary[0] }
},
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: [
{},
indicatorName
? {
min: 0,
max: 1,
axisLabel: {
formatter: function (value: number) {
if (value === 0) {
return '不越限'
} else if (value === 1) {
return '越限'
}
return value
}
}
}
: {}
],
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
type: 'bar',
name: powerName, // 动态设置功率名称
data: [],
itemStyle: {
normal: {
color: function (params: any) {
if (params.value[1] == 0 || params.value[1] == 3.14159) {
return '#ccc'
} else {
return config.layout.elementUiPrimary[0]
}
} }
} }
} },
yAxisIndex: 0
}, },
yAxisIndex: 0 {
}, name: indicatorName, // 动态设置指标名称
{ type: 'line',
name: '谐波总畸变率', step: 'end',
type: 'line', showSymbol: false,
showSymbol: false, // smooth: true,
smooth: true, data: [],
data: [ yAxisIndex: 1
['2025-10-16 07:00:00', 0], }
['2025-10-16 07:15:00', 0], ]
['2025-10-16 07:30:00', 0], }
['2025-10-16 07:45:00', 0],
['2025-10-16 08:00:00', 0],
['2025-10-16 08:15:00', 0.1],
['2025-10-16 08:30:00', 0.1],
['2025-10-16 08:45:00', 0.1],
['2025-10-16 09:00:00', 1],
['2025-10-16 09:15:00', 1],
['2025-10-16 09:30:00', 1],
['2025-10-16 09:45:00', 1],
['2025-10-16 10:00:00', 0.8],
['2025-10-16 10:15:00', 0.8],
['2025-10-16 10:30:00', 0.8],
['2025-10-16 10:45:00', 0.8],
['2025-10-16 11:00:00', 0.8],
['2025-10-16 11:15:00', 0.1],
['2025-10-16 11:30:00', 0.1],
['2025-10-16 11:45:00', 0.1],
['2025-10-16 12:00:00', 0],
['2025-10-16 12:15:00', 0],
['2025-10-16 12:30:00', 0],
['2025-10-16 12:45:00', 0]
],
yAxisIndex: 1
}
]
} }
})
const headerHeight = ref(57)
const selectChange = (showSelect: any, height: any) => {
headerHeight.value = height
}
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
try {
// 用户功率数据和电能质量数据
let powerData: any[] = []
let qualityData: any[] = []
chartsList.value.forEach((item: any) => {
// 根据统计项ID判断是功率数据还是电能质量数据
if (item.statisticalIndex === tableStore.table.params.power) {
powerData.push(item)
} else if (item.statisticalIndex === tableStore.table.params.indicator) {
qualityData.push(item)
}
})
// 处理功率数据
const processedPowerData = powerData.map((item: any) => {
return [
item.time,
item.statisticalData !== null && item.statisticalData !== undefined
? parseFloat(item.statisticalData.toFixed(2))
: null,
item.unit
]
})
// 处理电能质量数据
const processedQualityData = qualityData.map((item: any) => {
return [
item.time,
item.statisticalData !== null && item.statisticalData !== undefined
? parseFloat(item.statisticalData.toFixed(2))
: null,
item.unit
]
})
// 检查是否有有效数据
const hasPowerData = processedPowerData.length > 0 && processedPowerData.some(item => item[1] !== null)
const hasQualityData = processedQualityData.length > 0 && processedQualityData.some(item => item[1] !== null)
// 更新图表配置
echartList.value.options.series[0].data = processedPowerData
echartList.value.options.series[1].data = processedQualityData
} catch (error) {
console.error('处理图表数据时出错:', error)
}
}
const initCode = () => {
queryByCode('steady_state_limit_fitting').then(res => {
queryCsDictTree(res.data.id).then(item => {
powerList.value = item.data.filter((item: any) => {
return item.name == '三相总无功功率' || item.name == '三相总有功功率'
})
indicatorList.value = item.data.filter((item: any) => {
return item.name != '三相总无功功率' && item.name != '三相总有功功率'
})
tableStore.table.params.power = powerList.value[0].id
tableStore.table.params.indicator = indicatorList.value[0].id
nextTick(() => {
// setTime()
tableStore.index()
})
})
})
}
const tableStore: any = new TableStore({
url: '/cs-device-boot/csGroup/fittingData',
method: 'POST',
showPage: false, showPage: false,
exportName: '主要监测点列表', exportName: '主要监测点列表',
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime // 只有当 lineList 已加载且有数据时才设置默认 lineId
if (!tableStore.table.params.lineId && lineList.value && lineList.value.length > 0) {
if (fullscreen.value) { tableStore.table.params.lineId = lineList.value[0].lineId
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
} }
// 如果缓存中没有则使用默认值 // 构建请求参数 lists
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0] let lists: any = []
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1] // 处理用户功率指标
const selectedPower = powerList.value?.find((item: any) => item.id === tableStore.table.params.power)
if (selectedPower) {
lists.push({
statisticalId: tableStore.table.params.power,
valueType: tableStore.table.params.valueType
})
}
// 处理电能质量指标
const selectedIndicator = indicatorList.value?.find(
(item: any) => item.id === tableStore.table.params.indicator
)
if (selectedIndicator) {
let frequencys = ''
if (selectedIndicator.name.includes('谐波含有率')) {
frequencys = tableStore.table.params.harmonicCount
}
lists.push({
statisticalId: tableStore.table.params.indicator,
frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : ''
})
}
// 将 lists 添加到请求参数中
tableStore.table.params.list = lists
tableStore.table.params.dataLevel = 'Primary'
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
// 数据加载完成后的处理
if (tableStore.table.data) {
chartsList.value = JSON.parse(JSON.stringify(tableStore.table.data))
setEchart()
}
} }
}) })
const tableRef = ref() const tableRef = ref()
provide('tableRef', tableRef) provide('tableRef', tableRef)
tableStore.table.params.power = '1'
tableStore.table.params.indicator = '1' tableStore.table.params.indicator = '1'
tableStore.table.params.exceedingTheLimit = '1' tableStore.table.params.exceedingTheLimit = '1'
tableStore.table.params.valueType = 'avg'
tableStore.table.params.searchValue = '' tableStore.table.params.searchValue = ''
provide('tableStore', tableStore) provide('tableStore', tableStore)
onMounted(() => { // 添加谐波次数选项2-50
tableStore.index() const harmonicCountOptions = ref(Array.from({ length: 49 }, (_, i) => i + 2))
// 判断是否应该显示谐波次数选择框
const shouldShowHarmonicCount = () => {
if (!tableStore.table.params.indicator || !indicatorList.value) return false
const currentIndicator = indicatorList.value.find((item: any) => item.id === tableStore.table.params.indicator)
return (
currentIndicator &&
(currentIndicator.name.includes('电压谐波含有率') || currentIndicator.name.includes('电流谐波含有率'))
)
}
// 获取谐波类型名称
const getHarmonicTypeName = () => {
const currentIndicator = indicatorList.value.find((item: any) => item.id === tableStore.table.params.indicator)
if (currentIndicator) {
if (currentIndicator.name.includes('电压谐波含有率')) {
return '电压'
} else if (currentIndicator.name.includes('电流谐波含有率')) {
return '电流'
}
}
return ''
}
// 监听指标变化,当指标变化时重置谐波次数
watch(
() => tableStore.table.params.indicator,
newVal => {
if (shouldShowHarmonicCount()) {
// 如果之前没有设置过谐波次数则默认设置为2
if (!tableStore.table.params.harmonicCount) {
tableStore.table.params.harmonicCount = 2
}
} else {
// 如果不是谐波含有率指标,则清除谐波次数设置
tableStore.table.params.harmonicCount = ''
}
}
)
onMounted(async () => {
await initLineList()
}) })
watch( watch(
() => prop.timeKey, () => prop.timeKey,
val => { val => {
tableStore.index() tableStore.index()
} }
) )
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )
const addMenu = () => {}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-select) { // :deep(.el-select) {
min-width: 80px; // min-width: 80px;
} // }
</style> </style>

View File

@@ -0,0 +1,201 @@
<template>
<div>
<!-- 指标越限详情 -->
<el-dialog draggable title="指标越限详情" v-model="dialogVisible" append-to-body width="70%">
<TableHeader datePicker showExport :showReset="false" ref="tableHeaderRef">
<template v-slot:select>
<el-form-item label="监测点">
<el-select
v-model="tableStore.table.params.lineId"
placeholder="请选择监测点"
style="width: 150px"
filterable
>
<el-option
v-for="item in options"
:key="item.lineId"
:label="item.lineName"
:value="item.lineId"
/>
</el-select>
</el-form-item>
</template>
</TableHeader>
<Table ref="tableRef" @cell-click="cellClickEvent" isGroup :height="height"></Table>
</el-dialog>
<!-- 谐波电流谐波电压占有率 -->
<HarmonicRatio ref="harmonicRatioRef" v-if="dialogFlag" @close="onHarmonicRatioClose" />
</div>
</template>
<script setup lang="ts">
import { ref, provide,nextTick } from 'vue'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout'
import HarmonicRatio from '@/components/cockpit/gridSideStatistics/components/harmonicRatio.vue'
import { cslineList ,governLineList} from '@/api/harmonic-boot/cockpit/cockpit'
const dialogVisible: any = ref(false)
const harmonicRatioRef: any = ref(null)
const options = ref()
const height = mainHeight(0, 2).height as any
const tableHeaderRef = ref()
const dialogFlag = ref(false)
const loop50 = (key: string) => {
let list: any[] = []
for (let i = 2; i < 26; i++) {
list.push({
title: i + '次',
field: key + i + 'Overtime',
width: '60',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row[key + i + 'Overtime']}</span>`
}
})
}
return list
}
const tableStore: any = new TableStore({
url: '/cs-harmonic-boot/totalLimitStatistics/details',
method: 'POST',
publicHeight: 30,
showPage: false,
exportName: '每日越限占比统计',
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '日期',
field: 'time',
width: '150',
sortable: true
},
{
title: '名称',
field: 'lineName',
width: '150'
},
{
title: '长时闪变越限(%)',
field: 'flickerOvertime',
width: '90',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
}
},
{
title: '电压偏差越限(%)',
field: 'voltageDevOvertime',
width: '100',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
}
},
{
title: '三相不平衡度越限(%)',
field: 'ubalanceOvertime',
width: '100',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>`
}
},
{
title: '谐波电压越限(%)',
children: loop50('uharm')
},
{
title: '谐波电流越限(%)',
children: loop50('iharm')
},
// {
// title: '频率偏差越限(%)',
// field: 'freqDevOvertime',
// width: '100',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>`
// }
// }
],
beforeSearchFun: () => {
},
loadCallback: () => {
}
})
provide('tableStore', tableStore)
tableStore.table.params.sortBy = ''
tableStore.table.params.orderBy = ''
const time:any=ref([])
const open = async (row: any,searchBeginTime:any,searchEndTime:any) => {
dialogVisible.value = true
time.value=[searchBeginTime,searchEndTime]
initCSlineList()
tableStore.table.params.lineId = row.lineId
nextTick(() => {
tableHeaderRef.value.setTimeInterval([searchBeginTime, searchEndTime])
tableStore.table.params.searchBeginTime =searchBeginTime
tableStore.table.params.searchEndTime = searchEndTime
tableStore.index()
})
}
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name' && column.field != 'time') {
dialogFlag.value = true
dialogVisible.value = false
nextTick(() => {
harmonicRatioRef.value.openDialog(row,column.field,column.title.replace(/次/g, ""))
})
}
}
// 谐波弹窗关闭时的回调
const onHarmonicRatioClose = () => {
dialogFlag.value = false
// 重新打开指标越限详情弹窗
nextTick(() => {
dialogVisible.value = true
})
}
const initCSlineList = async () => {
// const res = await cslineList({})
const res = await governLineList({
endTime:time.value[1],
keywords:"",
pageNum:1,
pageSize:10000,
searchBeginTime:time.value[0],
searchEndTime:time.value[1],
startTime:time.value[0],
timeFlag:1
})
options.value = res.data.records
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@@ -1,47 +1,108 @@
<template> <template>
<div> <div>
<!--监测点列表 --> <!-- 监测点列表 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
<Table ref="tableRef" @cell-click="cellClickEvent" :height="`calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`"></Table> ref="TableHeaderRef"
:showReset="false"
@selectChange="selectChange"
v-if="fullscreen"
:timeKeyList="prop.timeKey"
>
<template #select>
<el-form-item label="关键字筛选">
<el-input
maxlength="32"
show-word-limit
style="width: 240px"
v-model.trim="tableStore.table.params.searchValue"
clearable
placeholder="请输入监测点名称"
/>
</el-form-item>
</template>
</TableHeader>
<Table
ref="tableRef"
@cell-click="cellClickEvent"
:height="`calc(${prop.height} - ${headerHeight}px + ${fullscreen ? -58 : 56}px )`"
></Table>
<!-- 指标越限详情 --> <!-- 指标越限详情 -->
<OverLimitDetails ref="OverLimitDetailsRef" /> <OverLimitDetails ref="OverLimitDetailsRef" />
<!-- 上传对话框 -->
<el-dialog
v-model="uploadDialogVisible"
title="上传报告"
append-to-body
width="500px"
@closed="handleDialogClosed"
>
<el-upload
ref="uploadRef"
class="upload-demo"
action=""
accept=".doc,.docx,.PDF"
:on-change="handleChange"
:before-upload="beforeUpload"
:limit="1"
:auto-upload="false"
:on-exceed="handleExceed"
:on-remove="handleRemove"
:file-list="fileList"
>
<el-button type="primary">点击上传</el-button>
<template #tip>
<div class="el-upload__tip">请上传Word或PDF文件</div>
</template>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="uploadDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleUpload">确定</el-button>
</span>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue' import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue' import Table from '@/components/table/index.vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { getTimeOfTheMonth } from '@/utils/formatTime' import OverLimitDetails from '@/components/cockpit/monitoringPointList/components/overLimitDetails.vue'
import { ElTag } from 'element-plus'
import OverLimitDetails from '@/components/cockpit/listOfMainMonitoringPoints/components/overLimitDetails.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router' import { uploadReport, getReportUrl } from '@/api/harmonic-boot/cockpit/cockpit'
import { useTimeCacheStore } from '@/stores/timeCache' import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const route = useRoute() const TableHeaderRef = ref()
const timeCacheStore = useTimeCacheStore()
// 上传相关
const uploadDialogVisible = ref(false)
const currentUploadRow = ref<any>(null)
const uploadRef = ref()
const fileList = ref([])
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 // if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // // 更新时间参数
// 更新表格参数 // tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] // tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1] // }
}
} }
// 计算是否全屏展示 // 计算是否全屏展示
@@ -58,11 +119,10 @@ const fullscreen = computed(() => {
const OverLimitDetailsRef = ref() const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0', url: '/cs-device-boot/csline/getSensitiveUserLineList',
method: 'POST', method: 'POST',
showPage: fullscreen.value ? true : false,
showPage: false, exportName: '监测点列表',
exportName: '主要监测点列表',
column: [ column: [
{ {
field: 'index', field: 'index',
@@ -73,120 +133,318 @@ const tableStore: any = new TableStore({
} }
}, },
{ {
title: '治理对象名称', title: '监测点名称',
field: 'name', field: 'lineName',
minWidth: '80' minWidth: '120',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.lineName}</span>`
}
},
{
title: '监测类型',
field: 'position',
minWidth: '80',
formatter: (row: any) => {
return row.cellValue || '/'
}
},
// {
// title: '监测点状态',
// field: 'runStatus',
// minWidth: '90',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return `<span style='color: ${row.runStatus === '中断' ? '#FF0000' : ''}'>${row.runStatus==null?'/':row.runStatus}</span>`
// }
// },
{
title: '监测点状态',
field: 'runStatus',
render: 'tag',
width: 100,
custom: {
停运: 'danger',
退运: 'danger',
运行: 'success',
在线: 'success',
中断: 'warning',
离线: 'danger',
检修: 'warning',
调试: 'warning',
null: 'info'
},
replaceValue: {
运行: '运行',
在线: '在线',
退运: '退运',
停运: '停运',
中断: '中断',
检修: '检修',
离线: '离线',
调试: '调试',
null: '/'
}
},
{
title: '治理对象',
field: 'sensitiveUser',
minWidth: '90',
formatter: (row: any) => {
return row.cellValue || '/'
}
}, },
{ {
title: '电压等级', title: '电压等级',
field: 'type', field: 'volGrade',
minWidth: '70' minWidth: '80',
formatter: (row: any) => {
return row.cellValue == 0 ? '/' : row.cellValue + 'kV' || '/'
}
}, },
{ {
title: '治理设备详情', title: '是否治理',
field: 'type1', field: 'govern',
minWidth: '70' minWidth: '80',
formatter: (row: any) => {
return row.cellValue || '/'
}
}, },
{ {
title: '治理前报告', title: '最新数据时间',
field: 'type2', field: 'latestTime',
minWidth: '80', minWidth: '140',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;' onclick="handleReportClick('${row.name}')">${row.type2}</span>` if (row.latestTime) {
return `<span>${row.latestTime}</span>`
} else {
return `<span>/</span>`
}
} }
}, },
{ title: '监测点名称', field: 'type3', minWidth: '70' }, // {
{ title: '监测类型', field: 'type4', minWidth: '60' }, // title: '报告',
// field: 'reportFilePath',
// minWidth: '120',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return row.reportFilePath == null
// ? '/'
// : `<span style='cursor: pointer;text-decoration: underline;'>${row.reportFilePath
// .split('/')
// .pop()}</span>`
// }
// },
{ {
title: '监测点状态', title: '报告',
field: 'type5', field: 'reportFilePath',
minWidth: '60', minWidth: '120',
render: 'customTemplate', formatter: (row: any) => {
customTemplate: (row: any) => { return row.cellValue == null ? '/' : row.cellValue.split('/').pop()
return `<span style='color: ${row.type5 === '中断' ? '#FF0000' : ''}'>${row.type5}</span>`
} }
}, },
{ title: '最新数据时间', field: 'type6', minWidth: '140' } {
title: '操作',
fixed: 'right',
width: 150,
render: 'buttons',
buttons: [
{
name: 'productSetting',
title: '上传报告',
type: 'primary',
icon: 'el-icon-upload',
render: 'basicButton',
click: row => {
uploadReportRow(row)
},
disabled: row => {
return row.reportFilePath
}
},
{
name: 'productSetting',
title: '下载报告',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton',
click: row => {
downloadTheReport(row.lineId, row.reportFilePath)
},
disabled: row => {
return row.reportFilePath == null || row.reportFilePath.length == 0
}
},
{
name: 'productSetting',
title: '重新上传',
type: 'primary',
icon: 'el-icon-upload',
render: 'basicButton',
click: row => {
uploadReportRow(row)
},
disabled: row => {
return row.reportFilePath == null || row.reportFilePath.length == 0
}
}
]
}
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.data = [
{
name: '1#变压器',
type: '0.38kV',
type1: '250A APF',
type2: '报告.doc',
type3: '1#变压器 电网侧',
type4: '电网侧',
type5: '运行',
type6: '2025-04-11 18:16:00'
},
{
name: '1#变压器',
type: '0.38kV',
type1: '250A APF',
type2: '报告.doc',
type3: '1#变压器 负载侧',
type4: '负载侧',
type5: '运行',
type6: '2025-04-11 18:16:00'
},
{
name: '2#变压器',
type: '0.38kV',
type1: '100A SVG',
type2: '/',
type3: '1#变压器 电网侧',
type4: '电网侧',
type5: '运行',
type6: '2025-04-11 18:16:00'
},
{
name: '2#变压器',
type: '0.38kV',
type1: '100A SVG',
type2: '/',
type3: '1#变压器 负载侧',
type4: '负载侧',
type5: '中断',
type6: '2025-04-11 18:16:00'
}
]
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
} }
}) })
const tableRef = ref() const tableRef = ref()
provide('tableRef', tableRef) provide('tableRef', tableRef)
tableStore.table.params.type = '' tableStore.table.params.keywords = ''
tableStore.table.params.searchValue = '' tableStore.table.params.searchValue = ''
provide('tableStore', tableStore) provide('tableStore', tableStore)
const setTime = () => {
// const time = getTime(
// (TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
// prop.timeKey,
// fullscreen.value
// ? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
// : prop.timeValue
// )
// if (Array.isArray(time)) {
// tableStore.table.params.searchBeginTime = time[0]
// tableStore.table.params.searchEndTime = time[1]
// TableHeaderRef.value?.setInterval(time[2] - 0)
// TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
// } else {
// console.warn('获取时间失败time 不是一个有效数组')
// }
}
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
if (column.field == 'name') { if (column.field == 'lineName') {
console.log(row) OverLimitDetailsRef.value.open(
OverLimitDetailsRef.value.open(row) row,
tableStore.table.params.searchBeginTime || prop.timeValue?.[0],
tableStore.table.params.searchEndTime || prop.timeValue?.[1]
)
}
}
// 下载报告
const downloadTheReport = (lineId: string, name: string) => {
getReportUrl({ lineId: lineId }).then((res: any) => {
forceDownloadPdf(res.data, name.split('/').pop() || '')
})
}
const forceDownloadPdf = async (pdfUrl, fileName = '文件.pdf') => {
try {
// 1. 请求 PDF 并转为 Blob关键绕开浏览器直接解析
const response = await fetch(pdfUrl, {
method: 'GET'
// 若需要鉴权,添加请求头(如 token
})
// 校验响应是否成功
if (!response.ok) throw new Error(`请求失败:${response.status}`)
// 2. 将响应转为 Blob指定类型为 PDF确保兼容性
const blob = await response.blob()
const pdfBlob = new Blob([blob], { type: 'application/pdf' })
// 3. 创建临时 URL 并触发下载
const blobUrl = URL.createObjectURL(pdfBlob)
const a = document.createElement('a')
a.href = blobUrl
a.download = fileName // 此时 Blob URL 是同源的download 必生效
a.style.display = 'none'
document.body.appendChild(a)
a.click() // 触发下载
// 4. 清理资源(避免内存泄漏)
document.body.removeChild(a)
URL.revokeObjectURL(blobUrl)
} catch (error) {
console.error('PDF 下载失败:', error)
// ElMessage.error('文件下载失败,请检查网络或文件地址') // 适配 Element Plus
}
}
// 上传报告
const uploadReportRow = (row: any) => {
currentUploadRow.value = row
// 打开弹窗前清空文件列表
fileList.value = []
uploadDialogVisible.value = true
}
// 处理弹窗关闭事件
const handleDialogClosed = () => {
// 清空文件列表
fileList.value = []
}
// 处理文件超出限制
const handleExceed = (files: any) => {
ElMessage.warning('只能上传一个文件,请先删除已选择的文件')
}
const handleRemove = (files: any) => {
fileList.value = []
}
// 文件变更处理函数
const handleChange = (file: any) => {
// 在这里直接处理文件上传逻辑
// beforeUpload(file.raw) // 注意使用 file.raw 获取原始文件对象
fileList.value = [file] // 只保留最新选择的文件
}
// 处理上传前检查
const beforeUpload = (file: any) => {
const isWord =
file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
file.type === 'application/msword' ||
file.name.endsWith('.doc') ||
file.name.endsWith('.docx')
const isPDF = file.type === 'application/pdf' || file.name.endsWith('.pdf')
const isValidType = isWord || isPDF
const isLt10M = file.size / 1024 / 1024 < 10
if (!isValidType) {
ElMessage.error('请上传(.doc/.docx/.pdf)格式文件!')
return false
}
// 校验通过后允许上传,交由 http-request 处理
return true
}
const handleUpload = async () => {
// return
const formData = new FormData()
formData.append('file', fileList.value[0]?.raw)
formData.append('lineId', currentUploadRow.value?.lineId || currentUploadRow.value?.id || '')
try {
const result = await uploadReport(formData)
ElMessage.success('上传成功')
uploadDialogVisible.value = false
tableStore.index()
return Promise.resolve(result)
} catch (error: any) {
ElMessage.error('上传失败: ' + (error.message || '未知错误'))
return Promise.reject(error)
} }
} }
@@ -200,39 +458,13 @@ watch(
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )
const handleReportClick = (id: string) => {
const row = tableStore.table.data.find((item: any) => item.id === id)
if (row && row.type2 !== '/') {
// 示例:触发下载逻辑(根据你注释掉的代码)
// getFileZip({ eventId: id }).then(res => {
// let blob = new Blob([res], { type: 'application/zip' })
// const url = window.URL.createObjectURL(blob)
// const link = document.createElement('a')
// link.href = url
// link.download = row.wavePath?.split('/')[2] || '报告文件.zip'
// link.click()
// })
console.log('点击了报告:', row.type2)
} else {
ElMessage.warning('暂无报告可下载')
}
}
// 挂载到 window 供 HTML 调用
window.handleReportClick = handleReportClick
// 组件销毁时清理全局方法
onBeforeUnmount(() => {
delete window.handleReportClick
})
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -0,0 +1,724 @@
<template>
<el-dialog draggable :title="titles" v-model="dialogVisible" append-to-body width="70%">
<!-- 总体指标占比详情谐波含有率 -->
<div>
<TableHeader ref="tableHeaderRef" :showSearch="false" @selectChange="selectChange">
<template v-slot:select>
<el-form-item>
<DatePicker ref="datePickerRef"></DatePicker>
</el-form-item>
<el-form-item label="统计指标" label-width="80px" v-if="props.showIndex">
<el-select
multiple
:multiple-limit="2"
collapse-tags
collapse-tags-tooltip
v-model="searchForm.index"
placeholder="请选择统计指标"
@change="onIndexChange($event)"
filterable
>
<el-option
v-for="item in indexOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-radio-group v-model="searchForm.dataLevel" @change="init()">
<el-radio-button label="一次值" value="Primary" />
<el-radio-button label="二次值" value="Secondary" />
</el-radio-group>
</el-form-item>
<el-form-item label="统计类型">
<el-select
style="min-width: 120px !important"
placeholder="请选择"
v-model="searchForm.valueType"
filterable
>
<el-option value="max" label="最大值"></el-option>
<el-option value="min" label="最小值"></el-option>
<el-option value="avg" label="平均值"></el-option>
<el-option value="cp95" label="cp95"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<div
class="history_count"
v-for="(item, index) in countData"
:key="index"
v-show="item.countOptions.length != 0"
>
<span class="mr12">
{{ item.name.includes('次数') ? item.name : item.name + '谐波次数' }}
</span>
<el-select
v-model="item.count"
@change="onCountChange($event, index)"
placeholder="请选择谐波次数"
style="width: 100px"
class="mr20"
filterable
>
<el-option
v-for="vv in item.countOptions"
:key="vv"
:label="item.name.includes('间谐波') ? vv - 0.5 : vv"
:value="vv"
></el-option>
</el-select>
</div>
</el-form-item>
</template>
<template #operation>
<el-button type="primary" icon="el-icon-Search" @click="init()">查询</el-button>
<el-button :type="timeControl ? 'primary' : ''" icon="el-icon-Sort" @click="setTimeControl">
缺失数据
</el-button>
</template>
</TableHeader>
</div>
<div class="history_chart" :style="pageHeight" v-loading="loading">
<MyEchart ref="historyChart" :options="echartsData" v-if="showEchart" />
<el-empty :style="pageHeight" v-else description="暂无数据" />
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { mainHeight } from '@/utils/layout'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { ref, onMounted, watch } from 'vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useDictData } from '@/stores/dictData'
import { queryStatistical } from '@/api/system-boot/csstatisticalset'
import { yMethod, exportCSV, completeTimeSeries } from '@/utils/echartMethod'
import TableHeader from '@/components/table/header/index.vue'
import { trendData } from '@/api/harmonic-boot/cockpit/cockpit'
import DatePicker from '@/components/form/datePicker/index.vue'
import { color } from '@/components/echarts/color'
import { ElMessage } from 'element-plus'
const dictData = useDictData()
defineOptions({
// name: 'govern/device/control'
})
const props = defineProps({
TrendList: {
type: Array
},
showIndex:{
type: Boolean,
default: true
}
})
const titles = ref('趋势图')
const dialogVisible: any = ref(false)
// console.log("🚀 ~ props:", props.TrendList)
const showEchart = ref(true)
const num = ref(0)
const timeControl = ref(false)
//值类型
const pageHeight = ref(mainHeight(57 * 1.6, 1.6))
const loading = ref(true)
const searchForm: any = ref({})
const tableHeaderRef = ref()
const typeOptions = [
{
name: '平均值',
id: 'avg'
},
{
name: '最大值',
id: 'max'
},
{
name: '最小值',
id: 'min'
},
{
name: 'CP95值',
id: 'cp95'
}
]
searchForm.value = {
index: [],
type: typeOptions[0].id,
count: '',
searchBeginTime: '',
searchEndTime: '',
dataLevel: 'Primary',
valueType: 'avg'
}
//统计指标
const indexOptions: any = ref([])
//谐波次数
const countOptions: any = ref([])
// Harmonic_Type
// portable-harmonic
const legendDictList: any = ref([])
const initCode = (field: string, title: string) => {
queryByCode('gridSide_exceedTheLimit').then(res => {
queryCsDictTree(res.data.id).then(item => {
//排序
indexOptions.value = item.data.sort((a: any, b: any) => {
return a.sort - b.sort
})
// const titleMap: Record<string, number> = {
// flickerOvertime: 0,
// uaberranceOvertime: 3,
// ubalanceOvertime: 4,
// freqDevOvertime: 5
// }
// let defaultIndex = 0 // 默认值
// if (field in titleMap) {
// defaultIndex = titleMap[field]
// } else if (field.includes('uharm')) {
// defaultIndex = 1
// } else if (field.includes('iharm')) {
// defaultIndex = 2
// }
let codeKey = field.includes('flickerOvertime')
? '闪变'
: field.includes('uharm')
? '谐波电压'
: field.includes('iharm')
? '谐波电流'
: field.includes('voltageDevOvertime')
? '电压偏差'
: field.includes('ubalanceOvertime')
? '不平衡'
: ''
let defaultIndex = indexOptions.value.findIndex((item: any) => item.name.includes(codeKey)) || 0
searchForm.value.index[0] = indexOptions.value[defaultIndex].id
})
queryStatistical(res.data.id).then(vv => {
legendDictList.value = vv.data
indexOptions.value.map((item: any, index: any) => {
if (!countDataCopy.value[index]) {
countDataCopy.value[index] = {
index: item.id,
countOptions: [],
count: [],
name: indexOptions.value.find((vv: any) => {
return vv.id == item.id
})?.name
}
}
legendDictList.value?.selectedList?.map((vv: any, vvs: any) => {
//查找相等的指标
if (item.id == vv.dataType) {
vv.eleEpdPqdVOS.map((kk: any, kks: any) => {
if (kk.harmStart && kk.harmEnd) {
range(0, 0, 0)
if (kk.showName.includes('间谐波电压')) {
countDataCopy.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1).map(
(item: any) => {
return item - 0.5
}
)
} else {
countDataCopy.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1)
}
if (title && countDataCopy.value[index].countOptions.includes(Number(title))) {
countDataCopy.value[index].count = Number(title)
} else if (title && countDataCopy.value[index].countOptions.includes(title)) {
countDataCopy.value[index].count = title
} else if (
!countDataCopy.value[index].count ||
countDataCopy.value[index].count.length == 0
) {
// 只有当count为空时才设置默认值
countDataCopy.value[index].count = countDataCopy.value[index].countOptions[0]
}
}
})
}
})
})
nextTick(() => {
formatCountOptions()
})
init()
})
})
}
const chartsList = ref<any>([])
const chartTitle: any = ref('')
const echartsData = ref<any>(null)
//加载echarts图表
//历史趋势数据
const historyDataList: any = ref([])
const range = (start: any, end: any, step: any) => {
return Array.from({ length: (end - start) / step + 1 }, (_, i) => start + i * step)
}
//获取请求趋势数据参数
const trendRequestData = ref()
const getTrendRequest = (val: any) => {
trendRequestData.value = val
// init()
}
//初始化趋势图
const headerRef = ref()
const datePickerRef = ref()
const lineStyle = [{ type: 'solid' }, { type: 'dashed' }, { type: 'dotted' }]
const init = async () => {
loading.value = true
// 选择指标的时候切换legend内容和data数据
echartsData.value = {}
let list: any = []
legendDictList.value?.selectedList?.map((item: any) => {
searchForm.value.index.map((vv: any) => {
if (item.dataType == vv) {
list.push(item.eleEpdPqdVOS)
}
})
})
//颜色数组
const colorList = color
//选择的指标使用方法处理
formatCountOptions()
//查询历史趋势
historyDataList.value = []
chartTitle.value = ''
searchForm.value.index.map((item: any, indexs: any) => {
indexOptions.value.map((vv: any) => {
if (vv.id == item) {
chartTitle.value += indexs == searchForm.value.index.length - 1 ? vv.name : vv.name + '/'
}
})
})
let lists: any = []
let frequencys: any = null
countData.value.map((item: any, index: any) => {
if (item.name.includes('谐波含有率')) {
frequencys = item.count
} else {
frequencys = ''
}
lists[index] = {
statisticalId: item.index,
frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : ''
}
})
let obj = {
//...trendRequestData.value,
lineId: trendRequestData.value.lineId,
list: lists,
dataLevel: searchForm.value.dataLevel,
valueType: searchForm.value.valueType,
searchBeginTime: datePickerRef.value && datePickerRef.value.timeValue[0],
searchEndTime: datePickerRef.value && datePickerRef.value.timeValue[1]
}
if (searchForm.value.index.length == 0) {
ElMessage.warning('请选择统计指标')
loading.value = false
return
}
if (obj.list.length != 0) {
try {
showEchart.value = true
await trendData(obj)
.then((res: any) => {
if (res.code == 'A0000') {
if (res.data.length == 0) {
loading.value = false
showEchart.value = false
return
}
historyDataList.value = res.data
chartsList.value = JSON.parse(JSON.stringify(res.data))
loading.value = false
setEchart()
}
})
.catch(error => {
loading.value = false
})
} catch (error) {
loading.value = false
}
}
}
const setEchart = () => {
loading.value = true
echartsData.value = {}
//icon图标替换legend图例
// y轴单位数组
let unitList: any = []
let groupedData = chartsList.value.reduce((acc: any, item: any) => {
let key = ''
if (item.phase == null) {
key = item.unit
} else {
key = item.anotherName
}
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
let result = Object.values(groupedData)
if (chartsList.value.length > 0) {
unitList = result.map((item: any) => {
return item[0].unit
})
}
echartsData.value = {
legend: {
itemWidth: 20,
itemHeight: 20,
itemStyle: { opacity: 0 }, //去圆点
type: 'scroll', // 开启滚动分页
// orient: 'vertical', // 垂直排列
top: 5,
right: 70
// width: 550,
// height: 50
},
grid: {
top: '80px'
},
tooltip: {
axisPointer: {
type: 'cross',
label: {
color: '#fff',
fontSize: 16
}
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter(params: any) {
const xname = params[0].value[0]
let str = `${xname}<br>`
params.forEach((el: any, index: any) => {
let marker = ''
if (el.value[3] == 'dashed') {
for (let i = 0; i < 3; i++) {
marker += `<span style="display:inline-block;border: 2px ${el.color} solid;margin-right:5px;width:10px;height:0px;background-color:#ffffff00;"></span>`
}
} else {
marker = `<span style="display:inline-block;border: 2px ${el.color} ${el.value[3]};margin-right:5px;width:40px;height:0px;background-color:#ffffff00;"></span>`
}
let unit = el.value[2] ? el.value[2] : ''
str += `${marker}${el.seriesName.split('(')[0]}${el.value[1]}${unit}
<br>`
})
return str
}
},
color: ['#DAA520', '#2E8B57', '#A52a2a', ...color],
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: [{}],
toolbox: {
featureProps: {
myTool1: {
show: true,
title: '下载csv',
icon: 'path://M588.8 551.253333V512H352v39.253333h236.373333z m0 78.933334v-39.253334H352v39.253334h236.373333z m136.533333 78.933333V334.933333l-157.866666-157.866666H273.066667A59.306667 59.306667 0 0 0 213.333333 236.373333v551.253334a59.306667 59.306667 0 0 0 59.306667 59.306666h274.773333v42.666667H853.333333v-180.48zM568.746667 234.666667l100.266666 100.693333h-81.066666a20.053333 20.053333 0 0 1-19.626667-20.053333z m-20.48 573.013333H273.066667a19.2 19.2 0 0 1-17.493334-19.626667V236.373333a19.2 19.2 0 0 1 19.626667-19.626666h256v98.133333a58.88 58.88 0 0 0 58.88 59.306667h96.426667v334.933333h-98.133334v-39.68H352v39.68h196.266667z m100.266666 23.04a37.973333 37.973333 0 0 1-32 15.786667 38.826667 38.826667 0 0 1-32.426666-15.786667 53.76 53.76 0 0 1-10.24-32.853333 42.666667 42.666667 0 0 1 42.666666-47.786667 35.84 35.84 0 0 1 37.546667 29.866667h-12.8a23.893333 23.893333 0 0 0-24.746667-19.2c-17.066667 0-29.013333 14.08-29.013333 35.84s11.52 37.546667 28.586667 37.546666a26.453333 26.453333 0 0 0 26.453333-25.6h12.8a39.253333 39.253333 0 0 1-7.253333 22.186667z m59.733334 15.786667a35.84 35.84 0 0 1-40.106667-34.56H682.666667a23.893333 23.893333 0 0 0 26.88 23.04c12.8 0 22.613333-6.4 22.613333-15.786667s-4.266667-11.52-14.506667-13.653333l-21.333333-5.12c-17.066667-4.266667-24.32-11.52-24.32-23.893334s12.8-26.453333 34.133333-26.453333a31.573333 31.573333 0 0 1 35.413334 30.293333h-13.653334a19.626667 19.626667 0 0 0-22.613333-18.773333c-12.8 0-20.48 5.12-20.48 12.8s5.12 11.093333 17.066667 13.653333l14.933333 2.986667a42.666667 42.666667 0 0 1 20.906667 8.96 23.893333 23.893333 0 0 1 7.68 17.92c-0.426667 17.066667-14.506667 28.16-37.12 28.16z m88.746666 0h-14.506666l-32.426667-92.16h14.08l19.626667 59.733333 6.4 20.053333c0-9.386667 3.413333-12.8 5.546666-20.053333l19.2-59.733333h14.08z',
onclick: e => {
// console.log("🚀 ~ init ~ echartsData.value:", echartsData.value.options.series.map(item => item.data))
let list = echartsData.value.options.series?.map((item: any) => item.data)
let dataList = list[0]?.map((item: any, index: any) => {
let value = [item[0], item[1]]
list.forEach((item1: any, index1: any) => {
if (index1 > 0) {
value.push(item1 && item1[index] ? item1[index][1] : null)
}
})
return value
})
exportCSV(
echartsData.value.options.series.map((item: any) => item.name),
dataList,
'监测点指标趋势.csv'
)
}
}
}
},
options: {
series: []
}
}
// console.log("🚀 ~ unitList.forEach ~ unitList:", unitList)
if (chartsList.value.length > 0) {
let yData: any = []
echartsData.value.yAxis = []
let setList = [...new Set(unitList)]
setList.forEach((item: any, index: any) => {
if (index > 2) {
echartsData.value.grid.right = (index - 1) * 80
}
yData.push([])
let right = {
position: 'right',
offset: (index - 1) * 80
}
// console.log("🚀 ~ unitList.forEach ~ right.index:", index)
echartsData.value.yAxis.push({
name: item,
yAxisIndex: index,
splitNumber: 5,
minInterval: 1,
splitLine: {
show: false
},
...(index > 0 ? right : null)
})
})
// console.log("🚀 ~ result.forEach ~ result:", result)
// '电压负序分量', '电压正序分量', '电压零序分量'
let ABCName = [
...new Set(
chartsList.value.map((item: any) => {
return item.anotherName == '电压负序分量'
? '电压不平衡'
: item.anotherName == '电压正序分量'
? '电压不平衡'
: item.anotherName == '电压零序分量'
? '电压不平衡'
: item.anotherName
})
)
]
// console.log("🚀 ~ .then ~ ABCName:", ABCName)
result.forEach((item: any, index: any) => {
let yMethodList: any = []
let ABCList = Object.values(
item.reduce((acc, item) => {
let key = ''
if (item.phase == null) {
key = item.anotherName
} else {
key = item.phase
}
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
)
// console.log("🚀 ~ ABCList.forEach ~ ABCList:", ABCList)
ABCList.forEach((kk: any) => {
let colorName = kk[0].phase?.charAt(0).toUpperCase()
let lineS = ABCName.findIndex(
item =>
item ===
(kk[0].anotherName == '电压负序分量'
? '电压不平衡'
: kk[0].anotherName == '电压正序分量'
? '电压不平衡'
: kk[0].anotherName == '电压零序分量'
? '电压不平衡'
: kk[0].anotherName)
)
let seriesList: any = []
kk.forEach((cc: any) => {
if (cc.statisticalData !== null) {
yData[setList.indexOf(kk[0].unit)].push(cc.statisticalData?.toFixed(2))
}
seriesList.push([cc.time, cc.statisticalData?.toFixed(2), cc.unit, lineStyle[lineS].type])
})
// console.log(kk);
echartsData.value.options.series.push({
name: kk[0].phase ? kk[0].phase + '相' + kk[0].anotherName : kk[0].anotherName,
type: 'line',
smooth: true,
color:
colorName == 'A' ? '#DAA520' : colorName == 'B' ? '#2E8B57' : colorName == 'C' ? '#A52a2a' : '',
symbol: 'none',
// data: seriesList,
data: timeControl.value ? completeTimeSeries(seriesList) : seriesList,
lineStyle: lineStyle[lineS],
yAxisIndex: setList.indexOf(kk[0].unit)
})
})
})
yData.forEach((item: any, index: any) => {
let [min, max] = yMethod(item)
echartsData.value.yAxis[index].min = min
echartsData.value.yAxis[index].max = max
})
// console.log("🚀 ~ result.forEach ~ echartsData.value:", echartsData.value)
}
loading.value = false
}
const setTimeControl = () => {
timeControl.value = !timeControl.value
setEchart()
}
const selectChange = (flag: boolean, height: any) => {
pageHeight.value = mainHeight(height * 1.6, 1.6)
}
//导出
const historyChart = ref()
const countData: any = ref([])
const countDataCopy: any = ref([])
//根据选择的指标处理谐波次数
const formatCountOptions = () => {
countData.value = []
if (searchForm.value.index.length != 0) {
searchForm.value.index.forEach((item: any, index: any) => {
countDataCopy.value.forEach((vv: any, vvs: any) => {
if (vv.index == item) {
countData.value.push(vv)
}
})
})
countData.value.map((item: any, key: any) => {
if (item.name.includes('间谐波电压')) {
item.name = '间谐波电压次数'
} else if (item.name.includes('谐波电流')) {
item.name = '谐波电流次数'
} else if (item.name.includes('谐波电压')) {
item.name = '谐波电压次数'
}
})
}
// setTimeout(() => {
// tableHeaderRef.value.computedSearchRow()
// }, 500)
}
// 判断下拉框是否存在
const onCountChange = (val: any, index: any) => {
if (val.length == 0) {
countData.value[index].count = countData.value[index].countOptions[0]
}
}
const flag = ref(true)
const onIndexChange = (val: any) => {
num.value += 1
let pp: any = []
indexOptions.value.forEach((item: any) => {
const filteredResult = val.filter(vv => item.id == vv)
if (filteredResult.length > 0) {
pp.push(filteredResult[0])
}
})
searchForm.value.index = pp
flag.value = true
formatCountOptions()
}
watch(
() => searchForm.value.index,
(val: any, oldval: any) => {},
{
deep: true,
immediate: true
}
)
const openDialog = async (row: any, field: any, title: any) => {
titles.value = `${row?.lineName ? `${row.lineName}_` : ''}趋势图`
dialogVisible.value = true
trendRequestData.value = row
nextTick(() => {
// 默认当天
datePickerRef.value.setInterval(5)
datePickerRef.value.timeValue = [row.time, row.time]
initCode(field, title)
})
}
defineExpose({ getTrendRequest, openDialog })
</script>
<style lang="scss" scoped>
.history_header {
display: flex;
// flex-wrap: wrap;
#history_select {
width: 100%;
display: flex;
// justify-content: flex-start;
// overflow-x: auto;
height: auto;
flex-wrap: wrap;
.el-form-item {
flex: none !important;
// max-width: 380px;
}
.el-select {
margin-right: 10px;
}
}
// #history_select::-webkit-scrollbar {
// width: 0 !important;
// display: none !important;
// }
.history_searchBtn {
flex: 1;
display: flex;
justify-content: flex-end;
margin-top: 3px;
}
}
.history_chart {
width: 100%;
// flex: 1;
margin-top: 10px;
}
.history_count {
.el-select {
min-width: 100px;
}
}
</style>

View File

@@ -0,0 +1,189 @@
<template>
<div>
<!-- 指标越限详情 -->
<el-dialog draggable title="指标越限详情" v-model="dialogVisible" append-to-body width="70%">
<TableHeader datePicker showExport :showReset="false" ref="tableHeaderRef">
<template v-slot:select>
<el-form-item label="监测点">
<el-select
v-model="tableStore.table.params.lineId"
placeholder="请选择监测点"
style="width: 150px"
filterable
>
<el-option
v-for="item in options"
:key="item.lineId"
:label="item.lineName"
:value="item.lineId"
/>
</el-select>
</el-form-item>
</template>
</TableHeader>
<Table ref="tableRef" @cell-click="cellClickEvent" isGroup :height="height"></Table>
</el-dialog>
<!-- 谐波电流谐波电压占有率 -->
<HarmonicRatio ref="harmonicRatioRef" v-if="dialogFlag" @close="onHarmonicRatioClose" />
</div>
</template>
<script setup lang="ts">
import { ref, provide,nextTick } from 'vue'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout'
import HarmonicRatio from '@/components/cockpit/overLimitStatistics/components/harmonicRatio.vue'
import { cslineList } from '@/api/harmonic-boot/cockpit/cockpit'
const dialogVisible: any = ref(false)
const harmonicRatioRef: any = ref(null)
const options = ref()
const height = mainHeight(0, 2).height as any
const tableHeaderRef = ref()
const dialogFlag = ref(false)
const loop50 = (key: string) => {
let list: any[] = []
for (let i = 2; i < 26; i++) {
list.push({
title: i + '次',
field: key + i + 'Overtime',
width: '60',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row[key + i + 'Overtime']}</span>`
}
})
}
return list
}
const tableStore: any = new TableStore({
url: '/cs-harmonic-boot/totalLimitStatistics/details',
method: 'POST',
publicHeight: 30,
showPage: false,
exportName: '每日越限占比统计',
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '日期',
field: 'time',
width: '150',
sortable: true
},
{
title: '名称',
field: 'lineName',
width: '150'
},
{
title: '长时闪变越限(%)',
field: 'flickerOvertime',
width: '90',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
}
},
{
title: '电压偏差越限(%)',
field: 'voltageDevOvertime',
width: '100',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
}
},
{
title: '三相不平衡度越限(%)',
field: 'ubalanceOvertime',
width: '100',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>`
}
},
{
title: '谐波电压越限(%)',
children: loop50('uharm')
},
{
title: '谐波电流越限(%)',
children: loop50('iharm')
},
// {
// title: '频率偏差越限(%)',
// field: 'freqDevOvertime',
// width: '100',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>`
// }
// }
],
beforeSearchFun: () => {
},
loadCallback: () => {
}
})
provide('tableStore', tableStore)
tableStore.table.params.sortBy = ''
tableStore.table.params.orderBy = ''
const open = async (row: any,searchBeginTime:any,searchEndTime:any,data: any) => {
dialogVisible.value = true
// initCSlineList()
options.value = data
tableStore.table.params.lineId = row.lineId
nextTick(() => {
tableHeaderRef.value.setTimeInterval([searchBeginTime, searchEndTime])
tableStore.table.params.searchBeginTime =searchBeginTime
tableStore.table.params.searchEndTime = searchEndTime
tableStore.index()
})
}
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name' && column.field != 'time') {
dialogFlag.value = true
dialogVisible.value = false
nextTick(() => {
harmonicRatioRef.value.openDialog(row,column.field,column.title.replace(/次/g, ""))
})
}
}
// 谐波弹窗关闭时的回调
const onHarmonicRatioClose = () => {
dialogFlag.value = false
// 重新打开指标越限详情弹窗
nextTick(() => {
dialogVisible.value = true
})
}
const initCSlineList = async () => {
const res = await cslineList({})
options.value = res.data
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@@ -1,7 +1,13 @@
<template> <template>
<div> <div>
<!--总体指标越限统计 --> <!--总体指标越限统计 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
:showReset="false"
ref="TableHeaderRef"
@selectChange="selectChange"
datePicker
v-if="fullscreen" :timeKeyList="prop.timeKey"
></TableHeader>
<my-echart <my-echart
class="tall" class="tall"
:options="echartList" :options="echartList"
@@ -26,33 +32,35 @@ import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue' import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime' import OverLimitDetails from '@/components/cockpit/overLimitStatistics/components/overLimitDetails.vue'
import OverLimitDetails from '@/components/cockpit/listOfMainMonitoringPoints/components/overLimitDetails.vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache' import { useTimeCacheStore } from '@/stores/timeCache'
import { totalLimitStatisticsData } from '@/api/harmonic-boot/cockpit/cockpit'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const route = useRoute() const TableHeaderRef = ref()
const timeCacheStore = useTimeCacheStore()
const echartList = ref({})
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
@@ -67,49 +75,59 @@ const fullscreen = computed(() => {
return false return false
} }
}) })
const echartList = ref({
title: {
text: '指标越限占比'
},
xAxis: { const initEcharts = () => {
// name: '(区域)', totalLimitStatisticsData({
data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡'] searchBeginTime: tableStore.table.params.searchBeginTime,
}, searchEndTime: tableStore.table.params.searchEndTime
}).then((res: any) => {
const dataArray = [res.data.flicker, res.data.uharm, res.data.iharm, res.data.voltageDev, res.data.ubalance]
echartList.value = {
title: {
text: '指标越限占比'
},
yAxis: { xAxis: {
name: '%', // 给X轴加单位 // name: '(区域)',
interval: 20 data: ['长时闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
}, },
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '越限占比',
data: [0, 45, 22, 0, 70],
barMaxWidth: 30
// label: { yAxis: {
// show: true, name: '%', // 给X轴加单位
// position: 'top', interval: 20
// textStyle: { },
// //数值样式 grid: {
// color: '#000' left: '10px',
// }, right: '20px'
// fontSize: 12 },
// } options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '越限占比',
data: dataArray,
barMaxWidth: 30
// label: {
// show: true,
// position: 'top',
// textStyle: {
// //数值样式
// color: '#000'
// },
// fontSize: 12
// }
}
]
} }
] }
} })
}) }
const OverLimitDetailsRef = ref() const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree', url: '/cs-harmonic-boot/totalLimitStatistics/list',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
@@ -125,90 +143,66 @@ const tableStore: any = new TableStore({
}, },
{ {
title: '名称', title: '名称',
field: 'name', field: 'lineName',
minWidth: '90' minWidth: '90'
}, },
{ {
title: '越限占比(%)', title: '越限占比(%)',
children: [ children: [
{ {
title: '闪变', title: '长时闪变',
field: 'type', field: 'flicker',
minWidth: '70', minWidth: '70',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>`
} }
}, },
{ {
title: '谐波电压', title: '谐波电压',
field: 'type1', field: 'uharm',
minWidth: '80', minWidth: '80',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type1}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.uharm}</span>`
} }
}, },
{ {
title: '谐波电流', title: '谐波电流',
field: 'type2', field: 'iharm',
minWidth: '80', minWidth: '80',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type2}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.iharm}</span>`
} }
}, },
{ {
title: '电压偏差', title: '电压偏差',
field: 'type3', field: 'voltageDev',
minWidth: '80', minWidth: '80',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type3}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.voltageDev}</span>`
} }
}, },
{ {
title: '三相不平衡', title: '三相不平衡',
field: 'type4', field: 'ubalance',
minWidth: '90', minWidth: '90',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type4}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalance}</span>`
} }
} }
] ]
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.data = [
{
name: '10kV1#电动机',
type: '0',
type1: '45',
type2: '22',
type3: '0',
type4: '70'
}
]
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
initEcharts()
} }
}) })
@@ -220,27 +214,52 @@ provide('tableStore', tableStore)
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') { if (column.field != 'name') {
console.log(row) OverLimitDetailsRef.value.open(
OverLimitDetailsRef.value.open(row) row,
tableStore.table.params.searchBeginTime || prop.timeValue?.[0],
tableStore.table.params.searchEndTime || prop.timeValue?.[1],
tableStore.table.data
)
} }
} }
onMounted(() => { onMounted(() => {
tableStore.index() tableStore.index()
}) })
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
watch( watch(
() => prop.timeKey, () => prop.timeKey,
val => { val => {
tableStore.index() tableStore.index()
initEcharts()
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )

View File

@@ -1,42 +1,67 @@
<template> <template>
<div> <div>
<!--敏感负荷列表 --> <!--敏感负荷列表 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
<Table ref="tableRef" @cell-click="cellClickEvent" :height="`calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`" isGroup></Table> ref="TableHeaderRef"
:showReset="false"
@selectChange="selectChange"
v-if="fullscreen"
:timeKeyList="prop.timeKey"
>
<template #select>
<el-form-item label="关键字筛选">
<el-input
maxlength="32"
show-word-limit
style="width: 240px"
v-model.trim="tableStore.table.params.searchValue"
clearable
placeholder="请输入敏感负荷名称"
/>
</el-form-item>
</template>
</TableHeader>
<Table
ref="tableRef"
@cell-click="cellClickEvent"
:height="`calc(${prop.height} - ${headerHeight}px + ${fullscreen ? -58 : 56}px )`"
isGroup
></Table>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue' import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue' import Table from '@/components/table/index.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router' import { useDictData } from '@/stores/dictData'
import { useTimeCacheStore } from '@/stores/timeCache' import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const route = useRoute() const TableHeaderRef = ref()
const timeCacheStore = useTimeCacheStore()
const dictData = useDictData()
const sensitiveUserType = dictData.getBasicData('Interference_Source')
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 // if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // // 更新时间参数
// 更新表格参数 // tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] // tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1] // }
}
} }
// 计算是否全屏展示 // 计算是否全屏展示
@@ -51,14 +76,11 @@ const fullscreen = computed(() => {
} }
}) })
const OverLimitDetailsRef = ref() const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree', url: '/cs-harmonic-boot/pqSensitiveUser/getListByUser',
method: 'POST', method: 'POST',
showPage: fullscreen.value ? true : false,
showPage: false,
column: [ column: [
{ {
field: 'index', field: 'index',
@@ -76,55 +98,36 @@ const tableStore: any = new TableStore({
{ {
title: '敏感负荷类型', title: '敏感负荷类型',
field: 'type', field: 'loadType',
minWidth: '70' minWidth: '70',
formatter: row => {
return sensitiveUserType.filter(item => item.id == row.cellValue)[0]?.name
}
}, },
{ {
title: '是否监测', title: '是否监测',
field: 'type1', field: 'isMonitor',
minWidth: '80' minWidth: '80',
formatter: (row: any) => {
return row.cellValue || '/'
}
}, },
{ {
title: '是否治理', title: '是否治理',
field: 'type2', field: 'isGovern',
minWidth: '80' minWidth: '80',
formatter: (row: any) => {
return row.cellValue || '/'
}
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
}, },
loadCallback: () => {
tableStore.table.data = [
{
name: '10kV1#变压器',
type: '机房',
type1: '是',
type2: '100A APF'
},
{
name: '380kV1#母线',
type: 'PLC',
type1: '是',
type2: 'UPS'
}
]
}
})
loadCallback: () => {}
})
tableStore.table.params.searchValue = ''
const tableRef = ref() const tableRef = ref()
provide('tableRef', tableRef) provide('tableRef', tableRef)
@@ -138,6 +141,24 @@ const cellClickEvent = ({ row, column }: any) => {
} }
} }
const setTime = () => {
// const time = getTime(
// (TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
// prop.timeKey,
// fullscreen.value
// ? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
// : prop.timeValue
// )
// if (Array.isArray(time)) {
// tableStore.table.params.searchBeginTime = time[0]
// tableStore.table.params.searchEndTime = time[1]
// TableHeaderRef.value?.setInterval(time[2] - 0)
// TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
// } else {
// console.warn('获取时间失败time 不是一个有效数组')
// }
}
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(() => {
tableStore.index() tableStore.index()
@@ -150,15 +171,13 @@ watch(
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )
const addMenu = () => {}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -1,15 +1,11 @@
<template> <template>
<div> <div>
<!-- 暂态事件列表 --> <!-- 暂态事件详情 -->
<el-dialog draggable title="暂态事件列表" v-model="dialogVisible" append-to-body width="70%"> <el-dialog draggable title="暂态事件详情 " v-model="dialogVisible" append-to-body width="70%">
<!-- <TableHeader datePicker showExport :showReset="false"> <TableHeader datePicker showExport :showReset="false" ref="tableHeaderRef" @selectChange="selectChange">
<template v-slot:select> <template v-slot:select>
<el-form-item label="监测点名称"> <el-form-item label="监测点" v-if="props.showLine">
<el-select <el-select v-model="tableStore.table.params.lineId" filterable placeholder="请选择监测点名称">
v-model="tableStore.table.params.searchValue"
placeholder="请选择监测点名称"
style="width: 240px"
>
<el-option <el-option
v-for="item in options" v-for="item in options"
:key="item.value" :key="item.value"
@@ -18,9 +14,41 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="暂态类型">
<el-select
v-model="tableStore.table.params.eventType"
style="min-width: 150px"
clearable
placeholder="请选择暂态类型"
>
<el-option
v-for="item in eventList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template> </template>
</TableHeader> --> </TableHeader>
<Table ref="tableRef" isGroup :height="height"></Table> <Table ref="tableRef" isGroup :height="heightRef"></Table>
</el-dialog>
<!-- 查看波形 -->
<el-dialog
v-model="isWaveCharts"
draggable
title="波形分析"
append-to-body
v-if="isWaveCharts"
width="70%"
@close="handleHideCharts"
>
<waveFormAnalysis
v-loading="loading"
ref="waveFormAnalysisRef"
@handleHideCharts="handleHideCharts"
:wp="wp"
/>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@@ -30,21 +58,47 @@ import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout' import { mainHeight } from '@/utils/layout'
import waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue'
import { analyseWave } from '@/api/common'
import { getSimpleLine } from '@/api/harmonic-boot/cockpit/cockpit'
interface Props {
showLine?: boolean
}
const props = withDefaults(defineProps<Props>(), {
showLine: true
})
const dialogVisible: any = ref(false) const dialogVisible: any = ref(false)
const harmonicRatioRef: any = ref(null) const waveFormAnalysisRef: any = ref(null)
const options = [ // 波形
{ const isWaveCharts = ref(false)
value: '35kV进线', const loading = ref(false)
label: '35kV进线' const wp = ref({})
} const boxoList: any = ref({})
const tableHeaderRef = ref()
const options = ref()
const heightRef = ref(mainHeight(168, 2.1).height)
const selectChange = (flag: boolean, h: any) => {
heightRef.value = mainHeight(h, 2.1).height
}
const eventList = [
{ label: '电压暂降', value: '1' },
{ label: '电压中断', value: '2' },
{ label: '电压暂升', value: '3' }
] ]
const height = mainHeight(0, 2).height as any const getSimpleLineList = async () => {
const res = await getSimpleLine()
options.value = res.data
}
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0', url: '/cs-harmonic-boot/event/pageEvent',
method: 'POST', method: 'POST',
publicHeight: 30, showPage: true,
showPage: false, exportName: '暂态事件详情',
exportName: '主要监测点列表',
column: [ column: [
{ {
field: 'index', field: 'index',
@@ -56,76 +110,143 @@ const tableStore: any = new TableStore({
}, },
{ {
title: '暂态时间', title: '暂态时间',
field: 'time', field: 'startTime',
minWidth: '180'
}, },
{ {
title: '测点名称', title: '测点名称',
field: 'name', field: 'lineName',
width: '150' minWidth: '150'
}, },
{ {
title: '暂态类型', title: '暂态类型',
field: 'flicker', field: 'tag',
width: '100', minWidth: '100'
}, },
{ {
title: '特征幅值(%)', title: '特征幅值(%)',
field: 'flicker', field: 'amplitude',
width: '100' minWidth: '100'
}, },
{ {
title: '暂降深度(%)', title: '暂降深度(%)',
field: 'flicker', field: 'depth',
width: '100' minWidth: '100',
formatter: (row: any) => {
// 当暂态类型不是电压暂升时,计算暂降深度 = 100 - 特征幅值
if (row.row.tag !== '电压暂升') {
const amplitude = parseFloat(row.row.amplitude)
if (!isNaN(amplitude)) {
return 100 - amplitude
}
return '-'
} else {
// 电压暂升时不显示暂降深度
return '/'
}
}
}, },
{ {
title: '持续时间(S)', title: '持续时间(S)',
field: 'flicker', field: 'persistTime',
width: '100' minWidth: '100'
}, },
{ {
title: '严重度', title: '严重度',
field: 'flicker', field: 'severity',
width: '80' minWidth: '80'
}, },
{
title: '波形',
minWidth: '100',
render: 'buttons',
buttons: [
{
name: 'edit',
text: '波形分析',
type: 'primary',
icon: 'el-icon-DataLine',
render: 'basicButton',
disabled: row => {
return !row.wavePath
},
click: async row => {
row.loading1 = true
loading.value = true
isWaveCharts.value = true
dialogVisible.value = false
// 在打开弹窗时立即设置高度
nextTick(() => {
if (waveFormAnalysisRef.value) {
// waveFormAnalysisRef.value.setHeight(false, 360)
waveFormAnalysisRef.value.setHeight(999, 130, 1.6666666)
}
})
await analyseWave(row.id)
.then(res => {
row.loading1 = false
if (res != undefined) {
boxoList.value = row
// boxoList.value = {
// ...row,
// duration: row.persistTime // 将 persistTime 值赋给 duration
// }
boxoList.value.featureAmplitude = (row.amplitude - 0) / 100
// row.evtParamVVaDepth != '-' ? row.evtParamVVaDepth - 0 : null
boxoList.value.systemType = 'YPT'
wp.value = res.data
}
loading.value = false
})
.catch(() => {
row.loading1 = false
loading.value = false
})
nextTick(() => {
waveFormAnalysisRef.value &&
waveFormAnalysisRef.value.getWpData(wp.value, boxoList.value, true)
})
}
},
{
name: 'edit',
text: '暂无波形',
type: 'info',
icon: 'el-icon-DataLine',
render: 'basicButton',
disabled: row => {
return !!row.wavePath
}
}
]
}
], ],
beforeSearchFun: () => {}, beforeSearchFun: () => {},
loadCallback: () => { loadCallback: () => {}
tableStore.table.data = [
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
}
]
}
}) })
tableStore.table.params.eventType = ''
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore) provide('tableStore', tableStore)
const open = async (row: any) => { const open = async (time: any) => {
tableStore.table.params.eventType = ''
dialogVisible.value = true dialogVisible.value = true
tableStore.index() getSimpleLineList()
tableStore.table.params.lineId = ''
nextTick(() => {
tableHeaderRef.value.setInterval(5)
tableHeaderRef.value.setTimeInterval([time, time])
tableStore.table.params.searchBeginTime = time
tableStore.table.params.searchEndTime = time
tableStore.index()
})
} }
// 点击行 const handleHideCharts = () => {
const cellClickEvent = ({ row, column }: any) => { isWaveCharts.value = false
if (column.field != 'name') { dialogVisible.value = true
harmonicRatioRef.value.openDialog(row)
}
} }
defineExpose({ open }) defineExpose({ open })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -1,76 +1,102 @@
<template> <template>
<div> <div>
<!--暂态事件明细 --> <!--暂态事件明细 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
<el-calendar v-model="value" :style="{ height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`, overflow: 'auto' }"> :showReset="false"
ref="TableHeaderRef"
@selectChange="selectChange"
datePicker
v-if="fullscreen"
:timeKeyList="prop.timeKey"
></TableHeader>
<el-calendar
v-model="value"
:style="{
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`,
overflow: 'auto'
}"
v-loading="tableStore.table.loading"
>
<template #date-cell="{ data }"> <template #date-cell="{ data }">
<div style="height: 100%; padding: 8px" :style="{ background: setBackground(data.day) }"> <div
style="padding: 8px"
:style="{
background: setBackground(data.day),
height: `calc((${prop.height} - 100px - ${headerHeight}px + ${fullscreen ? 0 : 56}px) / 5 )`
}"
>
<p :class="data.isSelected ? 'is-selected' : ''"> <p :class="data.isSelected ? 'is-selected' : ''">
{{ data.day.split('-').slice(2).join('-') }} {{ data.day.split('-').slice(2).join('-') }}
</p> </p>
<el-tooltip <el-tooltip effect="dark" placement="top" :hide-after="0" v-if="hasEventData(data.day)">
effect="dark"
placement="top"
:hide-after="0"
v-if="list?.filter(item => item.time == data.day)[0]?.type || false"
>
<template #content> <template #content>
<!-- <span v-html="list?.filter(item => item.time == data.day)[0]?.type || ''"></span> --> <!-- <span v-html="list?.filter(item => item.time == data.day)[0]?.type || ''"></span> -->
<div v-for="item in list?.filter(item => item.time == data.day)"> <div v-for="item in list?.filter((item:any) => item.name == data.day)">
<div>电压暂降{{ item.type || '' }}</div> <div>电压暂降{{ item.eventDown || 0 }}</div>
<div>电压中断{{ item.type1 || '' }}</div> <div>电压中断{{ item.eventOff || 0 }}</div>
<div>电压暂升{{ item.type2 || '' }}</div> <div>电压暂升{{ item.eventUp || 0 }}</div>
</div> </div>
</template> </template>
<div <div
style="text-decoration: underline" style="text-decoration: underline"
:style="{ height: `calc(${prop.height} / 5 - 47px)`, overflow: 'auto' }" class="details"
v-for="item in list?.filter(item => item.time == data.day)" v-for="item in list?.filter((item:any) => item.name == data.day)"
@click="descentClick(item)"
> >
<div @click="descentClick">电压暂降:{{ item.type || '' }}</div> <!-- <div>电压暂降:{{ item.eventDown || 0 }}</div>
<div>电压中断:{{ item.type1 || '' }}</div> <div>电压中断:{{ item.eventOff || 0 }}</div>
<div>电压暂升:{{ item.type2 || '' }}</div> <div>电压暂升:{{ item.eventUp || 0 }}</div> -->
<template v-if="fullscreen">
<div>电压暂降:{{ item.eventDown || 0 }}</div>
<div>电压中断:{{ item.eventOff || 0 }}</div>
<div>电压暂升:{{ item.eventUp || 0 }}</div>
</template>
<template v-else>暂态事件</template>
</div> </div>
</el-tooltip> </el-tooltip>
</div> </div>
</template> </template>
</el-calendar> </el-calendar>
<!-- 暂态事件列表 --> <!-- 暂态事件列表 -->
<TransientList ref="transientListRef" /> <TransientList ref="transientListRef" :showLine="false" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue' import { ref, onMounted, provide, reactive, watch, nextTick } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { dayjs } from 'element-plus' import { dayjs } from 'element-plus'
import TransientList from './components/transientList.vue' import TransientList from './components/transientList.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router' import { getTime } from '@/utils/formatTime'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const route = useRoute() const TableHeaderRef = ref()
const timeCacheStore = useTimeCacheStore()
const hasEventData = (day: string) => {
const item = list.value?.find((item: any) => item.name == day)
if (!item) return false
return (item.eventDown || item.eventOff || item.eventUp) > 0
}
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
@@ -90,88 +116,32 @@ dayjs.en.weekStart = 1 //设置日历的周起始日为星期一
const value = ref(new Date()) const value = ref(new Date())
const transientListRef = ref() const transientListRef = ref()
const list = ref([ const list = ref()
{
time: '2025-10-01',
key: 81,
type: 1,
type1: 1,
type2: 1
},
{
time: '2025-10-31',
key: 81,
type: 1,
type1: 1,
type2: 1
},
{
time: '2025-10-08',
key: 20,
type: 1,
type1: 1,
type2: 1
},
{
time: '2025-10-16',
key: 20,
type: 1,
type1: 1,
type2: 1
},
{
time: '2025-10-23',
key: 20,
type: 1,
type1: 1,
type2: 1
},
{
time: '2025-10-04',
key: 0
},
{
time: '2025-10-05',
key: 0
}
])
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree', url: '/cs-harmonic-boot/csevent/getEventDate',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
column: [], column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) { beforeSearchFun: () => {
const cached = timeCacheStore.getCache(route.path) setTime()
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.data = [] value.value = tableStore.table.params.searchBeginTime
list.value = tableStore.table.data
} }
}) })
const tableRef = ref([])
const setBackground = (value: string) => { const setBackground = (value: string) => {
let data = [] let data = []
data = list.value?.filter(item => item.time == value) data = list.value?.filter((item: any) => item.name == value)
if (data && data?.length > 0) { if (data && data?.length > 0) {
if (data[0].key > 0) { if (data[0].eventDown > 0 || data[0].eventOff > 0 || data[0].eventUp > 0) {
return '#Ff660090' return '#Ff660090'
} }
} }
@@ -179,52 +149,69 @@ const setBackground = (value: string) => {
return '#fff' return '#fff'
} }
provide('tableRef', tableRef)
provide('tableStore', tableStore) provide('tableStore', tableStore)
onMounted(() => { onMounted(() => {
tableStore.index() nextTick(() => {
})
watch(
() => prop.timeKey,
val => {
tableStore.index() tableStore.index()
})
})
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
} }
) }
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )
// 电压暂降点击事件 // 电压暂降点击事件
const descentClick = () => { const descentClick = (item: any) => {
transientListRef.value.open() transientListRef.value.open(item.name)
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-calendar) { :deep(.el-calendar) {
.el-calendar__header { .el-calendar__button-group {
display: none; display: none;
} }
.el-calendar__body { .el-calendar__body {
padding: 0px !important; padding: 0px !important;
height: 100%; height: calc(100% - 46px);
.el-calendar-table { .el-calendar-table {
height: 100%; height: 100%;
} }
} }
.el-calendar-day { .el-calendar-day {
// height: calc(912px / 5 );
height: 100%; height: 100%;
padding: 0px; padding: 0px;
overflow: hidden; overflow: hidden;
.details {
height: calc(100% - 20px);
overflow-y: auto;
}
} }
.el-calendar-table__row { .el-calendar-table__row {
.next { .next {

View File

@@ -1,51 +1,61 @@
<template> <template>
<div> <div>
<!--暂态事件概率分布 --> <!--暂态事件概率分布 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
ref="TableHeaderRef"
:timeKeyList="prop.timeKey"
:showReset="false"
@selectChange="selectChange"
datePicker
v-if="fullscreen"
></TableHeader>
<my-echart <my-echart
class="tall" class="tall"
:options="echartList" :options="echartList"
:style="{ width: prop.width, height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )` }" :style="{
width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}"
/> />
<my-echart <!-- <my-echart
class="mt10" class="mt10"
:options="echartList1" :options="echartList1"
:style="{ width: prop.width, height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )` }" :style="{
/> width: prop.width,
height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`
}"
/> -->
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, reactive, watch } from 'vue' import { ref, onMounted, provide, reactive, watch } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router' import { getTime } from '@/utils/formatTime'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const TableHeaderRef = ref()
const route = useRoute() const headerHeight = ref(57)
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
@@ -63,303 +73,193 @@ const fullscreen = computed(() => {
const config = useConfig() const config = useConfig()
const echartList = ref({ const echartList = ref({})
options: {
xAxis: null,
yAxis: null,
dataZoom: null,
backgroundColor: '#fff',
tooltip: {
// trigger: 'axis'
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter: function (params: any) {
console.log(params)
var tips = ''
for (var i = 0; i < params.length; i++) {
tips += params[i].name + '</br/>'
tips += '监测点数' + ':' + '&nbsp' + '&nbsp' + params[i].value + '</br/>'
}
return tips
}
},
title: {
text: '暂态事件概率分布',
x: 'center'
},
visualMap: { const echartList1 = ref({})
max: 20,
show: false,
inRange: {
color: ['#313695', '#00BB00', '#ff8000', '#a50026']
}
},
xAxis3D: {
type: 'category',
name: '特征幅值',
data: [
'0-10%',
'10%-20%',
'20%-30%',
'30%-40%',
'40%-50%',
'50%-60%',
'60%-70%',
'70%-80%',
'80%-90%',
'90%-100%'
],
axisLine: {
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111'
}
},
yAxis3D: {
type: 'category',
name: '持续时间',
data: ['0-0.01s', '0.01s-0.1s', '0.1s-1s', '1s-10s', '10s'],
nameTextStyle: {
color: '#111'
},
axisLine: {
show: true,
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111'
},
splitLine: {
lineStyle: {
// 使用深浅的间隔色
color: ['#111'],
type: 'dashed',
opacity: 0.5
}
}
},
zAxis3D: {
type: 'value',
splitNumber: 10,
minInterval: 10,
name: '暂态事件次数'
},
grid3D: {
viewControl: {
projection: 'perspective',
distance: 250
},
boxWidth: 200,
boxDepth: 80,
light: {
main: {
intensity: 1.2
},
ambient: {
intensity: 0.3
}
}
},
series: [
{
type: 'bar3D',
data: [
[0, 0, 1],
[0, 1, 1],
[0.2, 1]
],
shading: 'realistic',
label: {
show: false,
textStyle: {
fontSize: 16,
borderWidth: 1
}
},
itemStyle: { const processDataForChart = (rawData: any[]) => {
opacity: 1 // 将后端返回的扁平数据转换为 ECharts 需要的三维坐标格式 [x, y, z]
}, const chartData = rawData.map(item => [item.x, item.y, item.z])
emphasis: {
label: {
textStyle: {
fontSize: 20,
color: '#900'
}
},
itemStyle: {
color: '#900'
}
}
}
]
}
})
const echartList1 = ref({
title: {
text: '越限时间概率分布'
},
xAxis: { return chartData
// name: '时间', }
// data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: {
name: '次' // 给X轴加单位
},
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
type: 'line',
showSymbol: false,
// smooth: true,
name: '电压中断',
color: '#FF9100',
data: [
['2025-10-16 07:00:00', 10],
['2025-10-16 07:15:00', 10],
['2025-10-16 07:30:00', 10],
['2025-10-16 07:45:00', 10],
['2025-10-16 08:00:00', 30],
['2025-10-16 08:15:00', 50],
['2025-10-16 08:30:00', 60],
['2025-10-16 08:45:00', 70],
['2025-10-16 09:00:00', 100],
['2025-10-16 09:15:00', 120],
['2025-10-16 09:30:00', 130],
['2025-10-16 09:45:00', 140],
['2025-10-16 10:00:00', 160],
['2025-10-16 10:15:00', 160],
['2025-10-16 10:30:00', 130],
['2025-10-16 10:45:00', 120],
['2025-10-16 11:00:00', 140],
['2025-10-16 11:15:00', 80],
['2025-10-16 11:30:00', 70],
['2025-10-16 11:45:00', 90],
['2025-10-16 12:00:00', 60],
['2025-10-16 12:15:00', 60],
['2025-10-16 12:30:00', 60],
['2025-10-16 12:45:00', 60]
]
},
{
type: 'line',
showSymbol: false,
// smooth: true,
color: '#FFBF00',
name: '电压暂降',
data: [
['2025-10-16 07:00:00', 1],
['2025-10-16 07:15:00', 1],
['2025-10-16 07:30:00', 1],
['2025-10-16 07:45:00', 1],
['2025-10-16 08:00:00', 3],
['2025-10-16 08:15:00', 5],
['2025-10-16 08:30:00', 6],
['2025-10-16 08:45:00', 7],
['2025-10-16 09:00:00', 10],
['2025-10-16 09:15:00', 12],
['2025-10-16 09:30:00', 13],
['2025-10-16 09:45:00', 14],
['2025-10-16 10:00:00', 16],
['2025-10-16 10:15:00', 16],
['2025-10-16 10:30:00', 13],
['2025-10-16 10:45:00', 12],
['2025-10-16 11:00:00', 14],
['2025-10-16 11:15:00', 8],
['2025-10-16 11:30:00', 7],
['2025-10-16 11:45:00', 9],
['2025-10-16 12:00:00', 6],
['2025-10-16 12:15:00', 6],
['2025-10-16 12:30:00', 6],
['2025-10-16 12:45:00', 6]
]
},
{
type: 'line',
showSymbol: false,
// smooth: true,
name: '电压暂升',
color: config.layout.elementUiPrimary[0],
data: [
['2025-10-16 07:00:00', 19],
['2025-10-16 07:15:00', 19],
['2025-10-16 07:30:00', 19],
['2025-10-16 07:45:00', 19],
['2025-10-16 08:00:00', 39],
['2025-10-16 08:15:00', 59],
['2025-10-16 08:30:00', 69],
['2025-10-16 08:45:00', 79],
['2025-10-16 09:00:00', 109],
['2025-10-16 09:15:00', 129],
['2025-10-16 09:30:00', 139],
['2025-10-16 09:45:00', 149],
['2025-10-16 10:00:00', 169],
['2025-10-16 10:15:00', 169],
['2025-10-16 10:30:00', 139],
['2025-10-16 10:45:00', 129],
['2025-10-16 11:00:00', 149],
['2025-10-16 11:15:00', 89],
['2025-10-16 11:30:00', 79],
['2025-10-16 11:45:00', 99],
['2025-10-16 12:00:00', 69],
['2025-10-16 12:15:00', 69],
['2025-10-16 12:30:00', 69],
['2025-10-16 12:45:00', 69]
]
}
]
}
})
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree', url: '/cs-harmonic-boot/csevent/getEventCoords',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime },
loadCallback: () => {
const processedData = processDataForChart(tableStore.table.data.innerList || [])
const trendList = tableStore.table.data.trendList || []
const xlist = tableStore.table.data.xlist || []
if (fullscreen.value) { // 处理趋势图数据
const cached = timeCacheStore.getCache(route.path) const seriesData = trendList.map((item: any) => {
if (cached && cached.timeValue) { // 根据接口返回的name字段确定系列名称和颜色
beginTime = cached.timeValue[0] let name = ''
endTime = cached.timeValue[1] let color = ''
switch (item.name) {
case 'Evt_Sys_DipStr':
name = '电压暂降'
color = '#FFBF00'
break
case 'Evt_Sys_IntrStr':
name = '电压中断'
color = '#FF9100'
break
case 'Evt_Sys_SwlStr':
name = '电压暂升'
color = config.layout.elementUiPrimary[0]
break
default:
name = item.name
color = '#000000'
}
return {
name: name,
type: 'line',
showSymbol: false,
color: color,
data: item.trendList?.map((value: number, index: number) => [xlist[index], value]) || []
}
})
// 获取x轴和y轴的标签值
const xLabels = [
'0-10%',
'10%-20%',
'20%-30%',
'30%-40%',
'40%-50%',
'50%-60%',
'60%-70%',
'70%-80%',
'80%-90%',
'90%-100%'
]
const yLabels = ['0-0.01s', '0.01s-0.1s', '0.1s-1s', '1s-10s', '10s']
echartList.value = {
options: {
xAxis: null,
yAxis: null,
dataZoom: null,
backgroundColor: '#fff',
tooltip: {
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter: function (params: any) {
var tips = ''
tips += '持续时间: ' + yLabels[params.value[1]] + '</br>'
tips += '特征幅值: ' + xLabels[params.value[0]] + '</br>'
tips += '事件次数: ' + params.value[2] + '</br>'
return tips
}
},
title: {
text: '暂态事件概率分布',
x: 'center'
},
visualMap: {
max: 500,
show: false,
inRange: {
color: ['#313695', '#00BB00', '#ff8000', '#a50026']
}
},
xAxis3D: {
type: 'category',
name: '特征幅值',
data: xLabels,
nameGap: 40
},
yAxis3D: {
type: 'category',
name: '持续时间',
data: yLabels,
nameGap: 40,
splitLine: {
lineStyle: {
type: 'dashed',
opacity: 0.5
}
}
},
zAxis3D: {
type: 'value',
minInterval: 10,
name: '暂态事件次数',
nameGap: 30
},
grid3D: {
viewControl: {
projection: 'perspective',
distance: 260,
rotateSensitivity: 10,
zoomSensitivity: 2
},
boxWidth: 150,
boxDepth: 100,
boxHeight: 100,
light: {
main: {
intensity: 1.2
},
ambient: {
intensity: 0.4
}
}
},
series: [
{
type: 'bar3D',
data: processedData,
shading: 'realistic',
label: {
show: false,
textStyle: {
fontSize: 16,
borderWidth: 1
}
}
}
]
} }
} }
// 如果缓存中没有则使用默认值 echartList1.value = {
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0] title: {
text: '越限时间概率分布'
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1] },
}, xAxis: {
loadCallback: () => { type: 'category',
tableStore.table.data = [] data: xlist,
axisLabel: {
formatter: '{value}'
}
},
yAxis: {
name: '次'
},
grid: {
left: '10px',
right: '20px'
},
series: seriesData
}
} }
}) })
@@ -371,22 +271,41 @@ provide('tableStore', tableStore)
onMounted(() => { onMounted(() => {
tableStore.index() tableStore.index()
}) })
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
watch( watch(
() => prop.timeKey, () => prop.timeKey,
val => { val => {
tableStore.index() tableStore.index()
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )
const addMenu = () => {}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -2,16 +2,27 @@
<div> <div>
<!-- 暂态事件详情 --> <!-- 暂态事件详情 -->
<el-dialog draggable title="暂态事件详情 " v-model="dialogVisible" append-to-body width="70%"> <el-dialog draggable title="暂态事件详情 " v-model="dialogVisible" append-to-body width="70%">
<TableHeader datePicker showExport :showReset="false"> <TableHeader datePicker showExport :showReset="false" ref="tableHeaderRef" @selectChange="selectChange">
<template v-slot:select> <template v-slot:select>
<el-form-item label="监测点名称"> <el-form-item label="监测点">
<el-select <el-select filterable v-model="tableStore.table.params.lineId" placeholder="请选择监测点名称">
v-model="tableStore.table.params.searchValue"
placeholder="请选择监测点名称"
style="width: 240px"
>
<el-option <el-option
v-for="item in options" v-for="item in options"
:key="item.lineId"
:label="item.name"
:value="item.lineId"
/>
</el-select>
</el-form-item>
<el-form-item label="暂态类型">
<el-select
v-model="tableStore.table.params.eventType"
style="min-width: 150px"
clearable
placeholder="请选择暂态类型"
>
<el-option
v-for="item in eventList"
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
@@ -20,10 +31,25 @@
</el-form-item> </el-form-item>
</template> </template>
</TableHeader> </TableHeader>
<Table ref="tableRef" @cell-click="cellClickEvent" isGroup :height="height"></Table> <Table ref="tableRef" isGroup :height="heightRef"></Table>
</el-dialog> </el-dialog>
<!-- 查看波形 --> <!-- 查看波形 -->
<HarmonicRatio ref="harmonicRatioRef" /> <el-dialog
v-model="isWaveCharts"
draggable
title="波形分析"
append-to-body
width="70%"
@close="handleHideCharts"
>
<waveFormAnalysis
v-loading="loading"
v-if="isWaveCharts"
ref="waveFormAnalysisRef"
@handleHideCharts="handleHideCharts"
:wp="wp"
/>
</el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -32,21 +58,39 @@ import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout' import { mainHeight } from '@/utils/layout'
import HarmonicRatio from '@/components/cockpit/listOfMainMonitoringPoints/components/harmonicRatio.vue' import waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue'
import { analyseWave } from '@/api/common'
import { getSimpleLine } from '@/api/harmonic-boot/cockpit/cockpit'
const dialogVisible: any = ref(false) const dialogVisible: any = ref(false)
const harmonicRatioRef: any = ref(null) const waveFormAnalysisRef: any = ref(null)
const options = [ // 波形
{ const isWaveCharts = ref(false)
value: '35kV进线', const loading = ref(false)
label: '35kV进线' const wp = ref({})
} const boxoList: any = ref({})
const tableHeaderRef = ref()
const options = ref()
const heightRef = ref(mainHeight(168, 2.2).height)
const selectChange = (flag: boolean, h: any) => {
heightRef.value = mainHeight(h, 2.2).height
}
const eventList = [
{ label: '电压暂降', value: '1' },
{ label: '电压中断', value: '2' },
{ label: '电压暂升', value: '3' }
] ]
const height = mainHeight(0, 2).height as any const getSimpleLineList = async () => {
const res = await getSimpleLine()
options.value = res.data
}
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0', url: '/cs-harmonic-boot/event/pageEvent',
method: 'POST', method: 'POST',
publicHeight: 30, showPage: true,
showPage: false,
exportName: '主要监测点列表', exportName: '主要监测点列表',
column: [ column: [
{ {
@@ -59,92 +103,143 @@ const tableStore: any = new TableStore({
}, },
{ {
title: '暂态时间', title: '暂态时间',
field: 'time', field: 'startTime',
minWidth: '180'
}, },
{ {
title: '测点名称', title: '测点名称',
field: 'name', field: 'lineName',
width: '150' minWidth: '150'
}, },
{ {
title: '暂态类型', title: '暂态类型',
field: 'flicker', field: 'tag',
width: '100', minWidth: '100'
}, },
{ {
title: '特征幅值(%)', title: '特征幅值(%)',
field: 'flicker', field: 'amplitude',
width: '100' minWidth: '100'
}, },
{ {
title: '暂降深度(%)', title: '暂降深度(%)',
field: 'flicker', field: 'depth',
width: '100' minWidth: '100',
formatter: (row: any) => {
// 当暂态类型不是电压暂升时,计算暂降深度 = 100 - 特征幅值
if (row.row.tag !== '电压暂升') {
const amplitude = parseFloat(row.row.amplitude)
if (!isNaN(amplitude)) {
return 100 - amplitude
}
return '-'
} else {
// 电压暂升时不显示暂降深度
return '/'
}
}
}, },
{ {
title: '持续时间(S)', title: '持续时间(S)',
field: 'flicker', field: 'persistTime',
width: '100' minWidth: '100'
}, },
{ {
title: '严重度', title: '严重度',
field: 'flicker', field: 'severity',
width: '80' minWidth: '80'
}, },
{ {
title: '波形', title: '波形',
width: '100', width: '90',
render: 'buttons', render: 'buttons',
buttons: [ buttons: [
{ {
name: 'check', name: 'edit',
title: '查看波形', text: '波形分析',
type: 'primary', type: 'primary',
icon: 'el-icon-EditPen', icon: 'el-icon-DataLine',
render: 'basicButton', render: 'basicButton',
click: row => { disabled: row => {
return !row.wavePath
},
click: async row => {
row.loading1 = true
loading.value = true
isWaveCharts.value = true
dialogVisible.value = false
// 在打开弹窗时立即设置高度
nextTick(() => {
if (waveFormAnalysisRef.value) {
// waveFormAnalysisRef.value.setHeight(false, 360)
waveFormAnalysisRef.value.setHeight(999, 130, 1.6666666)
}
})
await analyseWave(row.id)
.then(res => {
row.loading1 = false
if (res != undefined) {
boxoList.value = row
// boxoList.value = {
// ...row,
// duration: row.persistTime // 将 persistTime 值赋给 duration
// }
// boxoList.value.featureAmplitude =
// row.evtParamVVaDepth != '-' ? row.evtParamVVaDepth - 0 : null
boxoList.value.featureAmplitude = (row.amplitude - 0) / 100
boxoList.value.systemType = 'YPT'
wp.value = res.data
}
loading.value = false
})
.catch(() => {
row.loading1 = false
loading.value = false
})
nextTick(() => {
waveFormAnalysisRef.value &&
waveFormAnalysisRef.value.getWpData(wp.value, boxoList.value, true)
})
}
},
{
name: 'edit',
text: '暂无波形',
type: 'info',
icon: 'el-icon-DataLine',
render: 'basicButton',
disabled: row => {
return !!row.wavePath
} }
} }
] ]
} }
], ],
beforeSearchFun: () => {}, beforeSearchFun: () => {},
loadCallback: () => { loadCallback: () => {}
tableStore.table.data = [
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
}
]
}
}) })
tableStore.table.params.eventType = ''
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore) provide('tableStore', tableStore)
const open = async (row: any) => { const open = async (row: any, searchBeginTime: any, searchEndTime: any) => {
tableStore.table.params.eventType = ''
dialogVisible.value = true dialogVisible.value = true
tableStore.index() getSimpleLineList()
tableStore.table.params.lineId = row.id
nextTick(() => {
tableHeaderRef.value.setTimeInterval([searchBeginTime, searchEndTime])
tableStore.table.params.searchBeginTime = searchBeginTime
tableStore.table.params.searchEndTime = searchEndTime
tableStore.index()
})
} }
// 点击行 const handleHideCharts = () => {
const cellClickEvent = ({ row, column }: any) => { isWaveCharts.value = false
if (column.field != 'name') { dialogVisible.value = true
harmonicRatioRef.value.openDialog(row)
}
} }
defineExpose({ open }) defineExpose({ open })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -1,7 +1,13 @@
<template> <template>
<div> <div>
<!--暂态事件统计 --> <!--暂态事件统计 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
ref="TableHeaderRef"
:showReset="false"
@selectChange="selectChange"
datePicker
v-if="fullscreen" :timeKeyList="prop.timeKey"
></TableHeader>
<my-echart <my-echart
class="tall" class="tall"
:options="echartList" :options="echartList"
@@ -24,35 +30,34 @@ import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue' import Table from '@/components/table/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import TransientStatisticsDetail from './components/transientStatisticsDetail.vue' import TransientStatisticsDetail from '@/components/cockpit/transientStatistics/components/transientStatisticsDetail.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router' import { netEventEcharts } from '@/api/harmonic-boot/cockpit/cockpit'
import { useTimeCacheStore } from '@/stores/timeCache' import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const TableHeaderRef = ref()
const headerHeight = ref(57) const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
@@ -69,79 +74,91 @@ const fullscreen = computed(() => {
}) })
const config = useConfig() const config = useConfig()
const data = [ const data = ref()
{
name: '电压中断',
value: 4
},
{
name: '电压暂降',
value: 41
},
{
name: '电压暂升',
value: 46
}
]
const echartList = ref({
title: {},
tooltip: { const echartList = ref()
trigger: 'item'
}, const eventEcharts = () => {
legend: { netEventEcharts({
orient: 'vertical', searchBeginTime: tableStore.table.params.searchBeginTime || prop.timeValue?.[0],
top: 'center', searchEndTime: tableStore.table.params.searchEndTime || prop.timeValue?.[1]
right: '5%', }).then(res => {
formatter: function (e: any) { // 整理接口数据为图表所需格式
return e + ' ' + data.filter(item => item.name == e)[0].value + '次' const rawData = res.data || {}
} data.value = [
},
xAxis: {
show: false
},
yAxis: {
show: false
},
grid: {
left: '10px',
right: '20px'
},
color: ['#FF9100', '#FFBF00', config.layout.elementUiPrimary[0]],
options: {
dataZoom: null,
title: [
{ {
text: '暂态事件统计', name: '电压中断',
left: 'center' value: rawData.eventOff || 0
}, },
{ {
text: data[0].value + data[1].value + data[2].value + '次', name: '电压暂降',
left: 'center', value: rawData.eventDown || 0
top: 'center' },
}
],
series: [
{ {
type: 'pie', name: '电压暂升',
center: 'center', value: rawData.eventUp || 0
radius: ['50%', '70%'],
label: {
show: false,
position: 'outside',
textStyle: {
//数值样式
}
},
name: '事件统计',
data: data
} }
] ]
}
}) echartList.value = {
title: {},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
top: '50',
right: '10',
formatter: function (e: any) {
return e + ' ' + data.value.filter((item: any) => item.name == e)[0].value + '次'
}
},
xAxis: {
show: false
},
yAxis: {
show: false
},
grid: {
left: '10px',
right: '20px'
},
color: ['#FF9100', '#FFBF00', config.layout.elementUiPrimary[0]],
options: {
dataZoom: null,
title: [
{
text: '暂态事件统计',
left: 'center'
},
{
text: rawData.eventOff + rawData.eventDown + rawData.eventUp + '次',
left: 'center',
top: 'center'
}
],
series: [
{
type: 'pie',
center: 'center',
radius: ['50%', '70%'],
label: {
show: false,
position: 'outside'
},
name: '事件统计',
data: data.value
}
]
}
}
})
}
const transientStatisticsDetailRef = ref() const transientStatisticsDetailRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree', url: '/cs-harmonic-boot/csevent/netEventTable',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
@@ -163,75 +180,40 @@ const tableStore: any = new TableStore({
{ {
title: '电压中断(次)', title: '电压中断(次)',
field: 'type', field: 'eventOff',
minWidth: '70', minWidth: '70',
sortable: true,
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.eventOff}</span>`
} }
}, },
{ {
title: '电压暂降(次)', title: '电压暂降(次)',
field: 'type1', field: 'eventDown',
minWidth: '80', minWidth: '80',
sortable: true,
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type1}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.eventDown}</span>`
} }
}, },
{ {
title: '电压暂升(次)', title: '电压暂升(次)',
field: 'type2', field: 'eventUp',
minWidth: '80', minWidth: '80',
sortable: true,
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type2}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.eventUp}</span>`
} }
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
// 尝试从缓存获取时间值 setTime()
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.data = [ eventEcharts()
{
name: '35kV1进线',
type: '2',
type1: '38',
type2: '35'
},
{
name: '35kV1变压器',
type: '2',
type1: '1',
type2: '3'
},
{
name: '35kV1母线',
type: '0',
type1: '1',
type2: '4'
},
{
name: '35kV2母线',
type: '0',
type1: '1',
type2: '4'
}
]
} }
}) })
@@ -243,7 +225,30 @@ provide('tableStore', tableStore)
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') { if (column.field != 'name') {
transientStatisticsDetailRef.value.open(row) transientStatisticsDetailRef.value.open(
row,
tableStore.table.params.searchBeginTime || prop.timeValue?.[0],
tableStore.table.params.searchEndTime || prop.timeValue?.[1]
)
}
}
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
} }
} }
@@ -259,15 +264,13 @@ watch(
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )
const addMenu = () => {}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -1,96 +1,113 @@
<template> <template>
<div> <div>
<!--趋势对比 --> <!--趋势对比 -->
<TableHeader :showReset="false" datePicker @selectChange="selectChange" v-if="fullscreen"> <TableHeader
datePicker
ref="TableHeaderRef"
:timeKeyList="prop.timeKey"
:showReset="false"
@selectChange="selectChange"
v-if="fullscreen"
>
<template v-slot:select> <template v-slot:select>
<el-form-item label="监测点名称"> <el-form-item label="监测对象">
<el-select <el-select
v-model="tableStore.table.params.power" filterable
placeholder="请选择监测点名称" v-model="tableStore.table.params.sensitiveUserId"
placeholder="请选择监测对象"
clearable clearable
style="width: 130px"
> >
<el-option <el-option v-for="item in idList" :key="item.id" :label="item.name" :value="item.id" />
v-for="item in powerList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- <el-form-item label="监测点名称">
<el-select v-model="tableStore.table.params.lineId" placeholder="请选择监测点名称" clearable>
<el-option
v-for="item in lineIdList"
:key="item.lineId"
:label="item.name"
:value="item.lineId"
/>
</el-select>
</el-form-item> -->
<el-form-item label="电能质量指标"> <el-form-item label="电能质量指标">
<el-select <el-select v-model="tableStore.table.params.indicator" placeholder="请选择电能质量指标" clearable>
v-model="tableStore.table.params.indicator" <el-option v-for="item in indicatorList" :key="item.id" :label="item.name" :value="item.id" />
placeholder="请选择电能质量指标"
clearable
style="width: 130px"
>
<el-option
v-for="item in indicatorList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="谐波次数"> <el-form-item>
<el-radio-group v-model="tableStore.table.params.dataLevel">
<el-radio-button label="一次值" value="Primary" />
<el-radio-button label="二次值" value="Secondary" />
</el-radio-group>
</el-form-item>
<el-form-item label="统计类型">
<el-select <el-select
v-model="tableStore.table.params.exceedingTheLimit" style="min-width: 120px !important"
placeholder="请选择谐波次数" placeholder="请选择"
clearable v-model="tableStore.table.params.valueType"
style="width: 90px"
> >
<el-option <el-option value="max" label="最大值"></el-option>
v-for="item in exceedingTheLimitList" <el-option value="min" label="最小值"></el-option>
:key="item.value" <el-option value="avg" label="平均值"></el-option>
:label="item.label" <el-option value="cp95" label="cp95"></el-option>
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item>
<div v-if="shouldShowHarmonicCount()" style="display: flex; color: var(--el-text-color-regular)">
<span style="width: 160px">{{ getHarmonicTypeName() }}谐波次数</span>
<el-select
v-model="tableStore.table.params.harmonicCount"
placeholder="请选择谐波次数"
style="min-width: 80px !important"
>
<el-option
v-for="num in harmonicCountOptions"
:key="num"
:label="num"
:value="num"
></el-option>
</el-select>
</div>
</el-form-item>
</template> </template>
</TableHeader> </TableHeader>
<my-echart <my-echart
v-loading="tableStore.table.loading"
class="tall" class="tall"
:options="echartList" :options="echartList"
:style="{ width: prop.width, height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)` }" :style="{
width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}"
/> />
<!-- <el-empty description="暂无数据" /> -->
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h, computed } from 'vue' import { ref, onMounted, provide, reactive, watch, h, computed, nextTick } from 'vue'
import TableStore from '@/utils/tableStore' import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { useRoute } from 'vue-router' import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { useTimeCacheStore } from '@/stores/timeCache' import { getListByIds } from '@/api/harmonic-boot/cockpit/cockpit'
import { getTime } from '@/utils/formatTime'
import { yMethod, exportCSV } from '@/utils/echartMethod'
import { max } from 'lodash'
const prop = defineProps({ const prop = defineProps({
w: { type: String }, w: { type: [String, Number] },
h: { type: String }, h: { type: [String, Number] },
width: { type: String }, width: { type: [String, Number] },
height: { type: String }, height: { type: [String, Number] },
timeKey: { type: String }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const route = useRoute() const TableHeaderRef = ref()
const timeCacheStore = useTimeCacheStore()
const config = useConfig() const config = useConfig()
const powerList: any = ref([
{
label: '1#变压器',
value: '1'
},
{
label: '2#变压器',
value: '2'
}
])
// 计算是否全屏展示 // 计算是否全屏展示
const fullscreen = computed(() => { const fullscreen = computed(() => {
const w = Number(prop.w) const w = Number(prop.w)
@@ -102,197 +119,355 @@ const fullscreen = computed(() => {
return false return false
} }
}) })
const exceedingTheLimitList: any = ref([ // 添加谐波次数选项2-50
{ const harmonicCountOptions = ref(Array.from({ length: 49 }, (_, i) => i + 2))
label: '2次',
value: '2'
},
{
label: '3次',
value: '3'
},
{
label: '4次',
value: '4'
},
{
label: '5次',
value: '5'
}
])
const indicatorList: any = ref([
{
label: '谐波电压总畸变率',
value: '1'
},
{
label: '各次谐波电压',
value: '2'
},
{
label: '各次谐波电压',
value: '3'
},
{
label: '三相电压不平衡',
value: '4'
}
])
const echartList = ref({
title: {
text: '趋势对比'
},
xAxis: { const indicatorList = ref()
type: 'time',
axisLabel: { const echartList = ref()
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: {},
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'line',
name: '治理前',
showSymbol: false,
smooth: true,
data: [
['2025-10-16 07:00:00', 1],
['2025-10-16 07:15:00', 1],
['2025-10-16 07:30:00', 1],
['2025-10-16 07:45:00', 1],
['2025-10-16 08:00:00', 3],
['2025-10-16 08:15:00', 5],
['2025-10-16 08:30:00', 6],
['2025-10-16 08:45:00', 7],
['2025-10-16 09:00:00', 10],
['2025-10-16 09:15:00', 12],
['2025-10-16 09:30:00', 13],
['2025-10-16 09:45:00', 14],
['2025-10-16 10:00:00', 16],
['2025-10-16 10:15:00', 16],
['2025-10-16 10:30:00', 13],
['2025-10-16 10:45:00', 12],
['2025-10-16 11:00:00', 14],
['2025-10-16 11:15:00', 8],
['2025-10-16 11:30:00', 7],
['2025-10-16 11:45:00', 9],
['2025-10-16 12:00:00', 6],
['2025-10-16 12:15:00', 6],
['2025-10-16 12:30:00', 6],
['2025-10-16 12:45:00', 6]
],
itemStyle: {
normal: {
//这里是颜色
color: function (params: any) {
if (params.value[1] == 0 || params.value[1] == 3.14159) {
return '#ccc'
} else {
return config.layout.elementUiPrimary[0]
}
}
}
},
yAxisIndex: 0
},
{
name: '治理后',
type: 'line',
showSymbol: false,
smooth: true,
data: [
['2025-10-16 07:00:00', 10],
['2025-10-16 07:15:00', 10],
['2025-10-16 07:30:00', 10],
['2025-10-16 07:45:00', 10],
['2025-10-16 08:00:00', 30],
['2025-10-16 08:15:00', 50],
['2025-10-16 08:30:00', 60],
['2025-10-16 08:45:00', 70],
['2025-10-16 09:00:00', 100],
['2025-10-16 09:15:00', 120],
['2025-10-16 09:30:00', 130],
['2025-10-16 09:45:00', 140],
['2025-10-16 10:00:00', 160],
['2025-10-16 10:15:00', 160],
['2025-10-16 10:30:00', 130],
['2025-10-16 10:45:00', 120],
['2025-10-16 11:00:00', 140],
['2025-10-16 11:15:00', 80],
['2025-10-16 11:30:00', 70],
['2025-10-16 11:45:00', 90],
['2025-10-16 12:00:00', 60],
['2025-10-16 12:15:00', 60],
['2025-10-16 12:30:00', 60],
['2025-10-16 12:45:00', 60]
]
}
]
}
})
const headerHeight = ref(57) const headerHeight = ref(57)
// 监测对象
const idList = ref([])
// 监测对象
const initListByIds = () => {
getListByIds({}).then((res: any) => {
if (res.data?.length > 0) {
idList.value = res.data
initCode()
} else {
tableStore.index()
}
})
}
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// 如果有传入 datePicker 的值 if (datePickerValue && datePickerValue.timeValue) {
if (datePickerValue) { // 更新时间参数
// 更新表格参数 tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0] tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
} }
} }
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
showPage: false, const initCode = () => {
exportName: '主要监测点列表', queryByCode('steady_state_limit_trend').then(res => {
column: [], queryCsDictTree(res.data.id).then(item => {
beforeSearchFun: () => { indicatorList.value = item.data
// 尝试从缓存获取时间值 tableStore.table.params.indicator = indicatorList.value[0].id
let beginTime, endTime nextTick(() => {
tableStore.index()
})
})
})
}
// 治理前
const chartsListBefore = ref()
// 治理后
const chartsListAfter = ref()
if (fullscreen.value) { const setEchart = () => {
const cached = timeCacheStore.getCache(route.path) // 从接口数据中提取治理前和治理后的数据
if (cached && cached.timeValue) { const beforeData = chartsListBefore.value || []
beginTime = cached.timeValue[0] const afterData = chartsListAfter.value || []
endTime = cached.timeValue[1]
} // 按相位分组数据
const beforeGroupedByPhase: any = {}
const afterGroupedByPhase: any = {}
// 处理治理前数据
beforeData.forEach((item: any) => {
const phase = item.phase || 'default'
if (!beforeGroupedByPhase[phase]) {
beforeGroupedByPhase[phase] = []
}
beforeGroupedByPhase[phase].push([item.time, item.statisticalData, item.unit, 'solid'])
})
// 处理治理后数据
afterData.forEach((item: any) => {
const phase = item.phase || 'default'
if (!afterGroupedByPhase[phase]) {
afterGroupedByPhase[phase] = []
}
afterGroupedByPhase[phase].push([item.time, item.statisticalData, item.unit, 'dotted'])
})
// 构建系列数据
const series: any = []
// 定义相位颜色
const phaseColors: any = {
A: '#DAA520',
B: '#2E8B57',
C: '#A52a2a'
}
// 添加治理前数据系列(实线)
Object.keys(beforeGroupedByPhase).forEach(phase => {
const phaseName = phase === 'default' ? '' : `${phase}`
const color = phaseColors[phase] || config.layout.elementUiPrimary[0]
series.push({
name: `治理前${phaseName}`,
type: 'line',
showSymbol: false,
smooth: true,
symbol: 'none',
data: beforeGroupedByPhase[phase],
itemStyle: {
normal: {
color: color
}
},
lineStyle: {
type: 'solid', // 实线
width: 2 // 线条宽度
},
yAxisIndex: 0
})
})
// 添加治理后数据系列(虚线)
Object.keys(afterGroupedByPhase).forEach(phase => {
const phaseName = phase === 'default' ? '' : `${phase}`
const color = phaseColors[phase] || config.layout.elementUiPrimary[0]
series.push({
name: `治理后${phaseName}`,
type: 'line',
showSymbol: false,
smooth: true,
symbol: 'none',
data: afterGroupedByPhase[phase],
itemStyle: {
normal: {
color: color
}
},
lineStyle: {
type: 'dashed', // 虚线
width: 2 // 线条宽度
},
yAxisIndex: 0
})
})
// 获取指标名称用于图表标题
let titleText = '治理前后对比'
if (beforeData.length > 0 && beforeData[0].anotherName) {
titleText = beforeData[0].anotherName
} else if (afterData.length > 0 && afterData[0].anotherName) {
titleText = afterData[0].anotherName
}
// statisticalData
// chartsListBefore.value.map((item: any) => item.statisticalData)
// chartsListAfter.value = tableStore.table.data.after
// 构建图例数据
const legendData = series.map((item: any, index: number) => {
let color = config.layout.elementUiPrimary[0]
const name = item.name
if (name.includes('A相')) {
color = '#DAA520'
} else if (name.includes('B相')) {
color = '#2E8B57'
} else if (name.includes('C相')) {
color = '#A52a2a'
} }
// 如果缓存中没有则使用默认值 // 判断是治理前还是治理后,设置不同的线条样式
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0] const isBefore = name.includes('治理前')
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1] return {
name: item.name,
icon: isBefore
? 'rect'
: 'path://M0,2 L8,2 L8,6 L0,6 Z M12,2 L20,2 L20,6 L12,6 Z M24,2 L32,2 L32,6 L24,6 Z M36,2 L44,2 L44,6 L36,6 Z', // 矩形组成的粗虚线
itemStyle: {
color: color // 明确指定图例图标的颜色
},
lineStyle: {
type: isBefore ? 'solid' : 'dashed', // 治理前实线,治理后虚线
width: 2
}
}
})
let [min, max] = yMethod(
[...chartsListBefore.value.map((item: any) => item.statisticalData),
...chartsListAfter.value.map((item: any) => item.statisticalData)]
)
echartList.value = {
title: {
text: titleText
},
tooltip: {
axisPointer: {
type: 'cross',
label: {
color: '#fff',
fontSize: 16
}
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter(params: any) {
const xname = params[0].value[0]
let str = `${xname}<br>`
params.forEach((el: any, index: any) => {
let marker = ''
marker = `<span style="display:inline-block;border: 2px ${el.color} ${el.value[3]};margin-right:5px;width:40px;height:0px;background-color:#ffffff00;"></span>`
str += `${marker}${el.seriesName.split('(')[0]}${
el.value[1] != null ? el.value[1] + ' ' + (el.value[2] == null ? '' : el.value[2]) : '-'
}<br>`
})
return str
}
},
legend: {
data: legendData,
icon: 'rect',
itemWidth: 18,
itemHeight: 3,
itemStyle: {
borderWidth: 0
},
lineStyle: {
width: 2 // 确保图例线条宽度与系列线条宽度一致
}
},
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: {
name: beforeData.length > 0 ? beforeData[0].unit : afterData.length > 0 ? afterData[0].unit : '',
max: max,
min: min,
},
grid: {
left: '10px',
right: '20px'
},
series: series
}
}
const tableStore: any = new TableStore({
url: '/cs-device-boot/csGroup/sensitiveUserTrendData',
method: 'POST',
showPage: false,
exportName: '趋势对比',
column: [],
beforeSearchFun: () => {
setTime()
if (!tableStore.table.params.sensitiveUserId && idList.value?.length > 0) {
tableStore.table.params.sensitiveUserId = idList.value[0].id
}
let lists: any = []
// 处理电能质量指标
const selectedIndicator = indicatorList.value?.find(
(item: any) => item.id === tableStore.table.params.indicator
)
if (selectedIndicator) {
let frequencys = ''
if (selectedIndicator.name.includes('谐波含有率')) {
frequencys = tableStore.table.params.harmonicCount
}
lists.push({
statisticalId: tableStore.table.params.indicator,
frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : ''
})
}
// 将 lists 添加到请求参数中
tableStore.table.params.list = lists
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
// 数据加载完成后的处理
if (tableStore.table.data) {
chartsListBefore.value = tableStore.table.data.before
chartsListAfter.value = tableStore.table.data.after
setEchart()
}
} }
}) })
const tableRef = ref() tableStore.table.params.indicator = ''
provide('tableRef', tableRef) tableStore.table.params.exceedingTheLimit = ''
tableStore.table.params.power = '1' tableStore.table.params.dataLevel = 'Primary'
tableStore.table.params.indicator = '1' tableStore.table.params.valueType = 'avg'
tableStore.table.params.exceedingTheLimit = '1'
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore) provide('tableStore', tableStore)
onMounted(() => { onMounted(() => {
tableStore.index() initListByIds()
}) })
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
// 判断是否应该显示谐波次数选择框
const shouldShowHarmonicCount = () => {
if (!tableStore.table.params.indicator || !indicatorList.value) return false
const currentIndicator = indicatorList.value.find((item: any) => item.id === tableStore.table.params.indicator)
return (
currentIndicator &&
(currentIndicator.name.includes('幅值') || currentIndicator.name.includes('含有率'))
)
}
// 获取谐波类型名称
const getHarmonicTypeName = () => {
const currentIndicator = indicatorList.value.find((item: any) => item.id === tableStore.table.params.indicator)
if (currentIndicator) {
if (currentIndicator.name.includes('电压')) {
return '电压'
} else if (currentIndicator.name.includes('电流')) {
return '电流'
}
}
return ''
}
watch( watch(
() => prop.timeKey, () => prop.timeKey,
val => { val => {
@@ -300,19 +475,33 @@ watch(
} }
) )
watch( watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告) () => prop.timeValue,
(newVal, oldVal) => { (newVal, oldVal) => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true // 若 timeValue 是对象/数组,需开启深度监听 deep: true
} }
) )
const addMenu = () => {} // 监听指标变化,当指标变化时重置谐波次数
watch(
() => tableStore.table.params.indicator,
newVal => {
if (shouldShowHarmonicCount()) {
// 如果之前没有设置过谐波次数则默认设置为2
if (!tableStore.table.params.harmonicCount) {
tableStore.table.params.harmonicCount = 2
}
} else {
// 如果不是谐波含有率指标,则清除谐波次数设置
tableStore.table.params.harmonicCount = ''
}
}
)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-select) { // :deep(.el-select) {
min-width: 80px; // min-width: 80px;
} // }
</style> </style>

View File

@@ -106,13 +106,15 @@ const initChart = () => {
start: 0, start: 0,
bottom: '20px', bottom: '20px',
end: 100 end: 100,
filterMode: 'none'
}, },
{ {
start: 0, start: 0,
height: 13, height: 13,
bottom: '20px', bottom: '20px',
end: 100 end: 100,
filterMode: 'none'
} }
// { // {
// show: true, // show: true,

View File

@@ -0,0 +1,320 @@
// 辅助函数
const getMax = (temp, tempA, tempB, tempC) => {
temp = temp > tempA ? temp : tempA
temp = temp > tempB ? temp : tempB
if (tempC !== undefined) {
temp = temp > tempC ? temp : tempC
}
return temp
}
const getMaxTwo = (temp, tempA, tempB) => {
temp = temp > tempA ? temp : tempA
temp = temp > tempB ? temp : tempB
return temp
}
const getMin = (temp, tempA, tempB, tempC) => {
temp = temp < tempA ? temp : tempA
temp = temp < tempB ? temp : tempB
if (tempC !== undefined) {
temp = temp < tempC ? temp : tempC
}
return temp
}
const getMinOpen = (temp, tempA, tempB) => {
temp = temp < tempA ? temp : tempA
temp = temp < tempB ? temp : tempB
return temp
}
// 数据处理函数
const fliteWaveData = (wp, step, iphasicValue, isOpen) => {
const rmsData = wp.listRmsData
const pt = Number(wp.pt) / 1000
const ct = Number(wp.ct)
const titleList = wp.waveTitle
let xishu = pt
let aTitle = '',
bTitle = '',
cTitle = '',
unit = '电压'
let rmsvFirstX = 0,
rmsvFirstY = 0,
rmsvSecondX = 0,
rmsvSecondY = 0,
firstZhou = 'a',
secondeZhou = 'a'
let ifmax = 0,
ifmin = 0,
ismax = 0,
ismin = 0,
rfmax = 0,
rfmin = 0,
rsmax = 0,
rsmin = 0
const shunshiFA = []
const shunshiFB = []
const shunshiFC = []
const shunshiSA = []
const shunshiSB = []
const shunshiSC = []
const rmsFA = []
const rmsFB = []
const rmsFC = []
const rmsSA = []
const rmsSB = []
const rmsSC = []
if (titleList[iphasicValue * step + 1]?.substring(0, 1) !== 'U') {
xishu = ct
unit = '电流'
}
for (let i = 1; i <= iphasicValue; i++) {
switch (i) {
case 1:
aTitle = titleList[iphasicValue * step + i]?.substring(1) || ''
break
case 2:
bTitle = titleList[iphasicValue * step + i]?.substring(1) || ''
break
case 3:
cTitle = titleList[iphasicValue * step + i]?.substring(1) || ''
break
}
}
if (rmsData[0] && rmsData[0][iphasicValue * step + 1] !== undefined) {
rfmax = rmsData[0][iphasicValue * step + 1] * xishu
rfmin = rmsData[0][iphasicValue * step + 1] * xishu
rmsvFirstY = rmsData[0][iphasicValue * step + 1] * xishu
rmsvFirstX = rmsData[0][0]
rsmax = rmsData[0][iphasicValue * step + 1]
rsmin = rmsData[0][iphasicValue * step + 1]
rmsvSecondY = rmsData[0][iphasicValue * step + 1]
rmsvSecondX = rmsData[0][0]
}
for (let rms = 0; rms < rmsData.length; rms++) {
if (!rmsData[rms] || rmsData[rms][iphasicValue * step + 1] === undefined) {
break
}
switch (iphasicValue) {
case 1:
const rmsFirstA = rmsData[rms][iphasicValue * step + 1] * xishu
rmsFA.push([rmsData[rms][0], rmsFirstA])
rfmax = rfmax > rmsFirstA ? rfmax : rmsFirstA
rfmin = rfmin < rmsFirstA ? rfmin : rmsFirstA
if (rfmin < rmsvFirstY) {
rmsvFirstY = rfmin
firstZhou = 'a'
rmsvFirstX = rmsData[rms][0]
}
const rmsSecondA = rmsData[rms][iphasicValue * step + 1]
rmsSA.push([rmsData[rms][0], rmsSecondA])
rsmax = rsmax > rmsSecondA ? rsmax : rmsSecondA
rsmin = rsmin < rmsSecondA ? rsmin : rmsSecondA
if (rsmin < rmsvSecondY) {
rmsvSecondY = rsmin
secondeZhou = 'a'
rmsvSecondX = rmsData[rms][0]
}
break
case 2:
const rmsFirstA2 = rmsData[rms][iphasicValue * step + 1] * xishu
const rmsFirstB2 = rmsData[rms][iphasicValue * step + 2] * xishu
rmsFA.push([rmsData[rms][0], rmsFirstA2])
rmsFB.push([rmsData[rms][0], rmsFirstB2])
rfmax = getMaxTwo(rfmax, rmsFirstA2, rmsFirstB2)
rfmin = getMinOpen(rfmin, rmsFirstA2, rmsFirstB2)
if (rfmin < rmsvFirstY) {
rmsvFirstY = rfmin
if (rfmin === rmsFirstA2) {
firstZhou = 'a'
} else if (rfmin === rmsFirstB2) {
firstZhou = 'b'
}
rmsvFirstX = rmsData[rms][0]
}
const rmsSecondA2 = rmsData[rms][iphasicValue * step + 1]
const rmsSecondB2 = rmsData[rms][iphasicValue * step + 2]
rmsSA.push([rmsData[rms][0], rmsSecondA2])
rmsSB.push([rmsData[rms][0], rmsSecondB2])
rsmax = getMaxTwo(rsmax, rmsSecondA2, rmsSecondB2)
rsmin = getMinOpen(rsmin, rmsSecondA2, rmsSecondB2)
if (rsmin < rmsvSecondY) {
rmsvSecondY = rsmin
if (rsmin === rmsSecondA2) {
secondeZhou = 'a'
} else if (rsmin === rmsSecondB2) {
secondeZhou = 'b'
}
rmsvSecondX = rmsData[rms][0]
}
break
case 3:
const rmsFirstA3 = rmsData[rms][iphasicValue * step + 1] * xishu
const rmsFirstB3 = rmsData[rms][iphasicValue * step + 2] * xishu
const rmsFirstC3 = rmsData[rms][iphasicValue * step + 3] * xishu
rmsFA.push([rmsData[rms][0], rmsFirstA3])
rmsFB.push([rmsData[rms][0], rmsFirstB3])
rmsFC.push([rmsData[rms][0], rmsFirstC3])
rfmax = getMax(rfmax, rmsFirstA3, rmsFirstB3, rmsFirstC3)
rfmin = isOpen
? getMinOpen(rfmin, rmsFirstA3, rmsFirstC3)
: getMin(rfmin, rmsFirstA3, rmsFirstB3, rmsFirstC3)
if (rfmin < rmsvFirstY) {
rmsvFirstY = rfmin
if (rfmin === rmsFirstA3) {
firstZhou = 'a'
} else if (rfmin === rmsFirstB3) {
firstZhou = 'b'
} else {
firstZhou = 'c'
}
rmsvFirstX = rmsData[rms][0]
}
const rmsSecondA3 = rmsData[rms][iphasicValue * step + 1]
const rmsSecondB3 = rmsData[rms][iphasicValue * step + 2]
const rmsSecondC3 = rmsData[rms][iphasicValue * step + 3]
rmsSA.push([rmsData[rms][0], rmsSecondA3])
rmsSB.push([rmsData[rms][0], rmsSecondB3])
rmsSC.push([rmsData[rms][0], rmsSecondC3])
rsmax = getMax(rsmax, rmsSecondA3, rmsSecondB3, rmsSecondC3)
rsmin = isOpen
? getMinOpen(rsmin, rmsSecondA3, rmsSecondC3)
: getMin(rsmin, rmsSecondA3, rmsSecondB3, rmsSecondC3)
if (rsmin < rmsvSecondY) {
rmsvSecondY = rsmin
if (rsmin === rmsSecondA3) {
secondeZhou = 'a'
} else if (rsmin === rmsSecondB3) {
secondeZhou = 'b'
} else {
secondeZhou = 'c'
}
rmsvSecondX = rmsData[rms][0]
}
break
}
}
const instantF = { max: ifmax, min: ifmin }
const instantS = { max: ismax, min: ismin }
const RMSF = { max: rfmax, min: rfmin }
const RMSS = { max: rsmax, min: rsmin }
const RMSFMinDetail = { rmsvFirstX, rmsvFirstY, firstZhou }
const RMSSMinDetail = { rmsvSecondX, rmsvSecondY, secondeZhou }
const shunshiF = { shunshiFA, shunshiFB, shunshiFC }
const shunshiS = { shunshiSA, shunshiSB, shunshiSC }
const RMSFWave = { rmsFA, rmsFB, rmsFC }
const RMSSWave = { rmsSA, rmsSB, rmsSC }
const title = { aTitle, bTitle, cTitle, unit }
return {
instantF,
instantS,
RMSF,
RMSS,
RMSFMinDetail,
RMSSMinDetail,
shunshiF,
shunshiS,
RMSFWave,
RMSSWave,
title,
unit
}
}
// 监听消息
self.onmessage = function (e) {
const { wp, isOpen, value, boxoList } = JSON.parse(e.data)
try {
const iphasicValue = wp.iphasic || 1
const picCounts = (wp.waveTitle.length - 1) / iphasicValue
const waveDatas = []
for (let i = 0; i < picCounts; i++) {
const data = fliteWaveData(wp, i, iphasicValue, isOpen, boxoList)
waveDatas.push(data)
}
// 处理标题
let titles = ''
if (boxoList.systemType == 'pms') {
titles =
'变电站名称:' +
boxoList.powerStationName +
' 监测点名称:' +
boxoList.measurementPointName +
' 发生时刻:' +
boxoList.startTime +
' 暂降(骤升)幅值:' +
(boxoList.featureAmplitude * 100).toFixed(2) +
'%  持续时间:' +
boxoList.duration +
's'
} else if (boxoList.systemType == 'ZL') {
titles =
(boxoList.engineeringName == undefined ? '' : ' 项目名称:' + boxoList.engineeringName) +
' 监测点名称:' +
boxoList.equipmentName +
' 发生时刻:' +
boxoList.startTime +
' 暂降(骤升)幅值:' +
boxoList.evtParamVVaDepth +
'% 持续时间:' +
boxoList.evtParamTm +
's'
} else if (boxoList.systemType == 'YPT') {
titles =
(boxoList.engineeringName == undefined ? '' : ' 项目名称:' + boxoList.engineeringName) +
' 监测点名称:' +
boxoList.lineName +
' 发生时刻:' +
boxoList.startTime +
' 暂降(骤升)幅值:' +
(boxoList.featureAmplitude * 100).toFixed(2) +
'% 持续时间:' +
boxoList.persistTime +
's'
} else {
titles =
' 变电站名称:' +
boxoList.subName +
' 监测点名称:' +
boxoList.lineName +
' 发生时刻:' +
boxoList.startTime +
' 暂降(骤升)幅值:' +
(boxoList.featureAmplitude * 100).toFixed(2) +
'% 持续时间:' +
boxoList.duration +
's'
}
// 发送处理结果回主线程
self.postMessage({
titles: titles,
success: true,
waveDatas,
time: wp.time,
type: wp.waveType,
severity: wp.yzd,
iphasic: iphasicValue
})
} catch (error) {
self.postMessage({
success: false,
error: error.message
})
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,205 @@
// waveData.worker.js
self.addEventListener('message', function (e) {
const { wp, value, iphasic, isOpen, boxoList } = JSON.parse(e.data)
// 处理波形数据的函数
const fliteWaveData = (wp, step) => {
// 将原有的fliteWaveData函数实现复制到这里
const shunData = wp.listWaveData
const pt = Number(wp.pt) / 1000
const ct = Number(wp.ct)
const titleList = wp.waveTitle
let xishu = pt
let aTitle = '',
bTitle = '',
cTitle = '',
unit = '电压'
let ifmax = 0,
ifmin = 0,
ismax = 0,
ismin = 0
const shunshiFA = []
const shunshiFB = []
const shunshiFC = []
const shunshiSA = []
const shunshiSB = []
const shunshiSC = []
if (shunData.length > 0) {
if (titleList[iphasic * step + 1]?.substring(0, 1) !== 'U') {
xishu = ct
unit = '电流'
}
for (let i = 1; i <= iphasic; i++) {
switch (i) {
case 1:
aTitle = titleList[iphasic * step + i]?.substring(1) || ''
break
case 2:
bTitle = titleList[iphasic * step + i]?.substring(1) || ''
break
case 3:
cTitle = titleList[iphasic * step + i]?.substring(1) || ''
break
}
}
if (shunData[0][iphasic * step + 1] !== undefined) {
ifmax = shunData[0][iphasic * step + 1] * xishu
ifmin = shunData[0][iphasic * step + 1] * xishu
ismax = shunData[0][iphasic * step + 1]
ismin = shunData[0][iphasic * step + 1]
}
for (let shun = 0; shun < shunData.length; shun++) {
if (shunData[shun][iphasic * step + 1] === undefined) {
break
}
switch (iphasic) {
case 1:
const shunFirstA = shunData[shun][iphasic * step + 1] * xishu
shunshiFA.push([shunData[shun][0], shunFirstA])
ifmax = Math.max(ifmax, shunFirstA)
ifmin = Math.min(ifmin, shunFirstA)
const shunSecondA = shunData[shun][iphasic * step + 1]
shunshiSA.push([shunData[shun][0], shunSecondA])
ismax = Math.max(ismax, shunSecondA)
ismin = Math.min(ismin, shunSecondA)
break
case 2:
const shunFirstA2 = shunData[shun][iphasic * step + 1] * xishu
const shunFirstB2 = shunData[shun][iphasic * step + 2] * xishu
shunshiFA.push([shunData[shun][0], shunFirstA2])
shunshiFB.push([shunData[shun][0], shunFirstB2])
ifmax = Math.max(ifmax, shunFirstA2, shunFirstB2)
ifmin = Math.min(ifmin, shunFirstA2, shunFirstB2)
const shunSecondA2 = shunData[shun][iphasic * step + 1]
const shunSecondB2 = shunData[shun][iphasic * step + 2]
shunshiSA.push([shunData[shun][0], shunSecondA2])
shunshiSB.push([shunData[shun][0], shunSecondB2])
ismax = Math.max(ismax, shunSecondA2, shunSecondB2)
ismin = Math.min(ismin, shunSecondA2, shunSecondB2)
break
case 3:
const shunFirstA3 = shunData[shun][iphasic * step + 1] * xishu
const shunFirstB3 = shunData[shun][iphasic * step + 2] * xishu
const shunFirstC3 = shunData[shun][iphasic * step + 3] * xishu
shunshiFA.push([shunData[shun][0], shunFirstA3])
shunshiFB.push([shunData[shun][0], shunFirstB3])
shunshiFC.push([shunData[shun][0], shunFirstC3])
ifmax = Math.max(ifmax, shunFirstA3, shunFirstB3, shunFirstC3)
ifmin = isOpen
? Math.min(ifmin, shunFirstA3, shunFirstC3)
: Math.min(ifmin, shunFirstA3, shunFirstB3, shunFirstC3)
const shunSecondA3 = shunData[shun][iphasic * step + 1]
const shunSecondB3 = shunData[shun][iphasic * step + 2]
const shunSecondC3 = shunData[shun][iphasic * step + 3]
shunshiSA.push([shunData[shun][0], shunSecondA3])
shunshiSB.push([shunData[shun][0], shunSecondB3])
shunshiSC.push([shunData[shun][0], shunSecondC3])
ismax = Math.max(ismax, shunSecondA3, shunSecondB3, shunSecondC3)
ismin = isOpen
? Math.min(ismin, shunSecondA3, shunSecondC3)
: Math.min(ismin, shunSecondA3, shunSecondB3, shunSecondC3)
break
}
}
}
const instantF = { max: ifmax, min: ifmin }
const instantS = { max: ismax, min: ismin }
const shunshiF = { shunshiFA, shunshiFB, shunshiFC }
const shunshiS = { shunshiSA, shunshiSB, shunshiSC }
const title = { aTitle, bTitle, cTitle, unit }
return { instantF, instantS, shunshiF, shunshiS, title, unit }
}
// 处理标题
let titles = ''
if (boxoList.systemType == 'pms') {
titles =
'变电站名称:' +
boxoList.powerStationName +
' 监测点名称:' +
boxoList.measurementPointName +
' 发生时刻:' +
boxoList.startTime +
' 暂降(骤升)幅值:' +
(boxoList.featureAmplitude * 100).toFixed(2) +
'% 持续时间:' +
boxoList.duration +
's'
} else if (boxoList.systemType == 'ZL') {
titles =
(boxoList.engineeringName == undefined ? '' : ' 项目名称:' + boxoList.engineeringName) +
' 监测点名称:' +
boxoList.equipmentName +
' 发生时刻:' +
boxoList.startTime +
' 暂降(骤升)幅值:' +
boxoList.evtParamVVaDepth +
'% 持续时间:' +
boxoList.evtParamTm +
's'
} else if (boxoList.systemType == 'YPT') {
titles =
(boxoList.engineeringName == undefined ? '' : ' 项目名称:' + boxoList.engineeringName) +
' 监测点名称:' +
boxoList.lineName +
' 发生时刻:' +
boxoList.startTime +
' 暂降(骤升)幅值:' +
(boxoList.featureAmplitude * 100).toFixed(2) +
'% 持续时间:' +
boxoList.persistTime +
's'
} else {
titles =
'变电站名称:' +
boxoList.subName +
' 监测点名称:' +
boxoList.lineName +
' 发生时刻:' +
boxoList.startTime +
' 暂降(骤升)幅值:' +
(boxoList.featureAmplitude * 100).toFixed(2) +
'% 持续时间:' +
boxoList.duration +
's'
}
const iphasicValue = wp.iphasic || 1
const picCounts = (wp.waveTitle.length - 1) / iphasicValue
const waveDatas = []
for (let i = 0; i < picCounts; i++) {
const data = fliteWaveData(wp, i)
waveDatas.push(data)
}
const time = wp.time
const type = wp.waveType
let severity = wp.yzd
if (severity < 0) {
severity = '/'
type = '/'
}
// 将处理结果发送回主线程
self.postMessage({
waveDatas,
time,
type,
severity,
titles,
iphasic: iphasicValue
})
})

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,8 @@
style="min-width: 90px; width: 90px; margin-right: 10px" style="min-width: 90px; width: 90px; margin-right: 10px"
@change="timeChange" @change="timeChange"
> >
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in filteredTimeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
<el-date-picker <el-date-picker
v-model.trim="timeValue" v-model.trim="timeValue"
type="daterange" type="daterange"
@@ -21,6 +20,7 @@
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
:shortcuts="shortcuts" :shortcuts="shortcuts"
/> />
<el-button :disabled="backDisabled" type="primary" :icon="DArrowLeft" @click="preClick"></el-button> <el-button :disabled="backDisabled" type="primary" :icon="DArrowLeft" @click="preClick"></el-button>
<el-button type="primary" :icon="VideoPause" @click="nowTime">当前</el-button> <el-button type="primary" :icon="VideoPause" @click="nowTime">当前</el-button>
<el-button :disabled="preDisabled" type="primary" :icon="DArrowRight" @click="next"></el-button> <el-button :disabled="preDisabled" type="primary" :icon="DArrowRight" @click="next"></el-button>
@@ -36,13 +36,15 @@ interface Props {
theCurrentTime?: boolean theCurrentTime?: boolean
initialInterval?: number initialInterval?: number
initialTimeValue?: any initialTimeValue?: any
timeKeyList?: string[] //日期下拉
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
nextFlag: false, nextFlag: false,
theCurrentTime: true, theCurrentTime: true,
initialInterval: 3, initialInterval: 3,
initialTimeValue: undefined initialTimeValue: undefined,
timeKeyList: () => []
}) })
const emit = defineEmits(['change']) const emit = defineEmits(['change'])
@@ -90,6 +92,16 @@ const shortcuts = [
} }
} }
] ]
// 计算过滤后的 timeOptions
const filteredTimeOptions = computed(() => {
if (!props.timeKeyList || props.timeKeyList.length === 0) {
return timeOptions.value
}
return timeOptions.value.filter((option: any) => props.timeKeyList.includes(option.value.toString()))
})
onMounted(() => { onMounted(() => {
// 使用传入的初始值 // 使用传入的初始值
if (props.initialInterval !== undefined) { if (props.initialInterval !== undefined) {
@@ -99,9 +111,32 @@ onMounted(() => {
if (props.initialTimeValue) { if (props.initialTimeValue) {
timeValue.value = props.initialTimeValue timeValue.value = props.initialTimeValue
} }
nextTick(() => {
// 初始化时检查按钮状态
checkInitialButtonStatus()
})
timeChange(3) timeChange(3)
}) })
// 添加初始化按钮状态检查方法
const checkInitialButtonStatus = () => {
if (timeValue.value && timeValue.value.length >= 2) {
const endTime = timeValue.value[1]
const currentDate = window.XEUtils.toDateString(new Date(), 'yyyy-MM-dd')
// 只有当 props.nextFlag 为 false 时才应用限制
if (!props.nextFlag) {
// 如果结束时间早于当前日期则按钮可用preDisabled = false
// 如果结束时间晚于或等于当前日期则按钮禁用preDisabled = true
const endDateTime = new Date(endTime).getTime()
const currentDateTime = new Date(currentDate).getTime()
preDisabled.value = endDateTime >= currentDateTime
}
}
}
// 添加统一的事件触发方法 // 添加统一的事件触发方法
const emitChange = () => { const emitChange = () => {
nextTick(() => { nextTick(() => {
@@ -115,11 +150,9 @@ const emitChange = () => {
// 选择时间范围 // 选择时间范围
const timeChange = (e: number) => { const timeChange = (e: number) => {
backDisabled.value = false backDisabled.value = false
preDisabled.value = true
count.value = 0 count.value = 0
if (e == 1) { if (e == 1) {
disabledPicker.value = true disabledPicker.value = true
timeValue.value = [setTime(1), setTime()] timeValue.value = [setTime(1), setTime()]
} else if (e == 2) { } else if (e == 2) {
disabledPicker.value = true disabledPicker.value = true
@@ -140,7 +173,6 @@ const timeChange = (e: number) => {
} else if (e == 5) { } else if (e == 5) {
disabledPicker.value = false disabledPicker.value = false
backDisabled.value = true backDisabled.value = true
preDisabled.value = true
timeValue.value = [setTime(), setTime()] timeValue.value = [setTime(), setTime()]
} }
if (e == 1 || e == 2) { if (e == 1 || e == 2) {
@@ -148,6 +180,12 @@ const timeChange = (e: number) => {
} else { } else {
timeFlag.value = 1 timeFlag.value = 1
} }
nextTick(() => {
// 检查按钮状态
checkInitialButtonStatus()
})
// 触发 change 事件 // 触发 change 事件
emitChange() emitChange()
} }

View File

@@ -1,35 +1,36 @@
<template> <template>
<div class="mac-address-input" :class="{ disabled: disabled }"> <div class="mac-address-input" :class="{ disabled: disabled }">
<el-input <el-input
ref="inputRef" ref="inputRef"
v-model="macValue" placeholder="请输入设备mac地址"
type="text" v-model="macValue"
maxlength="17" type="text"
:disabled="disabled" maxlength="17"
@input="handleInput" :disabled="disabled"
@keydown="handleKeydown" @input="handleInput"
@focus="handleFocus" @keydown="handleKeydown"
@blur="handleBlur" @focus="handleFocus"
@paste="handlePaste" @blur="handleBlur"
/> @paste="handlePaste"
</div> />
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
interface Props { interface Props {
modelValue?: string modelValue?: string
disabled?: boolean disabled?: boolean
} }
interface Emits { interface Emits {
(e: 'update:modelValue', value: string): void (e: 'update:modelValue', value: string): void
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
modelValue: '', modelValue: '',
disabled: false disabled: false
}) })
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
@@ -42,35 +43,35 @@ const macValue = ref<string>('')
// 解析传入的MAC地址 // 解析传入的MAC地址
const parseMacAddress = (mac: string): string => { const parseMacAddress = (mac: string): string => {
if (!mac) return '' if (!mac) return ''
// 移除非十六进制字符并转为大写 // 移除非十六进制字符并转为大写
const cleanMac = mac.replace(/[^0-9a-fA-F]/g, '').toUpperCase() const cleanMac = mac.replace(/[^0-9a-fA-F]/g, '').toUpperCase()
// 按每2个字符分割并用冒号连接 // 按每2个字符分割并用冒号连接
let result = '' let result = ''
for (let i = 0; i < cleanMac.length; i += 2) { for (let i = 0; i < cleanMac.length; i += 2) {
if (i > 0) result += ':' if (i > 0) result += ':'
result += cleanMac.substr(i, 2) result += cleanMac.substr(i, 2)
} }
return result.substring(0, 17) // 最多17个字符 (12个数字+5个冒号) return result.substring(0, 17) // 最多17个字符 (12个数字+5个冒号)
} }
// 格式化MAC地址 - 改进版 // 格式化MAC地址 - 改进版
const formatMac = (value: string): string => { const formatMac = (value: string): string => {
// 移除所有冒号 // 移除所有冒号
const cleanValue = value.replace(/:/g, '') const cleanValue = value.replace(/:/g, '')
// 只保留十六进制字符并转为大写 // 只保留十六进制字符并转为大写
const hexOnly = cleanValue.replace(/[^0-9a-fA-F]/g, '').toUpperCase() const hexOnly = cleanValue.replace(/[^0-9a-fA-F]/g, '').toUpperCase()
// 按每两个字符添加冒号最多6段 // 按每两个字符添加冒号最多6段
let formatted = '' let formatted = ''
for (let i = 0; i < Math.min(hexOnly.length, 12); i += 2) { for (let i = 0; i < Math.min(hexOnly.length, 12); i += 2) {
if (i > 0) formatted += ':' if (i > 0) formatted += ':'
formatted += hexOnly.substr(i, 2) formatted += hexOnly.substr(i, 2)
} }
return formatted return formatted
} }
// 当前聚焦的输入框索引 // 当前聚焦的输入框索引
@@ -78,88 +79,86 @@ const focusedIndex = ref<number | null>(null)
// 处理输入事件 // 处理输入事件
const handleInput = (value: string) => { const handleInput = (value: string) => {
const formatted = formatMac(value) const formatted = formatMac(value)
macValue.value = formatted macValue.value = formatted
// 发出不带冒号的纯净值 // 发出不带冒号的纯净值
emit('update:modelValue', formatted.replace(/:/g, '')) emit('update:modelValue', formatted.replace(/:/g, ''))
} }
// 处理键盘事件 // 处理键盘事件
const handleKeydown = (event: KeyboardEvent) => { const handleKeydown = (event: KeyboardEvent) => {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
// 处理退格键 // 处理退格键
if (event.key === 'Backspace') { if (event.key === 'Backspace') {
// 处理在冒号前删除的情况 // 处理在冒号前删除的情况
const cursorPos = target.selectionStart || 0 const cursorPos = target.selectionStart || 0
if (cursorPos > 0 && macValue.value[cursorPos - 1] === ':' && if (cursorPos > 0 && macValue.value[cursorPos - 1] === ':' && target.selectionStart === target.selectionEnd) {
target.selectionStart === target.selectionEnd) { event.preventDefault()
event.preventDefault() // 删除冒号前的两个字符
// 删除冒号前的两个字符 const newValue = macValue.value.substring(0, cursorPos - 3) + macValue.value.substring(cursorPos)
const newValue = macValue.value.substring(0, cursorPos - 3) + macValue.value = newValue
macValue.value.substring(cursorPos) // 设置光标位置
macValue.value = newValue setTimeout(() => {
// 设置光标位置 if (target.setSelectionRange) {
setTimeout(() => { target.setSelectionRange(cursorPos - 3, cursorPos - 3)
if (target.setSelectionRange) { }
target.setSelectionRange(cursorPos - 3, cursorPos - 3) }, 0)
emit('update:modelValue', newValue.replace(/:/g, ''))
} }
}, 0)
emit('update:modelValue', newValue.replace(/:/g, ''))
} }
}
} }
// 处理焦点事件 // 处理焦点事件
const handleFocus = () => { const handleFocus = () => {
focusedIndex.value = 0 focusedIndex.value = 0
} }
// 处理失焦事件 // 处理失焦事件
const handleBlur = () => { const handleBlur = () => {
focusedIndex.value = null focusedIndex.value = null
} }
// 处理粘贴事件 // 处理粘贴事件
const handlePaste = (event: ClipboardEvent) => { const handlePaste = (event: ClipboardEvent) => {
event.preventDefault() event.preventDefault()
const pastedText = event.clipboardData?.getData('text') || '' const pastedText = event.clipboardData?.getData('text') || ''
// 清理粘贴的文本 // 清理粘贴的文本
const cleanPastedText = pastedText.replace(/[^0-9a-fA-F]/g, '').toUpperCase() const cleanPastedText = pastedText.replace(/[^0-9a-fA-F]/g, '').toUpperCase()
const formatted = formatMac(cleanPastedText) const formatted = formatMac(cleanPastedText)
macValue.value = formatted macValue.value = formatted
emit('update:modelValue', formatted.replace(/:/g, '')) emit('update:modelValue', formatted.replace(/:/g, ''))
} }
// 监听modelValue变化 // 监听modelValue变化
watch( watch(
() => props.modelValue, () => props.modelValue,
(newVal) => { newVal => {
const cleanNewVal = (newVal || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase() const cleanNewVal = (newVal || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase()
const currentCleanValue = macValue.value.replace(/:/g, '') const currentCleanValue = macValue.value.replace(/:/g, '')
if (cleanNewVal !== currentCleanValue) { if (cleanNewVal !== currentCleanValue) {
macValue.value = parseMacAddress(cleanNewVal) macValue.value = parseMacAddress(cleanNewVal)
} }
}, },
{ immediate: true } { immediate: true }
) )
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.mac-address-input { .mac-address-input {
width: 100%; width: 100%;
&.disabled { &.disabled {
opacity: 0.7; opacity: 0.7;
} }
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
input { input {
text-transform: uppercase; text-transform: uppercase;
font-family: inherit; // 使用继承的字体而不是等宽字体 font-family: inherit; // 使用继承的字体而不是等宽字体
}
} }
}
} }
</style> </style>

View File

@@ -21,8 +21,8 @@
<el-image <el-image
:hide-on-click-modal="true" :hide-on-click-modal="true"
:preview-teleported="true" :preview-teleported="true"
:preview-src-list="[fieldValue]" :preview-src-list="[imgList[fieldValue]]"
:src="fieldValue.length > 100 ? fieldValue : getUrl(fieldValue)" :src="fieldValue.length > 100 ? fieldValue : getUrl(fieldValue) ? imgList[fieldValue] : ''"
></el-image> ></el-image>
</div> </div>
@@ -226,10 +226,12 @@ const handlerCommand = (item: OptButton) => {
break break
} }
} }
const imgList: any = ref({})
const getUrl = (url: string) => { const getUrl = (url: string) => {
getFileUrl({ filePath: url }).then(res => { getFileUrl({ filePath: url }).then(res => {
return res.data imgList.value[url] = res.data
}) })
return true
} }
</script> </script>

View File

@@ -2,7 +2,7 @@
<div ref="tableHeader" class="cn-table-header"> <div ref="tableHeader" class="cn-table-header">
<div class="table-header ba-scroll-style" :key="num"> <div class="table-header ba-scroll-style" :key="num">
<el-form <el-form
style="flex: 1; height: 34px; margin-right: 20px; display: flex; flex-wrap: wrap" style="flex: 1; height: 34px; margin-right: 0px; display: flex; flex-wrap: wrap"
ref="headerForm" ref="headerForm"
@submit.prevent="" @submit.prevent=""
@keyup.enter="onComSearch" @keyup.enter="onComSearch"
@@ -14,9 +14,8 @@
ref="datePickerRef" ref="datePickerRef"
:nextFlag="nextFlag" :nextFlag="nextFlag"
:theCurrentTime="theCurrentTime" :theCurrentTime="theCurrentTime"
:initialInterval="initialInterval"
:initialTimeValue="initialTimeValue"
@change="handleDatePickerChange" @change="handleDatePickerChange"
:timeKeyList="props.timeKeyList"
></DatePicker> ></DatePicker>
</el-form-item> </el-form-item>
@@ -30,12 +29,27 @@
<Icon size="14" name="el-icon-ArrowUp" style="color: #fff" v-if="showSelect" /> <Icon size="14" name="el-icon-ArrowUp" style="color: #fff" v-if="showSelect" />
<Icon size="14" name="el-icon-ArrowDown" style="color: #fff" v-else /> <Icon size="14" name="el-icon-ArrowDown" style="color: #fff" v-else />
</el-button> </el-button>
<el-button @click="onComSearch" v-if="showSearch" type="primary" :icon="Search">查询</el-button> <el-button
<el-button @click="onResetForm" v-if="showSearch && showReset" :icon="RefreshLeft">重置</el-button> @click="onComSearch"
v-if="showSearch"
:loading="tableStore.table.loading"
type="primary"
:icon="Search"
>
查询
</el-button>
<el-button
@click="onResetForm"
v-if="showSearch && showReset"
:loading="tableStore.table.loading"
:icon="RefreshLeft"
>
重置
</el-button>
<el-button <el-button
@click="onExport" @click="onExport"
v-if="showExport" v-if="showExport"
:loading="tableStore.table.loading" :loading="tableStore.table.exportLoading"
type="primary" type="primary"
icon="el-icon-Download" icon="el-icon-Download"
> >
@@ -90,8 +104,8 @@ interface Props {
theCurrentTime?: boolean //控制时间前3天展示上个月时间 theCurrentTime?: boolean //控制时间前3天展示上个月时间
showReset?: boolean //是否显示重置 showReset?: boolean //是否显示重置
showExport?: boolean //导出控制 showExport?: boolean //导出控制
initialInterval?: 3 timeCacheFlag?: boolean //是否取缓存时间
initialTimeValue?: undefined timeKeyList?: string[] //日期下拉列表
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@@ -102,17 +116,16 @@ const props = withDefaults(defineProps<Props>(), {
theCurrentTime: true, theCurrentTime: true,
showReset: true, showReset: true,
showExport: false, showExport: false,
initialInterval: 3, timeCacheFlag: true,
initialTimeValue: undefined timeKeyList: () => ['1', '2', '3', '4', '5'] // 修改为箭头函数返回空数组
}) })
// 处理 DatePicker 值变化事件 // 处理 DatePicker 值变化事件
const handleDatePickerChange = (value: any) => { const handleDatePickerChange = (value: any) => {
// 将值缓存到 timeCache // 将值缓存到 timeCache
if (value) { // if (value) {
timeCacheStore.setCache(route.path, value.interval, value.timeValue) // timeCacheStore.setCache(route.path, value.interval, value.timeValue)
} // }
// 将 datePicker 的变化传递给父组件 // 将 datePicker 的变化传递给父组件
emit('selectChange', true, tableHeader.value.offsetHeight, value) emit('selectChange', true, tableHeader.value.offsetHeight, value)
@@ -139,18 +152,9 @@ const headerFormSecondStyleClose = {
onMounted(() => { onMounted(() => {
// 设置初始值到 DatePicker // 设置初始值到 DatePicker
if (props.datePicker && datePickerRef.value) { if (props.datePicker && datePickerRef.value) {
// 如果有传入的初始值,则设置到 DatePicker
if (props.initialInterval !== undefined) {
datePickerRef.value.setInterval(props.initialInterval)
}
if (props.initialTimeValue) {
datePickerRef.value.timeValue = props.initialTimeValue
}
// 从缓存中获取值并设置 // 从缓存中获取值并设置
const cached = timeCacheStore.getCache(route.path) const cached = timeCacheStore.getCache(route.path)
if (cached) { if (props.timeCacheFlag && cached) {
if (cached.interval !== undefined) { if (cached.interval !== undefined) {
datePickerRef.value.setInterval(cached.interval) datePickerRef.value.setInterval(cached.interval)
} }
@@ -245,12 +249,29 @@ const onResetForm = () => {
const setInterval = (val: any) => { const setInterval = (val: any) => {
datePickerRef.value.setInterval(val) datePickerRef.value.setInterval(val)
} }
const setTimeInterval = (val: any) => {
datePickerRef.value.timeValue = val
tableStore.table.params.searchBeginTime = val[0]
tableStore.table.params.searchEndTime = val[1]
tableStore.table.params.startTime = val[0]
tableStore.table.params.endTime = val[1]
}
// 导出 // 导出
const onExport = () => { const onExport = () => {
tableStore.onTableAction('export', { showAllFlag: true }) tableStore.onTableAction('export', { showAllFlag: true })
} }
defineExpose({ onComSearch, areaRef, setDatePicker, setInterval, datePickerRef, showSelectChange, computedSearchRow }) defineExpose({
onComSearch,
areaRef,
setDatePicker,
setInterval,
setTimeInterval,
datePickerRef,
showSelectChange,
computedSearchRow
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -1,24 +1,45 @@
<template> <template>
<div :style="{ height: typeof props.height === 'string' ? props.height : tableStore.table.height }"> <div :style="{ height: typeof props.height === 'string' ? props.height : tableStore.table.height }">
<vxe-table ref="tableRef" height="auto" :key="key" :data="tableStore.table.data" <vxe-table
v-loading="tableStore.table.loading" v-bind="Object.assign({}, defaultAttribute, $attrs)" ref="tableRef"
@checkbox-all="selectChangeEvent" @checkbox-change="selectChangeEvent" :showOverflow="showOverflow" height="auto"
:sort-config="{ remote: true }" @sort-change="handleSortChange"> :key="key"
:data="tableStore.table.data"
v-loading="tableStore.table.loading"
v-bind="Object.assign({}, defaultAttribute, $attrs)"
@checkbox-all="selectChangeEvent"
@checkbox-change="selectChangeEvent"
:showOverflow="showOverflow"
:sort-config="{ remote: true }"
@sort-change="handleSortChange"
>
<!-- Column 组件内部是 el-table-column --> <!-- Column 组件内部是 el-table-column -->
<template v-if="isGroup"> <template v-if="isGroup">
<GroupColumn :column="tableStore.table.column" /> <GroupColumn :column="tableStore.table.column" />
</template> </template>
<template v-else> <template v-else>
<Column :attr="item" :key="key + '-column'" v-for="(item, key) in tableStore.table.column" <Column
:tree-node="item.treeNode"> :attr="item"
:key="key + '-column'"
v-for="(item, key) in tableStore.table.column"
:tree-node="item.treeNode"
>
<!-- tableStore 预设的列 render 方案 --> <!-- tableStore 预设的列 render 方案 -->
<template v-if="item.render" #default="scope"> <template v-if="item.render" #default="scope">
<FieldRender :field="item" :row="scope.row" :column="scope.column" :index="scope.rowIndex" :key="key + <FieldRender
'-' + :field="item"
item.render + :row="scope.row"
'-' + :column="scope.column"
(item.field ? '-' + item.field + '-' + scope.row[item.field] : '') :index="scope.rowIndex"
" /> :key="
key +
'-' +
item.render +
'-' +
(item.field ? '-' + item.field + '-' + scope.row[item.field] : '')
"
/>
</template> </template>
</Column> </Column>
</template> </template>
@@ -27,11 +48,16 @@
</div> </div>
<div v-if="tableStore.showPage" class="table-pagination"> <div v-if="tableStore.showPage" class="table-pagination">
<el-pagination :currentPage="tableStore.table.params!.pageNum" :page-size="tableStore.table.params!.pageSize" <el-pagination
:page-sizes="pageSizes" background :currentPage="tableStore.table.params!.pageNum"
:page-size="tableStore.table.params!.pageSize"
:page-sizes="pageSizes"
background
:layout="config.layout.shrink ? 'prev, next, jumper' : 'sizes,total, ->, prev, pager, next, jumper'" :layout="config.layout.shrink ? 'prev, next, jumper' : 'sizes,total, ->, prev, pager, next, jumper'"
:total="tableStore.table.total" @size-change="onTableSizeChange" :total="tableStore.table.total"
@current-change="onTableCurrentChange"></el-pagination> @size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</div> </div>
<slot name="footer"></slot> <slot name="footer"></slot>
</template> </template>
@@ -66,6 +92,8 @@ const props = withDefaults(defineProps<Props>(), {
}) })
onMounted(() => { onMounted(() => {
tableStore.table.ref = tableRef.value as VxeTableInstance tableStore.table.ref = tableRef.value as VxeTableInstance
}) })
// console.log(props) // console.log(props)
const onTableSizeChange = (val: number) => { const onTableSizeChange = (val: number) => {
@@ -125,6 +153,7 @@ watch(
() => tableStore.table.allFlag, () => tableStore.table.allFlag,
newVal => { newVal => {
if (tableStore.table.allFlag) { if (tableStore.table.allFlag) {
console.log('🚀 ~ tableStore.table.allData:', tableStore.table.allData)
tableRef.value?.exportData({ tableRef.value?.exportData({
filename: tableStore.exportName || document.querySelectorAll('.ba-nav-tab.active')[0].textContent || '', // 文件名字 filename: tableStore.exportName || document.querySelectorAll('.ba-nav-tab.active')[0].textContent || '', // 文件名字

View File

@@ -0,0 +1,189 @@
<template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style="transition: all 0.3s; overflow: hidden">
<div class="mt10 mr10" style="display: flex; justify-content: end">
<el-button type="primary" icon="el-icon-Select" @click="save" :loading="loading">保存</el-button>
</div>
<Icon
v-show="menuCollapse"
@click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''"
size="18"
class="fold ml10 mt20 menu-collapse"
style="cursor: pointer"
/>
<div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1 }">
<div style="display: flex; align-items: center" class="mb10">
<el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
<template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" />
</template>
</el-input>
<el-tooltip placement="bottom" :hide-after="0" v-if="props.showPush">
<template #content>
<span>台账推送</span>
</template>
<Icon
name="el-icon-Promotion"
size="20"
class="fold ml10 menu-collapse"
style="cursor: pointer"
:style="{ color: config.getColorVal('elementUiPrimary') }"
@click="onAdd"
/>
</el-tooltip>
<!-- <Icon @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" v-else
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse'
style='cursor: pointer' v-if='props.canExpand' /> -->
</div>
<el-tree
:style="{ height: 'calc(100vh - 267px)' }"
style="overflow: auto"
ref="treeRef"
:props="defaultProps"
highlight-current
:default-expand-all="false"
@check-change="checkTreeNodeChange"
:filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<Icon
:name="data.icon"
style="font-size: 16px"
:style="{ color: data.color }"
v-if="data.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</div>
</template>
<script lang="ts" setup>
import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus'
import { emit } from 'process'
import { ref, watch } from 'vue'
import { t } from 'vxe-table'
import { useConfig } from '@/stores/config'
defineOptions({
name: 'govern/tree'
})
interface Props {
width?: string
canExpand?: boolean
showPush?: boolean
}
const loading = ref(false)
const props = withDefaults(defineProps<Props>(), {
width: '280px',
canExpand: true,
showPush: false
})
const config = useConfig()
const { proxy } = useCurrentInstance()
const menuCollapse = ref(false)
const filterText = ref('')
const defaultProps = {
label: 'name',
value: 'id'
}
const emit = defineEmits(['checkTreeNodeChange', 'onAdd', 'checkChange'])
watch(filterText, val => {
treeRef.value!.filter(val)
})
const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
}
const save = () => {
loading.value = true
emit('checkChange')
}
const filterNode = (value: string, data: any, node: any) => {
console.log(value, data, node, 'filterNode')
if (!value) return true
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node)
}
}
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) {
return true
}
const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了
if (level === 1) {
return false
}
// 先取当前节点的父节点
let parentData = node.parent
// 遍历当前节点的父节点
let index = 0
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true
}
// 否则的话再往上一层做匹配
parentData = parentData.parent
index++
}
// 没匹配到返回false
return false
}
const checkTreeNodeChange = () => {
// console.log(treeRef.value?.getCheckedNodes(), "ikkkkkiisiiisis");
emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes())
}
const onAdd = () => {
emit('onAdd')
}
const treeRef = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef, loading })
</script>
<style lang="scss" scoped>
.cn-tree {
flex-shrink: 0;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 10px;
height: 100%;
width: 100%;
:deep(.el-tree) {
border: 1px solid var(--el-border-color);
}
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: var(--el-color-primary-light-7);
}
.menu-collapse {
color: var(--el-color-primary);
}
}
.custom-tree-node {
display: flex;
align-items: center;
}
</style>

View File

@@ -5,7 +5,7 @@
style='cursor: pointer' /> style='cursor: pointer' />
<div class='cn-tree' :style='{ opacity: menuCollapse ? 0 : 1 }'> <div class='cn-tree' :style='{ opacity: menuCollapse ? 0 : 1 }'>
<div style='display: flex; align-items: center' class='mb10'> <div style='display: flex; align-items: center' class='mb10'>
<el-input maxlength="32" show-word-limit v-model.trim='filterText' placeholder='请输入内容' clearable> <el-input maxlength="32" v-model.trim='filterText' placeholder='请输入内容' clearable>
<template #prefix> <template #prefix>
<Icon name='el-icon-Search' style='font-size: 16px' /> <Icon name='el-icon-Search' style='font-size: 16px' />
</template> </template>
@@ -14,7 +14,7 @@
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse' :class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse'
style='cursor: pointer' v-if='props.canExpand' /> --> style='cursor: pointer' v-if='props.canExpand' /> -->
</div> </div>
<el-tree :style="{ height: 'calc(100vh - 200px)' }" <el-tree :style="{ height: 'calc(100vh - 230px)' }"
style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current :default-expand-all="false" style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current :default-expand-all="false"
@check-change="checkTreeNodeChange" :filter-node-method='filterNode' node-key='id' v-bind='$attrs'> @check-change="checkTreeNodeChange" :filter-node-method='filterNode' node-key='id' v-bind='$attrs'>
<template #default='{ node, data }'> <template #default='{ node, data }'>
@@ -32,7 +32,6 @@
<script lang='ts' setup> <script lang='ts' setup>
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus' import { ElTree } from 'element-plus'
import { emit } from 'process';
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
defineOptions({ defineOptions({

View File

@@ -1,27 +1,61 @@
<!-- 设备管理使用折叠面板渲染多个tree --> <!-- 设备管理使用折叠面板渲染多个tree -->
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style="display: flex; overflow: hidden"> <div :style="{ width: menuCollapse ? '40px' : props.width }" style="display: flex; overflow: hidden">
<Icon v-show="menuCollapse" @click="onMenuCollapse" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <Icon
:class="menuCollapse ? 'unfold' : ''" size="18" class="fold ml10 mt20 menu-collapse" v-show="menuCollapse"
style="cursor: pointer" /> @click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''"
size="18"
class="fold ml10 mt20 menu-collapse"
style="cursor: pointer"
/>
<div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1 }"> <div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1 }">
<div style="display: flex; align-items: center" class="mb10"> <div style="display: flex; align-items: center" class="mb10">
<!-- <el-form-item> --> <!-- <el-form-item> -->
<el-input maxlength="32" show-word-limit v-model.trim="filterText" autocomplete="off"
placeholder="请输入内容" clearable> <el-input
maxlength="32"
v-model.trim="filterText"
autocomplete="off"
placeholder="请输入内容"
clearable
>
<template #prepend>
<el-select v-model="treeType" @change="changeTreeType" style="min-width: 75px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<template #prefix> <template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
</el-input> </el-input>
<!-- </el-form-item> --> <!-- </el-form-item> -->
<Icon @click="onMenuCollapse" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <Icon
:class="menuCollapse ? 'unfold' : ''" size="18" class="fold ml10 menu-collapse" @click="onMenuCollapse"
style="cursor: pointer" v-if="props.canExpand" /> :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''"
size="18"
class="fold ml10 menu-collapse"
style="cursor: pointer"
v-if="props.canExpand"
/>
</div> </div>
<el-collapse :accordion="true" v-model.trim="activeName" style="flex: 1; height: 100%" <el-collapse
@change="changeDevice"> :accordion="true"
v-model.trim="activeName"
style="flex: 1; height: 100%"
@change="changeDevice"
v-if="treeType == '1'"
v-loading="loading"
>
<el-collapse-item title="治理设备" name="0" v-if="zlDeviceData.length != 0"> <el-collapse-item title="治理设备" name="0" v-if="zlDeviceData.length != 0">
<el-select v-model.trim="process" clearable placeholder="请选择状态" class="mb10"> <el-select v-model.trim="process" clearable placeholder="请选择状态" class="mb10">
<el-option label="功能调试" value="2"></el-option> <el-option label="功能调试" value="2"></el-option>
@@ -29,13 +63,30 @@
<el-option label="正式投运" value="4"></el-option> <el-option label="正式投运" value="4"></el-option>
</el-select> </el-select>
<el-tree <el-tree
:style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 278px)' }" :style="{
ref="treeRef1" :props="defaultProps" highlight-current :filter-node-method="filterNode" height:
node-key="id" :default-expand-all="false" v-bind="$attrs" :data="zlDevList" style="overflow: auto"> treeType.length != 0
? `calc(100vh - 380px - ${props.height}px)`
: 'calc(100vh - 278px)'
}"
ref="treeRef1"
:props="defaultProps"
highlight-current
:filter-node-method="filterNode"
node-key="id"
:default-expand-all="false"
v-bind="$attrs"
:data="zlDevList"
style="overflow: auto"
>
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" <Icon
v-if="data.icon" /> :name="data.icon"
style="font-size: 16px"
:style="{ color: data.color }"
v-if="data.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
@@ -43,35 +94,93 @@
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0"> <el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 280px)' : 'calc(100vh - 238px)' }" :style="{
ref="treeRef2" :props="defaultProps" highlight-current :default-expand-all="false" height:
:filter-node-method="filterNode" node-key="id" :data="bxsDeviceData" v-bind="$attrs" zlDeviceData.length != 0
style="overflow: auto"> ? `calc(100vh - 340px - ${props.height}px)`
: 'calc(100vh - 238px)'
}"
ref="treeRef2"
:props="defaultProps"
highlight-current
:default-expand-all="false"
:filter-node-method="filterNode"
node-key="id"
:data="bxsDeviceData"
v-bind="$attrs"
style="overflow: auto"
>
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" <Icon
v-if="data.icon" /> :name="data.icon"
style="font-size: 16px"
:style="{ color: data.color }"
v-if="data.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="在线设备" name="2" v-if="frontDeviceData.length != 0"> <el-collapse-item title="监测设备" name="2" v-if="frontDeviceData.length != 0">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 280px)' : 'calc(100vh - 238px)' }" :style="{
ref="treeRef3" :props="defaultProps" highlight-current :default-expand-all="false" height:
:filter-node-method="filterNode" node-key="id" :data="frontDeviceData" v-bind="$attrs" zlDeviceData.length != 0
style="overflow: auto"> ? `calc(100vh - 340px - ${props.height}px)`
: 'calc(100vh - 238px)'
}"
ref="treeRef3"
:props="defaultProps"
highlight-current
:default-expand-all="false"
:filter-node-method="filterNode"
node-key="id"
:data="frontDeviceData"
v-bind="$attrs"
style="overflow: auto"
>
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" <Icon
v-if="data.icon" /> :name="data.icon"
style="font-size: 16px"
:style="{ color: data.color }"
v-if="data.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
<div v-if="treeType == '2'" v-loading="loading">
<el-tree
:style="{ height: `calc(100vh - 188px - ${props.height}px )` }"
ref="treeRef4"
:props="defaultProps"
highlight-current
:filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
:data="data"
style="overflow: auto"
:default-expand-all="false"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<Icon
:name="data.icon"
style="font-size: 16px"
:style="{ color: data.color }"
v-if="data.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -84,20 +193,35 @@ import { ref, watch, defineEmits, onMounted, nextTick } from 'vue'
defineOptions({ defineOptions({
name: 'govern/tree' name: 'govern/tree'
}) })
const emit = defineEmits(['changeDeviceType']) const emit = defineEmits(['changeDeviceType', 'changeTreeType'])
interface Props { interface Props {
width?: string width?: string
canExpand?: boolean canExpand?: boolean
type?: string type?: string
data?: any data?: any
height?: number
engineering: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true, canExpand: true,
type: '', type: '',
data: [] data: [],
height: 0,
engineering: false
}) })
const treeType = ref('1')
const options = [
{
label: '设备',
value: '1'
},
{
label: '工程',
value: '2'
}
]
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const activeName = ref('0') const activeName = ref('0')
@@ -124,19 +248,18 @@ watch(
item.children.map((vv: any) => { item.children.map((vv: any) => {
zlDeviceData.value.push(vv) zlDeviceData.value.push(vv)
}) })
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value)) zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else if (item.name == '便携式设备') { } else if (item.name == '便携式设备') {
bxsDeviceData.value = [] bxsDeviceData.value = []
item.children.map((vv: any) => { item.children.map((vv: any) => {
bxsDeviceData.value.push(vv) bxsDeviceData.value.push(vv)
}) })
}else if (item.name == '在线设备') { } else if (item.name == '监测设备') {
frontDeviceData.value = [] frontDeviceData.value = []
item.children.map((vv: any) => { item.children.map((vv: any) => {
frontDeviceData.value.push(vv) frontDeviceData.value.push(vv)
}) })
} }
}) })
} }
@@ -148,7 +271,9 @@ watch(
) )
watch(filterText, val => { watch(filterText, val => {
if (activeName.value == '0') { if (treeType.value == '2') {
treeRef4.value!.filter(val)
} else if (activeName.value == '0') {
treeRef1.value!.filter(val) treeRef1.value!.filter(val)
} else if (activeName.value == '1') { } else if (activeName.value == '1') {
treeRef2.value!.filter(val) treeRef2.value!.filter(val)
@@ -157,8 +282,9 @@ watch(filterText, val => {
} }
}) })
watch(process, val => { watch(process, val => {
if (val == '') { if (val == '' || val == undefined) {
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value)) zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
console.log('🚀 ~ zlDevList.value:', zlDeviceData.value)
} else { } else {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value))) zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
} }
@@ -176,7 +302,7 @@ function filterProcess(nodes: any) {
const children = node.children ? filterProcess(node.children) : [] const children = node.children ? filterProcess(node.children) : []
// 如果当前节点的process=4或者有子节点满足条件则保留当前节点 // 如果当前节点的process=4或者有子节点满足条件则保留当前节点
if ( node.process == process.value || children.length > 0) { if (node.process == process.value || children.length > 0) {
return { return {
...node, ...node,
children: children children: children
@@ -228,7 +354,6 @@ const chooseNode = (value: string, data: any, node: any) => {
} }
const changeDevice = (val: any) => { const changeDevice = (val: any) => {
console.log('changeDevice', val)
let arr1: any = [] let arr1: any = []
//zlDeviceData //zlDeviceData
@@ -259,22 +384,30 @@ const changeDevice = (val: any) => {
arr2.map((item: any) => { arr2.map((item: any) => {
item.checked = false item.checked = false
}) })
treeRef1.value && treeRef1.value.setCurrentKey(arr1[0]?.id)
emit('changeDeviceType', activeName.value, arr1[0]) emit('changeDeviceType', activeName.value, arr1[0])
setTimeout(() => {
treeRef1.value?.setCurrentKey(arr1[0]?.id)
}, 100)
} }
if (val == '1') { if (val == '1') {
arr1.map((item: any) => { arr1.map((item: any) => {
item.checked = false item.checked = false
}) })
treeRef2.value && treeRef2.value.setCurrentKey(arr2[0]?.id)
emit('changeDeviceType', activeName.value, arr2[0]) emit('changeDeviceType', activeName.value, arr2[0])
setTimeout(() => {
treeRef2.value?.setCurrentKey(arr2[0]?.id)
}, 100)
} }
if (val == '2') { if (val == '2') {
arr3.map((item: any) => { arr3.map((item: any) => {
item.checked = false item.checked = false
}) })
treeRef3.value && treeRef3.value.setCurrentKey(arr3[0]?.id)
emit('changeDeviceType', activeName.value, arr3[0]) emit('changeDeviceType', activeName.value, arr3[0])
setTimeout(() => {
treeRef3.value?.setCurrentKey(arr3[0]?.id)
}, 100)
} }
} }
//治理 //治理
@@ -283,24 +416,44 @@ const treeRef1 = ref<InstanceType<typeof ElTree>>()
const treeRef2 = ref<InstanceType<typeof ElTree>>() const treeRef2 = ref<InstanceType<typeof ElTree>>()
//前置 //前置
const treeRef3 = ref<InstanceType<typeof ElTree>>() const treeRef3 = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef1, treeRef2 }) const treeRef4 = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef1, treeRef2, treeRef3, treeRef4 })
onMounted(() => { onMounted(() => {
treeType.value = props.engineering ? '2' : '1'
setTimeout(() => { setTimeout(() => {
if (zlDeviceData.value.length != 0) { setActiveName()
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
activeName.value = '0'
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length != 0) {
activeName.value = '1'
}
if (!zlDeviceData.value && !bxsDeviceData.value) {
activeName.value = ''
}
nextTick(() => {
changeDevice(activeName.value)
})
}, 500) }, 500)
}) })
const setActiveName = () => {
if (zlDeviceData.value.length != 0) {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
activeName.value = '0'
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length != 0) {
activeName.value = '1'
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length === 0) {
activeName.value = '2'
}
if (!zlDeviceData.value && !bxsDeviceData.value) {
activeName.value = ''
}
nextTick(() => {
changeDevice(activeName.value)
})
}
const loading = ref(false)
const changeTreeType = (val: string) => {
loading.value = true
emit('changeTreeType', val)
if (val == '1') {
setActiveName()
}
setTimeout(() => {
loading.value = false
}, 1000)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -330,4 +483,7 @@ onMounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
} }
:deep(.el-input-group__prepend) {
background-color: var(--el-fill-color-blank);
}
</style> </style>

View File

@@ -1,159 +1,97 @@
<template> <template>
<Tree ref="treRef" :width="width" :data="tree" default-expand-all @changePointType="changePointType" @onAdd="onAdd"/> <Tree
ref="treRef"
:width="width"
:showPush="props.showPush"
:expand-on-click-node="false"
:data="tree"
@changePointType="changePointType"
@onAdd="onAdd"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue' import { ref, nextTick, onMounted, defineProps } from 'vue'
import Tree from '../index.vue' import Tree from '../index.vue'
import { getLineTree,getCldTree } from '@/api/cs-device-boot/csLedger' import { getLineTree, getCldTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { getTemplateByDept } from '@/api/harmonic-boot/luckyexcel' import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
interface Props { interface Props {
template?: boolean template?: boolean
showPush?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
template: false template: false,
showPush: false
}) })
defineOptions({ defineOptions({
name: 'govern/deviceTree' name: 'govern/deviceTree'
}) })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy','onAdd']) const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy', 'onAdd'])
const config = useConfig() const config = useConfig()
const tree = ref() const tree = ref()
const dictData = useDictData() const dictData = useDictData()
const treRef = ref() const treRef = ref()
const width = ref('') const width = ref('')
const info = (selectedNodeId?: string) => { const info = (selectedNodeId?: string) => {
tree.value = [] tree.value = []
let arr1: any[] = [] let arr1: any[] = []
getCldTree().then(res => { getCldTree().then(res => {
try { res.data.icon = 'el-icon-Menu'
// 检查响应数据结构 res.data.color = config.getColorVal('elementUiPrimary')
let rootData = null; res.data?.children.map((item: any) => {
if (Array.isArray(res.data)) { item.icon = 'el-icon-HomeFilled'
// 旧的数据结构 - 数组 item.color = config.getColorVal('elementUiPrimary')
rootData = res.data.find((item: any) => item.name == '在线设备'); item.children.forEach((item: any) => {
} else if (res.data && res.data.name == '在线设备') { item.icon = 'el-icon-List'
// 新的数据结构 - 单个对象 item.color = config.getColorVal('elementUiPrimary')
rootData = res.data; item.children.forEach((item2: any) => {
} // item2.icon = 'el-icon-List'
// item2.color = config.getColorVal('elementUiPrimary')
// 治理设备 item2.icon = 'el-icon-Platform'
if (rootData) { item2.level = 2
rootData.icon = 'el-icon-Menu' item2.color = item2.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
rootData.level = 0 item2.children.forEach((item3: any) => {
rootData.color = config.getColorVal('elementUiPrimary') item3.icon = 'el-icon-Platform'
// 确保根节点的 children 是数组 item3.color =
if (!Array.isArray(rootData.children)) { item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
rootData.children = [] arr1.push(item3)
}
rootData.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
// 确保 children 是数组
if (!Array.isArray(item.children)) {
item.children = []
}
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.level = 2
item2.color = config.getColorVal('elementUiPrimary')
// 确保 children 是数组
if (!Array.isArray(item2.children)) {
item2.children = []
}
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 3
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// 确保 children 是数组
if (!Array.isArray(item3.children)) {
item3.children = []
}
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.level = 4
item4.color =
item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
arr1.push(item4)
})
})
}) })
}) })
tree.value = [rootData] // 确保 tree.value 是数组
} else {
tree.value = []
}
nextTick(() => {
if (arr1.length) {
// 安全检查 treRef 和 treeRef1 是否存在
if (treRef.value && treRef.value.treeRef1 && treRef.value.treeRef1.setCurrentKey) {
// 如果传入了要选中的节点ID则选中该节点否则选中第一个节点
console.log('selectedNodeId:', selectedNodeId);
if (selectedNodeId) {
treRef.value.treeRef1.setCurrentKey(selectedNodeId);
// 查找对应的节点数据并触发事件
let selectedNode = null;
const findNode = (nodes: any[]) => {
for (const node of nodes) {
if (node.id === selectedNodeId) {
selectedNode = node;
return true;
}
if (node.children && findNode(node.children)) {
return true;
}
}
return false;
};
findNode(tree.value);
if (selectedNode) {
emit('init', {
level: selectedNode.level,
...selectedNode
});
}
} else {
// 初始化选中第一个节点
treRef.value.treeRef1.setCurrentKey(arr1[0].id);
emit('init', {
level: 2,
...arr1[0]
});
}
}
} else {
}
}) })
} catch (error) { })
console.error('Error in processing getCldTree response:', error) tree.value = [res.data]
}
nextTick(() => {
setTimeout(() => {
//初始化选中
treRef.value?.treeRef.setCurrentKey(arr1[0].id)
// 注册父组件事件
emit('init', {
level: 3,
...arr1[0]
})
changePointType('4', arr1[0])
return
}, 500)
})
}) })
} }
const changePointType = (val: any, obj: any) => { const changePointType = (val: any, obj: any) => {
emit('pointTypeChange', val, obj) // emit('pointTypeChange', val, obj)
} }
const onAdd = () => { const onAdd = () => {
emit('onAdd') emit('onAdd')
} }
if (props.template) { if (props.template) {
getTemplateByDept({ id: dictData.state.area[0].id }) querySysExcel({ id: dictData.state.area[0]?.id })
.then((res: any) => { .then((res: any) => {
emit('Policy', res.data) emit('Policy', res.data)
info() info()

View File

@@ -0,0 +1,106 @@
<template>
<Tree
ref="treRef"
:width="width"
:showPush="props.showPush"
:data="tree"
default-expand-all
@changePointType="changePointType"
@onAdd="onAdd"
/>
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue'
import Tree from '../index.vue'
import { getLineTree, objTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config'
import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData'
interface Props {
template?: boolean
showPush?: boolean
}
const props = withDefaults(defineProps<Props>(), {
template: false,
showPush: false
})
defineOptions({
name: 'govern/deviceTree'
})
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy', 'onAdd'])
const config = useConfig()
const tree = ref()
const dictData = useDictData()
const treRef = ref()
const width = ref('')
const info = (selectedNodeId?: string) => {
tree.value = []
let arr1: any[] = []
objTree().then(res => {
try {
res.data.map((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item: any) => {
item.icon = 'el-icon-List'
item.level = 2
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
arr1.push(item2)
item2.icon = 'el-icon-Platform'
item2.level = 3
item2.color = config.getColorVal('elementUiPrimary')
})
})
})
tree.value = res.data
nextTick(() => {
if (arr1.length) {
//初始化选中
treRef.value.treeRef.setCurrentKey(arr1[0].id)
// 注册父组件事件
emit('init', arr1[0])
return
} else {
emit('init')
return
}
})
} catch (error) {
console.error('Error in processing getCldTree response:', error)
}
})
}
const changePointType = (val: any, obj: any) => {
emit('pointTypeChange', val, obj)
}
const onAdd = () => {
emit('onAdd')
}
if (props.template) {
querySysExcel({ id: dictData.state.area[0]?.id })
.then((res: any) => {
emit('Policy', res.data)
info()
})
.catch(err => {
info()
})
} else {
info()
}
// 暴露 info 方法给父组件调用
defineExpose({
info
})
onMounted(() => {})
</script>

View File

@@ -0,0 +1,187 @@
<template>
<Tree
ref="treRef"
:width="width"
:showPush="props.showPush"
:data="tree"
default-expand-all
@changePointType="changePointType"
@onAdd="onAdd"
/>
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue'
import Tree from '../index.vue'
import { getLineTree, lineTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config'
import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData'
interface Props {
template?: boolean
showPush?: boolean
}
const props = withDefaults(defineProps<Props>(), {
template: false,
showPush: false
})
defineOptions({
name: 'govern/deviceTree'
})
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy', 'onAdd'])
const config = useConfig()
const tree = ref()
const dictData = useDictData()
const treRef = ref()
const width = ref('')
const info = (selectedNodeId?: string) => {
tree.value = []
let arr1: any[] = []
lineTree().then(res => {
try {
// 检查响应数据结构
let rootData = null
if (Array.isArray(res.data)) {
// 旧的数据结构 - 数组
rootData = res.data.find((item: any) => item.name == '监测设备')
} else if (res.data && res.data.name == '监测设备') {
// 新的数据结构 - 单个对象
rootData = res.data
}
// 治理设备
if (rootData) {
rootData.icon = 'el-icon-Menu'
rootData.level = 0
rootData.color = config.getColorVal('elementUiPrimary')
// 确保根节点的 children 是数组
if (!Array.isArray(rootData.children)) {
rootData.children = []
}
rootData.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
// 确保 children 是数组
if (!Array.isArray(item.children)) {
item.children = []
}
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.level = 2
item2.color = config.getColorVal('elementUiPrimary')
// 确保 children 是数组
if (!Array.isArray(item2.children)) {
item2.children = []
}
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 3
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// 确保 children 是数组
if (!Array.isArray(item3.children)) {
item3.children = []
}
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.level = 4
item4.color =
item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
arr1.push(item4)
})
})
})
})
tree.value = [rootData] // 确保 tree.value 是数组
} else {
tree.value = []
}
nextTick(() => {
if (arr1.length) {
// 安全检查 treRef 和 treeRef 是否存在
console.log(
'🚀 ~ info ~ treRef.value && treRef.value.treeRef && treRef.value.treeRef.setCurrentKey:',
treRef.value && treRef.value.treeRef1 && treRef.value.treeRef1.setCurrentKey
)
if (treRef.value && treRef.value.treeRef && treRef.value.treeRef.setCurrentKey) {
// 如果传入了要选中的节点ID则选中该节点否则选中第一个节点
console.log('selectedNodeId:', selectedNodeId)
if (selectedNodeId) {
treRef.value.treeRef.setCurrentKey(selectedNodeId)
// 查找对应的节点数据并触发事件
let selectedNode = null
const findNode = (nodes: any[]) => {
for (const node of nodes) {
if (node.id === selectedNodeId) {
selectedNode = node
return true
}
if (node.children && findNode(node.children)) {
return true
}
}
return false
}
findNode(tree.value)
if (selectedNode) {
emit('init', {
level: selectedNode.level,
...selectedNode
})
}
} else {
// 初始化选中第一个节点
treRef.value.treeRef.setCurrentKey(arr1[0].id)
emit('init', {
level: 2,
...arr1[0]
})
}
}
} else {
}
})
} catch (error) {
console.error('Error in processing getCldTree response:', error)
}
})
}
const changePointType = (val: any, obj: any) => {
emit('pointTypeChange', val, obj)
}
const onAdd = () => {
emit('onAdd')
}
if (props.template) {
// id: dictData.state.area[0]?.id
querySysExcel({})
.then((res: any) => {
emit('Policy', res.data)
info()
})
.catch(err => {
info()
})
} else {
info()
}
// 暴露 info 方法给父组件调用
defineExpose({
info
})
onMounted(() => {})
</script>

View File

@@ -5,15 +5,20 @@
:default-checked-keys="defaultCheckedKeys" :default-checked-keys="defaultCheckedKeys"
:show-checkbox="props.showCheckbox" :show-checkbox="props.showCheckbox"
:data="tree" :data="tree"
:height="props.height"
@changeDeviceType="changeDeviceType" @changeDeviceType="changeDeviceType"
@changeTreeType="info"
:engineering="props.engineering"
/> />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, defineEmits } from 'vue' import { ref, nextTick } from 'vue'
import Tree from '../device.vue' import Tree from '../device.vue'
import { getDeviceTree } from '@/api/cs-device-boot/csLedger' import { getDeviceTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { throttle } from 'lodash'
defineOptions({ defineOptions({
name: 'govern/deviceTree' name: 'govern/deviceTree'
}) })
@@ -21,10 +26,14 @@ const props = withDefaults(
defineProps<{ defineProps<{
showCheckbox?: boolean showCheckbox?: boolean
defaultCheckedKeys?: any defaultCheckedKeys?: any
height?: number
engineering?: boolean
}>(), }>(),
{ {
showCheckbox: false, showCheckbox: false,
defaultCheckedKeys: [] defaultCheckedKeys: [],
height: 0,
engineering: false
} }
) )
const emit = defineEmits(['init', 'checkChange', 'deviceTypeChange']) const emit = defineEmits(['init', 'checkChange', 'deviceTypeChange'])
@@ -32,115 +41,168 @@ const config = useConfig()
const tree = ref() const tree = ref()
const treRef = ref() const treRef = ref()
const changeDeviceType = (val: any, obj: any) => { const changeDeviceType = (val: any, obj: any) => {
console.log("🚀 ~ changeDeviceType ~ val:", val,obj)
emit('deviceTypeChange', val, obj) emit('deviceTypeChange', val, obj)
} }
getDeviceTree().then(res => {
let arr: any[] = [] const info = (type?: string) => {
let arr2: any[] = [] getDeviceTree({ type: type == '2' ? 'engineering' : '' }).then(res => {
let arr3: any[] = [] let arr: any[] = []
//治理设备 let arr2: any[] = []
res.data.map((item: any) => { let arr3: any[] = []
if (item.name == '治理设备') { let arr4: any[] = []
item.children.forEach((item: any) => { //治理设备
res.data.map((item: any) => {
if (type == '2') {
item.icon = 'el-icon-HomeFilled' item.icon = 'el-icon-HomeFilled'
item.color = config.getColorVal('elementUiPrimary') item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 2
item3.color = config.getColorVal('elementUiPrimary')
if (item3.comFlag === 1) {
item3.color = '#e26257 !important'
}
arr.push(item3)
})
})
})
} else if (item.name == '便携式设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-Platform'
item.color = config.getColorVal('elementUiPrimary')
item.color = '#e26257 !important'
item.color = item.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
if (item.type == 'device') {
arr2.push(item)
}
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-Platform'
item2.color = item2.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// item2.children.forEach((item3: any) => {
// item3.icon = 'el-icon-Platform'
// item3.color = config.getColorVal('elementUiPrimary')
// if (item3.comFlag === 1) {
// item3.color = '#e26257 !important'
// }
// arr.push(item3)
// })
})
})
}else if (item.name == '在线设备') {
item.children.forEach((item: any) => { item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled' item.icon = 'el-icon-List'
item.color = config.getColorVal('elementUiPrimary') item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => { item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List' item2.icon = 'el-icon-Platform'
item2.color = config.getColorVal('elementUiPrimary') item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => { item2.color =
item3.icon = 'el-icon-Platform' item2.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item3.color = config.getColorVal('elementUiPrimary') arr4.push(item2)
if (item3.comFlag === 1) {
item3.color = '#e26257 !important'
}
arr3.push(item3)
}) })
}) })
}) } else {
} if (item.name == '治理设备') {
}) item.children.forEach((item: any) => {
console.log("🚀 ~ file: deviceTree.vue ~ line 18 ~ getDeviceTree ~ tree:", arr,arr2,arr3) item.icon = 'el-icon-HomeFilled'
tree.value = res.data item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
nextTick(() => { item2.icon = 'el-icon-List'
if (arr.length) { item2.color = config.getColorVal('elementUiPrimary')
treRef.value.treeRef1.setCurrentKey(arr[0].id) item2.children.forEach((item3: any) => {
// 注册父组件事件 item3.pName = '治理设备'
emit('init', { item3.icon = 'el-icon-Platform'
level: 2, item3.level = 2
...arr[0] item3.color = config.getColorVal('elementUiPrimary')
}) if (item3.comFlag === 1) {
return item3.color = '#e26257 !important'
} }
if (arr2.length) { arr.push(item3)
treRef.value.treeRef2.setCurrentKey(arr2[0].id) })
// 注册父组件事件 })
emit('init', { })
level: 2, } else if (item.name == '便携式设备') {
...arr2[0] item.children.forEach((item: any) => {
}) item.icon = 'el-icon-Platform'
return item.color = config.getColorVal('elementUiPrimary')
} item.color = '#e26257 !important'
console.log("🚀 ~ file: deviceTree.vue ~ line 33 ~ getDeviceTree ~ tree:", arr3.length) item.color = item.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
if (arr3.length) { // item.disabled =true
console.log("🚀 ~ file: deviceTree.vue ~ line 33 ~ getDeviceTree ~ tree:", arr3) item.pName = '便携式设备'
treRef.value.treeRef3.setCurrentKey(arr3[0].id) if (item.type == 'device') {
// 注册父组件事件 arr2.push(item)
emit('init', { }
level: 2, item.children.forEach((item2: any) => {
...arr3[0] item2.icon = 'el-icon-Platform'
}) item2.color =
return item2.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
} item2.pName = '便携式设备'
else { // item2.children.forEach((item3: any) => {
emit('init') // item3.icon = 'el-icon-Platform'
return // item3.color = config.getColorVal('elementUiPrimary')
} // if (item3.comFlag === 1) {
// item3.color = '#e26257 !important'
// }
// arr.push(item3)
// })
})
})
} else if (item.name == '监测设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.pName = '监测设备'
item3.icon = 'el-icon-Platform'
item3.color = config.getColorVal('elementUiPrimary')
if (item3.comFlag === 1) {
item3.color = '#e26257 !important'
}
arr3.push(item3)
})
})
})
}
}
})
tree.value = res.data
nextTick(() => {
setTimeout(() => {
if (type == '2') {
//初始化选中
treRef.value?.treeRef4.setCurrentKey(arr4[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr4[0]
})
// changePointType('4', arr4[0])
return
}
if (arr.length > 0) {
treRef.value.treeRef1.setCurrentKey(arr[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr[0]
})
return
} else if (arr2.length > 0) {
treRef.value.treeRef2.setCurrentKey(arr2[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr2[0]
})
return
} else if (arr3.length > 0) {
console.log('🚀 ~ arr3:', arr3)
treRef.value.treeRef3.setCurrentKey(arr3[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr3[0]
})
return
} else {
emit('init')
return
}
}, 500)
})
}) })
}
onMounted(() => {
info(props.engineering ? '2' : '1')
}) })
throttle(
(data: any, checked: any, indeterminate: any) => {
emit('checkChange', {
data,
checked,
indeterminate
})
},
300,
{
leading: true, // 首次触发立即执行(可选,默认 true
trailing: false // 节流结束后是否执行最后一次(可选,默认 true根据需求调整
}
)
const handleCheckChange = (data: any, checked: any, indeterminate: any) => { const handleCheckChange = (data: any, checked: any, indeterminate: any) => {
emit('checkChange', { emit('checkChange', {
data, data,
@@ -148,4 +210,7 @@ const handleCheckChange = (data: any, checked: any, indeterminate: any) => {
indeterminate indeterminate
}) })
} }
defineExpose({
treRef
})
</script> </script>

View File

@@ -3,7 +3,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getMarketList } from '@/api/user-boot/user' import { getMarketList } from '@/api/user-boot/user'
import Tree from '../index.vue' import Tree from '../cloudDevice.vue'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { ref, reactive, nextTick } from 'vue' import { ref, reactive, nextTick } from 'vue'
const config = useConfig() const config = useConfig()

View File

@@ -0,0 +1,29 @@
<template>
<Tree ref="treRef" :data="tree" />
</template>
<script setup lang="ts">
import { getFormalUserList } from '@/api/user-boot/official'
import Tree from '../cloudDevice.vue'
import { useConfig } from '@/stores/config'
import { ref, reactive, nextTick } from 'vue'
const config = useConfig()
const tree = ref()
const treRef = ref()
const emit = defineEmits(['selectUser'])
getFormalUserList().then((res: any) => {
if (res.code === 'A0000') {
tree.value = res.data.map((item: any) => {
return {
...item,
icon: 'el-icon-User',
color: 'royalblue'
}
})
emit('selectUser', tree.value[0])
nextTick(() => {
treRef.value.treeRef.setCurrentKey(tree.value[0].id)
})
}
})
</script>
<style lang="scss" scoped></style>

View File

@@ -1,5 +1,12 @@
<template> <template>
<Tree ref="treRef" :width="width" :data="tree" default-expand-all @changePointType="changePointType" /> <Tree
ref="treRef"
:width="width"
:data="tree"
default-expand-all
@changePointType="changePointType"
@changeTreeType="info"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@@ -7,7 +14,7 @@ import { ref, nextTick, onMounted, defineProps } from 'vue'
import Tree from '../point.vue' import Tree from '../point.vue'
import { getLineTree } from '@/api/cs-device-boot/csLedger' import { getLineTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { getTemplateByDept } from '@/api/harmonic-boot/luckyexcel' import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
// const props = defineProps(['template']) // const props = defineProps(['template'])
interface Props { interface Props {
@@ -27,110 +34,157 @@ const dictData = useDictData()
const treRef = ref() const treRef = ref()
const width = ref('') const width = ref('')
const info = () => { const info = (type?: string) => {
tree.value = [] tree.value = []
let arr1: any[] = [] let arr1: any[] = []
let arr2: any[] = [] let arr2: any[] = []
let arr3: any[] = [] let arr3: any[] = []
getLineTree().then(res => { let arr4: any[] = []
getLineTree({ type: type == '2' ? 'engineering' : '' }).then(res => {
//治理设备 //治理设备
res.data.map((item: any) => { res.data.map((item: any) => {
if (item.name == '治理设备') { if (type == '2') {
item.icon = 'el-icon-HomeFilled'
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item: any) => { item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled' item.icon = 'el-icon-List'
item.level = 1
item.color = config.getColorVal('elementUiPrimary') item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => { item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List' // item2.icon = 'el-icon-List'
item2.level = 1 // item2.color = config.getColorVal('elementUiPrimary')
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 2
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.color =
item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// item4.color = '#e26257 !important'
arr1.push(item4)
})
})
})
})
} else if (item.name == '便携式设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-Platform'
item.color = config.getColorVal('elementUiPrimary')
item.color = item.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-Platform' item2.icon = 'el-icon-Platform'
item2.level = 2
item2.color = item2.color =
item2.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important' item2.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
arr2.push(item2)
})
})
} else if (item.name == '在线设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.level = 1
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => { item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform' item3.icon = 'el-icon-Platform'
item3.level = 1
item3.color = item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important' item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item3.children.forEach((item4: any) => { arr4.push(item3)
item4.icon = 'el-icon-Platform' // item3.children.forEach((item4: any) => {
item4.color = // item4.icon = 'el-icon-Platform'
item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important' // item4.color =
// item4.color = '#e26257 !important' // item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
arr3.push(item4)
}) // })
}) })
}) })
}) })
} else {
if (item.name == '治理设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.level = 1
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 2
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.color =
item4.comFlag === 2
? config.getColorVal('elementUiPrimary')
: '#e26257 !important'
// item4.color = '#e26257 !important'
arr1.push(item4)
})
})
})
})
} else if (item.name == '便携式设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-Platform'
item.color = config.getColorVal('elementUiPrimary')
item.color = item.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-Platform'
item2.color =
item2.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
arr2.push(item2)
})
})
} else if (item.name == '监测设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.level = 1
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 1
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.color =
item4.comFlag === 2
? config.getColorVal('elementUiPrimary')
: '#e26257 !important'
// item4.color = '#e26257 !important'
arr3.push(item4)
})
})
})
})
}
} }
}) })
tree.value = res.data tree.value = res.data
nextTick(() => { nextTick(() => {
if (arr1.length) { setTimeout(() => {
//初始化选中 if (type == '2') {
treRef.value.treeRef1.setCurrentKey(arr1[0].id) //初始化选中
// 注册父组件事件
emit('init', { treRef.value?.treeRef4.setCurrentKey(arr4[0].id)
level: 2, // 注册父组件事件
...arr1[0] emit('init', {
}) level: 3,
return ...arr4[0]
} })
if (arr2.length) { changePointType('4', arr4[0])
//初始化选中 return
treRef.value.treeRef2.setCurrentKey(arr2[0].id) } else if (arr1.length > 0) {
// 注册父组件事件 //初始化选中
emit('init', { treRef.value?.treeRef1.setCurrentKey(arr1[0].id)
level: 2, // 注册父组件事件
...arr2[0] emit('init', {
}) level: 2,
return ...arr1[0]
} })
if(arr3.length){ return
treRef.value.treeRef3.setCurrentKey(arr3[0].id) } else if (arr2.length > 0) {
emit('init', { //初始化选中
level: 2, treRef.value?.treeRef2.setCurrentKey(arr2[0].id)
...arr3[0] // 注册父组件事件
}) emit('init', {
return level: 2,
} ...arr2[0]
else { })
emit('init') return
return } else if (arr3.length > 0) {
} treRef.value?.treeRef3?.setCurrentKey(arr3[0].id)
emit('init', {
level: 2,
...arr3[0]
})
return
} else {
emit('init')
return
}
}, 500)
}) })
}) })
} }
@@ -138,7 +192,8 @@ const changePointType = (val: any, obj: any) => {
emit('pointTypeChange', val, obj) emit('pointTypeChange', val, obj)
} }
if (props.template) { if (props.template) {
getTemplateByDept({ id: dictData.state.area[0].id }) // id: dictData.state.area[0]?.id
querySysExcel({})
.then((res: any) => { .then((res: any) => {
emit('Policy', res.data) emit('Policy', res.data)
info() info()

View File

@@ -1,181 +1,191 @@
<template> <template>
<div> <div>
<div style="transition: all 0.3s; overflow: hidden; height: 100%"> <div style="transition: all 0.3s; overflow: hidden; height: 100%">
<div class="cn-tree">
<div class="cn-tree"> <div style="display: flex; align-items: center" class="mb10">
<div style="display: flex; align-items: center" class="mb10"> <el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
<el-input maxlength="32" show-word-limit v-model.trim="filterText" placeholder="请输入内容" clearable> <template #prefix>
<template #prefix> <Icon name="el-icon-Search" style="font-size: 16px" />
<Icon name="el-icon-Search" style="font-size: 16px" /> </template>
</template> </el-input>
</el-input> </div>
</div> <el-tree
<el-tree style="flex: 1; overflow: auto" :props="defaultProps" highlight-current style="flex: 1; overflow: auto"
:filter-node-method="filterNode" node-key="id" v-bind="$attrs" default-expand-all :data="tree" :props="defaultProps"
ref="treRef" @node-click="clickNode" :expand-on-click-node="false"> highlight-current
<template #default="{ node, data }"> :filter-node-method="filterNode"
<span class="custom-tree-node"> node-key="id"
<div class="left"> v-bind="$attrs"
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" default-expand-all
v-if="data.icon" /> :data="tree"
<span>{{ node.label }}</span> ref="treRef"
</div> @node-click="clickNode"
:expand-on-click-node="false"
</span> >
</template> <template #default="{ node, data }">
</el-tree> <span class="custom-tree-node">
</div> <div class="left" style="display: flex; align-items: center">
</div> <Icon
</div> :name="data.icon"
</template> style="font-size: 16px"
:style="{ color: data.color }"
<script lang="ts" setup> v-if="data.icon"
import { ref, nextTick, watch, defineProps, defineEmits } from 'vue' />
import { getSchemeTree, getTestRecordInfo } from '@/api/cs-device-boot/planData' <span style="margin-left: 5px">{{ node.label }}</span>
import { useConfig } from '@/stores/config' </div>
import useCurrentInstance from '@/utils/useCurrentInstance' </span>
import { ElTree } from 'element-plus' </template>
import { getTemplateByDept } from '@/api/harmonic-boot/luckyexcel' </el-tree>
import { useDictData } from '@/stores/dictData' </div>
defineOptions({ </div>
name: 'govern/schemeTree' </div>
}) </template>
interface Props { <script lang="ts" setup>
template?: boolean import { ref, nextTick, watch, defineProps, defineEmits } from 'vue'
import { getSchemeTree, getTestRecordInfo } from '@/api/cs-device-boot/planData'
} import { useConfig } from '@/stores/config'
const dictData = useDictData() import useCurrentInstance from '@/utils/useCurrentInstance'
const props = withDefaults(defineProps<Props>(), { import { ElTree } from 'element-plus'
template: false, import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData'
}) defineOptions({
const filterText = ref('') name: 'govern/schemeTree'
watch(filterText, val => { })
treRef.value!.filter(val)
}) interface Props {
template?: boolean
const filterNode = (value: string, data: any, node: any) => { }
if (!value) return true const dictData = useDictData()
// return data.name.includes(value) const props = withDefaults(defineProps<Props>(), {
if (data.name) { template: false
return chooseNode(value, data, node) })
} const filterText = ref('')
} watch(filterText, val => {
const chooseNode = (value: string, data: any, node: any) => { treRef.value!.filter(val)
if (data.name.indexOf(value) !== -1) { })
return true
} const filterNode = (value: string, data: any, node: any) => {
const level = node.level if (!value) return true
// 如果传入的节点本身就是一级节点就不用校验了 // return data.name.includes(value)
if (level === 1) { if (data.name) {
return false return chooseNode(value, data, node)
} }
// 先取当前节点的父节点 }
let parentData = node.parent const chooseNode = (value: string, data: any, node: any) => {
// 遍历当前节点的父节点 if (data.name.indexOf(value) !== -1) {
let index = 0 return true
while (index < level - 1) { }
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤 const level = node.level
if (parentData.data.name.indexOf(value) !== -1) { // 如果传入的节点本身就是一级节点就不用校验了
return true if (level === 1) {
} return false
// 否则的话再往上一层做匹配 }
parentData = parentData.parent // 先取当前节点的父节点
index++ let parentData = node.parent
} // 遍历当前节点的父节点
// 没匹配到返回false let index = 0
return false while (index < level - 1) {
} // 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
/** 树形结构数据 */ if (parentData.data.name.indexOf(value) !== -1) {
const defaultProps = { return true
children: 'children', }
label: 'name', // 否则的话再往上一层做匹配
value: 'id' parentData = parentData.parent
} index++
}
// 没匹配到返回false
const emit = defineEmits(['init', 'checkChange', 'nodeChange', 'editNode', 'getChart', 'Policy']) return false
const config = useConfig() }
const tree = ref() /** 树形结构数据 */
const treRef = ref() const defaultProps = {
const id: any = ref(null) children: 'children',
const treeData = ref({}) label: 'name',
//获取方案树形数据 value: 'id'
const getTreeList = () => { }
getSchemeTree().then(res => {
let arr: any[] = [] const emit = defineEmits(['init', 'checkChange', 'nodeChange', 'editNode', 'getChart', 'Policy'])
const config = useConfig()
res.data.forEach((item: any) => { const tree = ref()
item.icon = 'el-icon-Menu' const treRef = ref()
item.color = config.getColorVal('elementUiPrimary') const id: any = ref(null)
item?.children.forEach((item2: any) => { const treeData = ref({})
item2.icon = 'el-icon-Document' //获取方案树形数据
item2.color = config.getColorVal('elementUiPrimary') const getTreeList = () => {
arr.push(item2) getSchemeTree().then(res => {
}) let arr: any[] = []
})
tree.value = res.data res.data.forEach((item: any) => {
nextTick(() => { item.icon = 'el-icon-Menu'
if (arr.length) { item.color = config.getColorVal('elementUiPrimary')
treRef.value.setCurrentKey(id.value || arr[0].id) item?.children.forEach((item2: any) => {
let list = id.value ? arr.find((item: any) => item.id == id.value) : arr[0] item2.icon = 'el-icon-Document'
// 注册父组件事件 item2.color = config.getColorVal('elementUiPrimary')
emit('init', { arr.push(item2)
level: 2, })
...list })
}) tree.value = res.data
} else { nextTick(() => {
emit('init') if (arr.length) {
} treRef.value.setCurrentKey(id.value || arr[0].id)
}) let list = id.value ? arr.find((item: any) => item.id == id.value) : arr[0]
}) // 注册父组件事件
} emit('init', {
level: 2,
//方案id ...list
const planId: any = ref('') })
} else {
const clickNode = (e: anyObj) => { emit('init')
e?.children ? (planId.value = e.id) : (planId.value = e.pid) }
id.value = e.id })
emit('nodeChange', e) })
} }
//方案id
if (props.template) { const planId: any = ref('')
getTemplateByDept({ id: dictData.state.area[0].id }).then((res: any) => {
emit('Policy', res.data) const clickNode = (e: anyObj) => {
getTreeList() e?.children ? (planId.value = e.id) : (planId.value = e.pid)
}).catch(err => { id.value = e.id
getTreeList() emit('nodeChange', e)
}) }
} else {
getTreeList() if (props.template) {
} // id: dictData.state.area[0]?.id
querySysExcel({})
</script> .then((res: any) => {
<style lang="scss" scoped> emit('Policy', res.data)
.cn-tree { getTreeList()
flex-shrink: 0; })
display: flex; .catch(err => {
flex-direction: column; getTreeList()
box-sizing: border-box; })
padding: 10px; } else {
height: 100%; getTreeList()
width: 100%; }
height: calc(100vh - 125px); </script>
overflow-y: auto; <style lang="scss" scoped>
.cn-tree {
:deep(.el-tree) { flex-shrink: 0;
border: 1px solid var(--el-border-color); display: flex;
} flex-direction: column;
box-sizing: border-box;
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) { padding: 10px;
background-color: var(--el-color-primary-light-7); height: 100%;
} width: 100%;
height: calc(100vh - 125px);
.menu-collapse { overflow-y: auto;
color: var(--el-color-primary);
} :deep(.el-tree) {
} border: 1px solid var(--el-border-color);
</style> }
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: var(--el-color-primary-light-7);
}
.menu-collapse {
color: var(--el-color-primary);
}
}
</style>

View File

@@ -33,7 +33,7 @@ const info = () => {
getLineTree().then(res => { getLineTree().then(res => {
//治理设备 //治理设备
res.data.map((item: any) => { res.data.map((item: any) => {
if (item.name == '在线设备') { if (item.name == '监测设备') {
item.children.forEach((item: any) => { item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled' item.icon = 'el-icon-HomeFilled'
item.level = 1 item.level = 1
@@ -59,7 +59,7 @@ const info = () => {
}) })
} }
}) })
tree.value = res.data.filter(item => item.name == '在线设备') tree.value = res.data.filter(item => item.name == '监测设备')
nextTick(() => { nextTick(() => {
if (arr3.length) { if (arr3.length) {
//初始化选中 //初始化选中
@@ -87,7 +87,7 @@ const handleCheckedNodesChange = (nodes: any[]) => {
if (props.template) { if (props.template) {
getTemplateByDept({ id: dictData.state.area[0].id }) getTemplateByDept({ id: dictData.state.area[0]?.id })
.then((res: any) => { .then((res: any) => {
emit('Policy', res.data) emit('Policy', res.data)
info() info()

View File

@@ -1,41 +1,61 @@
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style='transition: all 0.3s; overflow: hidden;'> <div :style="{ width: menuCollapse ? '40px' : props.width }" style="transition: all 0.3s; overflow: hidden">
<Icon v-show='menuCollapse' @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <Icon
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 mt20 menu-collapse' v-show="menuCollapse"
style='cursor: pointer' /> @click="onMenuCollapse"
<div class='cn-tree' :style='{ opacity: menuCollapse ? 0 : 1 }'> :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
<div style='display: flex; align-items: center' class='mb10'> :class="menuCollapse ? 'unfold' : ''"
<el-input maxlength="32" show-word-limit v-model.trim='filterText' placeholder='请输入内容' clearable> size="18"
class="fold ml10 mt20 menu-collapse"
style="cursor: pointer"
/>
<div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1 }">
<div style="display: flex; align-items: center" class="mb10">
<el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
<template #prefix> <template #prefix>
<Icon name='el-icon-Search' style='font-size: 16px' /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
</el-input> </el-input>
<el-tooltip placement="bottom" :hide-after="0"> <el-tooltip placement="bottom" :hide-after="0" v-if="props.showPush">
<template #content> <template #content>
<span>台账推送</span> <span>台账推送</span>
</template> </template>
<Icon <Icon
name="el-icon-Promotion" name="el-icon-Promotion"
size="20" size="20"
class="fold ml10 menu-collapse" class="fold ml10 mr10 menu-collapse"
style="cursor: pointer;" style="cursor: pointer"
:style="{ color: config.getColorVal('elementUiPrimary') }" :style="{ color: config.getColorVal('elementUiPrimary') }"
@click="onAdd" /> @click="onAdd"
/>
</el-tooltip> </el-tooltip>
<!-- <Icon @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <!-- <Icon @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" v-else
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse' :class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse'
style='cursor: pointer' v-if='props.canExpand' /> --> style='cursor: pointer' v-if='props.canExpand' /> -->
</div> </div>
<el-tree :style="{ height: 'calc(100vh - 200px)' }" <el-tree
style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current :default-expand-all="false" :style="{ height: 'calc(100vh - 190px)' }"
@check-change="checkTreeNodeChange" :filter-node-method='filterNode' node-key='id' v-bind='$attrs'> style="overflow: auto"
<template #default='{ node, data }'> ref="treeRef"
<span class='custom-tree-node'> :props="defaultProps"
<Icon :name='data.icon' style='font-size: 16px' :style='{ color: data.color }' highlight-current
v-if='data.icon' /> :default-expand-all="false"
<span style='margin-left: 4px'>{{ node.label }}</span> @check-change="checkTreeNodeChange"
:filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<Icon
:name="data.icon"
style="font-size: 16px"
:style="{ color: data.color }"
v-if="data.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
@@ -43,12 +63,10 @@
</div> </div>
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus' import { ElTree } from 'element-plus'
import { emit } from 'process';
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { t } from 'vxe-table';
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
defineOptions({ defineOptions({
@@ -58,11 +76,13 @@ defineOptions({
interface Props { interface Props {
width?: string width?: string
canExpand?: boolean canExpand?: boolean
showPush?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true canExpand: true,
showPush: false
}) })
const config = useConfig() const config = useConfig()
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
@@ -72,7 +92,7 @@ const defaultProps = {
label: 'name', label: 'name',
value: 'id' value: 'id'
} }
const emit = defineEmits(['checkTreeNodeChange','onAdd']) const emit = defineEmits(['checkTreeNodeChange', 'onAdd'])
watch(filterText, val => { watch(filterText, val => {
treeRef.value!.filter(val) treeRef.value!.filter(val)
}) })
@@ -81,18 +101,16 @@ const onMenuCollapse = () => {
proxy.eventBus.emit('cnTreeCollapse', menuCollapse) proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
} }
const filterNode = (value: string, data: any, node: any) => { const filterNode = (value: string, data: any, node: any) => {
console.log(value, data, node, 'filterNode'); console.log(value, data, node, 'filterNode')
if (!value) return true if (!value) return true
// return data.name.includes(value) // return data.name.includes(value)
if (data.name) { if (data.name) {
return chooseNode(value, data, node) return chooseNode(value, data, node)
} }
} }
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符. // 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => { const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) { if (data.name.indexOf(value) !== -1) {
return true return true
} }
@@ -130,13 +148,13 @@ const treeRef = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef }) defineExpose({ treeRef })
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
.cn-tree { .cn-tree {
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-sizing: border-box; box-sizing: border-box;
padding: 10px; // padding: 10px;
height: 100%; height: 100%;
width: 100%; width: 100%;

View File

@@ -1,23 +1,58 @@
<!-- 设备监控使用折叠面板渲染多个tree --> <!-- 设备监控使用折叠面板渲染多个tree -->
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style="display: flex; overflow: hidden"> <div :style="{ width: menuCollapse ? '40px' : props.width }" style="display: flex; overflow: hidden">
<Icon v-show="menuCollapse" @click="onMenuCollapse" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <Icon
:class="menuCollapse ? 'unfold' : ''" size="18" class="fold ml10 mt20 menu-collapse" style="cursor: pointer" v-show="menuCollapse"
v-if="route.path != '/admin/govern/reportCore/statistics/index'" /> @click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''"
size="18"
class="fold ml10 mt20 menu-collapse"
style="cursor: pointer"
v-if="route.path != '/admin/govern/reportCore/statistics/index'"
/>
<div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1, display: menuCollapse ? 'none' : '' }"> <div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1, display: menuCollapse ? 'none' : '' }">
<div style="display: flex; align-items: center" class="mb10"> <div style="display: flex; align-items: center" class="mb10">
<el-input maxlength="32" show-word-limit v-model.trim="filterText" placeholder="请输入内容" clearable> <!-- <el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
<template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" />
</template>
</el-input> -->
<el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
<template #prepend>
<el-select v-model="treeType" @change="changeTreeType" style="min-width: 75px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<template #prefix> <template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
</el-input> </el-input>
<Icon @click="onMenuCollapse" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <!-- -->
:class="menuCollapse ? 'unfold' : ''" size="18" class="fold ml10 menu-collapse" <Icon
@click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''"
size="18"
class="fold ml10 menu-collapse"
style="cursor: pointer" style="cursor: pointer"
v-if="props.canExpand && route.path != '/admin/govern/reportCore/statistics/index'" /> v-if="props.canExpand && route.path != '/admin/govern/reportCore/statistics/index'"
/>
</div> </div>
<el-collapse :accordion="true" v-model.trim="activeName" style="flex: 1; height: 100%"
@change="changeDevice"> <el-collapse
:accordion="true"
v-model.trim="activeName"
style="flex: 1; height: 100%"
@change="changeDevice"
v-if="treeType == '1'"
v-loading="loading"
>
<el-collapse-item title="治理设备" name="0" v-if="zlDeviceData.length != 0"> <el-collapse-item title="治理设备" name="0" v-if="zlDeviceData.length != 0">
<el-select v-model.trim="process" clearable placeholder="请选择状态" class="mb10"> <el-select v-model.trim="process" clearable placeholder="请选择状态" class="mb10">
<el-option label="功能调试" value="2"></el-option> <el-option label="功能调试" value="2"></el-option>
@@ -26,14 +61,25 @@
</el-select> </el-select>
<el-tree <el-tree
:style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 278px)' }" :style="{ height: treeType.length != 0 ? 'calc(100vh - 380px)' : 'calc(100vh - 278px)' }"
ref="treeRef1" :props="defaultProps" highlight-current :filter-node-method="filterNode" ref="treeRef1"
node-key="id" v-bind="$attrs" :data="zlDevList" style="overflow: auto" :props="defaultProps"
:default-expand-all="false"> highlight-current
:filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
:data="zlDevList"
style="overflow: auto"
:default-expand-all="false"
>
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" <Icon
v-if="data.icon" /> :name="data.icon"
style="font-size: 16px"
:style="{ color: data.color }"
v-if="data.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
@@ -41,35 +87,84 @@
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0"> <el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 330px)' : 'calc(100vh - 238px)' }" :style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 238px)' }"
ref="treeRef2" :props="defaultProps" highlight-current :default-expand-all="false" ref="treeRef2"
:filter-node-method="filterNode" node-key="id" :data="bxsDeviceData" v-bind="$attrs" :props="defaultProps"
style="overflow: auto" > highlight-current
:default-expand-all="false"
:filter-node-method="filterNode"
node-key="id"
:data="bxsDeviceData"
v-bind="$attrs"
style="overflow: auto"
>
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" <Icon
v-if="data.icon" /> :name="data.icon"
style="font-size: 16px"
:style="{ color: data.color }"
v-if="data.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="在线设备" name="2" v-if="yqfDeviceData.length != 0"> <el-collapse-item title="监测设备" name="2" v-if="yqfDeviceData.length != 0">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 330px)' : 'calc(100vh - 238px)' }" :style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 238px)' }"
ref="treeRef3" :props="defaultProps" highlight-current :default-expand-all="false" ref="treeRef3"
:filter-node-method="filterNode" node-key="id" :data="yqfDeviceData" v-bind="$attrs" :props="defaultProps"
style="overflow: auto"> highlight-current
:default-expand-all="false"
:filter-node-method="filterNode"
node-key="id"
:data="yqfDeviceData"
v-bind="$attrs"
style="overflow: auto"
>
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" <Icon
v-if="data.icon" /> :name="data.icon"
style="font-size: 16px"
:style="{ color: data.color }"
v-if="data.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
<div v-if="treeType == '2'" v-loading="loading">
<el-tree
:style="{ height: 'calc(100vh - 188px)' }"
class="pt10"
ref="treeRef4"
:props="defaultProps"
highlight-current
:filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
:data="data"
style="overflow: auto"
:default-expand-all="false"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<Icon
:name="data.icon"
style="font-size: 16px"
:style="{ color: data.color }"
v-if="data.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -83,7 +178,7 @@ import { useRoute } from 'vue-router'
defineOptions({ defineOptions({
name: 'govern/tree' name: 'govern/tree'
}) })
const emit = defineEmits(['changePointType']) const emit = defineEmits(['changePointType', 'changeTreeType'])
interface Props { interface Props {
width?: string width?: string
canExpand?: boolean canExpand?: boolean
@@ -107,38 +202,47 @@ const defaultProps = {
label: 'name', label: 'name',
value: 'id' value: 'id'
} }
const treeType = ref('1')
const options = [
{
label: '设备',
value: '1'
},
{
label: '工程',
value: '2'
}
]
//治理设备数据 //治理设备数据
const zlDeviceData = ref<any>([]) const zlDeviceData = ref<any>([])
const zlDevList = ref<any>([]) const zlDevList = ref<any>([])
//便携式设备数据 //便携式设备数据
const bxsDeviceData = ref<any>([]) const bxsDeviceData = ref<any>([])
//在线设备数据 //监测设备数据
const yqfDeviceData = ref<any>([]) const yqfDeviceData = ref<any>([])
watch( watch(
() => props.data, () => props.data,
(val, oldVal) => { (val, oldVal) => {
if (val && val.length != 0) { if (val && val.length != 0) {
val.map((item: any) => { val.map((item: any) => {
if (item.name == '治理设备') { if (item.name == '治理设备') {
zlDeviceData.value = [] zlDeviceData.value = []
item.children.map((vv: any) => { item.children.map((vv: any) => {
zlDeviceData.value.push(vv) zlDeviceData.value.push(vv)
}) })
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value)) zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else if (item.name == '便携式设备') { } else if (item.name == '便携式设备') {
bxsDeviceData.value = [] bxsDeviceData.value = []
item.children.map((vv: any) => { item.children.map((vv: any) => {
bxsDeviceData.value.push(vv) bxsDeviceData.value.push(vv)
}) })
}else if (item.name == '在线设备') { } else if (item.name == '监测设备') {
yqfDeviceData.value = [] yqfDeviceData.value = []
item.children.map((vv: any) => { item.children.map((vv: any) => {
yqfDeviceData.value.push(vv) yqfDeviceData.value.push(vv)
}) })
} }
}) })
} }
}, },
{ {
@@ -148,7 +252,9 @@ watch(
) )
watch(filterText, val => { watch(filterText, val => {
if (activeName.value == '0') { if (treeType.value == '2') {
treeRef4.value!.filter(val)
} else if (activeName.value == '0') {
treeRef1.value!.filter(val) treeRef1.value!.filter(val)
} else if (activeName.value == '1') { } else if (activeName.value == '1') {
treeRef2.value!.filter(val) treeRef2.value!.filter(val)
@@ -157,12 +263,10 @@ watch(filterText, val => {
} }
}) })
watch(process, val => { watch(process, val => {
if (val == '' || val == undefined) {
if (val == '') {
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value)) zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else { } else {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value))) zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
} }
setTimeout(() => { setTimeout(() => {
@@ -170,10 +274,7 @@ watch(process, val => {
}, 0) }, 0)
}) })
const changeDevice = (val: any) => { const changeDevice = (val: any) => {
let arr1: any = [] let arr1: any = []
//zlDeviceData //zlDeviceData
zlDevList.value.forEach((item: any) => { zlDevList.value.forEach((item: any) => {
@@ -206,22 +307,28 @@ const changeDevice = (val: any) => {
arr2.map((item: any) => { arr2.map((item: any) => {
item.checked = false item.checked = false
}) })
treeRef1?.value && treeRef1.value.setCurrentKey(arr1[0]?.id)
emit('changePointType', activeName.value, arr1[0]) emit('changePointType', activeName.value, arr1[0])
setTimeout(() => {
treeRef1.value?.setCurrentKey(arr1[0]?.id)
}, 100)
} }
if (val == '1') { if (val == '1') {
arr1.map((item: any) => { arr1.map((item: any) => {
item.checked = false item.checked = false
}) })
treeRef2?.value && treeRef2.value.setCurrentKey(arr2[0]?.id)
emit('changePointType', activeName.value, arr2[0]) emit('changePointType', activeName.value, arr2[0])
setTimeout(() => {
treeRef2.value?.setCurrentKey(arr2[0]?.id)
}, 100)
} }
if (val == '2') { if (val == '2') {
arr3.map((item: any) => { arr3.map((item: any) => {
item.checked = false item.checked = false
}) })
treeRef3?.value && treeRef3.value.setCurrentKey(arr3[0]?.id)
emit('changePointType', activeName.value, arr3[0]) emit('changePointType', activeName.value, arr3[0])
setTimeout(() => {
treeRef3.value?.setCurrentKey(arr3[0]?.id)
}, 100)
} }
// if(activeName.value){ // if(activeName.value){
// emit('changePointType', activeName.value) // emit('changePointType', activeName.value)
@@ -248,7 +355,7 @@ function filterProcess(nodes: any) {
// 递归处理子节点 // 递归处理子节点
const children = node.children ? filterProcess(node.children) : [] const children = node.children ? filterProcess(node.children) : []
// 对于装置层级level=2只保留 process 值匹配的节点 // 对于设备层级level=2只保留 process 值匹配的节点
if (node.level === 2) { if (node.level === 2) {
if (node.process == process.value) { if (node.process == process.value) {
return { return {
@@ -263,8 +370,7 @@ function filterProcess(nodes: any) {
// 1. 如果有满足条件的子节点则保留 // 1. 如果有满足条件的子节点则保留
// 2. 如果本身 process 值匹配则保留 // 2. 如果本身 process 值匹配则保留
// 3. 如果是叶子节点也保留(监测点通常没有子节点) // 3. 如果是叶子节点也保留(监测点通常没有子节点)
if (children.length > 0 || node.process == process.value || if (children.length > 0 || node.process == process.value || !node.children || node.children.length === 0) {
(!node.children || node.children.length === 0)) {
return { return {
...node, ...node,
children: children children: children
@@ -276,7 +382,6 @@ function filterProcess(nodes: any) {
.filter(Boolean) // 移除null节点 .filter(Boolean) // 移除null节点
} }
// function filterProcess(nodes: any) { // function filterProcess(nodes: any) {
// if (process.value == '') { // if (process.value == '') {
// return nodes // return nodes
@@ -332,26 +437,43 @@ const treeRef1 = ref<InstanceType<typeof ElTree>>()
const treeRef2 = ref<InstanceType<typeof ElTree>>() const treeRef2 = ref<InstanceType<typeof ElTree>>()
//在线 //在线
const treeRef3 = ref<InstanceType<typeof ElTree>>() const treeRef3 = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef1, treeRef2 }) // 工程
const treeRef4 = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef1, treeRef2, treeRef3, treeRef4 })
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(() => {
setActiveName()
if (zlDeviceData.value.length != 0) {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
activeName.value = '0'
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length != 0) {
activeName.value = '1'
}
if (!zlDeviceData.value && !bxsDeviceData.value) {
activeName.value = ''
}
nextTick(() => {
changeDevice(activeName.value)
})
}, 500) }, 500)
}) })
const setActiveName = () => {
if (zlDeviceData.value.length != 0) {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
activeName.value = '0'
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length != 0) {
activeName.value = '1'
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length === 0) {
activeName.value = '2'
}
if (!zlDeviceData.value && !bxsDeviceData.value) {
activeName.value = '2'
}
nextTick(() => {
changeDevice(activeName.value)
})
}
const loading = ref(false)
const changeTreeType = (val: string) => {
loading.value = true
emit('changeTreeType', val)
if (val == '1') {
setActiveName()
}
setTimeout(() => {
loading.value = false
}, 1000)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -382,4 +504,7 @@ onMounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
} }
:deep(.el-input-group__prepend) {
background-color: var(--el-fill-color-blank);
}
</style> </style>

View File

@@ -5,7 +5,7 @@
style='cursor: pointer' /> style='cursor: pointer' />
<div class='cn-tree' :style='{ opacity: menuCollapse ? 0 : 1 }'> <div class='cn-tree' :style='{ opacity: menuCollapse ? 0 : 1 }'>
<div style='display: flex; align-items: center' class='mb10'> <div style='display: flex; align-items: center' class='mb10'>
<el-input maxlength="32" show-word-limit v-model.trim='filterText' placeholder='请输入内容' clearable> <el-input maxlength="32" v-model.trim='filterText' placeholder='请输入内容' clearable>
<template #prefix> <template #prefix>
<Icon name='el-icon-Search' style='font-size: 16px' /> <Icon name='el-icon-Search' style='font-size: 16px' />
</template> </template>
@@ -218,11 +218,12 @@ const updateNodeCheckStatus = (currentCount: number) => {
const allNodes = treeRef.value.store._getAllNodes() const allNodes = treeRef.value.store._getAllNodes()
allNodes.forEach((node: any) => { allNodes.forEach((node: any) => {
if (node.level === 3) { // 监测点层级 if (node.level === 3) { // 监测点层级
// 如果已达到最大数量且该节点未被选中,则禁用勾选 // 如果已达到最大数量且该节点未被选中,则禁用勾选
if (isMaxSelected && !node.checked) { if (isMaxSelected && !node.data.checked) {
node.disabled = true node.data.disabled = true
} else { } else {
node.disabled = false node.data.disabled = false
} }
} }
}) })

View File

@@ -1,359 +1,359 @@
<template> <template>
<div class="layout-config-drawer"> <div class="layout-config-drawer">
<el-drawer :model-value="configStore.layout.showDrawer" title="布局配置" size="310px" @close="onCloseDrawer"> <el-drawer :model-value="configStore.layout.showDrawer" title="布局配置" size="310px" @close="onCloseDrawer">
<el-scrollbar class="layout-mode-style-scrollbar"> <el-scrollbar class="layout-mode-style-scrollbar">
<el-form ref="formRef" :model="configStore.layout"> <el-form ref="formRef" :model="configStore.layout">
<div class="layout-mode-styles-box"> <div class="layout-mode-styles-box">
<el-divider border-style="dashed">全局</el-divider> <el-divider border-style="dashed">全局</el-divider>
<div class="layout-config-global"> <div class="layout-config-global">
<el-form-item label="后台页面切换动画"> <el-form-item label="后台页面切换动画">
<el-select @change="onCommitState($event, 'mainAnimation')" <el-select @change="onCommitState($event, 'mainAnimation')"
:model-value="configStore.layout.mainAnimation" :model-value="configStore.layout.mainAnimation"
:placeholder="'layouts.Please select an animation name'"> :placeholder="'layouts.Please select an animation name'">
<el-option label="slide-right" value="slide-right"></el-option> <el-option label="slide-right" value="slide-right"></el-option>
<el-option label="slide-left" value="slide-left"></el-option> <el-option label="slide-left" value="slide-left"></el-option>
<el-option label="el-fade-in-linear" value="el-fade-in-linear"></el-option> <el-option label="el-fade-in-linear" value="el-fade-in-linear"></el-option>
<el-option label="el-fade-in" value="el-fade-in"></el-option> <el-option label="el-fade-in" value="el-fade-in"></el-option>
<el-option label="el-zoom-in-center" value="el-zoom-in-center"></el-option> <el-option label="el-zoom-in-center" value="el-zoom-in-center"></el-option>
<el-option label="el-zoom-in-top" value="el-zoom-in-top"></el-option> <el-option label="el-zoom-in-top" value="el-zoom-in-top"></el-option>
<el-option label="el-zoom-in-bottom" value="el-zoom-in-bottom"></el-option> <el-option label="el-zoom-in-bottom" value="el-zoom-in-bottom"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="组件主题色"> <el-form-item label="组件主题色">
<el-color-picker @change="onCommitColorState($event, 'elementUiPrimary')" <el-color-picker @change="onCommitColorState($event, 'elementUiPrimary')"
:model-value="configStore.getColorVal('elementUiPrimary')" /> :model-value="configStore.getColorVal('elementUiPrimary')" />
</el-form-item> </el-form-item>
<el-form-item label="表格标题栏背景颜色"> <el-form-item label="表格标题栏背景颜色">
<el-color-picker @change="onCommitColorState($event, 'tableHeaderBackground')" <el-color-picker @change="onCommitColorState($event, 'tableHeaderBackground')"
:model-value="configStore.getColorVal('tableHeaderBackground')" /> :model-value="configStore.getColorVal('tableHeaderBackground')" />
</el-form-item> </el-form-item>
<el-form-item label="表格标题栏文字颜色"> <el-form-item label="表格标题栏文字颜色">
<el-color-picker @change="onCommitColorState($event, 'tableHeaderColor')" <el-color-picker @change="onCommitColorState($event, 'tableHeaderColor')"
:model-value="configStore.getColorVal('tableHeaderColor')" /> :model-value="configStore.getColorVal('tableHeaderColor')" />
</el-form-item> </el-form-item>
<el-form-item label="表格激活栏颜色"> <el-form-item label="表格激活栏颜色">
<el-color-picker @change="onCommitColorState($event, 'tableCurrent')" <el-color-picker @change="onCommitColorState($event, 'tableCurrent')"
:model-value="configStore.getColorVal('tableCurrent')" /> :model-value="configStore.getColorVal('tableCurrent')" />
</el-form-item> </el-form-item>
</div> </div>
<el-divider border-style="dashed">侧边栏</el-divider> <el-divider border-style="dashed">侧边栏</el-divider>
<div class="layout-config-aside"> <div class="layout-config-aside">
<el-form-item label="侧边菜单栏背景色"> <el-form-item label="侧边菜单栏背景色">
<el-color-picker @change="onCommitColorState($event, 'menuBackground')" <el-color-picker @change="onCommitColorState($event, 'menuBackground')"
:model-value="configStore.getColorVal('menuBackground')" /> :model-value="configStore.getColorVal('menuBackground')" />
</el-form-item> </el-form-item>
<el-form-item label="侧边菜单文字颜色"> <el-form-item label="侧边菜单文字颜色">
<el-color-picker @change="onCommitColorState($event, 'menuColor')" <el-color-picker @change="onCommitColorState($event, 'menuColor')"
:model-value="configStore.getColorVal('menuColor')" /> :model-value="configStore.getColorVal('menuColor')" />
</el-form-item> </el-form-item>
<el-form-item label="侧边菜单激活项背景色"> <el-form-item label="侧边菜单激活项背景色">
<el-color-picker @change="onCommitColorState($event, 'menuActiveBackground')" <el-color-picker @change="onCommitColorState($event, 'menuActiveBackground')"
:model-value="configStore.getColorVal('menuActiveBackground')" /> :model-value="configStore.getColorVal('menuActiveBackground')" />
</el-form-item> </el-form-item>
<el-form-item label="侧边菜单激活项文字色"> <el-form-item label="侧边菜单激活项文字色">
<el-color-picker @change="onCommitColorState($event, 'menuActiveColor')" <el-color-picker @change="onCommitColorState($event, 'menuActiveColor')"
:model-value="configStore.getColorVal('menuActiveColor')" /> :model-value="configStore.getColorVal('menuActiveColor')" />
</el-form-item> </el-form-item>
<el-form-item label="显示侧边菜单顶栏(LOGO栏)"> <el-form-item label="显示侧边菜单顶栏(LOGO栏)">
<el-switch @change="onCommitState($event, 'menuShowTopBar')" <el-switch @change="onCommitState($event, 'menuShowTopBar')"
:model-value="configStore.layout.menuShowTopBar"></el-switch> :model-value="configStore.layout.menuShowTopBar"></el-switch>
</el-form-item> </el-form-item>
<el-form-item label="侧边菜单顶栏背景色"> <el-form-item label="侧边菜单顶栏背景色">
<el-color-picker @change="onCommitColorState($event, 'menuTopBarBackground')" <el-color-picker @change="onCommitColorState($event, 'menuTopBarBackground')"
:model-value="configStore.getColorVal('menuTopBarBackground')" /> :model-value="configStore.getColorVal('menuTopBarBackground')" />
</el-form-item> </el-form-item>
<el-form-item label="侧边菜单宽度(展开时)"> <el-form-item label="侧边菜单宽度(展开时)">
<el-input maxlength="32" show-word-limit @input="onCommitState($event, 'menuWidth')" <el-input maxlength="32" show-word-limit @input="onCommitState($event, 'menuWidth')"
type="number" :step="10" :model-value="configStore.layout.menuWidth"> type="number" :step="10" :model-value="configStore.layout.menuWidth">
<template #append>px</template> <template #append>px</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<!-- <el-form-item label="侧边菜单默认图标">--> <!-- <el-form-item label="侧边菜单默认图标">-->
<!-- <IconSelector--> <!-- <IconSelector-->
<!-- @change="onCommitMenuDefaultIcon($event, 'menuDefaultIcon')"--> <!-- @change="onCommitMenuDefaultIcon($event, 'menuDefaultIcon')"-->
<!-- :model-value="configStore.layout.menuDefaultIcon"--> <!-- :model-value="configStore.layout.menuDefaultIcon"-->
<!-- />--> <!-- />-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
<!-- <el-form-item label="侧边菜单水平折叠">--> <!-- <el-form-item label="侧边菜单水平折叠">-->
<!-- <el-switch--> <!-- <el-switch-->
<!-- @change="onCommitState($event, 'menuCollapse')"--> <!-- @change="onCommitState($event, 'menuCollapse')"-->
<!-- :model-value="configStore.layout.menuCollapse"--> <!-- :model-value="configStore.layout.menuCollapse"-->
<!-- ></el-switch>--> <!-- ></el-switch>-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
<!-- <el-form-item label="侧边菜单手风琴">--> <!-- <el-form-item label="侧边菜单手风琴">-->
<!-- <el-switch--> <!-- <el-switch-->
<!-- @change="onCommitState($event, 'menuUniqueOpened')"--> <!-- @change="onCommitState($event, 'menuUniqueOpened')"-->
<!-- :model-value="configStore.layout.menuUniqueOpened"--> <!-- :model-value="configStore.layout.menuUniqueOpened"-->
<!-- ></el-switch>--> <!-- ></el-switch>-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
</div> </div>
<el-divider border-style="dashed">顶栏</el-divider> <el-divider border-style="dashed">顶栏</el-divider>
<div class="layout-config-aside"> <div class="layout-config-aside">
<el-form-item label="顶栏背景色"> <el-form-item label="顶栏背景色">
<el-color-picker @change="onCommitColorState($event, 'headerBarBackground')" <el-color-picker @change="onCommitColorState($event, 'headerBarBackground')"
:model-value="configStore.getColorVal('headerBarBackground')" /> :model-value="configStore.getColorVal('headerBarBackground')" />
</el-form-item> </el-form-item>
<el-form-item label="顶栏文字色"> <el-form-item label="顶栏文字色">
<el-color-picker @change="onCommitColorState($event, 'headerBarTabColor')" <el-color-picker @change="onCommitColorState($event, 'headerBarTabColor')"
:model-value="configStore.getColorVal('headerBarTabColor')" /> :model-value="configStore.getColorVal('headerBarTabColor')" />
</el-form-item> </el-form-item>
<!-- <el-form-item label="顶栏悬停时背景色">--> <!-- <el-form-item label="顶栏悬停时背景色">-->
<!-- <el-color-picker--> <!-- <el-color-picker-->
<!-- @change="onCommitColorState($event, 'headerBarHoverBackground')"--> <!-- @change="onCommitColorState($event, 'headerBarHoverBackground')"-->
<!-- :model-value="configStore.getColorVal('headerBarHoverBackground')"--> <!-- :model-value="configStore.getColorVal('headerBarHoverBackground')"-->
<!-- />--> <!-- />-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
<!-- <el-form-item label="顶栏菜单激活项背景色">--> <!-- <el-form-item label="顶栏菜单激活项背景色">-->
<!-- <el-color-picker--> <!-- <el-color-picker-->
<!-- @change="onCommitColorState($event, 'headerBarTabActiveBackground')"--> <!-- @change="onCommitColorState($event, 'headerBarTabActiveBackground')"-->
<!-- :model-value="configStore.getColorVal('headerBarTabActiveBackground')"--> <!-- :model-value="configStore.getColorVal('headerBarTabActiveBackground')"-->
<!-- />--> <!-- />-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
<!-- <el-form-item label="顶栏菜单激活项文字色">--> <!-- <el-form-item label="顶栏菜单激活项文字色">-->
<!-- <el-color-picker--> <!-- <el-color-picker-->
<!-- @change="onCommitColorState($event, 'headerBarTabActiveColor')"--> <!-- @change="onCommitColorState($event, 'headerBarTabActiveColor')"-->
<!-- :model-value="configStore.getColorVal('headerBarTabActiveColor')"--> <!-- :model-value="configStore.getColorVal('headerBarTabActiveColor')"-->
<!-- />--> <!-- />-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
</div> </div>
<el-popconfirm @confirm="restoreDefault" title="确定要恢复全部配置到默认值吗?"> <el-popconfirm @confirm="restoreDefault" title="确定要恢复全部配置到默认值吗?">
<template #reference> <template #reference>
<div class="ba-center"> <div class="ba-center">
<el-button class="w80" type="info">恢复默认</el-button> <el-button class="w80" type="info">恢复默认</el-button>
</div> </div>
</template> </template>
</el-popconfirm> </el-popconfirm>
</div> </div>
</el-form> </el-form>
</el-scrollbar> </el-scrollbar>
</el-drawer> </el-drawer>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { useNavTabs } from '@/stores/navTabs' import { useNavTabs } from '@/stores/navTabs'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import IconSelector from '@/components/baInput/components/iconSelector.vue' import IconSelector from '@/components/baInput/components/iconSelector.vue'
import { STORE_CONFIG } from '@/stores/constant/cacheKey' import { STORE_CONFIG } from '@/stores/constant/cacheKey'
import { Local, Session } from '@/utils/storage' import { Local, Session } from '@/utils/storage'
import type { Layout } from '@/stores/interface' import type { Layout } from '@/stores/interface'
const configStore = useConfig() const configStore = useConfig()
const navTabs = useNavTabs() const navTabs = useNavTabs()
const router = useRouter() const router = useRouter()
const onCommitState = (value: any, name: any) => { const onCommitState = (value: any, name: any) => {
configStore.setLayout(name, value) configStore.setLayout(name, value)
} }
const onCommitColorState = (value: string | null, name: keyof Layout) => { const onCommitColorState = (value: string | null, name: keyof Layout) => {
if (value === null) return if (value === null) return
const colors = configStore.layout[name] as string[] const colors = configStore.layout[name] as string[]
if (configStore.layout.isDark) { if (configStore.layout.isDark) {
colors[1] = value colors[1] = value
} else { } else {
colors[0] = value colors[0] = value
} }
configStore.setLayout(name, colors) configStore.setLayout(name, colors)
} }
const setLayoutMode = (mode: string) => { const setLayoutMode = (mode: string) => {
configStore.setLayoutMode(mode) configStore.setLayoutMode(mode)
} }
// 修改默认菜单图标 // 修改默认菜单图标
const onCommitMenuDefaultIcon = (value: any, name: any) => { const onCommitMenuDefaultIcon = (value: any, name: any) => {
configStore.setLayout(name, value) configStore.setLayout(name, value)
const menus = navTabs.state.tabsViewRoutes const menus = navTabs.state.tabsViewRoutes
navTabs.setTabsViewRoutes([]) navTabs.setTabsViewRoutes([])
setTimeout(() => { setTimeout(() => {
navTabs.setTabsViewRoutes(menus) navTabs.setTabsViewRoutes(menus)
}, 200) }, 200)
} }
const onCloseDrawer = () => { const onCloseDrawer = () => {
configStore.setLayout('showDrawer', false) configStore.setLayout('showDrawer', false)
} }
const restoreDefault = () => { const restoreDefault = () => {
Local.remove(STORE_CONFIG) Local.remove(STORE_CONFIG)
router.go(0) router.go(0)
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.layout-config-drawer :deep(.el-input__inner) { .layout-config-drawer :deep(.el-input__inner) {
padding: 0 0 0 6px; padding: 0 0 0 6px;
} }
.layout-config-drawer :deep(.el-input-group__append) { .layout-config-drawer :deep(.el-input-group__append) {
padding: 0 10px; padding: 0 10px;
} }
.layout-config-drawer :deep(.el-drawer__header) { .layout-config-drawer :deep(.el-drawer__header) {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.layout-config-drawer :deep(.el-drawer__body) { .layout-config-drawer :deep(.el-drawer__body) {
padding: 0; padding: 0;
} }
.layout-mode-styles-box { .layout-mode-styles-box {
padding: 20px; padding: 20px;
} }
.layout-mode-box-style-row { .layout-mode-box-style-row {
margin-bottom: 15px; margin-bottom: 15px;
} }
.layout-mode-style { .layout-mode-style {
position: relative; position: relative;
height: 100px; height: 100px;
border: 1px solid var(--el-border-color-light); border: 1px solid var(--el-border-color-light);
border-radius: var(--el-border-radius-small); border-radius: var(--el-border-radius-small);
&:hover, &:hover,
&.active { &.active {
border: 1px solid var(--el-color-primary); border: 1px solid var(--el-color-primary);
} }
.layout-mode-style-name { .layout-mode-style-name {
position: absolute; position: absolute;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: var(--el-color-primary-light-5); color: var(--el-color-primary-light-5);
border-radius: 50%; border-radius: 50%;
height: 50px; height: 50px;
width: 50px; width: 50px;
border: 1px solid var(--el-color-primary-light-3); border: 1px solid var(--el-color-primary-light-3);
} }
.layout-mode-style-box { .layout-mode-style-box {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
&.default { &.default {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.layout-mode-style-aside { .layout-mode-style-aside {
width: 18%; width: 18%;
height: 90%; height: 90%;
background-color: var(--el-border-color-lighter); background-color: var(--el-border-color-lighter);
} }
.layout-mode-style-container-box { .layout-mode-style-container-box {
width: 68%; width: 68%;
height: 90%; height: 90%;
margin-left: 4%; margin-left: 4%;
.layout-mode-style-header { .layout-mode-style-header {
width: 100%; width: 100%;
height: 10%; height: 10%;
background-color: var(--el-border-color-lighter); background-color: var(--el-border-color-lighter);
} }
.layout-mode-style-container { .layout-mode-style-container {
width: 100%; width: 100%;
height: 85%; height: 85%;
background-color: var(--el-border-color-extra-light); background-color: var(--el-border-color-extra-light);
margin-top: 5%; margin-top: 5%;
} }
} }
} }
&.classic { &.classic {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.layout-mode-style-aside { .layout-mode-style-aside {
width: 18%; width: 18%;
height: 100%; height: 100%;
background-color: var(--el-border-color-lighter); background-color: var(--el-border-color-lighter);
} }
.layout-mode-style-container-box { .layout-mode-style-container-box {
width: 82%; width: 82%;
height: 100%; height: 100%;
.layout-mode-style-header { .layout-mode-style-header {
width: 100%; width: 100%;
height: 10%; height: 10%;
background-color: var(--el-border-color); background-color: var(--el-border-color);
} }
.layout-mode-style-container { .layout-mode-style-container {
width: 100%; width: 100%;
height: 90%; height: 90%;
background-color: var(--el-border-color-extra-light); background-color: var(--el-border-color-extra-light);
} }
} }
} }
&.streamline { &.streamline {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.layout-mode-style-container-box { .layout-mode-style-container-box {
width: 100%; width: 100%;
height: 100%; height: 100%;
.layout-mode-style-header { .layout-mode-style-header {
width: 100%; width: 100%;
height: 10%; height: 10%;
background-color: var(--el-border-color); background-color: var(--el-border-color);
} }
.layout-mode-style-container { .layout-mode-style-container {
width: 100%; width: 100%;
height: 90%; height: 90%;
background-color: var(--el-border-color-extra-light); background-color: var(--el-border-color-extra-light);
} }
} }
} }
&.double { &.double {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.layout-mode-style-aside { .layout-mode-style-aside {
width: 18%; width: 18%;
height: 100%; height: 100%;
background-color: var(--el-border-color); background-color: var(--el-border-color);
} }
.layout-mode-style-container-box { .layout-mode-style-container-box {
width: 82%; width: 82%;
height: 100%; height: 100%;
.layout-mode-style-header { .layout-mode-style-header {
width: 100%; width: 100%;
height: 10%; height: 10%;
background-color: var(--el-border-color); background-color: var(--el-border-color);
} }
.layout-mode-style-container { .layout-mode-style-container {
width: 100%; width: 100%;
height: 90%; height: 90%;
background-color: var(--el-border-color-extra-light); background-color: var(--el-border-color-extra-light);
} }
} }
} }
} }
.w80 { .w80 {
width: 90%; width: 90%;
} }
</style> </style>

View File

@@ -1,81 +1,81 @@
<template> <template>
<el-scrollbar ref="verticalMenusRef" class="vertical-menus-scrollbar"> <el-scrollbar ref="verticalMenusRef" class="vertical-menus-scrollbar">
<el-menu <el-menu
class="layouts-menu-vertical" class="layouts-menu-vertical"
:collapse-transition="false" :collapse-transition="false"
:unique-opened="config.layout.menuUniqueOpened" :unique-opened="config.layout.menuUniqueOpened"
:default-active="state.defaultActive" :default-active="state.defaultActive"
:collapse="config.layout.menuCollapse" :collapse="config.layout.menuCollapse"
> >
<MenuTree :menus="navTabs.state.tabsViewRoutes" /> <MenuTree :menus="navTabs.state.tabsViewRoutes" />
</el-menu> </el-menu>
</el-scrollbar> </el-scrollbar>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, onMounted, reactive, ref } from 'vue' import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import MenuTree from '@/layouts/admin/components/menus/menuTree.vue' import MenuTree from '@/layouts/admin/components/menus/menuTree.vue'
import { useRoute, onBeforeRouteUpdate, type RouteLocationNormalizedLoaded } from 'vue-router' import { useRoute, onBeforeRouteUpdate, type RouteLocationNormalizedLoaded } from 'vue-router'
import type { ScrollbarInstance } from 'element-plus' import type { ScrollbarInstance } from 'element-plus'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { useNavTabs } from '@/stores/navTabs' import { useNavTabs } from '@/stores/navTabs'
const config = useConfig() const config = useConfig()
const navTabs = useNavTabs() const navTabs = useNavTabs()
const route = useRoute() const route = useRoute()
const verticalMenusRef = ref<ScrollbarInstance>() const verticalMenusRef = ref<ScrollbarInstance>()
const state = reactive({ const state = reactive({
defaultActive: '', defaultActive: '',
}) })
const verticalMenusScrollbarHeight = computed(() => { const verticalMenusScrollbarHeight = computed(() => {
let menuTopBarHeight = 0 let menuTopBarHeight = 0
if (config.layout.menuShowTopBar) { if (config.layout.menuShowTopBar) {
menuTopBarHeight = 50 menuTopBarHeight = 50
} }
if (config.layout.layoutMode == 'Default') { if (config.layout.layoutMode == 'Default') {
return 'calc(100vh - ' + (32 + menuTopBarHeight) + 'px)' return 'calc(100vh - ' + (32 + menuTopBarHeight) + 'px)'
} else { } else {
return 'calc(100vh - ' + menuTopBarHeight + 'px)' return 'calc(100vh - ' + menuTopBarHeight + 'px)'
} }
}) })
// 激活当前路由的菜单 // 激活当前路由的菜单
const currentRouteActive = (currentRoute: RouteLocationNormalizedLoaded) => { const currentRouteActive = (currentRoute: RouteLocationNormalizedLoaded) => {
state.defaultActive = currentRoute.path state.defaultActive = currentRoute.path
} }
// 滚动条滚动到激活菜单所在位置 // 滚动条滚动到激活菜单所在位置
const verticalMenusScroll = () => { const verticalMenusScroll = () => {
nextTick(() => { nextTick(() => {
let activeMenu: HTMLElement | null = document.querySelector('.el-menu.layouts-menu-vertical li.is-active') let activeMenu: HTMLElement | null = document.querySelector('.el-menu.layouts-menu-vertical li.is-active')
if (!activeMenu) return false if (!activeMenu) return false
verticalMenusRef.value?.setScrollTop(activeMenu.offsetTop) verticalMenusRef.value?.setScrollTop(activeMenu.offsetTop)
}) })
} }
onMounted(() => { onMounted(() => {
currentRouteActive(route) currentRouteActive(route)
verticalMenusScroll() verticalMenusScroll()
}) })
onBeforeRouteUpdate((to) => { onBeforeRouteUpdate((to) => {
currentRouteActive(to) currentRouteActive(to)
}) })
</script> </script>
<style> <style>
.vertical-menus-scrollbar { .vertical-menus-scrollbar {
height: v-bind(verticalMenusScrollbarHeight); height: v-bind(verticalMenusScrollbarHeight);
background-color: v-bind('config.getColorVal("menuBackground")'); background-color: v-bind('config.getColorVal("menuBackground")');
} }
.layouts-menu-vertical { .layouts-menu-vertical {
border: 0; border: 0;
padding-bottom: 30px; padding-bottom: 30px;
--el-menu-bg-color: v-bind('config.getColorVal("menuBackground")'); --el-menu-bg-color: v-bind('config.getColorVal("menuBackground")');
--el-menu-text-color: v-bind('config.getColorVal("menuColor")'); --el-menu-text-color: v-bind('config.getColorVal("menuColor")');
--el-menu-active-color: v-bind('config.getColorVal("menuActiveColor")'); --el-menu-active-color: v-bind('config.getColorVal("menuActiveColor")');
--el-menu-hover-color: v-bind('config.getColorVal("menuActiveBackground")'); --el-menu-hover-color: v-bind('config.getColorVal("menuActiveBackground")');
} }
</style> </style>

View File

@@ -1,242 +1,246 @@
<template> <template>
<div class="nav-tabs" ref="tabScrollbarRef"> <div class="nav-tabs" ref="tabScrollbarRef">
<div
v-for="(item, idx) in navTabs.state.tabsView" <div
@click="onTab(item)" v-for="(item, idx) in navTabs.state.tabsView"
@contextmenu.prevent="onContextmenu(item, $event)" @click="onTab(item)"
class="ba-nav-tab" @contextmenu.prevent="onContextmenu(item, $event)"
:class="navTabs.state.activeIndex == idx ? 'active' : ''" class="ba-nav-tab"
:ref="tabsRefs.set" :class="navTabs.state.activeIndex == idx ? 'active' : ''"
:key="idx" :ref="tabsRefs.set"
> :key="idx"
{{ item.meta.title }} >
<transition @after-leave="selectNavTab(tabsRefs[navTabs.state.activeIndex])" name="el-fade-in"> {{ item.meta.title }}
<Icon <transition @after-leave="selectNavTab(tabsRefs[navTabs.state.activeIndex])" name="el-fade-in">
v-if="navTabs.state.tabsView.length > 1" <Icon
class="close-icon" v-if="navTabs.state.tabsView.length > 1"
@click.stop="closeTab(item)" class="close-icon"
size="15" @click.stop="closeTab(item)"
name="el-icon-Close" size="15"
/> name="el-icon-Close"
</transition> />
</div> </transition>
<!-- <div :style='activeBoxStyle' class='nav-tabs-active-box'></div>--> </div>
</div> <!-- <div :style='activeBoxStyle' class='nav-tabs-active-box'></div>-->
<Contextmenu ref="contextmenuRef" :items="state.contextmenuItems" @contextmenuItemClick="onContextmenuItem" /> </div>
</template> <Contextmenu ref="contextmenuRef" :items="state.contextmenuItems" @contextmenuItemClick="onContextmenuItem" />
</template>
<script setup lang="ts">
import { nextTick, onMounted, reactive, ref } from 'vue' <script setup lang="ts">
import { useRoute, useRouter, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router' import { nextTick, onMounted, reactive, ref } from 'vue'
import { useConfig } from '@/stores/config' import { useRoute, useRouter, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router'
import { useNavTabs } from '@/stores/navTabs' import { useConfig } from '@/stores/config'
import { useTemplateRefsList } from '@vueuse/core' import { useNavTabs } from '@/stores/navTabs'
import type { ContextMenuItem, ContextmenuItemClickEmitArg } from '@/components/contextmenu/interface' import { useTemplateRefsList } from '@vueuse/core'
import useCurrentInstance from '@/utils/useCurrentInstance' import type { ContextMenuItem, ContextmenuItemClickEmitArg } from '@/components/contextmenu/interface'
import Contextmenu from '@/components/contextmenu/index.vue' import useCurrentInstance from '@/utils/useCurrentInstance'
import horizontalScroll from '@/utils/horizontalScroll' import Contextmenu from '@/components/contextmenu/index.vue'
import { getFirstRoute, routePush } from '@/utils/router' import horizontalScroll from '@/utils/horizontalScroll'
import { adminBaseRoutePath } from '@/router/static' import { getFirstRoute, routePush } from '@/utils/router'
import { adminBaseRoutePath } from '@/router/static'
const route = useRoute()
const router = useRouter() const route = useRoute()
const config = useConfig() const router = useRouter()
const navTabs = useNavTabs() const config = useConfig()
const navTabs = useNavTabs()
const { proxy } = useCurrentInstance()
const tabScrollbarRef = ref() const { proxy } = useCurrentInstance()
const tabsRefs = useTemplateRefsList<HTMLDivElement>() const tabScrollbarRef = ref()
const tabsRefs = useTemplateRefsList<HTMLDivElement>()
const contextmenuRef = ref()
const contextmenuRef = ref()
const state: {
contextmenuItems: ContextMenuItem[] const state: {
} = reactive({ contextmenuItems: ContextMenuItem[]
contextmenuItems: [ } = reactive({
{ name: 'refresh', label: '重新加载', icon: 'fa fa-refresh' }, contextmenuItems: [
{ name: 'close', label: '关闭标签', icon: 'fa fa-times' }, { name: 'refresh', label: '重新加载', icon: 'fa fa-refresh' },
{ name: 'fullScreen', label: '当前标签全屏', icon: 'el-icon-FullScreen' }, { name: 'close', label: '关闭标签', icon: 'fa fa-times' },
{ name: 'closeOther', label: '关闭其他标签', icon: 'fa fa-minus' }, { name: 'fullScreen', label: '当前标签全屏', icon: 'el-icon-FullScreen' },
{ name: 'closeAll', label: '关闭全部标签', icon: 'fa fa-stop' } { name: 'closeOther', label: '关闭其他标签', icon: 'fa fa-minus' },
] { name: 'closeAll', label: '关闭全部标签', icon: 'fa fa-stop' }
}) ]
})
const activeBoxStyle = reactive({
width: '0', const activeBoxStyle = reactive({
transform: 'translateX(0px)' width: '0',
}) transform: 'translateX(0px)'
})
const onTab = (menu: RouteLocationNormalized) => {
router.push(menu) const onTab = (menu: RouteLocationNormalized) => {
} router.push(menu)
}
const onContextmenu = (menu: RouteLocationNormalized, el: MouseEvent) => {
// 禁用刷新 const onContextmenu = (menu: RouteLocationNormalized, el: MouseEvent) => {
state.contextmenuItems[0].disabled = route.path !== menu.path
// 禁用关闭其他和关闭全部 // 禁用刷新
state.contextmenuItems[4].disabled = state.contextmenuItems[3].disabled = state.contextmenuItems[0].disabled = route.path !== menu.path
navTabs.state.tabsView.length == 1 ? true : false
const { clientX, clientY } = el // 禁用关闭其他和关闭全部
contextmenuRef.value.onShowContextmenu(menu, { state.contextmenuItems[4].disabled = state.contextmenuItems[3].disabled =
x: clientX, navTabs.state.tabsView.length == 1 ? true : false
y: clientY
}) const { clientX, clientY } = el
} contextmenuRef.value.onShowContextmenu(menu, {
x: clientX,
// tab 激活状态切换 y: clientY
const selectNavTab = function (dom: HTMLDivElement) { })
if (!dom) { }
return false
} // tab 激活状态切换
activeBoxStyle.width = dom.clientWidth + 'px' const selectNavTab = function (dom: HTMLDivElement) {
activeBoxStyle.transform = `translateX(${dom.offsetLeft}px)` if (!dom) {
return false
let scrollLeft = dom.offsetLeft + dom.clientWidth - tabScrollbarRef.value.clientWidth }
if (dom.offsetLeft < tabScrollbarRef.value.scrollLeft) { activeBoxStyle.width = dom.clientWidth + 'px'
tabScrollbarRef.value.scrollTo(dom.offsetLeft, 0) activeBoxStyle.transform = `translateX(${dom.offsetLeft}px)`
} else if (scrollLeft > tabScrollbarRef.value.scrollLeft) {
tabScrollbarRef.value.scrollTo(scrollLeft, 0) let scrollLeft = dom.offsetLeft + dom.clientWidth - tabScrollbarRef.value.clientWidth
} if (dom.offsetLeft < tabScrollbarRef.value.scrollLeft) {
} tabScrollbarRef.value.scrollTo(dom.offsetLeft, 0)
} else if (scrollLeft > tabScrollbarRef.value.scrollLeft) {
const toLastTab = () => { tabScrollbarRef.value.scrollTo(scrollLeft, 0)
const lastTab = navTabs.state.tabsView.slice(-1)[0] }
if (lastTab) { }
router.push(lastTab)
} else { const toLastTab = () => {
router.push(adminBaseRoutePath) const lastTab = navTabs.state.tabsView.slice(-1)[0]
} if (lastTab) {
} router.push(lastTab)
} else {
const closeTab = (route: RouteLocationNormalized) => { router.push(adminBaseRoutePath)
navTabs.closeTab(route) }
proxy.eventBus.emit('onTabViewClose', route) }
if (navTabs.state.activeRoute?.path === route.path) {
toLastTab() const closeTab = (route: RouteLocationNormalized) => {
} else { navTabs.closeTab(route)
navTabs.setActiveRoute(navTabs.state.activeRoute!) proxy.eventBus.emit('onTabViewClose', route)
nextTick(() => { if (navTabs.state.activeRoute?.path === route.path) {
selectNavTab(tabsRefs.value[navTabs.state.activeIndex]) toLastTab()
}) } else {
} navTabs.setActiveRoute(navTabs.state.activeRoute!)
nextTick(() => {
contextmenuRef.value.onHideContextmenu() selectNavTab(tabsRefs.value[navTabs.state.activeIndex])
} })
}
const closeOtherTab = (menu: RouteLocationNormalized) => {
navTabs.closeTabs(menu) contextmenuRef.value.onHideContextmenu()
navTabs.setActiveRoute(menu) }
if (navTabs.state.activeRoute?.path !== route.path) {
router.push(menu!.path) const closeOtherTab = (menu: RouteLocationNormalized) => {
} navTabs.closeTabs(menu)
} navTabs.setActiveRoute(menu)
if (navTabs.state.activeRoute?.path !== route.path) {
const closeAllTab = (menu: RouteLocationNormalized) => { router.push(menu!.path)
let firstRoute = getFirstRoute(navTabs.state.tabsViewRoutes) }
if (firstRoute && firstRoute.path == menu.path) { }
return closeOtherTab(menu)
} const closeAllTab = (menu: RouteLocationNormalized) => {
if (firstRoute && firstRoute.path == navTabs.state.activeRoute?.path) { let firstRoute = getFirstRoute(navTabs.state.tabsViewRoutes)
return closeOtherTab(navTabs.state.activeRoute) if (firstRoute && firstRoute.path == menu.path) {
} return closeOtherTab(menu)
navTabs.closeTabs(false) }
if (firstRoute) routePush(firstRoute.path) if (firstRoute && firstRoute.path == navTabs.state.activeRoute?.path) {
} return closeOtherTab(navTabs.state.activeRoute)
}
const onContextmenuItem = async (item: ContextmenuItemClickEmitArg) => { navTabs.closeTabs(false)
const { name, menu } = item if (firstRoute) routePush(firstRoute.path)
if (!menu) return }
switch (name) {
case 'refresh': const onContextmenuItem = async (item: ContextmenuItemClickEmitArg) => {
proxy.eventBus.emit('onTabViewRefresh', menu) const { name, menu } = item
break if (!menu) return
case 'close': switch (name) {
closeTab(menu) case 'refresh':
break proxy.eventBus.emit('onTabViewRefresh', menu)
case 'closeOther': break
closeOtherTab(menu) case 'close':
break closeTab(menu)
case 'closeAll': break
closeAllTab(menu) case 'closeOther':
break closeOtherTab(menu)
case 'fullScreen': break
if (route.path !== menu?.path) { case 'closeAll':
router.push(menu?.path as string) closeAllTab(menu)
} break
navTabs.setFullScreen(true) case 'fullScreen':
break if (route.path !== menu?.path) {
} router.push(menu?.path as string)
} }
navTabs.setFullScreen(true)
const updateTab = function (newRoute: RouteLocationNormalized) { break
// 添加tab }
navTabs.addTab(newRoute) }
// 激活当前tab
navTabs.setActiveRoute(newRoute) const updateTab = function (newRoute: RouteLocationNormalized) {
// 添加tab
nextTick(() => { navTabs.addTab(newRoute)
selectNavTab(tabsRefs.value[navTabs.state.activeIndex]) // 激活当前tab
}) navTabs.setActiveRoute(newRoute)
}
nextTick(() => {
onBeforeRouteUpdate(async to => { selectNavTab(tabsRefs.value[navTabs.state.activeIndex])
})
}
updateTab(to)
onBeforeRouteUpdate(async to => {
})
onMounted(() => { updateTab(to)
updateTab(router.currentRoute.value)
new horizontalScroll(tabScrollbarRef.value) })
})
</script> onMounted(() => {
updateTab(router.currentRoute.value)
<style scoped lang="scss"> new horizontalScroll(tabScrollbarRef.value)
.dark { })
.close-icon { </script>
color: v-bind('config.getColorVal("headerBarTabColor")') !important;
} <style scoped lang="scss">
.dark {
.ba-nav-tab.active { .close-icon {
.close-icon { color: v-bind('config.getColorVal("headerBarTabColor")') !important;
color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important; }
}
} .ba-nav-tab.active {
} .close-icon {
color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important;
.nav-tabs { }
overflow-x: auto; }
overflow-y: hidden; }
margin-right: var(--ba-main-space);
scrollbar-width: none; .nav-tabs {
overflow-x: auto;
&::-webkit-scrollbar { overflow-y: hidden;
height: 5px; margin-right: var(--ba-main-space);
} // scrollbar-width: none;
// overflow-x: auto;
// &::-webkit-scrollbar {
//&::-webkit-scrollbar-thumb { height: 5px;
// background: #eaeaea; }
// border-radius: var(--el-border-radius-base);
// box-shadow: none; //
// -webkit-box-shadow: none; //&::-webkit-scrollbar-thumb {
//} // background: #eaeaea;
// // border-radius: var(--el-border-radius-base);
//&::-webkit-scrollbar-track { // box-shadow: none;
// background: v-bind('config.layout.layoutMode == "Default" ? "none":config.getColorVal("headerBarBackground")'); // -webkit-box-shadow: none;
//} //}
// //
//&:hover { //&::-webkit-scrollbar-track {
// &::-webkit-scrollbar-thumb:hover { // background: v-bind('config.layout.layoutMode == "Default" ? "none":config.getColorVal("headerBarBackground")');
// background: #c8c9cc; //}
// } //
//} //&:hover {
} // &::-webkit-scrollbar-thumb:hover {
// background: #c8c9cc;
.ba-nav-tab { // }
white-space: nowrap; //}
height: 40px; }
}
</style> .ba-nav-tab {
white-space: nowrap;
height: 40px;
}
</style>

View File

@@ -1,238 +1,241 @@
<template> <template>
<div class="nav-menus" :class="configStore.layout.layoutMode"> <div class="nav-menus" :class="configStore.layout.layoutMode">
<div @click="savePng" class="nav-menu-item"> <!-- <div @click="savePng" class="nav-menu-item">
<Icon <Icon
:color="configStore.getColorVal('headerBarTabColor')" :color="configStore.getColorVal('headerBarTabColor')"
class="nav-menu-icon" class="nav-menu-icon"
name="el-icon-Camera" name="el-icon-Camera"
size="18" size="18"
/> />
</div> </div>
<div @click="onFullScreen" class="nav-menu-item" :class="state.isFullScreen ? 'hover' : ''"> <div @click="onFullScreen" class="nav-menu-item" :class="state.isFullScreen ? 'hover' : ''">
<Icon <Icon
:color="configStore.getColorVal('headerBarTabColor')" :color="configStore.getColorVal('headerBarTabColor')"
class="nav-menu-icon" class="nav-menu-icon"
v-if="state.isFullScreen" v-if="state.isFullScreen"
name="fa-solid fa-compress" name="fa-solid fa-compress"
size="18" size="18"
/> />
<Icon <Icon
:color="configStore.getColorVal('headerBarTabColor')" :color="configStore.getColorVal('headerBarTabColor')"
class="nav-menu-icon" class="nav-menu-icon"
v-else v-else
name="fa-solid fa-expand" name="fa-solid fa-expand"
size="18" size="18"
/> />
</div> </div> -->
<el-dropdown style="height: 100%" @command="handleCommand"> <el-dropdown style="height: 100%" @command="handleCommand">
<div class="admin-info" :class="state.currentNavMenu == 'adminInfo' ? 'hover' : ''"> <div class="admin-info" :class="state.currentNavMenu == 'adminInfo' ? 'hover' : ''">
<el-avatar :size="25" fit="fill"> <el-avatar :size="25" fit="fill">
<img src="@/assets/avatar.png" alt="" /> <img src="@/assets/avatar.png" alt="" />
</el-avatar> </el-avatar>
<div class="admin-name">{{ adminInfo.nickname }}</div> <div class="admin-name">{{ adminInfo.nickname }}</div>
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item command="adminInfo">个人资料</el-dropdown-item> <el-dropdown-item command="adminInfo">个人资料</el-dropdown-item>
<el-dropdown-item command="changePwd">修改密码</el-dropdown-item> <el-dropdown-item command="changePwd">修改密码</el-dropdown-item>
<el-dropdown-item command="layout">退出登录</el-dropdown-item> <el-dropdown-item command="layout">退出登录</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<!-- <div @click="configStore.setLayout('showDrawer', true)" class="nav-menu-item"> <!-- <div @click="configStore.setLayout('showDrawer', true)" class="nav-menu-item">
<Icon <Icon
:color="configStore.getColorVal('headerBarTabColor')" :color="configStore.getColorVal('headerBarTabColor')"
class="nav-menu-icon" class="nav-menu-icon"
name="fa fa-cogs" name="fa fa-cogs"
size="18" size="18"
/> />
</div> -->" </div> -->
"
<Config />
<PopupPwd ref="popupPwd" /> <Config />
<AdminInfo ref="popupAdminInfo" /> <PopupPwd ref="popupPwd" />
<!-- <TerminalVue /> --> <AdminInfo ref="popupAdminInfo" />
</div> <!-- <TerminalVue /> -->
</template> </div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue' <script lang="ts" setup>
import screenfull from 'screenfull' import { reactive, ref } from 'vue'
import { useConfig } from '@/stores/config' import screenfull from 'screenfull'
import { ElMessage } from 'element-plus' import { useConfig } from '@/stores/config'
import Config from './config.vue' import { ElMessage } from 'element-plus'
import { useAdminInfo } from '@/stores/adminInfo' import Config from './config.vue'
import router from '@/router' import { useAdminInfo } from '@/stores/adminInfo'
import { routePush } from '@/utils/router' import router from '@/router'
import { fullUrl } from '@/utils/common' import { routePush } from '@/utils/router'
import html2canvas from 'html2canvas' import { fullUrl } from '@/utils/common'
import PopupPwd from './popup/password.vue' import html2canvas from 'html2canvas'
import AdminInfo from './popup/adminInfo.vue' import PopupPwd from './popup/password.vue'
import { useNavTabs } from '@/stores/navTabs' import AdminInfo from './popup/adminInfo.vue'
import { useNavTabs } from '@/stores/navTabs'
const adminInfo = useAdminInfo() const adminInfo = useAdminInfo()
const navTabs = useNavTabs() const navTabs = useNavTabs()
const configStore = useConfig() const configStore = useConfig()
const popupPwd = ref() const popupPwd = ref()
const popupAdminInfo = ref() const popupAdminInfo = ref()
const state = reactive({ const state = reactive({
isFullScreen: false, isFullScreen: false,
currentNavMenu: '', currentNavMenu: '',
showLayoutDrawer: false, showLayoutDrawer: false,
showAdminInfoPopover: false showAdminInfoPopover: false
}) })
const savePng = () => { const savePng = () => {
html2canvas(document.body, { html2canvas(document.body, {
scale: 1, scale: 1,
useCORS: true useCORS: true
}).then(function (canvas) { }).then(function (canvas) {
var link = document.createElement('a') var link = document.createElement('a')
link.href = canvas.toDataURL('image/png') link.href = canvas.toDataURL('image/png')
link.download = 'screenshot.png' link.download = 'screenshot.png'
link.click() link.click()
}) })
} }
const onFullScreen = () => { const onFullScreen = () => {
if (!screenfull.isEnabled) { if (!screenfull.isEnabled) {
ElMessage.warning('layouts.Full screen is not supported') ElMessage.warning('layouts.Full screen is not supported')
return false return false
} }
screenfull.toggle() screenfull.toggle()
screenfull.onchange(() => { screenfull.onchange(() => {
state.isFullScreen = screenfull.isFullscreen state.isFullScreen = screenfull.isFullscreen
}) })
} }
const handleCommand = (key: string) => { const handleCommand = async (key: string) => {
// console.log(key) // console.log(key)
switch (key) { switch (key) {
case 'adminInfo': case 'adminInfo':
popupAdminInfo.value.open() popupAdminInfo.value.open()
break break
case 'changePwd': case 'changePwd':
popupPwd.value.open() popupPwd.value.open()
break break
case 'layout': case 'layout':
navTabs.closeTabs() await window.location.reload()
window.localStorage.clear() setTimeout(() => {
adminInfo.reset() navTabs.closeTabs()
router.push({ name: 'login' }) window.localStorage.clear()
break adminInfo.reset()
default: router.push({ name: 'login' })
break }, 0)
} break
} default:
</script> break
}
<style scoped lang="scss"> }
.nav-menus.Default { </script>
border-radius: var(--el-border-radius-base);
box-shadow: var(--el-box-shadow-light); <style scoped lang="scss">
} .nav-menus.Default {
border-radius: var(--el-border-radius-base);
.nav-menus { box-shadow: var(--el-box-shadow-light);
display: flex; }
align-items: center;
height: 100%; .nav-menus {
margin-left: auto; display: flex;
background-color: v-bind('configStore.getColorVal("headerBarBackground")'); align-items: center;
height: 100%;
.nav-menu-item { margin-left: auto;
height: 100%; background-color: v-bind('configStore.getColorVal("headerBarBackground")');
width: 40px;
display: flex; .nav-menu-item {
align-items: center; height: 100%;
justify-content: center; width: 40px;
cursor: pointer; display: flex;
align-items: center;
.nav-menu-icon { justify-content: center;
box-sizing: content-box; cursor: pointer;
color: v-bind('configStore.getColorVal("headerBarTabColor")');
} .nav-menu-icon {
box-sizing: content-box;
&:hover { color: v-bind('configStore.getColorVal("headerBarTabColor")');
.icon { }
animation: twinkle 0.3s ease-in-out;
} &:hover {
} .icon {
} animation: twinkle 0.3s ease-in-out;
}
.admin-info { }
display: flex; }
height: 100%;
padding: 0 10px; .admin-info {
align-items: center; display: flex;
cursor: pointer; height: 100%;
user-select: none; padding: 0 10px;
color: v-bind('configStore.getColorVal("headerBarTabColor")'); align-items: center;
cursor: pointer;
&:hover { user-select: none;
color: v-bind('configStore.getColorVal("headerBarTabActiveColor")'); color: v-bind('configStore.getColorVal("headerBarTabColor")');
}
} &:hover {
color: v-bind('configStore.getColorVal("headerBarTabActiveColor")');
.admin-name { }
padding-left: 6px; }
white-space: nowrap;
} .admin-name {
padding-left: 6px;
.nav-menu-item:hover, white-space: nowrap;
.admin-info:hover, }
.nav-menu-item.hover,
.admin-info.hover { .nav-menu-item:hover,
background: v-bind('configStore.getColorVal("headerBarHoverBackground")'); .admin-info:hover,
.nav-menu-item.hover,
.nav-menu-icon { .admin-info.hover {
color: v-bind('configStore.getColorVal("headerBarTabActiveColor")') !important; background: v-bind('configStore.getColorVal("headerBarHoverBackground")');
}
} .nav-menu-icon {
} color: v-bind('configStore.getColorVal("headerBarTabActiveColor")') !important;
}
.dropdown-menu-box :deep(.el-dropdown-menu__item) { }
justify-content: center; }
}
.dropdown-menu-box :deep(.el-dropdown-menu__item) {
.admin-info-base { justify-content: center;
display: flex; }
justify-content: center;
flex-wrap: wrap; .admin-info-base {
padding-top: 10px; display: flex;
justify-content: center;
.admin-info-other { flex-wrap: wrap;
display: block; padding-top: 10px;
width: 100%;
text-align: center; .admin-info-other {
padding: 10px 0; display: block;
width: 100%;
.admin-info-name { text-align: center;
font-size: var(--el-font-size-large); padding: 10px 0;
}
} .admin-info-name {
} font-size: var(--el-font-size-large);
}
.admin-info-footer { }
padding: 10px 0; }
margin: 0 -12px -12px -12px;
display: flex; .admin-info-footer {
justify-content: space-around; padding: 10px 0;
} margin: 0 -12px -12px -12px;
display: flex;
.pt2 { justify-content: space-around;
padding-top: 2px; }
}
.pt2 {
@keyframes twinkle { padding-top: 2px;
0% { }
transform: scale(0);
} @keyframes twinkle {
80% { 0% {
transform: scale(1.2); transform: scale(0);
} }
100% { 80% {
transform: scale(1); transform: scale(1.2);
} }
} 100% {
</style> transform: scale(1);
}
}
</style>

View File

@@ -1,109 +1,123 @@
<template> <template>
<el-dialog draggable width="500px" v-model.trim="dialogVisible" :title="title"> <el-dialog draggable width="500px" v-model.trim="dialogVisible" :title="title">
<el-scrollbar> <el-scrollbar>
<el-form :inline="false" :model="form" label-width="120px" :rules="rules" class="form-one" ref="formRef"> <el-form :inline="false" :model="form" label-width="120px" :rules="rules" class="form-one" ref="formRef">
<el-form-item label="校验密码:" prop="password"> <el-form-item label="校验密码:" prop="password">
<el-input v-model.trim="form.password" type="password" placeholder="请输入校验密码" show-password /> <el-input v-model.trim="form.password" type="password" placeholder="请输入校验密码" show-password />
</el-form-item> </el-form-item>
<el-form-item label="新密码:" prop="newPwd"> <el-form-item label="新密码:" prop="newPwd">
<el-input v-model.trim="form.newPwd" type="password" placeholder="请输入新密码" show-password /> <el-input v-model.trim="form.newPwd" type="password" placeholder="请输入新密码" show-password />
</el-form-item> </el-form-item>
<el-form-item label="确认密码:" prop="confirmPwd"> <el-form-item label="确认密码:" prop="confirmPwd">
<el-input v-model.trim="form.confirmPwd" type="password" placeholder="请输入确认密码" show-password /> <el-input
</el-form-item> v-model.trim="form.confirmPwd"
</el-form> type="password"
</el-scrollbar> placeholder="请输入确认密码"
<template #footer> show-password
<span class="dialog-footer"> />
<el-button @click="dialogVisible = false">取消</el-button> </el-form-item>
<el-button type="primary" @click="submit">确认</el-button> </el-form>
</span> </el-scrollbar>
</template> <template #footer>
</el-dialog> <span class="dialog-footer">
</template> <el-button @click="dialogVisible = false">取消</el-button>
<script lang="ts" setup> <el-button type="primary" @click="submit">确认</el-button>
import { ref, inject } from 'vue' </span>
import { reactive } from 'vue' </template>
import { ElMessage } from 'element-plus' </el-dialog>
import { passwordConfirm, updatePassword } from '@/api/user-boot/user' </template>
import { validatePwd } from '@/utils/common' <script lang="ts" setup>
import { useAdminInfo } from '@/stores/adminInfo' import { ref, inject } from 'vue'
import { reactive } from 'vue'
const adminInfo = useAdminInfo() import { ElMessage } from 'element-plus'
const dialogVisible = ref(false) import { passwordConfirm, updatePassword } from '@/api/user-boot/user'
const title = ref('修改密码') import { validatePwd } from '@/utils/common'
const formRef = ref() import { useAdminInfo } from '@/stores/adminInfo'
// 注意不要和表单ref的命名冲突 import router from '@/router'
const form = reactive({ import { useNavTabs } from '@/stores/navTabs'
password: '', const adminInfo = useAdminInfo()
newPwd: '', const navTabs = useNavTabs()
confirmPwd: '' const dialogVisible = ref(false)
}) const title = ref('修改密码')
const rules = { const formRef = ref()
password: [ // 注意不要和表单ref的命名冲突
{ required: true, message: '请输入校验密码', trigger: 'blur' }, const form = reactive({
{ password: '',
min: 6, newPwd: '',
max: 16, confirmPwd: ''
message: '长度在 6 到 16 个字符', })
trigger: 'blur' const rules = {
} password: [
], { required: true, message: '请输入校验密码', trigger: 'blur' },
newPwd: [ {
{ required: true, message: '请输入密码', trigger: 'blur' }, min: 6,
{ max: 16,
min: 6, message: '长度在 6 到 16 个字符',
max: 16, trigger: 'blur'
message: '长度在 6 到 16 个字符', }
trigger: 'blur' ],
}, newPwd: [
{ validator: validatePwd, trigger: 'blur' } { required: true, message: '请输入密码', trigger: 'blur' },
], {
confirmPwd: [ min: 6,
{ required: true, message: '请确认密码', trigger: 'blur' }, max: 16,
{ message: '长度在 6 到 16 个字符',
min: 6, trigger: 'blur'
max: 16, },
message: '长度在 6 到 16 个字符', { validator: validatePwd, trigger: 'blur' }
trigger: 'blur' ],
}, confirmPwd: [
{ { required: true, message: '请确认密码', trigger: 'blur' },
validator: (rule: any, value: string, callback: any) => { {
if (value === '') { min: 6,
callback(new Error('请再次输入密码')) max: 16,
} else if (value !== form.newPwd) { message: '长度在 6 到 16 个字符',
callback(new Error('两次输入密码不一致!')) trigger: 'blur'
} else { },
callback() {
} validator: (rule: any, value: string, callback: any) => {
}, if (value === '') {
trigger: 'blur', callback(new Error('请再次输入密码'))
required: true } else if (value !== form.newPwd) {
} callback(new Error('两次输入密码不一致!'))
] } else {
} callback()
}
const open = () => { },
dialogVisible.value = true trigger: 'blur',
form.password = '' required: true
form.newPwd = '' }
form.confirmPwd = '' ]
} }
const submit = () => {
formRef.value.validate(async (valid: boolean) => { const open = () => {
if (valid) { dialogVisible.value = true
passwordConfirm(form.password).then(res => { form.password = ''
updatePassword({ form.newPwd = ''
id: adminInfo.$state.userIndex, form.confirmPwd = ''
newPassword: form.newPwd }
}).then((res: any) => { const submit = () => {
ElMessage.success('密码修改成功') formRef.value.validate(async (valid: boolean) => {
dialogVisible.value = false if (valid) {
}) passwordConfirm(form.password).then(res => {
}) updatePassword({
} id: adminInfo.$state.userIndex,
}) newPassword: form.newPwd
} }).then(async (res: any) => {
ElMessage.success('密码修改成功')
defineExpose({ open }) dialogVisible.value = false
</script>
setTimeout(() => {
navTabs.closeTabs()
window.localStorage.clear()
adminInfo.reset()
router.push({ name: 'login' })
}, 0)
})
})
}
})
}
defineExpose({ open })
</script>

View File

@@ -1,125 +1,126 @@
<template> <template>
<component :is='config.layout.layoutMode'></component> <component :is='config.layout.layoutMode'></component>
</template> </template>
<script setup lang='ts'> <script setup lang='ts'>
import { reactive } from 'vue' import { reactive } from 'vue'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { useNavTabs } from '@/stores/navTabs' import { useNavTabs } from '@/stores/navTabs'
import { useAdminInfo } from '@/stores/adminInfo' import { useAdminInfo } from '@/stores/adminInfo'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import Default from '@/layouts/admin/container/default.vue' import Default from '@/layouts/admin/container/default.vue'
import Classic from '@/layouts/admin/container/classic.vue' import Classic from '@/layouts/admin/container/classic.vue'
import Streamline from '@/layouts/admin/container/streamline.vue' import Streamline from '@/layouts/admin/container/streamline.vue'
import Double from '@/layouts/admin/container/double.vue' import Double from '@/layouts/admin/container/double.vue'
import { onMounted, onBeforeMount } from 'vue' import { onMounted, onBeforeMount } from 'vue'
import { handleAdminRoute, getFirstRoute, routePush } from '@/utils/router' import { handleAdminRoute, getFirstRoute, routePush } from '@/utils/router'
import router from '@/router/index' import router from '@/router/index'
import { useEventListener } from '@vueuse/core' import { useEventListener } from '@vueuse/core'
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { setNavTabsWidth } from '@/utils/layout' import { setNavTabsWidth } from '@/utils/layout'
import { adminBaseRoutePath } from '@/router/static' import { adminBaseRoutePath } from '@/router/static'
import { getRouteMenu, dictDataCache } from '@/api/auth' import { getRouteMenu, dictDataCache } from '@/api/auth'
import { getAreaList, areaSelect } from '@/api/common' import { getAreaList, areaSelect } from '@/api/common'
import { BasicDictData } from '@/stores/interface' import { BasicDictData } from '@/stores/interface'
import { getAllUserSimpleList, getUserById } from '@/api/user-boot/user' import { getAllUserSimpleList, getUserById } from '@/api/user-boot/user'
defineOptions({ defineOptions({
components: { Default, Classic, Streamline, Double } components: { Default, Classic, Streamline, Double }
}) })
const navTabs = useNavTabs() const navTabs = useNavTabs()
const config = useConfig() const config = useConfig()
const route = useRoute() const route = useRoute()
const adminInfo = useAdminInfo() const adminInfo = useAdminInfo()
const dictData = useDictData() const dictData = useDictData()
const state = reactive({ const state = reactive({
autoMenuCollapseLock: false autoMenuCollapseLock: false
}) })
onMounted(() => { onMounted(() => {
// if (!adminInfo.token) return router.push({ name: 'login' }) // if (!adminInfo.token) return router.push({ name: 'login' })
init() init()
setNavTabsWidth() setNavTabsWidth()
useEventListener(window, 'resize', setNavTabsWidth) useEventListener(window, 'resize', setNavTabsWidth)
}) })
onBeforeMount(() => { onBeforeMount(() => {
onAdaptiveLayout() onAdaptiveLayout()
useEventListener(window, 'resize', onAdaptiveLayout) useEventListener(window, 'resize', onAdaptiveLayout)
}) })
const init = async () => { const init = async () => {
await Promise.all([getAreaList(), dictDataCache(), getUserById(), areaSelect(),getAllUserSimpleList()]).then(res => { await Promise.all([getAreaList(), dictDataCache(), getUserById(), areaSelect(),getAllUserSimpleList()]).then(res => {
dictData.state.area = res[0].data dictData.state.area = res[0].data
dictData.state.basic = res[1].data dictData.state.basic = res[1].data
// dictData.state.userList=res[4].data // dictData.state.userList=res[4].data
adminInfo.dataFill(res[2].data) adminInfo.dataFill(res[2].data)
// dictData.state.areaTree = res[3].data // dictData.state.areaTree = res[3].data
}) })
/** /**
* 后台初始化请求,获取站点配置,动态路由等信息 * 后台初始化请求,获取站点配置,动态路由等信息
*/ */
getRouteMenu().then((res: any) => { getRouteMenu().then((res: any) => {
const handlerMenu = (data: any) => { const handlerMenu = (data: any) => {
data.forEach((item: any) => { data.forEach((item: any) => {
item.routePath = item.routePath =
item.routePath[0] == '/' ? item.routePath.substring(1, item.routePath.length) : item.routePath item.routePath[0] == '/' ? item.routePath.substring(1, item.routePath.length) : item.routePath
item.path = item.routePath item.path = item.routePath
item.name = item.routePath item.name = item.routePath
item.keepalive = item.routePath item.keepalive = item.routePath
item.component = item.routeName item.component = item.routeName
? item.routeName.indexOf('/src/views/') > -1 ? item.routeName.indexOf('/src/views/') > -1
? item.routeName ? item.routeName
: `/src/views/${item.routeName}/index.vue` : `/src/views/${item.routeName}/index.vue`
: '' : ''
item.type = item.children && item.children.length > 0 ? 'menu_dir' : 'menu' item.type = item.children && item.children.length > 0 ? 'menu_dir' : 'menu'
item.menu_type = item.children && item.children.length > 0 ? null : 'tab' item.menu_type = item.children && item.children.length > 0 ? null : 'tab'
if (item.children) { if (item.children) {
handlerMenu(item.children) handlerMenu(item.children)
} }
}) })
} }
handlerMenu(res.data) handlerMenu(res.data)
handleAdminRoute(res.data) handleAdminRoute(res.data)
if (route.params.to) { if (route.params.to) {
const lastRoute = JSON.parse(route.params.to as string) const lastRoute = JSON.parse(route.params.to as string)
if (lastRoute.path != adminBaseRoutePath) {
let query = !isEmpty(lastRoute.query) ? lastRoute.query : {} if (lastRoute.path != adminBaseRoutePath) {
routePush({ path: lastRoute.path, query: query }) let query = !isEmpty(lastRoute.query) ? lastRoute.query : {}
return routePush({ path: lastRoute.path, query: query })
} return
} }
// 跳转到第一个菜单 }
let firstRoute = getFirstRoute(navTabs.state.tabsViewRoutes) // 跳转到第一个菜单
if (firstRoute) routePush(firstRoute.path) let firstRoute = getFirstRoute(navTabs.state.tabsViewRoutes)
}) if (firstRoute) routePush(firstRoute.path)
} })
}
const onAdaptiveLayout = () => {
let defaultBeforeResizeLayout = { const onAdaptiveLayout = () => {
layoutMode: config.layout.layoutMode, let defaultBeforeResizeLayout = {
menuCollapse: config.layout.menuCollapse layoutMode: config.layout.layoutMode,
} menuCollapse: config.layout.menuCollapse
}
const clientWidth = document.body.clientWidth
if (clientWidth < 1024) { const clientWidth = document.body.clientWidth
/** if (clientWidth < 1024) {
* 锁定窗口改变自动调整 menuCollapse /**
* 避免已是小窗且打开了菜单栏时,意外的自动关闭菜单栏 * 锁定窗口改变自动调整 menuCollapse
*/ * 避免已是小窗且打开了菜单栏时,意外的自动关闭菜单栏
if (!state.autoMenuCollapseLock) { */
state.autoMenuCollapseLock = true if (!state.autoMenuCollapseLock) {
config.setLayout('menuCollapse', true) state.autoMenuCollapseLock = true
} config.setLayout('menuCollapse', true)
config.setLayout('shrink', true) }
config.setLayoutMode('Classic') config.setLayout('shrink', true)
} else { config.setLayoutMode('Classic')
state.autoMenuCollapseLock = false } else {
config.setLayout('menuCollapse', defaultBeforeResizeLayout.menuCollapse) state.autoMenuCollapseLock = false
config.setLayout('shrink', false) config.setLayout('menuCollapse', defaultBeforeResizeLayout.menuCollapse)
config.setLayoutMode(defaultBeforeResizeLayout.layoutMode) config.setLayout('shrink', false)
} config.setLayoutMode(defaultBeforeResizeLayout.layoutMode)
} }
</script> }
</script>

View File

@@ -1,47 +1,47 @@
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import staticRoutes from '@/router/static' import staticRoutes from '@/router/static'
import { useAdminInfo } from '@/stores/adminInfo' import { useAdminInfo } from '@/stores/adminInfo'
import NProgress from 'nprogress' import NProgress from 'nprogress'
import { loading } from '@/utils/loading' import { loading } from '@/utils/loading'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes: staticRoutes routes: staticRoutes
}) })
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
NProgress.configure({ showSpinner: false }) NProgress.configure({ showSpinner: false })
NProgress.start() NProgress.start()
if (!window.existLoading) { if (!window.existLoading) {
loading.show() loading.show()
window.existLoading = true window.existLoading = true
} }
if (to.path == '/login' || to.path == '/404'|| to.path == '/policy'|| to.path == '/agreement') { if (to.path == '/login' || to.path == '/404'|| to.path == '/policy'|| to.path == '/agreement') {
// 登录或者注册才可以往下进行 // 登录或者注册才可以往下进行
next() next()
} else { } else {
// 获取 token // 获取 token
const adminInfo = useAdminInfo() const adminInfo = useAdminInfo()
const token = adminInfo.getToken() const token = adminInfo.getToken()
// token 不存在 // token 不存在
if (token === null || token === '') { if (token === null || token === '') {
ElMessage.error('您还没有登录,请先登录') // ElMessage.error('您还没有登录,请先登录')
next('/login') next('/login')
} else { } else {
next() next()
} }
} }
// next() // next()
}) })
// 路由加载后 // 路由加载后
router.afterEach(() => { router.afterEach(() => {
if (window.existLoading) { if (window.existLoading) {
loading.hide() loading.hide()
} }
NProgress.done() NProgress.done()
}) })
export default router export default router

View File

@@ -35,6 +35,15 @@ export const adminBaseRoute = {
title: pageTitle('router.supplementaryRecruitment') title: pageTitle('router.supplementaryRecruitment')
} }
}, },
{
// 在线补召
path: '/versionMaintenance',
name: 'versionMaintenance',
component: () => import('@/views/govern/manage/programVersion/comp/versionMaintenance.vue'),
meta: {
title: pageTitle('router.versionMaintenance')
}
},
{ {
path: 'cockpit', path: 'cockpit',
name: '项目管理', name: '项目管理',

View File

@@ -1,108 +1,168 @@
import { reactive } from 'vue' import { reactive } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { STORE_TAB_VIEW_CONFIG } from '@/stores/constant/cacheKey' import { STORE_TAB_VIEW_CONFIG } from '@/stores/constant/cacheKey'
import type { NavTabs } from '@/stores/interface/index' import type { NavTabs } from '@/stores/interface/index'
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router' import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
import { adminBaseRoutePath } from '@/router/static' import { adminBaseRoutePath } from '@/router/static'
import { set } from 'lodash'
export const useNavTabs = defineStore(
'navTabs', export const useNavTabs = defineStore(
() => { 'navTabs',
const state: NavTabs = reactive({ () => {
// 激活tab的index const state: NavTabs = reactive({
activeIndex: 0, // 激活tab的index
// 激活的tab activeIndex: 0,
activeRoute: null, // 激活的tab
// tab列表 activeRoute: null,
tabsView: [], // tab列表
// 当前tab是否全屏 tabsView: [],
tabFullScreen: false, // 当前tab是否全屏
// 从后台加载到的菜单路由列表 tabFullScreen: false,
tabsViewRoutes: [], // 从后台加载到的菜单路由列表
// 按钮权限节点 tabsViewRoutes: [],
authNode: new Map(), // 按钮权限节点
}) authNode: new Map()
})
function addTab(route: RouteLocationNormalized) {
if (!route.meta.addtab) return function addTab(route: RouteLocationNormalized) {
for (const key in state.tabsView) { if (!route.meta.addtab) return
if (state.tabsView[key].path === route.path) { for (const key in state.tabsView) {
state.tabsView[key].params = route.params ? route.params : state.tabsView[key].params if (state.tabsView[key].path === route.path) {
state.tabsView[key].query = route.query ? route.query : state.tabsView[key].query state.tabsView[key].params = route.params ? route.params : state.tabsView[key].params
return state.tabsView[key].query = route.query ? route.query : state.tabsView[key].query
} state.tabsView[key].meta = route.query ? route.meta : state.tabsView[key].meta
} return
state.tabsView.push(route) }
} }
state.tabsView.push(route)
function closeTab(route: RouteLocationNormalized) { }
state.tabsView.map((v, k) => {
if (v.path == route.path) { function closeTab(route: RouteLocationNormalized) {
state.tabsView.splice(k, 1) state.tabsView.map((v, k) => {
return if (v.path == route.path) {
} state.tabsView.splice(k, 1)
}) return
} }
})
/** }
* 关闭多个标签
* @param retainMenu 需要保留的标签,否则关闭全部标签 /**
*/ * 关闭多个标签
const closeTabs = (retainMenu: RouteLocationNormalized | false = false) => { * @param retainMenu 需要保留的标签,否则关闭全部标签
if (retainMenu) { */
state.tabsView = [retainMenu] const closeTabs = (retainMenu: RouteLocationNormalized | false = false) => {
} else { if (retainMenu) {
state.tabsView = [] state.tabsView = [retainMenu]
} } else {
} state.tabsView = []
}
const setActiveRoute = (route: RouteLocationNormalized): void => { }
const currentRouteIndex: number = state.tabsView.findIndex((item: RouteLocationNormalized) => {
return item.path === route.path const setActiveRoute = (route: RouteLocationNormalized): void => {
}) const currentRouteIndex: number = state.tabsView.findIndex((item: RouteLocationNormalized) => {
if (currentRouteIndex === -1) return return item.path === route.path
state.activeRoute = route })
state.activeIndex = currentRouteIndex if (currentRouteIndex === -1) return
} state.activeRoute = route
state.activeIndex = currentRouteIndex
const setTabsViewRoutes = (data: RouteRecordRaw[]): void => { }
state.tabsViewRoutes = encodeRoutesURI(data)
} const setTabsViewRoutes = (data: RouteRecordRaw[]): void => {
state.tabsViewRoutes = encodeRoutesURI(JSON.parse(JSON.stringify(data)))
const setAuthNode = (key: string, data: string[]) => { }
state.authNode.set(key, data)
} const setAuthNode = (key: string, data: string[]) => {
state.authNode.set(key, data)
const fillAuthNode = (data: Map<string, string[]>) => { }
state.authNode = data
} const fillAuthNode = (data: Map<string, string[]>) => {
state.authNode = data
const setFullScreen = (fullScreen: boolean): void => { }
state.tabFullScreen = fullScreen
} const setFullScreen = (fullScreen: boolean): void => {
state.tabFullScreen = fullScreen
return { state, addTab, closeTab, closeTabs, setActiveRoute, setTabsViewRoutes, setAuthNode, fillAuthNode, setFullScreen } }
},
{ const refresh = () => {
persist: { // setTimeout(() => {
key: STORE_TAB_VIEW_CONFIG, // console.log(123, state.tabsViewRoutes)
paths: ['state.tabFullScreen'],
}, let list = matchAndReturnRouteData(state.tabsViewRoutes, state.tabsView)
} state.tabsView = []
) list.forEach(item => {
addTab(item)
/** })
* 对iframe的url进行编码 // }, 1000)
*/ }
function encodeRoutesURI(data: RouteRecordRaw[]) { return {
data.forEach((item) => { state,
if (item.meta?.menu_type == 'iframe') { addTab,
item.path = adminBaseRoutePath + '/iframe/' + encodeURIComponent(item.path) closeTab,
} closeTabs,
setActiveRoute,
if (item.children && item.children.length) { setTabsViewRoutes,
item.children = encodeRoutesURI(item.children) setAuthNode,
} fillAuthNode,
}) setFullScreen,
return data refresh
} }
},
{
persist: {
key: STORE_TAB_VIEW_CONFIG,
paths: ['state.tabFullScreen']
}
}
)
/**
* 核心逻辑:
* 1. 递归遍历树形菜单筛选出与routeList中name匹配的节点
* 2. 将匹配到的节点格式转换为routeList的结构并返回
*/
function matchAndReturnRouteData(tree, routeList) {
// 1. 构建路由name映射name -> 完整路由对象)
const routeMap = new Map()
routeList.forEach(route => {
if (route.name) {
routeMap.set(route.name, route)
}
})
// 2. 递归遍历树形菜单,收集匹配的节点
const matchedNodes = []
function recursion(node) {
// 匹配当前节点
if (routeMap.has(node.name)) {
// 深度克隆路由对象,避免修改原数据
const matchedRoute = JSON.parse(JSON.stringify(routeMap.get(node.name)))
matchedNodes.push(matchedRoute)
}
// 递归处理子节点
if (node.children && node.children.length) {
node.children.forEach(child => recursion(child))
}
}
// 遍历所有顶级节点
tree.forEach(node => recursion(node))
// 3. 返回匹配后的第二个数据格式和routeList结构一致
return matchedNodes
}
/**
* 对iframe的url进行编码
*/
function encodeRoutesURI(data: RouteRecordRaw[]) {
data.forEach(item => {
if (item.meta?.menu_type == 'iframe') {
item.path = adminBaseRoutePath + '/iframe/' + encodeURIComponent(item.path)
}
if (item.children && item.children.length) {
item.children = encodeRoutesURI(item.children)
}
})
return data
}

58
src/styles/vxeTable.css Normal file
View File

@@ -0,0 +1,58 @@
.vxe-header--row {
background: var(--vxe-table-header-background-color);
color: var(--vxe-table-header-font-color);
}
.is--checked.vxe-checkbox,
.is--checked.vxe-checkbox .vxe-checkbox--icon,
.is--checked.vxe-custom--checkbox-option,
.is--checked.vxe-custom--checkbox-option .vxe-checkbox--icon,
.is--checked.vxe-export--panel-column-option,
.is--checked.vxe-export--panel-column-option .vxe-checkbox--icon,
.is--checked.vxe-table--filter-option,
.is--checked.vxe-table--filter-option .vxe-checkbox--icon,
.is--indeterminate.vxe-checkbox,
.is--indeterminate.vxe-checkbox .vxe-checkbox--icon,
.is--indeterminate.vxe-custom--checkbox-option,
.is--indeterminate.vxe-custom--checkbox-option .vxe-checkbox--icon,
.is--indeterminate.vxe-export--panel-column-option,
.is--indeterminate.vxe-export--panel-column-option .vxe-checkbox--icon,
.is--indeterminate.vxe-table--filter-option,
.is--indeterminate.vxe-table--filter-option .vxe-checkbox--icon,
.vxe-table--render-default .is--checked.vxe-cell--checkbox,
.vxe-table--render-default .is--checked.vxe-cell--checkbox .vxe-checkbox--icon,
.vxe-table--render-default .is--indeterminate.vxe-cell--checkbox,
.vxe-table--render-default .is--indeterminate.vxe-cell--checkbox .vxe-checkbox--icon,
.vxe-table--render-default .is--checked.vxe-cell--radio .vxe-radio--icon {
color: var(--el-color-primary-light-3);
}
.vxe-checkbox:not(.is--disabled):hover .vxe-checkbox--icon,
.vxe-custom--checkbox-option:not(.is--disabled):hover .vxe-checkbox--icon,
.vxe-export--panel-column-option:not(.is--disabled):hover .vxe-checkbox--icon,
.vxe-table--filter-option:not(.is--disabled):hover .vxe-checkbox--icon,
.vxe-table--render-default .vxe-cell--checkbox:not(.is--disabled):hover .vxe-checkbox--icon,
.vxe-radio:not(.is--disabled):hover .vxe-radio--icon,
.vxe-custom--radio-option:not(.is--disabled):hover .vxe-radio--icon,
.vxe-export--panel-column-option:not(.is--disabled):hover .vxe-radio--icon,
.vxe-table--filter-option:not(.is--disabled):hover .vxe-radio--icon,
.vxe-table--render-default .vxe-cell--radio:not(.is--disabled):hover .vxe-radio--icon {
color: var(--el-color-primary-light-5);
}
.vxe-table--render-default .vxe-body--row.row--current,
.vxe-table--render-default .vxe-body--row.row--current:hover {
background-color: var(--el-color-primary-light-8);
}
.vxe-table--tooltip-wrapper {
z-index: 10000 !important;
}
.is--disabled {
background-color: var(--vxe-input-disabled-color);
}
.vxe-modal--wrapper {
z-index: 5000 !important;
}

1
src/styles/vxeTable.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.vxe-header--row{background:var(--vxe-table-header-background-color);color:var(--vxe-table-header-font-color)}.is--checked.vxe-checkbox,.is--checked.vxe-checkbox .vxe-checkbox--icon,.is--checked.vxe-custom--checkbox-option,.is--checked.vxe-custom--checkbox-option .vxe-checkbox--icon,.is--checked.vxe-export--panel-column-option,.is--checked.vxe-export--panel-column-option .vxe-checkbox--icon,.is--checked.vxe-table--filter-option,.is--checked.vxe-table--filter-option .vxe-checkbox--icon,.is--indeterminate.vxe-checkbox,.is--indeterminate.vxe-checkbox .vxe-checkbox--icon,.is--indeterminate.vxe-custom--checkbox-option,.is--indeterminate.vxe-custom--checkbox-option .vxe-checkbox--icon,.is--indeterminate.vxe-export--panel-column-option,.is--indeterminate.vxe-export--panel-column-option .vxe-checkbox--icon,.is--indeterminate.vxe-table--filter-option,.is--indeterminate.vxe-table--filter-option .vxe-checkbox--icon,.vxe-table--render-default .is--checked.vxe-cell--checkbox,.vxe-table--render-default .is--checked.vxe-cell--checkbox .vxe-checkbox--icon,.vxe-table--render-default .is--indeterminate.vxe-cell--checkbox,.vxe-table--render-default .is--indeterminate.vxe-cell--checkbox .vxe-checkbox--icon,.vxe-table--render-default .is--checked.vxe-cell--radio .vxe-radio--icon{color:var(--el-color-primary-light-3)}.vxe-checkbox:not(.is--disabled):hover .vxe-checkbox--icon,.vxe-custom--checkbox-option:not(.is--disabled):hover .vxe-checkbox--icon,.vxe-export--panel-column-option:not(.is--disabled):hover .vxe-checkbox--icon,.vxe-table--filter-option:not(.is--disabled):hover .vxe-checkbox--icon,.vxe-table--render-default .vxe-cell--checkbox:not(.is--disabled):hover .vxe-checkbox--icon,.vxe-radio:not(.is--disabled):hover .vxe-radio--icon,.vxe-custom--radio-option:not(.is--disabled):hover .vxe-radio--icon,.vxe-export--panel-column-option:not(.is--disabled):hover .vxe-radio--icon,.vxe-table--filter-option:not(.is--disabled):hover .vxe-radio--icon,.vxe-table--render-default .vxe-cell--radio:not(.is--disabled):hover .vxe-radio--icon{color:var(--el-color-primary-light-5)}.vxe-table--render-default .vxe-body--row.row--current,.vxe-table--render-default .vxe-body--row.row--current:hover{background-color:var(--el-color-primary-light-8)}.vxe-table--tooltip-wrapper{z-index:10000 !important}.is--disabled{background-color:var(--vxe-input-disabled-color)}.vxe-modal--wrapper{z-index:5000 !important}

View File

@@ -1,84 +1,87 @@
// .vxe-table--body-wrapper, // .vxe-table--body-wrapper,
// .boxbx { // .boxbx {
// &::-webkit-scrollbar { // &::-webkit-scrollbar {
// width: 10px; // width: 10px;
// height: 10px; // height: 10px;
// } // }
// &::-webkit-scrollbar-thumb { // &::-webkit-scrollbar-thumb {
// border-radius: 5px; // border-radius: 5px;
// height: 3px; // height: 3px;
// background-color: var(--el-color-primary) !important; // background-color: var(--el-color-primary) !important;
// border-radius: 30px !important; // border-radius: 30px !important;
// } // }
// } // }
// .vxe-table--body-wrapper::-webkit-scrollbar-thumb { // .vxe-table--body-wrapper::-webkit-scrollbar-thumb {
// border-radius: 5px; // border-radius: 5px;
// height: 3px; // height: 3px;
// background-color: var(--el-color-primary) !important; // background-color: var(--el-color-primary) !important;
// border-radius: 30px !important; // border-radius: 30px !important;
// } // }
.vxe-header--row { .vxe-header--row {
background: var(--vxe-table-header-background-color); background: var(--vxe-table-header-background-color);
color: var(--vxe-table-header-font-color); color: var(--vxe-table-header-font-color);
} }
.is--checked.vxe-checkbox, .is--checked.vxe-checkbox,
.is--checked.vxe-checkbox .vxe-checkbox--icon, .is--checked.vxe-checkbox .vxe-checkbox--icon,
.is--checked.vxe-custom--checkbox-option, .is--checked.vxe-custom--checkbox-option,
.is--checked.vxe-custom--checkbox-option .vxe-checkbox--icon, .is--checked.vxe-custom--checkbox-option .vxe-checkbox--icon,
.is--checked.vxe-export--panel-column-option, .is--checked.vxe-export--panel-column-option,
.is--checked.vxe-export--panel-column-option .vxe-checkbox--icon, .is--checked.vxe-export--panel-column-option .vxe-checkbox--icon,
.is--checked.vxe-table--filter-option, .is--checked.vxe-table--filter-option,
.is--checked.vxe-table--filter-option .vxe-checkbox--icon, .is--checked.vxe-table--filter-option .vxe-checkbox--icon,
.is--indeterminate.vxe-checkbox, .is--indeterminate.vxe-checkbox,
.is--indeterminate.vxe-checkbox .vxe-checkbox--icon, .is--indeterminate.vxe-checkbox .vxe-checkbox--icon,
.is--indeterminate.vxe-custom--checkbox-option, .is--indeterminate.vxe-custom--checkbox-option,
.is--indeterminate.vxe-custom--checkbox-option .vxe-checkbox--icon, .is--indeterminate.vxe-custom--checkbox-option .vxe-checkbox--icon,
.is--indeterminate.vxe-export--panel-column-option, .is--indeterminate.vxe-export--panel-column-option,
.is--indeterminate.vxe-export--panel-column-option .vxe-checkbox--icon, .is--indeterminate.vxe-export--panel-column-option .vxe-checkbox--icon,
.is--indeterminate.vxe-table--filter-option, .is--indeterminate.vxe-table--filter-option,
.is--indeterminate.vxe-table--filter-option .vxe-checkbox--icon, .is--indeterminate.vxe-table--filter-option .vxe-checkbox--icon,
.vxe-table--render-default .is--checked.vxe-cell--checkbox, .vxe-table--render-default .is--checked.vxe-cell--checkbox,
.vxe-table--render-default .is--checked.vxe-cell--checkbox .vxe-checkbox--icon, .vxe-table--render-default .is--checked.vxe-cell--checkbox .vxe-checkbox--icon,
.vxe-table--render-default .is--indeterminate.vxe-cell--checkbox, .vxe-table--render-default .is--indeterminate.vxe-cell--checkbox,
.vxe-table--render-default .is--indeterminate.vxe-cell--checkbox .vxe-checkbox--icon, .vxe-table--render-default .is--indeterminate.vxe-cell--checkbox .vxe-checkbox--icon,
.vxe-table--render-default .is--checked.vxe-cell--radio .vxe-radio--icon { .vxe-table--render-default .is--checked.vxe-cell--radio .vxe-radio--icon {
color: var(--el-color-primary-light-3); color: var(--el-color-primary-light-3);
} }
.vxe-checkbox:not(.is--disabled):hover .vxe-checkbox--icon, .vxe-checkbox:not(.is--disabled):hover .vxe-checkbox--icon,
.vxe-custom--checkbox-option:not(.is--disabled):hover .vxe-checkbox--icon, .vxe-custom--checkbox-option:not(.is--disabled):hover .vxe-checkbox--icon,
.vxe-export--panel-column-option:not(.is--disabled):hover .vxe-checkbox--icon, .vxe-export--panel-column-option:not(.is--disabled):hover .vxe-checkbox--icon,
.vxe-table--filter-option:not(.is--disabled):hover .vxe-checkbox--icon, .vxe-table--filter-option:not(.is--disabled):hover .vxe-checkbox--icon,
.vxe-table--render-default .vxe-cell--checkbox:not(.is--disabled):hover .vxe-checkbox--icon, .vxe-table--render-default .vxe-cell--checkbox:not(.is--disabled):hover .vxe-checkbox--icon,
.vxe-radio:not(.is--disabled):hover .vxe-radio--icon, .vxe-radio:not(.is--disabled):hover .vxe-radio--icon,
.vxe-custom--radio-option:not(.is--disabled):hover .vxe-radio--icon, .vxe-custom--radio-option:not(.is--disabled):hover .vxe-radio--icon,
.vxe-export--panel-column-option:not(.is--disabled):hover .vxe-radio--icon, .vxe-export--panel-column-option:not(.is--disabled):hover .vxe-radio--icon,
.vxe-table--filter-option:not(.is--disabled):hover .vxe-radio--icon, .vxe-table--filter-option:not(.is--disabled):hover .vxe-radio--icon,
.vxe-table--render-default .vxe-cell--radio:not(.is--disabled):hover .vxe-radio--icon { .vxe-table--render-default .vxe-cell--radio:not(.is--disabled):hover .vxe-radio--icon {
color: var(--el-color-primary-light-5); color: var(--el-color-primary-light-5);
} }
.vxe-table--render-default .vxe-body--row.row--current,
// .vxe-table--render-default .is--disabled.vxe-cell--checkbox .vxe-checkbox--icon{ .vxe-table--render-default .vxe-body--row.row--current:hover {
// color: #fff0; background-color: var(--el-color-primary-light-8);
// } }
.vxe-table--tooltip-wrapper { // .vxe-table--render-default .is--disabled.vxe-cell--checkbox .vxe-checkbox--icon{
z-index: 10000 !important; // color: #fff0;
} // }
.is--disabled { .vxe-table--tooltip-wrapper {
background-color: var(--vxe-input-disabled-color); z-index: 10000 !important;
} }
.is--disabled {
.vxe-modal--wrapper { background-color: var(--vxe-input-disabled-color);
z-index: 5000 !important; }
}
.vxe-table--body .vxe-body--row:nth-child(even) { .vxe-modal--wrapper {
background-color: #f9f9f9; z-index: 5000 !important;
// background-color: var(--el-color-primary-light-9); }
} // .vxe-table--body .vxe-body--row:nth-child(even) {
// background-color: #f9f9f9;
.vxe-table--body .vxe-body--row:nth-child(odd) { // // background-color: var(--el-color-primary-light-9);
background-color: #ffffff; // }
}
// .vxe-table--body .vxe-body--row:nth-child(odd) {
// background-color: #ffffff;
// }

View File

@@ -1,79 +1,79 @@
<template> <template>
<el-dialog width="600px" v-model.trim='dialogVisible' :title='title'> <el-dialog width="600px" v-model.trim='dialogVisible' :title='title'>
<el-scrollbar> <el-scrollbar>
<el-form :inline='false' :model='form' label-width='auto' class="form-one" :rules='rules' ref='formRef'> <el-form :inline='false' :model='form' label-width='auto' class="form-one" :rules='rules' ref='formRef'>
<el-form-item label='角色名称'> <el-form-item label='角色名称'>
<el-input maxlength="32" show-word-limit v-model.trim='form.name' placeholder='请输入菜单名称' /> <el-input maxlength="32" show-word-limit v-model.trim='form.name' placeholder='请输入菜单名称' />
</el-form-item> </el-form-item>
<el-form-item label='角色编码'> <el-form-item label='角色编码'>
<el-input maxlength="32" show-word-limit v-model.trim='form.code' placeholder='请输入菜单名称' /> <el-input maxlength="32" show-word-limit v-model.trim='form.code' placeholder='请输入菜单名称' />
</el-form-item> </el-form-item>
<el-form-item label='角色描述'> <el-form-item label='角色描述'>
<el-input maxlength="300" show-word-limit v-model.trim='form.remark' :rows='2' type='textarea' <el-input maxlength="300" show-word-limit v-model.trim='form.remark' :rows='2' type='textarea'
placeholder='请输入描述' /> placeholder='请输入描述' />
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-scrollbar> </el-scrollbar>
<template #footer> <template #footer>
<span class='dialog-footer'> <span class='dialog-footer'>
<el-button @click='dialogVisible = false'>取消</el-button> <el-button @click='dialogVisible = false'>取消</el-button>
<el-button type='primary' @click='submit'>确认</el-button> <el-button type='primary' @click='submit'>确认</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<script lang='ts' setup> <script lang='ts' setup>
import { ref, inject } from 'vue' import { ref, inject } from 'vue'
import { reactive } from 'vue' import { reactive } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import TableStore from '@/utils/tableStore' // 若不是列表页面弹框可删除 import TableStore from '@/utils/tableStore' // 若不是列表页面弹框可删除
const dialogVisible = ref(false) const dialogVisible = ref(false)
const title = ref('') const title = ref('')
const tableStore = inject('tableStore') as TableStore const tableStore = inject('tableStore') as TableStore
const formRef = ref() const formRef = ref()
// 注意不要和表单ref的命名冲突 // 注意不要和表单ref的命名冲突
const form = reactive({ const form = reactive({
code: '', code: '',
name: '', name: '',
remark: '', remark: '',
id: '' id: ''
}) })
const rules = { const rules = {
name: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }], name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
code: [{ required: true, message: '角色编码不能为空', trigger: 'blur' }] code: [{ required: true, message: '请输入角色编码', trigger: 'blur' }]
} }
const open = (text: string, data?: anyObj) => { const open = (text: string, data?: anyObj) => {
title.value = text title.value = text
dialogVisible.value = true dialogVisible.value = true
if (data) { if (data) {
// 表单赋值 // 表单赋值
for (let key in form) { for (let key in form) {
form[key] = data[key] form[key] = data[key]
} }
} else { } else {
// 在此处恢复默认表单 // 在此处恢复默认表单
for (let key in form) { for (let key in form) {
form[key] = '' form[key] = ''
} }
} }
} }
const submit = () => { const submit = () => {
formRef.value.validate(async (valid) => { formRef.value.validate(async (valid) => {
if (valid) { if (valid) {
if (form.id) { if (form.id) {
// await update(form) // await update(form)
} else { } else {
// await create(form) // await create(form)
} }
ElMessage.success('保存成功') ElMessage.success('保存成功')
tableStore.index() tableStore.index()
dialogVisible.value = false dialogVisible.value = false
} }
}) })
} }
defineExpose({ open }) defineExpose({ open })
</script> </script>

103
src/utils/downloadFile.ts Normal file
View File

@@ -0,0 +1,103 @@
import { ElMessage, ElMessageBox, ElInput, ElSegmented } from 'element-plus'
export const downLoadFile = (name: string, key: string, res: any) => {
let blob = new Blob([res], {
type: getFileType(key)
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a') // 创建a标签
link.href = url
link.download = name // 设置下载的文件名
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link)
ElMessage.success('下载成功')
}
const getFileType = (url: string) => {
const ext = url.split('.').pop()?.toLowerCase() || ''
const mimeMap: Record<string, string> = {
// Excel
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
// CSV
csv: 'text/csv',
// Word
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
// PDF
pdf: 'application/pdf',
// PowerPoint
ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
// 图片
png: 'image/png',
gif: 'image/gif',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
bmp: 'image/bmp',
ico: 'image/vnd.microsoft.icon',
tif: 'image/tiff',
tiff: 'image/tiff',
svg: 'image/svg+xml',
webp: 'image/webp',
// 音频
mp3: 'audio/mpeg',
aac: 'audio/aac',
mid: 'audio/midi',
midi: 'audio/midi',
oga: 'audio/ogg',
wav: 'audio/wav',
weba: 'audio/webm',
// 视频
avi: 'video/x-msvideo',
mpeg: 'video/mpeg',
ogv: 'video/ogg',
webm: 'video/webm',
'3gp': 'video/3gpp',
'3g2': 'video/3gpp2',
// 网页/代码
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
mjs: 'text/javascript',
json: 'application/json',
jsonld: 'application/ld+json',
xhtml: 'application/xhtml+xml',
xml: 'application/xml',
xul: 'application/vnd.mozilla.xul+xml',
// 文档
abw: 'application/x-abiword',
odp: 'application/vnd.oasis.opendocument.presentation',
ods: 'application/vnd.oasis.opendocument.spreadsheet',
odt: 'application/vnd.oasis.opendocument.text',
rtf: 'application/rtf',
txt: 'text/plain',
vsd: 'application/vnd.visio',
// 字体
otf: 'font/otf',
ttf: 'font/ttf',
woff: 'font/woff',
woff2: 'font/woff2',
eot: 'application/vnd.ms-fontobject',
// 压缩/归档
arc: 'application/x-freearc',
bz: 'application/x-bzip',
bz2: 'application/x-bzip2',
rar: 'application/x-rar-compressed',
tar: 'application/x-tar',
zip: 'application/zip',
'7z': 'application/x-7z-compressed',
// 其他
bin: 'application/octet-stream',
csh: 'application/x-csh',
epub: 'application/epub+zip',
azw: 'application/vnd.amazon.ebook',
ics: 'text/calendar',
jar: 'application/java-archive',
mpkg: 'application/vnd.apple.installer+xml',
ogx: 'application/ogg',
sh: 'application/x-sh',
swf: 'application/x-shockwave-flash'
}
return mimeMap[ext] || ''
}

View File

@@ -1,4 +1,4 @@
import { number } from "vue-types" import { number } from 'vue-types'
const dataProcessing = (arr: any[]) => { const dataProcessing = (arr: any[]) => {
return arr return arr
@@ -14,7 +14,7 @@ const calculateValue = (o: number, value: number, num: number, isMin: boolean) =
} else if (value > -1 && value < 0 && isMin == false) { } else if (value > -1 && value < 0 && isMin == false) {
return 0 return 0
} }
let base let base
if (Math.abs(o) >= 100) { if (Math.abs(o) >= 100) {
base = 100 base = 100
@@ -24,9 +24,10 @@ const calculateValue = (o: number, value: number, num: number, isMin: boolean) =
base = 1 base = 1
} else { } else {
const multiple = 1 / 0.1 const multiple = 1 / 0.1
// 先放大→向上取整→再缩小
return Math.ceil(Math.abs(o) * multiple) / multiple base = Math.ceil(Math.abs(o) * multiple) / multiple
} }
let calculatedValue let calculatedValue
if (isMin) { if (isMin) {
if (value < 0) { if (value < 0) {
@@ -39,12 +40,12 @@ const calculateValue = (o: number, value: number, num: number, isMin: boolean) =
calculatedValue = value - num * value calculatedValue = value - num * value
} else { } else {
calculatedValue = value + num * value calculatedValue = value + num * value
} }
} }
if (base === 0.1) { if (base === 0.1) {
return parseFloat(calculatedValue.toFixed(1)) // return parseFloat(calculatedValue.toFixed(1))
return Math.ceil(calculatedValue * 10) / 10
} else if (isMin) { } else if (isMin) {
return Math.floor(calculatedValue / base) * base return Math.floor(calculatedValue / base) * base
} else { } else {
@@ -62,7 +63,7 @@ export const yMethod = (arr: any) => {
let min = 0 let min = 0
maxValue = Math.max(...numList) maxValue = Math.max(...numList)
minValue = Math.min(...numList) minValue = Math.min(...numList)
const o = maxValue - minValue const o = maxValue - minValue == 0 ? maxValue : maxValue - minValue
min = calculateValue(o, minValue, num, true) min = calculateValue(o, minValue, num, true)
max = calculateValue(o, maxValue, num, false) max = calculateValue(o, maxValue, num, false)

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