126 Commits

Author SHA1 Message Date
guanj
4f907a80c4 优化项目 2026-06-04 19:06:36 +08:00
guanj
4f32f84132 修改项目树问题 绘制稳态治理分析页面 2026-06-04 09:08:37 +08:00
guanj
c2805d7e9e 联调电镀数据功能 2026-06-02 16:09:21 +08:00
guanj
7deafa6d69 联调电镀数据页面 2026-06-01 20:35:26 +08:00
guanj
c2f23aa957 修改测试问题 2026-05-28 20:36:49 +08:00
guanj
9466141bff 去除部门树接口 2026-05-28 15:10:40 +08:00
guanj
faac12615d 提交 2026-05-26 16:23:18 +08:00
guanj
8b80e0678f 提交代码 2026-04-25 15:22:28 +08:00
guanj
7abcdb3a6b 联调程序升级 2026-04-24 09:13:48 +08:00
guanj
c8a42948de 修改台账 2026-04-20 09:28:04 +08:00
guanj
99bc99a6fc 绘制稳态事件配置页面 2026-04-17 08:49:22 +08:00
guanj
01a28d88f3 修改台账 2026-04-15 19:29:36 +08:00
guanj
632a0104fb 调整台账录入页面 2026-04-15 13:44:28 +08:00
guanj
cfcbfc45d6 修改台账 2026-04-13 10:48:32 +08:00
guanj
2601068a55 1 2026-04-08 15:51:46 +08:00
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
247 changed files with 33548 additions and 24731 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

@@ -0,0 +1,42 @@
import createAxios from '@/utils/request'
/**
* 删除数据合理范围
**/
export const pqDelete = (data: any) => {
return createAxios({
url: '/algorithm-boot/pqReasonableRange/delete',
method: 'post',
params: data
})
}
/**
* 按条件获取数据合理范围
**/
export const getData = (data: any) => {
return createAxios({
url: '/algorithm-boot/pqReasonableRange/getData',
method: 'post',
data
})
}
/**
* 新增数据合理范围
**/
export const save = (data: any) => {
return createAxios({
url: '/algorithm-boot/pqReasonableRange/save',
method: 'post',
data
})
}
/**
* 更新数据合理范围
**/
export const update = (data: any) => {
return createAxios({
url: '/algorithm-boot/pqReasonableRange/update',
method: 'post',
data
})
}

View File

@@ -1,144 +1,153 @@
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,14 @@ 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'
}) })
} }
//查询升级日志
export function getByDevId(data: any) {
return createAxios({
url: '/cs-device-boot/csUpgradeLogs/getByDevId',
method: 'get',
params:data
})
}

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

@@ -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,99 @@
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
})
}
//新增稳态指标方案
export function save(data: any) {
return createAxios({
url: '/cs-harmonic-boot/csHarmonicPlan/save',
method: 'post',
data: data
})
}
//修改稳态指标方案
export function update(data: any) {
return createAxios({
url: '/cs-harmonic-boot/csHarmonicPlan/update',
method: 'post',
data: data
})
}
//新增稳态指标方案
export function deletePlan(data: any) {
return createAxios({
url: '/cs-harmonic-boot/csHarmonicPlan/delete',
method: 'post',
data: data
})
}
//根据ID查询稳态指标方案
export function getById(data: any) {
return createAxios({
url: '/cs-harmonic-boot/csHarmonicPlan/getById',
method: 'GET',
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,30 +1,69 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
/** /**
* 查询app个人中心信息详情 * 查询app个人中心信息详情
* @param id * @param id
*/ */
export const queryAppInfo = (type: string) => { export const queryAppInfo = (type: string) => {
let form = new FormData() let form = new FormData()
form.append('type', type) form.append('type', type)
return createAxios({ return createAxios({
url: '/cs-system-boot/appinfo/queryAppInfoByType', url: '/cs-system-boot/appinfo/queryAppInfoByType',
method: 'post', method: 'post',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
}, },
data: form data: form
}) })
} }
/**
/** * 新增app基础信息
* 新增app基础信息 **/
**/ export const addAppInfo = (data: { type: string; content: string }) => {
export const addAppInfo = (data: { type: string, content: string }) => { return createAxios({
return createAxios({ url: '/cs-system-boot/appinfo/addAppInfo',
url: '/cs-system-boot/appinfo/addAppInfo', method: 'post',
method: 'post', data: data
data: data })
}) }
} /**
* 切换告警配置启用状态
**/
export const toggleActive = (data: any) => {
return createAxios({
url: '/cs-system-boot/csAlarmSet/toggleActive',
method: 'post',
params: data
})
}
/**
* 切换告警配置启用状态
**/
export const csDelete = (data: any) => {
return createAxios({
url: '/cs-system-boot/csAlarmSet/delete',
method: 'post',
params: data
})
}
/**
* 新增告警配置
**/
export const add = (data: any) => {
return createAxios({
url: '/cs-system-boot/csAlarmSet/add',
method: 'post',
data: data
})
}
/**
* 修改告警配置
**/
export const update = (data: any) => {
return createAxios({
url: '/cs-system-boot/csAlarmSet/update',
method: 'post',
data: data
})
}

View File

@@ -1,98 +1,121 @@
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'
})
}
//监测设备接入
export function onlineRegister(data: any) {
return createAxios({
url: '/access-boot/device/onlineRegister',
method: 'post',
params: data
})
}
//重启设备
export function resetFactory(data: any) {
return createAxios({
url: '/access-boot/device/resetFactory',
method: 'post',
params: data
})
}

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

@@ -27,3 +27,23 @@ export const removeUserDev = (data: any) => {
data: data data: data
}) })
} }
/**
* 短信配置
*/
export const addUserDevices = (data: any) => {
return createAxios({
url: '/cs-system-boot/appMsgSet/addUserDevices',
method: 'post',
data: data
})
}
/**
* 短信配置
*/
export const queryDeviceIdsByUserId = (data: any) => {
return createAxios({
url: '/cs-system-boot/appMsgSet/queryDeviceIdsByUserId',
method: 'post',
params: data
})
}

View File

@@ -1,9 +1,9 @@
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
}) })
@@ -11,7 +11,7 @@ export function mainLineList(data: any) {
// 主要监测点指标越限详情 // 主要监测点指标越限详情
export function statLimitRateDetails(data: any) { export function statLimitRateDetails(data: any) {
return request({ return request({
url: '/harmonic-boot/mainLine/statLimitRateDetails', url: '/cs-harmonic-boot/mainLine/statLimitRateDetails',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -38,7 +38,7 @@ export function trendData(data: any) {
// 每日越限占比统计 // 每日越限占比统计
export function totalLimitStatisticsDetails(data: any) { export function totalLimitStatisticsDetails(data: any) {
return request({ return request({
url: '/harmonic-boot/totalLimitStatistics/details', url: '/cs-harmonic-boot/totalLimitStatistics/details',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -47,7 +47,7 @@ export function totalLimitStatisticsDetails(data: any) {
// 总体指标越限统计列表 // 总体指标越限统计列表
export function totalLimitStatisticsList(data: any) { export function totalLimitStatisticsList(data: any) {
return request({ return request({
url: '/harmonic-boot/totalLimitStatistics/list', url: '/cs-harmonic-boot/totalLimitStatistics/list',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -56,7 +56,7 @@ export function totalLimitStatisticsList(data: any) {
// 总体指标越限统计数据 // 总体指标越限统计数据
export function totalLimitStatisticsData(data: any) { export function totalLimitStatisticsData(data: any) {
return request({ return request({
url: '/harmonic-boot/totalLimitStatistics/data', url: '/cs-harmonic-boot/totalLimitStatistics/data',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -65,7 +65,7 @@ export function totalLimitStatisticsData(data: any) {
// 指标越限程度数据 // 指标越限程度数据
export function limitExtentData(data: any) { export function limitExtentData(data: any) {
return request({ return request({
url: '/harmonic-boot/limitRateDetailD/limitExtentData', url: '/cs-harmonic-boot/limitRateDetailD/limitExtentData',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -73,7 +73,7 @@ export function limitExtentData(data: any) {
// 指标日趋势图数据 // 指标日趋势图数据
export function limitExtentDayData(data: any) { export function limitExtentDayData(data: any) {
return request({ return request({
url: '/harmonic-boot/limitRateDetailD/limitExtentDayData', url: '/cs-harmonic-boot/limitRateDetailD/limitExtentDayData',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -82,7 +82,7 @@ export function limitExtentDayData(data: any) {
// 指标越限明细日历数据 // 指标越限明细日历数据
export function limitCalendarData(data: any) { export function limitCalendarData(data: any) {
return request({ return request({
url: '/harmonic-boot/limitRateDetailD/limitCalendarData', url: '/cs-harmonic-boot/limitRateDetailD/limitCalendarData',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -100,7 +100,7 @@ export function fittingData(data: any) {
//指标越限时间概率分布 //指标越限时间概率分布
export function limitTimeProbabilityData(data: any) { export function limitTimeProbabilityData(data: any) {
return request({ return request({
url: '/harmonic-boot/limitRateDetailD/limitTimeProbabilityData', url: '/cs-harmonic-boot/limitRateDetailD/limitTimeProbabilityData',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -109,17 +109,16 @@ export function limitTimeProbabilityData(data: any) {
//指标越限程度概率分布 //指标越限程度概率分布
export function limitProbabilityData(data: any) { export function limitProbabilityData(data: any) {
return request({ return request({
url: '/harmonic-boot/limitRateDetailD/limitProbabilityData', url: '/cs-harmonic-boot/limitRateDetailD/limitProbabilityData',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 电网侧指标越限统计列表 // 电网侧指标越限统计列表
export function gridSideLimitStatisticsList(data: any) { export function gridSideLimitStatisticsList(data: any) {
return request({ return request({
url: '/harmonic-boot/gridSideLimitStatistics/list', url: '/cs-harmonic-boot/gridSideLimitStatistics/list',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -128,7 +127,7 @@ export function gridSideLimitStatisticsList(data: any) {
// 电网侧指标越限统计数据 // 电网侧指标越限统计数据
export function gridSideLimitStatisticsData(data: any) { export function gridSideLimitStatisticsData(data: any) {
return request({ return request({
url: '/harmonic-boot/gridSideLimitStatistics/data', url: '/cs-harmonic-boot/gridSideLimitStatistics/data',
method: 'post', method: 'post',
data: data data: data
}) })
@@ -152,7 +151,6 @@ export function getListByIds(data: any) {
}) })
} }
// 上传治理报告 // 上传治理报告
export function uploadReport(data: any) { export function uploadReport(data: any) {
return request({ return request({
@@ -234,4 +232,68 @@ export function netEventTable(data: any) {
}) })
} }
// 分页查询暂降事件
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,25 @@ 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
}) })
} }
// 根据装置型号获取装置类型
export function findByDevTypeId(data: any) {
return createAxios({
url: '/cs-device-boot/edData/queryEdDataPage',
method: 'post',
data
})
}
// 装置升级
export function upgrade(params: any) {
return createAxios({
url: '/zl-event-boot/device/upgrade',
method: 'get',
params
})
}

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,6 +16,7 @@
<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"
@@ -18,10 +26,9 @@
}" }"
@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 <waveFormAnalysis
v-loading="loading" v-loading="loading"
v-if="isWaveCharts"
ref="waveFormAnalysisRef" ref="waveFormAnalysisRef"
@handleHideCharts="isWaveCharts = false" @handleHideCharts="isWaveCharts = false"
:wp="wp" :wp="wp"
@@ -33,28 +40,27 @@
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 { getTimeOfTheMonth } from '@/utils/formatTime'
import waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue' 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 { useTimeCacheStore } from '@/stores/timeCache'
import { analyseWave } from '@/api/common' import { analyseWave } from '@/api/common'
import { ElMessage } from 'element-plus'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number]}, w: { type: [String, Number] },
h: { type: [String, Number]}, h: { type: [String, Number] },
width: { type: [String, Number]}, width: { type: [String, Number] },
height: { type: [String, Number]}, height: { type: [String, Number] },
timeKey: { type: [String, Number]}, 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
@@ -77,7 +83,7 @@ const fullscreen = computed(() => {
} }
}) })
const echartList = ref({}) const echartList = ref()
const chartRef = ref() const chartRef = ref()
// 波形 // 波形
@@ -104,16 +110,14 @@ const tableStore: any = new TableStore({
showPage: false, showPage: false,
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}, },
loadCallback: () => { loadCallback: () => {
const gongData = gongfunction(tableStore.table.data) const gongData = gongfunction(tableStore.table.data)
data.gs = tableStore.table.data.length data.gs = tableStore.table.data.length
data.krr = gongData.pointI.length data.krr = gongData.pointF.length
data.bkrr = gongData.pointIun.length data.bkrr = gongData.pointFun.length
// console.log(gongData,'789000')
echartList.value = { echartList.value = {
title: { title: {
text: `F47曲线` text: `F47曲线`
@@ -143,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
@@ -165,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: '%'
} }
], ],
@@ -205,31 +215,23 @@ const tableStore: any = new TableStore({
// [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'
@@ -244,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
@@ -263,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
@@ -413,38 +435,51 @@ 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 = async (row: any) => { const handleTolerableEventClick = async (row: any) => {
console.log('可容忍事件详情:', row)
isWaveCharts.value = true
loading.value = true loading.value = true
isWaveCharts.value = true nextTick(() => {
await analyseWave(row.id) 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 => { .then(res => {
row.loading1 = false row.loading1 = false
if (res != undefined) { if (res != undefined) {
boxoList.value = row boxoList.value = {
boxoList.value.featureAmplitude = row.evtParamVVaDepth != '-' ? row.evtParamVVaDepth - 0 : null persistTime: row.value[0], //持续时间
boxoList.value.systemType = 'WX' featureAmplitude: (row.value[1] / 100).toFixed(2), //残余电压
startTime: row.value[2], //时间
lineName: row.value[4] //监测点名称
}
boxoList.value.systemType = 'YPT'
wp.value = res.data wp.value = res.data
} }
isWaveCharts.value = true
loading.value = false loading.value = false
}) })
.catch(() => { .catch(() => {
messageInstance.close()
row.loading1 = false row.loading1 = false
loading.value = false loading.value = false
}) })
nextTick(() => { nextTick(() => {
waveFormAnalysisRef.value && waveFormAnalysisRef.value.setHeight(999, 130, 1.6666666)
waveFormAnalysisRef.value && waveFormAnalysisRef.value.getWpData(wp.value, boxoList.value, true) waveFormAnalysisRef.value && waveFormAnalysisRef.value.getWpData(wp.value, boxoList.value, 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,35 +1,42 @@
<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, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, 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
@@ -53,85 +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: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}, },
loadCallback: () => {} 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
}))
)
const total = chartData.value.reduce((sum: any, item: any) => sum + item.value, 0)
echartList.value = {
title: {},
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
}
]
}
}
}
}) })
const tableRef = ref() const tableRef = ref()
@@ -139,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)
} }
} }
@@ -159,12 +198,7 @@ watch(
watch( watch(
() => prop.timeValue, () => 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 deep: true

View File

@@ -17,7 +17,7 @@ import { yMethod } from '@/utils/echartMethod'
const prop = defineProps({ const prop = defineProps({
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object }
}) })
@@ -89,8 +89,6 @@ const initData = async (row: any) => {
let [min, max] = yMethod(res.data.map((item: any) => item.value.split(',')).flat()) let [min, max] = yMethod(res.data.map((item: any) => item.value.split(',')).flat())
// 从第一条数据中提取时间作为x轴数据 // 从第一条数据中提取时间作为x轴数据
const firstItem = res.data[0]
const xAxisData = firstItem.time.split(',')
// 定义相位颜色映射 // 定义相位颜色映射
const phaseColors: any = { const phaseColors: any = {
@@ -100,28 +98,34 @@ const initData = async (row: any) => {
} }
// 处理每条相位数据 // 处理每条相位数据
const seriesData = res.data.map((item: any) => { const seriesData = res.data
const values = xAxisData.map((time: string, index: number) => { .filter(item => item.valueType == 'max')
// 将传入的日期与时间拼接成完整的时间字符串 .sort((a, b) => {
const fullTime = `${row.time} ${time}` return a.phasic.localeCompare(b.phasic)
const value = parseFloat(item.value.split(',')[index]) || 0
return [fullTime, value]
}) })
.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 { return {
name: `${item.phasic}`, name: `${item.phasic}`,
type: 'line', type: 'line',
showSymbol: false, showSymbol: false,
smooth: true, smooth: true,
data: values, data: values,
itemStyle: { itemStyle: {
normal: { normal: {
// 根据相位设置对应颜色 // 根据相位设置对应颜色
color: phaseColors[item.phasic] || config.layout.elementUiPrimary[0] color: phaseColors[item.phasic] || config.layout.elementUiPrimary[0]
}
} }
} }
} })
})
echartList.value.yAxis.max = max echartList.value.yAxis.max = max
echartList.value.yAxis.min = min echartList.value.yAxis.min = min
// 更新图表配置 // 更新图表配置
@@ -135,7 +139,7 @@ onMounted(() => {})
const open = async (row: any) => { const open = async (row: any) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = row.name + '日趋势图' dialogTitle.value = row.name + '日趋势图'
dialogText.value = `监测点名称:${row.lineName}_越限时间:${row.time}_指标名称:${row.name}` dialogText.value = `监测点名称:${row.lineName} 越限时间:${row.time} 指标名称:${row.name}`
nextTick(() => { nextTick(() => {
initData(row) initData(row)
}) })

View File

@@ -1,20 +1,15 @@
<template> <template>
<div> <div>
<!--指标越限程度 --> <!--指标越限程度 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader ref="TableHeaderRef" :showReset="false" @selectChange="selectChange" datePicker
<my-echart :timeKeyList="prop.timeKey" v-if="fullscreen"></TableHeader>
class="tall" <my-echart 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 v-if="dialogTrendChart" ref="dailyTrendChartRef" @close="dialogTrendChart = false" /> <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">
@@ -24,21 +19,24 @@ 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, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const TableHeaderRef = ref()
const headerHeight = ref(57)
const harmonicRatioRef: any = ref(null)
const dialogTrendChart = ref(false) const dialogTrendChart = ref(false)
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
@@ -66,7 +64,7 @@ const echartList = ref()
const dailyTrendChartRef = ref() const dailyTrendChartRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/harmonic-boot/limitRateDetailD/limitExtentData', url: '/cs-harmonic-boot/limitRateDetailD/limitExtentData',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
@@ -89,7 +87,7 @@ const tableStore: any = new TableStore({
{ {
title: '越限最大值', title: '越限最大值',
field: 'maxValue', field: 'maxValue',
minWidth: '60', minWidth: '70',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
const extentValue = const extentValue =
@@ -107,67 +105,41 @@ const tableStore: any = new TableStore({
{ {
title: '越限程度(%)', title: '越限程度(%)',
field: 'extent', field: 'extent',
minWidth: '60', minWidth: '70',
render: 'customTemplate', formatter: (row: any) => {
customTemplate: (row: any) => { return Math.floor(row.cellValue * 100) / 100
// 保留两个小数
const extentValue =
row.extent !== null && row.extent !== undefined && row.extent !== ''
? Math.floor(row.extent * 100) / 100
: '/'
return `<span>${extentValue}</span>`
} }
}, },
{ {
title: '越限时间', title: '越限时间',
field: 'time', field: 'time',
minWidth: '60', minWidth: '60',
render: 'customTemplate', formatter: (row: any) => {
customTemplate: (row: any) => { return row.cellValue || '/'
if (row.time !== null && row.time !== undefined && row.time !== '') {
return `<span>${row.time}</span>`
} else {
return `<span>/</span>`
}
} }
}, },
{ {
title: '越限最高监测点', title: '越限最高监测点',
field: 'lineName', field: 'lineName',
minWidth: '90', minWidth: '90',
render: 'customTemplate', formatter: (row: any) => {
customTemplate: (row: any) => { return row.cellValue || '/'
if (row.lineName !== null && row.lineName !== undefined && row.lineName !== '') {
return `<span>${row.lineName}</span>`
} else {
return `<span>/</span>`
}
} }
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}, },
loadCallback: () => { loadCallback: () => {
// 定义 x 轴标签顺序 // 定义 x 轴标签顺序
const xAxisLabels = ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
// 根据指标名称顺序提取对应的 extent 数据
const chartData = xAxisLabels.map(label => {
// 在表格数据中查找对应指标名称的数据项
const item = tableStore.table.data.find((row: any) => row.name === label)
// 如果找到对应项,则返回 extent 值,否则返回 0并保留两位小数
const extentValue = item ? item.extent || 0 : 0
return Math.round(extentValue * 100) / 100
})
echartList.value = { echartList.value = {
title: { title: {
text: '指标越限严重度' text: '指标越限严重度'
}, },
xAxis: { xAxis: {
data: xAxisLabels data: tableStore.table.data.map((item: any) => item.name)
}, },
yAxis: { yAxis: {
@@ -182,8 +154,8 @@ const tableStore: any = new TableStore({
series: [ series: [
{ {
type: 'bar', type: 'bar',
name: '越限占比', name: '指标越限严重度',
data: chartData, data: tableStore.table.data.map((item: any) => Math.floor(item.extent * 100) / 100),
barMaxWidth: 30 barMaxWidth: 30
} }
] ]
@@ -196,17 +168,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) => {
dialogTrendChart.value = true dialogTrendChart.value = true
if (column.field == 'maxValue' && row.lineId) {
nextTick(() => { if (column.field == 'maxValue') {
dailyTrendChartRef.value.open(row) 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()
}) })
@@ -219,18 +236,13 @@ watch(
watch( watch(
() => prop.timeValue, () => 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 deep: true
} }
) )
const addMenu = () => {} const addMenu = () => { }
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

File diff suppressed because one or more lines are too long

View File

@@ -1,38 +1,47 @@
<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" v-model="tableStore.table.params.tempId"
placeholder="请选择报表模板" placeholder="请选择模板策略"
clearable
>
<el-option v-for="item in templateList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="监测对象">
<el-select
v-model="tableStore.table.params.sensitiveUserId"
placeholder="请选择监测对象"
clearable clearable
> >
<el-option <el-option
v-for="item in idList" v-for="item in templateList"
:key="item.id" :key="item.id"
:label="item.name" :label="item.excelName"
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="监测对象">
<el-select
filterable
v-model="tableStore.table.params.sensitiveUserId"
placeholder="请选择监测对象"
clearable
>
<el-option v-for="item in idList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</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">
<div <div
id="luckysheet" id="luckysheet"
:style="{ :style="{
width: `calc(${prop.width} )`, width: `calc(${prop.width} )`,
@@ -43,44 +52,26 @@
</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, onUnmounted, 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 { destroyLuckysheet, renderLuckysheetReport } from '@/utils/luckysheetHelper'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useConfig } from '@/stores/config' import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import Json from './index.json'
import JsonAfter from './index.json'
import { getTemplateList } from '@/api/harmonic-boot/luckyexcel'
import { getListByIds } from '@/api/harmonic-boot/cockpit/cockpit' import { getListByIds } from '@/api/harmonic-boot/cockpit/cockpit'
import { getTime } from '@/utils/formatTime'
import { ElMessage } from 'element-plus'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const tableStore: any = new TableStore({ const TableHeaderRef = ref()
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
showPage: false,
exportName: '主要监测点列表',
column: [],
beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0]
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
},
loadCallback: () => {}
})
const tableRef = ref()
provide('tableRef', tableRef)
tableStore.table.params.power = '1'
provide('tableStore', tableStore)
// 报表模板列表 // 报表模板列表
const templateList = ref() const templateList = ref()
@@ -91,21 +82,34 @@ const idList = ref()
// 监测对象 // 监测对象
const initListByIds = () => { const initListByIds = () => {
getListByIds({}).then((res: any) => { getListByIds({}).then((res: any) => {
if (res.data.length > 0) { if (res.data?.length > 0) {
idList.value = res.data idList.value = res.data
if (!tableStore.table.params.sensitiveUserId && idList.value?.length > 0) { if (!tableStore.table.params.sensitiveUserId && idList.value?.length > 0) {
tableStore.table.params.sensitiveUserId = idList.value[0].id tableStore.table.params.sensitiveUserId = idList.value[0].id
} }
templateListData()
} else {
querySysExcel({}).then(res => {
templateList.value = res.data.filter(item => item.excelType == 4)
if (!tableStore.table.params.tempId && templateList.value?.length > 0) {
tableStore.table.params.tempId = templateList.value[0].id
}
})
tableStore.table.loading = false
} }
}) })
} }
const templateListData = () => { const templateListData = () => {
getTemplateList({}).then(res => { querySysExcel({}).then(res => {
templateList.value = res.data templateList.value = res.data.filter(item => item.excelType == 4)
if (!tableStore.table.params.tempId && templateList.value?.length > 0) { if (!tableStore.table.params.tempId && templateList.value?.length > 0) {
tableStore.table.params.tempId = res.data[0].id tableStore.table.params.tempId = templateList.value[0].id
} }
nextTick(() => {
tableStore.index()
})
}) })
} }
// 下载表格 // 下载表格
@@ -115,19 +119,9 @@ const downloadExcel = () => {
onMounted(() => { onMounted(() => {
initListByIds() initListByIds()
templateListData() })
luckysheet.create({ onUnmounted(() => {
container: 'luckysheet', destroyLuckysheet()
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: Json
})
tableStore.index()
}) })
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
@@ -149,6 +143,56 @@ 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: () => {
renderLuckysheetReport('luckysheet', tableStore.table.data, { allowEdit: false })
}
})
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 => {
@@ -158,12 +202,7 @@ watch(
watch( watch(
() => prop.timeValue, () => 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 deep: true

View File

@@ -1,238 +0,0 @@
<template>
<div>
<!--治理效果报表 -->
<TableHeader :showReset="false" datePicker @selectChange="selectChange" v-if="fullscreen">
<template v-slot:select>
<el-form-item label="监测对象">
<el-select
v-model="tableStore.table.params.power"
placeholder="请选择监测对象"
clearable
style="width: 130px"
>
<el-option
v-for="item in powerList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template>
<template v-slot:operation>
<el-button @click="downloadExcel" class="" type="primary" icon="el-icon-Download">导出excel</el-button>
</template>
</TableHeader>
<!-- 左右布局展示治理前后报表 -->
<div class="report-container">
<!-- 治理前报表 -->
<div class="report-panel before">
<div class="panel-header">治理前报表</div>
<div
id="luckysheet-before"
:style="{
width: '100%',
height: `calc(${prop.height} - ${fullscreen ? '114px' : '57px'} + ${fullscreen ? 0 : 56}px)`
}"
></div>
</div>
<!-- 治理后报表 -->
<div class="report-panel after">
<div class="panel-header">治理后报表</div>
<div
id="luckysheet-after"
:style="{
width: '100%',
height: `calc(${prop.height} - ${fullscreen ? '114px' : '57px'} + ${fullscreen ? 0 : 56}px)`
}"
></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, watch, computed } from 'vue'
import TableStore from '@/utils/tableStore'
import { exportExcel } from '@/views/govern/reportForms/export.js'
import TableHeader from '@/components/table/header/index.vue'
import Json from './index.json'
import JsonAfter from './index1.json'
const prop = defineProps({
w: { type: [String, Number] },
h: { type: [String, Number] },
width: { type: [String, Number] },
height: { type: [String, Number] },
timeKey: { type: [String, Number] },
timeValue: { type: Object }
})
const powerList: any = ref([
{
label: '1#变压器',
value: '1'
},
{
label: '2#变压器',
value: '2'
}
])
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
showPage: false,
exportName: '治理效果报表',
column: [],
beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0]
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
},
loadCallback: () => {}
})
const tableRef = ref()
provide('tableRef', tableRef)
tableStore.table.params.power = '1'
provide('tableStore', tableStore)
// 下载表格
const downloadExcel = () => {
// 可以选择导出治理前、治理后或两个报表
const beforeSheets = luckysheetBefore.getAllSheets()
const afterSheets = luckysheetAfter.getAllSheets()
exportExcel([...beforeSheets, ...afterSheets], '治理效果对比报表')
}
// 治理前和治理后的Luckysheet实例
let luckysheetBefore: any
let luckysheetAfter: any
onMounted(() => {
// 创建治理前报表
luckysheetBefore = luckysheet.create({
container: 'luckysheet-before',
title: '治理前',
lang: 'zh',
showtoolbar: false,
showinfobar: false,
showsheetbar: true,
allowEdit: false,
data: Json
})
// 创建治理后报表
luckysheetAfter = luckysheet.create({
container: 'luckysheet-after',
title: '治理后',
lang: 'zh',
showtoolbar: false,
showinfobar: false,
showsheetbar: true,
allowEdit: false,
data: JsonAfter // 使用治理后的数据
})
tableStore.index()
})
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
if (datePickerValue && datePickerValue.timeValue) {
// 更新时间参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
// 可以在这里更新报表数据
updateReportData()
}
}
// 更新报表数据的方法
const updateReportData = () => {
// 这里可以根据时间参数更新治理前后的报表数据
// 示例:重新加载数据或更新现有数据
console.log('更新报表数据:', {
beginTime: tableStore.table.params.searchBeginTime,
endTime: tableStore.table.params.searchEndTime
})
}
// 计算是否全屏展示
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
}
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
updateReportData()
}
)
watch(
() => prop.timeValue,
(newVal, oldVal) => {
// 当外部时间值变化时,更新表格的时间参数
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()
updateReportData()
}
},
{
deep: true
}
)
</script>
<style lang="scss" scoped>
.report-container {
display: flex;
height: calc(100% - 57px);
.report-panel {
flex: 1;
display: flex;
flex-direction: column;
border: 1px solid var(--el-border-color);
&.before {
margin-right: 5px;
}
&.after {
margin-left: 5px;
}
.panel-header {
padding: 10px 15px;
background-color: var(--el-color-primary-light-9);
font-weight: bold;
border-bottom: 1px solid var(--el-border-color);
text-align: center;
}
// 确保Luckysheet容器占满剩余空间
:deep(#luckysheet-before),
:deep(#luckysheet-after) {
flex: 1;
}
}
}
:deep(.el-select) {
min-width: 80px;
}
</style>

View File

@@ -16,6 +16,7 @@
v-model="searchForm.index" v-model="searchForm.index"
placeholder="请选择统计指标" placeholder="请选择统计指标"
@change="onIndexChange($event)" @change="onIndexChange($event)"
filterable
> >
<el-option <el-option
v-for="item in indexOptions" v-for="item in indexOptions"
@@ -33,14 +34,15 @@
</el-form-item> </el-form-item>
<el-form-item label="统计类型"> <el-form-item label="统计类型">
<el-select <el-select
style="min-width: 120px !important" style="min-width: 90px !important"
placeholder="请选择" placeholder="请选择"
v-model="searchForm.valueType" v-model="searchForm.valueType"
filterable
> >
<el-option value="max" label="最大值"></el-option> <el-option value="max" label="最大值"></el-option>
<el-option value="min" label="最小值"></el-option> <el-option value="min" label="最小值"></el-option>
<el-option value="avg" label="平均值"></el-option> <el-option value="avg" label="平均值"></el-option>
<el-option value="cp95" label="cp95"></el-option> <el-option value="cp95" label="CP95"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@@ -59,11 +61,12 @@
placeholder="请选择谐波次数" placeholder="请选择谐波次数"
style="width: 100px" style="width: 100px"
class="mr20" class="mr20"
filterable
> >
<el-option <el-option
v-for="vv in item.countOptions" v-for="vv in item.countOptions"
:key="vv" :key="vv"
:label="vv" :label="item.name.includes('间谐波') ? vv - 0.5 : vv"
:value="vv" :value="vv"
></el-option> ></el-option>
</el-select> </el-select>
@@ -79,11 +82,7 @@
</TableHeader> </TableHeader>
</div> </div>
<div class="history_chart" :style="pageHeight" v-loading="loading"> <div class="history_chart" :style="pageHeight" v-loading="loading">
<MyEchart <MyEchart ref="historyChart" :options="echartsData" v-if="showEchart" />
ref="historyChart"
:options="echartsData"
v-if="showEchart"
/>
<el-empty :style="pageHeight" v-else description="暂无数据" /> <el-empty :style="pageHeight" v-else description="暂无数据" />
</div> </div>
</el-dialog> </el-dialog>
@@ -157,28 +156,40 @@ const countOptions: any = ref([])
const legendDictList: any = ref([]) const legendDictList: any = ref([])
const initCode = (field: string, title: string) => { const initCode = (field: string, title: string) => {
queryByCode('steady_state_limit_trend').then(res => { queryByCode('gridSide_exceedTheLimit').then(res => {
queryCsDictTree(res.data.id).then(item => { queryCsDictTree(res.data.id).then(item => {
//排序 //排序
indexOptions.value = item.data.sort((a: any, b: any) => { indexOptions.value = item.data.sort((a: any, b: any) => {
return a.sort - b.sort return a.sort - b.sort
}) })
const titleMap: Record<string, number> = { let codeKey = field.includes('flickerOvertime')
flickerOvertime: 0, ? '闪变'
uaberranceOvertime: 3, : field.includes('uharm')
ubalanceOvertime: 4, ? '谐波电压'
freqDevOvertime: 5 : field.includes('iharm')
} ? '谐波电流'
: field.includes('voltageDevOvertime')
? '电压偏差'
: field.includes('ubalanceOvertime')
? '不平衡'
: ''
let defaultIndex = 0 // 默认值 // const titleMap: Record<string, number> = {
// flickerOvertime: 0,
// uaberranceOvertime: 3,
// ubalanceOvertime: 4,
// freqDevOvertime: 5
// }
if (field in titleMap) { // let defaultIndex = 0 // 默认值
defaultIndex = titleMap[field] let defaultIndex = indexOptions.value.findIndex((item: any) => item.name.includes(codeKey)) || 0
} else if (field.includes('uharm')) { // if (field in titleMap) {
defaultIndex = 1 // defaultIndex = titleMap[field]
} else if (field.includes('iharm')) { // } else if (field.includes('uharm')) {
defaultIndex = 2 // 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 searchForm.value.index[0] = indexOptions.value[defaultIndex].id
}) })
@@ -202,7 +213,7 @@ const initCode = (field: string, title: string) => {
if (kk.harmStart && kk.harmEnd) { if (kk.harmStart && kk.harmEnd) {
range(0, 0, 0) range(0, 0, 0)
if (kk.showName == '间谐波电压含有率') { if (kk.showName.includes('间谐波电压')) {
countDataCopy.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1).map( countDataCopy.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1).map(
(item: any) => { (item: any) => {
return item - 0.5 return item - 0.5
@@ -284,14 +295,15 @@ const init = async () => {
let lists: any = [] let lists: any = []
let frequencys: any = null let frequencys: any = null
countData.value.map((item: any, index: any) => { countData.value.map((item: any, index: any) => {
if (item.name.includes('谐波含有率')) { if (item.name.includes('谐波')) {
frequencys = item.count frequencys = item.count
} else { } else {
frequencys = '' frequencys = ''
} }
lists[index] = { lists[index] = {
statisticalId: item.index, statisticalId: item.index,
frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : '' frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : ''
} }
}) })
let obj = { let obj = {
@@ -597,12 +609,12 @@ const formatCountOptions = () => {
}) })
countData.value.map((item: any, key: any) => { countData.value.map((item: any, key: any) => {
if (item.name == '谐波电流有效值') { if (item.name.includes('谐波电压')) {
item.name = '谐波电流次数'
} else if (item.name == '谐波电压含有率') {
item.name = '谐波电压次数'
} else if (item.name == '间谐波电压含有率') {
item.name = '间谐波电压次数' item.name = '间谐波电压次数'
} else if (item.name.includes('谐波电流')) {
item.name = '谐波电流次数'
} else if (item.name.includes('谐波电压')) {
item.name = '谐波电压次数'
} }
}) })
} }

View File

@@ -9,11 +9,12 @@
v-model="tableStore.table.params.lineId" 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.lineId" :key="item.lineId"
:label="item.name" :label="item.lineName"
:value="item.lineId" :value="item.lineId"
/> />
</el-select> </el-select>
@@ -48,7 +49,7 @@ const loop50 = (key: string) => {
list.push({ list.push({
title: i + '次', title: i + '次',
field: key + i + 'Overtime', field: key + i + 'Overtime',
width: '80', width: '60',
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[key + i + 'Overtime']}</span>`
@@ -58,7 +59,7 @@ const loop50 = (key: string) => {
return list return list
} }
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/harmonic-boot/totalLimitStatistics/details', url: '/cs-harmonic-boot/totalLimitStatistics/details',
method: 'POST', method: 'POST',
publicHeight: 30, publicHeight: 30,
showPage: false, showPage: false,
@@ -84,21 +85,22 @@ const tableStore: any = new TableStore({
width: '150' width: '150'
}, },
{ {
title: '闪变越限(%)', title: '长时闪变越限(%)',
field: 'flickerOvertime', 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.flickerOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
} }
}, },
{ {
title: '谐波电压越限(%)', title: '电压偏差越限(%)',
children: loop50('uharm') field: 'voltageDevOvertime',
}, width: '100',
{ render: 'customTemplate',
title: '谐波电流越限(%)', customTemplate: (row: any) => {
children: loop50('iharm') return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
}
}, },
{ {
title: '三相不平衡度越限(%)', title: '三相不平衡度越限(%)',
@@ -110,23 +112,24 @@ const tableStore: any = new TableStore({
} }
}, },
{ {
title: '电压偏差越限(%)', title: '谐波电压越限(%)',
field: 'voltageDevOvertime', children: loop50('uharm')
width: '100',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
}
}, },
{ {
title: '频率偏差越限(%)', title: '谐波电流越限(%)',
field: 'freqDevOvertime', children: loop50('iharm')
width: '100', },
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>` // {
} // title: '频率偏差越限(%)',
} // field: 'freqDevOvertime',
// width: '100',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>`
// }
// }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
}, },
@@ -138,16 +141,20 @@ const tableStore: any = new TableStore({
provide('tableStore', tableStore) provide('tableStore', tableStore)
tableStore.table.params.sortBy = '' tableStore.table.params.sortBy = ''
tableStore.table.params.orderBy = '' tableStore.table.params.orderBy = ''
const open = async (row: any,searchBeginTime:any,searchEndTime:any) => { const open = async (row: any,searchBeginTime:any,searchEndTime:any,interval:any,list:any) => {
dialogVisible.value = true dialogVisible.value = true
initCSlineList() options.value = list
// initCSlineList()
tableStore.table.params.lineId = row.lineId tableStore.table.params.lineId = row.lineId
nextTick(() => { nextTick(() => {
tableHeaderRef.value.setTimeInterval([searchBeginTime, searchEndTime]) tableHeaderRef.value.setInterval(interval)
setTimeout(() => {
tableHeaderRef.value.setTimeInterval([searchBeginTime, searchEndTime])
tableStore.table.params.searchBeginTime =searchBeginTime tableStore.table.params.searchBeginTime =searchBeginTime
tableStore.table.params.searchEndTime = searchEndTime tableStore.table.params.searchEndTime = searchEndTime
tableStore.index() tableStore.index()
},100)
}) })
} }

View File

@@ -1,7 +1,14 @@
<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"
@@ -27,23 +34,25 @@ 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 OverLimitDetails from '@/components/cockpit/gridSideStatistics/components/overLimitDetails.vue' import OverLimitDetails from '@/components/cockpit/gridSideStatistics/components/overLimitDetails.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
import { gridSideLimitStatisticsData } from '@/api/harmonic-boot/cockpit/cockpit' import { gridSideLimitStatisticsData } from '@/api/harmonic-boot/cockpit/cockpit'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const echartList = ref({}) const echartList = ref({})
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
@@ -79,7 +88,7 @@ const initEcharts = () => {
xAxis: { xAxis: {
// name: '(区域)', // name: '(区域)',
data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡'] data: ['长时闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
}, },
yAxis: { yAxis: {
@@ -117,7 +126,7 @@ const initEcharts = () => {
const OverLimitDetailsRef = ref() const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/harmonic-boot/gridSideLimitStatistics/list', url: '/cs-harmonic-boot/gridSideLimitStatistics/list',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
@@ -140,7 +149,7 @@ const tableStore: any = new TableStore({
title: '越限占比(%)', title: '越限占比(%)',
children: [ children: [
{ {
title: '闪变', title: '长时闪变',
field: 'flicker', field: 'flicker',
minWidth: '70', minWidth: '70',
render: 'customTemplate', render: 'customTemplate',
@@ -188,8 +197,8 @@ const tableStore: any = new TableStore({
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1] tableStore.table.params.interval = TableHeaderRef.value?.datePickerRef?.interval || 3
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
@@ -204,18 +213,37 @@ provide('tableStore', tableStore)
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') { OverLimitDetailsRef.value.open(
OverLimitDetailsRef.value.open( row,
row, tableStore.table.params.searchBeginTime || prop.timeValue?.[0],
tableStore.table.params.searchBeginTime || prop.timeValue?.[0], tableStore.table.params.searchEndTime || prop.timeValue?.[1],
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 => {
@@ -226,12 +254,7 @@ watch(
watch( watch(
() => prop.timeValue, () => 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 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,7 +1,14 @@
<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>
<el-calendar <el-calendar
v-model="value" v-model="value"
:style="{ :style="{
@@ -25,7 +32,7 @@
<template #content> <template #content>
<span v-html="getTextForDate(data.day)"></span> <span v-html="getTextForDate(data.day)"></span>
</template> </template>
<div class="details" v-html="getTextForDate(data.day)"></div> <div class="details" v-html="fullscreen ? getTextForDate(data.day) : '有越限'"></div>
</el-tooltip> </el-tooltip>
</div> </div>
</template> </template>
@@ -37,20 +44,24 @@ 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 { dayjs } from 'element-plus' import { dayjs } from 'element-plus'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const list = ref() const list = ref()
const TableHeaderRef = ref()
dayjs.en.weekStart = 1 //设置日历的周起始日为星期一 dayjs.en.weekStart = 1 //设置日历的周起始日为星期一
const value = ref(new Date()) const value = ref(new Date())
@@ -82,46 +93,15 @@ const getTextForDate = (date: string) => {
} }
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
// url: '/user-boot/role/selectRoleDetail?id=0', url: '/cs-harmonic-boot/limitRateDetailD/limitCalendarData',
url: '/harmonic-boot/limitRateDetailD/limitCalendarData',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}, },
loadCallback: () => { loadCallback: () => {
value.value = tableStore.table.params.searchBeginTime value.value = tableStore.table.params.searchBeginTime
// 将后端返回的数据整合到 list 中
// tableStore.table.data = [
// {
// "time": "2025-11-13",
// "items": [
// "闪变越限",
// "谐波电流越限"
// ],
// "status": 1
// },
// {
// "time": "2025-11-14",
// "items": [
// "频率偏差越限",
// "三相电压不平衡越限",
// "谐波电压越限",
// "谐波电流越限",
// "频率偏差越限",
// "三相电压不平衡越限",
// "谐波电压越限",
// "谐波电流越限",
// "频率偏差越限",
// "三相电压不平衡越限",
// "谐波电压越限",
// "谐波电流越限"
// ],
// "status": 2
// }
// ]
if (tableStore.table.data && tableStore.table.data.length > 0) { if (tableStore.table.data && tableStore.table.data.length > 0) {
list.value = tableStore.table.data.map((item: any) => { list.value = tableStore.table.data.map((item: any) => {
// 将 items 数组转换为带换行的文本 // 将 items 数组转换为带换行的文本
@@ -162,30 +142,39 @@ const setBackground = (value: string) => {
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, () => 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 deep: true
} }
) )
const addMenu = () => {}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-calendar) { :deep(.el-calendar) {

View File

@@ -1,10 +1,17 @@
<template> <template>
<div> <div>
<!--指标越限概率分布 --> <!--指标越限概率分布 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"> <TableHeader
:showReset="false"
:timeKeyList="prop.timeKey"
ref="TableHeaderRef"
@selectChange="selectChange"
datePicker
v-if="fullscreen"
>
<template v-slot:select> <template v-slot:select>
<el-form-item label="监测点"> <el-form-item label="监测点">
<el-select size="small" v-model="tableStore.table.params.lineId"> <el-select size="small" filterable v-model="tableStore.table.params.lineId">
<el-option <el-option
v-for="item in lineList" v-for="item in lineList"
:key="item.lineId" :key="item.lineId"
@@ -17,21 +24,30 @@
</TableHeader> </TableHeader>
<div v-loading="tableStore.table.loading"> <div v-loading="tableStore.table.loading">
<my-echart <my-echart
v-if="lineShow"
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} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}" }"
/> />
<my-echart <el-empty
v-else
description="暂无监测点"
:style="{
width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}"
/>
<!-- <my-echart
class="mt10" class="mt10"
:options="echartList1" :options="echartList1"
: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 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`
}" }"
/> /> -->
</div> </div>
</div> </div>
</template> </template>
@@ -41,22 +57,26 @@ import TableStore from '@/utils/tableStore'
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 { limitProbabilityData, cslineList } from '@/api/harmonic-boot/cockpit/cockpit' import { limitProbabilityData, cslineList } from '@/api/harmonic-boot/cockpit/cockpit'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const lineShow = ref(true)
// const options = ref(JSON.parse(window.localStorage.getItem('lineIdList') || '[]')) // const options = ref(JSON.parse(window.localStorage.getItem('lineIdList') || '[]'))
const lineList = ref() const lineList = ref()
const headerHeight = ref(57) const headerHeight = ref(57)
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
@@ -87,6 +107,11 @@ const probabilityData = ref()
const initLineList = async () => { const initLineList = async () => {
cslineList({}).then(res => { cslineList({}).then(res => {
if (res.data.length == 0) {
lineShow.value = false
return (tableStore.table.loading = false)
}
lineShow.value = true
lineList.value = res.data lineList.value = res.data
tableStore.table.params.lineId = lineList.value[0].lineId tableStore.table.params.lineId = lineList.value[0].lineId
tableStore.index() tableStore.index()
@@ -110,7 +135,7 @@ const initProbabilityData = () => {
// 处理接口返回的数据,转换为图表所需格式 // 处理接口返回的数据,转换为图表所需格式
if (res.data && Array.isArray(res.data)) { if (res.data && Array.isArray(res.data)) {
// 定义指标类型顺序 // 定义指标类型顺序
const indicatorOrder = ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相电压不平衡度', '频率偏差'] const indicatorOrder = ['长时闪变', '谐波电压', '谐波电流', '电压偏差', '三相电压不平衡度', '频率偏差']
// 按照指定顺序排序数据 // 按照指定顺序排序数据
const sortedData = [...res.data].sort((a, b) => { const sortedData = [...res.data].sort((a, b) => {
return indicatorOrder.indexOf(a.indexName) - indicatorOrder.indexOf(b.indexName) return indicatorOrder.indexOf(a.indexName) - indicatorOrder.indexOf(b.indexName)
@@ -141,6 +166,9 @@ const initProbabilityData = () => {
const yAxisData = sortedData.map(item => item.indexName) const yAxisData = sortedData.map(item => item.indexName)
echartList.value = { echartList.value = {
title: {
text: '指标越限概率分布'
},
options: { options: {
backgroundColor: '#fff', backgroundColor: '#fff',
tooltip: { tooltip: {
@@ -157,18 +185,11 @@ const initProbabilityData = () => {
var tips = '' var tips = ''
tips += '指标类型: ' + yAxisData[yIndex] + '</br>' tips += '指标类型: ' + yAxisData[yIndex] + '</br>'
tips += '越限程度: ' + params.seriesName + '</br>' tips += '越限程度: ' + params.seriesName + '</br>'
tips += '越限数: ' + params.value[2] + '</br>' tips += '越限数: ' + params.value[2] + '</br>'
return tips return tips
} }
}, },
title: {
text: '指标越限概率分布',
x: 'center',
textStyle: {
fontSize: 16,
fontWeight: 'normal'
}
},
// 移除或隐藏 visualMap 组件 // 移除或隐藏 visualMap 组件
visualMap: { visualMap: {
show: false, // 设置为 false 隐藏右侧颜色条 show: false, // 设置为 false 隐藏右侧颜色条
@@ -187,43 +208,18 @@ const initProbabilityData = () => {
type: 'category', type: 'category',
name: '越限程度', name: '越限程度',
nameLocation: 'middle', nameLocation: 'middle',
nameGap: 30, nameGap: 50,
data: ['0-20%', '20-40%', '40-60%', '60-80%', '80-100%'], data: ['0-20%', '20-40%', '40-60%', '60-80%', '80-100%']
axisLine: {
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111',
margin: 15
},
nameTextStyle: {
color: '#111'
}
}, },
yAxis3D: { yAxis3D: {
type: 'category', type: 'category',
name: '指标类型', name: '指标类型',
nameLocation: 'middle', nameLocation: 'middle',
nameGap: 30, nameGap: 50,
data: yAxisData, data: yAxisData,
nameTextStyle: {
color: '#111'
},
axisLine: {
show: true,
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111',
margin: 15
},
splitLine: { splitLine: {
lineStyle: { lineStyle: {
color: ['#111'],
type: 'dashed', type: 'dashed',
opacity: 0.5 opacity: 0.5
} }
@@ -231,28 +227,17 @@ const initProbabilityData = () => {
}, },
zAxis3D: { zAxis3D: {
type: 'value', type: 'value',
name: '越限数', name: '越限数',
nameLocation: 'middle', nameLocation: 'middle',
nameGap: 30, nameGap: 30,
nameTextStyle: { minInterval: 10
color: '#111'
},
axisLine: {
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111'
},
min: 0,
max: zAxisMax // 使用计算出的最大值加5
// max: 100 // max: 100
}, },
grid3D: { grid3D: {
viewControl: { viewControl: {
projection: 'perspective', projection: 'perspective',
distance: 250, distance: 260,
rotateSensitivity: 10, rotateSensitivity: 10,
zoomSensitivity: 2 zoomSensitivity: 2
}, },
@@ -397,13 +382,12 @@ const initProbabilityData = () => {
} }
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/harmonic-boot/limitRateDetailD/limitTimeProbabilityData', url: '/cs-harmonic-boot/limitRateDetailD/limitTimeProbabilityData',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
// 只有当 lineList 已加载且有数据时才设置默认 lineId // 只有当 lineList 已加载且有数据时才设置默认 lineId
if (!tableStore.table.params.lineId && lineList.value && lineList.value.length > 0) { if (!tableStore.table.params.lineId && lineList.value && lineList.value.length > 0) {
tableStore.table.params.lineId = lineList.value[0].lineId tableStore.table.params.lineId = lineList.value[0].lineId
@@ -420,13 +404,14 @@ const tableStore: any = new TableStore({
return { return {
name: indexName, name: indexName,
type: 'line', type: 'line',
symbol: 'none',
data: dataIndex.map((item: any) => [item.timePeriod, item.times]) data: dataIndex.map((item: any) => [item.timePeriod, item.times])
} }
}) })
echartList1.value = { echartList1.value = {
title: { title: {
text: '越限时间概率分布' text: '指标越限时间概率分布'
}, },
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis'
@@ -454,6 +439,25 @@ provide('tableStore', tableStore)
onMounted(() => { onMounted(() => {
initLineList() 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( watch(
() => prop.timeKey, () => prop.timeKey,
val => { val => {
@@ -463,12 +467,7 @@ watch(
watch( watch(
() => prop.timeValue, () => 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 deep: true

View File

@@ -16,6 +16,7 @@
v-model="searchForm.index" v-model="searchForm.index"
placeholder="请选择统计指标" placeholder="请选择统计指标"
@change="onIndexChange($event)" @change="onIndexChange($event)"
filterable
> >
<el-option <el-option
v-for="item in indexOptions" v-for="item in indexOptions"
@@ -36,6 +37,7 @@
style="min-width: 120px !important" style="min-width: 120px !important"
placeholder="请选择" placeholder="请选择"
v-model="searchForm.valueType" v-model="searchForm.valueType"
filterable
> >
<el-option value="max" label="最大值"></el-option> <el-option value="max" label="最大值"></el-option>
<el-option value="min" label="最小值"></el-option> <el-option value="min" label="最小值"></el-option>
@@ -59,11 +61,12 @@
placeholder="请选择谐波次数" placeholder="请选择谐波次数"
style="width: 100px" style="width: 100px"
class="mr20" class="mr20"
filterable
> >
<el-option <el-option
v-for="vv in item.countOptions" v-for="vv in item.countOptions"
:key="vv" :key="vv"
:label="vv" :label="item.name.includes('间谐波') ? vv - 0.5 : vv"
:value="vv" :value="vv"
></el-option> ></el-option>
</el-select> </el-select>
@@ -78,12 +81,8 @@
</template> </template>
</TableHeader> </TableHeader>
</div> </div>
<div class="history_chart" :style="pageHeight" v-loading="loading"> <div class="history_chart" :style="pageHeight" v-loading="loading">
<MyEchart <MyEchart ref="historyChart" :options="echartsData" v-if="showEchart" />
ref="historyChart"
:options="echartsData"
v-if="showEchart"
/>
<el-empty :style="pageHeight" v-else description="暂无数据" /> <el-empty :style="pageHeight" v-else description="暂无数据" />
</div> </div>
</el-dialog> </el-dialog>
@@ -157,28 +156,40 @@ const countOptions: any = ref([])
const legendDictList: any = ref([]) const legendDictList: any = ref([])
const initCode = (field: string, title: string) => { const initCode = (field: string, title: string) => {
queryByCode('steady_state_limit_trend').then(res => { queryByCode('gridSide_exceedTheLimit').then(res => {
queryCsDictTree(res.data.id).then(item => { queryCsDictTree(res.data.id).then(item => {
//排序 //排序
indexOptions.value = item.data.sort((a: any, b: any) => { indexOptions.value = item.data.sort((a: any, b: any) => {
return a.sort - b.sort return a.sort - b.sort
}) })
const titleMap: Record<string, number> = { // const titleMap: Record<string, number> = {
flickerOvertime: 0, // flickerOvertime: 0,
uaberranceOvertime: 3, // uaberranceOvertime: 3,
ubalanceOvertime: 4, // ubalanceOvertime: 4,
freqDevOvertime: 5 // freqDevOvertime: 5
} // }
let defaultIndex = 0 // 默认值 // let defaultIndex = 0 // 默认值
if (field in titleMap) { // if (field in titleMap) {
defaultIndex = titleMap[field] // defaultIndex = titleMap[field]
} else if (field.includes('uharm')) { // } else if (field.includes('uharm')) {
defaultIndex = 1 // defaultIndex = 1
} else if (field.includes('iharm')) { // } else if (field.includes('iharm')) {
defaultIndex = 2 // 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 searchForm.value.index[0] = indexOptions.value[defaultIndex].id
}) })
@@ -291,7 +302,7 @@ const init = async () => {
} }
lists[index] = { lists[index] = {
statisticalId: item.index, statisticalId: item.index,
frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : '' frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : ''
} }
}) })
let obj = { let obj = {
@@ -576,7 +587,6 @@ const setTimeControl = () => {
} }
const selectChange = (flag: boolean, height: any) => { const selectChange = (flag: boolean, height: any) => {
pageHeight.value = mainHeight(height * 1.6, 1.6) pageHeight.value = mainHeight(height * 1.6, 1.6)
} }
//导出 //导出

View File

@@ -9,11 +9,12 @@
v-model="tableStore.table.params.lineId" 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.lineId" :key="item.lineId"
:label="item.name" :label="item.lineName"
:value="item.lineId" :value="item.lineId"
/> />
</el-select> </el-select>
@@ -23,11 +24,7 @@
<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 <HarmonicRatio ref="harmonicRatioRef" @close="onHarmonicRatioClose" v-if="dialogFlag" />
ref="harmonicRatioRef"
@close="onHarmonicRatioClose"
v-if="dialogFlag"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -53,7 +50,7 @@ const loop50 = (key: string) => {
list.push({ list.push({
title: i + '次', title: i + '次',
field: key + i + 'Overtime', field: key + i + 'Overtime',
width: '80', width: '60',
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[key + i + 'Overtime']}</span>`
@@ -63,7 +60,7 @@ const loop50 = (key: string) => {
return list return list
} }
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/harmonic-boot/mainLine/statLimitRateDetails', url: '/cs-harmonic-boot/mainLine/statLimitRateDetails',
method: 'POST', method: 'POST',
publicHeight: 30, publicHeight: 30,
showPage: false, showPage: false,
@@ -89,21 +86,21 @@ const tableStore: any = new TableStore({
width: '150' width: '150'
}, },
{ {
title: '闪变越限(分钟)', title: '越限(分钟)',
field: 'flickerOvertime', 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.flickerOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
} }
}, }, {
{ title: '电压偏差越限(分钟)',
title: '谐波电压越限(分钟)', field: 'uaberranceOvertime',
children: loop50('uharm') width: '100',
}, render: 'customTemplate',
{ customTemplate: (row: any) => {
title: '谐波电流越限(分钟)', return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
children: loop50('iharm') }
}, },
{ {
title: '三相不平衡度越限(分钟)', title: '三相不平衡度越限(分钟)',
@@ -115,23 +112,23 @@ const tableStore: any = new TableStore({
} }
}, },
{ {
title: '电压偏差越限(分钟)', title: '谐波电压越限(分钟)',
field: 'uaberranceOvertime', children: loop50('uharm')
width: '100',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
}
}, },
{ {
title: '频率偏差越限(分钟)', title: '谐波电流越限(分钟)',
field: 'freqDevOvertime', children: loop50('iharm')
width: '100', },
render: 'customTemplate',
customTemplate: (row: any) => { // {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>` // title: '频率偏差越限(分钟)',
} // field: 'freqDevOvertime',
} // width: '100',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>`
// }
// }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
}, },
@@ -143,9 +140,10 @@ const tableStore: any = new TableStore({
provide('tableStore', tableStore) provide('tableStore', tableStore)
tableStore.table.params.sortBy = '' tableStore.table.params.sortBy = ''
tableStore.table.params.orderBy = '' tableStore.table.params.orderBy = ''
const open = async (row: any,searchBeginTime:any,searchEndTime:any) => { const open = async (row: any,searchBeginTime:any,searchEndTime:any,data:any=[]) => {
dialogVisible.value = true dialogVisible.value = true
initCSlineList() // initCSlineList()
options.value = data
tableStore.table.params.lineId = row.lineId tableStore.table.params.lineId = row.lineId
nextTick(() => { nextTick(() => {
@@ -179,8 +177,8 @@ const onHarmonicRatioClose = () => {
} }
const initCSlineList = async () => { const initCSlineList = async () => {
const res = await cslineList({}) // const res = await cslineList({})
options.value = res.data // options.value = res.data
} }

View File

@@ -1,10 +1,17 @@
<template> <template>
<div> <div>
<!--主要监测点列表 --> <!--主要监测点列表 -->
<TableHeader :showReset="false" @selectChange="selectChange" v-if="fullscreen" datePicker ref="tableHeaderRef"> <TableHeader
:showReset="false"
:timeKeyList="prop.timeKey"
@selectChange="selectChange"
v-if="fullscreen"
ref="TableHeaderRef"
>
<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>
@@ -26,15 +33,15 @@ import { getTimeOfTheMonth } from '@/utils/formatTime'
import OverLimitDetails from '@/components/cockpit/indicatorFittingChart/components/overLimitDetails.vue' import OverLimitDetails from '@/components/cockpit/indicatorFittingChart/components/overLimitDetails.vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache' import { useTimeCacheStore } from '@/stores/timeCache'
import { log } from 'console'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, 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)
@@ -42,7 +49,7 @@ const headerHeight = ref(57)
const route = useRoute() const route = useRoute()
const timeCacheStore = useTimeCacheStore() const timeCacheStore = useTimeCacheStore()
const tableHeaderRef = ref() const TableHeaderRef = ref()
// 计算是否全屏展示 // 计算是否全屏展示
const fullscreen = computed(() => { const fullscreen = computed(() => {
@@ -59,15 +66,15 @@ const fullscreen = computed(() => {
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
if (datePickerValue && datePickerValue.timeValue) { // if (datePickerValue && datePickerValue.timeValue) {
// 更新时间参数 // // 更新时间参数
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({ const tableStore: any = new TableStore({
url: '/harmonic-boot/mainLine/list', url: '/cs-harmonic-boot/mainLine/list',
method: 'POST', method: 'POST',
showPage: fullscreen.value ? true : false, showPage: fullscreen.value ? true : false,
exportName: '主要监测点列表', exportName: '主要监测点列表',
@@ -93,19 +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', showOverflow: true } { title: '主要存在的电能质量问题', field: 'problems', minWidth: '150', showOverflow: true }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
@@ -120,33 +132,45 @@ provide('tableStore', tableStore)
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
if (column.field == 'lineName') { if (column.field == 'lineName') {
let time = getTimeOfTheMonth('3');
OverLimitDetailsRef.value.open( OverLimitDetailsRef.value.open(
row, row,
tableStore.table.params.searchBeginTime || prop.timeValue?.[0], time[0],
tableStore.table.params.searchEndTime || prop.timeValue?.[1] time[1],
tableStore.table.data
) )
} }
} }
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 不是一个有效数组')
// }
}
// 在组件挂载时设置缓存值到 DatePicker // 在组件挂载时设置缓存值到 DatePicker
onMounted(() => { onMounted(() => {
tableStore.index() tableStore.index()
}) })
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch( watch(
() => prop.timeValue, () => 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 deep: true

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,10 +1,16 @@
<template> <template>
<div> <div>
<!--指标拟合图 --> <!--指标拟合图 -->
<TableHeader 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-form-item label="监测点">
<el-select v-model="tableStore.table.params.lineId" placeholder="请选择监测点" clearable> <el-select filterable v-model="tableStore.table.params.lineId" placeholder="请选择监测点" clearable>
<el-option <el-option
v-for="item in lineList" v-for="item in lineList"
:key="item.lineId" :key="item.lineId"
@@ -14,7 +20,12 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="用户功率"> <el-form-item label="用户功率">
<el-select v-model="tableStore.table.params.power" placeholder="请选择用户功率" clearable> <el-select
filterable
v-model="tableStore.table.params.power"
placeholder="请选择用户功率"
clearable
>
<el-option v-for="item in powerList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in powerList" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -23,6 +34,7 @@
style="min-width: 120px !important" style="min-width: 120px !important"
placeholder="请选择" placeholder="请选择"
v-model="tableStore.table.params.valueType" v-model="tableStore.table.params.valueType"
filterable
> >
<el-option value="max" label="最大值"></el-option> <el-option value="max" label="最大值"></el-option>
<el-option value="min" label="最小值"></el-option> <el-option value="min" label="最小值"></el-option>
@@ -31,7 +43,12 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="电能质量指标"> <el-form-item label="电能质量指标">
<el-select v-model="tableStore.table.params.indicator" placeholder="请选择电能质量指标" clearable> <el-select
filterable
v-model="tableStore.table.params.indicator"
placeholder="请选择电能质量指标"
clearable
>
<el-option v-for="item in indicatorList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in indicatorList" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -42,6 +59,7 @@
v-model="tableStore.table.params.harmonicCount" v-model="tableStore.table.params.harmonicCount"
placeholder="请选择谐波次数" placeholder="请选择谐波次数"
style="min-width: 80px !important" style="min-width: 80px !important"
filterable
> >
<el-option <el-option
v-for="num in harmonicCountOptions" v-for="num in harmonicCountOptions"
@@ -57,12 +75,20 @@
<div v-loading="tableStore.table.loading"> <div v-loading="tableStore.table.loading">
<my-echart <my-echart
class="tall" class="tall"
v-if="lineShow"
:options="echartList" :options="echartList"
:style="{ :style="{
width: prop.width, width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )` height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`
}" }"
v-if="showEchart" />
<el-empty
v-else
description="暂无监测点"
:style="{
width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}"
/> />
</div> </div>
</div> </div>
@@ -76,28 +102,28 @@ import { useConfig } from '@/stores/config'
import { cslineList, fittingData } from '@/api/harmonic-boot/cockpit/cockpit' import { cslineList, fittingData } from '@/api/harmonic-boot/cockpit/cockpit'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree' import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const TableHeaderRef = ref()
const config = useConfig() const config = useConfig()
const lineList: any = ref() const lineList: any = ref()
const powerList: any = ref() const powerList: any = ref()
const countData: any = ref([])
const showEchart = ref(true)
const chartsList = ref<any>([]) const chartsList = ref<any>([])
const lineShow = ref(true)
// 计算是否全屏展示 // 计算是否全屏展示
const fullscreen = computed(() => { const fullscreen = computed(() => {
const w = Number(prop.w) const w = Number(prop.w)
@@ -124,8 +150,15 @@ const indicatorList = ref()
const initLineList = async () => { const initLineList = async () => {
cslineList({}).then(res => { cslineList({}).then(res => {
setTime()
if (res.data.length == 0) {
lineShow.value = false
return (tableStore.table.loading = false)
}
lineShow.value = true
lineList.value = res.data lineList.value = res.data
tableStore.table.params.lineId = lineList.value[0].lineId tableStore.table.params.lineId = lineList.value[0].lineId
initCode()
}) })
} }
@@ -134,7 +167,6 @@ const echartList = ref()
const headerHeight = ref(57) const headerHeight = ref(57)
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
if (datePickerValue && datePickerValue.timeValue) { if (datePickerValue && datePickerValue.timeValue) {
// 更新时间参数 // 更新时间参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0] tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
@@ -152,6 +184,30 @@ const setEchart = () => {
title: { title: {
text: `${indicatorName}${powerName}负荷曲线拟合图` text: `${indicatorName}${powerName}负荷曲线拟合图`
}, },
tooltip: {
trigger: 'axis',
formatter: function (params: any) {
let result = params[0].axisValueLabel
params.forEach((item: any) => {
if (item.seriesName === indicatorName) {
// 对于电能质量指标格式化Y轴值显示
let valueText = ''
if (item.value[1] == 0) {
valueText = '不越限'
} else if (item.value[1] == 1) {
valueText = '越限'
} else {
valueText = item.value[1]
}
result += `<br/>${item.marker}${item.seriesName}: ${valueText}`
} else {
// 对于功率数据,正常显示数值
result += `<br/>${item.marker}${item.seriesName}: ${item.value[1]} ${item.value[2]}`
}
})
return result
}
},
xAxis: { xAxis: {
type: 'time', type: 'time',
axisLabel: { axisLabel: {
@@ -162,7 +218,25 @@ const setEchart = () => {
} }
} }
}, },
yAxis: [{}, {}], yAxis: [
{},
indicatorName
? {
min: 0,
max: 1,
axisLabel: {
formatter: function (value: number) {
if (value === 0) {
return '不越限'
} else if (value === 1) {
return '越限'
}
return value
}
}
}
: {}
],
grid: { grid: {
left: '10px', left: '10px',
right: '20px' right: '20px'
@@ -189,6 +263,7 @@ const setEchart = () => {
{ {
name: indicatorName, // 动态设置指标名称 name: indicatorName, // 动态设置指标名称
type: 'line', type: 'line',
step: 'end',
showSymbol: false, showSymbol: false,
// smooth: true, // smooth: true,
data: [], data: [],
@@ -198,12 +273,6 @@ const setEchart = () => {
} }
} }
// 检查是否有数据
if (!chartsList.value || chartsList.value.length === 0) {
showEchart.value = false // 没有数据时隐藏图表
return
}
try { try {
// 用户功率数据和电能质量数据 // 用户功率数据和电能质量数据
let powerData: any[] = [] let powerData: any[] = []
@@ -224,7 +293,8 @@ const setEchart = () => {
item.time, item.time,
item.statisticalData !== null && item.statisticalData !== undefined item.statisticalData !== null && item.statisticalData !== undefined
? parseFloat(item.statisticalData.toFixed(2)) ? parseFloat(item.statisticalData.toFixed(2))
: null : null,
item.unit
] ]
}) })
@@ -234,7 +304,8 @@ const setEchart = () => {
item.time, item.time,
item.statisticalData !== null && item.statisticalData !== undefined item.statisticalData !== null && item.statisticalData !== undefined
? parseFloat(item.statisticalData.toFixed(2)) ? parseFloat(item.statisticalData.toFixed(2))
: null : null,
item.unit
] ]
}) })
@@ -242,20 +313,11 @@ const setEchart = () => {
const hasPowerData = processedPowerData.length > 0 && processedPowerData.some(item => item[1] !== null) const hasPowerData = processedPowerData.length > 0 && processedPowerData.some(item => item[1] !== null)
const hasQualityData = processedQualityData.length > 0 && processedQualityData.some(item => item[1] !== null) const hasQualityData = processedQualityData.length > 0 && processedQualityData.some(item => item[1] !== null)
if (!hasPowerData && !hasQualityData) {
showEchart.value = false
return
}
// 更新图表配置 // 更新图表配置
echartList.value.options.series[0].data = processedPowerData echartList.value.options.series[0].data = processedPowerData
echartList.value.options.series[1].data = processedQualityData echartList.value.options.series[1].data = processedQualityData
// 确保图表显示
showEchart.value = true
} catch (error) { } catch (error) {
console.error('处理图表数据时出错:', error) console.error('处理图表数据时出错:', error)
showEchart.value = false
} }
} }
@@ -273,6 +335,7 @@ const initCode = () => {
tableStore.table.params.power = powerList.value[0].id tableStore.table.params.power = powerList.value[0].id
tableStore.table.params.indicator = indicatorList.value[0].id tableStore.table.params.indicator = indicatorList.value[0].id
nextTick(() => { nextTick(() => {
// setTime()
tableStore.index() tableStore.index()
}) })
}) })
@@ -286,10 +349,7 @@ const tableStore: any = new TableStore({
exportName: '主要监测点列表', exportName: '主要监测点列表',
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
// 设置时间参数 setTime()
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0]
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
// 只有当 lineList 已加载且有数据时才设置默认 lineId // 只有当 lineList 已加载且有数据时才设置默认 lineId
if (!tableStore.table.params.lineId && lineList.value && lineList.value.length > 0) { if (!tableStore.table.params.lineId && lineList.value && lineList.value.length > 0) {
tableStore.table.params.lineId = lineList.value[0].lineId tableStore.table.params.lineId = lineList.value[0].lineId
@@ -330,11 +390,9 @@ const tableStore: any = new TableStore({
loadCallback: () => { loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
// 数据加载完成后的处理 // 数据加载完成后的处理
if (tableStore.table.data && tableStore.table.data.length > 0) { if (tableStore.table.data) {
chartsList.value = JSON.parse(JSON.stringify(tableStore.table.data)) chartsList.value = JSON.parse(JSON.stringify(tableStore.table.data))
setEchart() setEchart()
} else if (tableStore.table.data) {
showEchart.value = false
} }
} }
}) })
@@ -392,33 +450,44 @@ watch(
} }
) )
onMounted(() => { onMounted(async () => {
initLineList().then(() => { await initLineList()
initCode()
})
}) })
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, () => 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 deep: true
} }
) )
const addMenu = () => {}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// :deep(.el-select) { // :deep(.el-select) {

View File

@@ -9,11 +9,12 @@
v-model="tableStore.table.params.lineId" 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.lineId" :key="item.lineId"
:label="item.name" :label="item.lineName"
:value="item.lineId" :value="item.lineId"
/> />
</el-select> </el-select>
@@ -33,7 +34,7 @@ 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/gridSideStatistics/components/harmonicRatio.vue' import HarmonicRatio from '@/components/cockpit/gridSideStatistics/components/harmonicRatio.vue'
import { cslineList } from '@/api/harmonic-boot/cockpit/cockpit' import { cslineList ,governLineList} 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)
@@ -48,7 +49,7 @@ const loop50 = (key: string) => {
list.push({ list.push({
title: i + '次', title: i + '次',
field: key + i + 'Overtime', field: key + i + 'Overtime',
width: '80', width: '60',
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[key + i + 'Overtime']}</span>`
@@ -58,7 +59,7 @@ const loop50 = (key: string) => {
return list return list
} }
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/harmonic-boot/totalLimitStatistics/details', url: '/cs-harmonic-boot/totalLimitStatistics/details',
method: 'POST', method: 'POST',
publicHeight: 30, publicHeight: 30,
showPage: false, showPage: false,
@@ -84,21 +85,22 @@ const tableStore: any = new TableStore({
width: '150' width: '150'
}, },
{ {
title: '闪变越限(%)', title: '长时闪变越限(%)',
field: 'flickerOvertime', 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.flickerOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
} }
}, },
{ {
title: '谐波电压越限(%)', title: '电压偏差越限(%)',
children: loop50('uharm') field: 'voltageDevOvertime',
}, width: '100',
{ render: 'customTemplate',
title: '谐波电流越限(%)', customTemplate: (row: any) => {
children: loop50('iharm') return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
}
}, },
{ {
title: '三相不平衡度越限(%)', title: '三相不平衡度越限(%)',
@@ -110,23 +112,24 @@ const tableStore: any = new TableStore({
} }
}, },
{ {
title: '电压偏差越限(%)', title: '谐波电压越限(%)',
field: 'voltageDevOvertime', children: loop50('uharm')
width: '100',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
}
}, },
{ {
title: '频率偏差越限(%)', title: '谐波电流越限(%)',
field: 'freqDevOvertime', children: loop50('iharm')
width: '100', },
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>` // {
} // title: '频率偏差越限(%)',
} // field: 'freqDevOvertime',
// width: '100',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>`
// }
// }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
}, },
@@ -138,8 +141,10 @@ const tableStore: any = new TableStore({
provide('tableStore', tableStore) provide('tableStore', tableStore)
tableStore.table.params.sortBy = '' tableStore.table.params.sortBy = ''
tableStore.table.params.orderBy = '' tableStore.table.params.orderBy = ''
const time:any=ref([])
const open = async (row: any,searchBeginTime:any,searchEndTime:any) => { const open = async (row: any,searchBeginTime:any,searchEndTime:any) => {
dialogVisible.value = true dialogVisible.value = true
time.value=[searchBeginTime,searchEndTime]
initCSlineList() initCSlineList()
tableStore.table.params.lineId = row.lineId tableStore.table.params.lineId = row.lineId
@@ -174,8 +179,19 @@ const onHarmonicRatioClose = () => {
} }
const initCSlineList = async () => { const initCSlineList = async () => {
const res = await cslineList({}) // const res = await cslineList({})
options.value = res.data 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
} }

View File

@@ -1,7 +1,26 @@
<template> <template>
<div> <div>
<!-- 监测点列表 --> <!-- 监测点列表 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader> <TableHeader
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 <Table
ref="tableRef" ref="tableRef"
@cell-click="cellClickEvent" @cell-click="cellClickEvent"
@@ -11,27 +30,35 @@
<OverLimitDetails ref="OverLimitDetailsRef" /> <OverLimitDetails ref="OverLimitDetailsRef" />
<!-- 上传对话框 --> <!-- 上传对话框 -->
<el-dialog v-model="uploadDialogVisible" title="上传报告" width="500px" @closed="handleDialogClosed"> <el-dialog
v-model="uploadDialogVisible"
title="上传报告"
append-to-body
width="500px"
@closed="handleDialogClosed"
>
<el-upload <el-upload
ref="uploadRef" ref="uploadRef"
class="upload-demo" class="upload-demo"
:auto-upload="true" action=""
accept=".doc,.docx,.PDF"
:on-change="handleChange" :on-change="handleChange"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:http-request="handleUpload"
:limit="1" :limit="1"
:auto-upload="false"
:on-exceed="handleExceed" :on-exceed="handleExceed"
:on-remove="handleRemove"
:file-list="fileList" :file-list="fileList"
> >
<el-button type="primary">点击上传</el-button> <el-button type="primary">点击上传</el-button>
<template #tip> <template #tip>
<div class="el-upload__tip">只能上传Word或PDF文件且不超过10MB</div> <div class="el-upload__tip">上传Word或PDF文件</div>
</template> </template>
</el-upload> </el-upload>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="uploadDialogVisible = false">取消</el-button> <el-button @click="uploadDialogVisible = false">取消</el-button>
<el-button type="primary" @click="uploadDialogVisible = false">确定</el-button> <el-button type="primary" @click="handleUpload">确定</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
@@ -46,18 +73,22 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import OverLimitDetails from '@/components/cockpit/monitoringPointList/components/overLimitDetails.vue' import OverLimitDetails from '@/components/cockpit/monitoringPointList/components/overLimitDetails.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { uploadReport, getReportUrl } from '@/api/harmonic-boot/cockpit/cockpit' import { uploadReport, getReportUrl } from '@/api/harmonic-boot/cockpit/cockpit'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const TableHeaderRef = ref()
// 上传相关 // 上传相关
const uploadDialogVisible = ref(false) const uploadDialogVisible = ref(false)
const currentUploadRow = ref<any>(null) const currentUploadRow = ref<any>(null)
@@ -67,11 +98,11 @@ 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
if (datePickerValue && datePickerValue.timeValue) { // if (datePickerValue && datePickerValue.timeValue) {
// 更新时间参数 // // 更新时间参数
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]
} // }
} }
// 计算是否全屏展示 // 计算是否全屏展示
@@ -101,51 +132,87 @@ const tableStore: any = new TableStore({
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1 return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
} }
}, },
{
title: '治理对象',
field: 'sensitiveUser',
minWidth: '80'
},
{
title: '电压等级',
field: 'volGrade',
minWidth: '70'
},
{
title: '是否治理',
field: 'govern',
minWidth: '70'
},
// {
// title: '治理前报告',
// field: 'reportFileName',
// minWidth: '80',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.reportFileName}</span>`
// }
// },
{ {
title: '监测点名称', title: '监测点名称',
field: 'lineName', field: 'lineName',
minWidth: '70', minWidth: '120',
render: 'customTemplate', render: 'customTemplate',
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.lineName}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.lineName}</span>`
} }
}, },
{ title: '监测类型', field: 'position', minWidth: '60' }, {
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: '监测点状态', title: '监测点状态',
field: 'runStatus', field: 'runStatus',
minWidth: '60', render: 'tag',
render: 'customTemplate',
customTemplate: (row: any) => { width: 100,
return `<span style='color: ${row.runStatus === '中断' ? '#FF0000' : ''}'>${row.runStatus}</span>` 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: '电压等级',
field: 'volGrade',
minWidth: '80',
formatter: (row: any) => {
return row.cellValue == 0 ? '/' : row.cellValue + 'kV' || '/'
}
},
{
title: '是否治理',
field: 'govern',
minWidth: '80',
formatter: (row: any) => {
return row.cellValue || '/'
}
},
{ {
title: '最新数据时间', title: '最新数据时间',
field: 'latestTime', field: 'latestTime',
@@ -159,10 +226,31 @@ const tableStore: any = new TableStore({
} }
} }
}, },
// {
// 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: '报告',
field: 'reportFilePath',
minWidth: '120',
formatter: (row: any) => {
return row.cellValue == null ? '/' : row.cellValue.split('/').pop()
}
},
{ {
title: '操作', title: '操作',
minWidth: 120, fixed: 'right',
// fixed: 'right', width: 150,
render: 'buttons', render: 'buttons',
buttons: [ buttons: [
{ {
@@ -186,7 +274,7 @@ const tableStore: any = new TableStore({
icon: 'el-icon-EditPen', icon: 'el-icon-EditPen',
render: 'basicButton', render: 'basicButton',
click: row => { click: row => {
downloadTheReport(row.lineId) downloadTheReport(row.lineId, row.reportFilePath)
}, },
disabled: row => { disabled: row => {
return row.reportFilePath == null || row.reportFilePath.length == 0 return row.reportFilePath == null || row.reportFilePath.length == 0
@@ -209,8 +297,7 @@ const tableStore: any = new TableStore({
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
@@ -220,8 +307,27 @@ const tableStore: any = new TableStore({
const tableRef = ref() const tableRef = ref()
provide('tableRef', tableRef) provide('tableRef', tableRef)
tableStore.table.params.keywords = '' tableStore.table.params.keywords = ''
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 == 'lineName') { if (column.field == 'lineName') {
@@ -234,16 +340,43 @@ const cellClickEvent = ({ row, column }: any) => {
} }
// 下载报告 // 下载报告
const downloadTheReport = (lineId: string) => { const downloadTheReport = (lineId: string, name: string) => {
getReportUrl({ lineId: lineId }).then((res: any) => { getReportUrl({ lineId: lineId }).then((res: any) => {
const link = document.createElement('a') forceDownloadPdf(res.data, name.split('/').pop() || '')
link.href = res.data
link.download = '治理报告'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}) })
} }
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) => { const uploadReportRow = (row: any) => {
@@ -260,14 +393,18 @@ const handleDialogClosed = () => {
} }
// 处理文件超出限制 // 处理文件超出限制
const handleExceed = (files: any, fileList: any) => { const handleExceed = (files: any) => {
ElMessage.warning('只能上传一个文件,请先删除已选择的文件') ElMessage.warning('只能上传一个文件,请先删除已选择的文件')
} }
const handleRemove = (files: any) => {
fileList.value = []
}
// 文件变更处理函数 // 文件变更处理函数
const handleChange = (file: any, fileList: any) => { const handleChange = (file: any) => {
// 在这里直接处理文件上传逻辑 // 在这里直接处理文件上传逻辑
beforeUpload(file.raw) // 注意使用 file.raw 获取原始文件对象 // beforeUpload(file.raw) // 注意使用 file.raw 获取原始文件对象
fileList.value = [file] // 只保留最新选择的文件
} }
// 处理上传前检查 // 处理上传前检查
@@ -284,11 +421,7 @@ const beforeUpload = (file: any) => {
const isLt10M = file.size / 1024 / 1024 < 10 const isLt10M = file.size / 1024 / 1024 < 10
if (!isValidType) { if (!isValidType) {
ElMessage.error('上传文件只能是 Word 文档(.doc/.docx) 或 PDF 文件(.pdf)!') ElMessage.error('上传(.doc/.docx/.pdf)格式文件!')
return false
}
if (!isLt10M) {
ElMessage.error('上传文件大小不能超过 10MB!')
return false return false
} }
@@ -296,10 +429,11 @@ const beforeUpload = (file: any) => {
return true return true
} }
const handleUpload = async (options: any) => { const handleUpload = async () => {
const { file } = options // return
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', fileList.value[0]?.raw)
formData.append('lineId', currentUploadRow.value?.lineId || currentUploadRow.value?.id || '') formData.append('lineId', currentUploadRow.value?.lineId || currentUploadRow.value?.id || '')
try { try {
@@ -326,12 +460,7 @@ watch(
watch( watch(
() => prop.timeValue, () => 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 deep: true

View File

@@ -1,5 +1,5 @@
<template> <template>
<el-dialog draggable title="趋势图" v-model="dialogVisible" append-to-body width="70%"> <el-dialog draggable :title="titles" v-model="dialogVisible" append-to-body width="70%">
<!-- 总体指标占比详情谐波含有率 --> <!-- 总体指标占比详情谐波含有率 -->
<div> <div>
<TableHeader ref="tableHeaderRef" :showSearch="false" @selectChange="selectChange"> <TableHeader ref="tableHeaderRef" :showSearch="false" @selectChange="selectChange">
@@ -7,7 +7,7 @@
<el-form-item> <el-form-item>
<DatePicker ref="datePickerRef"></DatePicker> <DatePicker ref="datePickerRef"></DatePicker>
</el-form-item> </el-form-item>
<el-form-item label="统计指标" label-width="80px"> <el-form-item label="统计指标" label-width="80px" v-if="props.showIndex">
<el-select <el-select
multiple multiple
:multiple-limit="2" :multiple-limit="2"
@@ -16,6 +16,7 @@
v-model="searchForm.index" v-model="searchForm.index"
placeholder="请选择统计指标" placeholder="请选择统计指标"
@change="onIndexChange($event)" @change="onIndexChange($event)"
filterable
> >
<el-option <el-option
v-for="item in indexOptions" v-for="item in indexOptions"
@@ -36,6 +37,7 @@
style="min-width: 120px !important" style="min-width: 120px !important"
placeholder="请选择" placeholder="请选择"
v-model="searchForm.valueType" v-model="searchForm.valueType"
filterable
> >
<el-option value="max" label="最大值"></el-option> <el-option value="max" label="最大值"></el-option>
<el-option value="min" label="最小值"></el-option> <el-option value="min" label="最小值"></el-option>
@@ -59,11 +61,12 @@
placeholder="请选择谐波次数" placeholder="请选择谐波次数"
style="width: 100px" style="width: 100px"
class="mr20" class="mr20"
filterable
> >
<el-option <el-option
v-for="vv in item.countOptions" v-for="vv in item.countOptions"
:key="vv" :key="vv"
:label="vv" :label="item.name.includes('间谐波') ? vv - 0.5 : vv"
:value="vv" :value="vv"
></el-option> ></el-option>
</el-select> </el-select>
@@ -79,11 +82,7 @@
</TableHeader> </TableHeader>
</div> </div>
<div class="history_chart" :style="pageHeight" v-loading="loading"> <div class="history_chart" :style="pageHeight" v-loading="loading">
<MyEchart <MyEchart ref="historyChart" :options="echartsData" v-if="showEchart" />
ref="historyChart"
:options="echartsData"
v-if="showEchart"
/>
<el-empty :style="pageHeight" v-else description="暂无数据" /> <el-empty :style="pageHeight" v-else description="暂无数据" />
</div> </div>
</el-dialog> </el-dialog>
@@ -108,9 +107,14 @@ defineOptions({
const props = defineProps({ const props = defineProps({
TrendList: { TrendList: {
type: Array type: Array
},
showIndex:{
type: Boolean,
default: true
} }
}) })
const titles = ref('趋势图')
const dialogVisible: any = ref(false) const dialogVisible: any = ref(false)
// console.log("🚀 ~ props:", props.TrendList) // console.log("🚀 ~ props:", props.TrendList)
const showEchart = ref(true) const showEchart = ref(true)
@@ -157,28 +161,40 @@ const countOptions: any = ref([])
const legendDictList: any = ref([]) const legendDictList: any = ref([])
const initCode = (field: string, title: string) => { const initCode = (field: string, title: string) => {
queryByCode('steady_state_limit_trend').then(res => { queryByCode('gridSide_exceedTheLimit').then(res => {
queryCsDictTree(res.data.id).then(item => { queryCsDictTree(res.data.id).then(item => {
//排序 //排序
indexOptions.value = item.data.sort((a: any, b: any) => { indexOptions.value = item.data.sort((a: any, b: any) => {
return a.sort - b.sort return a.sort - b.sort
}) })
const titleMap: Record<string, number> = { // const titleMap: Record<string, number> = {
flickerOvertime: 0, // flickerOvertime: 0,
uaberranceOvertime: 3, // uaberranceOvertime: 3,
ubalanceOvertime: 4, // ubalanceOvertime: 4,
freqDevOvertime: 5 // freqDevOvertime: 5
} // }
let defaultIndex = 0 // 默认值 // let defaultIndex = 0 // 默认值
if (field in titleMap) { // if (field in titleMap) {
defaultIndex = titleMap[field] // defaultIndex = titleMap[field]
} else if (field.includes('uharm')) { // } else if (field.includes('uharm')) {
defaultIndex = 1 // defaultIndex = 1
} else if (field.includes('iharm')) { // } else if (field.includes('iharm')) {
defaultIndex = 2 // 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 searchForm.value.index[0] = indexOptions.value[defaultIndex].id
}) })
@@ -202,7 +218,7 @@ const initCode = (field: string, title: string) => {
if (kk.harmStart && kk.harmEnd) { if (kk.harmStart && kk.harmEnd) {
range(0, 0, 0) range(0, 0, 0)
if (kk.showName == '间谐波电压含有率') { if (kk.showName.includes('间谐波电压')) {
countDataCopy.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1).map( countDataCopy.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1).map(
(item: any) => { (item: any) => {
return item - 0.5 return item - 0.5
@@ -257,6 +273,7 @@ const lineStyle = [{ type: 'solid' }, { type: 'dashed' }, { type: 'dotted' }]
const init = async () => { const init = async () => {
loading.value = true loading.value = true
// 选择指标的时候切换legend内容和data数据 // 选择指标的时候切换legend内容和data数据
echartsData.value = {}
let list: any = [] let list: any = []
legendDictList.value?.selectedList?.map((item: any) => { legendDictList.value?.selectedList?.map((item: any) => {
searchForm.value.index.map((vv: any) => { searchForm.value.index.map((vv: any) => {
@@ -291,7 +308,7 @@ const init = async () => {
} }
lists[index] = { lists[index] = {
statisticalId: item.index, statisticalId: item.index,
frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : '' frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : ''
} }
}) })
let obj = { let obj = {
@@ -396,6 +413,7 @@ const setEchart = () => {
formatter(params: any) { formatter(params: any) {
const xname = params[0].value[0] const xname = params[0].value[0]
let str = `${xname}<br>` let str = `${xname}<br>`
params.forEach((el: any, index: any) => { params.forEach((el: any, index: any) => {
let marker = '' let marker = ''
@@ -597,12 +615,12 @@ const formatCountOptions = () => {
}) })
countData.value.map((item: any, key: any) => { countData.value.map((item: any, key: any) => {
if (item.name == '谐波电流有效值') { if (item.name.includes('谐波电压')) {
item.name = '谐波电流次数'
} else if (item.name == '谐波电压含有率') {
item.name = '谐波电压次数'
} else if (item.name == '间谐波电压含有率') {
item.name = '间谐波电压次数' item.name = '间谐波电压次数'
} else if (item.name.includes('谐波电流')) {
item.name = '谐波电流次数'
} else if (item.name.includes('谐波电压')) {
item.name = '谐波电压次数'
} }
}) })
} }
@@ -640,6 +658,7 @@ watch(
) )
const openDialog = async (row: any, field: any, title: any) => { const openDialog = async (row: any, field: any, title: any) => {
titles.value = `${row?.lineName ? `${row.lineName}_` : ''}趋势图`
dialogVisible.value = true dialogVisible.value = true
trendRequestData.value = row trendRequestData.value = row

View File

@@ -9,11 +9,12 @@
v-model="tableStore.table.params.lineId" 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.lineId" :key="item.lineId"
:label="item.name" :label="item.lineName"
:value="item.lineId" :value="item.lineId"
/> />
</el-select> </el-select>
@@ -48,7 +49,7 @@ const loop50 = (key: string) => {
list.push({ list.push({
title: i + '次', title: i + '次',
field: key + i + 'Overtime', field: key + i + 'Overtime',
width: '80', width: '60',
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[key + i + 'Overtime']}</span>`
@@ -58,7 +59,7 @@ const loop50 = (key: string) => {
return list return list
} }
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/harmonic-boot/totalLimitStatistics/details', url: '/cs-harmonic-boot/totalLimitStatistics/details',
method: 'POST', method: 'POST',
publicHeight: 30, publicHeight: 30,
showPage: false, showPage: false,
@@ -84,21 +85,22 @@ const tableStore: any = new TableStore({
width: '150' width: '150'
}, },
{ {
title: '闪变越限(%)', title: '长时闪变越限(%)',
field: 'flickerOvertime', 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.flickerOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
} }
}, },
{ {
title: '谐波电压越限(%)', title: '电压偏差越限(%)',
children: loop50('uharm') field: 'voltageDevOvertime',
}, width: '100',
{ render: 'customTemplate',
title: '谐波电流越限(%)', customTemplate: (row: any) => {
children: loop50('iharm') return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
}
}, },
{ {
title: '三相不平衡度越限(%)', title: '三相不平衡度越限(%)',
@@ -110,23 +112,24 @@ const tableStore: any = new TableStore({
} }
}, },
{ {
title: '电压偏差越限(%)', title: '谐波电压越限(%)',
field: 'voltageDevOvertime', children: loop50('uharm')
width: '100',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.uaberranceOvertime}</span>`
}
}, },
{ {
title: '频率偏差越限(%)', title: '谐波电流越限(%)',
field: 'freqDevOvertime', children: loop50('iharm')
width: '100', },
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>` // {
} // title: '频率偏差越限(%)',
} // field: 'freqDevOvertime',
// width: '100',
// render: 'customTemplate',
// customTemplate: (row: any) => {
// return `<span style='cursor: pointer;text-decoration: underline;'>${row.freqDevOvertime}</span>`
// }
// }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
}, },
@@ -138,9 +141,10 @@ const tableStore: any = new TableStore({
provide('tableStore', tableStore) provide('tableStore', tableStore)
tableStore.table.params.sortBy = '' tableStore.table.params.sortBy = ''
tableStore.table.params.orderBy = '' tableStore.table.params.orderBy = ''
const open = async (row: any,searchBeginTime:any,searchEndTime:any) => { const open = async (row: any,searchBeginTime:any,searchEndTime:any,data: any) => {
dialogVisible.value = true dialogVisible.value = true
initCSlineList() // initCSlineList()
options.value = data
tableStore.table.params.lineId = row.lineId tableStore.table.params.lineId = row.lineId
nextTick(() => { nextTick(() => {

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"
@@ -30,20 +36,21 @@ import OverLimitDetails from '@/components/cockpit/overLimitStatistics/component
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 { totalLimitStatisticsData } from '@/api/harmonic-boot/cockpit/cockpit'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, 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 echartList = ref({})
@@ -82,7 +89,7 @@ const initEcharts = () => {
xAxis: { xAxis: {
// name: '(区域)', // name: '(区域)',
data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡'] data: ['长时闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
}, },
yAxis: { yAxis: {
@@ -120,7 +127,7 @@ const initEcharts = () => {
const OverLimitDetailsRef = ref() const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/harmonic-boot/totalLimitStatistics/list', url: '/cs-harmonic-boot/totalLimitStatistics/list',
method: 'POST', method: 'POST',
showPage: false, showPage: false,
@@ -143,7 +150,7 @@ const tableStore: any = new TableStore({
title: '越限占比(%)', title: '越限占比(%)',
children: [ children: [
{ {
title: '闪变', title: '长时闪变',
field: 'flicker', field: 'flicker',
minWidth: '70', minWidth: '70',
render: 'customTemplate', render: 'customTemplate',
@@ -191,8 +198,7 @@ const tableStore: any = new TableStore({
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}, },
loadCallback: () => { loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)` tableStore.table.height = `calc(${prop.height} - 80px)`
@@ -211,7 +217,8 @@ const cellClickEvent = ({ row, column }: any) => {
OverLimitDetailsRef.value.open( OverLimitDetailsRef.value.open(
row, row,
tableStore.table.params.searchBeginTime || prop.timeValue?.[0], tableStore.table.params.searchBeginTime || prop.timeValue?.[0],
tableStore.table.params.searchEndTime || prop.timeValue?.[1] tableStore.table.params.searchEndTime || prop.timeValue?.[1],
tableStore.table.data
) )
} }
} }
@@ -219,6 +226,26 @@ const cellClickEvent = ({ row, column }: any) => {
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 => {
@@ -229,12 +256,7 @@ watch(
watch( watch(
() => prop.timeValue, () => 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 deep: true

View File

@@ -1,8 +1,32 @@
<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 ? -58 : 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">
@@ -10,33 +34,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 TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number]}, w: { type: [String, Number] },
h: { type: [String, Number]}, h: { type: [String, Number] },
width: { type: [String, Number]}, width: { type: [String, Number] },
height: { type: [String, Number]}, height: { type: [String, Number] },
timeKey: { type: [String, Number]}, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
const dictData = useDictData() const TableHeaderRef = ref()
const sensitiveUserType = dictData.getBasicData('Sensitive_User_Type')
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
if (datePickerValue && datePickerValue.timeValue) { // if (datePickerValue && datePickerValue.timeValue) {
// 更新时间参数 // // 更新时间参数
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,10 +76,9 @@ const fullscreen = computed(() => {
} }
}) })
const OverLimitDetailsRef = ref() const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/cs-harmonic-boot/pqSensitiveUser/getList', url: '/cs-harmonic-boot/pqSensitiveUser/getListByUser',
method: 'POST', method: 'POST',
showPage: fullscreen.value ? true : false, showPage: fullscreen.value ? true : false,
column: [ column: [
@@ -76,30 +100,34 @@ const tableStore: any = new TableStore({
title: '敏感负荷类型', title: '敏感负荷类型',
field: 'loadType', field: 'loadType',
minWidth: '70', minWidth: '70',
formatter: row => { formatter: row => {
return sensitiveUserType.filter(item => item.id == row.cellValue)[0]?.name return sensitiveUserType.filter(item => item.id == row.cellValue)[0]?.name
} }
}, },
{ {
title: '是否监测', title: '是否监测',
field: 'isMonitor', field: 'isMonitor',
minWidth: '80' minWidth: '80',
formatter: (row: any) => {
return row.cellValue || '/'
}
}, },
{ {
title: '是否治理', title: '是否治理',
field: 'isGovern', field: 'isGovern',
minWidth: '80' minWidth: '80',
formatter: (row: any) => {
return row.cellValue || '/'
}
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}, },
loadCallback: () => { loadCallback: () => {}
}
}) })
tableStore.table.params.searchValue = ''
const tableRef = ref() const tableRef = ref()
provide('tableRef', tableRef) provide('tableRef', tableRef)
@@ -113,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()
@@ -127,17 +173,11 @@ watch(
watch( watch(
() => prop.timeValue, () => 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 deep: true
} }
) )
</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,146 @@ 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',
loading: 'loading1',
disabled: row => {
return !row.wavePath
},
click: async row => {
row.loading1 = 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 => {
isWaveCharts.value = true
loading.value = true
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

@@ -7,6 +7,7 @@
@selectChange="selectChange" @selectChange="selectChange"
datePicker datePicker
v-if="fullscreen" v-if="fullscreen"
:timeKeyList="prop.timeKey"
></TableHeader> ></TableHeader>
<el-calendar <el-calendar
v-model="value" v-model="value"
@@ -14,9 +15,16 @@
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`, height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`,
overflow: 'auto' 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>
@@ -31,38 +39,44 @@
</template> </template>
<div <div
style="text-decoration: underline" style="text-decoration: underline"
:style="{ height: `calc(${prop.height} / 5 - 40px)`, overflow: 'auto' }" class="details"
v-for="item in list?.filter((item:any) => item.name == data.day)" v-for="item in list?.filter((item:any) => item.name == data.day)"
@click="descentClick(item)"
> >
<div @click="descentClick">电压暂降:{{ item.eventDown || 0 }}</div> <!-- <div>电压暂降:{{ item.eventDown || 0 }}</div>
<div>电压中断:{{ item.eventOff || 0 }}</div> <div>电压中断:{{ item.eventOff || 0 }}</div>
<div>电压暂升:{{ item.eventUp || 0 }}</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, nextTick } 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, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object } timeValue: { type: Object },
interval: { type: Number }
}) })
const headerHeight = ref(57) const headerHeight = ref(57)
@@ -79,11 +93,11 @@ const hasEventData = (day: string) => {
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
// if (datePickerValue && datePickerValue.timeValue) { if (datePickerValue && datePickerValue.timeValue) {
// // 更新时间参数 // 更新时间参数
// 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]
// } }
} }
// 计算是否全屏展示 // 计算是否全屏展示
@@ -113,13 +127,11 @@ const tableStore: any = new TableStore({
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
if (prop.timeValue && Array.isArray(prop.timeValue)) { setTime()
tableStore.table.params.searchBeginTime = prop.timeValue[0]
tableStore.table.params.searchEndTime = prop.timeValue[1]
}
}, },
loadCallback: () => { loadCallback: () => {
value.value = tableStore.table.params.searchBeginTime
list.value = tableStore.table.data list.value = tableStore.table.data
} }
}) })
@@ -141,61 +153,65 @@ provide('tableStore', tableStore)
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
if (TableHeaderRef.value && typeof TableHeaderRef.value.setDatePicker === 'function') {
TableHeaderRef.value.setDatePicker([{ label: '月份', value: 3 }])
}
if (fullscreen.value) {
TableHeaderRef.value.setInterval(3)
}
tableStore.index() tableStore.index()
}) })
}) })
watch(
() => prop.timeKey, const setTime = () => {
val => { const time = getTime(
tableStore.index() (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, () => prop.timeValue,
// (newVal, oldVal) => { (newVal, oldVal) => {
// // 当外部时间值变化时,更新表格的时间参数
// 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()
// }
// },
val => {
tableStore.index() tableStore.index()
}, },
{ {
deep: true 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,23 +1,30 @@
<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="{ :style="{
width: prop.width, width: prop.width,
height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )` height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}" }"
/> />
<my-echart <!-- <my-echart
class="mt10" class="mt10"
:options="echartList1" :options="echartList1"
: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 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`
}" }"
/> /> -->
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -26,16 +33,20 @@ import TableStore from '@/utils/tableStore'
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 TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, 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 selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
@@ -66,135 +77,6 @@ const echartList = ref({})
const echartList1 = ref({}) const echartList1 = ref({})
// const echartList1 = ref({
// title: {
// text: '越限时间概率分布'
// },
// xAxis: {
// // 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 processDataForChart = (rawData: any[]) => { const processDataForChart = (rawData: any[]) => {
// 将后端返回的扁平数据转换为 ECharts 需要的三维坐标格式 [x, y, z] // 将后端返回的扁平数据转换为 ECharts 需要的三维坐标格式 [x, y, z]
const chartData = rawData.map(item => [item.x, item.y, item.z]) const chartData = rawData.map(item => [item.x, item.y, item.z])
@@ -208,8 +90,7 @@ const tableStore: any = new TableStore({
showPage: false, showPage: false,
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}, },
loadCallback: () => { loadCallback: () => {
const processedData = processDataForChart(tableStore.table.data.innerList || []) const processedData = processDataForChart(tableStore.table.data.innerList || [])
@@ -217,29 +98,37 @@ const tableStore: any = new TableStore({
const xlist = tableStore.table.data.xlist || [] const xlist = tableStore.table.data.xlist || []
// 处理趋势图数据 // 处理趋势图数据
const seriesData = [ const seriesData = trendList.map((item: any) => {
{ // 根据接口返回的name字段确定系列名称和颜色
name: '电压中断', let name = ''
type: 'line', let color = ''
showSymbol: false,
color: '#FF9100', switch (item.name) {
data: trendList[0]?.map((value: number, index: number) => [xlist[index], value]) || [] case 'Evt_Sys_DipStr':
}, name = '电压暂降'
{ color = '#FFBF00'
name: '电压暂降', break
type: 'line', case 'Evt_Sys_IntrStr':
showSymbol: false, name = '电压中断'
color: '#FFBF00', color = '#FF9100'
data: trendList[1]?.map((value: number, index: number) => [xlist[index], value]) || [] break
}, case 'Evt_Sys_SwlStr':
{ name = '电压暂升'
name: '电压暂升', color = config.layout.elementUiPrimary[0]
type: 'line', break
showSymbol: false, default:
color: config.layout.elementUiPrimary[0], name = item.name
data: trendList[2]?.map((value: number, index: number) => [xlist[index], value]) || [] color = '#000000'
} }
]
return {
name: name,
type: 'line',
showSymbol: false,
color: color,
data: item.trendList?.map((value: number, index: number) => [xlist[index], value]) || []
}
})
// 获取x轴和y轴的标签值 // 获取x轴和y轴的标签值
const xLabels = [ const xLabels = [
@@ -272,9 +161,7 @@ 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 (params: any) { formatter: function (params: any) {
console.log(params, '99000')
var tips = '' var tips = ''
tips += '持续时间: ' + yLabels[params.value[1]] + '</br>' tips += '持续时间: ' + yLabels[params.value[1]] + '</br>'
tips += '特征幅值: ' + xLabels[params.value[0]] + '</br>' tips += '特征幅值: ' + xLabels[params.value[0]] + '</br>'
tips += '事件次数: ' + params.value[2] + '</br>' tips += '事件次数: ' + params.value[2] + '</br>'
@@ -296,34 +183,16 @@ const tableStore: any = new TableStore({
type: 'category', type: 'category',
name: '特征幅值', name: '特征幅值',
data: xLabels, data: xLabels,
axisLine: { nameGap: 40
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111'
}
}, },
yAxis3D: { yAxis3D: {
type: 'category', type: 'category',
name: '持续时间', name: '持续时间',
data: yLabels, data: yLabels,
nameTextStyle: { nameGap: 40,
color: '#111'
},
axisLine: {
show: true,
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111'
},
splitLine: { splitLine: {
lineStyle: { lineStyle: {
color: ['#111'],
type: 'dashed', type: 'dashed',
opacity: 0.5 opacity: 0.5
} }
@@ -331,25 +200,28 @@ const tableStore: any = new TableStore({
}, },
zAxis3D: { zAxis3D: {
type: 'value', type: 'value',
splitNumber: 10,
minInterval: 10, minInterval: 10,
name: '暂态事件次数' name: '暂态事件次数',
nameGap: 30
}, },
grid3D: { grid3D: {
viewControl: { viewControl: {
projection: 'perspective', projection: 'perspective',
distance: 250 distance: 260,
}, rotateSensitivity: 10,
boxWidth: 200, zoomSensitivity: 2
boxDepth: 80,
light: {
main: {
intensity: 1.2
}, },
ambient: { boxWidth: 150,
intensity: 0.3 boxDepth: 100,
boxHeight: 100,
light: {
main: {
intensity: 1.2
},
ambient: {
intensity: 0.4
}
} }
}
}, },
series: [ series: [
{ {
@@ -362,20 +234,6 @@ const tableStore: any = new TableStore({
fontSize: 16, fontSize: 16,
borderWidth: 1 borderWidth: 1
} }
},
itemStyle: {
opacity: 1
},
emphasis: {
label: {
textStyle: {
fontSize: 20,
color: '#900'
}
},
itemStyle: {
color: '#900'
}
} }
} }
] ]
@@ -413,26 +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, () => 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 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,15 +31,22 @@
</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>
<!-- 查看波形 --> <!-- 查看波形 -->
<el-dialog v-model="isWaveCharts" draggable title="瞬时/RMS波形" append-to-body width="70%"> <el-dialog
v-model="isWaveCharts"
draggable
title="波形分析"
append-to-body
width="70%"
@close="handleHideCharts"
>
<waveFormAnalysis <waveFormAnalysis
v-loading="loading" v-loading="loading"
v-if="isWaveCharts" v-if="isWaveCharts"
ref="waveFormAnalysisRef" ref="waveFormAnalysisRef"
@handleHideCharts="isWaveCharts = false" @handleHideCharts="handleHideCharts"
:wp="wp" :wp="wp"
/> />
</el-dialog> </el-dialog>
@@ -42,6 +60,8 @@ 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 waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue'
import { analyseWave } from '@/api/common' import { analyseWave } from '@/api/common'
import { getSimpleLine } from '@/api/harmonic-boot/cockpit/cockpit'
const dialogVisible: any = ref(false) const dialogVisible: any = ref(false)
const waveFormAnalysisRef: any = ref(null) const waveFormAnalysisRef: any = ref(null)
// 波形 // 波形
@@ -50,18 +70,27 @@ const loading = ref(false)
const wp = ref({}) const wp = ref({})
const boxoList: any = ref({}) const boxoList: any = ref({})
const options = [ const tableHeaderRef = ref()
{
value: '35kV进线', const options = ref()
label: '35kV进线' 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: [
{ {
@@ -74,65 +103,95 @@ 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',
loading: 'loading1', loading: 'loading1',
// disabled: row => { disabled: row => {
// return !row.wavePath return !row.wavePath
// }, },
click: async row => { click: async row => {
row.loading1 = true row.loading1 = true
loading.value = true
isWaveCharts.value = true // 在打开弹窗时立即设置高度
nextTick(() => {
if (waveFormAnalysisRef.value) {
// waveFormAnalysisRef.value.setHeight(false, 360)
waveFormAnalysisRef.value.setHeight(999, 130, 1.6666666)
}
})
await analyseWave(row.id) await analyseWave(row.id)
.then(res => { .then(res => {
loading.value = true
isWaveCharts.value = true
dialogVisible.value = false
row.loading1 = false row.loading1 = false
if (res != undefined) { if (res != undefined) {
boxoList.value = row boxoList.value = row
boxoList.value.featureAmplitude = // boxoList.value = {
row.evtParamVVaDepth != '-' ? row.evtParamVVaDepth - 0 : null // ...row,
boxoList.value.systemType = 'WX' // 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 wp.value = res.data
} }
loading.value = false loading.value = false
@@ -147,44 +206,41 @@ const tableStore: any = new TableStore({
waveFormAnalysisRef.value.getWpData(wp.value, boxoList.value, true) 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
isWaveCharts.value = true
}
} }
defineExpose({ open }) defineExpose({ open })

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,25 +30,27 @@ 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/cockpit/transientStatistics/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 { useTimeCacheStore } from '@/stores/timeCache'
import { netEventEcharts } from '@/api/harmonic-boot/cockpit/cockpit' import { netEventEcharts } from '@/api/harmonic-boot/cockpit/cockpit'
import { getTime } from '@/utils/formatTime'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, 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 selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height headerHeight.value = height
@@ -76,7 +84,7 @@ const eventEcharts = () => {
searchEndTime: tableStore.table.params.searchEndTime || prop.timeValue?.[1] searchEndTime: tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}).then(res => { }).then(res => {
// 整理接口数据为图表所需格式 // 整理接口数据为图表所需格式
const rawData = res.data || {}; const rawData = res.data || {}
data.value = [ data.value = [
{ {
name: '电压中断', name: '电压中断',
@@ -90,8 +98,8 @@ const eventEcharts = () => {
name: '电压暂升', name: '电压暂升',
value: rawData.eventUp || 0 value: rawData.eventUp || 0
} }
]; ]
echartList.value = { echartList.value = {
title: {}, title: {},
tooltip: { tooltip: {
@@ -99,8 +107,8 @@ const eventEcharts = () => {
}, },
legend: { legend: {
orient: 'vertical', orient: 'vertical',
top: 'center', top: '50',
right: '5%', right: '10',
formatter: function (e: any) { formatter: function (e: any) {
return e + ' ' + data.value.filter((item: any) => item.name == e)[0].value + '次' return e + ' ' + data.value.filter((item: any) => item.name == e)[0].value + '次'
} }
@@ -124,7 +132,7 @@ const eventEcharts = () => {
left: 'center' left: 'center'
}, },
{ {
text: (rawData.eventOff + rawData.eventDown + rawData.eventUp) + '次', text: rawData.eventOff + rawData.eventDown + rawData.eventUp + '次',
left: 'center', left: 'center',
top: 'center' top: 'center'
} }
@@ -174,6 +182,7 @@ const tableStore: any = new TableStore({
title: '电压中断(次)', title: '电压中断(次)',
field: 'eventOff', 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.eventOff}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.eventOff}</span>`
@@ -183,6 +192,7 @@ const tableStore: any = new TableStore({
title: '电压暂降(次)', title: '电压暂降(次)',
field: 'eventDown', 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.eventDown}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.eventDown}</span>`
@@ -192,6 +202,7 @@ const tableStore: any = new TableStore({
title: '电压暂升(次)', title: '电压暂升(次)',
field: 'eventUp', 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.eventUp}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.eventUp}</span>`
@@ -199,8 +210,7 @@ const tableStore: any = new TableStore({
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
}, },
loadCallback: () => { loadCallback: () => {
eventEcharts() eventEcharts()
@@ -215,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 不是一个有效数组')
} }
} }
@@ -233,12 +266,7 @@ watch(
watch( watch(
() => prop.timeValue, () => 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 deep: true

View File

@@ -1,10 +1,22 @@
<template> <template>
<div> <div>
<!--趋势对比 --> <!--趋势对比 -->
<TableHeader datePicker :showReset="false" @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 v-model="tableStore.table.params.sensitiveUserId" placeholder="请选择监测对象" clearable> <el-select
filterable
v-model="tableStore.table.params.sensitiveUserId"
placeholder="请选择监测对象"
clearable
>
<el-option v-for="item in idList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in idList" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -68,8 +80,8 @@
width: prop.width, width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)` height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}" }"
v-if="showEchart"
/> />
<!-- <el-empty description="暂无数据" /> -->
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -78,26 +90,24 @@ import TableStore from '@/utils/tableStore'
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 { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree' import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { getListByIds } from '@/api/harmonic-boot/cockpit/cockpit' 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, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: [String, Number] }, 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 lineIdList = ref(JSON.parse(window.localStorage.getItem('lineIdList') || '[]'))
// 计算是否全屏展示 // 计算是否全屏展示
const fullscreen = computed(() => { const fullscreen = computed(() => {
const w = Number(prop.w) const w = Number(prop.w)
@@ -116,19 +126,19 @@ const indicatorList = ref()
const echartList = ref() const echartList = ref()
const showEchart = ref(true)
const headerHeight = ref(57) const headerHeight = ref(57)
// 监测对象 // 监测对象
const idList = ref() const idList = ref([])
// 监测对象 // 监测对象
const initListByIds = () => { const initListByIds = () => {
getListByIds({}).then((res: any) => { getListByIds({}).then((res: any) => {
if (res.data.length > 0) { if (res.data?.length > 0) {
idList.value = res.data idList.value = res.data
initCode() initCode()
} else {
tableStore.index()
} }
}) })
} }
@@ -173,7 +183,7 @@ const setEchart = () => {
if (!beforeGroupedByPhase[phase]) { if (!beforeGroupedByPhase[phase]) {
beforeGroupedByPhase[phase] = [] beforeGroupedByPhase[phase] = []
} }
beforeGroupedByPhase[phase].push([item.time, item.statisticalData]) beforeGroupedByPhase[phase].push([item.time, item.statisticalData, item.unit, 'solid'])
}) })
// 处理治理后数据 // 处理治理后数据
@@ -182,7 +192,7 @@ const setEchart = () => {
if (!afterGroupedByPhase[phase]) { if (!afterGroupedByPhase[phase]) {
afterGroupedByPhase[phase] = [] afterGroupedByPhase[phase] = []
} }
afterGroupedByPhase[phase].push([item.time, item.statisticalData]) afterGroupedByPhase[phase].push([item.time, item.statisticalData, item.unit, 'dotted'])
}) })
// 构建系列数据 // 构建系列数据
@@ -209,13 +219,7 @@ const setEchart = () => {
data: beforeGroupedByPhase[phase], data: beforeGroupedByPhase[phase],
itemStyle: { itemStyle: {
normal: { normal: {
color: function (params: any) { color: color
if (params.value[1] == 0 || params.value[1] == 3.14159) {
return '#ccc'
} else {
return color
}
}
} }
}, },
lineStyle: { lineStyle: {
@@ -240,13 +244,7 @@ const setEchart = () => {
data: afterGroupedByPhase[phase], data: afterGroupedByPhase[phase],
itemStyle: { itemStyle: {
normal: { normal: {
color: function (params: any) { color: color
if (params.value[1] == 0 || params.value[1] == 3.14159) {
return '#ccc'
} else {
return color
}
}
} }
}, },
lineStyle: { lineStyle: {
@@ -265,6 +263,11 @@ const setEchart = () => {
titleText = 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) => { const legendData = series.map((item: any, index: number) => {
let color = config.layout.elementUiPrimary[0] let color = config.layout.elementUiPrimary[0]
@@ -294,11 +297,45 @@ const setEchart = () => {
} }
} }
}) })
let [min, max] = yMethod(
[...chartsListBefore.value.map((item: any) => item.statisticalData),
...chartsListAfter.value.map((item: any) => item.statisticalData)]
)
echartList.value = { echartList.value = {
title: { title: {
text: titleText 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: { legend: {
data: legendData, data: legendData,
icon: 'rect', icon: 'rect',
@@ -322,7 +359,9 @@ const setEchart = () => {
} }
}, },
yAxis: { yAxis: {
name: beforeData.length > 0 ? beforeData[0].unit : afterData.length > 0 ? afterData[0].unit : '' name: beforeData.length > 0 ? beforeData[0].unit : afterData.length > 0 ? afterData[0].unit : '',
max: max,
min: min,
}, },
grid: { grid: {
left: '10px', left: '10px',
@@ -339,8 +378,7 @@ const tableStore: any = new TableStore({
exportName: '趋势对比', exportName: '趋势对比',
column: [], column: [],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.searchBeginTime = tableStore.table.params.searchBeginTime || prop.timeValue?.[0] setTime()
tableStore.table.params.searchEndTime = tableStore.table.params.searchEndTime || prop.timeValue?.[1]
if (!tableStore.table.params.sensitiveUserId && idList.value?.length > 0) { if (!tableStore.table.params.sensitiveUserId && idList.value?.length > 0) {
tableStore.table.params.sensitiveUserId = idList.value[0].id tableStore.table.params.sensitiveUserId = idList.value[0].id
} }
@@ -371,14 +409,12 @@ const tableStore: any = new TableStore({
chartsListBefore.value = tableStore.table.data.before chartsListBefore.value = tableStore.table.data.before
chartsListAfter.value = tableStore.table.data.after chartsListAfter.value = tableStore.table.data.after
setEchart() setEchart()
} else if (tableStore.table.data) {
showEchart.value = false
} }
} }
}) })
tableStore.table.params.indicator = '1' tableStore.table.params.indicator = ''
tableStore.table.params.exceedingTheLimit = '1' tableStore.table.params.exceedingTheLimit = ''
tableStore.table.params.dataLevel = 'Primary' tableStore.table.params.dataLevel = 'Primary'
tableStore.table.params.valueType = 'avg' tableStore.table.params.valueType = 'avg'
provide('tableStore', tableStore) provide('tableStore', tableStore)
@@ -387,6 +423,25 @@ onMounted(() => {
initListByIds() 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 = () => { const shouldShowHarmonicCount = () => {
if (!tableStore.table.params.indicator || !indicatorList.value) return false if (!tableStore.table.params.indicator || !indicatorList.value) return false
@@ -395,7 +450,7 @@ const shouldShowHarmonicCount = () => {
return ( return (
currentIndicator && currentIndicator &&
(currentIndicator.name.includes('电压谐波含有率') || currentIndicator.name.includes('电流谐波含有率')) (currentIndicator.name.includes('幅值') || currentIndicator.name.includes('含有率'))
) )
} }
@@ -404,9 +459,9 @@ const getHarmonicTypeName = () => {
const currentIndicator = indicatorList.value.find((item: any) => item.id === tableStore.table.params.indicator) const currentIndicator = indicatorList.value.find((item: any) => item.id === tableStore.table.params.indicator)
if (currentIndicator) { if (currentIndicator) {
if (currentIndicator.name.includes('电压谐波含有率')) { if (currentIndicator.name.includes('电压')) {
return '电压' return '电压'
} else if (currentIndicator.name.includes('电流谐波含有率')) { } else if (currentIndicator.name.includes('电流')) {
return '电流' return '电流'
} }
} }
@@ -422,12 +477,7 @@ watch(
watch( watch(
() => prop.timeValue, () => 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 deep: true
@@ -449,8 +499,6 @@ watch(
} }
} }
) )
const addMenu = () => {}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// :deep(.el-select) { // :deep(.el-select) {

View File

@@ -69,7 +69,8 @@ const initChart = () => {
top: 15, top: 15,
feature: { feature: {
saveAsImage: { saveAsImage: {
title: '保存图片' title: '下载图片',
name: props.options?.title?.text || '图表'
}, },
...(props.options?.toolbox?.featureProps || null) ...(props.options?.toolbox?.featureProps || null)
}, },
@@ -106,13 +107,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

@@ -1,311 +1,322 @@
// 辅助函数 // 辅助函数
const getMax = (temp, tempA, tempB, tempC) => { const getMax = (temp, tempA, tempB, tempC) => {
temp = temp > tempA ? temp : tempA; temp = temp > tempA ? temp : tempA
temp = temp > tempB ? temp : tempB; temp = temp > tempB ? temp : tempB
if (tempC !== undefined) { if (tempC !== undefined) {
temp = temp > tempC ? temp : tempC; temp = temp > tempC ? temp : tempC
} }
return temp; return temp
}; }
const getMaxTwo = (temp, tempA, tempB) => { const getMaxTwo = (temp, tempA, tempB) => {
temp = temp > tempA ? temp : tempA; temp = temp > tempA ? temp : tempA
temp = temp > tempB ? temp : tempB; temp = temp > tempB ? temp : tempB
return temp; return temp
}; }
const getMin = (temp, tempA, tempB, tempC) => { const getMin = (temp, tempA, tempB, tempC) => {
temp = temp < tempA ? temp : tempA; temp = temp < tempA ? temp : tempA
temp = temp < tempB ? temp : tempB; temp = temp < tempB ? temp : tempB
if (tempC !== undefined) { if (tempC !== undefined) {
temp = temp < tempC ? temp : tempC; temp = temp < tempC ? temp : tempC
} }
return temp; return temp
}; }
const getMinOpen = (temp, tempA, tempB) => { const getMinOpen = (temp, tempA, tempB) => {
temp = temp < tempA ? temp : tempA; temp = temp < tempA ? temp : tempA
temp = temp < tempB ? temp : tempB; temp = temp < tempB ? temp : tempB
return temp; return temp
}; }
// 数据处理函数 // 数据处理函数
const fliteWaveData = (wp, step, iphasicValue, isOpen) => { const fliteWaveData = (wp, step, iphasicValue, isOpen) => {
const rmsData = wp.listRmsData; const rmsData = wp.listRmsData
const pt = Number(wp.pt) / 1000; const pt = Number(wp.pt) / 1000
const ct = Number(wp.ct); const ct = Number(wp.ct)
const titleList = wp.waveTitle; const titleList = wp.waveTitle
let xishu = pt; let xishu = pt
let aTitle = "", let aTitle = '',
bTitle = "", bTitle = '',
cTitle = "", cTitle = '',
unit = "电压"; unit = '电压'
let rmsvFirstX = 0, let rmsvFirstX = 0,
rmsvFirstY = 0, rmsvFirstY = 0,
rmsvSecondX = 0, rmsvSecondX = 0,
rmsvSecondY = 0, rmsvSecondY = 0,
firstZhou = "a", firstZhou = 'a',
secondeZhou = "a"; secondeZhou = 'a'
let ifmax = 0, let ifmax = 0,
ifmin = 0, ifmin = 0,
ismax = 0, ismax = 0,
ismin = 0, ismin = 0,
rfmax = 0, rfmax = 0,
rfmin = 0, rfmin = 0,
rsmax = 0, rsmax = 0,
rsmin = 0; rsmin = 0
const shunshiFA = []; const shunshiFA = []
const shunshiFB = []; const shunshiFB = []
const shunshiFC = []; const shunshiFC = []
const shunshiSA = []; const shunshiSA = []
const shunshiSB = []; const shunshiSB = []
const shunshiSC = []; const shunshiSC = []
const rmsFA = []; const rmsFA = []
const rmsFB = []; const rmsFB = []
const rmsFC = []; const rmsFC = []
const rmsSA = []; const rmsSA = []
const rmsSB = []; const rmsSB = []
const rmsSC = []; const rmsSC = []
if (titleList[iphasicValue * step + 1]?.substring(0, 1) !== 'U') {
xishu = ct
if (titleList[iphasicValue * step + 1]?.substring(0, 1) !== "U") { unit = '电流'
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) { for (let i = 1; i <= iphasicValue; i++) {
case 1: switch (i) {
const rmsFirstA = rmsData[rms][iphasicValue * step + 1] * xishu; case 1:
rmsFA.push([rmsData[rms][0], rmsFirstA]); aTitle = titleList[iphasicValue * step + i]?.substring(1) || ''
rfmax = rfmax > rmsFirstA ? rfmax : rmsFirstA; break
rfmin = rfmin < rmsFirstA ? rfmin : rmsFirstA; case 2:
if (rfmin < rmsvFirstY) { bTitle = titleList[iphasicValue * step + i]?.substring(1) || ''
rmsvFirstY = rfmin; break
firstZhou = "a"; case 3:
rmsvFirstX = rmsData[rms][0]; cTitle = titleList[iphasicValue * step + i]?.substring(1) || ''
break
} }
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 }; if (rmsData[0] && rmsData[0][iphasicValue * step + 1] !== undefined) {
const instantS = { max: ismax, min: ismin }; rfmax = rmsData[0][iphasicValue * step + 1] * xishu
const RMSF = { max: rfmax, min: rfmin }; rfmin = rmsData[0][iphasicValue * step + 1] * xishu
const RMSS = { max: rsmax, min: rsmin }; rmsvFirstY = rmsData[0][iphasicValue * step + 1] * xishu
const RMSFMinDetail = { rmsvFirstX, rmsvFirstY, firstZhou }; rmsvFirstX = rmsData[0][0]
const RMSSMinDetail = { rmsvSecondX, rmsvSecondY, secondeZhou }; rsmax = rmsData[0][iphasicValue * step + 1]
const shunshiF = { shunshiFA, shunshiFB, shunshiFC }; rsmin = rmsData[0][iphasicValue * step + 1]
const shunshiS = { shunshiSA, shunshiSB, shunshiSC }; rmsvSecondY = rmsData[0][iphasicValue * step + 1]
const RMSFWave = { rmsFA, rmsFB, rmsFC }; rmsvSecondX = rmsData[0][0]
const RMSSWave = { rmsSA, rmsSB, rmsSC }; }
const title = { aTitle, bTitle, cTitle, unit };
for (let rms = 0; rms < rmsData.length; rms++) {
return { if (!rmsData[rms] || rmsData[rms][iphasicValue * step + 1] === undefined) {
break
instantF, }
instantS,
RMSF, switch (iphasicValue) {
RMSS, case 1:
RMSFMinDetail, const rmsFirstA = rmsData[rms][iphasicValue * step + 1] * xishu
RMSSMinDetail, rmsFA.push([rmsData[rms][0], rmsFirstA])
shunshiF, rfmax = rfmax > rmsFirstA ? rfmax : rmsFirstA
shunshiS, rfmin = rfmin < rmsFirstA ? rfmin : rmsFirstA
RMSFWave, if (rfmin < rmsvFirstY) {
RMSSWave, rmsvFirstY = rfmin
title, firstZhou = 'a'
unit, 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) { self.onmessage = function (e) {
const { wp, isOpen, value, boxoList } = JSON.parse(e.data); const { wp, isOpen, value, boxoList, requestId } = e.data
try { try {
const iphasicValue = wp.iphasic || 1; const iphasicValue = wp.iphasic || 1
const picCounts = (wp.waveTitle.length - 1) / iphasicValue; const picCounts = (wp.waveTitle.length - 1) / iphasicValue
const waveDatas = []; const waveDatas = []
for (let i = 0; i < picCounts; i++) { for (let i = 0; i < picCounts; i++) {
const data = fliteWaveData(wp, i, iphasicValue, isOpen,boxoList); const data = fliteWaveData(wp, i, iphasicValue, isOpen, boxoList)
waveDatas.push(data); 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({
requestId,
titles: titles,
success: true,
waveDatas,
time: wp.time,
type: wp.waveType,
severity: wp.yzd,
iphasic: iphasicValue
})
} catch (error) {
self.postMessage({
requestId,
success: false,
error: error.message
})
} }
// 处理标题 }
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.equipmentName +
" 发生时刻:" +
boxoList.startTime +
" 残余电压:" +
boxoList.evtParamVVaDepth +
" 持续时间:" +
boxoList.evtParamTm +
"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,
});
}
};

View File

@@ -1,11 +1,8 @@
<template> <template>
<div v-loading="loading" style="position: relative; height: 100%">
<div v-loading="loading" :element-loading-background="'rgba(122, 122, 122, 0.8)'" <div id="boxr">
style="position: relative;height: 100%;"> <div id="rmsp" :style="`height:${vh};overflow: hidden;min-height: 200px;`">
<div class="bx" id="rms" style="min-height: 200px"></div>
<div id="boxr" style="background: #343849c7">
<div id="rmsp" :style="`height:${vh};overflow: hidden;`">
<div class="bx" id="rms"></div>
</div> </div>
</div> </div>
</div> </div>
@@ -16,9 +13,16 @@ import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import html2canvas from 'html2canvas' import html2canvas from 'html2canvas'
import $ from 'jquery' import $ from 'jquery'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { mainHeight } from '@/utils/layout'
import { calcRmsYAxisRange, formatAxisLabel } from '@/utils/chartAxisHelper'
import url from '@/assets/img/point.png' import url from '@/assets/img/point.png'
import url2 from '@/assets/img/dw.png' import url2 from '@/assets/img/dw.png'
const worker = ref<Worker | null>(null) import { buildWaveCacheKey, getWaveCache, setWaveCache } from '@/utils/waveCache'
import { getRmsWorker, buildWorkerPayload } from '@/utils/waveWorkerPool'
let waveRequestId = 0
const pendingCacheKeys = new Map<number, string>()
let rmsWorker: Worker | null = null
interface WaveData { interface WaveData {
instantF: { max: number; min: number } instantF: { max: number; min: number }
instantS: { max: number; min: number } instantS: { max: number; min: number }
@@ -66,7 +70,7 @@ interface WaveData {
interface Props { interface Props {
value?: number value?: number
flag?: string | number | boolean flag?: string | number | boolean
parentHeight?: string | number | boolean parentHeight?: any
DColor?: boolean DColor?: boolean
boxoList?: any boxoList?: any
wp?: any wp?: any
@@ -78,7 +82,7 @@ const props = withDefaults(defineProps<Props>(), {
parentHeight: 0, parentHeight: 0,
DColor: false, DColor: false,
boxoList: () => ({}), boxoList: () => ({}),
wp: () => ({}), wp: () => ({})
}) })
const loading = ref(true) const loading = ref(true)
@@ -110,76 +114,92 @@ const myChartess4 = ref<echarts.ECharts | null>(null)
const myChartess5 = ref<echarts.ECharts | null>(null) const myChartess5 = ref<echarts.ECharts | null>(null)
const vh = computed(() => { const vh = computed(() => {
if (props.flag == 1) { if (props.parentHeight == 999) {
return '690px' return `calc((60vh - 150px) / 2 )`
} else if (props.parentHeight != 0) { } else if (props.parentHeight != 0) {
return '310px' return mainHeight(props.parentHeight, 2).height
}
if (props.flag == 1) {
return mainHeight(200).height
} else { } else {
return '350px' return mainHeight(200, 2).height
} }
}) })
const vw = computed(() => '100%') const vw = computed(() => '100%')
watch(() => props.value, (newVal) => { watch(
if (newVal == 2) { () => props.value,
initWaves() () => {
} else { query()
$('#wave1').remove()
initWaves()
} }
}) )
watch(
() => props.wp,
() => {
query()
}
)
const applyWorkerResult = (data: any) => {
titles.value = data.titles
waveDatas.value = data.waveDatas
time.value = data.time
type.value = data.type
severity.value = data.severity
iphasic.value = data.iphasic
if (Number(severity.value) < 0) {
severity.value = '/'
type.value = '/'
}
initWave(waveDatas.value, time.value, type.value, severity.value, isOpen.value)
loading.value = false
}
onMounted(() => { onMounted(() => {
const zoomValue = document.body.style.getPropertyValue('zoom'); const zoomValue = document.body.style.getPropertyValue('zoom')
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1); zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1)
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
// 初始化 Web Worker rmsWorker = getRmsWorker(data => {
worker.value = new Worker(new URL('./rmsWorker.js', import.meta.url)); if (data.requestId !== waveRequestId) return
worker.value.onmessage = (e) => { if (!data.success) {
if (e.data.success) { console.error('Worker error:', data.error)
const data = e.data
titles.value = data.titles
waveDatas.value = data.waveDatas
time.value = data.time
type.value = data.type
severity.value = data.severity
iphasic.value = data.iphasic
// 初始化波形图
initWave(waveDatas.value, time.value, type.value, severity.value, isOpen.value)
} else {
console.error('Worker error:', e.data.error)
loading.value = false loading.value = false
return
} }
} const cacheKey = pendingCacheKeys.get(data.requestId)
if (cacheKey) {
setWaveCache(cacheKey, data)
pendingCacheKeys.delete(data.requestId)
}
applyWorkerResult(data)
})
nextTick(() => { nextTick(() => {
setTimeout(() => { query()
query()
}, 500)
}) })
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (worker.value) {
worker.value.terminate()
}
backbxlb() backbxlb()
window.removeEventListener('resize', handleResize) window.removeEventListener('resize', handleResize)
}) })
const handleResize = () => { const handleResize = () => {
const zoomValue = document.body.style.getPropertyValue('zoom'); const zoomValue = document.body.style.getPropertyValue('zoom')
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1); zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1)
} }
const download = () => { const download = () => {
const boxr = document.getElementById('boxr') const boxr = document.getElementById('boxr')
if (boxr) { if (boxr) {
html2canvas(boxr, { html2canvas(boxr, {
scale: 2, scale: 2
}).then(function (canvas) { }).then(function (canvas) {
const creatIMg = document.createElement('a') const creatIMg = document.createElement('a')
creatIMg.download = 'rms波形.png' creatIMg.download = 'rms波形.png'
@@ -190,22 +210,15 @@ const download = () => {
} }
} }
const query = () => { const EXTRA_PANEL_CLASS = 'wave-extra-panel'
loading.value = true
if (props.wp) {
// 使用 Worker 处理数据
if (worker.value) {
worker.value.postMessage(JSON.stringify({ const resetWaveDom = () => {
wp: props.wp, backbxlb()
isOpen: isOpen.value, $('#rmsp').nextAll(`.${EXTRA_PANEL_CLASS}`).remove()
value: props.value, }
boxoList: props.boxoList
})) const query = () => {
} initWaves()
} else {
initWave(null, null, null, null, null)
}
} }
const waveData = ( const waveData = (
@@ -239,42 +252,57 @@ const waveData = (
} }
const initWaves = () => { const initWaves = () => {
if (props.wp) { if (!props.wp?.listRmsData?.length) {
iphasic.value = props.wp.iphasic || 1
const picCounts = (props.wp.waveTitle.length - 1) / iphasic.value
waveDatas.value = []
for (let i = 0; i < picCounts; i++) {
const data = fliteWaveData(props.wp, i)
waveDatas.value.push(data)
}
time.value = props.wp.time
type.value = props.wp.waveType
severity.value = props.wp.yzd
if (Number(severity.value) < 0) {
severity.value = '/'
type.value = '/'
}
initWave(waveDatas.value, time.value, type.value, severity.value, isOpen.value)
} else {
initWave(null, null, null, null, null) initWave(null, null, null, null, null)
loading.value = false
return
} }
const cacheKey = buildWaveCacheKey('rms', props.wp, props.value, isOpen.value, props.boxoList)
const cached = getWaveCache<any>(cacheKey)
if (cached) {
applyWorkerResult(cached)
return
}
loading.value = true
iphasic.value = props.wp.iphasic || 1
const currentRequestId = ++waveRequestId
pendingCacheKeys.set(currentRequestId, cacheKey)
rmsWorker?.postMessage(
buildWorkerPayload('rms', props.wp, props.boxoList, {
requestId: currentRequestId,
value: props.value,
isOpen: isOpen.value
})
)
} }
const fliteWaveData = (wp: any, step: number): WaveData => { const fliteWaveData = (wp: any, step: number): WaveData => {
const rmsData = wp.listRmsData const rmsData = wp.listRmsData
const pt = Number(wp.pt) / 1000 const pt = Number(wp.pt) / 1000
const ct = Number(wp.ct) const ct = Number(wp.ct)
const titleList = wp.waveTitle const titleList = wp.waveTitle
let xishu = pt let xishu = pt
let aTitle = '', bTitle = '', cTitle = '', unit = '电压' let aTitle = '',
let rmsvFirstX = 0, rmsvFirstY = 0, rmsvSecondX = 0, rmsvSecondY = 0, firstZhou = 'a', secondeZhou = 'a' bTitle = '',
let ifmax = 0, ifmin = 0, ismax = 0, ismin = 0, rfmax = 0, rfmin = 0, rsmax = 0, rsmin = 0 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: number[][] = [] const shunshiFA: number[][] = []
const shunshiFB: number[][] = [] const shunshiFB: number[][] = []
@@ -289,7 +317,6 @@ const fliteWaveData = (wp: any, step: number): WaveData => {
const rmsSB: number[][] = [] const rmsSB: number[][] = []
const rmsSC: number[][] = [] const rmsSC: number[][] = []
if (titleList[iphasic.value * step + 1]?.substring(0, 1) !== 'U') { if (titleList[iphasic.value * step + 1]?.substring(0, 1) !== 'U') {
xishu = ct xishu = ct
unit = '电流' unit = '电流'
@@ -309,8 +336,6 @@ const fliteWaveData = (wp: any, step: number): WaveData => {
} }
} }
if (rmsData[0][iphasic.value * step + 1] !== undefined) { if (rmsData[0][iphasic.value * step + 1] !== undefined) {
rfmax = rmsData[0][iphasic.value * step + 1] * xishu rfmax = rmsData[0][iphasic.value * step + 1] * xishu
rfmin = rmsData[0][iphasic.value * step + 1] * xishu rfmin = rmsData[0][iphasic.value * step + 1] * xishu
@@ -322,8 +347,6 @@ const fliteWaveData = (wp: any, step: number): WaveData => {
rmsvSecondX = rmsData[0][0] rmsvSecondX = rmsData[0][0]
} }
for (let rms = 0; rms < rmsData.length; rms++) { for (let rms = 0; rms < rmsData.length; rms++) {
if (rmsData[rms][iphasic.value * step + 1] === undefined) { if (rmsData[rms][iphasic.value * step + 1] === undefined) {
break break
@@ -388,12 +411,14 @@ const fliteWaveData = (wp: any, step: number): WaveData => {
const rmsFirstA3 = rmsData[rms][iphasic.value * step + 1] * xishu const rmsFirstA3 = rmsData[rms][iphasic.value * step + 1] * xishu
const rmsFirstB3 = rmsData[rms][iphasic.value * step + 2] * xishu const rmsFirstB3 = rmsData[rms][iphasic.value * step + 2] * xishu
const rmsFirstC3 = rmsData[rms][iphasic.value * step + 3] * xishu const rmsFirstC3 = rmsData[rms][iphasic.value * step + 3] * xishu
rmsFA.push([rmsData[rms][0], rmsFirstA3]); rmsFA.push([rmsData[rms][0], rmsFirstA3])
rmsFB.push([rmsData[rms][0], rmsFirstB3]); rmsFB.push([rmsData[rms][0], rmsFirstB3])
rmsFC.push([rmsData[rms][0], rmsFirstC3]); rmsFC.push([rmsData[rms][0], rmsFirstC3])
rfmax = getMax(rfmax, rmsFirstA3, rmsFirstB3, rmsFirstC3) rfmax = getMax(rfmax, rmsFirstA3, rmsFirstB3, rmsFirstC3)
rfmin = isOpen.value ? getMinOpen(rfmin, rmsFirstA3, rmsFirstC3) : getMin(rfmin, rmsFirstA3, rmsFirstB3, rmsFirstC3) rfmin = isOpen.value
? getMinOpen(rfmin, rmsFirstA3, rmsFirstC3)
: getMin(rfmin, rmsFirstA3, rmsFirstB3, rmsFirstC3)
if (rfmin < rmsvFirstY) { if (rfmin < rmsvFirstY) {
rmsvFirstY = rfmin rmsvFirstY = rfmin
if (rfmin === rmsFirstA3) { if (rfmin === rmsFirstA3) {
@@ -413,7 +438,9 @@ const fliteWaveData = (wp: any, step: number): WaveData => {
rmsSB.push([rmsData[rms][0], rmsSecondB3]) rmsSB.push([rmsData[rms][0], rmsSecondB3])
rmsSC.push([rmsData[rms][0], rmsSecondC3]) rmsSC.push([rmsData[rms][0], rmsSecondC3])
rsmax = getMax(rsmax, rmsSecondA3, rmsSecondB3, rmsSecondC3) rsmax = getMax(rsmax, rmsSecondA3, rmsSecondB3, rmsSecondC3)
rsmin = isOpen.value ? getMinOpen(rsmin, rmsSecondA3, rmsSecondC3) : getMin(rsmin, rmsSecondA3, rmsSecondB3, rmsSecondC3) rsmin = isOpen.value
? getMinOpen(rsmin, rmsSecondA3, rmsSecondC3)
: getMin(rsmin, rmsSecondA3, rmsSecondB3, rmsSecondC3)
if (rsmin < rmsvSecondY) { if (rsmin < rmsvSecondY) {
rmsvSecondY = rsmin rmsvSecondY = rsmin
if (rsmin === rmsSecondA3) { if (rsmin === rmsSecondA3) {
@@ -429,7 +456,6 @@ const fliteWaveData = (wp: any, step: number): WaveData => {
} }
} }
const instantF = { max: ifmax, min: ifmin } const instantF = { max: ifmax, min: ifmin }
const instantS = { max: ismax, min: ismin } const instantS = { max: ismax, min: ismin }
const RMSF = { max: rfmax, min: rfmin } const RMSF = { max: rfmax, min: rfmin }
@@ -442,12 +468,30 @@ const fliteWaveData = (wp: any, step: number): WaveData => {
const RMSSWave = { rmsSA, rmsSB, rmsSC } const RMSSWave = { rmsSA, rmsSB, rmsSC }
const title = { aTitle, bTitle, cTitle, unit } const title = { aTitle, bTitle, cTitle, unit }
return waveData(instantF, instantS, RMSF, RMSS, RMSFMinDetail, RMSSMinDetail, shunshiF, shunshiS, RMSFWave, RMSSWave, title, unit) return waveData(
instantF,
instantS,
RMSF,
RMSS,
RMSFMinDetail,
RMSSMinDetail,
shunshiF,
shunshiS,
RMSFWave,
RMSSWave,
title,
unit
)
} }
const initWave = (waveDatas: WaveData[] | null, time: string | null, type: string | null, severity: string | null, isOpen: boolean | null) => { const initWave = (
waveDatas: WaveData[] | null,
$('div.bx').remove() time: string | null,
type: string | null,
severity: string | null,
isOpen: boolean | null
) => {
resetWaveDom()
let picHeight = vh.value let picHeight = vh.value
const show = !isOpen const show = !isOpen
@@ -456,9 +500,15 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
let rmscm: number[][] = [] let rmscm: number[][] = []
let titleText = '' let titleText = ''
let unit = '' let unit = ''
let a: string | null = null, b: string | null = null, c: string | null = null let a: string | null = null,
let radata: number[][] = [], rbdata: number[][] = [], rcdata: number[][] = [] b: string | null = null,
let rmsvX: any = 0, rmsvY: any = 0, zhou = '' c: string | null = null
let radata: number[][] = [],
rbdata: number[][] = [],
rcdata: number[][] = []
let rmsvX: any = 0,
rmsvY: any = 0,
zhou = ''
if (!waveDatas) { if (!waveDatas) {
titleText = '该事件暂无波形图' titleText = '该事件暂无波形图'
@@ -543,10 +593,7 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
] ]
unit = 'kV' unit = 'kV'
} else { } else {
fz.value = [ fz.value = [props.wp.listRmsMinData[0][0], props.wp.listRmsMinData[0][1]]
props.wp.listRmsMinData[0][0],
props.wp.listRmsMinData[0][1]
]
unit = 'V' unit = 'V'
} }
} else { } else {
@@ -555,18 +602,46 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
for (let step = waveDatas.length - 1; step > 0 && step < waveDatas.length; step--) { for (let step = waveDatas.length - 1; step > 0 && step < waveDatas.length; step--) {
const rmsId = 'rms' + step const rmsId = 'rms' + step
const newDivRms = $(`<div style="height:${vh.value};overflow: hidden;"><div class='bx' id='${rmsId}'></div></div>`) const newDivRms = $(
`<div class="${EXTRA_PANEL_CLASS}" style="height:${vh.value};overflow: hidden;min-height: 200px;"><div class='bx' id='${rmsId}'></div></div>`
)
newDivRms.insertAfter($('#rmsp')) newDivRms.insertAfter($('#rmsp'))
$(`#${rmsId}`).css('height', picHeight).css('width', vw.value) $(`#${rmsId}`).css('height', picHeight).css('width', vw.value).css('min-height', '200px')
} }
} else { } else {
titleText = `变电站名称:${subName.value} 监测点名称:${lineName.value} 发生时刻:${time} 残余电压:${(Number(eventValue.value) * 1).toFixed(0)}% 持续时间:${persistTime.value}s` titleText = `变电站名称:${subName.value} 监测点名称:${lineName.value} 发生时刻:${time} 暂降(骤升)幅值:${(
Number(eventValue.value) * 1
).toFixed(0)}% 持续时间:${persistTime.value}s`
} }
const rms = document.getElementById('rmsp') const rms = document.getElementById('rmsp')
if (!rms) return if (!rms) return
const yRange = calcRmsYAxisRange(rmscu[0]?.[1] ?? 0, rmscm[0]?.[1] ?? 0)
const existingChart = echarts.getInstanceByDom(rms)
if (existingChart) existingChart.dispose()
const myChartes = echarts.init(rms) const myChartes = echarts.init(rms)
const echartsColor = { WordColor: "#fff", thread: "#fff", FigureColor: ["#07CCCA ", "#00BFF5", "#FFBF00", "#77DA63", "#D5FF6B", "#Ff6600", "#FF9100", "#5B6E96", "#66FFCC", "#B3B3B3", "#FF00FF", "#CC00FF", "#FF9999"] } const echartsColor = {
WordColor: '#000',
thread: '#000',
FigureColor: [
'#07CCCA ',
'#00BFF5',
'#FFBF00',
'#77DA63',
'#D5FF6B',
'#Ff6600',
'#FF9100',
'#5B6E96',
'#66FFCC',
'#B3B3B3',
'#FF00FF',
'#CC00FF',
'#FF9999'
]
}
setTimeout(() => { setTimeout(() => {
rms.style.width = '100%' rms.style.width = '100%'
@@ -582,7 +657,12 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
let tips = '时刻:' + params[0].data[0] + '</br/>' let tips = '时刻:' + params[0].data[0] + '</br/>'
for (let i = 0; i < params.length; i++) { for (let i = 0; i < params.length; i++) {
if (params[i].seriesName != '暂降触发点') { if (params[i].seriesName != '暂降触发点') {
tips += params[i].marker + params[i].seriesName + ':' + (params[i].value[1] - 0).toFixed(2) + '<br/>' tips +=
params[i].marker +
params[i].seriesName +
':' +
(params[i].value[1] - 0).toFixed(2) +
'<br/>'
} }
} }
return tips return tips
@@ -604,8 +684,8 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
align: 'left' align: 'left'
}, },
textStyle: { textStyle: {
fontSize: '0.8rem', fontSize: '16px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
} }
}, },
legend: { legend: {
@@ -615,8 +695,8 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
enabled: true, enabled: true,
itemDistance: 5, itemDistance: 5,
textStyle: { textStyle: {
fontSize: '0.7rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
rich: { a: { verticalAlign: 'middle' } }, rich: { a: { verticalAlign: 'middle' } },
padding: [0, 0, 0, 0] padding: [0, 0, 0, 0]
} }
@@ -641,13 +721,13 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
title: { title: {
text: 'ms', text: 'ms',
textStyle: { textStyle: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
}, },
enabled: true, enabled: true,
align: 'high' align: 'high'
}, },
nameTextStyle: { fontSize: '0.6rem' }, nameTextStyle: { fontSize: '12px' },
labels: { labels: {
formatter: function () { formatter: function () {
return this.value return this.value
@@ -657,13 +737,13 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
axisTick: { alignWithLabel: true }, axisTick: { alignWithLabel: true },
axisLine: { axisLine: {
lineStyle: { lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread color: props.DColor ? '#000' : echartsColor.thread
}, },
onZero: false onZero: false
}, },
axisLabel: { axisLabel: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) { formatter: function (value: number) {
if (valA.value != (value - 0).toFixed(0)) { if (valA.value != (value - 0).toFixed(0)) {
valA.value = Number((value - 0).toFixed(0)) valA.value = Number((value - 0).toFixed(0))
@@ -683,43 +763,45 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
rotation: 0, rotation: 0,
y: -10 y: -10
}, },
max: rmscm[0]?.[1] * 1.06 || 0, // max: rmscm[0]?.[1] * 1.06 || 0,
min: rmscu[0]?.[1] - (rmscu[0]?.[1] * 0.04) || 0, // min: rmscu[0]?.[1] - rmscu[0]?.[1] * 0.2 || 0,
max: yRange.max,
min: yRange.min,
boundaryGap: [0, '100%'], boundaryGap: [0, '100%'],
showLastLabel: true, showLastLabel: true,
opposite: false, opposite: false,
nameTextStyle: { nameTextStyle: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
}, },
axisLine: { axisLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread color: props.DColor ? '#000' : echartsColor.thread
}, },
onZero: false onZero: false
}, },
axisLabel: { axisLabel: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) { formatter: function (value: number) {
return (value - 0).toFixed(2) return formatAxisLabel(value)
} }
}, },
splitLine: { splitLine: {
lineStyle: { lineStyle: {
color: [props.DColor ? '#fff' : echartsColor.thread], color: [props.DColor ? '#000' : echartsColor.thread],
type: 'dashed', type: 'dashed',
opacity: 0.5 opacity: 0.5
} }
} }
}, },
grid: { grid: {
left: '1%', left: '60px',
right: '45px', right: '45px',
bottom: '40px', bottom: '40px',
top: '70px', top: '60px'
containLabel: true // containLabel: true
}, },
dataZoom: [ dataZoom: [
{ {
@@ -772,7 +854,7 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
data: rmscu data: rmscu
}, },
{ {
name: '最小残余电压', name: '最小暂降(骤升)幅值',
type: 'scatter', type: 'scatter',
symbol: 'image://' + url2, symbol: 'image://' + url2,
itemStyle: { width: 45, height: 45 }, itemStyle: { width: 45, height: 45 },
@@ -780,7 +862,7 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
show: true, show: true,
position: [5, -20], position: [5, -20],
color: '#000', color: '#000',
fontSize: '14px', fontSize: '12px',
fontWeight: 'bold', fontWeight: 'bold',
borderType: 'solid', borderType: 'solid',
borderWidth: 0.5, borderWidth: 0.5,
@@ -792,7 +874,6 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
] ]
} }
myChartes.setOption(option) myChartes.setOption(option)
myChartess.value = myChartes myChartess.value = myChartes
@@ -809,12 +890,25 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
} }
} }
const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show: boolean, myChartes1: echarts.ECharts, rmscm: number[][], rmscu: number[][], title: string) => { const drawPics = (
waveDataTemp: WaveData,
picHeight: string,
step: number,
show: boolean,
myChartes1: echarts.ECharts,
rmscm: number[][],
rmscu: number[][],
title: string
) => {
step = step + 1 step = step + 1
const rmsId = 'rms' + step const rmsId = 'rms' + step
let a = '', b = '', c = '' let a = '',
b = '',
c = ''
let unit = '' let unit = ''
let radata: number[][] = [], rbdata: number[][] = [], rcdata: number[][] = [] let radata: number[][] = [],
rbdata: number[][] = [],
rcdata: number[][] = []
switch (iphasic.value) { switch (iphasic.value) {
case 1: case 1:
@@ -876,8 +970,30 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
const rmsIds = document.getElementById(rmsId) const rmsIds = document.getElementById(rmsId)
if (!rmsIds) return if (!rmsIds) return
const subMin = props.value === 1 ? waveDataTemp.RMSF.min : waveDataTemp.RMSS.min
const subMax = props.value === 1 ? waveDataTemp.RMSF.max : waveDataTemp.RMSS.max
const yRange = calcRmsYAxisRange(subMin, subMax)
const myChartes = echarts.init(rmsIds) const myChartes = echarts.init(rmsIds)
const echartsColor = { WordColor: "#fff", thread: "#fff", FigureColor: ["#07CCCA ", "#00BFF5", "#FFBF00", "#77DA63", "#D5FF6B", "#Ff6600", "#FF9100", "#5B6E96", "#66FFCC", "#B3B3B3", "#FF00FF", "#CC00FF", "#FF9999"] } const echartsColor = {
WordColor: '#000',
thread: '#000',
FigureColor: [
'#07CCCA ',
'#00BFF5',
'#FFBF00',
'#77DA63',
'#D5FF6B',
'#Ff6600',
'#FF9100',
'#5B6E96',
'#66FFCC',
'#B3B3B3',
'#FF00FF',
'#CC00FF',
'#FF9999'
]
}
const option = { const option = {
tooltip: { tooltip: {
@@ -888,7 +1004,12 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
let tips = '时刻:' + params[0].data[0] + '</br/>' let tips = '时刻:' + params[0].data[0] + '</br/>'
for (let i = 0; i < params.length; i++) { for (let i = 0; i < params.length; i++) {
if (params[i].seriesName != '暂降触发点') { if (params[i].seriesName != '暂降触发点') {
tips += params[i].marker + params[i].seriesName + ':' + (params[i].value[1] - 0).toFixed(2) + '<br/>' tips +=
params[i].marker +
params[i].seriesName +
':' +
(params[i].value[1] - 0).toFixed(2) +
'<br/>'
} }
} }
return tips return tips
@@ -904,10 +1025,10 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
}, },
title: { title: {
left: 'center', left: 'center',
text: '',//titlename || title, text: '', //titlename || title,
textStyle: { textStyle: {
fontSize: '0.8rem', fontSize: '16px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
} }
}, },
legend: { legend: {
@@ -917,8 +1038,8 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
enabled: true, enabled: true,
itemDistance: 5, itemDistance: 5,
textStyle: { textStyle: {
fontSize: '0.7rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
rich: { a: { verticalAlign: 'middle' } }, rich: { a: { verticalAlign: 'middle' } },
padding: [0, 0, 0, 0] padding: [0, 0, 0, 0]
} }
@@ -932,8 +1053,8 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
title: { title: {
text: 'ms', text: 'ms',
textStyle: { textStyle: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
}, },
enabled: true, enabled: true,
align: 'high' align: 'high'
@@ -943,18 +1064,18 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
return this.value return this.value
} }
}, },
nameTextStyle: { fontSize: '0.6rem' }, nameTextStyle: { fontSize: '12px' },
splitLine: { show: false }, splitLine: { show: false },
axisTick: { alignWithLabel: true }, axisTick: { alignWithLabel: true },
axisLine: { axisLine: {
lineStyle: { lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread color: props.DColor ? '#000' : echartsColor.thread
}, },
onZero: false onZero: false
}, },
axisLabel: { axisLabel: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) { formatter: function (value: number) {
if (valB.value != (value - 0).toFixed(0)) { if (valB.value != (value - 0).toFixed(0)) {
valB.value = Number((value - 0).toFixed(0)) valB.value = Number((value - 0).toFixed(0))
@@ -976,39 +1097,43 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
}, },
boundaryGap: [0, '100%'], boundaryGap: [0, '100%'],
showLastLabel: true, showLastLabel: true,
max: yRange.max,
min: yRange.min,
opposite: false, opposite: false,
// max: Math.floor((rmscm[0]?.[1] * 1.06 || 0) * 1.1 * 10) / 10,
// min: Math.floor((rmscu[0]?.[1] - rmscu[0]?.[1] * 0.2 || 0) * 10) / 10,
nameTextStyle: { nameTextStyle: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
}, },
axisLine: { axisLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread color: props.DColor ? '#000' : echartsColor.thread
}, },
onZero: false onZero: false
}, },
axisLabel: { axisLabel: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) { formatter: function (value: number) {
return (value - 0).toFixed(2) return formatAxisLabel(value)
} }
}, },
splitLine: { splitLine: {
lineStyle: { lineStyle: {
color: [props.DColor ? '#fff' : echartsColor.thread], color: [props.DColor ? '#000' : echartsColor.thread],
type: 'dashed', type: 'dashed',
opacity: 0.5 opacity: 0.5
} }
} }
}, },
grid: { grid: {
left: '1%', left: '60px',
right: '45px', right: '45px',
bottom: '40px', bottom: '40px',
top: '70px', top: '60px'
containLabel: true // containLabel: true
}, },
dataZoom: [ dataZoom: [
{ {
@@ -1059,11 +1184,21 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
myChartes.setOption(option) myChartes.setOption(option)
switch (step) { switch (step) {
case 1: myChartess1.value = myChartes; break case 1:
case 2: myChartess2.value = myChartes; break myChartess1.value = myChartes
case 3: myChartess3.value = myChartes; break break
case 4: myChartess4.value = myChartes; break case 2:
case 5: myChartess5.value = myChartes; break myChartess2.value = myChartes
break
case 3:
myChartess3.value = myChartes
break
case 4:
myChartess4.value = myChartes
break
case 5:
myChartess5.value = myChartes
break
} }
setTimeout(() => { setTimeout(() => {
@@ -1076,7 +1211,14 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
const backbxlb = () => { const backbxlb = () => {
waveDatas.value = [] waveDatas.value = []
const charts = [myChartess.value, myChartess1.value, myChartess2.value, myChartess3.value, myChartess4.value, myChartess5.value] const charts = [
myChartess.value,
myChartess1.value,
myChartess2.value,
myChartess3.value,
myChartess4.value,
myChartess5.value
]
charts.forEach(chart => { charts.forEach(chart => {
if (chart) { if (chart) {
@@ -1091,14 +1233,7 @@ const backbxlb = () => {
myChartess4.value = null myChartess4.value = null
myChartess5.value = null myChartess5.value = null
// echarts.disconnect(charts.filter(Boolean) as echarts.ECharts[]) $('#rmsp').nextAll(`.${EXTRA_PANEL_CLASS}`).remove()
charts
.filter(Boolean)
.forEach(chart => {
if (chart && typeof chart.dispose === 'function') {
chart.dispose();
}
});
} }
const getMax = (temp: number, tempA: number, tempB: number, tempC: number): number => { const getMax = (temp: number, tempA: number, tempB: number, tempC: number): number => {
@@ -1126,4 +1261,4 @@ const getMinOpen = (temp: number, tempA: number, tempB: number): number => {
temp = temp < tempB ? temp : tempB temp = temp < tempB ? temp : tempB
return temp return temp
} }
</script> </script>

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,9 +1,8 @@
<template> <template>
<div v-loading="loading" :element-loading-background="'rgba(122, 122, 122, 0.8)'" class="boxbx" <div v-loading="loading" class="boxbx" style="position: relative; height: 100%">
style="position: relative;height: 100%;"> <div id="boxsj">
<div id="boxsj" style="background: #343849c7"> <div id="shushi" :style="containerStyle">
<div id="shushi" :style="`height:${vh};overflow: hidden;`"> <div class="bx" id="wave" :style="waveStyle"></div>
<div class="bx" id="wave"></div>
</div> </div>
</div> </div>
</div> </div>
@@ -14,9 +13,15 @@ import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import html2canvas from 'html2canvas' import html2canvas from 'html2canvas'
import $ from 'jquery' import $ from 'jquery'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { mainHeight } from '@/utils/layout'
import { calcShuYAxisRange, formatAxisLabel } from '@/utils/chartAxisHelper'
import url from '@/assets/img/point.png' import url from '@/assets/img/point.png'
// 创建Worker import { buildWaveCacheKey, getWaveCache, setWaveCache } from '@/utils/waveCache'
let waveDataWorker: Worker | null = null; import { getShuWorker, buildWorkerPayload } from '@/utils/waveWorkerPool'
let waveRequestId = 0
const pendingCacheKeys = new Map<number, string>()
let shuWorker: Worker | null = null
interface WaveData { interface WaveData {
instantF: { max: number; min: number } instantF: { max: number; min: number }
instantS: { max: number; min: number } instantS: { max: number; min: number }
@@ -42,7 +47,7 @@ interface WaveData {
interface Props { interface Props {
value?: number value?: number
flag?: string | number | boolean flag?: string | number | boolean
parentHeight?: string | number | boolean parentHeight?: any
DColor?: boolean DColor?: boolean
boxoList?: any boxoList?: any
wp?: any wp?: any
@@ -87,60 +92,119 @@ const myChartess4 = ref<echarts.ECharts | null>(null)
const myChartess5 = ref<echarts.ECharts | null>(null) const myChartess5 = ref<echarts.ECharts | null>(null)
const vh = computed(() => { const vh = computed(() => {
if (props.flag == 1) { if (props.parentHeight == 999) {
return '690px' return `calc((60vh - 150px) / 2 )`
} else if (props.parentHeight != 0) { } else if (props.parentHeight != 0) {
return '310px' return mainHeight(props.parentHeight, 2).height
} else {
return '350px'
} }
if (props.flag == 1) {
return mainHeight(200).height
} else {
return mainHeight(200, 2).height
}
// if (props.flag == 1) {
// return '690px'
// } else if (props.parentHeight != 0) {
// return '310px'
// } else {
// return '350px'
// }
}) })
const vw = computed(() => '100%') const vw = computed(() => '100%')
watch(() => props.value, (newVal) => { const containerStyle = computed(() => ({
if (newVal == 2) { height: vh.value,
initWaves() overflow: 'hidden',
} else { minHeight: '200px'
$('#wave1').remove() }))
initWaves()
const waveStyle = computed(() => ({
width: '100%',
height: '100%',
minHeight: '200px'
}))
const applyChartSize = (el: HTMLElement) => {
el.style.width = '100%'
el.style.height = vh.value
}
const finishChartRender = (chart: echarts.ECharts, endLoading = false) => {
nextTick(() => {
chart.resize()
if (endLoading) {
loading.value = false
}
})
}
watch(
() => props.value,
() => {
query()
} }
}) )
watch(
() => props.wp,
() => {
query()
}
)
const applyWorkerResult = (data: any) => {
titles.value = data.titles
iphasic.value = data.iphasic
time.value = data.time
type.value = data.type
severity.value = data.severity
initWave(data.waveDatas, data.time, data.type, data.severity, isOpen.value)
loading.value = false
}
onMounted(() => { onMounted(() => {
const zoomValue = document.body.style.getPropertyValue('zoom'); const zoomValue = document.body.style.getPropertyValue('zoom')
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1); zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1)
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
shuWorker = getShuWorker(data => {
if (data.requestId !== waveRequestId) return
if (!data.success) {
console.error('Worker error:', data.error)
loading.value = false
return
}
const cacheKey = pendingCacheKeys.get(data.requestId)
if (cacheKey) {
setWaveCache(cacheKey, data)
pendingCacheKeys.delete(data.requestId)
}
applyWorkerResult(data)
})
nextTick(() => { nextTick(() => {
setTimeout(() => { query()
query()
}, 500)
}) })
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
console.log('组件卸载'); backbxlb()
window.removeEventListener('resize', handleResize)
if (waveDataWorker) {
waveDataWorker.terminate();
waveDataWorker = null;
}
backbxlb();
window.removeEventListener('resize', handleResize);
}) })
const handleResize = () => { const handleResize = () => {
const zoomValue = document.body.style.getPropertyValue('zoom'); const zoomValue = document.body.style.getPropertyValue('zoom')
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1); zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1)
} }
const download = () => { const download = () => {
const boxsj = document.getElementById('boxsj') const boxsj = document.getElementById('boxsj')
if (boxsj) { if (boxsj) {
html2canvas(boxsj, { html2canvas(boxsj, {
scale: 2, scale: 2
}).then(function (canvas) { }).then(function (canvas) {
const creatIMg = document.createElement('a') const creatIMg = document.createElement('a')
creatIMg.download = '瞬间波形.png' creatIMg.download = '瞬间波形.png'
@@ -151,6 +215,13 @@ const download = () => {
} }
} }
const EXTRA_PANEL_CLASS = 'wave-extra-panel'
const resetWaveDom = () => {
backbxlb()
$('#shushi').nextAll(`.${EXTRA_PANEL_CLASS}`).remove()
}
const query = () => { const query = () => {
loading.value = true loading.value = true
initWaves() initWaves()
@@ -169,50 +240,42 @@ const waveData = (instantF: any, instantS: any, shunshiF: any, shunshiS: any, ti
// 在组件中修改initWaves函数 // 在组件中修改initWaves函数
const initWaves = () => { const initWaves = () => {
if (props.wp) { if (!props.wp?.listWaveData?.length) {
loading.value = true; initWave(null, null, null, null, null)
iphasic.value = props.wp.iphasic || 1 loading.value = false
// 使用Web Worker处理数据 return
if (!waveDataWorker) {
waveDataWorker = new Worker(new URL('./shuWorker.js', import.meta.url));
waveDataWorker.onmessage = function (e) {
const data = e.data;
titles.value = data.titles;
iphasic.value = data.iphasic;
time.value = data.time;
type.value = data.type;
severity.value = data.severity;
initWave(data.waveDatas, data.time, data.type, data.severity, isOpen.value);
loading.value = false;
};
waveDataWorker.onerror = function (error) {
console.error('Worker error:', error);
loading.value = false;
// 备用方案:在主线程处理数据
// processDataInMainThread();
};
}
// 发送数据到Worker
waveDataWorker.postMessage(JSON.stringify({
wp: props.wp,
value: props.value,
iphasic: iphasic.value,
isOpen: isOpen.value,
boxoList: props.boxoList
}));
} else {
initWave(null, null, null, null, null);
} }
const cacheKey = buildWaveCacheKey('shu', props.wp, props.value, isOpen.value, props.boxoList)
const cached = getWaveCache<any>(cacheKey)
if (cached) {
applyWorkerResult(cached)
return
}
loading.value = true
iphasic.value = props.wp.iphasic || 1
const currentRequestId = ++waveRequestId
pendingCacheKeys.set(currentRequestId, cacheKey)
shuWorker?.postMessage(
buildWorkerPayload('shu', props.wp, props.boxoList, {
requestId: currentRequestId,
value: props.value,
isOpen: isOpen.value,
iphasic: iphasic.value
})
)
} }
const initWave = (
const initWave = (waveDatas: WaveData[] | null, time: string | null, type: string | null, severity: string | null, isOpen: boolean | null) => { waveDatas: WaveData[] | null,
time: string | null,
type: string | null,
severity: string | null,
isOpen: boolean | null
) => {
resetWaveDom()
$('div.bx1').remove() $('div.bx1').remove()
let picHeight = vh.value let picHeight = vh.value
@@ -221,9 +284,14 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
let cu: number[][] = [] let cu: number[][] = []
let titleText = '' let titleText = ''
let unit = '' let unit = ''
let max: any = 0, min: any = 0 let max: any = 0,
let a: string | null = null, b: string | null = null, c: string | null = null min: any = 0
let adata: number[][] = [], bdata: number[][] = [], cdata: number[][] = [] let a: string | null = null,
b: string | null = null,
c: string | null = null
let adata: number[][] = [],
bdata: number[][] = [],
cdata: number[][] = []
const colors: string[] = [] const colors: string[] = []
if (!waveDatas) { if (!waveDatas) {
@@ -249,7 +317,7 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
min = waveDatas[0].instantS.min min = waveDatas[0].instantS.min
adata = waveDatas[0].shunshiS.shunshiSA adata = waveDatas[0].shunshiS.shunshiSA
} }
colors.push('#DAA520', '#fff', '#fff') colors.push('#DAA520', '#000', '#000')
break break
case 2: case 2:
a = waveDatas[0].title.aTitle a = waveDatas[0].title.aTitle
@@ -267,7 +335,7 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
adata = waveDatas[0].shunshiS.shunshiSA adata = waveDatas[0].shunshiS.shunshiSA
bdata = waveDatas[0].shunshiS.shunshiSB bdata = waveDatas[0].shunshiS.shunshiSB
} }
colors.push('#DAA520', '#2E8B57', '#fff') colors.push('#DAA520', '#2E8B57', '#000')
break break
case 3: case 3:
a = waveDatas[0].title.aTitle a = waveDatas[0].title.aTitle
@@ -300,26 +368,48 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
for (let step = waveDatas.length - 1; step > 0 && step < waveDatas.length; step--) { for (let step = waveDatas.length - 1; step > 0 && step < waveDatas.length; step--) {
const waveId = 'wave' + step const waveId = 'wave' + step
const newDivShunshi = $(`<div style="height:${vh.value};overflow: hidden;"> const newDivShunshi = $(`<div class="${EXTRA_PANEL_CLASS}" style="height:${vh.value};overflow: hidden;min-height: 200px;">
<div class='bx1' id='${waveId}'></div> <div class='bx1' id='${waveId}'></div>
</div>`) </div>`)
newDivShunshi.insertAfter($('#shushi')) newDivShunshi.insertAfter($('#shushi'))
$(`#${waveId}`).css('height', picHeight).css('width', vw.value) $(`#${waveId}`).css('height', picHeight).css('width', vw.value).css('min-height', '200px')
} }
} else { } else {
titleText = `变电站名称:${subName.value} 监测点名称:${lineName.value} 发生时刻:${time} 残余电压:${(Number(eventValue.value) * 1).toFixed(0)}% 持续时间:${persistTime.value}s` titleText = `变电站名称:${subName.value} 监测点名称:${lineName.value} 发生时刻:${time} 暂降(骤升)幅值:${(
Number(eventValue.value) * 1
).toFixed(0)}% 持续时间:${persistTime.value}s`
} }
const wave = document.getElementById('wave') const wave = document.getElementById('wave')
if (!wave) return if (!wave) return
const myChartes = echarts.init(wave) applyChartSize(wave)
const echartsColor = { WordColor: "#fff", thread: "#fff", FigureColor: ["#07CCCA ", "#00BFF5", "#FFBF00", "#77DA63", "#D5FF6B", "#Ff6600", "#FF9100", "#5B6E96", "#66FFCC", "#B3B3B3", "#FF00FF", "#CC00FF", "#FF9999"] }
setTimeout(() => { const yRange = calcShuYAxisRange(Number(min), Number(max))
wave.style.width = '100%'
wave.style.height = vh.value const existingChart = echarts.getInstanceByDom(wave)
}, 0) if (existingChart) existingChart.dispose()
const myChartes = echarts.init(wave)
const echartsColor = {
WordColor: '#000',
thread: '#000',
FigureColor: [
'#07CCCA ',
'#00BFF5',
'#FFBF00',
'#77DA63',
'#D5FF6B',
'#Ff6600',
'#FF9100',
'#5B6E96',
'#66FFCC',
'#B3B3B3',
'#FF00FF',
'#CC00FF',
'#FF9999'
]
}
const option = { const option = {
tooltip: { tooltip: {
@@ -335,7 +425,12 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
let tips = '时刻:' + params[0].data[0] + '</br/>' let tips = '时刻:' + params[0].data[0] + '</br/>'
for (let i = 0; i < params.length; i++) { for (let i = 0; i < params.length; i++) {
if (params[i].seriesName != '暂降触发点') { if (params[i].seriesName != '暂降触发点') {
tips += params[i].marker + params[i].seriesName + ':' + (params[i].value[1] - 0).toFixed(2) + '<br/>' tips +=
params[i].marker +
params[i].seriesName +
':' +
(params[i].value[1] - 0).toFixed(2) +
'<br/>'
} }
} }
return tips return tips
@@ -345,7 +440,7 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
fontStyle: 'normal', fontStyle: 'normal',
opacity: 0.35, opacity: 0.35,
fontSize: 14 fontSize: 14
}, },
backgroundColor: 'rgba(0,0,0,0.55)', backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0 borderWidth: 0
}, },
@@ -353,8 +448,8 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
left: 'center', left: 'center',
text: titleText, text: titleText,
textStyle: { textStyle: {
fontSize: '0.8rem', fontSize: '16px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
} }
}, },
legend: { legend: {
@@ -364,8 +459,8 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
enabled: true, enabled: true,
itemDistance: 5, itemDistance: 5,
textStyle: { textStyle: {
fontSize: '0.6rem', fontSize: 12,
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
rich: { a: { verticalAlign: 'middle' } }, rich: { a: { verticalAlign: 'middle' } },
padding: [0, 0, 0, 0] padding: [0, 0, 0, 0]
} }
@@ -390,24 +485,24 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
title: { title: {
text: 'ms', text: 'ms',
textStyle: { textStyle: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
}, },
enabled: true, enabled: true,
align: 'high' align: 'high'
}, },
splitLine: { show: false }, splitLine: { show: false },
nameTextStyle: { fontSize: '0.6rem' }, nameTextStyle: { fontSize: '12px' },
axisTick: { alignWithLabel: true }, axisTick: { alignWithLabel: true },
axisLine: { axisLine: {
lineStyle: { lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread color: props.DColor ? '#000' : echartsColor.thread
}, },
onZero: false onZero: false
}, },
axisLabel: { axisLabel: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) { formatter: function (value: number) {
if (valA.value != (value - 0).toFixed(0)) { if (valA.value != (value - 0).toFixed(0)) {
valA.value = Number((value - 0).toFixed(0)) valA.value = Number((value - 0).toFixed(0))
@@ -429,41 +524,40 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
}, },
boundaryGap: [0, '100%'], boundaryGap: [0, '100%'],
showLastLabel: true, showLastLabel: true,
max: max.toFixed(2) * 1.1, max: yRange.max,
min: min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1, min: yRange.min,
opposite: false, opposite: false,
nameTextStyle: { nameTextStyle: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
}, },
axisLine: { axisLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread color: props.DColor ? '#000' : echartsColor.thread
}, },
onZero: false onZero: false
}, },
axisLabel: { axisLabel: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) { formatter: function (value: number) {
return (value - 0).toFixed(2) return formatAxisLabel(value)
} }
}, },
splitLine: { splitLine: {
lineStyle: { lineStyle: {
color: [props.DColor ? '#fff' : echartsColor.thread], color: [props.DColor ? '#000' : echartsColor.thread],
type: 'dashed', type: 'dashed',
opacity: 0.5 opacity: 0.5
} }
} }
}, },
grid: { grid: {
left: '1%', left: '60px',
right: '2.8%', right: '45px',
bottom: '40px', bottom: '40px',
top: '70px', top: '60px'
containLabel: true
}, },
dataZoom: [ dataZoom: [
{ {
@@ -523,11 +617,7 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
myChartes.setOption(option) myChartes.setOption(option)
myChartess.value = myChartes myChartess.value = myChartes
finishChartRender(myChartes, true)
setTimeout(() => {
myChartes.resize()
loading.value = false
}, 400)
if (waveDatas && waveDatas.length > 1) { if (waveDatas && waveDatas.length > 1) {
const waveDatasTemp = waveDatas.slice(1) const waveDatasTemp = waveDatas.slice(1)
@@ -537,23 +627,36 @@ const initWave = (waveDatas: WaveData[] | null, time: string | null, type: strin
} }
} }
const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show: boolean, myChartes1: echarts.ECharts, title: string) => { const drawPics = (
waveDataTemp: WaveData,
picHeight: any,
step: number,
show: boolean,
myChartes1: echarts.ECharts,
title: string
) => {
step = step + 1 step = step + 1
const waveId = 'wave' + step const waveId = 'wave' + step
let a: string | null = null, b: string | null = null, c: string | null = null let a: string | null = null,
let max: any = 0, min: any = 0, unit = '' b: string | null = null,
let adata: number[][] = [], bdata: number[][] = [], cdata: number[][] = [] c: string | null = null
let max: any = 0,
min: any = 0,
unit = ''
let adata: number[][] = [],
bdata: number[][] = [],
cdata: number[][] = []
const colors: string[] = [] const colors: string[] = []
switch (iphasic.value) { switch (iphasic.value) {
case 1: case 1:
a = waveDataTemp.title.aTitle a = waveDataTemp.title.aTitle
colors.push('#DAA520', '#fff', '#fff') colors.push('#DAA520', '#000', '#000')
break break
case 2: case 2:
a = waveDataTemp.title.aTitle a = waveDataTemp.title.aTitle
b = waveDataTemp.title.bTitle b = waveDataTemp.title.bTitle
colors.push('#DAA520', '#2E8B57', '#fff') colors.push('#DAA520', '#2E8B57', '#000')
break break
case 3: case 3:
a = waveDataTemp.title.aTitle a = waveDataTemp.title.aTitle
@@ -611,8 +714,33 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
const waveIds = document.getElementById(waveId) const waveIds = document.getElementById(waveId)
if (!waveIds) return if (!waveIds) return
applyChartSize(waveIds)
const yRange = calcShuYAxisRange(Number(min), Number(max))
const existingChart = echarts.getInstanceByDom(waveIds)
if (existingChart) existingChart.dispose()
const myChartes = echarts.init(waveIds) const myChartes = echarts.init(waveIds)
const echartsColor = { WordColor: "#fff", thread: "#fff", FigureColor: ["#07CCCA ", "#00BFF5", "#FFBF00", "#77DA63", "#D5FF6B", "#Ff6600", "#FF9100", "#5B6E96", "#66FFCC", "#B3B3B3", "#FF00FF", "#CC00FF", "#FF9999"] } const echartsColor = {
WordColor: '#000',
thread: '#000',
FigureColor: [
'#07CCCA ',
'#00BFF5',
'#FFBF00',
'#77DA63',
'#D5FF6B',
'#Ff6600',
'#FF9100',
'#5B6E96',
'#66FFCC',
'#B3B3B3',
'#FF00FF',
'#CC00FF',
'#FF9999'
]
}
const option = { const option = {
tooltip: { tooltip: {
@@ -638,10 +766,10 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
}, },
title: { title: {
left: 'center', left: 'center',
text: '',//titlename || title, text: '', //titlename || title,
textStyle: { textStyle: {
fontSize: '0.8rem', fontSize: '16px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
} }
}, },
legend: { legend: {
@@ -651,8 +779,8 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
enabled: true, enabled: true,
itemDistance: 5, itemDistance: 5,
textStyle: { textStyle: {
fontSize: '0.6rem', fontSize: 12,
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
rich: { a: { verticalAlign: 'middle' } }, rich: { a: { verticalAlign: 'middle' } },
padding: [0, 0, 0, 0] padding: [0, 0, 0, 0]
} }
@@ -666,8 +794,8 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
title: { title: {
text: 'ms', text: 'ms',
textStyle: { textStyle: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
}, },
enabled: true, enabled: true,
align: 'high' align: 'high'
@@ -676,14 +804,14 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
axisTick: { alignWithLabel: true }, axisTick: { alignWithLabel: true },
axisLine: { axisLine: {
lineStyle: { lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread color: props.DColor ? '#000' : echartsColor.thread
}, },
onZero: false onZero: false
}, },
nameTextStyle: { fontSize: '0.6rem' }, nameTextStyle: { fontSize: '12px' },
axisLabel: { axisLabel: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) { formatter: function (value: number) {
if (valB.value != (value - 0).toFixed(0)) { if (valB.value != (value - 0).toFixed(0)) {
valB.value = Number((value - 0).toFixed(0)) valB.value = Number((value - 0).toFixed(0))
@@ -705,41 +833,41 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
}, },
boundaryGap: [0, '100%'], boundaryGap: [0, '100%'],
showLastLabel: true, showLastLabel: true,
max: max.toFixed(2) * 1.1, max: yRange.max,
min: min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1, min: yRange.min,
opposite: false, opposite: false,
nameTextStyle: { nameTextStyle: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
}, },
axisLine: { axisLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread color: props.DColor ? '#000' : echartsColor.thread
}, },
onZero: false onZero: false
}, },
axisLabel: { axisLabel: {
fontSize: '0.6rem', fontSize: '12px',
color: props.DColor ? '#fff' : echartsColor.WordColor, color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) { formatter: function (value: number) {
return (value - 0).toFixed(2) return formatAxisLabel(value)
} }
}, },
splitLine: { splitLine: {
lineStyle: { lineStyle: {
color: [props.DColor ? '#fff' : echartsColor.thread], color: [props.DColor ? '#000' : echartsColor.thread],
type: 'dashed', type: 'dashed',
opacity: 0.5 opacity: 0.5
} }
} }
}, },
grid: { grid: {
left: '1%', left: '60px',
right: '2.8%', right: '45px',
bottom: '40px', bottom: '40px',
top: '70px', top: '60px'
containLabel: true // containLabel: true
}, },
dataZoom: [ dataZoom: [
{ {
@@ -793,24 +921,38 @@ const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show:
myChartes.setOption(option) myChartes.setOption(option)
switch (step) { switch (step) {
case 1: myChartess1.value = myChartes; break case 1:
case 2: myChartess2.value = myChartes; break myChartess1.value = myChartes
case 3: myChartess3.value = myChartes; break break
case 4: myChartess4.value = myChartes; break case 2:
case 5: myChartess5.value = myChartes; break myChartess2.value = myChartes
break
case 3:
myChartess3.value = myChartes
break
case 4:
myChartess4.value = myChartes
break
case 5:
myChartess5.value = myChartes
break
} }
setTimeout(() => { finishChartRender(myChartes)
myChartes.resize()
loading.value = false
}, 400)
echarts.connect([myChartes1, myChartes]) echarts.connect([myChartes1, myChartes])
} }
const backbxlb = () => { const backbxlb = () => {
waveDatas.value = [] waveDatas.value = []
const charts = [myChartess.value, myChartess1.value, myChartess2.value, myChartess3.value, myChartess4.value, myChartess5.value] const charts = [
myChartess.value,
myChartess1.value,
myChartess2.value,
myChartess3.value,
myChartess4.value,
myChartess5.value
]
charts.forEach(chart => { charts.forEach(chart => {
if (chart) { if (chart) {
@@ -825,14 +967,8 @@ const backbxlb = () => {
myChartess4.value = null myChartess4.value = null
myChartess5.value = null myChartess5.value = null
// echarts.disconnect(charts.filter(Boolean) as echarts.ECharts[]) $('#shushi').nextAll(`.${EXTRA_PANEL_CLASS}`).remove()
charts $('div.bx1').remove()
.filter(Boolean)
.forEach(chart => {
if (chart && typeof chart.dispose === 'function') {
chart.dispose();
}
});
} }
const getMax = (temp: number, tempA: number, tempB: number, tempC: number): number => { const getMax = (temp: number, tempA: number, tempB: number, tempC: number): number => {
@@ -860,4 +996,4 @@ const getMinOpen = (temp: number, tempA: number, tempB: number): number => {
temp = temp < tempB ? temp : tempB temp = temp < tempB ? temp : tempB
return temp return temp
} }
</script> </script>

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
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"
@@ -20,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>
@@ -35,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'])
@@ -89,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) {
@@ -113,9 +126,13 @@ const checkInitialButtonStatus = () => {
const endTime = timeValue.value[1] const endTime = timeValue.value[1]
const currentDate = window.XEUtils.toDateString(new Date(), 'yyyy-MM-dd') const currentDate = window.XEUtils.toDateString(new Date(), 'yyyy-MM-dd')
// 如果结束时间小于当前日期,则不禁用"下一个"按钮 // 只有当 props.nextFlag 为 false 时才应用限制
if (new Date(endTime + ' 00:00:00').getTime() < new Date(currentDate + ' 00:00:00').getTime()) { if (!props.nextFlag) {
preDisabled.value = false // 如果结束时间早于当前日期则按钮可用preDisabled = false
// 如果结束时间晚于或等于当前日期则按钮禁用preDisabled = true
const endDateTime = new Date(endTime).getTime()
const currentDateTime = new Date(currentDate).getTime()
preDisabled.value = endDateTime >= currentDateTime
} }
} }
} }
@@ -164,30 +181,15 @@ const timeChange = (e: number) => {
timeFlag.value = 1 timeFlag.value = 1
} }
// 检查按钮状态 nextTick(() => {
checkButtonStatus() // 检查按钮状态
checkInitialButtonStatus()
})
// 触发 change 事件 // 触发 change 事件
emitChange() emitChange()
} }
// 添加按钮状态检查方法
const checkButtonStatus = () => {
if (timeValue.value && timeValue.value.length >= 2) {
const endTime = timeValue.value[1]
const currentDate = window.XEUtils.toDateString(new Date(), 'yyyy-MM-dd')
// 如果结束时间大于等于当前日期,且 nextFlag 为 false则禁用"下一个"按钮
if (!props.nextFlag) {
if (new Date(endTime + ' 00:00:00').getTime() >= new Date(currentDate + ' 00:00:00').getTime()) {
preDisabled.value = true
} else {
preDisabled.value = false
}
}
}
}
// 当前 // 当前
const nowTime = () => { const nowTime = () => {
// console.log(interval.value, '000000000') // console.log(interval.value, '000000000')

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"
@@ -15,6 +15,7 @@
:nextFlag="nextFlag" :nextFlag="nextFlag"
:theCurrentTime="theCurrentTime" :theCurrentTime="theCurrentTime"
@change="handleDatePickerChange" @change="handleDatePickerChange"
:timeKeyList="props.timeKeyList"
></DatePicker> ></DatePicker>
</el-form-item> </el-form-item>
@@ -28,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"
> >
@@ -88,6 +104,8 @@ interface Props {
theCurrentTime?: boolean //控制时间前3天展示上个月时间 theCurrentTime?: boolean //控制时间前3天展示上个月时间
showReset?: boolean //是否显示重置 showReset?: boolean //是否显示重置
showExport?: boolean //导出控制 showExport?: boolean //导出控制
timeCacheFlag?: boolean //是否取缓存时间
timeKeyList?: string[] //日期下拉列表
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@@ -97,7 +115,9 @@ const props = withDefaults(defineProps<Props>(), {
nextFlag: false, nextFlag: false,
theCurrentTime: true, theCurrentTime: true,
showReset: true, showReset: true,
showExport: false showExport: false,
timeCacheFlag: true,
timeKeyList: () => ['1', '2', '3', '4', '5'] // 修改为箭头函数返回空数组
}) })
// 处理 DatePicker 值变化事件 // 处理 DatePicker 值变化事件
@@ -134,7 +154,7 @@ onMounted(() => {
if (props.datePicker && datePickerRef.value) { if (props.datePicker && datePickerRef.value) {
// 从缓存中获取值并设置 // 从缓存中获取值并设置
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)
} }

View File

@@ -10,10 +10,11 @@
@checkbox-all="selectChangeEvent" @checkbox-all="selectChangeEvent"
@checkbox-change="selectChangeEvent" @checkbox-change="selectChangeEvent"
:showOverflow="showOverflow" :showOverflow="showOverflow"
:sort-config="{ remote: true }"
@sort-change="handleSortChange"
> >
<!-- :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" />

View File

@@ -0,0 +1,149 @@
<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" v-if="showBut">
<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="18px"
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="20px"
class="fold ml10 menu-collapse"
style="cursor: pointer"
:style="{ color: config.getColorVal('elementUiPrimary') }"
@click="onAdd"
/>
</el-tooltip>
</div>
<el-tree
:style="{ height: `calc(100vh - ${height}px)` }"
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: nodeData }">
<span class="custom-tree-node">
<Icon
:name="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.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 { ref, watch } from 'vue'
import { useConfig } from '@/stores/config'
import { createTreeFilterNode } from './govern/treeFilterUtils'
defineOptions({ name: 'govern/allocation', inheritAttrs: false })
interface Props {
width?: string
canExpand?: boolean
showPush?: boolean
showBut?: boolean
height?: number
}
const props = withDefaults(defineProps<Props>(), {
width: '280px',
canExpand: true,
showPush: false,
showBut: true,
height: 267
})
const emit = defineEmits(['checkTreeNodeChange', 'onAdd', 'checkChange'])
const config = useConfig()
const { proxy } = useCurrentInstance()
const menuCollapse = ref(false)
const filterText = ref('')
const loading = ref(false)
const defaultProps = { label: 'name', value: 'id' }
const filterNode = createTreeFilterNode()
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 checkTreeNodeChange = () => {
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

@@ -1,27 +1,43 @@
<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="18px"
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>
<!-- <Icon @click='onMenuCollapse' :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-tree :style="{ height: 'calc(100vh - 110px)' }" <el-tree
style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current :default-expand-all="false" :style="{ height: 'calc(100vh - 230px)' }"
@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: nodeData }">
<span class="custom-tree-node">
<Icon
:name="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
@@ -29,15 +45,13 @@
</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 { createTreeFilterNode } from './govern/treeFilterUtils'
defineOptions({ defineOptions({ name: 'govern/cloudDevice', inheritAttrs: false })
name: 'govern/tree'
})
interface Props { interface Props {
width?: string width?: string
@@ -48,66 +62,31 @@ const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true canExpand: true
}) })
const emit = defineEmits(['checkTreeNodeChange'])
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const filterText = ref('') const filterText = ref('')
const defaultProps = { const defaultProps = { label: 'name', value: 'id' }
label: 'name', const filterNode = createTreeFilterNode()
value: 'id'
} watch(filterText, val => treeRef.value?.filter(val))
const emit = defineEmits(['checkTreeNodeChange'])
watch(filterText, val => {
treeRef.value!.filter(val)
})
const onMenuCollapse = () => { const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse) proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
} }
const filterNode = (value: string, data: any, node: any) => {
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 = () => { const checkTreeNodeChange = () => {
// console.log(treeRef.value?.getCheckedNodes(), "ikkkkkiisiiisis");
emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes()) emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes())
} }
const treeRef = ref<InstanceType<typeof ElTree>>() 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;

View File

@@ -1,305 +1,385 @@
<!-- 设备管理使用折叠面板渲染多个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="18px"
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-input
<el-input maxlength="32" show-word-limit v-model.trim="filterText" autocomplete="off" maxlength="32"
placeholder="请输入内容" clearable> 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> -->
<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="18px"
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="activeName"
<el-collapse-item title="治理设备" name="0" v-if="zlDeviceData.length != 0"> style="flex: 1; height: 100%"
<el-select v-model.trim="process" clearable placeholder="请选择状态" class="mb10"> @change="changeDevice"
<el-option label="功能调试" value="2"></el-option> v-if="treeType == '1'"
<el-option label="出厂调试" value="3"></el-option> v-loading="loading"
<el-option label="正式投运" value="4"></el-option> >
<el-collapse-item title="治理设备" name="0" v-if="zlDeviceData.length">
<el-select v-model="process" clearable placeholder="请选择状态" class="mb10">
<el-option label="功能调试" value="2" />
<el-option label="出厂调试" value="3" />
<el-option label="正式投运" value="4" />
</el-select> </el-select>
<el-tree <el-tree
:style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 278px)' }" :style="{ height: governTreeHeight }"
ref="treeRef1" :props="defaultProps" highlight-current :filter-node-method="filterNode" ref="treeRef1"
node-key="id" :default-expand-all="false" v-bind="$attrs" :data="zlDevList" style="overflow: auto"> :props="defaultProps"
<template #default="{ node, data }"> 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: nodeData }">
<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="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.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="1" v-if="bxsDeviceData.length != 0"> <el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 280px)' : 'calc(100vh - 238px)' }" :style="{ height: otherTreeHeight }"
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
<template #default="{ node, data }"> :default-expand-all="false"
:filter-node-method="filterNode"
node-key="id"
:data="bxsDeviceData"
v-bind="$attrs"
style="overflow: auto"
>
<template #default="{ node, data: nodeData }">
<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="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.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">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 280px)' : 'calc(100vh - 238px)' }" :style="{ height: otherTreeHeight }"
ref="treeRef3" :props="defaultProps" highlight-current :default-expand-all="false" ref="treeRef3"
:filter-node-method="filterNode" node-key="id" :data="frontDeviceData" v-bind="$attrs" :props="defaultProps"
style="overflow: auto"> highlight-current
<template #default="{ node, data }"> :default-expand-all="false"
:filter-node-method="filterNode"
node-key="id"
:data="frontDeviceData"
v-bind="$attrs"
style="overflow: auto"
>
<template #default="{ node, data: nodeData }">
<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="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.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: engineeringTreeHeight }"
ref="treeRef4"
:props="defaultProps"
highlight-current
:filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
:data="props.data"
style="overflow: auto"
:default-expand-all="false"
>
<template #default="{ node, data: nodeData }">
<span class="custom-tree-node">
<Icon
:name="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</div> </div>
</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, type CollapseModelValue } from 'element-plus'
import { ref, watch, defineEmits, onMounted, nextTick } from 'vue' import { ref, watch, onMounted, nextTick, computed } from 'vue'
import { collectDeviceLeaves } from './govern/lineTreeUtils'
import { collectDeviceApiLeaves } from './govern/deviceTreeUtils'
defineOptions({ defineOptions({
name: 'govern/tree' name: 'govern/tree',
inheritAttrs: false
}) })
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
/** line: getLineTree 四层叶子device: getDeviceTree 三层叶子 */
leafMode?: 'line' | 'device'
} }
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,
leafMode: 'line'
}) })
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')
const filterText = ref('') const filterText = ref('')
const defaultProps = {
label: 'name',
value: 'id'
}
const process = ref('') const process = ref('')
//治理设备数据 const loading = ref(false)
const zlDeviceData = ref([])
const zlDevList = ref<any>([]) const defaultProps = { label: 'name', value: 'id' }
//便携式设备数据
const bxsDeviceData = ref([]) const zlDeviceData = ref<any[]>([])
//前置设备数据 const zlDevList = ref<any[]>([])
const frontDeviceData = ref([]) const bxsDeviceData = ref<any[]>([])
const frontDeviceData = ref<any[]>([])
const governTreeHeight = computed(() => `calc(100vh - 380px - ${props.height}px)`)
const otherTreeHeight = computed(() =>
zlDeviceData.value.length ? `calc(100vh - 340px - ${props.height}px)` : `calc(100vh - 238px - ${props.height}px)`
)
const engineeringTreeHeight = computed(() => `calc(100vh - 188px - ${props.height}px)`)
const treeRef1 = ref<InstanceType<typeof ElTree>>()
const treeRef2 = ref<InstanceType<typeof ElTree>>()
const treeRef3 = ref<InstanceType<typeof ElTree>>()
const treeRef4 = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef1, treeRef2, treeRef3, treeRef4 })
function splitTreeData(val: any[]) {
zlDeviceData.value = []
bxsDeviceData.value = []
frontDeviceData.value = []
val.forEach(item => {
if (item.name === '治理设备') {
zlDeviceData.value = item.children ?? []
} else if (item.name === '便携式设备') {
bxsDeviceData.value = item.children ?? []
} else if (item.name === '监测设备') {
frontDeviceData.value = item.children ?? []
}
})
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
}
function filterProcess(nodes: any[]): any[] {
if (!process.value) return nodes
return nodes
.map(node => {
const children = node.children ? filterProcess(node.children) : []
if (node.process == process.value || children.length > 0) {
return { ...node, children }
}
return null
})
.filter(Boolean)
}
function getActiveTreeRef() {
if (treeType.value === '2') return treeRef4.value
if (activeName.value === '0') return treeRef1.value
if (activeName.value === '1') return treeRef2.value
return treeRef3.value
}
function resolveActiveName() {
if (zlDeviceData.value.length) return '0'
if (bxsDeviceData.value.length) return '1'
if (frontDeviceData.value.length) return '2'
return ''
}
function selectDevicePanel(panelName: string, node?: any) {
if (!node) return
emit('changeDeviceType', panelName, node)
nextTick(() => {
getActiveTreeRef()?.setCurrentKey(node.id)
})
}
const changeDevice = (val: CollapseModelValue) => {
if (Array.isArray(val) || val == null || val === '') return
const panelName = String(val)
const collectLeaves = props.leafMode === 'device' ? collectDeviceApiLeaves : collectDeviceLeaves
const { govern, portable, monitor } = collectLeaves(
zlDevList.value,
bxsDeviceData.value,
frontDeviceData.value
)
const panelMap: Record<string, { nodes: any[]; clearOthers: any[][] }> = {
'0': { nodes: govern, clearOthers: [portable, monitor] },
'1': { nodes: portable, clearOthers: [govern, monitor] },
'2': { nodes: monitor, clearOthers: [govern, portable] }
}
const panel = panelMap[panelName]
if (!panel) return
panel.clearOthers.forEach(list => list.forEach(item => (item.checked = false)))
selectDevicePanel(panelName, panel.nodes[0])
}
const setActiveName = () => {
activeName.value = resolveActiveName()
if (activeName.value) {
nextTick(() => changeDevice(activeName.value))
}
}
watch( watch(
() => props.data, () => props.data,
(val, oldVal) => { val => {
if (val && val.length != 0) { if (!val?.length) return
val.map((item: any) => { splitTreeData(val)
if (item.name == '治理设备') { if (treeType.value === '1') {
zlDeviceData.value = [] nextTick(() => setActiveName())
item.children.map((vv: any) => {
zlDeviceData.value.push(vv)
})
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else if (item.name == '便携式设备') {
bxsDeviceData.value = []
item.children.map((vv: any) => {
bxsDeviceData.value.push(vv)
})
}else if (item.name == '在线设备') {
frontDeviceData.value = []
item.children.map((vv: any) => {
frontDeviceData.value.push(vv)
})
}
})
} }
}, },
{ { immediate: true, deep: true }
immediate: true,
deep: true
}
) )
watch(filterText, val => { watch(filterText, val => {
if (activeName.value == '0') { getActiveTreeRef()?.filter(val)
treeRef1.value!.filter(val) })
} else if (activeName.value == '1') {
treeRef2.value!.filter(val) watch(process, () => {
} else { zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
treeRef3.value!.filter(val) if (activeName.value === '0') {
nextTick(() => changeDevice(activeName.value))
} }
}) })
watch(process, val => {
if (val == '') {
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
}
setTimeout(() => {
changeDevice(activeName.value)
}, 0)
})
function filterProcess(nodes: any) {
if (process.value == '') {
return nodes
}
return nodes
.map(node => {
// 递归处理子节点
const children = node.children ? filterProcess(node.children) : []
// 如果当前节点的process=4或者有子节点满足条件则保留当前节点
if ( node.process == process.value || children.length > 0) {
return {
...node,
children: children
}
}
// 否则过滤掉当前节点
return null
})
.filter(Boolean) // 移除null节点
}
const onMenuCollapse = () => { const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value menuCollapse.value = !menuCollapse.value
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): boolean => {
if (!value) return true if (!value) return true
// return data.name.includes(value) if (!data.name) return false
if (data.name) { return chooseNode(value, data, node)
return chooseNode(value, data, node)
}
} }
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符. const chooseNode = (value: string, data: any, node: any): boolean => {
const chooseNode = (value: string, data: any, node: any) => { if (data.name.indexOf(value) !== -1) return true
if (data.name.indexOf(value) !== -1) {
return true
}
const level = node.level const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了 if (level === 1) return false
if (level === 1) {
return false
}
// 先取当前节点的父节点
let parentData = node.parent let parentData = node.parent
// 遍历当前节点的父节点 for (let i = 0; i < level - 1; i++) {
let index = 0 if (parentData?.data?.name?.indexOf(value) !== -1) return true
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true
}
// 否则的话再往上一层做匹配
parentData = parentData.parent parentData = parentData.parent
index++
} }
// 没匹配到返回false
return false return false
} }
const changeDevice = (val: any) => { const changeTreeType = (val: string) => {
console.log('changeDevice', val) loading.value = true
let arr1: any = [] emit('changeTreeType', val)
if (val === '1') {
//zlDeviceData nextTick(() => setActiveName())
zlDevList.value.forEach((item: any) => {
item.children.forEach((item2: any) => {
item2.children.forEach((item3: any) => {
arr1.push(item3)
})
})
})
let arr2: any = []
bxsDeviceData.value.forEach((item: any) => {
// item.children.forEach((item2: any) => {
arr2.push(item)
// })
})
let arr3: any = []
frontDeviceData.value.forEach((item: any) => {
item.children.forEach((item2: any) => {
item2.children.forEach((item3: any) => {
arr3.push(item3)
})
})
})
if (val == '0') {
arr2.map((item: any) => {
item.checked = false
})
treeRef1.value && treeRef1.value.setCurrentKey(arr1[0]?.id)
emit('changeDeviceType', activeName.value, arr1[0])
} }
if (val == '1') {
arr1.map((item: any) => {
item.checked = false
})
treeRef2.value && treeRef2.value.setCurrentKey(arr2[0]?.id)
emit('changeDeviceType', activeName.value, arr2[0])
}
if (val == '2') {
arr3.map((item: any) => {
item.checked = false
})
treeRef3.value && treeRef3.value.setCurrentKey(arr3[0]?.id)
emit('changeDeviceType', activeName.value, arr3[0])
}
}
//治理
const treeRef1 = ref<InstanceType<typeof ElTree>>()
//便携式
const treeRef2 = ref<InstanceType<typeof ElTree>>()
//前置
const treeRef3 = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef1, treeRef2 })
onMounted(() => {
setTimeout(() => { setTimeout(() => {
if (zlDeviceData.value.length != 0) { loading.value = false
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value))) }, 300)
activeName.value = '0' }
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length != 0) { onMounted(() => {
activeName.value = '1' treeType.value = props.engineering ? '2' : '1'
}
if (!zlDeviceData.value && !bxsDeviceData.value) {
activeName.value = ''
}
nextTick(() => {
changeDevice(activeName.value)
})
}, 500)
}) })
</script> </script>
@@ -330,4 +410,10 @@ onMounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
} }
:deep(.el-input-group__prepend) {
background-color: var(--el-fill-color-blank);
}
:deep(.is-disabled) {
display: none !important;
}
</style> </style>

View File

@@ -0,0 +1,205 @@
<template>
<Tree ref="treRef" @check="handleCheck" @check-change="handleCheckChange" :default-checked-keys="defaultCheckedKeys" check-strictly
:show-checkbox="props.showCheckbox" :data="tree" :height="props.height" @changeDeviceType="changeDeviceType"
@changeTreeType="loadTree" :engineering="props.engineering" />
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted } from 'vue'
import Tree from '../device.vue'
import { getLineTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config'
import {
createLineTreeDecorators,
decorateLineTree,
type LineTreeLeaves
} from './lineTreeUtils'
defineOptions({
name: 'govern/analyzeTree'
})
const props = withDefaults(
defineProps<{
showCheckbox?: boolean
defaultCheckedKeys?: any
height?: number
engineering?: boolean
}>(),
{
showCheckbox: false,
defaultCheckedKeys: () => [],
height: 0,
engineering: false
}
)
const emit = defineEmits(['init', 'checkChange', 'check', 'deviceTypeChange'])
const config = useConfig()
const tree = ref<any[]>([])
const treRef = ref<InstanceType<typeof Tree>>()
const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const changeDeviceType = (val: any, obj: any) => {
emit('deviceTypeChange', val, obj)
}
type TreeRefKey = 'treeRef1' | 'treeRef2' | 'treeRef3' | 'treeRef4'
async function waitForTreeRef(refKey: TreeRefKey, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await nextTick()
if (treRef.value?.[refKey]) return treRef.value[refKey]
await new Promise(resolve => setTimeout(resolve, 50))
}
return null
}
async function selectInitialNode(
type: string | undefined,
leaves: LineTreeLeaves
) {
const candidates: { refKey: TreeRefKey; list: any[]; level: number }[] =
type === '2'
? [{ refKey: 'treeRef4', list: leaves.engineering, level: 3 }]
: [
{ refKey: 'treeRef1', list: leaves.govern, level: 2 },
{ refKey: 'treeRef2', list: leaves.portable, level: 2 },
{ refKey: 'treeRef3', list: leaves.monitor, level: 2 }
]
for (const { refKey, list, level } of candidates) {
const node = list[0]
if (!node) continue
const treeInstance = await waitForTreeRef(refKey)
treeInstance?.setCurrentKey(node.id)
emit('init', { ...node })
return
}
emit('init')
}
const loadTree = (type?: string) => {
getLineTree({ type: type === '2' ? 'engineering' : '' }).then(res => {
const leaves = decorateLineTree(res.data, type, decorators)
tree.value = res.data
selectInitialNode(type, leaves)
})
}
onMounted(() => {
loadTree(props.engineering ? '2' : '1')
})
const handleCheck = (data: any, state: any) => {
emit('check', data, state)
}
const handleCheckChange = (data: any, checked: any, indeterminate: any) => {
emit('checkChange', { data, checked, indeterminate })
}
defineExpose({ treRef })
</script>

View File

@@ -1,174 +1,81 @@
<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 } from 'vue'
import Tree from '../index.vue' import Tree from '../index.vue'
import { getLineTree,getCldTree } from '@/api/cs-device-boot/csLedger' import { 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'
import { createLineTreeDecorators } from './lineTreeUtils'
import { decorateCloudTree } from './deviceTreeUtils'
import { bootstrapWithTemplate, selectTreeNode } from './treeCommonUtils'
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({
name: 'govern/deviceTree'
}) })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy','onAdd']) defineOptions({ name: 'govern/cloudDeviceEntryTree' })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy', 'onAdd'])
const config = useConfig() const config = useConfig()
const tree = ref()
const dictData = useDictData() const dictData = useDictData()
const treRef = ref() const tree = ref<any[]>([])
const treRef = ref<InstanceType<typeof Tree>>()
const width = ref('') const width = ref('')
const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const info = (selectedNodeId?: string) => { const changePointType = (_val: any, obj: any) => {
emit('pointTypeChange', _val, obj)
}
const onAdd = () => emit('onAdd')
async function loadTree() {
tree.value = [] tree.value = []
let arr1: any[] = [] const res = await getCldTree()
getCldTree().then(res => { const leaves = decorateCloudTree(res.data, decorators)
try { tree.value = [res.data]
// 检查响应数据结构
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;
}
// 治理设备 const node = leaves[0]
if (rootData) { if (!node) {
rootData.icon = 'el-icon-Menu' emit('init')
rootData.level = 0 return
rootData.color = config.getColorVal('elementUiPrimary') }
// 确保根节点的 children 是数组
if (!Array.isArray(rootData.children)) { await selectTreeNode(treRef.value, node, {
rootData.children = [] level: 3,
} onSelect: selected => {
rootData.children.forEach((item: any) => { emit('init', { ...selected, level: 3 })
item.icon = 'el-icon-HomeFilled' changePointType('4', selected)
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)
} }
}) })
} }
bootstrapWithTemplate(
props.template,
loadTree,
() => querySysExcel({ id: dictData.state.area[0]?.id }),
data => emit('Policy', data)
)
const changePointType = (val: any, obj: any) => { defineExpose({ info: loadTree })
emit('pointTypeChange', val, obj)
}
const onAdd = () => {
emit('onAdd')
}
if (props.template) {
getTemplateByDept({ id: dictData.state.area[0].id })
.then((res: any) => {
emit('Policy', res.data)
info()
})
.catch(err => {
info()
})
} else {
info()
}
// 暴露 info 方法给父组件调用
defineExpose({
info
})
onMounted(() => {})
</script> </script>

View File

@@ -0,0 +1,70 @@
<template>
<Tree ref="treRef" :width="width" :height="height" :showPush="props.showPush" :data="tree" default-expand-all
@changePointType="changePointType" @onAdd="onAdd" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import Tree from '../index.vue'
import { objTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config'
import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData'
import { createLineTreeDecorators } from './lineTreeUtils'
import { decorateObjTree } from './deviceTreeUtils'
import { bootstrapWithTemplate, selectTreeNode } from './treeCommonUtils'
interface Props {
template?: boolean
showPush?: boolean
height?: number
}
const props = withDefaults(defineProps<Props>(), {
template: false,
showPush: false,
height: 0
})
defineOptions({ name: 'govern/cloudDeviceEntryTreeZL' })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy', 'onAdd'])
const config = useConfig()
const dictData = useDictData()
const tree = ref<any[]>([])
const treRef = ref<InstanceType<typeof Tree>>()
const width = ref('')
const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const changePointType = (val: any, obj: any) => emit('pointTypeChange', val, obj)
const onAdd = () => emit('onAdd')
async function loadTree() {
tree.value = []
const res = await objTree()
const leaves = decorateObjTree(res.data, decorators)
tree.value = res.data
const node = leaves[0]
if (!node) {
emit('init')
return
}
await selectTreeNode(treRef.value, node, {
onSelect: selected => emit('init', selected)
})
}
bootstrapWithTemplate(
props.template,
loadTree,
() => querySysExcel({ id: dictData.state.area[0]?.id }),
data => emit('Policy', data)
)
defineExpose({ info: loadTree })
</script>

View File

@@ -0,0 +1,80 @@
<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 } from 'vue'
import Tree from '../index.vue'
import { lineTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config'
import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { createLineTreeDecorators } from './lineTreeUtils'
import { decorateLedgerLineTree, resolveMonitorRoot } from './deviceTreeUtils'
import { bootstrapWithTemplate, findNodeById, selectTreeNode } from './treeCommonUtils'
interface Props {
template?: boolean
showPush?: boolean
}
const props = withDefaults(defineProps<Props>(), {
template: false,
showPush: false
})
defineOptions({ name: 'govern/csLedgerLineTree' })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy', 'onAdd'])
const config = useConfig()
const tree = ref<any[]>([])
const treRef = ref<InstanceType<typeof Tree>>()
const width = ref('')
const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const changePointType = (val: any, obj: any) => emit('pointTypeChange', val, obj)
const onAdd = () => emit('onAdd')
async function loadTree(selectedNodeId?: string) {
tree.value = []
const res = await lineTree()
const rootData = resolveMonitorRoot(res.data)
const leaves = decorateLedgerLineTree(rootData, decorators)
tree.value = rootData ? [rootData] : []
if (!leaves.length) {
emit('init')
return
}
const targetNode = selectedNodeId
? findNodeById(tree.value, selectedNodeId) ?? leaves[0]
: leaves[0]
await selectTreeNode(treRef.value, targetNode, {
onSelect: selected =>
emit('init', {
level: selected.level ?? 2,
...selected
})
})
}
bootstrapWithTemplate(
props.template,
() => loadTree(),
() => querySysExcel({}),
data => emit('Policy', data)
)
defineExpose({ info: loadTree })
</script>

View File

@@ -1,107 +1,64 @@
<template> <template>
<Tree <Tree
ref="treRef" ref="treRef"
@checkTreeNodeChange="handleCheckChange" @checkTreeNodeChange="handleCheckChange"
:default-checked-keys="defaultCheckedKeys" :default-checked-keys="defaultCheckedKeys"
:show-checkbox="props.showCheckbox" :show-checkbox="props.showCheckbox"
:data="tree" :data="tree"
/> />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, defineProps } from 'vue' import { ref } from 'vue'
import Tree from '../index.vue' import Tree from '../index.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'
defineOptions({ import { createLineTreeDecorators } from './lineTreeUtils'
name: 'govern/deviceTree' import { decorateDeviceInfoTree } from './deviceTreeUtils'
}) import { selectTreeNode } from './treeCommonUtils'
const props = withDefaults(
defineProps<{ defineOptions({ name: 'govern/deviceInfoTree' })
showCheckbox?: boolean
defaultCheckedKeys?: any const props = withDefaults(
}>(), defineProps<{
{ showCheckbox?: boolean
showCheckbox: false, defaultCheckedKeys?: any[]
defaultCheckedKeys: [] }>(),
} {
) showCheckbox: false,
defaultCheckedKeys: () => []
const emit = defineEmits(['init', 'checkChange']) }
const config = useConfig() )
const tree = ref()
const treRef = ref() const emit = defineEmits(['init', 'checkChange'])
getDeviceTree().then(res => {
return const config = useConfig()
let arr: any[] = [] const tree = ref<any[]>([])
res.data.forEach((item: any) => { const treRef = ref<InstanceType<typeof Tree>>()
item.icon = 'el-icon-HomeFilled' const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => { async function initTree(list: any[]) {
item2.icon = 'el-icon-List' const leaves = decorateDeviceInfoTree(list, decorators)
item2.color = config.getColorVal('elementUiPrimary') tree.value = list
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform' const node = leaves[0]
item3.color = config.getColorVal('elementUiPrimary') if (!node) {
if (item3.comFlag === 1) { emit('init')
item3.color = '#e26257 !important' return
} }
arr.push(item3)
}) await selectTreeNode(treRef.value, node, {
}) onSelect: selected => emit('init', { ...selected, level: 2 })
}) })
tree.value = res.data }
nextTick(() => {
if (arr.length) { getDeviceTree().then(res => initTree(res.data))
treRef.value.treeRef.setCurrentKey(arr[0].id)
// 注册父组件事件 const getTreeList = (list: any[]) => initTree(list)
emit('init', {
level: 2, const handleCheckChange = (data: any) => {
...arr[0] emit('checkChange', { data })
}) }
} else {
emit('init') defineExpose({ getTreeList })
} </script>
})
})
const getTreeList = (list: any) => {
let arr: any[] = []
list.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.icon = 'el-icon-Platform'
item3.color = config.getColorVal('elementUiPrimary')
if (item3.comFlag === 1) {
item3.color = '#e26257 !important'
item3.color = item3.comFlag == 3 ? '#e26257 !important' : config.getColorVal('elementUiPrimary')
}
arr.push(item3)
})
})
})
tree.value = list
nextTick(() => {
if (arr.length) {
treRef.value.treeRef.setCurrentKey(arr[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr[0]
})
} else {
emit('init')
}
})
}
//接收tree选择的数据后传递给父级组件
const handleCheckChange = (data: any) => {
emit('checkChange', {
data
})
}
defineExpose({ getTreeList })
</script>

View File

@@ -1,151 +1,81 @@
<template> <template>
<Tree <Tree ref="treRef" @check-change="handleCheckChange" :default-checked-keys="defaultCheckedKeys"
ref="treRef" :show-checkbox="props.showCheckbox" :data="tree" :height="props.height" @changeDeviceType="changeDeviceType"
@check-change="handleCheckChange" @changeTreeType="loadTree" :engineering="props.engineering" leaf-mode="device" />
:default-checked-keys="defaultCheckedKeys"
:show-checkbox="props.showCheckbox"
:data="tree"
@changeDeviceType="changeDeviceType"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, defineEmits } from 'vue' import { ref, onMounted } from 'vue'
import { throttle } from 'lodash'
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'
defineOptions({ import { waitForTreeRef, type TreeRefKey } from './lineTreeUtils'
name: 'govern/deviceTree' import { createLineTreeDecorators } from './lineTreeUtils'
}) import { decorateDeviceTree } from './deviceTreeUtils'
import type { LineTreeLeaves } from './lineTreeUtils'
defineOptions({ name: 'govern/deviceTree' })
const props = withDefaults( 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'])
const config = useConfig() const config = useConfig()
const tree = ref() const tree = ref<any[]>([])
const treRef = ref() const treRef = ref<InstanceType<typeof Tree>>()
const changeDeviceType = (val: any, obj: any) => { const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
console.log("🚀 ~ changeDeviceType ~ val:", val,obj)
emit('deviceTypeChange', val, obj) const changeDeviceType = (val: any, obj: any) => emit('deviceTypeChange', val, obj)
async function selectInitialNode(type: string | undefined, leaves: LineTreeLeaves) {
const candidates: { refKey: TreeRefKey; list: any[]; level: number }[] =
type === '2'
? [{ refKey: 'treeRef4', list: leaves.engineering, level: 2 }]
: [
{ refKey: 'treeRef1', list: leaves.govern, level: 2 },
{ refKey: 'treeRef2', list: leaves.portable, level: 2 },
{ refKey: 'treeRef3', list: leaves.monitor, level: 2 }
]
for (const { refKey, list, level } of candidates) {
const node = list[0]
if (!node) continue
const treeInstance = await waitForTreeRef(treRef.value, refKey)
treeInstance?.setCurrentKey(node.id)
emit('init', { ...node })
return
}
emit('init')
} }
getDeviceTree().then(res => {
let arr: any[] = []
let arr2: any[] = []
let arr3: any[] = []
//治理设备
res.data.map((item: any) => {
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.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') { const loadTree = (type?: string) => {
arr2.push(item) getDeviceTree({ type: type === '2' ? 'engineering' : '' }).then(res => {
} const leaves = decorateDeviceTree(res.data, type, decorators)
item.children.forEach((item2: any) => { tree.value = res.data
item2.icon = 'el-icon-Platform' selectInitialNode(type, leaves)
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.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.icon = 'el-icon-Platform'
item3.color = config.getColorVal('elementUiPrimary')
if (item3.comFlag === 1) {
item3.color = '#e26257 !important'
}
arr3.push(item3)
})
})
})
}
})
console.log("🚀 ~ file: deviceTree.vue ~ line 18 ~ getDeviceTree ~ tree:", arr,arr2,arr3)
tree.value = res.data
nextTick(() => {
if (arr.length) {
treRef.value.treeRef1.setCurrentKey(arr[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr[0]
})
return
}
if (arr2.length) {
treRef.value.treeRef2.setCurrentKey(arr2[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr2[0]
})
return
}
console.log("🚀 ~ file: deviceTree.vue ~ line 33 ~ getDeviceTree ~ tree:", arr3.length)
if (arr3.length) {
console.log("🚀 ~ file: deviceTree.vue ~ line 33 ~ getDeviceTree ~ tree:", arr3)
treRef.value.treeRef3.setCurrentKey(arr3[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr3[0]
})
return
}
else {
emit('init')
return
}
})
})
const handleCheckChange = (data: any, checked: any, indeterminate: any) => {
emit('checkChange', {
data,
checked,
indeterminate
}) })
} }
onMounted(() => loadTree(props.engineering ? '2' : '1'))
const handleCheckChange =
(data: any, checked: any, indeterminate: any) => {
emit('checkChange', { data, checked, indeterminate })
}
defineExpose({ treRef })
</script> </script>

View File

@@ -0,0 +1,224 @@
import type { LineTreeDecorators, LineTreeLeaves } from './lineTreeUtils'
/** getDeviceTree 接口专用装饰(与 getLineTree 层级不同) */
export function decorateDeviceTree(
data: any[],
type: string | undefined,
decorators: LineTreeDecorators
): LineTreeLeaves {
const leaves: LineTreeLeaves = { govern: [], portable: [], monitor: [], engineering: [] }
const { primary, statusColor, applyMeta } = decorators
data.forEach(item => {
if (type === '2') {
applyMeta(item, { icon: 'el-icon-HomeFilled', color: primary() })
item.children?.forEach((child: any) => {
applyMeta(child, { icon: 'el-icon-List', color: primary() })
child.children?.forEach((grand: any) => {
applyMeta(grand, {
icon: 'el-icon-Platform',
color: statusColor(grand.comFlag),
level: 3
})
leaves.engineering.push(grand)
})
})
return
}
if (item.name === '治理设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary() })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary() })
l2.children?.forEach((l3: any) => {
l3.pName = '治理设备'
applyMeta(l3, {
icon: 'el-icon-Platform',
level: 2,
color: l3.comFlag === 1 ? '#e26257 !important' : primary()
})
leaves.govern.push(l3)
})
})
})
} else if (item.name === '便携式设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, {
icon: 'el-icon-Platform',
color: statusColor(l1.comFlag)
})
l1.pName = '便携式设备'
if (l1.type === 'device') {
leaves.portable.push(l1)
}
l1.children?.forEach((l2: any) => {
applyMeta(l2, {
icon: 'el-icon-Platform',
color: statusColor(l2.comFlag)
})
l2.pName = '便携式设备'
})
})
} else if (item.name === '监测设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary() })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary() })
l2.children?.forEach((l3: any) => {
l3.pName = '监测设备'
applyMeta(l3, {
icon: 'el-icon-Platform',
level: 3,
color: l3.comFlag === 1 ? '#e26257 !important' : primary()
})
leaves.monitor.push(l3)
})
})
})
}
})
return leaves
}
/** 装饰 getDeviceTree 扁平列表deviceInfoTree */
export function decorateDeviceInfoTree(list: any[], decorators: LineTreeDecorators): any[] {
const { primary, applyMeta } = decorators
const leaves: any[] = []
list.forEach(item => {
applyMeta(item, { icon: 'el-icon-HomeFilled', color: primary() })
item.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary() })
l2.children?.forEach((l3: any) => {
applyMeta(l3, {
icon: 'el-icon-Platform',
color: l3.comFlag === 1 || l3.comFlag === 3 ? '#e26257 !important' : primary()
})
leaves.push(l3)
})
})
})
return leaves
}
/** 装饰云端设备树 getCldTree */
export function decorateCloudTree(root: any, decorators: LineTreeDecorators): any[] {
const { primary, statusColor, applyMeta } = decorators
const leaves: any[] = []
applyMeta(root, { icon: 'el-icon-Menu', color: primary() })
root.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary() })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary() })
l2.children?.forEach((l3: any) => {
applyMeta(l3, {
icon: 'el-icon-Platform',
level: 2,
color: statusColor(l3.comFlag)
})
leaves.push(l3)
})
})
})
return leaves
}
/** 装饰 objTree治理对象树 */
export function decorateObjTree(data: any[], decorators: LineTreeDecorators): any[] {
const { primary, applyMeta } = decorators
const leaves: any[] = []
data.forEach(l1 => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary(), level: 1 })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary(), level: 2 })
l2.children?.forEach((l3: any) => {
applyMeta(l3, { icon: 'el-icon-Platform', color: primary(), level: 3 })
leaves.push(l3)
})
})
})
return leaves
}
/** 装饰 lineTree 台账线路树(监测设备根节点) */
export function decorateLedgerLineTree(root: any, decorators: LineTreeDecorators): any[] {
const { primary, statusColor, applyMeta } = decorators
const leaves: any[] = []
if (!root) return leaves
applyMeta(root, { icon: 'el-icon-Menu', color: primary(), level: 0 })
if (!Array.isArray(root.children)) root.children = []
root.children.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary(), level: 1 })
if (!Array.isArray(l1.children)) l1.children = []
l1.children.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary(), level: 2 })
if (!Array.isArray(l2.children)) l2.children = []
l2.children.forEach((l3: any) => {
applyMeta(l3, {
icon: 'el-icon-Platform',
color: statusColor(l3.comFlag),
level: 3
})
if (!Array.isArray(l3.children)) l3.children = []
l3.children.forEach((l4: any) => {
applyMeta(l4, {
icon: 'el-icon-Platform',
color: statusColor(l4.comFlag),
level: 4
})
leaves.push(l4)
})
})
})
})
return leaves
}
/** getDeviceTree 接口叶子收集3 层结构,便携式为 type=device 节点) */
export function collectDeviceApiLeaves(
governNodes: any[],
portableNodes: any[],
monitorNodes: any[]
): { govern: any[]; portable: any[]; monitor: any[] } {
const govern: any[] = []
const portable: any[] = []
const monitor: any[] = []
governNodes.forEach(l1 => {
l1.children?.forEach((l2: any) => {
l2.children?.forEach((l3: any) => govern.push(l3))
})
})
portableNodes.forEach(l1 => {
if (l1.type === 'device') portable.push(l1)
})
monitorNodes.forEach(l1 => {
l1.children?.forEach((l2: any) => {
l2.children?.forEach((l3: any) => monitor.push(l3))
})
})
return { govern, portable, monitor }
}
/** 从 lineTree 数据中解析监测设备根节点 */
export function resolveMonitorRoot(data: any): any | null {
if (Array.isArray(data)) {
return data.find(item => item.name === '监测设备') ?? null
}
if (data?.name === '监测设备') return data
return null
}

View File

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

View File

@@ -0,0 +1,203 @@
import { nextTick } from 'vue'
export interface LineTreeLeaves {
govern: any[]
portable: any[]
monitor: any[]
engineering: any[]
}
export interface LineTreeDecorators {
primary: () => string
statusColor: (comFlag: number) => string
applyMeta: (
node: any,
meta: { icon: string; color?: string; level?: number; disabled?: boolean }
) => void
}
export function createLineTreeDecorators(getPrimaryColor: () => string): LineTreeDecorators {
const offlineColor = '#e26257 !important'
const statusColor = (comFlag: number) => (comFlag === 2 ? getPrimaryColor() : offlineColor)
const applyMeta = (
node: any,
meta: { icon: string; color?: string; level?: number; disabled?: boolean }
) => {
node.icon = meta.icon
if (meta.color !== undefined) node.color = meta.color
if (meta.level !== undefined) node.level = meta.level
if (meta.disabled) node.disabled = true
}
return {
primary: getPrimaryColor,
statusColor,
applyMeta
}
}
export type TreeRefKey = 'treeRef1' | 'treeRef2' | 'treeRef3' | 'treeRef4'
/** 线路树可选叶子节点元数据 */
export const LINE_LEAF_META = { level: 3, type: 'line' as const }
/** 是否为线路树可选叶子(监测点/线路) */
export function isLineTreeLeaf(node: any): boolean {
if (!node?.id) return false
return node.type === 'line' || node.level === 3
}
/** 是否为报告/导出可选监测点 */
export function isReportMonitorPoint(node: any): boolean {
if (!node?.id) return false
return isLineTreeLeaf(node) || node.level === 3 || (!node.children?.length && !!node.pid)
}
export interface DecorateLineTreeOptions {
/** 是否禁用父级节点(分析树隐藏父节点,测点树不禁用) */
disableParents?: boolean
}
/** 装饰线路树节点并收集可选叶子节点 */
export function decorateLineTree(
data: any[],
type: string | undefined,
decorators: LineTreeDecorators,
options: DecorateLineTreeOptions = {}
): LineTreeLeaves {
const leaves: LineTreeLeaves = { govern: [], portable: [], monitor: [], engineering: [] }
const { primary, statusColor, applyMeta } = decorators
const disableParents = options.disableParents ?? true
const parentDisabled = disableParents ? ({ disabled: true } as const) : {}
data.forEach(item => {
if (type === '2') {
applyMeta(item, { icon: 'el-icon-HomeFilled', color: primary(), ...parentDisabled })
item.children?.forEach((child: any) => {
applyMeta(child, { icon: 'el-icon-List', color: primary(), ...parentDisabled })
child.children?.forEach((grand: any) => {
applyMeta(grand, {
icon: 'el-icon-Platform',
color: statusColor(grand.comFlag),
level: 2,
...parentDisabled
})
grand.children?.forEach((leaf: any) => {
applyMeta(leaf, {
icon: 'el-icon-Platform',
color: statusColor(leaf.comFlag),
...LINE_LEAF_META
})
leaves.engineering.push(leaf)
})
})
})
return
}
if (item.name === '治理设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary(), level: 1, ...parentDisabled })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary(), level: 1, ...parentDisabled })
l2.children?.forEach((l3: any) => {
applyMeta(l3, {
icon: 'el-icon-Platform',
color: statusColor(l3.comFlag),
level: 2,
...parentDisabled
})
l3.children?.forEach((l4: any) => {
applyMeta(l4, {
icon: 'el-icon-Platform',
color: statusColor(l4.comFlag),
...LINE_LEAF_META
})
leaves.govern.push(l4)
})
})
})
})
} else if (item.name === '便携式设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-Platform', color: statusColor(l1.comFlag) })
l1.children?.forEach((l2: any) => {
applyMeta(l2, {
icon: 'el-icon-Platform',
color: statusColor(l2.comFlag),
...LINE_LEAF_META
})
leaves.portable.push(l2)
})
})
} else if (item.name === '监测设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary(), level: 1, ...parentDisabled })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary(), level: 1, ...parentDisabled })
l2.children?.forEach((l3: any) => {
applyMeta(l3, {
icon: 'el-icon-Platform',
color: statusColor(l3.comFlag),
level: 1,
...parentDisabled
})
l3.children?.forEach((l4: any) => {
applyMeta(l4, {
icon: 'el-icon-Platform',
color: statusColor(l4.comFlag),
...LINE_LEAF_META
})
leaves.monitor.push(l4)
})
})
})
})
}
})
return leaves
}
/** 从折叠面板树数据中收集叶子节点(与 decorateLineTree 层级一致) */
export function collectDeviceLeaves(
governNodes: any[],
portableNodes: any[],
monitorNodes: any[]
): Pick<LineTreeLeaves, 'govern' | 'portable' | 'monitor'> {
const govern: any[] = []
const portable: any[] = []
const monitor: any[] = []
governNodes.forEach(l1 => {
l1.children?.forEach((l2: any) => {
l2.children?.forEach((l3: any) => {
l3.children?.forEach((l4: any) => govern.push(l4))
})
})
})
portableNodes.forEach(l1 => {
l1.children?.forEach((l2: any) => portable.push(l2))
})
monitorNodes.forEach(l1 => {
l1.children?.forEach((l2: any) => {
l2.children?.forEach((l3: any) => {
l3.children?.forEach((l4: any) => monitor.push(l4))
})
})
})
return { govern, portable, monitor }
}
export async function waitForTreeRef(treRef: any, refKey: TreeRefKey, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await nextTick()
if (treRef?.[refKey]) return treRef[refKey]
await new Promise(resolve => setTimeout(resolve, 50))
}
return null
}

View File

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

View File

@@ -1,153 +1,103 @@
<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="loadTree"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue' import { ref, nextTick } 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 {
// const props = defineProps(['template']) createLineTreeDecorators,
decorateLineTree,
waitForTreeRef,
type LineTreeLeaves,
type TreeRefKey
} from './lineTreeUtils'
interface Props { interface Props {
template?: boolean template?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
template: false template: false
}) })
defineOptions({ defineOptions({
name: 'govern/deviceTree' name: 'govern/pointTree'
}) })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy']) const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy'])
const config = useConfig() const config = useConfig()
const tree = ref() const tree = ref<any[]>([])
const dictData = useDictData() const treRef = ref<InstanceType<typeof Tree>>()
const treRef = ref()
const width = ref('') const width = ref('')
const info = () => { const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
tree.value = []
let arr1: any[] = []
let arr2: any[] = []
let arr3: any[] = []
getLineTree().then(res => {
//治理设备
res.data.map((item: any) => {
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
nextTick(() => {
if (arr1.length) {
//初始化选中
treRef.value.treeRef1.setCurrentKey(arr1[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr1[0]
})
return
}
if (arr2.length) {
//初始化选中
treRef.value.treeRef2.setCurrentKey(arr2[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr2[0]
})
return
}
if(arr3.length){
treRef.value.treeRef3.setCurrentKey(arr3[0].id)
emit('init', {
level: 2,
...arr3[0]
})
return
}
else {
emit('init')
return
}
})
})
}
const changePointType = (val: any, obj: any) => { const changePointType = (val: any, obj: any) => {
emit('pointTypeChange', val, obj) emit('pointTypeChange', val, obj)
} }
if (props.template) {
getTemplateByDept({ id: dictData.state.area[0].id }) async function selectInitialNode(type: string | undefined, leaves: LineTreeLeaves) {
.then((res: any) => { const candidates: { refKey: TreeRefKey; list: any[]; level: number }[] =
emit('Policy', res.data) type === '2'
info() ? [{ refKey: 'treeRef4', list: leaves.engineering, level: 3 }]
}) : [
.catch(err => { { refKey: 'treeRef1', list: leaves.govern, level: 2 },
info() { refKey: 'treeRef2', list: leaves.portable, level: 2 },
}) { refKey: 'treeRef3', list: leaves.monitor, level: 2 }
} else { ]
info()
for (const { refKey, list, level } of candidates) {
const node = list[0]
if (!node) continue
const treeInstance = await waitForTreeRef(treRef.value, refKey)
treeInstance?.setCurrentKey(node.id)
emit('init', { ...node })
if (type === '2') {
changePointType('4', node)
}
return
}
emit('init')
} }
onMounted(() => {})
const loadTree = (type?: string) => {
tree.value = []
getLineTree({ type: type === '2' ? 'engineering' : '' }).then(res => {
const leaves = decorateLineTree(res.data, type, decorators, { disableParents: false })
tree.value = res.data
nextTick(() => selectInitialNode(type, leaves))
})
}
function bootstrap() {
if (props.template) {
querySysExcel({})
.then((res: any) => {
emit('Policy', res.data)
loadTree()
})
.catch(() => loadTree())
} else {
loadTree()
}
}
bootstrap()
defineExpose({ treRef })
</script> </script>

View File

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

View File

@@ -1,105 +1,64 @@
<template> <template>
<Tree ref="treRef" :width="width" :data="tree" default-expand-all @checkedNodesChange="handleCheckedNodesChange"/> <Tree
ref="treRef"
:width="width"
:data="tree"
default-expand-all
@checkedNodesChange="handleCheckedNodesChange"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue' import { ref } from 'vue'
import Tree from '../select.vue' import Tree from '../select.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 { getTemplateByDept } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
// const props = defineProps(['template']) import { createLineTreeDecorators, decorateLineTree } from './lineTreeUtils'
import { bootstrapWithTemplate, selectTreeNode } from './treeCommonUtils'
interface Props { interface Props {
template?: boolean template?: boolean
} }
const props = withDefaults(defineProps<Props>(), {
template: false const props = withDefaults(defineProps<Props>(), { template: false })
})
defineOptions({ defineOptions({ name: 'govern/selectTree' })
name: 'govern/deviceTree'
})
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy']) const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy'])
const config = useConfig() const config = useConfig()
const tree = ref()
const dictData = useDictData() const dictData = useDictData()
const treRef = ref() const tree = ref<any[]>([])
const treRef = ref<InstanceType<typeof Tree>>()
const width = ref('') const width = ref('')
const info = () => { const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const handleCheckedNodesChange = (nodes: any[]) => emit('checkChange', nodes)
async function loadTree() {
tree.value = [] tree.value = []
let arr3: any[] = [] const res = await getLineTree()
getLineTree().then(res => { const leaves = decorateLineTree(res.data, '1', decorators, { disableParents: false })
//治理设备 tree.value = res.data.filter((item: any) => item.name === '监测设备')
res.data.map((item: any) => {
if (item.name == '在线设备') { const node = leaves.monitor[0]
item.children.forEach((item: any) => { if (!node) {
item.icon = 'el-icon-HomeFilled' emit('init')
item.level = 1 return
item.color = config.getColorVal('elementUiPrimary') }
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List' await selectTreeNode(treRef.value, node, {
item2.level = 1 onSelect: selected => emit('init', { level: 2, ...selected })
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.filter(item => item.name == '在线设备')
nextTick(() => {
if (arr3.length) {
//初始化选中
treRef.value.treeRef.setCurrentKey(arr3[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr3[0]
})
return
}
else {
emit('init')
return
}
})
}) })
} }
// 处理子组件传递的勾选节点变化,并转发给父组件 bootstrapWithTemplate(
const handleCheckedNodesChange = (nodes: any[]) => { props.template,
// 先给父组件 loadTree,
emit('checkChange', nodes) () => getTemplateByDept({ id: dictData.state.area[0]?.id }),
} data => emit('Policy', data)
)
if (props.template) {
getTemplateByDept({ id: dictData.state.area[0].id })
.then((res: any) => {
emit('Policy', res.data)
info()
})
.catch(err => {
info()
})
} else {
info()
}
onMounted(() => {})
</script> </script>

View File

@@ -0,0 +1,58 @@
import { nextTick } from 'vue'
import { createLineTreeDecorators, type LineTreeDecorators } from './lineTreeUtils'
export { createLineTreeDecorators, type LineTreeDecorators }
export function findNodeById(nodes: any[], id: string): any | null {
for (const node of nodes) {
if (node.id === id) return node
if (node.children?.length) {
const found = findNodeById(node.children, id)
if (found) return found
}
}
return null
}
export async function waitForSingleTreeRef(treRef: any, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await nextTick()
if (treRef?.treeRef) return treRef.treeRef
await new Promise(resolve => setTimeout(resolve, 50))
}
return null
}
export async function selectTreeNode(
treRef: any,
node: any | undefined,
options?: { level?: number; onSelect?: (node: any) => void }
) {
if (!node) return false
const treeInstance = await waitForSingleTreeRef(treRef)
treeInstance?.setCurrentKey(node.id)
options?.onSelect?.(node)
return true
}
export function bootstrapWithTemplate(
template: boolean,
loadFn: () => void,
fetchTemplate: () => Promise<any>,
onPolicy: (data: any) => void
) {
if (template) {
fetchTemplate()
.then(res => {
onPolicy(res.data)
loadFn()
})
.catch(() => loadFn())
} else {
loadFn()
}
}
export function mapUserTreeNodes(data: any[], icon = 'el-icon-User', color = 'royalblue') {
return data.map(item => ({ ...item, icon, color }))
}

View File

@@ -0,0 +1,20 @@
/** 树节点搜索:匹配当前节点或任一祖先节点名称 */
export function matchTreeNodeName(value: string, data: any, node: any): boolean {
if (!value) return true
if (!data?.name) return false
if (data.name.indexOf(value) !== -1) return true
const level = node.level
if (level === 1) return false
let parentData = node.parent
for (let i = 0; i < level - 1; i++) {
if (parentData?.data?.name?.indexOf(value) !== -1) return true
parentData = parentData.parent
}
return false
}
export function createTreeFilterNode() {
return (value: string, data: any, node: any): boolean => matchTreeNodeName(value, data, node)
}

View File

@@ -1,41 +1,67 @@
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style='transition: all 0.3s; overflow: hidden;'> <div
<Icon v-show='menuCollapse' @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" class="cn-tree-root"
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 mt20 menu-collapse' :class="{ 'is-collapsed': menuCollapse, 'is-fill-height': props.fillHeight }"
style='cursor: pointer' /> :style="{ width: menuCollapse ? '40px' : props.width }"
<div class='cn-tree' :style='{ opacity: menuCollapse ? 0 : 1 }'> >
<div style='display: flex; align-items: center' class='mb10'> <!-- <Icon
<el-input maxlength="32" show-word-limit v-model.trim='filterText' placeholder='请输入内容' clearable> v-show="menuCollapse"
@click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
size="18px"
class="cn-tree-root__collapse-btn cn-tree-root__collapse-btn--float"
/> -->
<div class="cn-tree" :class="{ 'is-hidden': menuCollapse }">
<div class="cn-tree__toolbar">
<el-input
maxlength="32"
v-model.trim="filterText"
placeholder="请输入内容"
clearable
class="cn-tree__search"
>
<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 v-if="props.showPush" placement="bottom" :hide-after="0" content="台账推送">
<template #content> <Icon
<span>台账推送</span> name="el-icon-Promotion"
</template> size="20px"
class="cn-tree__push-btn"
<Icon :style="{ color: config.getColorVal('elementUiPrimary') }"
name="el-icon-Promotion" @click="onAdd"
size="20" />
class="fold ml10 menu-collapse"
style="cursor: pointer;"
:style="{ color: config.getColorVal('elementUiPrimary') }"
@click="onAdd" />
</el-tooltip> </el-tooltip>
<!-- <Icon @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <!-- <Icon
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse' v-if="props.canExpand"
style='cursor: pointer' v-if='props.canExpand' /> --> @click="onMenuCollapse"
name="el-icon-Fold"
size="18px"
class="cn-tree__collapse-btn"
/> -->
</div> </div>
<el-tree :style="{ height: 'calc(100vh - 200px)' }" <el-tree
style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current :default-expand-all="false" ref="treeRef"
@check-change="checkTreeNodeChange" :filter-node-method='filterNode' node-key='id' v-bind='$attrs'> :style="{ height: treeHeight }"
<template #default='{ node, data }'> :props="defaultProps"
<span class='custom-tree-node'> highlight-current
<Icon :name='data.icon' style='font-size: 16px' :style='{ color: data.color }' :default-expand-all="false"
v-if='data.icon' /> @check-change="checkTreeNodeChange"
<span style='margin-left: 4px'>{{ node.label }}</span> :filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
>
<template #default="{ node, data: nodeData }">
<span class="custom-tree-node">
<Icon
v-if="nodeData.icon"
:name="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
/>
<span class="custom-tree-node__label">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
@@ -43,118 +69,158 @@
</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, computed } from 'vue'
import { ref, watch } from 'vue'
import { t } from 'vxe-table';
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { createTreeFilterNode } from './govern/treeFilterUtils'
defineOptions({ defineOptions({ name: 'govern/indexTree', inheritAttrs: false })
name: 'govern/tree'
})
interface Props { interface Props {
width?: string width?: string
canExpand?: boolean canExpand?: boolean
showPush?: boolean
/** 默认高度基准偏移100vh - baseOffset */
baseOffset?: number
/** 在 baseOffset 基础上额外减去的高度 */
height?: number
/** 自定义树高度,优先级最高 */
treeHeight?: string
/** 撑满父容器高度 */
fillHeight?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true canExpand: true,
showPush: false,
baseOffset: 190,
height: 0,
treeHeight: '',
fillHeight: false
}) })
const emit = defineEmits(['checkTreeNodeChange', 'onAdd', 'changePointType'])
const config = useConfig() const config = useConfig()
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const filterText = ref('') const filterText = ref('')
const defaultProps = { const defaultProps = { label: 'name', value: 'id' }
label: 'name', const filterNode = createTreeFilterNode()
value: 'id'
} const SEARCH_BAR_HEIGHT = 42
const emit = defineEmits(['checkTreeNodeChange','onAdd'])
watch(filterText, val => { const treeHeight = computed(() => {
treeRef.value!.filter(val) if (props.treeHeight) return props.treeHeight
if (props.fillHeight) return `calc(100% - ${SEARCH_BAR_HEIGHT}px)`
return `calc(100vh - ${props.baseOffset}px - ${props.height}px)`
}) })
watch(filterText, val => treeRef.value?.filter(val))
const onMenuCollapse = () => { const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse) proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
} }
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 = () => { const checkTreeNodeChange = () => {
// console.log(treeRef.value?.getCheckedNodes(), "ikkkkkiisiiisis");
emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes()) emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes())
} }
const onAdd = () => { const onAdd = () => emit('onAdd')
emit('onAdd')
}
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef }) defineExpose({ treeRef })
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
.cn-tree { .cn-tree-root {
flex-shrink: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden;
height: 100%;
transition: width 0.3s;
box-sizing: border-box;
&.is-fill-height {
height: 100%;
}
&__collapse-btn {
color: var(--el-color-primary);
cursor: pointer;
flex-shrink: 0;
&--float {
margin: 20px 0 0 10px;
}
}
}
.cn-tree {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 10px; padding: 10px;
height: 100%; transition: opacity 0.3s;
width: 100%;
&.is-hidden {
opacity: 0;
pointer-events: none;
}
&__toolbar {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
flex-shrink: 0;
}
&__search {
flex: 1;
min-width: 0;
}
&__push-btn,
&__collapse-btn {
color: var(--el-color-primary);
cursor: pointer;
flex-shrink: 0;
}
&__body {
flex: 1;
min-height: 0;
overflow: auto;
}
:deep(.el-tree) { :deep(.el-tree) {
border: 1px solid var(--el-border-color); border: 1px solid var(--el-border-color);
border-radius: 4px;
} }
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) { :deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: var(--el-color-primary-light-7); background-color: var(--el-color-primary-light-7);
} }
.menu-collapse {
color: var(--el-color-primary);
}
} }
.custom-tree-node { .custom-tree-node {
display: flex; display: flex;
align-items: center; align-items: center;
min-width: 0;
&__label {
margin-left: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
</style> </style>

View File

@@ -1,357 +1,381 @@
<!-- 设备监控使用折叠面板渲染多个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="18px"
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 #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'" <!-- <Icon
:class="menuCollapse ? 'unfold' : ''" size="18" class="fold ml10 menu-collapse" @click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''"
size="18px"
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-item title="治理设备" name="0" v-if="zlDeviceData.length != 0">
<el-select v-model.trim="process" clearable placeholder="请选择状态" class="mb10">
<el-option label="功能调试" value="2"></el-option>
<el-option label="出厂调试" value="3"></el-option>
<el-option label="正式投运" value="4"></el-option>
</el-select>
<el-collapse
:accordion="true"
v-model="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">
<el-select v-model="process" clearable placeholder="请选择状态" class="mb10">
<el-option label="功能调试" value="2" />
<el-option label="出厂调试" value="3" />
<el-option label="正式投运" value="4" />
</el-select>
<el-tree <el-tree
:style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 278px)' }" :style="{ height: governTreeHeight }"
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
<template #default="{ node, data }"> :filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
:data="zlDevList"
style="overflow: auto"
:default-expand-all="false"
>
<template #default="{ node, data: nodeData }">
<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="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.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="1" v-if="bxsDeviceData.length != 0"> <el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 330px)' : 'calc(100vh - 238px)' }" :style="{ height: otherTreeHeight }"
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
<template #default="{ node, data }"> :default-expand-all="false"
:filter-node-method="filterNode"
node-key="id"
:data="bxsDeviceData"
v-bind="$attrs"
style="overflow: auto"
>
<template #default="{ node, data: nodeData }">
<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="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.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">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 330px)' : 'calc(100vh - 238px)' }" :style="{ height: otherTreeHeight }"
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
<template #default="{ node, data }"> :default-expand-all="false"
:filter-node-method="filterNode"
node-key="id"
:data="yqfDeviceData"
v-bind="$attrs"
style="overflow: auto"
>
<template #default="{ node, data: nodeData }">
<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="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.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: engineeringTreeHeight }"
class="pt10"
ref="treeRef4"
:props="defaultProps"
highlight-current
:filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
:data="props.data"
style="overflow: auto"
:default-expand-all="false"
>
<template #default="{ node, data: nodeData }">
<span class="custom-tree-node">
<Icon
:name="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</div> </div>
</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, type CollapseModelValue } from 'element-plus'
import { el, fa } from 'element-plus/es/locale' import { ref, watch, nextTick, computed } from 'vue'
import { ref, watch, defineEmits, onMounted, nextTick, computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { collectDeviceLeaves } from './govern/lineTreeUtils'
defineOptions({ defineOptions({
name: 'govern/tree' name: 'govern/point',
inheritAttrs: false
}) })
const emit = defineEmits(['changePointType'])
const emit = defineEmits(['changePointType', 'changeTreeType'])
interface Props { interface Props {
width?: string width?: string
canExpand?: boolean canExpand?: boolean
type?: string type?: string
data?: any data?: any[]
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
width: '100%', width: '100%',
canExpand: true, canExpand: true,
type: '', type: '',
data: [] data: () => []
}) })
const process = ref('')
const route = useRoute() const route = useRoute()
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const treeType = ref('1')
const options = [
{ label: '设备', value: '1' },
{ label: '工程', value: '2' }
]
const menuCollapse = ref(false) const menuCollapse = ref(false)
const activeName = ref('0') const activeName = ref('0')
const filterText = ref('') const filterText = ref('')
const defaultProps = { const process = ref('')
label: 'name', const loading = ref(false)
value: 'id'
}
//治理设备数据
const zlDeviceData = ref<any>([])
const zlDevList = ref<any>([])
//便携式设备数据
const bxsDeviceData = ref<any>([])
//在线设备数据
const yqfDeviceData = ref<any>([])
watch(
() => props.data,
(val, oldVal) => {
if (val && val.length != 0) {
val.map((item: any) => {
if (item.name == '治理设备') {
zlDeviceData.value = []
item.children.map((vv: any) => {
zlDeviceData.value.push(vv)
})
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else if (item.name == '便携式设备') {
bxsDeviceData.value = []
item.children.map((vv: any) => {
bxsDeviceData.value.push(vv)
})
}else if (item.name == '在线设备') {
yqfDeviceData.value = []
item.children.map((vv: any) => {
yqfDeviceData.value.push(vv)
})
}
})
} const defaultProps = { label: 'name', value: 'id' }
},
{ const zlDeviceData = ref<any[]>([])
immediate: true, const zlDevList = ref<any[]>([])
deep: true const bxsDeviceData = ref<any[]>([])
} const yqfDeviceData = ref<any[]>([])
const governTreeHeight = computed(() => 'calc(100vh - 380px)')
const otherTreeHeight = computed(() =>
zlDeviceData.value.length ? 'calc(100vh - 340px)' : 'calc(100vh - 238px)'
) )
const engineeringTreeHeight = computed(() => 'calc(100vh - 188px)')
watch(filterText, val => { const treeRef1 = ref<InstanceType<typeof ElTree>>()
if (activeName.value == '0') { const treeRef2 = ref<InstanceType<typeof ElTree>>()
treeRef1.value!.filter(val) const treeRef3 = ref<InstanceType<typeof ElTree>>()
} else if (activeName.value == '1') { const treeRef4 = ref<InstanceType<typeof ElTree>>()
treeRef2.value!.filter(val)
} else {
treeRef3.value!.filter(val)
}
})
watch(process, val => {
if (val == '') {
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
}
setTimeout(() => { defineExpose({ treeRef1, treeRef2, treeRef3, treeRef4 })
changeDevice(activeName.value)
}, 0)
})
function splitTreeData(val: any[]) {
zlDeviceData.value = []
bxsDeviceData.value = []
yqfDeviceData.value = []
val.forEach(item => {
const changeDevice = (val: any) => { if (item.name === '治理设备') {
zlDeviceData.value = item.children ?? []
let arr1: any = [] } else if (item.name === '便携式设备') {
//zlDeviceData bxsDeviceData.value = item.children ?? []
zlDevList.value.forEach((item: any) => { } else if (item.name === '监测设备') {
item.children.forEach((item2: any) => { yqfDeviceData.value = item.children ?? []
item2.children.forEach((item3: any) => { }
item3.children.forEach((item4: any) => {
arr1.push(item4)
})
})
})
}) })
let arr2: any = []
bxsDeviceData.value.forEach((item: any) => { zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
item.children.forEach((item2: any) => {
arr2.push(item2)
})
})
let arr3: any = []
yqfDeviceData.value.forEach((item: any) => {
item.children.forEach((item2: any) => {
item2.children.forEach((item3: any) => {
item3.children.forEach((item4: any) => {
arr3.push(item4)
})
})
})
})
activeName.value = val
if (val == '0') {
arr2.map((item: any) => {
item.checked = false
})
treeRef1?.value && treeRef1.value.setCurrentKey(arr1[0]?.id)
emit('changePointType', activeName.value, arr1[0])
}
if (val == '1') {
arr1.map((item: any) => {
item.checked = false
})
treeRef2?.value && treeRef2.value.setCurrentKey(arr2[0]?.id)
emit('changePointType', activeName.value, arr2[0])
}
if (val == '2') {
arr3.map((item: any) => {
item.checked = false
})
treeRef3?.value && treeRef3.value.setCurrentKey(arr3[0]?.id)
emit('changePointType', activeName.value, arr3[0])
}
// if(activeName.value){
// emit('changePointType', activeName.value)
// }
}
const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
}
const filterNode = (value: string, data: any, node: any) => {
if (!value) return true
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node)
}
} }
function filterProcess(nodes: any) { /** 测点树专用level=2 按 process 过滤,其余层级保留有子节点或匹配的节点 */
if (process.value == '') { function filterProcess(nodes: any[]): any[] {
return nodes if (!process.value) return nodes
}
return nodes return nodes
.map(node => { .map(node => {
// 递归处理子节点
const children = node.children ? filterProcess(node.children) : [] const children = node.children ? filterProcess(node.children) : []
// 对于装置层级level=2只保留 process 值匹配的节点
if (node.level === 2) { if (node.level === 2) {
if (node.process == process.value) { if (node.process == process.value) {
return { return { ...node, children }
...node,
children: children
}
} }
return null return null
} }
// 对于其他节点: if (children.length > 0 || node.process == process.value || !node.children?.length) {
// 1. 如果有满足条件的子节点则保留 return { ...node, children }
// 2. 如果本身 process 值匹配则保留
// 3. 如果是叶子节点也保留(监测点通常没有子节点)
if (children.length > 0 || node.process == process.value ||
(!node.children || node.children.length === 0)) {
return {
...node,
children: children
}
} }
return null return null
}) })
.filter(Boolean) // 移除null节点 .filter(Boolean)
} }
function getActiveTreeRef() {
if (treeType.value === '2') return treeRef4.value
if (activeName.value === '0') return treeRef1.value
if (activeName.value === '1') return treeRef2.value
return treeRef3.value
}
// function filterProcess(nodes: any) { function resolveActiveName() {
// if (process.value == '') { if (zlDeviceData.value.length) return '0'
// return nodes if (bxsDeviceData.value.length) return '1'
// } if (yqfDeviceData.value.length) return '2'
// return nodes return ''
// .map(node => { }
// // 递归处理子节点
// const children = node.children ? filterProcess(node.children) : []
// // 如果当前节点的process=4或者有子节点满足条件则保留当前节点 function selectPointPanel(panelName: string, node?: any) {
if (!node) return
emit('changePointType', panelName, node)
nextTick(() => {
getActiveTreeRef()?.setCurrentKey(node.id)
})
}
// if (node.process == process.value || children.length > 0) { const changeDevice = (val: CollapseModelValue) => {
// return { if (Array.isArray(val) || val == null || val === '') return
// ...node, const panelName = String(val)
// children: node.children const { govern, portable, monitor } = collectDeviceLeaves(
// } zlDevList.value,
// } bxsDeviceData.value,
yqfDeviceData.value
)
// // 否则过滤掉当前节点 const panelMap: Record<string, { nodes: any[]; clearOthers: any[][] }> = {
// return null '0': { nodes: govern, clearOthers: [portable, monitor] },
// }) '1': { nodes: portable, clearOthers: [govern, monitor] },
// .filter(Boolean) // 移除null节点 '2': { nodes: monitor, clearOthers: [govern, portable] }
// }
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) {
return true
} }
const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了 const panel = panelMap[panelName]
if (level === 1) { if (!panel) return
return false
panel.clearOthers.forEach(list => list.forEach(item => (item.checked = false)))
selectPointPanel(panelName, panel.nodes[0])
}
const setActiveName = () => {
activeName.value = resolveActiveName()
if (activeName.value) {
nextTick(() => changeDevice(activeName.value))
} }
// 先取当前节点的父节点 }
let parentData = node.parent
// 遍历当前节点的父节点 watch(
let index = 0 () => props.data,
while (index < level - 1) { val => {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤 if (!val?.length) return
if (parentData.data.name.indexOf(value) !== -1) { splitTreeData(val)
return true if (treeType.value === '1') {
nextTick(() => setActiveName())
} }
// 否则的话再往上一层做匹配 },
parentData = parentData.parent { immediate: true, deep: true }
index++ )
watch(filterText, val => {
getActiveTreeRef()?.filter(val)
})
watch(process, () => {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
if (activeName.value === '0') {
nextTick(() => changeDevice(activeName.value))
}
})
const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
}
const filterNode = (value: string, data: any, node: any): boolean => {
if (!value) return true
if (!data.name) return false
return chooseNode(value, data, node)
}
const chooseNode = (value: string, data: any, node: any): boolean => {
if (data.name.indexOf(value) !== -1) return true
const level = node.level
if (level === 1) return false
let parentData = node.parent
for (let i = 0; i < level - 1; i++) {
if (parentData?.data?.name?.indexOf(value) !== -1) return true
parentData = parentData.parent
} }
// 没匹配到返回false
return false return false
} }
//治理
const treeRef1 = ref<InstanceType<typeof ElTree>>() const changeTreeType = (val: string) => {
//便携式 loading.value = true
const treeRef2 = ref<InstanceType<typeof ElTree>>() emit('changeTreeType', val)
//在线 if (val === '1') {
const treeRef3 = ref<InstanceType<typeof ElTree>>() nextTick(() => setActiveName())
defineExpose({ treeRef1, treeRef2 }) }
onMounted(() => {
setTimeout(() => { setTimeout(() => {
loading.value = false
if (zlDeviceData.value.length != 0) { }, 300)
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)
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -382,4 +406,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,36 +1,45 @@
<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="18px"
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>
</div> </div>
<el-tree <el-tree
:style="{ height: 'calc(100vh)' }" :style="{ height: 'calc(100vh - 120px)' }"
style='overflow: auto;' style="overflow: auto"
ref='treeRef' ref="treeRef"
:props='defaultProps' :props="defaultProps"
highlight-current highlight-current
:filter-node-method='filterNode' :filter-node-method="filterNode"
node-key='id' node-key="id"
show-checkbox show-checkbox
@check="handleCheckChange" @check="handleCheckChange"
@node-click="handleNodeClick"
:default-checked-keys="defaultCheckedKeys" :default-checked-keys="defaultCheckedKeys"
v-bind='$attrs' v-bind="$attrs"
:default-expand-all="false" :default-expand-all="false"
> >
<template #default='{ node, data }'> <template #default="{ node, data: nodeData }">
<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="nodeData.icon"
<span style='margin-left: 4px'>{{ node.label }}</span> style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
@@ -38,15 +47,14 @@
</div> </div>
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElMessage, ElTree } from 'element-plus' import { ElMessage, ElTree } from 'element-plus'
import { emit } from 'process';
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { createTreeFilterNode } from './govern/treeFilterUtils'
import { isLineTreeLeaf } from './govern/lineTreeUtils'
defineOptions({ defineOptions({ name: 'govern/select', inheritAttrs: false })
name: 'govern/tree'
})
interface Props { interface Props {
width?: string width?: string
@@ -57,173 +65,67 @@ const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true canExpand: true
}) })
const emit = defineEmits(['changePointType', 'checkedNodesChange'])
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const filterText = ref('') const filterText = ref('')
const defaultProps = { const defaultProps = { label: 'name', value: 'id' }
label: 'name', const filterNode = createTreeFilterNode()
value: 'id' const checkedNodes = ref<any[]>([])
} const defaultCheckedKeys = ref<string[]>([])
const emit = defineEmits(['changePointType', 'checkedNodesChange']) const MAX_CHECK = 5
const isMonitorLeaf = (node: any) => isLineTreeLeaf(node)
watch(filterText, val => treeRef.value?.filter(val))
watch(filterText, val => {
treeRef.value!.filter(val)
})
const onMenuCollapse = () => { const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse) proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
} }
const filterNode = (value: string, data: any, node: any) => {
if (!value) return true
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node) const handleCheckChange = (_data: any, checkInfo: any) => {
} const monitoringPointNodes = (checkInfo.checkedNodes as any[]).filter(isMonitorLeaf)
}
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符. if (monitoringPointNodes.length > MAX_CHECK) {
const chooseNode = (value: string, data: any, node: any) => { const previousCheckedNodes = checkedNodes.value
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 handleNodeClick = (data: any, node: any, event: any) => {
}
// 存储所有勾选的节点
const checkedNodes = ref<any[]>([])
const defaultCheckedKeys = ref<string[]>([])
// 处理节点勾选变化
const handleCheckChange = (data: any, checkInfo: any) => {
const { checkedNodes: nodes } = checkInfo
// 过滤出监测点层级(level=3)的节点
const monitoringPointNodes = nodes.filter((node: any) => {
return node.level === 3
})
// 限制最多只能勾选5个监测点
if (monitoringPointNodes.length > 5) {
// 获取之前选中的节点
const previousCheckedNodes = checkedNodes.value || []
// 计算新增的节点
const newNodes = monitoringPointNodes.filter( const newNodes = monitoringPointNodes.filter(
(node: any) => !previousCheckedNodes.some((prev: any) => prev.id === node.id) node => !previousCheckedNodes.some(prev => prev.id === node.id)
) )
// 如果是从父级勾选导致超过限制,保留前几个直到达到限制数量
if (newNodes.length > 0) { if (newNodes.length > 0) {
const allowedNewCount = 5 - previousCheckedNodes.length const allowedNewCount = MAX_CHECK - previousCheckedNodes.length
if (allowedNewCount > 0) { if (allowedNewCount > 0) {
// 允许添加allowedNewCount个新节点 const finalNodes = [...previousCheckedNodes, ...newNodes.slice(0, allowedNewCount)]
const allowedNewNodes = newNodes.slice(0, allowedNewCount)
const finalNodes = [...previousCheckedNodes, ...allowedNewNodes]
checkedNodes.value = finalNodes checkedNodes.value = finalNodes
// 设置树的勾选状态为正确的节点
treeRef.value?.setCheckedNodes(finalNodes) treeRef.value?.setCheckedNodes(finalNodes)
// 将勾选的监测点节点暴露出去
emit('checkedNodesChange', finalNodes) emit('checkedNodesChange', finalNodes)
// 更新节点的可勾选状态
updateNodeCheckStatus(finalNodes.length) updateNodeCheckStatus(finalNodes.length)
if (monitoringPointNodes.length > MAX_CHECK) {
// 只有在真正超过5个时才提示警告 ElMessage.warning(`最多只能选择${MAX_CHECK}个监测点`)
if (monitoringPointNodes.length > 5) {
ElMessage.warning('最多只能选择5个监测点')
} }
return return
} }
} }
// 其他情况回滚到之前的状态 ElMessage.warning(`最多只能选择${MAX_CHECK}个监测点`)
ElMessage.warning('最多只能选择5个监测点')
treeRef.value?.setCheckedNodes(checkedNodes.value) treeRef.value?.setCheckedNodes(checkedNodes.value)
return return
} }
checkedNodes.value = monitoringPointNodes checkedNodes.value = monitoringPointNodes
// 将勾选的监测点节点暴露出去
emit('checkedNodesChange', monitoringPointNodes) emit('checkedNodesChange', monitoringPointNodes)
// 更新节点的可勾选状态
updateNodeCheckStatus(monitoringPointNodes.length)
}
// 处理节点勾选变化
const handleCheckChange2 = (data: any, checkInfo: any) => {
const { checkedNodes: nodes } = checkInfo
// 过滤出监测点层级(level=3)的节点
const monitoringPointNodes = nodes.filter((node: any) => {
// 监测点节点通常具有 comFlag 属性或其他标识
return node.level === 3
})
// 限制最多只能勾选5个监测点
if (monitoringPointNodes.length > 5) {
ElMessage.warning('最多只能选择5个监测点')
// 保持之前勾选的状态
treeRef.value?.setCheckedNodes(checkedNodes.value)
return
}
checkedNodes.value = monitoringPointNodes
// 将勾选的监测点节点暴露出去
emit('checkedNodesChange', monitoringPointNodes)
// 更新节点的可勾选状态
updateNodeCheckStatus(monitoringPointNodes.length) updateNodeCheckStatus(monitoringPointNodes.length)
} }
// 更新节点的可勾选状态
const updateNodeCheckStatus = (currentCount: number) => { const updateNodeCheckStatus = (currentCount: number) => {
if (!treeRef.value) return if (!treeRef.value) return
const isMaxSelected = currentCount >= MAX_CHECK
// 如果已经选了5个则禁用其他未选中的监测点节点 treeRef.value.store._getAllNodes().forEach((node: any) => {
const isMaxSelected = currentCount >= 5 if (isMonitorLeaf(node.data)) {
node.data.disabled = isMaxSelected && !node.checked
// 获取所有节点并更新状态
const allNodes = treeRef.value.store._getAllNodes()
allNodes.forEach((node: any) => {
if (node.level === 3) { // 监测点层级
// 如果已达到最大数量且该节点未被选中,则禁用勾选
if (isMaxSelected && !node.checked) {
node.disabled = true
} else {
node.disabled = false
}
} }
}) })
} }
@@ -232,8 +134,7 @@ 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;

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,79 +1,79 @@
<template> <template>
<div class="layout-logo"> <div class="layout-logo">
<img v-if="!config.layout.menuCollapse" class="logo-img" :src="getTheme.logoUrl" /> <img v-if="!config.layout.menuCollapse && getTheme.logoUrl" class="logo-img" :src="getTheme.logoUrl" />
<!-- <div--> <!-- <div-->
<!-- v-if="!config.layout.menuCollapse"--> <!-- v-if="!config.layout.menuCollapse"-->
<!-- :style="{ color: config.getColorVal('menuActiveColor') }"--> <!-- :style="{ color: config.getColorVal('menuActiveColor') }"-->
<!-- class="website-name"--> <!-- class="website-name"-->
<!-- >--> <!-- >-->
<!-- 灿能--> <!-- 灿能-->
<!-- </div>--> <!-- </div>-->
<Icon <Icon
v-if="config.layout.layoutMode != 'Streamline'" v-if="config.layout.layoutMode != 'Streamline'"
@click="onMenuCollapse" @click="onMenuCollapse"
:name="config.layout.menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" :name="config.layout.menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="config.layout.menuCollapse ? 'unfold' : ''" :class="config.layout.menuCollapse ? 'unfold' : ''"
:color="config.getColorVal('menuActiveColor')" :color="config.getColorVal('menuActiveColor')"
style="margin: 15px;" style="margin: 15px;"
size="18" size="18"
class="fold" class="fold"
/> />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { closeShade } from '@/utils/pageShade' import { closeShade } from '@/utils/pageShade'
import { Session } from '@/utils/storage' import { getStoredTheme } from '@/utils/storage'
import { setNavTabsWidth } from '@/utils/layout' import { setNavTabsWidth } from '@/utils/layout'
const config = useConfig() const config = useConfig()
const getTheme = JSON.parse(window.localStorage.getItem('getTheme') as string) const getTheme = getStoredTheme()
const onMenuCollapse = function () { const onMenuCollapse = function () {
if (config.layout.shrink && !config.layout.menuCollapse) { if (config.layout.shrink && !config.layout.menuCollapse) {
closeShade() closeShade()
} }
config.setLayout('menuCollapse', !config.layout.menuCollapse) config.setLayout('menuCollapse', !config.layout.menuCollapse)
// 等待侧边栏动画结束后重新计算导航栏宽度 // 等待侧边栏动画结束后重新计算导航栏宽度
setTimeout(() => { setTimeout(() => {
setNavTabsWidth() setNavTabsWidth()
}, 350) }, 350)
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.layout-logo { .layout-logo {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-sizing: border-box; box-sizing: border-box;
padding: 5px 10px; padding: 5px 10px;
background: v-bind( background: v-bind(
'config.layout.layoutMode != "Streamline" ? config.getColorVal("menuTopBarBackground"):"transparent"' 'config.layout.layoutMode != "Streamline" ? config.getColorVal("menuTopBarBackground"):"transparent"'
); );
} }
.logo-img { .logo-img {
height: 50px; height: 50px;
object-fit: cover; object-fit: cover;
margin: auto; margin: auto;
} }
.website-name { .website-name {
display: block; display: block;
width: 180px; width: 180px;
padding-left: 4px; padding-left: 4px;
font-size: var(--el-font-size-extra-large); font-size: var(--el-font-size-extra-large);
font-weight: 600; font-weight: 600;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.fold { .fold {
margin-left: auto; margin-left: auto;
} }
.unfold { .unfold {
margin: 0 auto; margin: 0 auto;
} }
</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,111 +1,112 @@
<template> <template>
<div class="nav-bar"> <div class="nav-bar">
<div v-if="config.layout.shrink && config.layout.menuCollapse" class="unfold"> <div v-if="config.layout.shrink && config.layout.menuCollapse" class="unfold">
<Icon @click="onMenuCollapse" name="fa fa-indent" :color="config.getColorVal('menuActiveColor')" <!-- <Icon @click="onMenuCollapse" name="fa fa-indent" :color="config.getColorVal('menuActiveColor')"
size="18" /> size="18" /> -->
</div> </div>
<span class="nav-bar-title">{{ getTheme.name }} <span style="font-size: 14px;" v-if="Version?.versionName"> <span class="nav-bar-title">{{ themeName }} <span style="font-size: 14px;" v-if="Version?.versionName">
({{ Version?.versionName }}) ({{ Version?.versionName }})
</span></span> </span></span>
<NavMenus /> <NavMenus />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import NavTabs from '@/layouts/admin/components/navBar/tabs.vue' import NavTabs from '@/layouts/admin/components/navBar/tabs.vue'
import NavMenus from '../navMenus.vue' import NavMenus from '../navMenus.vue'
import { showShade } from '@/utils/pageShade' import { showShade } from '@/utils/pageShade'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { getLastData } from '@/api/systerm' import { getLastData } from '@/api/systerm'
const config = useConfig() import { getStoredThemeName } from '@/utils/storage'
const getTheme = JSON.parse(window.localStorage.getItem('getTheme') as string) const config = useConfig()
const Version: any = ref({}) const themeName = getStoredThemeName()
const onMenuCollapse = () => { const Version: any = ref({})
showShade('ba-aside-menu-shade', () => { const onMenuCollapse = () => {
config.setLayout('menuCollapse', true) showShade('ba-aside-menu-shade', () => {
}) config.setLayout('menuCollapse', true)
config.setLayout('menuCollapse', false) })
} config.setLayout('menuCollapse', false)
onMounted(() => { }
getLastData({ versionType: 'WEB' }).then(res => { onMounted(() => {
Version.value = res.data getLastData({ versionType: 'WEB' }).then(res => {
Version.value = res.data
})
document.title = getTheme.name })
}) document.title = themeName
</script> })
</script>
<style scoped lang="scss">
.nav-bar { <style scoped lang="scss">
display: flex; .nav-bar {
align-items: center; display: flex;
height: 60px; align-items: center;
width: 100%; height: 60px;
background-color: v-bind('config.getColorVal("headerBarBackground")'); width: 100%;
background-color: v-bind('config.getColorVal("headerBarBackground")');
.nav-bar-title {
color: v-bind('config.getColorVal("headerBarTabColor")'); .nav-bar-title {
font-size: 24px; color: v-bind('config.getColorVal("headerBarTabColor")');
margin-left: 10px; font-size: 24px;
font-weight: 700; margin-left: 10px;
} font-weight: 700;
}
:deep(.nav-tabs) {
display: flex; :deep(.nav-tabs) {
height: 100%; display: flex;
position: relative; height: 100%;
position: relative;
.ba-nav-tab {
display: flex; .ba-nav-tab {
align-items: center; display: flex;
justify-content: center; align-items: center;
padding: 0 20px; justify-content: center;
cursor: pointer; padding: 0 20px;
z-index: 1; cursor: pointer;
height: 100%; z-index: 1;
user-select: none; height: 100%;
color: v-bind('config.getColorVal("headerBarTabColor")'); user-select: none;
transition: all 0.2s; color: v-bind('config.getColorVal("headerBarTabColor")');
-webkit-transition: all 0.2s; transition: all 0.2s;
-webkit-transition: all 0.2s;
.close-icon {
padding: 2px; .close-icon {
margin: 2px 0 0 4px; padding: 2px;
color: v-bind('config.getColorVal("headerBarTabColor")') !important; margin: 2px 0 0 4px;
} color: v-bind('config.getColorVal("headerBarTabColor")') !important;
}
&.active {
color: v-bind('config.getColorVal("headerBarTabActiveColor")'); &.active {
color: v-bind('config.getColorVal("headerBarTabActiveColor")');
.close-icon {
color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important; .close-icon {
} color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important;
} }
}
&:hover {
color: v-bind('config.getColorVal("headerBarTabActiveColor")'); &:hover {
background-color: v-bind('config.getColorVal("headerBarHoverBackground")'); color: v-bind('config.getColorVal("headerBarTabActiveColor")');
background-color: v-bind('config.getColorVal("headerBarHoverBackground")');
.close-icon {
color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important; .close-icon {
} color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important;
} }
} }
}
.nav-tabs-active-box {
position: absolute; .nav-tabs-active-box {
height: 50px; position: absolute;
background-color: v-bind('config.getColorVal("headerBarTabActiveBackground")'); height: 50px;
transition: all 0.2s; background-color: v-bind('config.getColorVal("headerBarTabActiveBackground")');
-webkit-transition: all 0.2s; transition: all 0.2s;
} -webkit-transition: all 0.2s;
} }
} }
}
.unfold {
align-self: center; .unfold {
padding-left: var(--ba-main-space); align-self: center;
} padding-left: var(--ba-main-space);
</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,51 +1,51 @@
<template> <template>
<el-dialog draggable width="600px" v-model.trim="dialogVisible" :title="title"> <el-dialog draggable width="500px" v-model.trim="dialogVisible" :title="title">
<el-form :inline="false" :model="form" label-width="auto" class="form-one"> <el-form :inline="false" :model="form" label-width="auto" class="form-one">
<el-form-item label="用户名称:"> <el-form-item label="用户名称:">
<el-input v-model.trim="form.name" :disabled="true"></el-input> <el-input v-model.trim="form.name" :disabled="true"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="登录名称:" class="top"> <el-form-item label="登录名称:" class="top">
<el-input v-model.trim="form.loginName" :disabled="true"></el-input> <el-input v-model.trim="form.loginName" :disabled="true"></el-input>
</el-form-item> </el-form-item>
<!-- <el-form-item label="归属部门名称:" class="top"> <!-- <el-form-item label="归属部门名称:" class="top">
<el-input v-model.trim="form.deptName" :disabled="true"></el-input> <el-input v-model.trim="form.deptName" :disabled="true"></el-input>
</el-form-item> --> </el-form-item> -->
<el-form-item label="拥有的角色:" class="top"> <el-form-item label="拥有的角色:" class="top">
<el-input v-model.trim="form.role" :disabled="true"></el-input> <el-input v-model.trim="form.role" :disabled="true"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="电话号码:" class="top"> <el-form-item label="电话号码:" class="top">
<el-input v-model.trim="form.phone" :disabled="true"></el-input> <el-input v-model.trim="form.phone" :disabled="true"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="电子邮箱:" class="top"> <el-form-item label="电子邮箱:" class="top">
<el-input v-model.trim="form.email" :disabled="true"></el-input> <el-input v-model.trim="form.email" :disabled="true"></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
</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 { useAdminInfo } from '@/stores/adminInfo' import { useAdminInfo } from '@/stores/adminInfo'
const dialogVisible = ref(false) const dialogVisible = ref(false)
const title = ref('用户信息') const title = ref('用户信息')
const adminInfo = useAdminInfo() const adminInfo = useAdminInfo()
const formRef = ref() const formRef = ref()
const form = reactive({ const form = reactive({
name: '', name: '',
deptName: '', deptName: '',
phone: '', phone: '',
email: '', email: '',
role: '', role: '',
loginName: '' loginName: ''
}) })
const open = () => { const open = () => {
dialogVisible.value = true dialogVisible.value = true
for (const key in form) { for (const key in form) {
form[key] = Array.isArray(adminInfo.$state[key]) ? adminInfo.$state[key].join(',') : adminInfo.$state[key] form[key] = Array.isArray(adminInfo.$state[key]) ? adminInfo.$state[key].join(',') : adminInfo.$state[key]
} }
} }
defineExpose({ open }) defineExpose({ open })
</script> </script>

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

@@ -51,11 +51,11 @@ onBeforeMount(() => {
}) })
const init = async () => { const init = async () => {
await Promise.all([getAreaList(), dictDataCache(), getUserById(), areaSelect(),getAllUserSimpleList()]).then(res => { await Promise.all([ 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[0].data
// dictData.state.userList=res[4].data // dictData.state.userList=res[4].data
adminInfo.dataFill(res[2].data) adminInfo.dataFill(res[1].data)
// dictData.state.areaTree = res[3].data // dictData.state.areaTree = res[3].data
}) })
/** /**

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