276 Commits

Author SHA1 Message Date
2765bcab10 故障告警,搜索栏 告警等级宽度调整 2026-01-29 20:08:33 +08:00
b55d9e2d5c Merge branch 'develop' into single-develop 2026-01-29 20:07:34 +08:00
29ab53056a 故障告警,搜索栏 告警等级宽度调整 2026-01-29 19:57:10 +08:00
0c32439146 Merge branch 'develop' into single-develop 2026-01-28 21:45:43 +08:00
5250db915f Merge branch 'develop' of http://101.43.41.9:13000/xzzn/emsfront into develop 2026-01-28 21:36:14 +08:00
814103c881 统计报表优化 2026-01-28 21:34:58 +08:00
55b7fba021 修改图片 2026-01-27 16:58:50 +08:00
da4ecc4792 修改顺序 2026-01-27 13:45:14 +08:00
498dc117f2 变更图片 2026-01-27 10:43:54 +08:00
058879ec45 Merge branch 'develop' into single-develop 2026-01-26 23:36:47 +08:00
4d29de93a1 大数据图片更新 2026-01-26 23:30:01 +08:00
445e9dfc9f 大数据图片改为切换模式 2026-01-26 22:40:57 +08:00
3a26f6312b 更新注释 2026-01-25 17:02:21 +08:00
138b0753af Merge branch 'develop' into single-develop 2026-01-25 16:56:29 +08:00
ac1d1ae154 更新workStatus状态枚举 2026-01-25 16:45:33 +08:00
825243e741 电池簇备注 2026-01-25 16:36:23 +08:00
0389ed85f3 Merge branch 'develop' of http://101.43.41.9:13000/xzzn/emsfront into develop 2026-01-25 16:33:00 +08:00
aef94f406a 电池簇、电池堆工作状态更新 2026-01-25 16:30:43 +08:00
95d69fb7b1 簇枚举变更 2026-01-25 16:27:35 +08:00
684002ffc8 簇枚举变更 2026-01-25 15:12:07 +08:00
098dfa05f8 簇枚举变更 2026-01-25 14:34:01 +08:00
d6c9310e50 净置改成静置 2026-01-25 13:54:23 +08:00
085f6fd252 Merge branch 'develop' into single-develop 2026-01-24 21:19:27 +08:00
142de3102b 大数据图片更新 2026-01-24 21:14:43 +08:00
d3e51322ab Merge branch 'develop' into single-develop 2026-01-24 10:37:31 +08:00
9b5806a2c0 0或没有返回值隐藏alarmNum的展示 2026-01-24 10:27:43 +08:00
2b6697fa5a pointName改成 dataPointName 2026-01-24 10:02:11 +08:00
3bb859b693 BMS电池簇数据同步 2026-01-23 22:32:12 +08:00
5b3701afd0 BMS总览数据同步 2026-01-23 22:02:02 +08:00
f531075853 电表数据同步 2026-01-23 21:46:02 +08:00
81f8f75d7e pcs开关机状态判断更新 2026-01-23 20:06:47 +08:00
fccddab04a Merge branch 'develop' into single-develop 2026-01-23 19:52:13 +08:00
8cb6fbee3e 大数据展示页面 2026-01-23 18:00:14 +08:00
f454b02c99 pcs、bmszl设备工作状态更新 2026-01-23 16:30:20 +08:00
bfe72cf2c3 首页字段、收益表格 2026-01-23 14:18:22 +08:00
fb90d81bb3 站点首页优化 2026-01-22 17:27:03 +08:00
823c0949d0 文案修改,实时运行图表切换站点数据未更新问题修复,电表未知类型 2026-01-21 18:58:37 +08:00
1b712a70f6 Merge branch 'develop' into single-develop 2026-01-21 14:42:02 +08:00
c1c411e48a 用户归属站点 2026-01-21 14:37:39 +08:00
43153a791d 用户归属站点 2026-01-21 14:05:09 +08:00
5d84972b8a 菜单 2026-01-16 16:08:54 +08:00
b122ab35ab Merge branch 'develop' into single-develop
# Conflicts:
#	src/router/ems.js
2026-01-16 15:59:17 +08:00
11111d035b 优化 2026-01-13 16:38:21 +08:00
80f07876c6 Merge branch 'develop' into single-develop 2026-01-12 14:17:32 +08:00
ab9bb1e85d pcs\bmszl\bmsdcc 标题颜色规则更新 2026-01-12 14:15:21 +08:00
bef8f01d0d pcs开关机接口参数更新 2026-01-08 17:14:57 +08:00
b469190d9f Merge branch 'develop' into single-develop 2026-01-08 16:59:47 +08:00
4079c40e5d workStatus、deviceStatus枚举更新,pcs开关机接口参数更新 2026-01-08 16:59:15 +08:00
e98b4ed287 Merge branch 'develop' into single-develop 2025-12-31 17:25:47 +08:00
4ebd5f0988 样式优化 2025-12-31 17:23:08 +08:00
a0095b4054 pcs设备类型,新增pcs设备配置选项,策略配置时间问题修复 2025-12-31 17:15:26 +08:00
6140ca8f14 Merge branch 'develop' into single-develop 2025-12-26 21:01:54 +08:00
84bc29410a 新增设备:TCP设备新增从站地址
策略配置:新增放电状态、可输入负数、开始、结束时间组件更换
2025-12-26 20:19:20 +08:00
a3d6cf7c55 Merge branch 'develop' into single-develop 2025-12-19 21:51:51 +08:00
f3fda19c64 保护方案 2025-12-19 21:49:19 +08:00
6fb706b528 云下展示pcs开关机按钮 2025-12-18 16:37:59 +08:00
579920e007 Merge branch 'develop' into single-develop 2025-12-18 16:35:34 +08:00
9a8247f833 云上隐藏pcs开关机按钮 2025-12-18 16:26:57 +08:00
d9eeee106f Merge branch 'develop' into single-develop 2025-12-16 13:48:09 +08:00
c4c79aaa64 设备列表顶部下载按钮展示 2025-12-16 13:44:48 +08:00
c90f4f5766 Merge branch 'develop' into single-develop 2025-12-16 11:14:50 +08:00
0966813c25 设备列表,隐藏顶部上传、下载按钮,pcs开机关机按钮显示条件优化 2025-12-16 11:03:59 +08:00
4a4d26f018 生产、云下环境变量配置 2025-12-15 17:41:29 +08:00
1a11c67aad test 环境变量配置 2025-12-15 14:42:27 +08:00
71fd48918d Merge branch 'develop' into single-develop
# Conflicts:
#	src/assets/styles/common.scss
2025-12-12 17:47:57 +08:00
dd4fa36597 优化 2025-12-12 17:38:26 +08:00
9b14d96e24 pcs开关机,设备列表上传、下载 2025-12-11 17:59:34 +08:00
34d9e038e6 合并代码 2025-12-10 15:09:33 +08:00
ac54ce999e ems、箱型图 2025-12-10 15:00:12 +08:00
f49efb5edd ems接口、综合查询 2025-12-09 18:25:36 +08:00
97f9b3ff0e ems 2025-12-08 17:00:18 +08:00
674a0c6c33 ems、收益报表 2025-12-08 16:58:43 +08:00
b4c49c3e58 Merge branch 'develop' into single-develop 2025-12-05 09:49:54 +08:00
7f3d02b8fb 点位、报警点位 2025-12-05 09:46:31 +08:00
9cfce671a6 Merge branch 'develop' into single-develop 2025-12-05 09:16:54 +08:00
cd8871d45a 点位上传、下载 2025-12-04 17:46:11 +08:00
11d44de513 表格内容fong-size调小1px 2025-11-28 17:53:34 +08:00
87e3db9f0f 云下使用深色主题区分云上 2025-11-28 15:21:42 +08:00
4132d8e539 合并develop最新代码 2025-11-28 15:18:27 +08:00
78eb81549b 设备列表上传 2025-11-28 15:11:34 +08:00
65f0e92296 2025-11-20紧急优化内容修复 2025-11-27 15:58:23 +08:00
9a585e97db 2025-11-20紧急优化内容-动环、消防 2025-11-27 15:51:13 +08:00
7fb6d1aa47 2025-11-20紧急优化内容 2025-11-26 17:50:06 +08:00
1378947a9e 设备监控-点位清单 2025-11-25 17:56:12 +08:00
28176ce052 Merge branch 'develop' into single-develop 2025-11-13 16:53:19 +08:00
0b2e7d9b86 mqtt新增站点、qos 2025-11-13 16:49:39 +08:00
fdcf82e343 Merge branch 'develop' into single-develop 2025-11-12 16:38:00 +08:00
87ffaca398 测试环境项目打包文件缺失问题 2025-11-12 16:35:43 +08:00
e4c6f1f798 Merge branch 'develop' into single-develop 2025-11-12 10:59:13 +08:00
06ef1c3d1d Merge branch 'develop' of http://101.43.41.9:13000/xzzn/emsfront into develop 2025-11-12 10:57:14 +08:00
bc5f2b4470 环境变量配置 2025-11-12 10:57:07 +08:00
f24241533b 修改站点首页 2025-11-12 10:50:46 +08:00
87cd27b798 环境配置 2025-11-11 17:53:40 +08:00
f4a42c168f Merge branch 'develop' into single-develop 2025-11-07 14:59:56 +08:00
3fd6f812b9 mqtt 2025-11-07 11:03:00 +08:00
1772c7e0e5 mqtt 2025-11-07 10:59:34 +08:00
eb5abec9ff 点位列表 2025-11-06 14:57:08 +08:00
834bd04d45 点位列表 2025-11-05 18:00:05 +08:00
371a2d8be0 菜单更新,单站监控展示 2025-11-05 14:50:11 +08:00
c403922639 Merge branch 'develop' into single-develop 2025-11-05 14:10:00 +08:00
da0d3d12ef 更新设备状态枚举 2025-11-01 13:02:01 +08:00
1280b7196c 设备保护 2025-10-31 21:34:52 +08:00
567ddf85a5 设备保护 2025-10-28 18:15:10 +08:00
3766b2c0fa 设备保护 2025-10-26 22:44:04 +08:00
695fcd0f8f 电池堆综合查询数据问题 2025-10-22 17:16:29 +08:00
11269efa2d 单体电池综合查询数据问题 2025-10-22 09:44:41 +08:00
4b8380a6a9 样式修复 2025-10-21 17:04:23 +08:00
5c150b0d26 设备列表、电价列表、综合搜索、图表详情优化 2025-10-21 15:42:00 +08:00
96e4f7874f 设备列表、电价列表、综合搜索、图表详情优化 2025-10-21 15:41:45 +08:00
bf833fc763 点位清单参数更新 2025-10-16 16:34:32 +08:00
f4e6821484 综合查询相关接口不再使用中文categoryName统一使用deviceCategory 2025-10-16 16:10:29 +08:00
fb0eda4565 单站监控-设备监控-动态三级菜单,点位清单排序 2025-10-15 14:26:06 +08:00
fef1704cbd 告警红点标志 2025-10-13 17:01:13 +08:00
679f8f2a07 单体电池图表、设备列表点位清单图表 2025-10-12 23:10:15 +08:00
376b50c3b5 电表报表 2025-10-11 17:30:23 +08:00
7d51c37cad 电表报表 2025-10-11 14:02:43 +08:00
ad95debdac 电价配置 2025-10-10 10:49:42 +08:00
cceca2af4f 电价配置 2025-10-09 17:38:26 +08:00
887af476ba 工单状态 2025-09-29 09:29:34 +08:00
28a6e525d8 编辑工单接口新增ticketNo 2025-09-28 16:56:11 +08:00
66e2a3daab 更新电表点位名称 2025-09-28 16:18:34 +08:00
d3558f5ee4 液冷页面优化 2025-09-28 15:52:17 +08:00
4005b921e6 优化 2025-09-28 15:37:01 +08:00
7d2d49b7e2 优化 2025-09-28 14:59:02 +08:00
cc4d6ab347 不展示菜单风格设置菜单 2025-09-28 14:55:22 +08:00
7497c1d1b9 告警页面先生成告警再跳转,更新电表返回值,更新登陆、注册页面表格左边距离 2025-09-28 14:31:24 +08:00
a1fdc958db 样式优化 2025-09-27 17:44:03 +08:00
9dcf2a6c12 综合查询切换站点、设备清空点位 2025-09-27 17:37:23 +08:00
fb4f9d4abc 功率、pcs、电池堆曲线更新为折线图 2025-09-27 17:26:08 +08:00
651a78bceb 站点监控接口没有返回站点菜单时不展示 2025-09-27 17:15:59 +08:00
a4ae820e98 登陆页面背景图片 2025-09-27 15:45:56 +08:00
466d3a14b6 logo更新 2025-09-27 15:19:15 +08:00
b421d11bc2 实时运行图标更新 2025-09-27 14:50:20 +08:00
408ba489ca 时间选择范围上下时段 2025-09-26 18:51:55 +08:00
b5956822ab 时间选择范围上下时段 2025-09-26 18:43:38 +08:00
6600a5db7a 点位图表数据名称、时间限制 2025-09-26 17:34:12 +08:00
506cf28c96 液冷图表 2025-09-26 15:09:58 +08:00
5f7a1c0f4b 电表、液冷、功率曲线 2025-09-26 14:47:45 +08:00
1f49a3af20 优化0918 2025-09-25 17:58:22 +08:00
a0ea6041cc 优化0918 2025-09-25 17:30:16 +08:00
2a3d2fcf63 Merge branch 'develop' into single-develop 2025-09-22 17:59:25 +08:00
0e7b54291f 设备监控使用统一的时间选择组件、文案更新 2025-09-22 17:57:30 +08:00
cbc7a341c9 点位 2025-09-22 10:16:21 +08:00
85cfd204ae 电位列表文案错误修改,实时运行顶部数据1分钟刷新 2025-09-22 09:48:11 +08:00
2cb4dbcd84 电位列表 2025-09-18 10:07:41 +08:00
6ad3ef9d7c 电位列表 2025-09-17 17:53:21 +08:00
25bb28f77a Merge branch 'develop' into single-develop 2025-09-17 14:57:30 +08:00
36b1e19120 设备监控标题样式统一 2025-09-17 14:52:08 +08:00
902e9a0a03 站点首页-当日功率曲线 2025-09-17 11:15:54 +08:00
7f560cd140 统计报表优化 2025-09-16 18:20:48 +08:00
e3224d37a1 统计报表优化 2025-09-14 23:33:53 +08:00
52b6083baa 设备检控官查看点位图表 2025-09-13 22:56:07 +08:00
98e2c4c3be 设备列表查看电位表格 2025-09-13 22:41:41 +08:00
4c31eeb837 设备监控点击数据查看表格 2025-09-13 20:36:46 +08:00
b61a202267 ['#4472c4','#70ad47'],//所有充放电颜色保持统一 2025-09-13 14:58:42 +08:00
c51baa166b 站点首页总充电量、总放电量单位更新 2025-09-13 14:47:37 +08:00
f005448705 优化 2025-09-10 14:25:00 +08:00
ba4df32176 优化 2025-09-10 09:56:22 +08:00
ae1c2a5d6c 定时刷新 2025-09-10 09:54:29 +08:00
f16b92582c 优化 2025-09-09 17:51:52 +08:00
cc3164a423 优化 2025-09-08 18:01:48 +08:00
2742f874ce 综合查询 2025-09-05 20:50:27 +08:00
e7ea8c8f44 综合查询 2025-09-05 20:37:44 +08:00
c502688737 综合查询 2025-09-04 20:12:05 +08:00
243724c9a6 综合查询 2025-09-04 15:37:15 +08:00
8808b25682 综合查询 2025-09-01 18:06:56 +08:00
f5fc1d64ec 优化 2025-08-25 14:29:27 +08:00
3d5d8918d7 合并develop最新代码 2025-08-20 17:53:26 +08:00
a0790b100a 主线路图 2025-08-20 17:26:01 +08:00
c3d64e4d04 单体电池 2025-08-18 18:13:24 +08:00
89cc734595 站点首页告警 2025-08-18 13:48:35 +08:00
587094e85e 不需要的console 2025-08-15 14:18:50 +08:00
0299d32a52 电表状态显示 2025-08-15 14:13:54 +08:00
15196f8d71 单体电池 2025-08-15 09:50:30 +08:00
8b20c89cb2 单体电池 2025-08-14 23:29:53 +08:00
ba8b86d523 单体电池 2025-08-14 18:09:47 +08:00
2cd60ea105 单体电池、策略 2025-08-14 17:59:58 +08:00
10033348d6 首页、单体电池 2025-08-13 17:49:25 +08:00
9a9198b168 菜单展开、折叠三级菜单页面问题 2025-08-13 16:09:34 +08:00
9ad720823a echarts整体调整 2025-08-13 14:51:26 +08:00
763338cc2a 优化 2025-08-11 21:52:04 +08:00
d203ca541a 优化 2025-08-11 21:50:38 +08:00
9db3b4f9f7 优化 2025-08-11 17:34:39 +08:00
7a13a73758 单站监控首页更新 2025-08-06 23:06:12 +08:00
7b88d754ee 站点首页更新 2025-08-06 17:34:35 +08:00
87d683273a 单体电池接口新增参数 2025-07-29 20:02:20 +08:00
fe14089562 在路由权限校验中获取设备列表 2025-07-23 21:35:40 +08:00
f71119885a 策略曲线 2025-07-23 21:22:21 +08:00
b68f2608f3 本地 2025-07-21 23:00:41 +08:00
000c8fec25 电池温度接口新增参数siteId 2025-07-21 21:25:25 +08:00
9406b0eb98 主线路图样式 2025-07-19 15:22:26 +08:00
fd9caf8da6 主线路图样式 2025-07-19 14:14:21 +08:00
658fa5042c 单体电池数据点击可以查看表格 2025-07-18 19:15:05 +08:00
62eb0e7e7d 单体电池表格曲线图,统计报表概览统计没有数据返回 隐藏 2025-07-17 17:47:18 +08:00
e9c8b10d29 主线路图样式 2025-07-16 22:23:28 +08:00
c837caa78a 工单管理新增图片展示、预计完成时间 2025-07-15 19:29:37 +08:00
5ee8a6d17f 策略运行按钮展示问题 2025-07-13 20:16:46 +08:00
ffa76c5a7a 主线路图 2025-07-13 20:10:30 +08:00
c0bed77a5d 策略模板时间冲突校验 2025-07-13 17:40:24 +08:00
d0a31f9ee8 策略 2025-07-13 16:13:45 +08:00
be1fb382ff 策略运行 2025-07-13 00:29:19 +08:00
3308887e4c 头像展示优化 2025-07-12 16:54:18 +08:00
bcfeb04fd3 头像展示、设备新增弹窗滚动位置优化 2025-07-12 16:48:00 +08:00
21bbaa5c30 设备列表 2025-07-12 16:23:07 +08:00
8fc9f975e1 设备列表 2025-07-12 16:18:15 +08:00
893c690e7d 设备列表 隐藏查看详情按钮 2025-07-12 15:58:00 +08:00
ee6628a19b 设备列表、工单列表 2025-07-12 15:55:05 +08:00
111079eb48 优化 20250710 2025-07-11 17:24:45 +08:00
b84bb3fd89 工单管理 2025-07-11 17:14:02 +08:00
b22795134d 工单管理部分接口联调 2025-07-11 00:58:47 +08:00
be4d99fb94 主线路图 2025-07-11 00:14:58 +08:00
2509077842 功率曲线页面loading 2025-07-10 21:14:35 +08:00
28cf2d1946 单站监控 站点首页、故障告警页面新增工单按钮 点击跳转到工单列表页面 2025-07-10 21:10:04 +08:00
0336e67ac5 电表报表 2025-07-09 23:52:19 +08:00
b8827248cb 首页看板、电表报表接口联调,部分接口参数更新 2025-07-09 21:16:54 +08:00
b3b4d9c0dc 概率统计接口联调 2025-07-09 00:03:52 +08:00
cc190e42f8 电池平均SOC滚动 2025-07-07 22:16:19 +08:00
ec59f226b3 Merge branch 'develop' of http://101.43.41.9:13000/xzzn/emsfront into develop 2025-07-07 22:10:53 +08:00
6523e061bd db接口参数更新,实时运行接口联调 2025-07-07 22:10:25 +08:00
59d2a09644 pcs顶部方块样式调整 2025-07-04 15:12:25 +08:00
21a8871e3c bms总览接口参数更新 2025-07-03 21:59:40 +08:00
bf92b8010b Merge branch 'develop' of http://101.43.41.9:13000/xzzn/emsfront into develop 2025-07-03 21:37:04 +08:00
d06feb2078 站点管理菜单调整,新增设备列表页面 2025-07-03 21:34:46 +08:00
4d58c9883c 修改站点首页 2025-07-03 16:57:23 +08:00
ec35538afd 新增站点管理页面 2025-07-02 19:51:39 +08:00
328f893da9 有分页的表格,搜索或重置的时候pageNum切换到第一页 2025-07-02 17:44:14 +08:00
c53bc4dc45 bcm总览接口参数更新,电量指标没有返回数据时图表显示问题 2025-07-02 16:57:34 +08:00
cacd939dd6 概率统计-电量指标接口联调,站点信息6个方块样式调整 2025-07-01 22:41:45 +08:00
b59b564544 取消单体电池搜索栏的清空按钮 2025-07-01 15:20:51 +08:00
0861c3e15f 搜索栏清空按钮 2025-07-01 15:14:07 +08:00
edab894447 Merge branch 'develop' into baicai 2025-07-01 14:43:33 +08:00
99b019ebc9 故障告警接口联调 2025-07-01 14:42:53 +08:00
9b78635fd8 故障告警 2025-07-01 14:11:41 +08:00
ab9a13264c 单体电池表格新增簇号字段 2025-07-01 12:56:00 +08:00
46490aebee 故障告警接口联调 2025-07-01 10:45:13 +08:00
b8779bee61 Merge branch 'develop' into baicai 2025-07-01 09:35:40 +08:00
c296e63df4 单体电池表格温度取值错误解决 2025-07-01 09:35:09 +08:00
3fa574d18b 单体电池接口参数更新 2025-06-30 22:47:17 +08:00
ca7e9a60b2 故障告警部分功能 2025-06-30 22:44:18 +08:00
d763eb2cd8 单体电池接口参数更新 2025-06-30 22:10:23 +08:00
7aafcf2113 接口联调 2025-06-30 19:06:56 +08:00
62fff8c9f4 接口联调 2025-06-30 18:56:33 +08:00
3a6c668476 工单管理 2025-06-30 18:39:41 +08:00
71cd3cd76c 电表、单体电池接口联调 2025-06-30 18:37:37 +08:00
86cbf64a9d pcs头部数据处理 2025-06-30 17:57:09 +08:00
f64b5d30cb 合并最新代码 解决冲突 2025-06-30 17:38:22 +08:00
e42f1204ee 接口联调 2025-06-30 17:32:04 +08:00
5f4fcdf754 问题修正 2025-06-30 14:18:32 +08:00
b3598f1ba8 init 2025-06-28 20:03:49 +08:00
37d2d9bde5 单体电池切换站点时清空搜索项 2025-06-28 15:22:08 +08:00
d0d4c25377 pcs、bms接口联调 2025-06-28 14:52:49 +08:00
13310948c3 策略配置 2025-06-26 16:49:55 +08:00
1b19a91d74 新增策略页面 2025-06-26 01:29:16 +08:00
89711b6a07 单词拼写纠正 2025-06-25 12:55:58 +08:00
6ad00c60fe 更新图片大小、调整logo样式 2025-06-25 11:29:31 +08:00
5f055ce5c9 更新logo 登陆北京图片、项目Copyright 2025-06-25 11:02:55 +08:00
e8333c6552 站点地图重复调用问题 2025-06-25 00:17:08 +08:00
d68d48ddee 页面样式调整 2025-06-24 23:50:29 +08:00
6cec2849ba 页面样式调整 2025-06-24 22:48:33 +08:00
7145afd5a0 新增概率统计报表=>电表报表、电池温度 2025-06-24 19:52:11 +08:00
4d9e8e5373 选择框精确到日 2025-06-24 13:26:40 +08:00
76ffff363a 概率统计页面背景颜色问题 2025-06-23 20:03:37 +08:00
f556105030 新增概率统计报表=>统计页面 2025-06-23 19:51:17 +08:00
282568273d 提取公共的表格样式 新增故障告警页面 2025-06-23 13:56:43 +08:00
02d3ab04bb 站点选择下拉框的数据获取、存储处理逻辑更新,解决因为多层router-view嵌套,二级路由以上页面不会被缓存的而导致单站监控页面站点选择下拉框闪烁的问题 2025-06-22 22:20:34 +08:00
dea617d853 设备监控=>PCS、BMS总览、BMS电池簇页面,单站监控首页页面样式调整 2025-06-22 21:01:34 +08:00
201587e13e 新增单体电池、电表、液冷页面 2025-06-22 17:22:40 +08:00
5ae09461be 新增单站监控=>设备监控=>PCS、BMS总览、BMS电池簇页面 2025-06-21 23:59:01 +08:00
48c93b3efd 使用全等判断 删除不需要的log 2025-06-20 14:19:05 +08:00
51a479923e 站点地图、单站监控-实时运行部分接口联调 2025-06-20 13:53:36 +08:00
c19d829a87 站点地图展示,站点地图、单站监控首页部分接口联调 2025-06-20 02:00:10 +08:00
4218776b77 单站监控菜单栏 2025-06-18 01:01:17 +08:00
9174e8b6aa 提取公共的card样式 2025-06-17 00:46:20 +08:00
da100a4fa7 更新单词拼写错误,新增首页 2025-06-16 22:46:40 +08:00
f2dff98cba 站点地图刷新后菜单不高亮问题 2025-06-16 16:20:48 +08:00
b2fdb2d6b8 新增站点地图静态页面,项目新增ems router文件、components文件、mixin文件、scss文件和view文件 2025-06-16 15:54:10 +08:00
138 changed files with 16743 additions and 503 deletions

View File

@ -1,11 +1,15 @@
# 页面标题
VUE_APP_TITLE = EMS管理系统
VUE_APP_TITLE = 上动新能源-EMS管理系统
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true
# 开发环境配置
ENV = 'development'
NODE_ENV = 'development'
# EMS管理系统/开发环境
VUE_APP_BASE_API = '/dev-api'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true
# EMS管理系统/开发环境 图片拼接地址
VUE_APP_IMG_URL = '/dev-api'

View File

@ -1,8 +1,13 @@
# 页面标题
VUE_APP_TITLE = EMS管理系统
VUE_APP_TITLE = 上动新能源-EMS管理系统
# 生产环境配置
ENV = 'production'
NODE_ENV = 'production'
# EMS管理系统/生产环境
VUE_APP_BASE_API = '/dev-api'
VUE_APP_BASE_API= 'http://127.0.0.1:8089'
# EMS管理系统/生产环境 图片拼接地址
VUE_APP_IMG_URL = 'http://127.0.0.1:8089'

View File

@ -1,12 +1,12 @@
# 页面标题
VUE_APP_TITLE = EMS管理系统
BABEL_ENV = production
NODE_ENV = production
VUE_APP_TITLE = 上动新能源-EMS管理系统
# 测试环境配置
ENV = 'staging'
NODE_ENV = 'staging'
# EMS管理系统/测试环境
VUE_APP_BASE_API = '/stage-api'
VUE_APP_BASE_API= 'http://110.40.171.179:8089'
# EMS管理系统/测试环境 图片拼接地址
VUE_APP_IMG_URL = 'http://110.40.171.179:8089'

View File

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="icon" href="<%= BASE_URL %>logo-icon.png">
<title><%= webpackConfig.name %></title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>

BIN
public/logo-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -10,7 +10,7 @@ import ThemePicker from "@/components/ThemePicker"
export default {
name: "App",
components: { ThemePicker }
components: { ThemePicker },
}
</script>
<style scoped>

384
src/api/ems/dzjk.js Normal file
View File

@ -0,0 +1,384 @@
import request from '@/utils/request'
//获取单个站点的信息
export function getDzjkHomeView(siteId) {
return request({
url: `/ems/siteMonitor/homeView?siteId=${siteId}`,
method: 'get'
})
}
//站点首页 冲放曲线
export function getSevenChargeData({siteId, startDate, endDate}) {
return request({
url: `/ems/siteMap/getSevenChargeData?siteId=${siteId}&startDate=${startDate}&endDate=${endDate}`,
method: 'get'
})
}
// 获取站点包含的设备种类 用来判断单站监控设备监控的菜单栏展示
export function getSiteAllDeviceCategory(siteId) {
return request({
url: `/ems/siteConfig/getSiteAllDeviceCategory?siteId=${siteId}`,
method: 'get'
})
}
//EMS
export function getEmsDataList(siteId) {
return request({
url: `/ems/siteMonitor/getEmsDataList?siteId=${siteId}`,
method: 'get',
})
}
//获取pcs、实时运行头部的设备信息
export function getRunningHeadInfo(siteId) {
return request({
url: `/ems/siteMonitor/runningHeadInfo?siteId=${siteId}`,
method: 'get'
})
}
//获取pcs列表
export function getPcsDetailInfo(siteId) {
return request({
url: `/ems/siteMonitor/getPcsDetailInfo?siteId=${siteId}`,
method: 'get'
})
}
//获取BMS总览数据
export function getBMSOverView(siteId) {
return request({
url: `/ems/siteMonitor/getBMSOverView?siteId=${siteId}`,
method: 'get'
})
}
//获取BMS电池簇总览数据
export function getBMSBatteryCluster(siteId) {
return request({
url: `/ems/siteMonitor/getBMSBatteryCluster?siteId=${siteId}`,
method: 'get'
})
}
//获取单体电池 电池堆列表数据
export function getStackNameList(siteId) {
return request({
url: `/ems/siteMonitor/getStackNameList?siteId=${siteId}`,
method: 'get'
})
}
//获取单体电池 电池簇列表数据
export function getClusterNameList({stackDeviceId, siteId}) {
return request({
url: `/ems/siteMonitor/getClusterNameList?stackDeviceId=${stackDeviceId}&siteId=${siteId}`,
method: 'get'
})
}
//单体电池表格数据
export function getClusterDataInfoList({siteId, stackDeviceId, clusterDeviceId, batteryId, pageSize, pageNum}) {
return request({
url: `/ems/siteMonitor/getClusterDataInfoList?clusterDeviceId=${clusterDeviceId}&siteId=${siteId}&stackDeviceId=${stackDeviceId}&batteryId=${batteryId}&pageSize=${pageSize}&pageNum=${pageNum}`,
method: 'get'
})
}
// 单体电池图表
//http://localhost:8089/ems/siteMonitor/getSingleBatteryData?clusterDeviceId=BMSC01&siteId=021_FXX_01&deviceId=001&startDate=2025-07-11&endDate=2025-07-18
export function getSingleBatteryData({siteId, deviceId, clusterDeviceId, startDate, endDate}) {
return request({
url: `/ems/siteMonitor/getSingleBatteryData?siteId=${siteId}&deviceId=${deviceId}&startDate=${startDate}&endDate=${endDate}&clusterDeviceId=${clusterDeviceId}`,
method: 'get'
})
}
//获取液冷列表数据
export function getCoolingDataList(siteId) {
return request({
url: `/ems/siteMonitor/getCoolingDataList?siteId=${siteId}`,
method: 'get'
})
}
//获取动环数据
export function getDhDataList(siteId) {
return request({
url: `/ems/siteMonitor/getDhDataList?siteId=${siteId}`,
method: 'get'
})
}
//获取消防数据
export function getXfDataList(siteId) {
return request({
url: `/ems/siteMonitor/getXfDataList?siteId=${siteId}`,
method: 'get'
})
}
//获取电表数据
export function getAmmeterDataList(siteId) {
return request({
url: `/ems/siteMonitor/getAmmeterDataList?siteId=${siteId}`,
method: 'get'
})
}
// 故障告警
export function getAlarmDetailList({
status,
siteId,
deviceId,
alarmLevel,
alarmStartTime,
alarmEndTime,
pageSize,
pageNum
}) {
return request({
url: `/ems/siteAlarm/getAlarmDetailList?siteId=${siteId}&status=${status}&deviceId=${deviceId}&alarmLevel=${alarmLevel}&alarmStartTime=${alarmStartTime}&alarmEndTime=${alarmEndTime}&pageSize=${pageSize}&pageNum=${pageNum}`,
method: 'get'
})
}
// 告警生成工单
export function createTicketNo(data) {
return request({
url: `/ems/siteAlarm/createTicketNo`,
method: 'post',
data
})
}
// 概率统计
//获取概率统计 电量指标接口
export function getElectricData({siteId, startDate, endDate}) {
return request({
url: `/ems/statsReport/getElectricData?siteId=${siteId}&startDate=${startDate}&endDate=${endDate}`,
method: 'get'
})
}
//获取pcs列表
export function getPcsNameList(siteId) {
return request({
url: `/ems/siteMonitor/getPcsNameList?siteId=${siteId}`,
method: 'get'
})
}
//pcs曲线
export function getPCSData({siteId, startTime, endTime, dataType}) {
return request({
url: `/ems/statsReport/getPCSData?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}&dataType=${dataType}`,
method: 'get'
})
}
//电池堆曲线
export function getStackData({siteId, startTime, endTime, dataType}) {
return request({
url: `/ems/statsReport/getStackData?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}&dataType=${dataType}`,
method: 'get'
})
}
//电池温度
export function getClusterData({siteId, stackId, clusterId, dateTime, pageNum, pageSize}) {
return request({
url: `/ems/statsReport/getClusterData?siteId=${siteId}&stackId=${stackId}&clusterId=${clusterId}&dateTime=${dateTime}&pageNum=${pageNum}&pageSize=${pageSize}`,
method: 'get'
})
}
// 实时运行
//储能
export function storagePower(siteId, startTime, endTime) {
return request({
url: `/ems/siteMonitor/runningGraph/storagePower?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}`,
method: 'get'
})
}
//poc温度
export function pcsMaxTemp(siteId, startTime, endTime) {
return request({
url: `/ems/siteMonitor/runningGraph/pcsMaxTemp?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}`,
method: 'get'
})
}
// 电池平均soc
export function batteryAveSoc(siteId, startTime, endTime) {
return request({
url: `/ems/siteMonitor/runningGraph/batteryAveSoc?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}`,
method: 'get'
})
}
// 电池平均温度
export function batteryAveTemp(siteId, startTime, endTime) {
return request({
url: `/ems/siteMonitor/runningGraph/batteryAveTemp?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}`,
method: 'get'
})
}
// 功率曲线
export function getPowerData({siteId, startDate, endDate}) {
return request({
url: `/ems/statsReport/getPowerData?siteId=${siteId}&startDate=${startDate}&endDate=${endDate}`,
method: 'get'
})
}
//电表列表
export function getLoadNameList(siteId) {
return request({
url: `/ems/statsReport/getLoadNameList?siteId=${siteId}`,
method: 'get'
})
}
// 电表报表
export function getAmmeterData({siteId, startTime, endTime, pageSize, pageNum}) {
return request({
url: `/ems/statsReport/getAmmeterData?siteId=${siteId}&startTime=${startTime}&endTime=${endTime}&pageSize=${pageSize}&pageNum=${pageNum}`,
method: 'get'
})
}
// 电价报表
export function getAmmeterRevenueData(data) {
return request({
url: `/ems/statsReport/getAmmeterRevenueData`,
method: 'get',
params: data
})
}
//策略列表
export function strategyRunningList(siteId) {
return request({
url: `/system/strategyRunning/list?siteId=${siteId}`,
method: 'get'
})
}
//停止策略
export function stopStrategyRunning(id) {
return request({
url: `/system/strategyRunning/stop?id=${id}`,
method: 'get'
})
}
// 获取所有主策略
export function getMainStrategyList() {
return request({
url: `/system/strategyRunning/getMainStrategyList`,
method: 'get'
})
}
//获取所有辅助策略
export function getAuxStrategyList() {
return request({
url: `/system/strategyRunning/getAuxStrategyList`,
method: 'get'
})
}
//配置策略
export function configStrategy(data) {
return request({
url: `/system/strategyRunning/configStrategy`,
method: 'post',
data
})
}
//http://localhost:8089/strategy/temp/getTempNameList?strategyId=1&siteId=021_FXX_01
//获取策略下的所有模板列表
export function getTempNameList({siteId, strategyId}) {
return request({
url: `/strategy/temp/getTempNameList?siteId=${siteId}&strategyId=${strategyId}`,
method: 'get',
})
}
//获取模板详情
///strategy/temp/list?templateId=1
export function getStrategyTempDetail(templateId) {
return request({
url: `/strategy/temp/list?templateId=${templateId}`,
method: 'get',
})
}
//新增模板
export function addStrategyTemp(data) {
return request({
url: `/strategy/temp`,
method: 'post',
data
})
}
export function editStrategyTemp(data) {
return request({
url: `/strategy/temp`,
method: 'put',
data
})
}
//http://localhost:8089/strategy/temp/{id}
export function deleteStrategyTemp(id) {
return request({
url: `/strategy/temp/` + id,
method: 'delete',
})
}
//http://localhost:8089/strategy/timeConfig/list?strategyId=1&siteId=021_FXX_01
export function timeConfigList({siteId, strategyId}) {
return request({
url: `/strategy/timeConfig/list?siteId=${siteId}&strategyId=${strategyId}`,
method: 'get',
})
}
//保存时间配置
// http://localhost:8089/strategy/timeConfig
export function setTimeConfigList(data) {
return request({
url: `/strategy/timeConfig`,
method: 'post',
data
})
}
// 策略曲线图
//http://localhost:8089/strategy/curve/curveList?strategyId=1&siteId=021_FXX_01
export function curveList({siteId, strategyId}) {
return request({
url: `/strategy/curve/curveList?siteId=${siteId}&strategyId=${strategyId}`,
method: 'get',
})
}
//单站监控 首页 当日功率曲线
export function getPointData({siteId, startDate, endDate}) {
return request({
url: `/ems/siteMonitor/getPointData?siteId=${siteId}&startDate=${startDate}&endDate=${endDate}`,
method: 'get',
})
}

15
src/api/ems/home.js Normal file
View File

@ -0,0 +1,15 @@
import request from '@/utils/request'
// 首页、站点地图页面页面顶部 所有站点信息
export function getSiteTotalInfo() {
return request({
url: '/ems/homePage/getSiteTotalInfo',
method: 'get'
})
}
// 图表
export function dataList() {
return request({
url: '/ems/homePage/dataList',
method: 'get'
})
}

View File

@ -0,0 +1,42 @@
import request from '@/utils/request'
// 新增
export function addPriceConfig(data) {
return request({
url: '/ems/energyPriceConfig',
method: 'post',
data
})
}
//修改
export function editPriceConfig(data) {
return request({
url: '/ems/energyPriceConfig',
method: 'put',
data
})
}
//删除
export function energyPriceConfig(id) {
return request({
url: `/ems/energyPriceConfig/${id}`,
method: 'DELETE',
})
}
//详情
export function detailPriceConfig(id) {
return request({
url: `/ems/energyPriceConfig/${id}`,
method: 'get',
})
}
//列表
export function listPriceConfig({startTime,endTime,pageSize,pageNum,siteId}) {
return request({
url: `/ems/energyPriceConfig/list?startTime=${startTime}&endTime=${endTime}&pageNum=${pageNum}&pageSize=${pageSize}&siteId=${siteId}`,
method: 'get',
})
}

32
src/api/ems/search.js Normal file
View File

@ -0,0 +1,32 @@
import request from '@/utils/request'
// 获取设备列表
export function getAllDeviceCategory() {
return request({
url: '/ems/generalQuery/getAllDeviceCategory',
method: 'get'
})
}
// 点位列表
export function pointFuzzyQuery(data) {
return request({
url: '/ems/generalQuery/pointFuzzyQuery',
method: 'post',
data
})
}
// 图表
export function getPointValueList(data) {
return request({
url: '/ems/generalQuery/getPointValueList',
method: 'post',
data
})
}
// 图表
export function getAllBatteryIdsBySites(data) {
return request({
url: `/ems/generalQuery/getAllBatteryIdsBySites/${data}`,
method: 'get',
})
}

203
src/api/ems/site.js Normal file
View File

@ -0,0 +1,203 @@
import request from '@/utils/request'
// 站点列表
export function getSiteInfoList({siteName, startTime, endTime, pageSize, pageNum}) {
return request({
url: `/ems/siteConfig/getSiteInfoList?siteName=${siteName}&startTime=${startTime}&endTime=${endTime}&pageSize=${pageSize}&pageNum=${pageNum}`,
method: 'get'
})
}
// 设备列表
export function getDeviceInfoList(data) {
return request({
url: `/ems/siteConfig/getDeviceInfoList`,
method: 'get',
params: data
})
}
// 设备详情
export function getDeviceDetailInfo(id) {
return request({
url: `/ems/siteConfig/getDeviceDetailInfo?id=${id}`,
method: 'get'
})
}
// 获取所有设备类别
export function getDeviceCategory() {
return request({
url: `/ems/siteConfig/getDeviceCategory`,
method: 'get'
})
}
// 新增设备
export function addDevice(data) {
return request({
url: `/ems/siteConfig/addDevice`,
method: 'post',
data
})
}
// 编辑设备
export function updateDevice(data) {
return request({
url: `/ems/siteConfig/updateDevice`,
method: 'post',
data
})
}
// 删除设备
export function deleteService(id) {
return request({
url: `/ems/siteConfig/deleteService/` + id,
method: 'delete',
})
}
//pcs开、关机
export function updateDeviceStatus(data) {
return request({
url: `/ems/siteConfig/updateDeviceStatus`,
method: 'post',
data
})
}
// 获取上级设备id列表
export function getParentDeviceId({siteId, deviceCategory}) {
return request({
url: `/ems/siteConfig/getParentDeviceId?siteId=${siteId}&deviceCategory=${deviceCategory}`,
method: 'get',
})
}
//获取所有设备
export function getDeviceList(siteId) {
return request({
url: `/ems/siteConfig/getDeviceList?siteId=${siteId}`,
method: 'get',
})
}
//获取设备点位table
export function getDevicePointList(data) {
return request({
url: `/ems/siteConfig/getDevicePointList`,
method: 'get',
params: data
})
}
//获取设备类型下面的所有设备列表
export function getDeviceListBySiteAndCategory({siteId, deviceCategory}) {
return request({
url: `/ems/siteConfig/getDeviceListBySiteAndCategory?siteId=${siteId}&deviceCategory=${deviceCategory}`,
method: 'get',
})
}
//新增设备保护
export function addProtectPlan(data) {
return request({
url: `/ems/protectPlan`,
method: 'post',
data
})
}
//修改设备保护
export function updateProtectPlan(data) {
return request({
url: `/ems/protectPlan`,
method: 'put',
data
})
}
//删除设备保护
export function deleteProtectPlan(id) {
return request({
url: `/ems/protectPlan/${id}`,
method: 'delete',
})
}
//设备保护详情
export function getProtectPlan(id) {
return request({
url: `/ems/protectPlan/${id}`,
method: 'get',
})
}
//设备保护详情列表
//http://localhost:8089/ems/protectPlan/list?pageSize=10&pageNum=1&faultName=总压&siteId=021_DDS_01
export function protectPlanList({siteId, faultName, pageSize, pageNum}) {
return request({
url: `/ems/protectPlan/list?siteId=${siteId}&faultName=${faultName}&pageSize=${pageSize}&pageNum=${pageNum}`,
method: 'get',
})
}
// 点位导出
export function exportPointList(data) {
return request({
url: `/ems/pointMatch/export`,
method: 'post',
data
})
}
// 点位导入
export function importPointList(data) {
return request({
url: `/ems/pointMatch/importData`,
method: 'post',
data
})
}
//mqtt
export function getMqttList({pageSize, pageNum, mqttTopic, topicName, siteId}) {
return request({
url: `/ems/mqttConfig/list?pageSize=${pageSize}&pageNum=${pageNum}&mqttTopic=${mqttTopic}&topicName=${topicName}&siteId=${siteId}`,
method: 'get',
})
}
export function getMqttDetail(id) {
return request({
url: `/ems/mqttConfig/${id}`,
method: 'get',
})
}
export function addMqtt(data) {
return request({
url: `/ems/mqttConfig`,
method: 'post',
data
})
}
export function editMqtt(data) {
return request({
url: `/ems/mqttConfig`,
method: 'put',
data
})
}
export function deleteMqtt(id) {
return request({
url: `/ems/mqttConfig/${id}`,
method: 'delete',
})
}

52
src/api/ems/ticket.js Normal file
View File

@ -0,0 +1,52 @@
import request from '@/utils/request'
// 查询工单主列表
export function listTicket({pageNum, pageSize}) {
return request({
url: `/ticket/list?pageNum=${pageNum}&pageSize=${pageSize}`,
method: 'get',
})
}
// 查询工单主详细
export function getTicket(id) {
return request({
url: '/ticket/' + id,
method: 'get'
})
}
// 新增工单主
export function addTicket(data) {
return request({
url: '/ticket',
method: 'post',
data: data
})
}
// 修改工单主
export function updateTicket(data) {
return request({
url: '/ticket',
method: 'put',
data: data
})
}
// 删除工单主
export function delTicket(data) {
return request({
url: `/ticket/drop`,
method: 'post',
data
})
}
//查询所有的用户列表
export function getAllUser() {
return request({
url: `/system/user/getAllUser`,
method: 'get',
})
}

15
src/api/ems/zddt.js Normal file
View File

@ -0,0 +1,15 @@
import request from '@/utils/request'
// 查询缓存名称列表
export function getAllSites() {
return request({
url: '/ems/homePage/getAllSites',
method: 'get'
})
}
//获取单个站点的基本信息
export function getSingleSiteBaseInfo(siteId) {
return request({
url: `/ems/siteMap/getSingleSiteBaseInfo?siteId=${siteId}`,
method: 'get'
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 KiB

View File

@ -0,0 +1,333 @@
/*
ems管理平台公共css样式
*/
//右侧内容区域
//父元素
.ems-dashboard-editor-container{
background-color: #FFFFFF;//#F1F5FC
padding: 24px;
font-size: 12px;
}
//除去顶部信息(如搜索栏、站点基本信息等)外的 白色背景内容区域
.ems-content-container {
background-color: #ffffff;
margin-top: 24px;
}
//需要设置内padding的白色背景区域
.ems-content-container-padding {
padding: 24px;
}
//card通用样式 标题、body
.common-card-container {
.el-card__header {
padding: 14px;
border-bottom: none;
font-size: 12px;
background: #F1F5FB;
position: relative;
.card-title {
font-weight: 500;
color: #333333;
}
.el-button--text {
color: #666666;
}
}
}
.common-card-container-body-no-padding {
.el-card__body {
padding: 0;
}
}
.common-card-container-no-title-bg {
.el-card__header {
background-color: transparent;
}
}
//单站监控 设备监控card公共样式
.sbjk-card-container {
.el-card__header {
background-color: transparent;
padding: 5px 14px;
color: #ffffff;
position: relative;
border-radius: 5px 5px 0 0;
.large-title {
font-size: 18px;
font-weight: 500;
line-height: 40px;
padding: 0 50px 0 11px;
display: inline-block;
vertical-align: middle;
}
.info {
display: inline-block;
vertical-align: middle;
color: #ffffff;
font-size: 12px;
line-height: 20px;
}
.el-button--text {
color: #666666;
}
.alarm {
position: absolute;
right: 25px;
top: 50%;
transform: translateY(-50%);
.alarm-icon {
font-size: 22px;
color: #fff;
display: block;
cursor: pointer;
}
}
}
//红色背景颜色标题
&.warning-card-container {
.el-card__header {
background-color: #b64040; //#fc6b69;
}
.work-status {
color: #b64040 !important;;
}
}
//绿色背景颜色标题
&.running-card-container {
.el-card__header {
background-color: #40b6a5; //#05aea3;
}
.work-status {
color: #40b6a5 !important;
}
}
//灰色背景颜色标题
&.timing-card-container {
.el-card__header {
background-color: #666666;
}
.work-status {
color: #666666 !important;;
}
}
}
/* card标题里的时间选择器 */
.time-range-card {
&.common-card-container .el-card__header {
padding-top: 0;
padding-bottom: 0;
.time-range-header {
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
.card-title {
line-height: 40px;
}
}
}
}
//描述样式 PCS、BMS总览、BMS电池簇页面公共样式
.descriptions-main {
padding: 24px;
position: relative;
&.descriptions-main-bg-color {
background-color: #f1f5fc;
.el-descriptions__body {
background-color: #f1f5fc;
}
}
.el-descriptions-item__cell[colspan='1'] {
width: 25%
}
.el-descriptions__body .el-descriptions__table {
.descriptions-direction {
line-height: 19px;
color: #666666;
font-size: 16px;
font-weight: 500;
}
.descriptions-label {
line-height: 14px;
color: #666666;
font-size: 12px;
}
.danger {
color: #FC6B69;
}
.save {
color: #09ADA3;
}
.keep {
color: #3C81FF;
}
}
}
//电表、液冷公共样式
.device-info-row {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-left: 1px solid #eee;
border-top: 1px solid #eee;
.device-info-col {
padding: 10px 0;
text-align: center;
font-size: 12px;
color: #666666;
line-height: 14px;
border-bottom: 1px solid #eee;
border-right: 1px solid #eee;
.left {
}
.right {
display: block;
font-weight: 500;
font-size: 16px;
line-height: 18px;
margin-top: 10px;
}
}
}
//公共表格样式
.el-table {
font-size: 13px;
}
.common-table.el-table {
color: #333333;
.el-table__header-wrapper th, .el-table__fixed-header-wrapper th {
background: #f1f5fc;
border-bottom: none;
.table-head {
color: #515a6e;
}
}
.warning-status {
color: #FC6B69;
&.circle::before {
content: "";
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #FC6B69;
margin-right: 8px;
}
}
}
//二、三级菜单栏样式
.ems-second-menu {
width: fit-content;
.el-menu-item {
line-height: 40px;
height: 40px;
padding: 0;
}
&.el-menu--horizontal > .el-menu-item.is-active, &.el-menu--horizontal > .el-menu-item {
border-bottom: none !important;
}
.el-menu-item.is-active {
background: #0366c1 !important;
}
}
.ems-third-menu-container {
position: relative;
padding-left: 160px;
background-color: #ffffff;
.ems-third-menu {
border-right: none;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
height: fit-content;
position: absolute;
top:20px;
left:20px;
.el-menu-item{
line-height: 45px;
height: 45px;
padding: 0 !important;
width: 125px;
text-align: center;
}
.el-menu-item:hover {
background: #67b1ff !important;
color: #ffffff !important;
}
.el-menu-item.is-active {
background: #409eff !important;
}
}
}
//按钮栏 选中样式
.ems-btns-group {
.activeBtn {
background-color: #0366c1;
border-color: #0366c1;
color: #ffffff;
font-weight: 600;
}
}
//搜索栏样式
.select-container.el-form--inline .el-form-item {
margin-right: 15px;
}
//红色背景颜色按钮
.alarm-btn, .alarm-btn:hover, .alarm-btn:focus {
background-color: #FC6B69;
border-color: #FC6B69;
}

View File

@ -4,6 +4,7 @@
@import './element-ui.scss';
@import './sidebar.scss';
@import './btn.scss';
@import './common.scss';
body {
height: 100%;

View File

@ -0,0 +1,137 @@
<template>
<el-dialog
:fullscreen="true"
:append-to-body="true"
:visible.sync="show"
:show-close="false"
top="0"
custom-class="big-data-dialog"
>
<div class="swiper-container">
<div class="swiper-icon left-icon" v-show="imgIndex > 0">
<i class="el-icon-d-arrow-left icon" @click="toLeft"></i>
</div>
<div v-show="showRightIcon" class="swiper-icon right-icon">
<i class="el-icon-d-arrow-right icon" @click="toRight"></i>
</div>
<div
class="img-container"
:style="{ transform: 'translateX(' + imgIndex * -100 + 'vw)' }"
>
<img
v-for="index in maxImgNumber"
:key="'swiperImg' + index"
:src="require(`@/assets/images/ems/bigData-${index}.png`)"
alt=""
/>
</div>
</div>
<div class="close-btn" @click="show = false">
<i class="el-icon-close"></i>
</div>
</el-dialog>
</template>
<style lang="scss" scoped>
.close-btn {
position: absolute;
right: 10px;
top: 10px;
font-size: 23px;
line-height: 20px;
color: rgba(217, 242, 255, 1);
cursor: pointer;
}
.swiper-container {
height: 100%;
width: 100%;
overflow: hidden;
position: relative;
.swiper-icon {
color: rgba(217, 242, 255, 1);
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 20;
cursor: pointer;
font-size: 30px;
padding: 20px;
background: transparent;
&.left-icon {
left: 20px;
}
&.right-icon {
right: 20px;
}
&:hover {
.icon {
opacity: 1;
}
}
.icon {
transition: all 0.6s;
opacity: 0;
}
}
.img-container {
height: 100%;
transition: all 1s;
display: flex;
flex-direction: row;
z-index: 0;
img {
width: 100vw;
height: 100vh;
display: block;
margin: 0;
flex-shrink: 0;
}
}
}
</style>
<style lang="scss">
.big-data-dialog {
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 0;
margin: 0;
position: relative;
}
}
</style>
<script>
export default {
data() {
return {
show: false,
imgIndex: 0,
maxImgNumber: 3,
};
},
computed: {
showRightIcon() {
return this.imgIndex < this.maxImgNumber - 1;
},
},
watch: {
show: {
handler(newValue) {
if (!newValue) this.imgIndex = 0;
},
immediate: true,
},
},
methods: {
toLeft() {
if (this.imgIndex === 0) return;
this.imgIndex -= 1;
},
toRight() {
if (this.imgIndex >= this.maxImgNumber - 1) return;
this.imgIndex += 1;
},
},
};
</script>

View File

@ -0,0 +1,150 @@
<template>
<div class="time-range">
<el-date-picker
v-model="dateRange"
:class="miniTimePicker ? 'mini-date-picker' : ''"
type="daterange"
range-separator=""
start-placeholder="开始时间"
value-format="yyyy-MM-dd"
:clearable="false"
:picker-options="pickerOptions"
end-placeholder="结束时间">
</el-date-picker>
<template v-if="!showIcon">
<el-button size="mini" style="margin-left: 10px;" :loading="loading" @click="reset">重置</el-button>
<el-button type="primary" size="mini" :loading="loading" @click="search">搜索</el-button>
<el-button type="primary" size="mini" :loading="loading" @click="timeLine('before')">上一时段</el-button>
<el-button type="primary" size="mini" :loading="loading" @click="timeLine('next')" :disabled="disabledNextBtn">
下一时段
</el-button>
</template>
<template v-else>
<el-button class="btn-icon" icon="el-icon-refresh-right" circle size="mini" style="margin-left: 8px;"
:loading="loading"
@click="reset"></el-button>
<el-button class="btn-icon" type="primary" size="mini" icon="el-icon-search" circle :loading="loading"
@click="search"></el-button>
<el-button class="btn-icon" type="primary" size="mini" icon="el-icon-d-arrow-left" circle :loading="loading"
@click="timeLine('before')"></el-button>
<el-button class="btn-icon" type="primary" size="mini" icon="el-icon-d-arrow-right" circle :loading="loading"
@click="timeLine('next')"
:disabled="disabledNextBtn"></el-button>
</template>
</div>
</template>
<script>
import {formatDate} from '@/filters/ems'
export default {
props: {
showIcon: {
type: Boolean,
required: false,
default: false
},
miniTimePicker: {
type: Boolean,
required: false,
default: false
}
},
computed: {
disabledNextBtn() {
return new Date(this.dateRange[1]) >= new Date(this.defaultDateRange[1])
}
},
data() {
return {
loading: false,
dateRange: [],
defaultDateRange: [],
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
},
},
}
},
methods: {
init(today = false) {
const now = new Date(), formatNow = formatDate(now);
const weekAgo = formatDate(today ? new Date(now.getTime()) : new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000))
this.dateRange = [weekAgo, formatNow];
this.defaultDateRange = [weekAgo, formatNow];
this.$emit('updateDate', this.dateRange)
},
showBtnLoading(status) {
this.loading = status
},
resetDate() {
this.dateRange = this.defaultDateRange
},
//重置 设置时间范围为初始化时间段
reset() {
this.resetDate()
this.$emit('reset')
this.$emit('updateDate', this.dateRange)
},
// 搜索
search() {
this.$emit('updateDate', this.dateRange)
},
timeLine(type) {
if (!this.dateRange || !this.dateRange[0] || !this.dateRange[1]) return
const nowStartTimes = new Date(this.dateRange[0]).getTime(), nowEndTimes = new Date(this.dateRange[1]).getTime(),
maxTime = new Date(this.defaultDateRange[1]).getTime()
const nowDis = nowEndTimes - nowStartTimes//用户当前选择时间差 可能=0
//baseTime,maxTime 毫秒数
const baseDis = 24 * 60 * 60 * 1000
const calcDis = nowDis === 0 ? baseDis : nowDis
let start = type === 'before' ? nowStartTimes - calcDis : nowStartTimes + calcDis
if (start > maxTime) start = maxTime
let end = type === 'before' ? nowEndTimes - calcDis : nowEndTimes + calcDis
if (end > maxTime) end = maxTime
this.dateRange = [formatDate(start), formatDate(end)]
this.$emit('updateDate', this.dateRange)
},
}
}
</script>
<style lang="scss" scoped>
.time-range {
display: flex;
::v-deep {
.el-range-editor--medium .el-range__icon, .el-range-editor--medium .el-range__close-icon {
line-height: 22px;
}
.el-range-editor--medium.el-input__inner {
height: 30px;
}
.el-range-editor--medium .el-range-separator {
line-height: 22px;
}
.el-button--mini {
padding: 3px 10px;
}
// 展示icon的小组件
.btn-icon.el-button--mini {
padding: 3px 8px;
margin-left: 6px;
}
//小宽度时间选择框
.mini-date-picker {
width: 250px !important;
}
}
}
</style>

View File

@ -0,0 +1,35 @@
<!--单独的背景颜色渐变宽高100%的内容展示方块组件-->
<template>
<el-card shadow="always" class="single-square-box" :style="{background: 'linear-gradient(180deg, '+data.bgColor+' 0%,rgba(255,255,255,0) 100%)'}">
<div class="single-square-box-title">{{ data.title }}</div>
<div class="single-square-box-value">{{ data.value | formatNumber }}</div>
</el-card>
</template>
<style scoped lang="scss">
.single-square-box{
width: 100%;
height: 100%;
box-sizing: border-box;
color:#666666;
text-align: left;
.single-square-box-title{
font-size: 12px;
line-height: 12px;
padding-bottom: 12px;
}
.single-square-box-value{
font-size: 26px;
line-height: 26px;
font-weight: 500;
}
::v-deep .el-card__body{
padding: 12px 10px;
}
}
</style>
<script>
export default {
props: ['data'],
}
</script>

View File

@ -0,0 +1,89 @@
<!--首页地图站点页面顶部信息方块-->
<template>
<el-row type="flex" >
<el-card shadow="hover" class="card common-card-container-body-no-padding" v-for="(item,index) in data" :key="index+'zdInfo'" :style="{borderBottomColor:item.color}">
<div class="info">{{ item.title }}</div>
<div class="num">{{item.num | formatNumber}}</div>
</el-card>
</el-row>
</template>
<script>
import { getSiteTotalInfo } from "@/api/ems/home"
export default {
data() {
return {
data:[{
title:'站点总数(座)',
num:'',
color:'#FFBD00',
attr:'siteNum'
},{
title:'装机功率MW',
num:'',
color:'#3C81FF',
attr:'installPower'
},{
title:'装机容量MW',
num:'',
color:'#5AC7C0',
attr:'installCapacity'
},{
title:'总充电量KWh',
num:'',
color:'#A696FF',
attr:'totalChargedCap'
},{
title:'总放电量KWh',
num:'',
color:'#A696FF',
attr:'totalDischargedCap'
}]
}
},
methods: {
setData(res = {}){
this.data.forEach((item)=>{
item.num =res[item.attr]
})
}
},
mounted() {
getSiteTotalInfo().then(response => {
console.log('单个站点基本信息返回数据',response)
this.setData(response?.data || {})
}).catch(()=>{
this.setData({})
})
}
}
</script>
<style scoped lang="scss">
.card{
min-width: 150px;
height: 96px;
margin-right: 27px;
border-bottom: 4px solid transparent;
text-align: center;
box-sizing: border-box;
padding:0 10px;
.info{
color: #666666;
line-height: 14px;
padding-top: 18px;
}
.num{
color: rgba(51,51,51,1);
font-size: 26px;
line-height: 26px;
font-weight: 500;
margin-top: 14px;
}
}
</style>

View File

@ -0,0 +1,91 @@
<!--站点选择组件-->
<template>
<div class="zd-select-container">
<el-form :inline="true">
<el-form-item label="站点选择">
<el-select v-model="id" placeholder="请选择换电站名称" :loading="loading" loading-text="正在加载数据" @change="onSubmit">
<el-option :label="item.siteName" :value="item.siteId" v-for="(item,index) in siteList" :key="index+'zdxeSelect'"></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item>-->
<!-- <el-button type="primary" :loading="searchLoading" @click="onSubmit">搜索</el-button>-->
<!-- </el-form-item>-->
</el-form>
</div>
</template>
<style scoped lang="scss">
</style>
<script>
import {getAllSites} from '@/api/ems/zddt'
import {mapGetters} from "vuex"
export default {
props:{
getListByStore:{
type:Boolean,
default:false,
required:false
},
defaultSiteId:{//默认展示的站点ID
type:String,
default:'',
required:false
}
},
data() {
return {
id:'',
loading:false,
searchLoading:false,
siteList:[],
}
},
computed:{
...mapGetters(["zdList"]),
},
methods:{
onSubmit(){
this.$emit('submitSite',this.id)
},
setDefaultSite(){
const defaultSite = this.defaultSiteId
if(defaultSite && this.siteList.find(item=>item.siteId === defaultSite)){
this.id = defaultSite
}else if(!defaultSite && this.siteList.length>0){
this.id = this.siteList[0].siteId
}
this.$emit('submitSite',this.id)
},
getList(){
return getAllSites().then(response => {
this.siteList = response.data || []
console.log("获取站点列表返回数据",response,this.siteList)
this.setDefaultSite()
}).finally(() => {this.loading=false;this.searchLoading=false})
}
},
mounted(){
this.loading=true
this.searchLoading=true
this.$nextTick(()=>{
if(this.getListByStore){
if(this.zdList.length === 0){
this.getList().then(()=>{
this.$store.commit('SET_ZD_LIST', this.siteList)
console.log("从store中获取站点列表数据,但是store中的zdList=[],所以从接口获取数据",this.zdList,this.siteList)
})
}else{
this.siteList = this.zdList
this.loading=false
this.searchLoading=false
console.log("从store中获取站点列表数据",this.zdList,this.siteList)
this.setDefaultSite()
}
}else{
this.getList()
}
})
}
}
</script>

View File

@ -196,7 +196,9 @@ export default {
// 上传成功回调
handleUploadSuccess(res, file) {
if (res.code === 200) {
this.uploadList.push({ name: res.fileName, url: res.fileName })
this.uploadList.push({ name: res.fileName, url: res.url })
// todo
// this.uploadList.push({ name: res.fileName, url: res.fileName })
this.uploadedSuccessfully()
} else {
this.number--
@ -256,7 +258,7 @@ export default {
::v-deep .el-upload-list--picture-card.is-disabled + .el-upload--picture-card {
display: none !important;
}
}
// 去掉动画效果
::v-deep .el-list-enter-active,

1
src/data/ems/china.json Normal file

File diff suppressed because one or more lines are too long

24
src/filters/ems.js Normal file
View File

@ -0,0 +1,24 @@
export const formatNumber = (val) => {
if(val || [0,'0'].includes(val)) {
return val
}else {return '-'}
}
export const formatDate = (val,toSeconds = false,onlyTime=false) => {
if(!val) return ''
const date = new Date(val)
const month = date.getMonth() + 1,day = date.getDate()
const hours = date.getHours(),minuets=date.getMinutes(),seconds = date.getSeconds();
const front = `${date.getFullYear()}-${month<10?'0'+month : month}-${day<10 ? '0'+day : day}`
const back = `${hours<10 ? '0'+hours : hours}:${minuets<10 ? '0'+minuets : minuets}:${seconds<10 ? '0'+seconds : seconds}`
if(onlyTime) return back
if(!toSeconds){
return front
} else{
return front +' '+back
}
}

View File

@ -1,10 +1,11 @@
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<!-- todo 单站监控页面中二级路由选择时不需要切换页面的动态效果-->
<!-- <transition name="fade-transform" mode="out-in">-->
<keep-alive :include="cachedViews">
<router-view v-if="!$route.meta.link" :key="key" />
</keep-alive>
</transition>
<!-- </transition>-->
<iframe-toggle />
<copyright />
</section>

View File

@ -1,19 +1,24 @@
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container"
@toggleClick="toggleSideBar"/>
<breadcrumb v-if="!topNav" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="topNav" id="topmenu-container" class="topmenu-container" />
<breadcrumb v-if="!topNav" id="breadcrumb-container" class="breadcrumb-container"/>
<top-nav v-if="topNav" id="topmenu-container" class="topmenu-container"/>
<div class="right-menu">
<template v-if="device!=='mobile'">
<search id="header-search" class="right-menu-item" />
<div class="big-data-container">
<i class="el-icon-s-marketing big-data-icon" @click.stop="showBigDataImg"></i>
</div>
<search id="header-search" class="right-menu-item"/>
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<screenfull id="screenfull" class="right-menu-item hover-effect"/>
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
<size-select id="size-select" class="right-menu-item hover-effect"/>
</el-tooltip>
</template>
@ -32,22 +37,23 @@
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<div class="right-menu-item hover-effect setting" @click="setLayout" v-if="setting">
<svg-icon icon-class="more-up" />
<svg-icon icon-class="more-up"/>
</div>
</div>
<BigDataPopup ref="bigDataPopup"/>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import {mapGetters} from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'
import BigDataPopup from '@/components/BigDataPopup'
export default {
emits: ['setLayout'],
@ -57,7 +63,8 @@ export default {
Hamburger,
Screenfull,
SizeSelect,
Search
Search,
BigDataPopup
},
computed: {
...mapGetters([
@ -78,6 +85,9 @@ export default {
}
},
methods: {
showBigDataImg() {
this.$refs.bigDataPopup.show = true
},
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
@ -93,7 +103,8 @@ export default {
this.$store.dispatch('LogOut').then(() => {
location.href = '/index'
})
}).catch(() => {})
}).catch(() => {
})
}
}
}
@ -105,7 +116,7 @@ export default {
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08);
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
.hamburger-container {
line-height: 46px;
@ -113,7 +124,7 @@ export default {
float: left;
cursor: pointer;
transition: background .3s;
-webkit-tap-highlight-color:transparent;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, .025)
@ -139,6 +150,17 @@ export default {
height: 100%;
line-height: 50px;
.big-data-container {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 24px;
color: #5a5e66;
vertical-align: text-bottom;
cursor: pointer;
}
&:focus {
outline: none;
}
@ -168,6 +190,7 @@ export default {
.avatar-wrapper {
margin-top: 10px;
position: relative;
padding-right: 10px;
.user-avatar {
cursor: pointer;
@ -176,7 +199,7 @@ export default {
border-radius: 50%;
}
.user-nickname{
.user-nickname {
position: relative;
bottom: 10px;
font-size: 14px;

View File

@ -1,45 +1,60 @@
<template>
<div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
<div
class="sidebar-logo-container"
:class="{ collapse: collapse }"
:style="{
backgroundColor:
sideTheme === 'theme-dark'
? variables.menuBackground
: variables.menuLightBackground,
}"
>
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
<router-link
v-if="collapse"
key="collapse"
class="sidebar-logo-link"
to="/"
>
<img :src="logoSmall" class="sidebar-logo" />
<!-- <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>-->
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
<img :src="logo" class="sidebar-logo" />
<!-- <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>-->
</router-link>
</transition>
</div>
</template>
<script>
import logoImg from '@/assets/logo/logo.png'
import variables from '@/assets/styles/variables.scss'
import variables from "@/assets/styles/variables.scss";
import logo from "@/assets/images/ems/logo.png";
import logoSmall from "@/assets/images/ems/logo-small.png";
export default {
name: 'SidebarLogo',
name: "SidebarLogo",
props: {
collapse: {
type: Boolean,
required: true
}
required: true,
},
},
computed: {
variables() {
return variables
return variables;
},
sideTheme() {
return this.$store.state.settings.sideTheme
}
return this.$store.state.settings.sideTheme;
},
},
data() {
return {
title: process.env.VUE_APP_TITLE,
logo: logoImg
}
}
}
logo: logo,
logoSmall: logoSmall,
};
},
};
</script>
<style lang="scss" scoped>
@ -66,10 +81,10 @@ export default {
width: 100%;
& .sidebar-logo {
width: 32px;
height: 32px;
width: 100%;
height: 100%;
vertical-align: middle;
margin-right: 12px;
//margin-right: 12px;
}
& .sidebar-title {

View File

@ -55,3 +55,12 @@ export default {
}
}
</script>
<style lang="scss" scoped>
::v-deep{
//,.el-submenu.is-active>.el-submenu__title 选中了二级菜单的以及菜单
.el-menu-item.is-active{
background-color: rgba(0,0,0,0.1) !important;
}
}
</style>

View File

@ -36,6 +36,8 @@ import DictTag from '@/components/DictTag'
// 字典数据组件
import DictData from '@/components/DictData'
import {formatNumber} from '@/filters/ems'
// 全局方法挂载
Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey
@ -74,7 +76,7 @@ Vue.use(Element, {
})
Vue.config.productionTip = false
Vue.filter('formatNumber', formatNumber)
new Vue({
el: '#app',
router,

View File

@ -0,0 +1,25 @@
// 用于单站监控二级菜单页面获取路由中的站点ID
const getQuerySiteId= {
data: function () {
return {
siteId:''
}
},
watch: {
'$store.state.ems.zdList':{
handler (newQuery,oldQuery) {
if(!newQuery || newQuery.length === 0){return}
// 参数变化处理
this.$nextTick(() => {
const {siteId} =newQuery[0]
this.siteId = siteId
siteId && this.init(newQuery.siteId)
console.log('watch站点列表返回数据newQuery=',newQuery)
console.log('设置页面siteIdthis.siteId=',this.siteId)
})
},
immediate: true,
}
},
}
export default getQuerySiteId

View File

@ -0,0 +1,24 @@
// 定时刷新
const intervalUpdate= {
data: function () {
return {
intervalUpdateTimer:null
}
},
beforeDestroy() {
console.log('销毁之前 清空定时器')
if( this.intervalUpdateTimer) {
window.clearInterval(this.intervalUpdateTimer)
this.intervalUpdateTimer = null
}
},
methods:{
updateInterval: function (cn,time=60000) {
window.clearInterval(this.intervalUpdateTimer)
this.intervalUpdateTimer = null
this.intervalUpdateTimer = window.setInterval(cn,time)
}
}
}
export default intervalUpdate

56
src/mixins/ems/resize.js Normal file
View File

@ -0,0 +1,56 @@
import { debounce } from '@/utils'
export default {
data() {
return {
$_sidebarElm: null,
$_resizeHandler: null
}
},
mounted() {
this.initListener()
},
activated() {
if (!this.$_resizeHandler) {
// avoid duplication init
this.initListener()
}
// when keep-alive chart activated, auto resize
this.resize()
},
beforeDestroy() {
this.destroyListener()
},
deactivated() {
this.destroyListener()
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.$_resizeHandler()
}
},
initListener() {
this.$_resizeHandler = debounce(() => {
this.resize()
}, 100)
window.addEventListener('resize', this.$_resizeHandler)
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
destroyListener() {
window.removeEventListener('resize', this.$_resizeHandler)
this.$_resizeHandler = null
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
},
resize() {
const { chart } = this
chart && chart.resize()
}
}
}

View File

@ -31,6 +31,7 @@ router.beforeEach((to, from, next) => {
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
store.dispatch('getZdList')
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表

267
src/router/ems.js Normal file
View File

@ -0,0 +1,267 @@
import Layout from "@/layout/index.vue";
//单站监控
// todo 本地设置了 hidden:true,不会显示在侧边栏,需要在系统管理、菜单管理中手动添加菜单后才会展示在侧边栏
export const dzjk = [
{
path: '/dzjk',
component: Layout,
redirect: '/dzjk/home',
meta: {title: '单站监控', icon: 'server',},
alwaysShow: false,
name: 'Dzjk',
children: [
{
path: '/dzjk/home',
component: () => import('@/views/ems/dzjk/home/index.vue'),
name: 'DzjkHome',
meta: {title: '站点首页', breadcrumb: false, activeMenu: '/dzjk/home', activeSecondMenuName: 'DzjkHome'}
},
{
path: '/dzjk/zxlt',
component: () => import('@/views/ems/dzjk/zxlt/index.vue'),
name: 'DzjkZxlt',
meta: {title: '主线路图', breadcrumb: false, activeMenu: '/dzjk/zxlt', activeSecondMenuName: 'DzjkZxlt'}
},
{
path: '/dzjk/sbjk',
component: () => import('@/views/ems/dzjk/sbjk/index.vue'),
name: 'DzjkSbjk',
alwaysShow: false,
meta: {title: '设备监控', breadcrumb: false, activeMenu: '/dzjk/sbjk'},
hidden: false,
redirect: '/dzjk/sbjk/ssyx',
children: [
{
path: 'ssyx',
component: () => import('@/views/ems/dzjk/sbjk/ssyx/index.vue'),
name: 'DzjkSbjkSsyx',
hidden: true,
meta: {
title: '实时运行',
breadcrumb: false,
activeMenu: '/dzjk/sbjk',
activeSecondMenuName: 'DzjkSbjk',
deviceCategory: 'SSYX'
},
},
{
path: 'ems',
component: () => import('@/views/ems/dzjk/sbjk/ems/index.vue'),
name: 'DzjkSbjkEms',
hidden: true,
meta: {
title: 'EMS',
breadcrumb: false,
activeMenu: '/dzjk/sbjk',
activeSecondMenuName: 'DzjkSbjk',
deviceCategory: 'EMS'
},
},
{
path: 'pcs',
component: () => import('@/views/ems/dzjk/sbjk/pcs/index.vue'),
name: 'DzjkSbjkPcs',
hidden: true,
meta: {
title: 'PCS',
breadcrumb: false,
activeMenu: '/dzjk/sbjk',
activeSecondMenuName: 'DzjkSbjk',
deviceCategory: 'PCS'
},
},
{
path: 'bmszl',
component: () => import('@/views/ems/dzjk/sbjk/bmszl/index.vue'),
name: 'DzjkSbjkBmszl',
hidden: true,
meta: {
title: 'BMS总览',
breadcrumb: false,
activeMenu: '/dzjk/sbjk',
activeSecondMenuName: 'DzjkSbjk',
deviceCategory: 'STACK'
},
},
{
path: 'bmsdcc',
component: () => import('@/views/ems/dzjk/sbjk/bmsdcc/index.vue'),
name: 'DzjkSbjkBmsdcc',
hidden: true,
meta: {
title: 'BMS电池簇',
breadcrumb: false,
activeMenu: '/dzjk/sbjk',
activeSecondMenuName: 'DzjkSbjk',
deviceCategory: 'CLUSTER'
},
},
{
path: 'dtdc',
component: () => import('@/views/ems/dzjk/sbjk/dtdc/index.vue'),
name: 'DzjkSbjkDtdc',
hidden: true,
meta: {
title: '单体电池',
breadcrumb: false,
activeMenu: '/dzjk/sbjk',
activeSecondMenuName: 'DzjkSbjk',
deviceCategory: 'BATTERY'
},
},
{
path: 'db',
component: () => import('@/views/ems/dzjk/sbjk/db/index.vue'),
name: 'DzjkSbjkDb',
hidden: true,
meta: {
title: '电表',
breadcrumb: false,
activeMenu: '/dzjk/sbjk',
activeSecondMenuName: 'DzjkSbjk',
deviceCategory: 'AMMETER'
},
},
{
path: 'yl',
component: () => import('@/views/ems/dzjk/sbjk/yl/index.vue'),
name: 'DzjkSbjkYl',
hidden: true,
meta: {
title: '液冷',
breadcrumb: false,
activeMenu: '/dzjk/sbjk',
activeSecondMenuName: 'DzjkSbjk',
deviceCategory: 'COOLING'
},
},
{
path: 'dh',
component: () => import('@/views/ems/dzjk/sbjk/dh/index.vue'),
name: 'DzjkSbjkDh',
hidden: true,
meta: {
title: '动环',
breadcrumb: false,
activeMenu: '/dzjk',
activeSecondMenuName: 'DzjkSbjk',
deviceCategory: 'DH'
},
},
{
path: 'xf',
component: () => import('@/views/ems/dzjk/sbjk/xf/index.vue'),
name: 'DzjkSbjkXf',
hidden: true,
meta: {
title: '消防',
breadcrumb: false,
activeMenu: '/dzjk',
activeSecondMenuName: 'DzjkSbjk',
deviceCategory: 'XF'
},
}
]
},
{
path: '/dzjk/gzgj',
component: () => import('@/views/ems/dzjk/gzgj/index.vue'),
name: 'DzjkGzgj',
meta: {title: '故障告警', breadcrumb: false, activeMenu: '/dzjk/gzgj', activeSecondMenuName: 'DzjkGzgj'}
},
{
path: '/dzjk/tjbb',
component: () => import('@/views/ems/dzjk/tjbb/index.vue'),
name: 'DzjkTjbb',
alwaysShow: false,
meta: {title: '统计报表', breadcrumb: false, activeMenu: '/dzjk/tjbb'},
hidden: false,
redirect: '/dzjk/tjbb/gltj',
children: [
{
path: 'gltj',
component: () => import('@/views/ems/dzjk/tjbb/gltj/index.vue'),
name: 'DzjkTjbbGltj',
hidden: true,
meta: {
title: '运行统计',
breadcrumb: false,
activeMenu: '/dzjk/tjbb',
activeSecondMenuName: 'DzjkTjbb'
},
},
{
path: 'glqx',
component: () => import('@/views/ems/dzjk/tjbb/glqx/index.vue'),
name: 'DzjkTjbbGlqx',
hidden: true,
meta: {
title: '功率曲线',
breadcrumb: false,
activeMenu: '/dzjk/tjbb',
activeSecondMenuName: 'DzjkTjbb'
},
},
{
path: 'pcsqx',
component: () => import('@/views/ems/dzjk/tjbb/pcsqx/index.vue'),
name: 'DzjkTjbbPcsqx',
hidden: true,
meta: {
title: 'PCS曲线',
breadcrumb: false,
activeMenu: '/dzjk/tjbb',
activeSecondMenuName: 'DzjkTjbb'
},
},
{
path: 'dcdqx',
component: () => import('@/views/ems/dzjk/tjbb/dcdqx/index.vue'),
name: 'DzjkTjbbDcdqx',
hidden: true,
meta: {
title: '电池堆曲线',
breadcrumb: false,
activeMenu: '/dzjk/tjbb',
activeSecondMenuName: 'DzjkTjbb'
},
},
{
path: 'dcwd',
component: () => import('@/views/ems/dzjk/tjbb/dcwd/index.vue'),
name: 'DzjkTjbbDcwd',
hidden: true,
meta: {
title: '电池温度',
breadcrumb: false,
activeMenu: '/dzjk/tjbb',
activeSecondMenuName: 'DzjkTjbb'
},
},
{
path: 'dbbb',
component: () => import('@/views/ems/dzjk/tjbb/dbbb/index.vue'),
name: 'DzjkTjbbDbbb',
hidden: true,
meta: {
title: '电表报表',
breadcrumb: false,
activeMenu: '/dzjk/tjbb',
activeSecondMenuName: 'DzjkTjbb'
},
}
]
},
{
path: '/dzjk/clpz',
component: () => import('@/views/ems/dzjk/clpz/clyx/index.vue'),
name: 'DzjkClpz',
meta: {title: '策略配置', breadcrumb: false, activeMenu: '/dzjk/clpz'},
}
]
}
]

View File

@ -1,5 +1,6 @@
import Vue from 'vue'
import Router from 'vue-router'
import {dzjk} from '@/router/ems'
Vue.use(Router)
@ -68,7 +69,7 @@ export const constantRoutes = [
children: [
{
path: 'index',
component: () => import('@/views/index'),
component: () => import('@/views/ems/home/index'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true }
}
@ -87,7 +88,9 @@ export const constantRoutes = [
meta: { title: '个人中心', icon: 'user' }
}
]
}
},
// EMS管理系统routers
...dzjk
]
// 动态路由,基于用户权限动态去加载

View File

@ -12,7 +12,7 @@ module.exports = {
/**
* 系统布局配置
*/
showSettings: true,
showSettings: false,
/**
* 是否显示顶部导航
@ -52,5 +52,5 @@ module.exports = {
/**
* 底部版权文本内容
*/
footerContent: 'Copyright © 2018-2025 xzzn. All Rights Reserved.'
footerContent: 'Copyright © 2025 上动新能源 版权所有'
}

View File

@ -16,6 +16,7 @@ const getters = {
permission_routes: state => state.permission.routes,
topbarRouters: state => state.permission.topbarRouters,
defaultRoutes: state => state.permission.defaultRoutes,
sidebarRouters: state => state.permission.sidebarRouters
sidebarRouters: state => state.permission.sidebarRouters,
zdList:state=>state.ems.zdList
}
export default getters

View File

@ -6,6 +6,7 @@ import user from './modules/user'
import tagsView from './modules/tagsView'
import permission from './modules/permission'
import settings from './modules/settings'
import ems from './modules/ems'
import getters from './getters'
Vue.use(Vuex)
@ -17,7 +18,8 @@ const store = new Vuex.Store({
user,
tagsView,
permission,
settings
settings,
ems
},
getters
})

81
src/store/modules/ems.js Normal file
View File

@ -0,0 +1,81 @@
import {getAllSites} from '@/api/ems/zddt'
import {getAlarmDetailList, getSiteAllDeviceCategory} from '@/api/ems/dzjk'
const ems = {
state: {
dzjkAlarmLighting: false,//单站监控 告警统计红点标志
zdList: [],
zdDeviceCategoryOptions: {},//站点各个站点包含的设备种类 {021_DDS_01:["BATTERY","CLUSTER","STACK", "DH", "AMMETER", "PCS", "XF"],021_DDS_02:[]...}
CLUSTERWorkStatusOptions: {'0': '静置', '1': '充电', '2': '放电', '3': '待机', '5': '运行', '9': "故障"},//电池簇工作状态
PCSWorkStatusOptions: {'0': '运行', '1': '停机', '2': '故障', '3': '待机', '4': '充电', '5': '放电'},//PCS工作状态
STACKWorkStatusOptions: {
"0": "静置",
"1": "充电",
"2": "放电",
"3": "浮充",
'4': '待机',
'5': '运行',
'9': "故障"
},//STACKBMS总览工作状态
deviceStatusOptions: {'0': '离线', '1': '在线'},//设备状态
gridStatusOptions: {'0': '并网', '1': '未并网'},//并网状态
controlModeOptions: {'0': '远程', '1': '本地'},//控制模式
warnOptions: {0: '正常', 1: '中断', 2: '不在线', 3: '异常'},//告警状态
communicationStatusOptions: {'0': '正常', '1': '通讯中断', '2': '异常'},//通讯状态
workModeOptions: {'0': '正常', '1': '停止'},//工作模式
alarmLevelOptions: {'A': '提示', 'B': '一般', 'C': '严重', 'D': '紧急'},//告警等级
alarmStatusOptions: {'0': '待处理', '1': '已处理', '2': '处理中'},//告警状态
deviceTypeOptions: {'TCP': 'TCP', 'RTU': 'RTU'},//设备类型
ticketStatusOptions: {1: '待处理', 2: '处理中', 3: '已处理'},//工单处理状态
strategyStatusOptions: {'0': '未启用', '1': '已运行', '2': '已暂停', '3': '禁用', '4': '删除'},//策略状态
chargeStatusOptions: {'1': '充电', '2': '待机', '3': '放电'},//冲放状态
comparisonOperatorOptions: {'>': '>', '<': '<', '=': '=', '>=': '>=', '<=': '<='},
relationWithPoint: {'||': '||', '&&': '&&'}
},
mutations: {
SET_ZD_LIST(state, list) {
state.zdList = list || []
},
SET_DZJK_ALARM_LIGHTING(state, status) {
state.dzjkAlarmLighting = status
},
SET_ZD_DEVICE_CATEGORY_OPTIONS(state, {siteId, data}) {
state.zdDeviceCategoryOptions = Object.assign({}, state.zdDeviceCategoryOptions, {[siteId]: data})
}
},
actions: {
getZdList({commit, state}) {
if (state.zdList.length === 0) {
getAllSites().then(response => {
commit('SET_ZD_LIST', response?.data || [])
console.log('store action getZdList 获取站点数据', state.zdList)
})
}
},
//查询站点的所有待处理0的告警 存在展示红点标志
getSiteAlarmNum({state, commit}, siteId) {
getAlarmDetailList({
status: 0,
siteId,
pageSize: 10,
pageNum: 1,
deviceId: '',
alarmLevel: '',
alarmStartTime: '',
alarmEndTime: ''
}).then(response => {
commit('SET_DZJK_ALARM_LIGHTING', !!response?.total || false)
})
},
getSiteDeviceCategory({state, commit}, siteId) {
getSiteAllDeviceCategory(siteId).then(response => {
let data = response?.data || [];
data.unshift('SSYX');
commit('SET_ZD_DEVICE_CATEGORY_OPTIONS', {siteId, data})
})
}
}
}
export default ems

View File

@ -34,6 +34,13 @@ const permission = {
return new Promise(resolve => {
// 向后端请求路由数据
getRouters().then(res => {
let sysDzjk = -1
if(res?.data){
sysDzjk = res.data.findIndex(i=>{
return i.children && i.children.find(j=>j.path.indexOf('dzjk')>-1)
})
if(sysDzjk>-1) res.data.splice(sysDzjk,1)
}
const sdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data))
const sidebarRoutes = filterAsyncRouter(sdata)
@ -41,6 +48,10 @@ const permission = {
const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
router.addRoutes(asyncRoutes)
if(sysDzjk === -1){
const index = constantRoutes.findIndex(i=>i.path.indexOf('dzjk')>-1)
constantRoutes.splice(index,1)
}
commit('SET_ROUTES', rewriteRoutes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
commit('SET_DEFAULT_ROUTES', sidebarRoutes)
@ -111,7 +122,7 @@ export function filterDynamicRoutes(routes) {
}
export const loadView = (view) => {
if (process.env.NODE_ENV === 'development') {
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'staging') {
return (resolve) => require([`@/views/${view}`], resolve)
} else {
// 使用 import 实现生产环境的路由懒加载

View File

@ -65,7 +65,7 @@ const user = {
const user = res.user
let avatar = user.avatar || ""
if (!isHttp(avatar)) {
avatar = (isEmpty(avatar)) ? defAva : process.env.VUE_APP_BASE_API + avatar
avatar = (isEmpty(avatar)) ? defAva : process.env.VUE_APP_IMG_URL + avatar
}
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', res.roles)

View File

@ -17,7 +17,7 @@ const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
timeout: 60000
})
// request拦截器

View File

@ -11,7 +11,7 @@ export function isPathMatch(pattern, path) {
}
/**
* 判断value字符串是否为空
* 判断value字符串是否为空
* @param {string} value
* @returns {Boolean}
*/
@ -23,7 +23,7 @@ export function isEmpty(value) {
}
/**
* 判断url是否是http或https
* 判断url是否是http或https
* @param {string} url
* @returns {Boolean}
*/
@ -112,3 +112,13 @@ export function isArray(arg) {
}
return Array.isArray(arg)
}
/**
* @param {string}
* @returns {Boolean}
* 只能输入中文、英文、数字、特殊字符!@#$%^&*-,./
*/
export function validText(text) {
const reg = /^[a-zA-Z0-9\u4e00-\u9fa5!@#$%^&*-,.]+$/
return reg.test(text)
}

View File

@ -0,0 +1,119 @@
<template>
<el-container class="clyx-container">
<el-header class="clyx-header">
<div class="clyx-title">策略状态<span class="clyx-status save">{{strategyStatusOptions[info.status]}}</span></div>
<div class="clyx-btns">
<el-button v-if="!hideSettingBtn" size="small" @click="$emit('showSetting')">配置</el-button>
<el-button v-if="info.status === '1'" type="warning" class="alarm-btn" size="small" @click='close'>停止策略</el-button>
</div>
</el-header>
<el-main class="clyx-main">
<slot name="default"></slot>
</el-main>
</el-container>
</template>
<script>
import {mapState} from "vuex";
import {stopStrategyRunning} from '@/api/ems/dzjk'
export default {
name:'DzjkClpzClContainer',
props:{
hideSettingBtn:{
type:Boolean,
default:false
},
info:{
type:Object,
default:()=>{return {}},
}
},
computed:{
...mapState({
strategyStatusOptions: state => state.ems.strategyStatusOptions,
})
},
data() {
return {
loading:true
}
},
methods:{
close(){
this.$confirm('确认要停止策略吗?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
showClose:false,
closeOnClickModal:false,
type: 'warning',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
this.loading = true
stopStrategyRunning(this.info.id).then(response => {
response.code === 200 && done();
}).finally(() => {
this.loading = false
instance.confirmButtonLoading = false;
})
} else {
done();
}
}
}).then(() => {
//只有在故障复位成功的情况下会走到这里
this.$message({
type: 'success',
message: '停止策略成功!'
});
this.$emit('update')
}).catch(() => {
//取消复位
});
}
},
mounted(){
}
}
</script>
<style scoped lang="scss">
.clyx-container{
border:1px solid #eee;
.clyx-header{
background: #F1F5FB;
display: flex;
position: relative;
justify-content: flex-start;
align-items: center;
padding: 0;
height: 60px;
border-radius: 6px 6px 0 0;
.clyx-title{
color: #333333;
line-height: 20px;
padding: 0 50px 0 25px;
.clyx-status{
font-weight: 500;
font-size: 16px;
&.danger{
color:#FC6B69;
}
&.save{
color:#09ADA3;
}
&.keep{
color:#3C81FF;
}
}
}
.clyx-btns{
position: absolute;
right: 25px;
top: 50%;
transform: translateY(-50%);
}
}
}
</style>

View File

@ -0,0 +1,107 @@
<template>
<el-dialog
title="主从策略配置"
custom-class="ems-dialog"
:visible.sync="dialogFormVisible"
width="420px"
destroy-on-close
lock-scroll
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="false"
@close="cancel"
>
<el-form ref="form" :model="form" :rules='rule' label-width="100px" v-loading="loading > 0">
<el-form-item label="主策略" prop="mainStrategyId">
<el-select v-model="form.mainStrategyId" placeholder="请选择">
<el-option :label="item.strategyName" :value="item.id" v-for="(item,index) in mainStrategyList" :key="index+'mainStrategyList'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="辅助策略" prop="auxStrategyId">
<el-select v-model="form.auxStrategyId" placeholder="请选择" clearable>
<el-option :label="item.strategyName" :value="item.id" v-for="(item,index) in auxStrategyList" :key="index+'auxStrategyList'"></el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="primary" :loading="loading > 0" @click="sure"> </el-button>
</div>
</el-dialog>
</template>
<script>
import {getMainStrategyList, getAuxStrategyList, configStrategy} from '@/api/ems/dzjk'
export default {
inject:['$home'],
data(){
return {
info:{},
loading:0,
dialogFormVisible:false,
mainStrategyList:[],
auxStrategyList:[],
form:{
mainStrategyId:'',
auxStrategyId:''
},
rule:{
mainStrategyId:[
{ required: true, message: '请选择主策略', trigger: ['blur','change'] },
]
}
}
},
methods:{
showSettingDialog(item){
console.log('打开弹窗',item)
this.dialogFormVisible = true
this.info = item || {}
const {auxStrategyId = '',mainStrategyId = ''} = item
this.form.auxStrategyId=auxStrategyId
this.form.mainStrategyId=mainStrategyId
this.init()
},
init(){
this.loading+=2
this.mainStrategyList=[]
this.auxStrategyList=[]
getMainStrategyList().then(response => {
this.mainStrategyList=response?.data || []
}).finally(()=>{this.loading-=1})
getAuxStrategyList().then(response => {
this.auxStrategyList=response?.data || []
}).finally(()=>{this.loading-=1})
},
cancel(){
this.$refs.form.resetFields()
this.form={
mainStrategyId:'',
auxStrategyId:''
}
this.dialogFormVisible = false;
},
sure(){
this.$refs['form'].validate(valid => {
if (!valid) return
console.log('this.$home',this.$home)
this.loading +=1
const {mainStrategyId='', auxStrategyId=''} = this.form
configStrategy({mainStrategyId,auxiliaryStrategyId:auxStrategyId,siteId:this.$home.siteId,id:this.info?.id || ''}).then(response => {
if(response?.code === 200){
this.$home.init()
this.cancel()
}
}).finally(()=>{
this.loading -=1
})
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,129 @@
<template>
<div v-loading="loading" class="ems-dashboard-editor-container ems-content-container-padding">
<template v-if="!showTemp">
<el-button v-if="!showTemp" type="primary" plain @click="settingStrategy" style="margin-bottom: 20px;">新增策略</el-button>
<cl-container v-for="(item,index) in list" :key="index+'clContainer'" :info="item" :hide-setting-btn="showTemp" class="contain" @update="init" @showSetting="settingStrategy(item)">
<template v-slot:default>
<div class="cl-items cl-items-main" @click="toDetail(item.mainStrategyId,item.mainStrategyName)">
<div class="cl-header">主策略</div>
<div class="cl-content" >
<div class="cl-name">{{item.mainStrategyName}}</div>
</div>
</div>
<div class="cl-items" v-show="item.auxStrategyName" @click="toDetail(item.auxStrategyId,item.auxStrategyName)">
<div class="cl-header">辅助策略</div>
<div class="cl-content">
<div class="cl-name">{{item.auxStrategyName}}</div>
</div>
</div>
</template>
</cl-container>
<div v-if="list.length === 0">
<el-empty :image-size="200" ></el-empty>
</div>
</template>
<xftg ref="xftgTemp" v-else/>
<setting ref="setting"/>
</div>
</template>
<script>
import Setting from './../Setting.vue'
import ClContainer from './../ClContainer.vue'
import getQuerySiteId from '@/mixins/ems/getQuerySiteId'
import Xftg from './../xftg/index.vue'
import {strategyRunningList} from '@/api/ems/dzjk'
export default {
name:'DzjkClpzClyx',
components:{ClContainer,Setting,Xftg},
mixins: [getQuerySiteId],
data() {
return {
showTemp:false,
updateStrategyId:'',
updateStrategyName:'',
loading:false,
list:[],
}
},
provide(){
return {$home:this}
},
methods:{
settingStrategy(item=''){
this.$refs.setting.showSettingDialog(item)
},
init(){
this.loading = true
this.list = []
strategyRunningList(this.siteId).then(response => {
this.list =JSON.parse(JSON.stringify(response?.data || {}))
}).finally(() => {this.loading=false})
this.$nextTick(() => {
// 每次切换站点 展示策略运行 清空策略配置的数据
this.$refs['xftgTemp'] && this.$refs['xftgTemp'].changeSiteId()
this.showTemp=false
this.updateStrategyId=''
this.updateStrategyName=''
})
},
toDetail(id,name){
this.updateStrategyId=id
this.updateStrategyName = name
this.showTemp = true
this.$nextTick(() => {
this.$refs['xftgTemp'].init()
})
}
},
}
</script>
<style scoped lang="scss">
.contain{
margin-bottom: 20px;
}
.clyx-main{
padding:0 12px 0 12px;
.cl-items{
cursor: pointer;
width: 228px;
height: 156px;
display: inline-block;
margin:24px 12px 0 12px;
font-size:14px;
color:#333333;
border: 1px solid #eeeeee;
.cl-header{
background: #FFE5AC;
text-align: center;
font-size:16px;
line-height: 40px;
height: 40px;
width: 100%;
}
.cl-content{
background-color: #ffffff;
position: relative;
height: 114px;
width: 100%;
.cl-name{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
}
}
&.cl-items-main{
.cl-header{
background: #FFBE29;
}
.cl-content{
background-color: #FFF2CB;
}
}
}
}
</style>

View File

@ -0,0 +1,325 @@
<template>
<el-dialog :visible.sync="dialogTableVisible" class="ems-dialog add-template-dialog"
:title="mode === 'add'?'新增模板':`编辑模板` ">
<el-form ref="addTempForm" :model="formData" :rules="rules" size="medium" label-width="100px">
<el-form-item label="模板名称" prop="templateName">
<el-input v-model="formData.templateName" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="soc限制" prop="sdcLimit" required>
<el-switch :active-value="1" :inactive-value="0" v-model="formData.sdcLimit"></el-switch>
</el-form-item>
<!-- <template v-if="formData.sdcLimit === 1">-->
<el-form-item label="soc下限" prop="sdcDown">
<el-input v-model="formData.sdcDown" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
</el-form-item>
<el-form-item label="soc上限" prop="sdcUp">
<el-input v-model="formData.sdcUp" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
</el-form-item>
<!-- </template>-->
</el-form>
<el-button type="primary" size="mini" @click="addTime">新增</el-button>
<!-- 新增时间段表单-->
<el-collapse-transition>
<el-card v-show="showAddTime" shadow="always" class="common-card-container" style="margin-top:25px;">
<el-form class="add-time-form transition-box" ref="addTimeForm" :model="formInline" :rules="formInlineRule"
label-width="100px" style="margin-top:25px">
<!-- <el-form-item label="开始时间" prop="startTime">-->
<!-- <el-time-select-->
<!-- placeholder="开始时间"-->
<!-- v-model="formInline.startTime"-->
<!-- :picker-options="{-->
<!-- start: '00:00',-->
<!-- step: '00:01',-->
<!-- end: '23:00',-->
<!-- }">-->
<!-- </el-time-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="结束时间" prop="endTime">-->
<!-- <el-time-select-->
<!-- placeholder="结束时间"-->
<!-- v-model="formInline.endTime"-->
<!-- :picker-options="{-->
<!-- start: '00:00',-->
<!-- step: '00:01',-->
<!-- end: '23:00',-->
<!-- minTime: formInline.startTime-->
<!-- }">-->
<!-- </el-time-select>-->
<!-- </el-form-item>-->
<el-form-item label="时间范围" prop="timeRange">
<el-time-picker
is-range
v-model="formInline.timeRange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="选择时间范围"
format="HH:mm"
value-format="HH:mm"
:style="{width: '100%'}">
</el-time-picker>
</el-form-item>
<el-form-item label="冲放功率" prop="chargeDischargePower">
<el-input v-model="formInline.chargeDischargePower" placeholder="请输入"
:style="{width: '100%'}"></el-input>
</el-form-item>
<el-form-item label="充电状态" prop="chargeStatus">
<el-select v-model="formInline.chargeStatus" placeholder="请选择" :style="{width: '100%'}">
<el-option v-for="(value,key) in chargeStatusOptions" :key="key+'chargeStatusOptions'" :label="value"
:value="key"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" size="mini" @click="saveTime">保存</el-button>
<el-button size="mini" @click="cancelAddTime">取消</el-button>
</el-form-item>
</el-form>
</el-card>
</el-collapse-transition>
<el-table
:data="tableData"
border
style="width: 100%;margin-top:25px">
<el-table-column
prop="startTime"
label="开始时间">
</el-table-column>
<el-table-column
prop="endTime"
label="结束时间">
</el-table-column>
<el-table-column
prop="chargeDischargePower"
label="充放功率kW">
</el-table-column>
<el-table-column
prop="chargeStatus"
label="充电状态">
<template slot-scope="scope">
{{ chargeStatusOptions[scope.row.chargeStatus] }}
</template>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
width="120">
<template slot-scope="scope">
<el-button
@click.native.prevent="deleteRow(scope.$index, tableData)"
type="warning"
size="mini">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div slot="footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="saveDialog">确定</el-button>
</div>
</el-dialog>
</template>
<script>
import {mapState} from 'vuex'
import {addStrategyTemp, editStrategyTemp, getStrategyTempDetail} from '@/api/ems/dzjk'
export default {
inject: ['$home'],
data() {
const now = new Date()
const year = now.getFullYear(), month = now.getMonth(), day = now.getDate()
const range = [new Date(year, month, day, 0), new Date(year, month, day, 23)]
return {
mode: '',
editTempId: '',
dialogTableVisible: false,
secondRange: range,
formData: {
templateName: '',
sdcLimit: false,
sdcDown: '',
sdcUp: '',
},
rules: {
templateName: [{
required: true,
message: '请输入',
trigger: 'blur'
}],
sdcDown: [
{required: true, message: '请输入', trigger: 'blur'},
{pattern: /^(0|[1-9]\d*)(\.\d+)?$/, message: '请输入合法数字或小数'}
],
sdcUp: [
{required: true, message: '请输入', trigger: 'blur'},
{pattern: /^(0|[1-9]\d*)(\.\d+)?$/, message: '请输入合法数字或小数'}
],
},
showAddTime: false,
formInline: {
timeRange: range,
chargeDischargePower: '',
chargeStatus: ''
},
formInlineRule: {
timeRange: [{
required: true,
message: '请选择时间范围',
trigger: 'change'
}],
chargeDischargePower: [{
required: true,
message: '请输入冲放功率',
trigger: 'blur'
},
{pattern: /^-?\d*\.?\d*$/, message: '请输入合法数字或小数'}
],
chargeStatus: [{
required: true,
message: '请选择充放状态',
trigger: ['blur', 'change']
}
]
},
tableData: [],
}
},
computed: {
...mapState({
chargeStatusOptions: state => state?.ems?.chargeStatusOptions || {},
})
},
methods: {
changeSiteId() {
this.dialogTableVisible = false
this.mode = ''
this.editTempId = ''
this.formData = {
templateName: '',
sdcLimit: false,
sdcDown: '',
sdcUp: '',
}
this.formInline = {
timeRange: this.secondRange, chargeDischargePower: '', chargeStatus: ''
}//startTime: '', endTime: '',
this.showAddTime = false
this.tableData = []
},
show({mode = 'add', editTempId = ''}) {
this.$nextTick(() => {
this.dialogTableVisible = true
this.mode = mode
this.editTempId = editTempId
if (mode === 'edit' && editTempId) {
getStrategyTempDetail(this.editTempId).then(response => {
const data = JSON.parse(JSON.stringify(response?.data || []));
if (data.length > 0) {
const {templateName, sdcLimit, sdcDown, sdcUp} = JSON.parse(JSON.stringify(data[0]));
this.formData.templateName = templateName
this.formData.sdcLimit = sdcLimit
this.formData.sdcDown = sdcDown
this.formData.sdcUp = sdcUp
}
if (data.length === 1) {
const {startTime, endTime} = data;
if (!startTime || !endTime) {
this.tableData = []
} else {
this.tableData = data
}
} else {
this.tableData = data
}
})
}
})
},
addTime() {
this.showAddTime = true
},
cancelAddTime() {
this.$refs.addTimeForm.resetFields()
this.showAddTime = false
this.formInline = {timeRange: this.secondRange, chargeDischargePower: '', chargeStatus: ''}//startTime: '', endTime: '',
},
saveTime() {
//表单校验校验成功添加到tableData里
this.$refs.addTimeForm.validate(valid => {
if (!valid) return
const {timeRange: [startTime, endTime], chargeDischargePower, chargeStatus} = this.formInline
this.tableData.push({startTime, endTime, chargeDischargePower, chargeStatus})
this.$nextTick(() => {
this.cancelAddTime()
})
})
},
deleteRow(index) {
this.tableData.splice(index, 1)
},
saveDialog() {
this.$refs.addTempForm.validate(valid => {
if (!valid) return
const {templateName, sdcLimit, sdcDown, sdcUp} = this.formData
const {siteId, updateStrategyId} = this.$home
const {tableData} = this
if (this.mode === 'edit') {
editStrategyTemp({
siteId,
strategyId: updateStrategyId,
templateId: this.editTempId,
templateName,
sdcLimit,
sdcDown,
sdcUp,
timeConfigList: tableData
}).then(response => {
if (response?.code === 200) {
this.closeDialog()
this.$emit('update')
this.$emit('updateTimeSetting')
}
})
} else {
addStrategyTemp({
siteId,
strategyId: updateStrategyId,
templateName,
sdcLimit,
sdcDown,
sdcUp,
timeConfigList: tableData
}).then(response => {
if (response?.code === 200) {
this.closeDialog()
this.$emit('update')
}
})
}
})
},
closeDialog() {
// 清空所有数据
this.$refs.addTempForm.resetFields()
this.formData = {
templateName: '',
sdcLimit: 0,
sdcDown: '',
sdcUp: '',
}
this.tableData = []
this.cancelAddTime()
this.dialogTableVisible = false
}
}
}
</script>
<style lang="scss" scoped>
.add-template-dialog {
max-height: 90vh;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,117 @@
<template>
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding" style="margin-top:25px;">
<div slot="header">
<span class="card-title">策略模板曲线展示</span>
</div>
<div style="height: 360px" id="tempChart"/>
</el-card>
</template>
<style scoped lang="scss"></style>
<script>
import * as echarts from 'echarts'
import resize from '@/mixins/ems/resize'
import {curveList} from '@/api/ems/dzjk'
export default {
inject:['$home'],
mixins: [resize],
data() {
return {
chart: null
}
},
beforeDestroy() {
this.destoryChart()
},
methods: {
destoryChart(){
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
changeSiteId(){
this.destoryChart()
},
init(){
if(!this.chart){
this.chart = echarts.init(document.getElementById('tempChart'))
}
const strategyId = this.$home.updateStrategyId
const siteId=this.$home.siteId
curveList({strategyId,siteId}).then(response => {
this.setOption(response?.data || [])
})
},
setOption(data) {
if(!this.chart) return
let obj = {}
for(var i=0;i<=23;i++){
obj[i] = {
title:i<=9?`0${i}:00` : `${i}:00`
}
}
const nowMonth = new Date().getMonth()+1;
const localMonth = data.find(item=>item.month === nowMonth)?.powerList || []
localMonth.forEach(item => {
const startHours = parseInt(item.startTime.split(':')[0], 10)
const endHours =parseInt(item.endTime.split(':')[0], 10)
for(let i=startHours;i<=endHours;i++){
obj[i].value = item.powerData
}
})
let source = [['时间','冲放功率']]
Object.values(obj).forEach(item => {
const {title,value} = item
source.push([title,value])
})
this.chart.setOption({
color:['#FFBD00','#3C81FF'],
grid: {
containLabel: true
},
legend: {
left: 'center',
bottom: '15',
},
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
textStyle:{
color:"#333333",
},
xAxis: {
type: 'category',
axisLine: {
lineStyle:{
color: '#333333',
}
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle:{
color: '#333333',
}
}
},
dataset: {
source
},
series: [
{
name:'冲放功率',
type: 'line',
}]
})
}
}
}
</script>

View File

@ -0,0 +1,196 @@
<template>
<el-card shadow="always" class="common-card-container">
<div slot="header">
<span class="card-title">新增模板</span>
<div style="float: right; padding: 3px 0">
<el-button type="text" size="mini" @click="back">返回策略运行</el-button>
<el-button type="primary" size="mini" @click="addTemp">新增</el-button>
<template v-if="tempList.length>0">
<el-button type="warning" size="mini" @click="editTemp">编辑</el-button>
<el-button type="primary" class="alarm-btn" size="mini" @click="deleteTemp">删除</el-button>
</template>
</div>
</div>
<div>
<template v-if="tempList.length>0">
<el-button-group class="ems-btns-group">
<el-button v-for="(item,index) in tempList" :key="index+'tempList'" :class="{'activeBtn' : activeBtn === item.templateId}" size="small" @click="changeTemp(item.templateId)">{{item.templateName}}</el-button>
</el-button-group>
<el-table
:data="tableData"
:span-method="tableSpanFilter"
border
style="width: 100%;margin-top:25px;">
<!-- todo 如果要在span-method中使用column.property 在表格中必须定义prop="xxx"属性-->
<el-table-column
prop="templateName"
label="模板名称"
width="180">
</el-table-column>
<el-table-column
prop="sdcLimit"
label="SOC限制">
<template slot-scope="scope">
<el-switch disabled :active-value="1" :inactive-value="0" v-model="scope.row.sdcLimit"></el-switch>
</template>
</el-table-column>
<el-table-column
prop="sdcDown"
label="SOC下限">
<template slot-scope="scope">
{{scope.row.sdcDown ? scope.row. sdcDown + '%' : '-'}}
</template>
</el-table-column>
<el-table-column
prop="sdcUp"
label="SOC上限">
<template slot-scope="scope">
{{scope.row.sdcUp ? scope.row.sdcUp + '%' : '-'}}
</template>
</el-table-column>
<el-table-column
prop="startTime"
label="开始时间">
</el-table-column>
<el-table-column
prop="endTime"
label="结束时间">
</el-table-column>
<el-table-column
prop="chargeDischargePower"
label="充放功率kW">
</el-table-column>
<el-table-column
prop="chargeStatus"
label="充电状态">
<template slot-scope="scope">
{{chargeStatusOptions[scope.row.chargeStatus]}}
</template>
</el-table-column>
</el-table>
</template>
<template v-else>
<el-empty :image-size="150"></el-empty>
</template>
</div>
<add-template ref="addTemplate" :mode="handlerType" :editTempId="activeBtn" @update="init" @updateTimeSetting="$emit('updateTimeSetting')"/>
</el-card>
</template>
<script>
import {mapState} from 'vuex'
import AddTemplate from "./AddTemplate.vue";
import {getTempNameList, getStrategyTempDetail, deleteStrategyTemp} from '@/api/ems/dzjk'
export default {
components: {AddTemplate},
inject:['$home'],
data() {
return {
handlerType:'',
activeBtn:'',
tempList:[],
tableData:[],
mixinPrototype:['templateName','sdcLimit','sdcDown','sdcUp']
}
},
computed:{
...mapState({
chargeStatusOptions: state => state?.ems?.chargeStatusOptions || {},
}),
activeTempData(){
return this.tempList.find(item=>item.templateId === this.activeBtn) || {}
}
},
methods:{
back(){
this.$home.init()
},
changeSiteId(){
this.tempList=[]
this.tableData=[]
this.handlerType=''
this.activeBtn=''
this.$refs.addTemplate && this.$refs.addTemplate.changeSiteId()
},
addTemp(){
this.$refs.addTemplate.show({mode:'add',editTempId:''})
},
editTemp(){
this.$refs.addTemplate.show({mode:'edit',editTempId:this.activeBtn})
},
init(){
getTempNameList({strategyId:this.$home.updateStrategyId,siteId:this.$home.siteId}).then(response => {
const data = response?.data || [];
this.tempList =data
if(data.length ===0 && this.activeBtn){
this.activeBtn=''
}else if(!this.activeBtn && data.length>0){
this.activeBtn =data[0].templateId
}else if(this.activeBtn && data.length>0 && !data.find(item=>item.templateId === this.activeBtn)){
this.activeBtn =data[0].templateId
}
this.activeBtn && getStrategyTempDetail(this.activeBtn).then(response => {
this.tableData=response?.data || [];
})
})
},
deleteTemp(){
this.$confirm(`确认要删除${this.activeTempData.templateName}吗?`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
showClose:false,
closeOnClickModal:false,
type: 'warning',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
deleteStrategyTemp(this.activeBtn).then(response => {
if(response?.code === 200){
//删除成功
done();
}
}).finally(() => {
instance.confirmButtonLoading = false;
})
} else {
done();
}
}
}).then(() => {
//只有删除成功的情况下会走到这里
this.$message({
type: 'success',
message: '删除成功!'
});
this.init()
this.$emit('updateTimeSetting')
}).catch(() => {
//取消
});
},
changeTemp(id=''){
//切换模板 更新数据
if(id !== this.activeBtn){
this.activeBtn=id;
this.init()
}
},
tableSpanFilter({ row, column, rowIndex, columnIndex }){
if(!this.mixinPrototype.includes(column.property)) return { rowspan: 1,colspan: 1 }
if(rowIndex===0){
return {
rowspan: this.tableData.length,
colspan: 1
};
}else{
return {
rowspan: 0,
colspan: 0
}
}
}
},
}
</script>

View File

@ -0,0 +1,149 @@
<template>
<el-card shadow="always" class="common-card-container" style="margin-top:25px;">
<div slot="header">
<span class="card-title">策略配置{{$home.updateStrategyName}}</span>
<div style="float: right; padding: 3px 0">
<el-button type="warning" size="mini" @click="changeEditStatus">{{edit?'保存':'编辑'}}</el-button>
<el-button type="default" size="mini" @click="cancelChange" v-show="edit === true">取消</el-button>
</div>
</div>
<div style="padding:0 6px 12px 6px;">
<div class="cl-items" v-for="(item,index) in tableData" :key="index+'lclf'">
<div class="cl-header">{{item.month}}</div>
<div class="cl-content">
<div class="cl-name" v-if="!edit">
{{item.templateName || '-'}}
</div>
<template v-else>
<el-select v-model="tableData[index].templateId" :disabled="!edit" class="temp-select" placeholder="请选择" clearable size="mini">
<el-option
v-for="(item,index) in tempList"
:key="index+'sjpz'"
:label="item.templateName"
:value="item.templateId">
</el-option>
</el-select>
</template>
</div>
</div>
</div>
</el-card>
</template>
<script>
import {timeConfigList, getTempNameList,setTimeConfigList} from '@/api/ems/dzjk'
export default {
inject:['$home'],
data() {
return {
edit:false,
loading:false,
tableData:[],
tempList:[]
}
},
methods:{
changeSiteId(){
this.tableData=[]
this.tempList=[]
this.edit = false
},
init(){
this.getTimeConfigList()
},
getTempList(){
const strategyId = this.$home.updateStrategyId
const siteId=this.$home.siteId
getTempNameList({strategyId,siteId}).then(response => {
this.tempList =response?.data || [];
})
},
getTimeConfigList(){
const strategyId = this.$home.updateStrategyId
const siteId=this.$home.siteId
return timeConfigList({strategyId,siteId}).then(response => {
const data = JSON.parse(JSON.stringify(response?.data || []))
if(data.length === 0){
for(var i=1;i<=12;i++){
data.push({
month:i,
strategyId,
siteId,
templateId:'',
templateName:''
})
}
}
this.tableData=data
})
},
cancelChange(){
this.edit =false
this.getTimeConfigList()
},
changeEditStatus(){
//当前状态是编辑状态
if(this.edit){
console.log('this.tableData',this.tableData)
const strategyId = this.$home.updateStrategyId
const siteId=this.$home.siteId
this.tableData.forEach(item=>{
item.siteId=siteId
item.strategyId=strategyId
item.templateName = (this.tempList.find(temp=>temp.templateId===item.templateId) || {}).templateName || ''
})
setTimeConfigList(this.tableData).then(response => {
if(response?.code === 200){
//更改成功 调用接口更新数据
this.getTimeConfigList()
this.edit=false;
}
})
}else{
this.edit=true;
this.getTempList()
}
}
}
}
</script>
<style scoped lang="scss">
.cl-items{
width: 144px;
height: 90px;
line-height: 16px;
display: inline-block;
margin:12px 6px 0 6px;
font-size:14px;
color:#333333;
border: 1px solid #eeeeee;
.cl-header{
background: #FFE5AC;
text-align: center;
font-size:16px;
line-height: 30px;
height: 30px;
width: 100%;
}
.cl-content{
background-color: #ffffff;
position: relative;
height: 58px;
width: 100%;
.cl-name{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
}
.temp-select{
width: 90%;
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
}
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<!-- <cl-container :hideSettingBtn="true">-->
<!-- <template v-slot:default>-->
<div class="ems-dashboard-editor-container ems-content-container-padding">
<temp-table ref="tempTable" @updateTimeSetting="updateTimeSetting"/>
<time-setting ref="timeSetting"/>
<temp-power-chart ref="tomePowerChart"/>
</div>
<!-- </template>-->
<!-- </cl-container>-->
</template>
<script>
import ClContainer from './../ClContainer.vue'
import TempTable from "./TempTable.vue";
import TimeSetting from "./TimeSetting.vue";
import TempPowerChart from "./TempPowerChart.vue";
export default {
name:'DzjkClpzXftg',
components:{ClContainer,TempTable,TimeSetting,TempPowerChart},
data() {
return {
}
},
methods:{
init(){
this.$nextTick(() => {
this.$refs.tempTable.init()
this.$refs.timeSetting.init()
this.$refs.tomePowerChart.init()
})
},
changeSiteId(){
this.$nextTick(() => {
this.$refs.tempTable.changeSiteId()
this.$refs.timeSetting.changeSiteId()
this.$refs.tomePowerChart.changeSiteId()
})
},
updateTimeSetting(){
this.$refs.timeSetting.init()
this.$refs.tomePowerChart.init()
}
},
mounted(){
}
}
</script>
<style scoped lang="scss">
::v-deep{
.common-card-container .el-card__header{
padding:5px 20px;
height:44px;
.card-title{
line-height: 34px;
}
}
}
</style>

View File

@ -0,0 +1,251 @@
<template>
<div v-loading="loading" class="ems-dashboard-editor-container ems-content-container-padding">
<!-- 搜索栏-->
<el-form :inline="true" class="select-container">
<el-form-item label="设备清单">
<el-select v-model="search.deviceId" clearable placeholder="请选择" :loading="loading"
loading-text="正在加载数据">
<el-option :label="item.deviceName" :value="item.deviceId" v-for="(item,key) in deviceOptions"
:key="key+'deviceIdOptions'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="告警等级">
<el-select v-model="search.alarmLevel" clearable placeholder="请选择" :loading="loading"
loading-text="正在加载数据" style="width: 130px">
<el-option :label="value" :value="key" v-for="(value,key) in $store.state.ems.alarmLevelOptions"
:key="key+'alarmLevelOptions'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="时间选择">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始时间"
:picker-options="pickerOptions"
:default-value="defaultDateRange"
end-placeholder="结束时间">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearch" native-type="button">搜索</el-button>
</el-form-item>
<el-form-item>
<el-button @click="onReset" native-type="button">重置</el-button>
</el-form-item>
</el-form>
<div style="margin:30px 0;">
<!-- 二个选择按钮-->
<el-row style="">
<el-col :xs="24" :sm="24" :lg="24">
<el-button-group class="ems-btns-group">
<el-button v-for="(item,index) in btnList" :key="index+'dtdcBtns'"
:class="{'activeBtn' : activeBtn === item.id}" @click="changeDataType(item.id)">{{ item.name }}
</el-button>
</el-button-group>
</el-col>
</el-row>
<!--表格-->
<el-table
class="common-table"
:data="tableData"
stripe
max-height="500"
style="width: 100%;margin-top:25px;">
<el-table-column
prop="deviceName"
label="设备名称">
</el-table-column>
<el-table-column
label="告警等级"
>
<template slot-scope="scope">
<span>{{ $store.state.ems.alarmLevelOptions[scope.row.alarmLevel] }}</span>
</template>
</el-table-column>
<el-table-column
prop="alarmContent"
show-overflow-tooltip
label="告警内容">
</el-table-column>
<el-table-column
prop="alarmStartTime"
label="告警发生时间">
<template slot-scope="scope">
<span>{{ formatDate(scope.row.alarmStartTime, true) }}</span>
</template>
</el-table-column>
<el-table-column
prop="alarmEndTime"
label="告警结束时间">
<template slot-scope="scope">
<span>{{ formatDate(scope.row.alarmEndTime, true) }}</span>
</template>
</el-table-column>
<el-table-column
label="状态">
<template slot-scope="scope">
<span
:class="['0','2'].includes(scope.row.status) ? 'warning-status' : ''">{{ $store.state.ems.alarmStatusOptions[scope.row.status] }}</span>
</template>
</el-table-column>
<el-table-column
label="工单"
fixed="right"
width="250"
>
<template slot-scope="scope">
<el-button type="text" size="mini" v-if="scope.row.ticketNo" @click="toTicket">
已生成工单(工单号:{{ scope.row.ticketNo }})
</el-button>
<el-button type="primary" size="mini" v-else @click="createTicket(scope.row.id)">生成工单</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-show="tableData.length>0"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-size="pageSize"
:page-sizes="[10, 20, 30, 40]"
layout="total, sizes, prev, pager, next, jumper"
:total="totalSize"
style="margin-top:15px;text-align: center"
>
</el-pagination>
</div>
</div>
</template>
<script>
import {createTicketNo, getAlarmDetailList} from '@/api/ems/dzjk'
import {getDeviceList} from '@/api/ems/site'
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {formatDate} from '@/filters/ems'
export default {
name: 'DzjkGzgj',
mixins: [getQuerySiteId],
data() {
return {
loading: false,
btnList: [
{name: '未处理告警', id: 'today'},
{name: '历史告警', id: 'history'},
],
deviceOptions: [],//设备列表
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
},
},
defaultDateRange: [],//默认展示的时间
dateRange: [],//alarmStartTime,alarmEndTime
activeBtn: 'today',
search: {deviceId: '', alarmLevel: ''},
// 表格、分页
tableData: [],
pageSize: 10,//分页栏当前每个数据总数
pageNum: 1,//分页栏当前页数
totalSize: 0,//table表格数据总数
}
},
methods: {
formatDate,
toTicket() {
this.$router.push({path: '/ticket'})
},
//生成工单
createTicket(id) {
this.loading = true
createTicketNo({id}).then(response => {
response?.data && this.toTicket()
}).finally(() => {
this.loading = false
})
},
// 判断是否是同一天
isSameDay(day1, day2) {
const date1 = new Date(day1), date2 = new Date(day2)
return date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();
},
// 分页
handleSizeChange(val) {
this.pageSize = val;
this.$nextTick(() => {
this.getData()
})
},
handleCurrentChange(val) {
this.pageNum = val
this.$nextTick(() => {
this.getData()
})
},
// 搜索
onSearch() {
this.pageNum = 1//每次搜索从1开始搜索
this.getData()
},
// 重置
onReset() {
this.search = {deviceId: '', alarmLevel: ''}
this.dateRange = []
this.pageNum = 1//每次搜索从1开始搜索
this.getData()
},
// 切换今日、历史告警
changeDataType(id) {
if (id !== this.activeBtn) {
console.log('点击了不同的菜单,更新数据')
this.activeBtn = id;
this.getData()
}
},
// 获取数据
getData() {
this.$store.dispatch('getSiteAlarmNum', this.siteId)
this.loading = true
const {deviceId, alarmLevel} = this.search
const {siteId, pageNum, pageSize, activeBtn} = this
const [alarmStartTime = '', alarmEndTime = ''] = (this.dateRange || [])
let status = activeBtn === 'today' ? '0' : '1,2'
getAlarmDetailList({
status,
deviceId,
alarmLevel,
siteId,
pageSize,
pageNum,
alarmStartTime: formatDate(alarmStartTime),
alarmEndTime: formatDate(alarmEndTime)
}).then(response => {
this.tableData = response?.rows || [];
this.totalSize = response?.total || 0
}).finally(() => {
this.loading = false
})
},
getDeviceOptions() {
getDeviceList(this.siteId).then(response => {
this.deviceOptions = JSON.parse(JSON.stringify(response?.data || []))
})
},
init() {
this.getDeviceOptions()
this.onReset()
},
},
mounted() {
const now = new Date();
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
this.defaultDateRange = [lastMonth, now];
}
}
</script>

View File

@ -0,0 +1,127 @@
<template>
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding time-range-card">
<div slot="header" class="time-range-header">
<span class="card-title">当日功率曲线</span>
<date-range-select ref="dateRangeSelect" :showIcon="true" :mini-time-picker="true" @updateDate="updateDate"/>
</div>
<div style="height: 310px" id="activeChart"></div>
</el-card>
</template>
<script>
import * as echarts from 'echarts'
import resize from '@/mixins/ems/resize'
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue'
import {getPointData} from '@/api/ems/dzjk'
import intervalUpdate from "@/mixins/ems/intervalUpdate";
export default {
mixins: [resize, intervalUpdate],
components: {DateRangeSelect},
data() {
return {
chart: null,
timeRange: [],
siteId: '',
isInit: true
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
// 更新时间范围 重置图表
updateDate(data) {
this.timeRange = data
!this.isInit && this.getGVQXData()
this.isInit = false
},
getGVQXData() {
this.showLoading()
const {siteId, timeRange} = this
getPointData({siteId, startDate: timeRange[0], endDate: timeRange[1]}).then(response => {
this.setOption(response?.data || [])
}).finally(() => this.hideLoading())
},
init(siteId) {
//初始化 清空数据
this.siteId = siteId
this.isInit = true
this.timeRange = []
this.$refs.dateRangeSelect.init(true)
this.getGVQXData()
this.updateInterval(this.getGVQXData)
},
initChart() {
this.chart = echarts.init(document.querySelector('#activeChart'))
},
showLoading() {
this.chart && this.chart.showLoading()
},
hideLoading() {
this.chart && this.chart.hideLoading()
},
setOption(data) {
const source = [['日期', '电网功率', '负载功率', '储能功率', '光伏功率', 'soc平均值', 'soh平均值', '电池平均温度平均值']]
console.log('source.slice(1)', source[0].slice(1))
this.chart && data.forEach((item) => {
source.push([item.statisDate, item.gridPower, item.loadPower, item.storagePower, item.pvPower, item.avgSoc, item.avgSoh, item.avgTemp])
})
this.chart.setOption({
grid: {
containLabel: true
},
legend: {
left: 'center',
bottom: '15',
},
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
textStyle: {
color: "#333333",
},
xAxis: {
type: 'category',
},
yAxis: [
{
type: 'value',
},
{
type: 'value',
},
],
dataset: {source},
series: source[0].slice(1).map((item, index) => {
return {
type: 'line',//index === 5 ? 'bar' : 'line',
showSymbol: false,
symbolSize: 2,
smooth: true,
areaStyle: {
opacity: 0.5,
},
yAxisIndex: index <= 4 ? 0 : 1
}
})
})
},
}
}
</script>

View File

@ -0,0 +1,86 @@
<template>
<!-- <el-card shadow="always" class="common-card-container common-card-container-body-no-padding">
<div slot="header">
<span class="card-title">当前报警</span>
</div>
<div class="ssgj-container">
<el-table
class="common-table"
:data="tableData"
height="100%"
stripe
style="width: 100%">
<el-table-column
prop="deviceName"
label="名称">
</el-table-column>
<el-table-column
label="状态"
>
<template slot-scope="scope">
<span :class="{'circle warning-status' : scope.row.status !== 0}">{{ $store.state.ems.warnOptions[scope.row.status]}}</span>
</template>
</el-table-column>
<el-table-column
class-name="alarm-content"
prop="alarmContent"
show-overflow-tooltip
label="告警内容">
</el-table-column>
<el-table-column
label="工单"
fixed="right"
show-overflow-tooltip
>
<template slot-scope="scope">
<el-button type="text" size="mini" v-if="scope.row.ticketNo" @click="toTicket">已生成工单(工单号:{{scope.row.ticketNo}})</el-button>
<el-button type="primary" size="mini" v-else @click="toTicket">生成工单</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-card> -->
<el-alert type="error" :closable="false">
<template>
<div style="cursor: pointer" @click="toAlarm">设备告警</div>
</template>
</el-alert>
</template>
<script>
export default {
props: {
tableData: {
require: true,
type: Array,
default: () => {
return [];
},
},
},
data() {
return {};
},
methods: {
toAlarm() {
this.$router.push({ path: "/dzjk/gzgj", query: this.$route.query });
},
toTicket() {
this.$router.push({ path: "/ticket" });
},
},
};
</script>
<style lang="scss" scoped>
//实时告警
.ssgj-container {
padding: 20px;
height: 250px;
box-sizing: border-box;
::v-deep {
.el-table .el-table__header-wrapper th,
.el-table .el-table__fixed-header-wrapper th {
background: #fff2cb;
}
}
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<el-card
shadow="always"
class="common-card-container common-card-container-body-no-padding"
>
<div slot="header">
<span class="card-title">策略信息</span>
</div>
<!-- <el-empty :image-size="100" ></el-empty> -->
<div
style="
box-sizing: border-box;
height: 250px;
padding: 20px 15px;
overflow-y: auto;
"
>
<el-descriptions class="home-normal-info" :column="2">
<el-descriptions-item size="mini" label="模板名称">{{
info.mainStrategyName || "-"
}}</el-descriptions-item>
<el-descriptions-item size="mini" label="SOC限制">{{
mainInfo.sdcLimit === 1 ? "开" : mainInfo.sdcLimit === 0 ? "关" : "-"
}}</el-descriptions-item>
<el-descriptions-item size="mini" label="SOC下限%">{{
formatNumber(mainInfo.sdcDown)
}}</el-descriptions-item>
<el-descriptions-item size="mini" label="SOC上限%">{{
formatNumber(mainInfo.sdcUp)
}}</el-descriptions-item>
</el-descriptions>
<el-table
:data="info.siteMonitorDataVo || []"
border
size="mini"
style="width: 100%; margin-top: 15px"
>
<el-table-column prop="startTime" label="开始时间"> </el-table-column>
<el-table-column prop="endTime" label="结束时间"> </el-table-column>
<el-table-column prop="chargeDischargePower" label="充放功率kW">
</el-table-column>
<el-table-column prop="chargeStatus" label="充电状态">
<template slot-scope="scope">
{{ chargeStatusOptions[scope.row.chargeStatus] }}
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</template>
<script>
import { mapState } from "vuex";
import { formatNumber } from "@/filters/ems";
export default {
props: {
info: {
require: true,
type: Object,
default: () => {
return {};
},
},
},
computed: {
...mapState({
chargeStatusOptions: (state) => state?.ems?.chargeStatusOptions || {},
}),
mainInfo() {
return this.info?.siteMonitorDataVo?.length > 0
? this.info.siteMonitorDataVo[0]
: {};
},
},
data() {
return {};
},
methods: {
formatNumber,
},
};
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,120 @@
<template>
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding time-range-card">
<div slot="header" class="time-range-header">
<span class="card-title">一周充放曲线</span>
<date-range-select ref="dateRangeSelect" :showIcon="true" :mini-time-picker="true" @updateDate="updateDate"/>
</div>
<div style="height: 310px" id="weekChart"></div>
</el-card>
</template>
<script>
import * as echarts from 'echarts'
import resize from '@/mixins/ems/resize'
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue'
import {getSevenChargeData} from '@/api/ems/dzjk'
export default {
mixins: [resize],
components: {DateRangeSelect},
data() {
return {
chart: null,
timeRange: [],
siteId: '',
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
// 更新时间范围 重置图表
updateDate(data) {
this.timeRange = data
this.getWeekKData()
},
getWeekKData() {
this.showLoading()
const {siteId, timeRange} = this
getSevenChargeData({siteId, startDate: timeRange[0], endDate: timeRange[1]}).then(response => {
this.setOption(response?.data || [])
}).finally(() => this.hideLoading())
},
init(siteId) {
//初始化 清空数据
this.siteId = siteId
this.timeRange = []
this.deviceId = ''
this.$refs.dateRangeSelect.init()
},
initChart() {
this.chart = echarts.init(document.querySelector('#weekChart'))
},
showLoading() {
this.chart && this.chart.showLoading()
},
hideLoading() {
this.chart && this.chart.hideLoading()
},
setOption(data, unit) {
const source = [['日期', '充电量', '放电量']]
data.forEach(item => {
source.push([item.ammeterDate, item.chargedCap, item.disChargedCap])
})
this.chart && this.chart.setOption({
color: ['#4472c4', '#70ad47'],//所有充放电颜色保持统一
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
grid: {
containLabel: true
},
legend: {
left: 'center',
bottom: '15',
},
xAxis: {
type: 'category',
name: unit,
nameLocation: 'center'
},
yAxis: [{
type: 'value',
name: '充电量/放电量kWh',
axisLine: {
lineStyle: {
color: '#333333',
},
onZero: false
}
}],
dataset: {
source
},
series: [
{
yAxisIndex: 0,
type: 'bar',
},
{
yAxisIndex: 0,
type: 'bar',
},
]
})
}
}
}
</script>

View File

@ -0,0 +1,342 @@
<template>
<div
class="ems-dashboard-editor-container ems-content-container-padding"
v-loading="loading"
>
<el-row style="background: #fff" class="row-container" :gutter="15">
<el-col :xs="24" :sm="24" :lg="5">
<!-- 站点信息-->
<el-card
shadow="always"
class="common-card-container common-card-container-body-no-padding"
>
<div slot="header">
<span class="card-title">站点信息</span>
<div class="alarm-msg" v-if="tableData.length > 0" @click="toAlarm">
<i class="el-icon-message-solid"></i> 设备告警
</div>
</div>
<div
style="box-sizing: border-box; height: 218px; padding: 20px 15px"
>
<!-- 地址运行时间-->
<div class="site-info site-info-address">
<div class="title">
<i class="el-icon-location"></i>
</div>
<div class="value">{{ info.siteAddress }}</div>
</div>
<div class="site-info">
<div class="title">
<i class="el-icon-date"></i>
</div>
<div class="value">{{ info.runningTime || '-' }}</div>
</div>
<!-- 装机功率容量 -->
<el-row :gutter="10" style="margin-top:20px;">
<el-col
:span="12"
class="sjgl-col power-col"
>
<div class="sjgl-wrapper">
<div class="sjgl-title">装机功率(MW)</div>
<div class="sjgl-value">
{{ info.installPower | formatNumber }}
</div>
</div>
</el-col>
<el-col
:span="12"
class="sjgl-col power-col"
>
<div class="sjgl-wrapper">
<div class="sjgl-title">装机容量(MW)</div>
<div class="sjgl-value">
{{ info.installCapacity | formatNumber }}
</div>
</div>
</el-col>
</el-row>
</div>
</el-card>
</el-col>
<!-- 总累计运行数据-->
<el-col :xs="24" :sm="24" :lg="19">
<el-card
shadow="always"
class="common-card-container common-card-container-body-no-padding"
>
<div slot="header">
<span class="card-title">总累计运行数据</span>
<div class="total-count">
<span class="title">总收入</span>
<span class="value">{{ runningInfo.totalRevenue | formatNumber }}</span>
<span class="unit"></span>
</div>
</div>
<div
style="box-sizing: border-box; height: 218px; padding: 20px 15px"
>
<el-row :gutter="10">
<el-col
:span="6"
v-for="(item, index) in sjglData"
:key="index + 'sjglData'"
class="sjgl-col"
>
<div class="sjgl-wrapper">
<div class="sjgl-title">{{ item.title }}</div>
<div class="sjgl-value" :style="{color:item.color}">
{{ runningInfo[item.attr] | formatNumber }}
</div>
</div>
</el-col>
</el-row>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :lg="12">
<week-chart ref="weekChart"/>
</el-col>
<el-col :xs="24" :sm="24" :lg="12">
<active-chart ref="activeChart"/>
</el-col>
</el-row>
</div>
</template>
<script>
import {getSingleSiteBaseInfo} from "@/api/ems/zddt";
import {getDzjkHomeView} from "@/api/ems/dzjk";
import WeekChart from "./WeekChart.vue";
import ActiveChart from "./ActiveChart.vue";
import AlarmTable from "./AlarmTable.vue";
import ClInfo from "./ClInfo.vue";
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import intervalUpdate from "@/mixins/ems/intervalUpdate";
export default {
name: "DzjkSbjkHome",
components: {WeekChart, ActiveChart, AlarmTable, ClInfo},
mixins: [getQuerySiteId, intervalUpdate],
data() {
return {
loading: false,
sjglData: [
{
title: "今日充电量kWh",
attr: "dayChargedCap",
color: '#4472c4'
},
{
title: "今日放电量kWh",
attr: "dayDisChargedCap",
color: '#70ad47'
},
{
title: "总充电量kWh",
attr: "totalChargedCap",
color: '#4472c4'
},
{
title: "今日实时收入(元)",
attr: "dayRevenue",
color: '#f67438'
},
{
title: "昨日充电量kWh",
attr: "yesterdayChargedCap",
color: '#4472c4'
},
{
title: "昨日放电量kWh",
attr: "yesterdayDisChargedCap",
color: '#70ad47'
},
{
title: "总放电量kWh",
attr: "totalDischargedCap",
color: '#70ad47'
},
{
title: "昨日实时收入(元)",
attr: "yesterdayRevenue",
color: '#f67438'
},
],
info: {}, //基本信息
runningInfo: {}, //总累计运行数据+报警表格
};
},
computed: {
tableData() {
return this.runningInfo?.siteMonitorHomeAlarmVo || [];
},
},
methods: {
toAlarm() {
this.$router.push({path: "/dzjk/gzgj", query: this.$route.query});
},
getBaseInfo() {
return getSingleSiteBaseInfo(this.siteId).then((response) => {
this.info = response?.data || {};
});
},
getRunningInfo() {
return getDzjkHomeView(this.siteId).then((response) => {
this.runningInfo = response?.data || {};
});
},
init() {
this.loading = true;
// 功率曲线
this.$refs.activeChart.init(this.siteId);
// 一周冲放曲线
this.$refs.weekChart.init(this.siteId);
// 静态信息 this.getBaseInfo()
// 总累计运行数据+故障告警 this.getRunningInfo()
Promise.all([this.getBaseInfo(), this.getRunningInfo()]).finally(() => {
this.loading = false;
});
// 一分钟循环一次总累计运行数据
this.updateInterval(this.getRunningInfo);
},
},
};
</script>
<style scoped lang="scss">
//设备告警
.alarm-msg {
background: #ff4949;
padding: 2px 5px;
font-size: 10px;
font-weight: bolder;
border-radius: 3px;
line-height: 20px;
cursor: pointer;
color: #fff;
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
}
//基本信息-地址 运行️时间
.site-info {
display: flex;
font-size: 12px;
line-height: 20px;
margin-bottom: 10px;
align-items: center;
&.site-info-address {
height: 40px;
}
.title {
color: #1791ff;
font-size: 18px;
line-height: 20px;
margin-right: 7px;
}
.value {
color: #000;
font-size: 12px;
max-height: 40px;
overflow: hidden;
}
}
//总收入
.total-count {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
font-size: 12px;
font-weight: bolder;
color: #333;
line-height: 14px;
.unit {
font-style: italic;
}
.value {
font-size: 22px;
font-weight: bolder;
color: #ed2f1d;
font-style: italic;
padding: 0 5px;
}
}
.row-container {
& > .el-col {
margin-bottom: 20px;
}
}
//数据概览
.sjgl-col {
.sjgl-wrapper {
text-align: left;
padding: 15px 20px;
background-color: #f2f7fb;
}
&.power-col {
.sjgl-wrapper {
padding: 10px;
.sjgl-value {
color: #c44444;
}
}
}
&:nth-child(4),
&:nth-child(2),
&:nth-child(3),
&:nth-child(4) {
margin-bottom: 10px;
}
.sjgl-title {
color: #717171;
line-height: 14px;
font-weight: bold;
}
.sjgl-value {
color: rgba(51, 51, 51, 1);
font-size: 22px;
line-height: 26px;
font-weight: bolder;
font-style: italic;
margin-top: 14px;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>
<style lang="scss">
.home-normal-info {
font-size: 12px;
.el-descriptions-item__container {
.el-descriptions-item__label {
color: #666666;
}
.el-descriptions-item__content {
color: #333333;
}
}
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<div class="ems-dashboard-editor-container">
<zd-select :get-list-by-store="true" :default-site-id="$route.query.siteId" @submitSite="submitSite"/>
<el-menu
class="ems-second-menu"
:default-active="$route.meta.activeSecondMenuName"
background-color="#1890ff"
text-color="#ffffff"
active-text-color="#ffffff"
mode="horizontal"
>
<el-menu-item :index="item.name" v-for="(item,index) in childrenRoute" :key="index+'dzjkChildrenRoute'" :class="{'lighting':item.path.indexOf('gzgj')>-1 && dzjkAlarmLighting}">
<router-link style="height: 100%;width: 100%;display: block;padding:0 20px;" :to="{path:item.path,query:$route.query}">
{{item.meta.title}}
</router-link>
</el-menu-item>
</el-menu>
<div class="ems-content-container ems-content-container-padding dzjk-ems-content-container">
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
</div>
</template>
<script>
import { dzjk } from '@/router/ems'
const childrenRoute = dzjk[0].children[0].children//获取到单站监控下面的字路由
console.log('childrenRoute',childrenRoute)
import ZdSelect from '@/components/Ems/ZdSelect/index.vue'
import {mapState} from "vuex";
export default {
components:{ZdSelect},
data(){
return {
childrenRoute,
activeMenu:''
}
},
computed:{
...mapState({
dzjkAlarmLighting:state=>state.ems.dzjkAlarmLighting
})
},
methods:{
submitSite(id){
if(id !== this.$route.query.siteId){
// console.log('单站监控选择了其他的站点id=',id,'并更新页面地址参数')
this.$router.push({query:{...this.$route.query,siteId:id}})
}else{
// console.log('单站监控选择了相同的其他的站点id=',id,'页面地址不发生改变')
}
//获取告警列表数据
this.$store.dispatch('getSiteAlarmNum',id)
}
},
beforeRouteLeave(to,from, next){
//从单站监控下面的所有子页面跳出时会触发
// 清空store中的zdList 保障下次进入到单站监控会重新调用接口获取数据
this.$store.commit('SET_ZD_LIST',[])
next()
},
}
</script>
<style scoped lang="scss">
.dzjk-ems-content-container{
margin-top:0;
min-height: 60vh;
}
.lighting{
position: relative;
z-index: 1;
&::after{
content:"";
display: block;
background-color: red;
height: 10px;
width: 10px;
border-radius: 100%;
position: absolute;
right: -2px;
top: -2px;
}
}
</style>

View File

@ -0,0 +1,420 @@
<!--电位展示图表-->
<template>
<el-dialog
:visible.sync="show"
:title="pointName"
:close-on-click-modal="false"
show-close
destroy-on-close
lock-scroll
append-to-body
width="1000px"
class="ems-dialog"
:before-close="handleClosed"
>
<el-card
shadow="always"
class="common-card-container common-card-container-body-no-padding time-range-card"
>
<div slot="header" class="time-range-header">
<el-radio-group class="card-title" v-model="dataUnit">
<el-radio :label="1">分钟</el-radio>
<el-radio :label="2">小时</el-radio>
<el-radio :label="3"></el-radio>
</el-radio-group>
<date-time-select
ref="dateTimeSelect"
:data-unit="dataUnit"
@initDate="(e) => (dataRange = e || [])"
@updateDate="updateDate"
/>
</div>
<div style="height: 350px" id="searchChart"></div>
</el-card>
</el-dialog>
</template>
<script>
import * as echarts from "echarts";
import resize from "@/mixins/ems/resize";
import DateTimeSelect from "@/views/ems/search/DateTimeSelect.vue";
import {getPointValueList} from "@/api/ems/search";
import DateRangeSelect from "@/components/Ems/DateRangeSelect/index.vue";
export default {
components: {DateRangeSelect, DateTimeSelect},
mixins: [resize],
props: {
siteId: {
type: String,
required: true,
},
},
computed: {
isDtdc() {
return this.deviceCategory === "BATTERY";
},
},
watch: {
show(val) {
if (!val) {
this.pointName = "";
this.deviceCategory = "";
this.deviceId = "";
this.dataUnit = 1;
this.child = "";
if (!this.chart) {
return;
}
this.hideLoading();
this.chart.dispose();
this.chart = null;
}
},
dataUnit: {
handler(newVal, oldVal) {
if (!this.show) return;
console.log("wacth到了dataUnit的变化", newVal, oldVal);
this.$nextTick(() => {
this.$refs.dateTimeSelect.init();
this.getDate();
});
},
},
},
data() {
return {
show: false,
chart: null,
dataUnit: 1,
dataRange: [],
child: "", //单体电池需要数据
pointName: "",
deviceCategory: "",
deviceId: "",
};
},
methods: {
showChart({pointName, deviceCategory, deviceId, child = ""}) {
//初始化数据
this.pointName = pointName;
this.deviceCategory = deviceCategory;
this.deviceId = deviceId;
child && (this.child = child);
this.show = true;
this.$nextTick(() => {
this.$refs.dateTimeSelect.init();
this.initChart();
this.getDate();
});
},
initChart() {
this.chart = echarts.init(document.querySelector("#searchChart"));
},
showLoading() {
this.chart && this.chart.showLoading();
},
hideLoading() {
this.chart && this.chart.hideLoading();
},
getDate() {
this.showLoading();
const {
siteId,
dataUnit,
dataRange: [start = "", end = ""],
child,
deviceId,
deviceCategory,
pointName,
} = this;
let siteDeviceMap = {};
child && (siteDeviceMap[siteId] = child);
let startDate, endDate;
if (start && dataUnit === 3) {
// startDate= start + `${dataUnit === 2 ? ':00' : ' 00:00:00'}`
startDate = start + " 00:00:00";
} else {
startDate = start;
}
if (end && dataUnit === 3) {
// endDate= end + `${dataUnit === 2 ? ':00' : ' 00:00:00'}`
endDate = end + " 00:00:00";
} else {
endDate = end;
}
getPointValueList({
siteIds: [siteId],
deviceId,
dataUnit,
deviceCategory,
pointName,
startDate,
endDate,
siteDeviceMap,
})
.then((response) => {
this.setOption(response?.data || []);
})
.finally(() => {
this.hideLoading();
});
},
setOption(data) {
if (!this.chart) return;
this.chart.clear();
console.log("返回的数据", data);
if (!data || data.length <= 0) {
this.$message.warning("暂无数据");
}
console.log('展示的图表类型chartType', data[0].chartType)
if (data[0].chartType === 2) {
// 箱型图
this.setBoxOption(data)
} else {
//折线图
this.setLineOption(data)
}
},
setLineOption(data) {
let dataset = [];
data.forEach((item, index) => {
item.deviceList.forEach((inner) => {
dataset.push({
name: `${
this.isDtdc
? `${inner.parentDeviceId ? inner.parentDeviceId + "-" : ""}${inner.deviceId}`
: `${inner.deviceId}`
}`,
type: "line",
markPoint: {
symbolSize: 30,
emphasis: {
disabled: false//打开 鼠标高亮
},
data: [//最大值、最小值
{
// type: 'max',
name: `最大值`,
coord: [inner.maxDate, inner.maxValue],
relativeTo: 'coordinate',
label: {
position: "top",
formatter: item.dataType === 2 ? ([
`最大值:${inner.maxValue}`,
// `平均值:${inner.avgValue}`,
`差值:${inner.diffValue}`,
]).join('\n') : ([
`最大值:${inner.maxValue}`,
// `平均值:${inner.avgValue}`,
]).join('\n'),
},
},
{
// type: 'min',
name: `最小值`,
coord: [inner.minDate, inner.minValue],
relativeTo: 'coordinate',
label: {
position: "top",
formatter: item.dataType === 2 ? ([
`最小值:${inner.minValue}`,
// `平均值:${inner.avgValue}`,
`差值:${inner.diffValue}`,
]).join('\n') : ([
`最小值:${inner.minValue}`,
// `平均值:${inner.avgValue}`,
]).join('\n'),
}
}
]
},
xdata: [],
data: [],
});
const length = dataset.length;
inner.pointValueList.forEach((value) => {
dataset[length - 1].xdata.push(value.valueDate);
dataset[length - 1].data.push(value.pointValue);
});
});
});
console.log("折线图图表数据", dataset);
this.chart.setOption({
legend: {
// left: 'center',
// top: '10',
},
grid: {
containLabel: true,
},
tooltip: {
trigger: "axis",
axisPointer: {
type: 'cross',
},
// axisPointer: {
// // 坐标轴指示器,坐标轴触发有效
// type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
// },
},
textStyle: {
color: "#333333",
},
xAxis: {type: "category", data: dataset?.[0]?.xdata || []},
yAxis: {
type: "value",
},
dataZoom: [
{
type: "inside",
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
series: dataset,
});
},
setBoxOption(data) {
let dataset = [];
data.forEach((item, index) => {
item.deviceList.forEach((inner) => {
dataset.push({
name: `${
this.isDtdc
? `${inner.parentDeviceId ? inner.parentDeviceId + "-" : ""}${inner.deviceId}`
: `${inner.deviceId}`
}`,
type: "boxplot",
// markPoint: {
// symbolSize: 30,
// emphasis: {
// disabled: false//打开 鼠标高亮
// },
// data: [//最大值、最小值
// {
// // type: 'max',
// name: `最大值`,
// coord: [inner.maxDate, inner.maxValue],
// relativeTo: 'coordinate',
// label: {
// position: "top",
// formatter: item.dataType === 2 ? ([
// `最大值:${inner.maxValue}`,
// // `平均值:${inner.avgValue}`,
// `差值:${inner.diffValue}`,
// ]).join('\n') : ([
// `最大值:${inner.maxValue}`,
// // `平均值:${inner.avgValue}`,
// ]).join('\n'),
// },
// },
// {
// // type: 'min',
// name: `最小值`,
// coord: [inner.minDate, inner.minValue],
// relativeTo: 'coordinate',
// label: {
// position: "top",
// formatter: item.dataType === 2 ? ([
// `最小值:${inner.minValue}`,
// // `平均值:${inner.avgValue}`,
// `差值:${inner.diffValue}`,
// ]).join('\n') : ([
// `最小值:${inner.minValue}`,
// // `平均值:${inner.avgValue}`,
// ]).join('\n'),
// }
// }
// ]
// },
xdata: [],
data: [],
});
const length = dataset.length;
inner.pointValueList.forEach((value) => {
const {valueDate, min, q1, median, q3, max} = value
// const mid = (max - min) / 2, minLine = min + Math.abs(median / 2),
// maxLine = max - Math.abs(median / 2)
dataset[length - 1].xdata.push(valueDate);
dataset[length - 1].data.push([min, q1, median, q3, max]);
});
});
});
console.log("箱型图图表数据", dataset);
this.chart.setOption({
legend: {
// left: 'center',
// top: '10',
},
grid: {
containLabel: true,
},
tooltip: {
trigger: 'item',
formatter: function (params) {
let data = params.data;
let result = params.marker + params.name + ' ' + params.seriesName + '<br/>';
result += '最小值: ' + data[1] + '<br/>';
result += '平均值: ' + data[3] + '<br/>';
result += '最大值: ' + data[5];
return result;
}
// trigger: "axis",
// axisPointer: {
// type: 'cross',
// },
// axisPointer: {
// // 坐标轴指示器,坐标轴触发有效
// type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
// },
},
textStyle: {
color: "#333333",
},
xAxis: {type: "category", data: dataset?.[0]?.xdata || []},
yAxis: {
type: "value",
},
dataZoom: [
{
type: "inside",
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
series: dataset,
});
},
updateDate(val) {
this.dataRange = val || [];
this.getDate();
},
handleClosed(done) {
if (!this.chart) {
return done();
}
this.chart.dispose();
this.chart = null;
done();
},
},
};
</script>
<style scoped lang="scss">
::v-deep {
.card-title .el-radio {
line-height: 40px;
}
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<!-- 6个方块-->
<el-row :gutter="10">
<el-col :xs="12" :sm="8" :lg="4" style="margin-bottom: 10px;" class="single-square-box-container" v-for="(item,index) in singleZdSqaure" :key="index+'singleSquareBox'">
<single-square-box :data="{...item,value:formatNumber(data[item.attr])}" ></single-square-box>
</el-col>
</el-row>
</template>
<script>
import SingleSquareBox from "@/components/Ems/SingleSquareBox/index.vue";
import {formatNumber} from '@/filters/ems'
export default {
components:{SingleSquareBox},
props:{
data:{
type:Object,
required:false,
default:()=>{return {}}
},
},
methods:{formatNumber},
data() {
return {
// 单个电站 四个方块数据
singleZdSqaure:[{
title:'实时有功功率kW',
value:'',
bgColor:'#FFF2CB',
attr:'totalActivePower'
},{
title:'实时无功功率kVar',
value:'',
bgColor:'#CBD6FF',
attr:'totalReactivePower'
},{
title:'电池堆SOC',
value:'',
bgColor:'#DCCBFF',
attr:'soc'
},{
title:'电池堆SOH',
value:'',
bgColor:'#FFD4CB',
attr:'soh'
},{
title:'今日充电量kWh',
value:'',
bgColor:'#FFD6F8',
attr:'dayChargedCap'
},{
title:'今日放电量kWh',
value:'',
bgColor:'#E1FFCA',
attr:'dayDisChargedCap'
}]
}
},
}
</script>
<style scoped lang="scss">
@media only screen and (min-width: 1200px) {
.single-square-box-container {
min-width: 16.6666666667%;
width: fit-content;
}
}
</style>

View File

@ -0,0 +1,250 @@
<template>
<div v-loading="loading">
<div v-for="(baseInfo,index) in baseInfoList" :key="index+'bmsdccContainer'" style="margin-bottom:25px;">
<el-card shadow="always"
class="sbjk-card-container common-card-container-body-no-padding common-card-container-no-title-bg"
:class="handleCardClass(baseInfo)">
<div slot="header">
<span
class="large-title">{{
baseInfo.parentDeviceName ? `${baseInfo.parentDeviceName} -> ` : ''
}}{{ baseInfo.deviceName }}</span>
<div class="info">
<div>数据更新时间{{ baseInfo.dataUpdateTime || '-' }}</div>
</div>
<div class="alarm">
<el-button type="primary" round size="small" style="margin-right:20px;"
@click="pointDetail(baseInfo,'point')">详细
</el-button>
<el-badge :hidden="!baseInfo.alarmNum" :value="baseInfo.alarmNum || 0" class="item">
<i
class="el-icon-message-solid alarm-icon"
@click="pointDetail(baseInfo,'alarmPoint')"
></i>
</el-badge>
</div>
</div>
<div class="descriptions-main">
<el-descriptions direction="vertical" :column="3" :colon="false">
<el-descriptions-item
contentClassName="descriptions-direction work-status"
:span="1" label="工作状态">
{{ CLUSTERWorkStatusOptions[baseInfo.workStatus] }}
</el-descriptions-item>
<el-descriptions-item contentClassName="descriptions-direction"
:span="1" label="与PCS通信">
{{ $store.state.ems.communicationStatusOptions[baseInfo.pcsCommunicationStatus] }}
</el-descriptions-item>
<el-descriptions-item contentClassName="descriptions-direction"
:span="1" label="与EMS通信">
{{ $store.state.ems.communicationStatusOptions[baseInfo.emsCommunicationStatus] }}
</el-descriptions-item>
</el-descriptions>
</div>
<div class="descriptions-main descriptions-main-bg-color">
<el-descriptions direction="vertical" :column="3" :colon="false">
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction"
v-for="(item,index) in infoData" :key="index+'pcsInfoData'" :span="1"
:label="item.label">
<span class="pointer" @click="showChart(item.pointName || '',baseInfo.deviceId)">
{{ baseInfo[item.attr] | formatNumber }} <span v-if="item.unit" v-html="item.unit"></span>
</span>
</el-descriptions-item>
</el-descriptions>
<!-- 进度-->
<div class="process-container">
<div class="process-line-bg">
<div class="process-line" :style="{height:baseInfo.currentSoc+'%'}"></div>
</div>
<div class="process pointer" @click="showChart( '当前SOC',baseInfo.deviceId)">当前SOC :
{{ baseInfo.currentSoc }}%
</div>
</div>
</div>
<el-table
class="common-table"
:data="baseInfo.batteryDataList"
stripe
style="width: 100%;margin-top:25px;">
<el-table-column
prop="dataName"
label="名称">
<template slot-scope="scope">
<span v-html="scope.row.dataName+''+unitObj[scope.row.dataName]+''"></span>
</template>
</el-table-column>
<el-table-column
prop="avgData"
label="单体平均值"
>
<template slot-scope="scope">
<span class="pointer"
@click="showChart( tablePointNameMap[scope.row.dataName+scope.column.label],baseInfo.deviceId)">{{
scope.row.avgData
}}</span>
</template>
</el-table-column>
<el-table-column
prop="minData"
label="单体最小值">
<template slot-scope="scope">
<span class="pointer"
@click="showChart( tablePointNameMap[scope.row.dataName+scope.column.label],baseInfo.deviceId)">{{
scope.row.minData
}}</span>
</template>
</el-table-column>
<el-table-column
prop="minDataID"
label="单体最小值ID">
</el-table-column>
<el-table-column
prop="maxData"
label="单体最大值">
<template slot-scope="scope">
<span class="pointer "
@click="showChart( tablePointNameMap[scope.row.dataName+scope.column.label],baseInfo.deviceId)">{{
scope.row.maxData
}}</span>
</template>
</el-table-column>
<el-table-column
prop="maxDataID"
label="单体最大值ID">
</el-table-column>
</el-table>
</el-card>
</div>
<el-empty v-show="baseInfoList.length<=0" :image-size="200"></el-empty>
<point-chart ref="pointChart" :site-id="siteId"/>
<point-table ref="pointTable"/>
</div>
</template>
<script>
import pointChart from "./../PointChart.vue";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
import {getBMSBatteryCluster} from '@/api/ems/dzjk'
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import intervalUpdate from "@/mixins/ems/intervalUpdate";
import {mapState} from "vuex";
export default {
name: 'DzjkSbjkBmsdcc',
mixins: [getQuerySiteId, intervalUpdate],
components: {PointTable, pointChart},
computed: {
...mapState({
CLUSTERWorkStatusOptions: state => state?.ems?.CLUSTERWorkStatusOptions || {},
})
},
data() {
return {
loading: false,
unitObj: {
'电压': 'V',
'温度': '&#8451;',
'SOC': '%'
},
tablePointNameMap: {
'电压单体最小值': '最低单体电压',
'电压单体平均值': '电压平均值',
'电压单体最大值': '最高单体电压',
'温度单体最小值': '最低单体温度',
'温度单体平均值': '平均单体温度',
'温度单体最大值': '最高单体温度',
'SOC单体最小值': '最低单体SOC',
'SOC单体平均值': '当前SOC',
'SOC单体最大值': '最高单体SOC',
},
baseInfoList: [],
infoData: [
{label: '簇电压', attr: 'clusterVoltage', unit: 'V', pointName: '簇电压'},
{label: '可充电量', attr: 'chargeableCapacity', unit: 'kWh', pointName: '可充电量'},
{label: '累计充电量', attr: 'totalChargedCapacity', unit: 'kWh', pointName: '累计充电量'},
{label: '簇电流', attr: 'clusterCurrent', unit: 'A', pointName: '簇电流'},
{label: '可放电量', attr: 'dischargeableCapacity', unit: 'kWh', pointName: '可放电量'},
{label: '累计放电量', attr: 'totalDischargedCapacity', unit: 'kWh', pointName: '累计放电量'},
{label: 'SOH', attr: 'soh', unit: '%', pointName: 'SOH'},
{label: '平均温度', attr: 'averageTemperature', unit: '&#8451;', pointName: '平均温度'},
{label: '绝缘电阻', attr: 'insulationResistance', unit: '&Omega;', pointName: '绝缘电阻'},
],
}
},
methods: {
handleCardClass(item) {
const {workStatus = ''} = item
return !(Object.keys(this.CLUSTERWorkStatusOptions).includes(item.workStatus)) ? "timing-card-container" : workStatus === '9' ? 'warning-card-container' : 'running-card-container'
},
// 查看设备电位表格
pointDetail(row, dataType) {
const {siteId, deviceId} = row
this.$refs.pointTable.showTable({siteId, deviceId, deviceCategory: 'CLUSTER'}, dataType)
},
showChart(pointName, deviceId) {
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory: 'CLUSTER', deviceId})
},
updateData() {
this.loading = true
getBMSBatteryCluster(this.siteId).then(response => {
this.baseInfoList = JSON.parse(JSON.stringify(response?.data || []));
}).finally(() => {
this.loading = false
})
},
init() {
this.updateData()
this.updateInterval(this.updateData)
}
}
}
</script>
<style scoped lang="scss">
::v-deep {
//描述列表样式
.descriptions-main {
padding: 24px 300px 24px 24px;
}
.descriptions-main-bottom {
padding: 14px 300px 14px 24px;
}
}
// 进度条样式
.process-container {
width: 100px;
position: absolute;
right: 70px;
top: 50%;
transform: translateY(-50%);
.process-line-bg {
position: relative;
width: 100%;
height: 110px;
background-color: #fff2cb;
border-radius: 6px;
box-shadow: 0 0 10px #fff2cb, 0 0 0 rgba(255, 242, 203, 0.5);
.process-line {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 25%;
background-color: #ffbf14;
border-radius: 0 0 6px 6px;
box-shadow: 0 0 10px #ffbf14, 0 0 0 rgba(255, 191, 20, 0.5);
}
}
.process {
margin-top: 15px;
color: #666666;
}
}
</style>

View File

@ -0,0 +1,270 @@
<template>
<div v-loading="loading">
<div v-for="(baseInfo,index) in baseInfoList" :key="index+'bmszlContainer'" style="margin-bottom:25px;">
<el-card
:class="handleCardClass(baseInfo)"
class="sbjk-card-container common-card-container-body-no-padding common-card-container-no-title-bg"
shadow="always">
<div slot="header">
<span class="large-title">{{ baseInfo.deviceName }}</span>
<div class="info">
<div>数据更新时间{{ baseInfo.dataUpdateTime || '-' }}</div>
</div>
<div class="alarm">
<el-button type="primary" round size="small" style="margin-right:20px;"
@click="pointDetail(baseInfo,'point')">详细
</el-button>
<el-badge :hidden="!baseInfo.alarmNum" :value="baseInfo.alarmNum || 0" class="item">
<i
class="el-icon-message-solid alarm-icon"
@click="pointDetail(baseInfo,'alarmPoint')"
></i>
</el-badge>
</div>
</div>
<div class="descriptions-main">
<el-descriptions :colon="false" :column="3" direction="vertical">
<el-descriptions-item
contentClassName="descriptions-direction work-status"
label="工作状态" labelClassName="descriptions-label">
{{ STACKWorkStatusOptions[baseInfo.workStatus] }}
</el-descriptions-item>
<el-descriptions-item :span="1" contentClassName="descriptions-direction" label="与PCS通信"
labelClassName="descriptions-label">
{{ $store.state.ems.communicationStatusOptions[baseInfo.pcsCommunicationStatus] }}
</el-descriptions-item>
<el-descriptions-item :span="1" contentClassName="descriptions-direction" label="与EMS通信"
labelClassName="descriptions-label">
{{ $store.state.ems.communicationStatusOptions[baseInfo.emsCommunicationStatus] }}
</el-descriptions-item>
</el-descriptions>
</div>
<div class="descriptions-main descriptions-main-bg-color">
<el-descriptions :colon="false" :column="3" direction="vertical">
<el-descriptions-item v-for="(item,index) in infoData" :key="index+'pcsInfoData'" :label="item.label"
:span="1" contentClassName="descriptions-direction"
labelClassName="descriptions-label">
<span class="pointer" @click="showChart(item.pointName || '',baseInfo.deviceId)">
{{ baseInfo[item.attr] | formatNumber }}<span v-if="item.unit" v-html="item.unit"></span>
</span>
</el-descriptions-item>
</el-descriptions>
<!-- 进度-->
<div class="process-container">
<div class="process-line-bg">
<div :style="{height:baseInfo.stackSoc+'%'}" class="process-line"></div>
</div>
<div class="process pointer" @click="showChart('当前SOC',baseInfo.deviceId)">当前SOC :
{{ baseInfo.stackSoc }}%
</div>
</div>
</div>
<el-table
:data="baseInfo.batteryDataList"
class="common-table"
max-height="500"
stripe
style="width: 100%;margin-top:25px;">
<el-table-column
label="簇号"
prop="clusterId">
</el-table-column>
<el-table-column
label="簇电压"
>
<template slot-scope="scope">
<span class="pointer"
@click="showChart('簇电压',scope.row.clusterId,'CLUSTER')">{{ scope.row.clusterVoltage }} V</span>
</template>
</el-table-column>
<el-table-column
label="簇电流">
<template slot-scope="scope">
<span class="pointer"
@click="showChart('簇电流',scope.row.clusterId,'CLUSTER')">{{ scope.row.clusterCurrent }} A</span>
</template>
</el-table-column>
<el-table-column
label="簇SOC">
<template slot-scope="scope">
<span class="pointer"
@click="showChart('当前SOC',scope.row.clusterId,'CLUSTER')">{{ scope.row.currentSoc }} %</span>
</template>
</el-table-column>
<el-table-column
label="单体最高电压"
prop="maxVoltage">
<template slot-scope="scope">
<span class="pointer"
@click="showChart('最高单体电压',scope.row.clusterId,'CLUSTER')">{{
scope.row.maxCellVoltage
}} V</span>
</template>
</el-table-column>
<el-table-column
label="电池号码"
prop="maxCellVoltageId">
</el-table-column>
<el-table-column
label="单体最低电压"
prop="minVoltage">
<template slot-scope="scope">
<span class="pointer"
@click="showChart('最低单体电压',scope.row.clusterId,'CLUSTER')">{{
scope.row.minCellVoltage
}} V</span>
</template>
</el-table-column>
<el-table-column
label="电池号码"
prop="minCellVoltageId">
</el-table-column>
<el-table-column
label="单体最高温度">
<template slot-scope="scope">
<span class="pointer"
@click="showChart('最高单体温度',scope.row.clusterId,'CLUSTER')">{{
scope.row.maxCellTemp
}} &#8451;</span>
</template>
</el-table-column>
<el-table-column
label="电池号码"
prop="maxCellTempId">
</el-table-column>
<el-table-column
label="单体最低温度"
prop="minTemperature">
<template slot-scope="scope">
<span class="pointer"
@click="showChart('最低单体温度',scope.row.clusterId,'CLUSTER')">{{
scope.row.minCellTemp
}} &#8451;</span>
</template>
</el-table-column>
<el-table-column
label="电池号码"
prop="minCellTempId">
</el-table-column>
</el-table>
</el-card>
</div>
<el-empty v-show="baseInfoList.length<=0" :image-size="200"></el-empty>
<point-chart ref="pointChart" :site-id="siteId"/>
<point-table ref="pointTable"/>
</div>
</template>
<script>
import {getBMSOverView} from '@/api/ems/dzjk'
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import intervalUpdate from "@/mixins/ems/intervalUpdate";
import pointChart from "./../PointChart.vue";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
import {mapState} from "vuex";
export default {
name: 'DzjkSbjkBmszl',
components: {pointChart, PointTable},
mixins: [getQuerySiteId, intervalUpdate],
computed: {
...mapState({
STACKWorkStatusOptions: state => state?.ems?.STACKWorkStatusOptions || {},
})
},
data() {
return {
loading: false,
baseInfoList: [],
infoData: [
{label: '电池堆总电压', attr: 'stackVoltage', unit: 'V', pointName: '电池堆电压'},
{label: '可充电量', attr: 'availableChargeCapacity', unit: 'kWh', pointName: '可充电量'},
{label: '累计充电量', attr: 'totalChargeCapacity', unit: 'kWh', pointName: '累计充电量'},
{label: '电池堆总电流', attr: 'stackCurrent', unit: 'A', pointName: '电池堆总电流'},
{label: '可放电量', attr: 'availableDischargeCapacity', unit: 'kWh', pointName: '可放电量'},
{label: '累计放电量', attr: 'totalDischargeCapacity', unit: 'kWh', pointName: '累计放电量'},
{label: 'SOH', attr: 'stackSoh', unit: '%', pointName: 'SOH'},
{label: '平均温度', attr: 'operatingTemp', unit: '&#8451;', pointName: '平均温度'},
{label: '绝缘电阻', attr: 'stackInsulationResistance', unit: '&Omega;', pointName: '绝缘电阻'},
]
}
},
methods: {
handleCardClass(item) {
const {workStatus = ''} = item
return !Object.keys(this.STACKWorkStatusOptions).find(i => i === workStatus) ? "timing-card-container" : workStatus === '9' ? 'warning-card-container' : 'running-card-container'
},
// 查看设备电位表格
pointDetail(row, dataType) {
const {siteId, deviceId} = row
this.$refs.pointTable.showTable({siteId, deviceId, deviceCategory: 'STACK'}, dataType)
},
showChart(pointName, deviceId, deviceCategory = 'STACK') {
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory, deviceId})
},
updateData() {
this.loading = true
getBMSOverView(this.siteId).then(response => {
this.baseInfoList = JSON.parse(JSON.stringify(response?.data || []));
}).finally(() => {
this.loading = false
})
},
init() {
this.updateData()
this.updateInterval(this.updateData)
}
}
}
</script>
<style lang="scss" scoped>
::v-deep {
//描述列表样式
.descriptions-main {
padding: 24px 300px 24px 24px;
.descriptions-main-bg-color {
padding: 14px 300px 14px 24px;
}
}
}
// 进度条样式
.process-container {
width: 100px;
position: absolute;
right: 70px;
top: 50%;
transform: translateY(-50%);
.process-line-bg {
position: relative;
width: 100%;
height: 110px;
background-color: #fff2cb;
border-radius: 6px;
box-shadow: 0 0 10px #fff2cb, 0 0 0 rgba(255, 242, 203, 0.5);
.process-line {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 0;
background-color: #fc6c6c;
border-radius: 0 0 6px 6px;
box-shadow: 0 0 10px rgb(252 108 108), 0 0 0 rgba(252, 108, 108, 0.5);
}
}
.process {
margin-top: 15px;
color: #666666;
}
}
</style>

View File

@ -0,0 +1,237 @@
<template>
<div v-loading="loading">
<el-card
v-for="(item,index) in list"
:key="index+'dbList'"
shadow="always"
class="sbjk-card-container list"
:class="{
'timing-card-container':!['0','2'].includes(item.emsCommunicationStatus),
'warning-card-container':item.emsCommunicationStatus === '2',
'running-card-container':item.emsCommunicationStatus === '0'
}"
>
<div slot="header">
<span class="large-title">{{ item.deviceName }}</span>
<div class="info">
<div>
{{
communicationStatusOptions[item.emsCommunicationStatus] || '-'
}}
</div>
<div>数据更新时间{{ item.dataUpdateTime || '-' }}</div>
</div>
<div class="alarm">
<el-button type="primary" round size="small" style="margin-right:20px;" @click="pointDetail(item,'point')">
详细
</el-button>
<el-badge :hidden="!item.alarmNum" :value="item.alarmNum || 0" class="item">
<i
class="el-icon-message-solid alarm-icon"
@click="pointDetail(item,'alarmPoint')"
></i>
</el-badge>
</div>
</div>
<el-row class="device-info-row">
<el-col v-for="(tempDataItem,tempDataIndex) in (deviceIdTypeMsg[item.deviceId] || otherTypeMsg)"
:key="tempDataIndex+'dbTempData'"
:span="8" class="device-info-col">
<span class="pointer" @click="showChart(tempDataItem.pointName,item.deviceId)">
<span class="left">{{ tempDataItem.name }}</span> <span class="right">{{ item[tempDataItem.attr] || '-' }}<span
v-html="tempDataItem.unit"></span></span>
</span>
</el-col>
</el-row>
</el-card>
<el-empty v-show="list.length<=0" :image-size="200"></el-empty>
<point-chart ref="pointChart" :site-id="siteId"/>
<point-table ref="pointTable"/>
</div>
</template>
<script>
import pointChart from "./../PointChart.vue";
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getAmmeterDataList} from "@/api/ems/dzjk";
import intervalUpdate from "@/mixins/ems/intervalUpdate";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
import {mapState} from "vuex";
export default {
name: "DzjkSbjkDb",
mixins: [getQuerySiteId, intervalUpdate],
components: {PointTable, pointChart},
computed: {
...mapState({
communicationStatusOptions: state => state?.ems?.communicationStatusOptions || {},
})
},
data() {
return {
loading: false,
list: [],
deviceIdTypeMsg: {
'LOAD': [
{
name: '正向有功电能',
attr: 'forwardActive',
pointName: '正向有功电能',
unit: 'kWh'
},
{
name: '反向有功电能',
attr: 'reverseActive',
pointName: '反向有功电能',
unit: 'kWh'
},
{
name: '正向无功电能',
attr: 'forwardReactive',
pointName: '正向无功电能',
unit: 'kvarh'
},
{
name: '反向无功电能',
attr: 'reverseReactive',
pointName: '反向无功电能',
unit: 'kvarh'
},
{
name: '有功功率',
attr: 'activePower',
pointName: '总有功功率',
unit: 'kW'
},
{
name: '无功功率',
attr: 'reactivePower',
pointName: '总无功功率',
unit: 'kvar'
}
],
'METE': [
{
name: '正向有功电能',
attr: 'forwardActive',
pointName: '正向有功电能',
unit: 'kWh'
},
{
name: '反向有功电能',
attr: 'reverseActive',
pointName: '反向有功电能',
unit: 'kWh'
},
{
name: '正向无功电能',
attr: 'forwardReactive',
pointName: '正向无功电能',
unit: 'kvarh'
},
{
name: '反向无功电能',
attr: 'reverseReactive',
pointName: '反向无功电能',
unit: 'kvarh'
},
{
name: '有功功率',
attr: 'activePower',
pointName: '总有功功率',
unit: 'kW'
},
{
name: '无功功率',
attr: 'reactivePower',
pointName: '总无功功率',
unit: 'kvar'
}
],
'METEGF': [
{
name: '有功电能',
attr: 'activeEnergy',
pointName: '有功电能',
unit: 'kWh'
},
{
name: '无功电能',
attr: 'reactiveEnergy',
pointName: '无功电能',
unit: 'kvarh'
},
{
name: '有功功率',
attr: 'activePower',
pointName: '总有功功率',
unit: 'kW'
},
{
name: '无功功率',
attr: 'reactivePower',
pointName: '总无功功率',
unit: 'kvar'
}
]
},
otherTypeMsg: [
{
name: '正向有功电能',
attr: 'forwardActive',
pointName: '正向有功电能',
unit: 'kWh'
},
{
name: '反向有功电能',
attr: 'reverseActive',
pointName: '反向有功电能',
unit: 'kWh'
},
{
name: '有功功率',
attr: 'activePower',
pointName: '总有功功率',
unit: 'kW'
},
]
};
},
methods: {
// 查看设备电位表格
pointDetail(row, dataType) {
const {deviceId} = row
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'AMMETER'}, dataType)
},
showChart(pointName, deviceId) {
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory: 'AMMETER', deviceId})
},
updateData() {
this.loading = true;
getAmmeterDataList(this.siteId)
.then((response) => {
this.list = response?.data || []
})
.finally(() => {
this.loading = false;
});
},
init() {
this.updateData()
this.updateInterval(this.updateData)
},
},
mounted() {
},
};
</script>
<style scoped lang="scss">
.sbjk-card-container {
&.list:not(:last-child) {
margin-bottom: 25px;
}
}
</style>

View File

@ -0,0 +1,120 @@
<template>
<div v-loading="loading">
<el-card
v-for="(item,index) in list"
:key="index+'ylLise'"
class="sbjk-card-container running-card-container"
shadow="always">
<div slot="header">
<span class="large-title">{{ item.deviceName }}</span>
<div class="info">
<div>数据更新时间{{ item.dataUpdateTime || '-' }}</div>
</div>
<div class="alarm">
<el-button type="primary" round size="small" style="margin-right:20px;" @click="pointDetail(item,'point')">
详细
</el-button>
<el-badge :hidden="!item.alarmNum" :value="item.alarmNum || 0" class="item">
<i
class="el-icon-message-solid alarm-icon"
@click="pointDetail(item,'alarmPoint')"
></i>
</el-badge>
</div>
</div>
<el-row class="device-info-row">
<el-col v-for="(tempDataItem,tempDataIndex) in tempData" :key="tempDataIndex+'hdTempData'" :span="12"
class="device-info-col">
<span class="pointer" @click="showChart(tempDataItem.title,item.deviceId)">
<span class="left">{{ tempDataItem.title }}</span> <span
class="right">{{ item[tempDataItem.attr] || '-' }}<span
v-html="tempDataItem.unit"></span></span>
</span>
</el-col>
</el-row>
</el-card>
<el-empty v-show="list.length<=0" :image-size="200"></el-empty>
<point-chart ref="pointChart" :site-id="siteId"/>
<point-table ref="pointTable"/>
</div>
</template>
<script>
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getDhDataList} from '@/api/ems/dzjk'
import intervalUpdate from "@/mixins/ems/intervalUpdate";
import pointChart from "./../PointChart.vue";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
export default {
name: 'DzjkSbjkDh',
mixins: [getQuerySiteId, intervalUpdate],
components: {pointChart, PointTable},
data() {
return {
loading: false,
list: [],
tempData: [
{title: '湿度', attr: 'humidity', unit: ''},
{title: '温度', attr: 'temperature', unit: '&#8451;'},
]
}
},
methods: {
// 查看设备电位表格
pointDetail(row, dataType) {
const {deviceId} = row
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'DH'}, dataType)
},
showChart(pointName, deviceId) {
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory: 'DH', deviceId})
},
updateData() {
this.loading = true
getDhDataList(this.siteId).then(response => {
this.list = JSON.parse(JSON.stringify(response?.data || []));
}).finally(() => {
this.loading = false
})
},
init() {
this.updateData()
this.updateInterval(this.updateData)
}
},
mounted() {
}
}
</script>
<style scoped lang="scss">
.sbjk-card-container {
&:not(:last-child) {
margin-bottom: 25px;
}
.el-row {
background-color: #ffffff;
border: 1px solid #eeeeee;
font-size: 14px;
line-height: 16px;
color: #333333;
.el-col {
padding: 12px 0;
text-align: center;
position: relative;
}
.el-col {
border-bottom: 1px solid #eeeeee;
}
.el-col:not(:nth-child(3n)) {
border-right: 1px solid #eeeeee;
}
}
}
</style>

View File

@ -0,0 +1,81 @@
<template>
<div style="height: 360px" id="dtdcChart"/>
</template>
<style scoped lang="scss"></style>
<script>
import * as echarts from 'echarts'
import resize from '@/mixins/ems/resize'
export default {
mixins: [resize],
data() {
return {
chart: null
}
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.querySelector('#dtdcChart'))
},
setOption() {
const source = [['日期','电压','温度','SOC','SOH']]
source.push(['1月','12','13','14','15'],['2月','12','13','14','15'])
this.chart.setOption({
color:['#FFBD00','#3C81FF','#05AEA3','#F86F70'],
legend: {
bottom: '10',
},
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
textStyle:{
color:"#333333",
},
xAxis: {
type: 'category',
},
yAxis: {
type: 'value',
},
dataset:{
source
// source: [['日期','充电量','放电量']]
},
series: [
{
name:'电压',
type: 'bar',
},{
name:'温度',
type: 'bar',
},
{
name:'SOC',
type: 'bar',
},{
name:'SOH',
type: 'bar',
}]
})
}
},
mounted() {
this.initChart()
}
}
</script>

View File

@ -0,0 +1,199 @@
<template>
<el-dialog
:visible.sync="dialogTableVisible"
:close-on-click-modal="false"
show-close
destroy-on-close
lock-scroll
append-to-body
width="700px"
class="ems-dialog chart-detail-dialog"
:before-close="handleColsed"
>
<el-card
shadow="always"
class="common-card-container time-range-card"
style="margin-top: 20px"
>
<div slot="header" class="time-range-header">
<span class="card-title"></span>
<date-range-select ref="dateRangeSelect" @updateDate="updateDate" />
</div>
<div class="card-main" v-loading="loading">
<div id="lineChart" style="height: 310px"></div>
</div>
</el-card>
</el-dialog>
</template>
<script>
import * as echarts from "echarts";
import resize from "@/mixins/ems/resize";
import { getSingleBatteryData } from "@/api/ems/dzjk";
import DateRangeSelect from "@/components/Ems/DateRangeSelect/index.vue";
export default {
components: { DateRangeSelect },
mixins: [resize],
data() {
return {
loading: false,
siteId: "",
deviceId: "",
clusterDeviceId: "",
dataType: "", //展示的数据类型 空值展示所有数据
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
},
},
dialogTableVisible: false,
dateRange: [],
};
},
methods: {
// 更新时间范围 重置图表
updateDate(data) {
this.dateRange = data || [];
this.getData();
},
handleColsed(done) {
if (!this.chart) {
return done();
}
this.chart.dispose();
this.chart = null;
done();
},
getData() {
if (this.loading) return;
this.loading = true;
this.chart.showLoading();
const {
siteId,
deviceId,
clusterDeviceId,
dateRange: [startDate = "", endDate = ""],
} = this;
getSingleBatteryData({
siteId,
deviceId,
clusterDeviceId,
startDate,
endDate,
})
.then((response) => {
this.setOption(response?.data || []);
})
.finally(() => {
this.loading = false;
this.chart.hideLoading();
});
},
initChart({ siteId, clusterDeviceId, deviceId }, dataType) {
this.siteId = siteId;
this.clusterDeviceId = clusterDeviceId;
this.deviceId = deviceId;
this.dataType = dataType;
this.dialogTableVisible = true;
this.$nextTick(() => {
!this.chart &&
(this.chart = echarts.init(document.querySelector("#lineChart")));
this.$refs.dateRangeSelect.init();
});
},
setOption(data) {
const obj = {
voltage: "电压",
temperature: "温度",
soc: "SOC",
soh: "SOH",
};
let source,
series,
{ dataType } = this;
if (dataType) {
source = [["日期", obj[dataType]]];
data.forEach((item) => {
source.push([item.dataTimestamp, item[dataType]]);
});
series = [
{
name: obj[dataType],
type: "line",
},
];
} else {
source = [["日期", "电压", "温度", "SOC", "SOH"]];
data.forEach((item) => {
source.push([
item.dataTimestamp,
item.voltage,
item.temperature,
item.soc,
item.soh,
]);
});
series = [
{
name: "电压",
type: "line",
},
{
name: "温度",
type: "line",
},
{
name: "SOC",
type: "line",
},
{
name: "SOH",
type: "line",
},
];
}
this.chart &&
this.chart.setOption({
color: ["#FFBD00", "#3C81FF", "#05AEA3", "#F86F70"],
grid: {
containLabel: true,
},
legend: {
left: "center",
bottom: "15",
},
tooltip: {
trigger: "axis",
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
},
},
textStyle: {
color: "#333333",
},
xAxis: {
type: "category",
},
yAxis: {
type: "value",
},
dataset: {
source,
},
series,
});
},
},
mounted() {},
};
</script>
<style lang="scss" scoped>
.chart-detail-dialog {
::v-deep {
.el-dialog__body {
padding-top: 0;
}
}
}
</style>

View File

@ -0,0 +1,193 @@
<template>
<div>
<template v-if="totalSize.length === 0">
<el-empty :size="200"></el-empty>
</template>
<template v-else>
<div class="lists-container clearfix">
<div
class="lists"
v-for="(item, index) in tableData"
:key="index + 'dtdcList'"
:class="handleListClass(item)"
>
<div style="font-size: 10px; font-weight: 600">
{{ item.clusterDeviceId }}
</div>
<div>#{{ item.deviceId }}</div>
<div class="dy pointer" @click="chartDetail(item, '电压 (V)')">
{{ item.voltage }}V
</div>
<div class="wd pointer" @click="chartDetail(item, '温度 (℃)')">
{{ item.temperature }}
</div>
</div>
</div>
<!-- <el-pagination
v-show="tableData.length > 0"
background
@size-change="(val) => $emit('handleSizeChange', val)"
@current-change="(val) => $emit('handleCurrentChange', val)"
:current-page="pageNum"
:page-size="pageSize"
:page-sizes="[10, 20, 30, 40]"
layout="total, sizes, prev, pager, next, jumper"
:total="totalSize"
style="margin-top: 15px; text-align: center"
>
</el-pagination> -->
</template>
</div>
</template>
<script>
export default {
props: {
pointIdList: {
require: true,
type: Object,
default: () => {
return {};
},
},
tableData: {
require: true,
type: Array,
default: () => {
return [];
},
},
totalSize: {
require: true,
type: Number,
default: 0,
},
// pageNum: {
// require: true,
// type: Number,
// default: 1,
// },
// pageSize: {
// require: true,
// type: Number,
// default: 10,
// },
},
data() {
return {
//最低单体温度 最高温度 最低电压 最高电压 todo 这里的顺序需要和图形组件里的顺序保持一致,
colorMap: {
0: "minwd",
1: "maxwd",
2: "mindy",
3: "maxdy",
},
};
},
methods: {
//处理图形class 对应高亮设置
handleListClass(item) {
let className = "";
const { clusterDeviceId, deviceId } = item,
clusterIdList = Object.keys(this.pointIdList);
if (clusterIdList.includes(clusterDeviceId)) {
const index = this.pointIdList[clusterDeviceId].findIndex(
(ids) => ids === parseInt(deviceId)
);
if (index > -1) {
className = this.colorMap[index];
}
}
return className;
},
//查看表格行图表
chartDetail(row, dataType = "") {
const { clusterDeviceId, deviceId } = row;
this.$emit("chart", { ...row, dataType });
},
},
};
</script>
<style lang="scss" scoped>
.lists-container {
padding: 20px 0;
.lists {
margin: 10px 5px;
padding: 5px 9px;
font-size: 11px;
line-height: 20px;
border: 1.6px solid #09ada3;
border-radius: 5px;
position: relative;
color: #333333;
float: left;
box-sizing: content-box;
min-width: 60px;
width: auto;
&::before {
display: block;
content: "";
top: -7px;
left: 50%;
transform: translateX(-50%);
position: absolute;
width: 45%;
height: 0;
border-bottom: 7px solid #09ada3;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
&.minwd {
border-color: #3794ff;
.wd {
color: #3794ff;
}
&::before {
border-bottom-color: #3794ff;
}
}
&.maxwd {
border-color: #ff3a3b;
.wd {
color: #ff3a3b;
}
&::before {
border-bottom-color: #ff3a3b;
}
}
&.mindy {
border-color: #de6902;
.dy {
color: #de6902;
}
&::before {
border-bottom-color: #de6902;
}
}
&.maxdy {
border-color: #ffb521;
.dy {
color: #ffb521;
}
&::before {
border-bottom-color: #ffb521;
}
}
}
}
.dtdc-pagination {
::v-deep {
.el-button {
padding: 2px 10px !important;
font-size: 11px;
line-height: 16px;
}
.activeBtn {
background-color: #09ada3;
border-color: #09ada3;
}
}
}
</style>

View File

@ -0,0 +1,127 @@
<template>
<div>
<el-table
class="common-table"
:data="tableData"
stripe
style="width: 100%; margin-top: 25px"
>
<el-table-column prop="deviceId" label="单体编号"></el-table-column>
<el-table-column prop="clusterDeviceId" label="簇号"></el-table-column>
<el-table-column prop="voltage" label="电压 (V)">
<template slot-scope="scope">
<el-button
@click="chartDetail(scope.row, '电压 (V)')"
type="text"
size="small"
>
{{ scope.row.voltage }}
</el-button>
</template>
</el-table-column>
<el-table-column prop="temperature" label="温度 (℃)">
<template slot-scope="scope">
<el-button
@click="chartDetail(scope.row, '温度 (℃)')"
type="text"
size="small"
>
{{ scope.row.temperature }}
</el-button>
</template>
</el-table-column>
<el-table-column prop="soc" label="SOC (%)">
<template slot-scope="scope">
<el-button
@click="chartDetail(scope.row, 'SOC (%)')"
type="text"
size="small"
>
{{ scope.row.soc }}
</el-button>
</template>
</el-table-column>
<el-table-column prop="soh" label="SOH (%)">
<template slot-scope="scope">
<el-button
@click="chartDetail(scope.row, 'SOH (%)')"
type="text"
size="small"
>
{{ scope.row.soh }}
</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template slot-scope="scope">
<el-button @click="$emit('pointDetail',scope.row,'point')" type="text" size="small">
详细
</el-button>
<el-button @click="$emit('pointDetail',scope.row,'alarmPoint')" type="text" size="small">
报警点位详细
</el-button>
</template>
</el-table-column>
</el-table>
<!-- <el-pagination
v-show="tableData.length > 0"
background
@size-change="(val) => $emit('handleSizeChange', val)"
@current-change="(val) => $emit('handleCurrentChange', val)"
:current-page="pageNum"
:page-size="pageSize"
:page-sizes="[10, 20, 30, 40]"
layout="total, sizes, prev, pager, next, jumper"
:total="totalSize"
style="margin-top: 15px; text-align: center"
>
</el-pagination> -->
</div>
</template>
<script>
export default {
props: {
tableData: {
require: true,
type: Array,
default: () => {
return [];
},
},
pointIdList: {
require: true,
type: Object,
default: () => {
return {};
},
},
totalSize: {
require: true,
type: Number,
default: 0,
},
// pageNum: {
// require: true,
// type: Number,
// default: 1,
// },
// pageSize: {
// require: true,
// type: Number,
// default: 10,
// },
},
data() {
return {};
},
methods: {
//查看表格行图表
chartDetail(row, dataType = "") {
this.$emit("chart", {...row, dataType});
},
},
};
</script>
<style></style>

View File

@ -0,0 +1,354 @@
<template>
<el-card
v-loading="loading"
shadow="always"
class="sbjk-card-container common-card-container-no-title-bg running-card-container"
>
<div slot="header">
<span class="large-title">单体电池实时数据</span>
</div>
<!-- 搜索栏-->
<el-form :inline="true" class="select-container">
<el-form-item label="编号">
<el-input
v-model="search.batteryId"
placeholder="请输入"
clearable
style="width: 150px"
/>
</el-form-item>
<el-form-item label="电池堆">
<el-select
v-model="search.stackId"
placeholder="请选择"
@change="changeStackId"
>
<el-option
:label="item.deviceName"
:value="item.id"
v-for="(item, index) in stackOptions"
:key="index + 'stackOptions'"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="电池簇">
<el-select
v-model="search.clusterId"
:no-data-text="
!search.stackId && stackOptions.length > 0
? '请先选择电池堆'
: '无数据'
"
placeholder="请选择"
:loading="clusterloading"
loading-text="正在加载数据"
>
<el-option
:label="item.deviceName"
:value="item.id"
v-for="(item, index) in clusterOptions"
:key="index + 'clusterOptions'"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearch" native-type="button"
>搜索</el-button
>
</el-form-item>
<el-form-item>
<el-button @click="onReset" native-type="button">重置</el-button>
</el-form-item>
</el-form>
<!-- 切换 -->
<div class="tip-container">
<div class="color-tip" v-show="activeBtn === 'list'">
单体信息
<span class="tip minwd">最低单体温度</span>
<span class="tip maxwd">最高单体温度</span>
<span class="tip mindy">单体最低电压</span>
<span class="tip maxdy">单体最高电压</span>
</div>
<el-button-group class="ems-btns-group">
<el-button
:class="{ activeBtn: activeBtn === 'table' }"
@click="changeMenu('table')"
>图表</el-button
>
<el-button
:class="{ activeBtn: activeBtn === 'list' }"
@click="changeMenu('list')"
>图形</el-button
>
</el-button-group>
</div>
<component
:is="activeBtn === 'table' ? 'DtdcTable' : 'DtdcList'"
:tableData="tableData"
:totalSize="totalSize"
:pointIdList="pointIdList"
@chart="chartDetail"
@pointDetail="pointDetail"
></component>
<el-pagination
v-show="tableData.length > 0"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-size="pageSize"
:page-sizes="[10, 20, 30, 40]"
layout="total, sizes, prev, pager, next, jumper"
:total="totalSize"
style="margin-top: 15px; text-align: center"
>
</el-pagination>
<chart-detail ref="chartDetail" />
<point-chart ref="pointChart" :site-id="siteId" />
<point-table ref="pointTable"/>
</el-card>
</template>
<script>
import BarChart from "./BarChart";
import {
getStackNameList,
getClusterNameList,
getClusterDataInfoList,
} from "@/api/ems/dzjk";
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import ChartDetail from "./ChartDetail.vue";
import Table from "./Table.vue";
import List from "./List.vue";
import pointChart from "./../PointChart.vue";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
export default {
name: "DzjkSbjkDtdc",
mixins: [getQuerySiteId],
components: {
PointTable,
BarChart,
ChartDetail,
DtdcTable: Table,
DtdcList: List,
pointChart,
},
computed: {
pointIdList() {
let obj = {};
this.pointData.forEach((item) => {
const {
maxCellTempId,
maxCellVoltageId,
minCellTempId,
minCellVoltageId,
} = item;
obj[item.clusterId] = [
parseInt(minCellTempId || 0),
parseInt(maxCellTempId || 0),
parseInt(minCellVoltageId || 0),
parseInt(maxCellVoltageId || 0),
]; //最低单体温度 最高温度 最低电压 最高电压 todo 这里的顺序需要和图形组件里的顺序保持一致,
});
return obj;
},
},
data() {
return {
loading: false,
clusterloading: false,
search: { stackId: "", clusterId: "", batteryId: "" },
stackOptions: [], //{id:'',deviceName:''}
clusterOptions: [], //{id:'',deviceName:''}
tableData: [],
pointData: [],
pageSize: 40, //分页栏当前每个数据总数
pageNum: 1, //分页栏当前页数
totalSize: 0, //table表格数据总数
activeBtn: "table",
};
},
methods: {
// 查看设备电位表格
pointDetail(row,dataType){
const {deviceId,clusterDeviceId} = row
this.$refs.pointTable.showTable({siteId:this.siteId,deviceId,deviceCategory:'BATTERY',parentId:clusterDeviceId},dataType)
},
changeMenu(menu) {
const { activeBtn } = this;
activeBtn !== menu && (this.activeBtn = menu);
},
//查看表格行图表
chartDetail({ deviceId, clusterDeviceId, dataType = "" }) {
dataType &&
this.$refs.pointChart.showChart({
pointName: dataType,
deviceCategory:'BATTERY',
deviceId: clusterDeviceId,
child: [deviceId],
});
},
// 分页
handleSizeChange(val) {
this.pageSize = val;
this.$nextTick(() => {
this.getTableData();
});
},
handleCurrentChange(val) {
this.pageNum = val;
this.$nextTick(() => {
this.getTableData();
});
},
// 搜索
onSearch() {
this.pageNum = 1; //每次搜索从1开始搜索
this.getTableData();
},
// 重置
// 清空搜索栏选中数据
// 清空电池簇列表,保留电池堆列表
onReset() {
this.search = { stackId: "", clusterId: "", batteryId: "" };
this.clusterOptions = [];
this.pageNum = 1;
this.getTableData();
},
changeStackId(val) {
if (val) {
console.log(
"选择了电池堆需要获取对应的电池簇",
val,
this.search.stackId
);
this.search.clusterId = "";
this.getClusterList();
}
},
//表格数据
getTableData() {
this.loading = true;
const {
stackId: stackDeviceId,
clusterId: clusterDeviceId,
batteryId,
} = this.search;
const { siteId, pageNum, pageSize } = this;
getClusterDataInfoList({
stackDeviceId,
clusterDeviceId,
siteId,
batteryId,
pageNum,
pageSize,
})
.then((response) => {
this.tableData = response?.rows?.[0]?.batteryList || []; //todo check
this.pointData = response?.rows?.[0]?.clusterList || []; //todo check
this.totalSize = response?.total || 0;
})
.finally(() => {
this.loading = false;
});
},
getStackList() {
getStackNameList(this.siteId).then((response) => {
this.stackOptions = JSON.parse(JSON.stringify(response?.data || []));
});
},
getClusterList() {
this.clusterloading = true;
getClusterNameList({
stackDeviceId: this.search.stackId,
siteId: this.siteId,
})
.then((response) => {
this.clusterOptions = JSON.parse(
JSON.stringify(response?.data || [])
);
})
.finally(() => {
this.clusterloading = false;
});
},
init() {
// 只有页面初次加载或切换站点的时候调用电池堆列表,其他情况不需要
this.search = { stackId: "", clusterId: "", batteryId: "" }; //保证切换站点时,清空选择项
this.clusterOptions = [];
this.pageNum = 1;
this.totalSize = 0;
this.getStackList();
this.getTableData();
},
},
mounted() {},
};
</script>
<style scoped lang="scss">
.tip-container {
text-align: right;
position: relative;
.color-tip {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
font-size: 11px;
line-height: 12px;
color: #333;
.tip {
padding-left: 30px;
position: relative;
&::before {
display: block;
content: "";
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
width: 8px;
height: 8px;
border-radius: 50%;
}
&.minwd {
color: #3794ff;
&::before {
background: #3794ff;
}
}
&.maxwd {
color: #ff3a3b;
&::before {
background: #ff3a3b;
}
}
&.mindy {
color: #de6902;
&::before {
background: #de6902;
}
}
&.maxdy {
color: #ffb521;
&::before {
background: #ffb521;
}
}
}
}
::v-deep {
.el-button-group.ems-btns-group {
& > .el-button {
padding: 5px 30px !important;
font-size: 11px;
line-height: 16px;
// padding-left: 50px;
// padding-right: 50px;
// font-size: 16px;
// line-height: 24px;
}
}
}
}
</style>

View File

@ -0,0 +1,133 @@
<template>
<div v-loading="loading" class="ems">
<el-card
v-for="(item,index) in list"
:key="index+'emsList'"
class="sbjk-card-container list running-card-container"
shadow="always"
>
<div slot="header">
<span class="large-title">{{ item.deviceName }}</span>
<div class="info">
<div>
EMS控制模式: {{
item.emsStatus === 0 ? '自动' : '手动'
}}
</div>
<div>数据更新时间{{ item.dataUpdateTime }}</div>
</div>
<div class="alarm">
<el-button size="small" round style="margin-right:20px;" type="primary" @click="pointDetail(item,'point')">详细
</el-button>
<el-badge :hidden="!item.alarmNum" :value="item.alarmNum || 0" class="item">
<i
class="el-icon-message-solid alarm-icon"
@click="pointDetail(item,'alarmPoint')"
></i>
</el-badge>
</div>
</div>
<el-row class="device-info-row">
<el-col v-for="(tempDataItem,tempDataIndex) in bmsDataList" :key="tempDataIndex+'bmsTempData'"
:span="6" class="device-info-col">
<span class="pointer" @click="showChart(tempDataItem.name,item.deviceId)">
<span class="left">{{ tempDataItem.name }}</span> <span class="right">{{ item[tempDataItem.attr] }}<span
v-html="tempDataItem.unit"></span></span>
</span>
</el-col>
<el-col v-for="(tempDataItem,tempDataIndex) in pcsDataList" :key="tempDataIndex+'pcsTempData'"
:span="6" class="device-info-col">
<span class="pointer" @click="showChart(tempDataItem.name,item.deviceId)">
<span class="left">{{ tempDataItem.name }}</span> <span class="right">{{ item[tempDataItem.attr] }}<span
v-html="tempDataItem.unit"></span></span>
</span>
</el-col>
</el-row>
</el-card>
<el-empty v-show="list.length <= 0" :image-size="200"></el-empty>
<point-chart ref="pointChart" :site-id="siteId"/>
<point-table ref="pointTable"/>
</div>
</template>
<script>
import pointChart from "./../PointChart.vue";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getEmsDataList} from "@/api/ems/dzjk";
import intervalUpdate from "@/mixins/ems/intervalUpdate";
export default {
name: "DzjkSbjkEms",
components: {pointChart, PointTable},
mixins: [getQuerySiteId, intervalUpdate],
data() {
return {
loading: false,
list: [],
bmsDataList: [{
name: 'BMS1SOC',
attr: 'bms1Soc'
},
{
name: 'BMS2SOC',
attr: 'bms2Soc'
},
{
name: 'BMS3SOC',
attr: 'bms3Soc'
},
{
name: 'BMS4SOC',
attr: 'bms4Soc'
}],
pcsDataList: [{
name: 'PCS-1有功功率',
attr: 'pcs1Yggl'
},
{
name: 'PCS-2有功功率',
attr: 'pcs2Yggl'
},
{
name: 'PCS-3有功功率',
attr: 'pcs3Yggl'
},
{
name: 'PCS-4有功功率',
attr: 'pcs4Yggl'
}]
};
},
methods: {
// 查看设备电位表格
pointDetail(row, dataType) {
const {deviceId} = row
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'EMS'}, dataType)
},
showChart(pointName, deviceId) {
pointName &&
this.$refs.pointChart.showChart({pointName, deviceCategory: 'EMS', deviceId});
},
getData() {
this.loading = true;
getEmsDataList(this.siteId)
.then((response) => {
const data = response?.data || {};
this.list = JSON.parse(JSON.stringify(data));
})
.finally(() => (this.loading = false));
},
updateData() {
this.getData();
},
init() {
this.updateData();
this.updateInterval(this.updateData);
},
},
};
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,77 @@
<template>
<div class="ems-dashboard-editor-container ems-third-menu-container" v-loading="loading">
<el-menu
class="ems-third-menu"
:default-active="$route.name"
background-color="#ffffff"
text-color="#666666"
active-text-color="#ffffff"
>
<el-menu-item :index="item.name" v-for="(item,index) in categoryRouter" :key="index+'dzjkChildrenRoute'">
<router-link style="height: 100%;width: 100%;display: block" :to="{path:item.path,query:$route.query}">
{{item.meta.title}}
</router-link>
</el-menu-item>
</el-menu>
<div class="ems-content-container ems-content-container-padding sbjk-ems-content-container">
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
</div>
</template>
<script>
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import { dzjk } from '@/router/ems'
import {mapState} from "vuex";
const childrenRoute = ((dzjk[0]?.children || []).find(item=> item.name==='DzjkSbjk').children) || []//获取到单站监控-设备监控下面的字路由
export default {
name:'DzjkSbjk',
mixins:[getQuerySiteId],
computed:{
...mapState({
zdDeviceCategoryOptions: state => state.ems.zdDeviceCategoryOptions,
}),
locationSiteCategory(){
return this.zdDeviceCategoryOptions[this.siteId] || []
},
categoryRouter(){
const routeData =this.childrenRoute.filter(item=>this.locationSiteCategory.includes(item.meta.deviceCategory))
if(this.siteId && routeData.length > 0 && this.locationSiteCategory && this.locationSiteCategory.length >1){
const locationPageDeviceCategory = this.$route.meta?.deviceCategory || ''
if(!routeData.some(item=> item.meta.deviceCategory===locationPageDeviceCategory)){
this.$router.replace({path:'/dzjk/sbjk/ssyx',query:this.$route.query})
}
}
return routeData
}
},
data(){
return {
childrenRoute,
activeMenu:'',
loading:false,
}
},
methods:{
init(){
this.loading=true
this.$store.dispatch('getSiteDeviceCategory',this.siteId).finally(()=>this.loading=false)
}
}
}
</script>
<style scoped lang="scss">
.sbjk-ems-dashboard-editor-container {
display: flex;
background: #ffffff;
}
.sbjk-ems-content-container {
margin-top: 0;
padding-top: 0;
padding-right: 0;
flex: 1;
}
</style>

View File

@ -0,0 +1,331 @@
<template>
<div v-loading="loading" class="pcs-ems-dashboard-editor-container">
<!-- 顶部六个方块-->
<real-time-base-info :data="runningHeadData"/>
<div
v-for="(pcsItem, pcsIndex) in pcsList"
:key="pcsIndex + 'PcsHome'"
style="margin-bottom: 25px"
>
<el-card
:class="handleCardClass(pcsItem)"
class="sbjk-card-container common-card-container-body-no-padding common-card-container-no-title-bg"
shadow="always"
>
<div slot="header">
<span class="large-title"
>{{ pcsItem.deviceName }}</span
>
<div class="info">
<div>
{{
$store.state.ems.communicationStatusOptions[
pcsItem.communicationStatus
]
}}
</div>
<div>数据更新时间{{ pcsItem.dataUpdateTime }}</div>
</div>
<div class="alarm">
<pcs-switch style="margin-right:10px;"
:round="true"
size="small"
type="danger"
:data="pcsItem"
@updateSuccess="init"/>
<el-button type="primary" round size="small" style="margin-right:20px;"
@click="pointDetail(pcsItem,'point')">
详细
</el-button>
<el-badge :hidden="!pcsItem.alarmNum" :value="pcsItem.alarmNum || 0" class="item">
<i
class="el-icon-message-solid alarm-icon"
@click="pointDetail(pcsItem,'alarmPoint')"
></i>
</el-badge>
</div>
</div>
<div class="descriptions-main">
<el-descriptions :colon="false" :column="4" direction="vertical">
<el-descriptions-item
contentClassName="descriptions-direction work-status"
:span="1"
label="工作状态"
labelClassName="descriptions-label"
>{{
PCSWorkStatusOptions[pcsItem.workStatus]
}}
</el-descriptions-item
>
<el-descriptions-item
:span="1"
contentClassName="descriptions-direction"
label="并网状态"
labelClassName="descriptions-label"
>{{
$store.state.ems.gridStatusOptions[pcsItem.gridStatus]
}}
</el-descriptions-item
>
<el-descriptions-item
:contentClassName="`descriptions-direction ${
pcsItem.deviceStatus === '1' ? 'save' : 'danger'
}`"
:span="1"
label="设备状态"
labelClassName="descriptions-label"
>{{
$store.state.ems.deviceStatusOptions[pcsItem.deviceStatus]
}}
</el-descriptions-item
>
<el-descriptions-item
:span="1"
contentClassName="descriptions-direction"
label="控制模式"
labelClassName="descriptions-label"
>{{
$store.state.ems.controlModeOptions[pcsItem.controlMode]
}}
</el-descriptions-item
>
</el-descriptions>
</div>
<div class="descriptions-main descriptions-main-bg-color">
<el-descriptions
:colon="false"
:column="4"
contentClassName="descriptions-direction"
direction="vertical"
labelClassName="descriptions-label"
>
<el-descriptions-item
v-for="(item, index) in infoData"
:key="index + 'pcsInfoData'"
:label="item.label"
:span="1"
>
<span
class="pointer"
@click="
showChart(item.pointName || '', pcsItem.deviceId)
"
>
{{ pcsItem[item.attr] | formatNumber }}
<span v-if="item.unit" v-html="item.unit"></span>
</span>
</el-descriptions-item>
</el-descriptions>
</div>
<div
v-for="(item, index) in pcsItem.pcsBranchInfoList"
:key="index + 'pcsBranchInfoList'"
class="descriptions-main"
>
<el-descriptions
:colon="false"
:column="4"
contentClassName="descriptions-direction keep"
direction="vertical"
labelClassName="descriptions-label"
>
<el-descriptions-item
:label="'支路' + (index + 1)"
:span="4"
contentClassName="descriptions-direction keep"
labelClassName="descriptions-label"
>{{ item.dischargeStatus }}
</el-descriptions-item
>
<el-descriptions-item
:span="1"
contentClassName="descriptions-direction"
label="直流功率"
labelClassName="descriptions-label"
>
<span
class="pointer"
@click="showChart('直流功率', item.deviceId,true)"
>{{ item.dcPower }}kW</span
>
</el-descriptions-item>
<el-descriptions-item
:span="1"
contentClassName="descriptions-direction"
label="直流电压"
labelClassName="descriptions-label"
>
<span
class="pointer"
@click="showChart('直流电压', item.deviceId,true)"
>{{ item.dcVoltage }}V</span
>
</el-descriptions-item>
<el-descriptions-item
:span="1"
contentClassName="descriptions-direction"
label="直流电流"
labelClassName="descriptions-label"
>
<span
class="pointer"
@click="showChart('直流电流', item.deviceId,true)"
>{{ item.dcCurrent }}A</span
>
</el-descriptions-item>
</el-descriptions>
</div>
</el-card>
</div>
<el-empty v-show="pcsList.length <= 0" :image-size="200"></el-empty>
<point-chart ref="pointChart" :site-id="siteId"/>
<point-table ref="pointTable"/>
</div>
</template>
<script>
import pointChart from "./../PointChart.vue";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
import RealTimeBaseInfo from "./../RealTimeBaseInfo.vue";
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getPcsDetailInfo, getRunningHeadInfo} from "@/api/ems/dzjk";
import intervalUpdate from "@/mixins/ems/intervalUpdate";
import PcsSwitch from "@/views/ems/site/sblb/PcsSwitch.vue";
import {mapState} from "vuex";
export default {
name: "DzjkSbjkPcs",
components: {RealTimeBaseInfo, pointChart, PointTable, PcsSwitch},
mixins: [getQuerySiteId, intervalUpdate],
computed: {
...mapState({
PCSWorkStatusOptions: state => state?.ems?.PCSWorkStatusOptions || {},
})
},
data() {
return {
loading: false,
runningHeadData: {}, //运行信息
pcsList: [],
infoData: [
{
label: "总交流有功功率",
attr: "totalActivePower",
unit: "kW",
pointName: "总交流有功功率",
},
{
label: "当天交流充电量",
attr: "dailyAcChargeEnergy",
unit: "kWh",
pointName: "当天交流充电量 (kWh)",
},
{label: "A相电压", attr: "aPhaseVoltage", unit: "V", pointName: ""},
{
label: "A相电流",
attr: "aPhaseCurrent",
unit: "A",
pointName: "A相电流",
},
{
label: "总交流无功功率",
attr: "totalReactivePower",
unit: "kVar",
pointName: "总交流无功功率",
},
{
label: "当天交流放电量",
attr: "dailyAcDischargeEnergy",
unit: "kWh",
pointName: "当天交流放电量 (kWh)",
},
{label: "B相电压", attr: "bPhaseVoltage", unit: "V", pointName: ""},
{
label: "B相电流",
attr: "bPhaseCurrent",
unit: "A",
pointName: "B相电流",
},
{
label: "总交流视在功率",
attr: "totalApparentPower",
unit: "kVA",
pointName: "总交流视在功率",
},
{
label: "PCS模块温度",
attr: "pcsModuleTemperature",
unit: "&#8451;",
pointName: "",
},
{label: "C相电压", attr: "cPhaseVoltage", unit: "V", pointName: ""},
{
label: "C相电流",
attr: "cPhaseCurrent",
unit: "A",
pointName: "C相电流",
},
{
label: "总交流功率因数",
attr: "totalPowerFactor",
unit: "",
pointName: "总交流功率因数",
},
{
label: "PCS环境温度",
attr: "pcsEnvironmentTemperature",
unit: "&#8451;",
pointName: "",
},
{
label: "交流频率",
attr: "acFrequency",
unit: "Hz",
pointName: "交流频率",
},
],
pcsBranchList: [], //pcs的支路列表
};
},
methods: {
handleCardClass(item) {
const {workStatus = ''} = item
return workStatus === '1' || !Object.keys(this.PCSWorkStatusOptions).find(i => i === workStatus) ? "timing-card-container" : workStatus === '2' ? 'warning-card-container' : 'running-card-container'
},
// 查看设备电位表格
pointDetail(row, dataType) {
const {deviceId} = row
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'PCS'}, dataType)
},
showChart(pointName, deviceId, isBranch = false) {
pointName &&
this.$refs.pointChart.showChart({pointName, deviceCategory: isBranch ? 'BRANCH' : 'PCS', deviceId});
},
//6个方块数据
getRunningHeadData() {
getRunningHeadInfo(this.siteId).then((response) => {
this.runningHeadData = response?.data || {};
});
},
getPcsList() {
this.loading = true;
getPcsDetailInfo(this.siteId)
.then((response) => {
const data = response?.data || {};
this.pcsList = JSON.parse(JSON.stringify(data));
})
.finally(() => (this.loading = false));
},
updateData() {
this.getRunningHeadData();
this.getPcsList();
},
init() {
this.updateData();
this.updateInterval(this.updateData);
},
},
};
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,144 @@
<template>
<el-card
shadow="always"
class="common-card-container common-card-container-body-no-padding"
>
<div slot="header">
<span class="card-title">PCS有功功率/PCS无功功率</span>
</div>
<div style="height: 360px" id="cnglqxChart"/>
</el-card>
</template>
<style scoped lang="scss"></style>
<script>
import * as echarts from "echarts";
import resize from "@/mixins/ems/resize";
import {storagePower} from "@/api/ems/dzjk";
export default {
mixins: [resize],
data() {
return {
chart: null,
};
},
mounted() {
this.chart = echarts.init(document.querySelector("#cnglqxChart"));
},
beforeDestroy() {
if (!this.chart) {
return;
}
this.chart.dispose();
this.chart = null;
},
methods: {
init(siteId, timeRange) {
this.chart.showLoading();
const [startTime = '', endTime = ''] = timeRange;
storagePower(siteId, startTime, endTime)
.then((response) => {
this.setOption(response?.data?.pcsPowerList || []);
})
.finally(() => {
this.chart.hideLoading();
});
},
setOption(data) {
let xdata = [],
series = [];
data.forEach((element, index) => {
if (index === 0) {
xdata = (element.energyStoragePowList || []).map((i) => i.createDate);
}
series.push(
{
type: "line",
name: `${element.deviceId}有功功率`,
areaStyle: {
// color:'#FFBD00'
},
data: (element.energyStoragePowList || []).map(
(i) => {
return {
value: i.pcsTotalActPower,
year: i.dateDay || ''
}
}
)
},
{
type: "line",
name: `${element.deviceId}无功功率`,
areaStyle: {
// color:'#FFBD00'
},
data: (element.energyStoragePowList || []).map(
(i) => {
return {
value: i.pcsTotalReactivePower,
year: i.dateDay || ''
}
}
),
}
);
});
this.chart && this.chart.setOption({
legend: {
left: "center",
top: "5",
itemWidth: 10,
itemHeight: 5,
textStyle: {
fontSize: 9,
},
},
grid: {
containLabel: true,
},
tooltip: {
show: true,
trigger: "axis",
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
},
formatter: (params) => {
if (params.length <= 0) return
let result = (params[0].data.year || '') + ' ' + params[0].name + '<div>'
params.forEach(item => {
const {color, seriesName, value} = item
result += `<div style="position: relative;padding-left:20px;line-height: 20px;">
<div style="position: absolute;top:50%;left:0;width:12px;height:12px;border-radius:100%;background: ${color};transform: translateY(-50%)"></div>
<span>${seriesName}</span><span style="margin-left:20px;font-weight: 700">${value}</span></div>`
})
result += '</div>'
return result
}
},
textStyle: {
color: "#333333",
},
xAxis: {type: "category", data: xdata},
yAxis: {
type: "value",
},
dataZoom: [
{
type: "inside",
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
series,
}, true);
},
},
};
</script>

View File

@ -0,0 +1,122 @@
<template>
<el-card
shadow="always"
class="common-card-container common-card-container-body-no-padding"
>
<div slot="header">
<span class="card-title">平均SOC</span>
</div>
<div style="height: 360px" id="dcpjsocChart" />
</el-card>
</template>
<style scoped lang="scss"></style>
<script>
import * as echarts from "echarts";
import resize from "@/mixins/ems/resize";
import { batteryAveSoc } from "@/api/ems/dzjk";
export default {
mixins: [resize],
data() {
return {
chart: null,
};
},
mounted() {
this.chart = echarts.init(document.querySelector("#dcpjsocChart"));
},
beforeDestroy() {
if (!this.chart) {
return;
}
this.chart.dispose();
this.chart = null;
},
methods: {
init(siteId,timeRange) {
this.chart.showLoading();
const [startTime='', endTime=''] = timeRange;
batteryAveSoc(siteId,startTime,endTime)
.then((response) => {
this.setOption(response?.data?.batteryAveSOCList || []);
})
.finally(() => {
this.chart.hideLoading();
});
},
setOption(data) {
let xdata = [],
ydata = [];
data.forEach((element) => {
xdata.push(element.createDate);
ydata.push({
value:element.batterySOC,
year:element.dateDay,
});
});
this.chart && this.chart.setOption({
legend: {
left: "center",
top: "5",
itemWidth: 10,
itemHeight: 5,
textStyle: {
fontSize: 9,
},
},
grid: {
containLabel: true,
},
tooltip: {
show:true,
trigger: "axis",
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
},
formatter :(params)=>{
if(params.length <= 0) return
let result = (params[0].data.year || '')+' '+params[0].name + '<div>'
params.forEach(item=>{
const {color,seriesName,value} = item
result += `<div style="position: relative;padding-left:20px;line-height: 20px;">
<div style="position: absolute;top:50%;left:0;width:12px;height:12px;border-radius:100%;background: ${color};transform: translateY(-50%)"></div>
<span>${seriesName}</span><span style="margin-left:20px;font-weight: 700">${value}</span></div>`
})
result+='</div>'
return result
}
},
textStyle: {
color: "#333333",
},
xAxis: { type: "category", data: xdata },
yAxis: {
type: "value",
},
dataZoom: [
{
type: "inside",
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
series: [
{
type: "line",
name: `平均SOC`,
areaStyle: {
// color:'#FFBD00'
},
data: ydata,
},
],
},true);
},
},
};
</script>

View File

@ -0,0 +1,125 @@
<template>
<el-card
shadow="always"
class="common-card-container common-card-container-body-no-padding"
>
<div slot="header">
<span class="card-title">电池平均温度</span>
</div>
<div style="height: 360px" id="dcpjwdChart" />
</el-card>
</template>
<style scoped lang="scss"></style>
<script>
import * as echarts from "echarts";
import resize from "@/mixins/ems/resize";
import { batteryAveTemp } from "@/api/ems/dzjk";
export default {
mixins: [resize],
data() {
return {
chart: null,
};
},
mounted() {
this.chart = echarts.init(document.querySelector("#dcpjwdChart"));
},
beforeDestroy() {
if (!this.chart) {
return;
}
this.chart.dispose();
this.chart = null;
},
methods: {
init(siteId,timeRange) {
this.chart.showLoading();
const [startTime='', endTime=''] = timeRange;
batteryAveTemp(siteId,startTime,endTime)
.then((response) => {
this.setOption(response?.data?.batteryAveTempList || []);
})
.finally(() => {
this.chart.hideLoading();
});
},
setOption(data) {
let xdata = [],
ydata = [];
data.forEach((element) => {
xdata.push(element.createDate);
ydata.push(
{
value: element.batteryTemp,
year: element.dateDay
}
);
});
this.chart && this.chart.setOption({
legend: {
left: "center",
top: "5",
itemWidth: 10,
itemHeight: 5,
textStyle: {
fontSize: 9,
},
},
grid: {
containLabel: true,
},
tooltip: {
show:true,
trigger: "axis",
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
},
formatter :(params)=>{
if(params.length <= 0) return
let result = (params[0].data.year || '')+' '+params[0].name + '<div>'
params.forEach(item=>{
const {color,seriesName,value} = item
result += `<div style="position: relative;padding-left:20px;line-height: 20px;">
<div style="position: absolute;top:50%;left:0;width:12px;height:12px;border-radius:100%;background: ${color};transform: translateY(-50%)"></div>
<span>${seriesName}</span><span style="margin-left:20px;font-weight: 700">${value}</span></div>`
})
result+='</div>'
return result
}
},
textStyle: {
color: "#333333",
},
xAxis: { type: "category", data: xdata },
yAxis: {
type: "value",
},
dataZoom: [
{
type: "inside",
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
series: [
{
type: "line",
name: `电池平均温度`,
areaStyle: {
// color:'#FFBD00'
},
data: ydata,
},
],
},true);
},
},
};
</script>

View File

@ -0,0 +1,125 @@
<template>
<el-card
shadow="always"
class="common-card-container common-card-container-body-no-padding"
>
<div slot="header">
<span class="card-title">PCS最高温度</span>
</div>
<div style="height: 360px" id="pocpjwdChart" />
</el-card>
</template>
<style scoped lang="scss"></style>
<script>
import * as echarts from "echarts";
import resize from "@/mixins/ems/resize";
import { pcsMaxTemp } from "@/api/ems/dzjk";
export default {
mixins: [resize],
data() {
return {
chart: null,
};
},
mounted() {
this.chart = echarts.init(document.querySelector("#pocpjwdChart"));
},
beforeDestroy() {
if (!this.chart) {
return;
}
this.chart.dispose();
this.chart = null;
},
methods: {
init(siteId,timeRange) {
this.chart.showLoading();
const [startTime='', endTime=''] = timeRange;
pcsMaxTemp(siteId,startTime,endTime)
.then((response) => {
this.setOption(response?.data?.pcsMaxTempList || []);
})
.finally(() => {
this.chart.hideLoading();
});
},
setOption(data) {
let xdata = [],
series = [];
data.forEach((element, index) => {
if (index === 0) {
xdata = (element.maxTempVoList || []).map((i) => i.createDate);
}
series.push({
type: "line",
name: `${element.deviceId}最高温度`,
areaStyle: {
// color:'#FFBD00'
},
data: (element.maxTempVoList || []).map((i) => {
return {
value: i.temp,
year: i.dateDay
}
}),
});
});
this.chart && this.chart.setOption({
legend: {
left: "center",
top: "5",
itemWidth: 10,
itemHeight: 5,
textStyle: {
fontSize: 9,
},
},
grid: {
containLabel: true,
},
tooltip: {
show:true,
trigger: "axis",
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
},
formatter :(params)=>{
if(params.length <= 0) return
let result = (params[0].data.year || '')+' '+params[0].name + '<div>'
params.forEach(item=>{
const {color,seriesName,value} = item
result += `<div style="position: relative;padding-left:20px;line-height: 20px;">
<div style="position: absolute;top:50%;left:0;width:12px;height:12px;border-radius:100%;background: ${color};transform: translateY(-50%)"></div>
<span>${seriesName}</span><span style="margin-left:20px;font-weight: 700">${value}</span></div>`
})
result+='</div>'
return result
}
},
textStyle: {
color: "#333333",
},
xAxis: { type: "category", data: xdata },
yAxis: {
type: "value",
},
dataZoom: [
{
type: "inside",
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
series,
},true);
},
},
};
</script>

View File

@ -0,0 +1,86 @@
<template>
<div class="ssyx-ems-dashboard-editor-container">
<!-- 6个方块-->
<real-time-base-info :data="runningHeadData"/>
<!-- 时间选择 -->
<date-range-select ref="dateRangeSelect" @updateDate="updateDate" style="margin-top:20px;"/>
<!-- echart图表-->
<el-row :gutter="32" style="background:#fff;margin:30px 0;">
<el-col :xs="24" :sm="12" :lg="12">
<cnglqx-chart ref='cnglqx'/>
</el-col>
<el-col :xs="24" :sm="12" :lg="12">
<pocpjwd-chart ref='pocpjwd'/>
</el-col>
</el-row>
<el-row :gutter="32" style="margin:30px 0;">
<el-col :xs="24" :sm="12" :lg="12">
<dcpjsoc-chart ref="dcpjsoc"/>
</el-col>
<el-col :xs="24" :sm="12" :lg="12">
<dcpjwd-chart ref="dcpjwd"/>
</el-col>
</el-row>
</div>
</template>
<style scoped lang="scss">
</style>
<script>
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue'
import RealTimeBaseInfo from "./../RealTimeBaseInfo.vue";
import CnglqxChart from './CnglqxChart.vue'
import PocpjwdChart from './PocpjwdChart.vue'
import DcpjwdChart from './DcpjwdChart.vue'
import DcpjsocChart from './DcpjsocChart.vue'
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getRunningHeadInfo} from '@/api/ems/dzjk'
import intervalUpdate from "@/mixins/ems/intervalUpdate";
export default {
name:'DzjkSbjkSsyx',
components:{RealTimeBaseInfo,CnglqxChart,PocpjwdChart,DcpjwdChart,DcpjsocChart,DateRangeSelect},
mixins:[getQuerySiteId,intervalUpdate],
data() {
return {
runningHeadData:{},//运行信息
timeRange:[],
isInit:true
}
},
methods:{
//6个方块数据
getRunningHeadData(){
getRunningHeadInfo(this.siteId).then(response => {
this.runningHeadData = response?.data || {}
})
},
// 更新时间范围 重置图表
updateDate(data){
this.timeRange=data
!this.isInit && this.updateChart()
this.isInit = false
},
updateChart(){
this.$refs.cnglqx.init(this.siteId,this.timeRange||[])
this.$refs.pocpjwd.init(this.siteId,this.timeRange||[])
this.$refs.dcpjsoc.init(this.siteId,this.timeRange||[])
this.$refs.dcpjwd.init(this.siteId,this.timeRange||[])
this.updateInterval(this.updateData)
},
updateData(){
this.getRunningHeadData()
this.updateChart()
},
init(){
this.$refs.dateRangeSelect.init(true)
this.$nextTick(()=>{
this.updateData()
})
}
}
}
</script>

View File

@ -0,0 +1,134 @@
<template>
<div v-loading="loading">
<el-card
v-for="(item,index) in list"
:key="index+'ylLise'"
class="sbjk-card-container running-card-container"
:class="{
'warning-card-container':item.emsCommunicationStatus && item.emsCommunicationStatus !== '0',
'running-card-container':item.emsCommunicationStatus === '0'
}"
shadow="always">
<div slot="header">
<span class="large-title">{{ item.deviceName }}</span>
<div class="info">
<div>
{{
$store.state.ems.communicationStatusOptions[
item.emsCommunicationStatus
]
}}
</div>
<div>数据更新时间{{ item.dataUpdateTime || '-' }}</div>
</div>
<div class="alarm">
<el-button type="primary" round size="small" style="margin-right:20px;" @click="pointDetail(item,'point')">
详细
</el-button>
<el-badge :hidden="!item.alarmNum" :value="item.alarmNum || 0" class="item">
<i
class="el-icon-message-solid alarm-icon"
@click="pointDetail(item,'alarmPoint')"
></i>
</el-badge>
</div>
</div>
<el-row class="device-info-row">
<el-col v-for="(tempDataItem,tempDataIndex) in tempData" :key="tempDataIndex+'hdTempData'" :span="12"
class="device-info-col">
<span class="pointer" @click="showChart(tempDataItem.title,item.deviceId)">
<span class="left">{{ tempDataItem.title }}</span> <span class="right">{{
item[tempDataItem.attr] || '-'
}}<span
v-html="tempDataItem.unit"></span></span>
</span>
</el-col>
</el-row>
</el-card>
<el-empty v-show="list.length<=0" :image-size="200"></el-empty>
<point-chart ref="pointChart" :site-id="siteId"/>
<point-table ref="pointTable"/>
</div>
</template>
<script>
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getXfDataList} from '@/api/ems/dzjk'
import intervalUpdate from "@/mixins/ems/intervalUpdate";
import pointChart from "./../PointChart.vue";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
export default {
name: 'DzjkSbjkXf',
mixins: [getQuerySiteId, intervalUpdate],
components: {pointChart, PointTable},
data() {
return {
loading: false,
list: [],
tempData: [
{title: '主电源备用电池状态', attr: 'dczt', unit: ''},
{title: '手自动状态延时状态', attr: 'yszt', unit: ''},
{title: '启动喷洒气体喷洒状态', attr: 'pszt', unit: ''},
{title: '压力开关状态电磁阀状态', attr: 'dcfzt', unit: ''},
]
}
},
methods: {
// 查看设备电位表格
pointDetail(row, dataType) {
const {deviceId} = row
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'XF'}, dataType)
},
showChart(pointName, deviceId) {
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory: 'XF', deviceId})
},
updateData() {
this.loading = true
getXfDataList(this.siteId).then(response => {
this.list = JSON.parse(JSON.stringify(response?.data || []));
}).finally(() => {
this.loading = false
})
},
init() {
this.updateData()
this.updateInterval(this.updateData)
}
},
mounted() {
}
}
</script>
<style scoped lang="scss">
.sbjk-card-container {
&:not(:last-child) {
margin-bottom: 25px;
}
.el-row {
background-color: #ffffff;
border: 1px solid #eeeeee;
font-size: 14px;
line-height: 16px;
color: #333333;
.el-col {
padding: 12px 0;
text-align: center;
position: relative;
}
.el-col {
border-bottom: 1px solid #eeeeee;
}
.el-col:not(:nth-child(3n)) {
border-right: 1px solid #eeeeee;
}
}
}
</style>

View File

@ -0,0 +1,103 @@
<template>
<div v-loading="loading">
<el-card
v-for="(item,index) in list"
:key="index+'ylLise'"
class="sbjk-card-container running-card-container"
shadow="always">
<div slot="header">
<span class="large-title">{{ item.deviceName }}</span>
<div class="info">
<div>数据更新时间{{ item.dataUpdateTime || '-' }}</div>
</div>
<div class="alarm">
<el-button type="primary" round size="small" style="margin-right:20px;" @click="pointDetail(item,'point')">
详细
</el-button>
<el-badge :hidden="!item.alarmNum" :value="item.alarmNum || 0" class="item">
<i
class="el-icon-message-solid alarm-icon"
@click="pointDetail(item,'alarmPoint')"
></i>
</el-badge>
</div>
</div>
<el-row class="device-info-row">
<el-col v-for="(tempDataItem,tempDataIndex) in tempData" :key="tempDataIndex+'ylTempData'" :span="8"
class="device-info-col">
<span class="pointer" @click="showChart(tempDataItem.title,item.deviceId)">
<span class="left">{{ tempDataItem.title }}</span> <span
class="right">{{ item[tempDataItem.attr] || '-' }}<span
v-html="tempDataItem.unit"></span></span>
</span>
</el-col>
</el-row>
</el-card>
<el-empty v-show="list.length<=0" :image-size="200"></el-empty>
<point-chart ref="pointChart" :site-id="siteId"/>
<point-table ref="pointTable"/>
</div>
</template>
<script>
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getCoolingDataList} from '@/api/ems/dzjk'
import intervalUpdate from "@/mixins/ems/intervalUpdate";
import pointChart from "./../PointChart.vue";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
export default {
name: 'DzjkSbjkYl',
mixins: [getQuerySiteId, intervalUpdate],
components: {pointChart, PointTable},
data() {
return {
loading: false,
list: [],
tempData: [
{title: '供水温度', attr: 'gsTemp', unit: '&#8451;'},
{title: '回水温度', attr: 'hsTemp', unit: '&#8451;'},
{title: '供水压力', attr: 'gsPressure', unit: 'bar'},
{title: '回水压力', attr: 'hsPressure', unit: 'bar'},
{title: '冷源水温度', attr: 'lysTemp', unit: '&#8451;'},
{title: 'VB01开度', attr: 'vb01Kd', unit: '%'},
{title: 'VB02开度', attr: 'vb02Kd', unit: '%'},
]
}
},
methods: {
// 查看设备电位表格
pointDetail(row, dataType) {
const {deviceId} = row
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'COOLING'}, dataType)
},
showChart(pointName, deviceId) {
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory: 'COOLING', deviceId})
},
updateData() {
this.loading = true
getCoolingDataList(this.siteId).then(response => {
this.list = JSON.parse(JSON.stringify(response?.data || []));
}).finally(() => {
this.loading = false
})
},
init() {
this.updateData()
this.updateInterval(this.updateData)
}
},
mounted() {
}
}
</script>
<style scoped lang="scss">
.sbjk-card-container {
&:not(:last-child) {
margin-bottom: 25px;
}
}
</style>

View File

@ -0,0 +1,216 @@
<template>
<div style="width:100%" v-loading="loading">
<!-- 搜索栏-->
<el-form :inline="true" class="select-container">
<el-form-item label="时间选择">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
:clearable="false"
:picker-options="pickerOptions"
:default-value="defaultDateRange"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearch" native-type="button">搜索</el-button>
</el-form-item>
<el-form-item>
<el-button @click="onReset" native-type="button">重置</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="exportTable" native-type="button">导出</el-button>
</el-form-item>
</el-form>
<!--表格-->
<el-table
class="common-table"
:data="tableData"
stripe
style="width: 100%;margin-top:25px;">
<!-- 汇总列-->
<el-table-column label="汇总">
<el-table-column
prop="dataTime"
label="日期"
width="120">
</el-table-column>
</el-table-column>
<!--充电量列-->
<el-table-column label="充电量" align="center">
<el-table-column
align="center"
prop="activePeakKwh"
label="尖">
</el-table-column>
<el-table-column
align="center"
prop="activeHighKwh"
label="峰">
</el-table-column>
<el-table-column
align="center"
prop="activeFlatKwh"
label="平">
</el-table-column>
<el-table-column
align="center"
prop="activeValleyKwh"
label="谷">
</el-table-column>
<el-table-column
align="center"
prop="activeTotalKwh"
label="总">
</el-table-column>
</el-table-column>
<!--充电量列-->
<el-table-column label="放电量" align="center">
<el-table-column
align="center"
prop="reActivePeakKwh"
label="尖">
</el-table-column>
<el-table-column
align="center"
prop="reActiveHighKwh"
label="峰">
</el-table-column>
<el-table-column
align="center"
prop="reActiveFlatKwh"
label="平">
</el-table-column>
<el-table-column
align="center"
prop="reActiveValleyKwh"
label="谷">
</el-table-column>
<el-table-column
align="center"
prop="reActiveTotalKwh"
label="总">
</el-table-column>
</el-table-column>
<!-- 效率-->
<el-table-column label="效率(%)" align="center">
<el-table-column
align="center"
prop="effect">
</el-table-column>
</el-table-column>
</el-table>
<el-pagination
v-show="tableData.length>0"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-size="pageSize"
:page-sizes="[10, 20, 30, 40]"
layout="total, sizes, prev, pager, next, jumper"
:total="totalSize"
style="margin-top:15px;text-align: center"
>
</el-pagination>
</div>
</template>
<script>
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getAmmeterData} from '@/api/ems/dzjk'
import {formatDate} from "@/filters/ems";
export default {
name: 'DzjkTjbbDbbb',
mixins: [getQuerySiteId],
data() {
return {
loading: false,
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
},
},
defaultDateRange: [],//默认展示的时间
dateRange: [],
tableData: [],
pageSize: 10,//分页栏当前每个数据总数
pageNum: 1,//分页栏当前页数
totalSize: 0,//table表格数据总数
}
},
methods: {
// 导出表格
exportTable() {
if (!this.dateRange?.length) return
const [startTime, endTime] = this.dateRange
this.download('ems/statsReport/exportAmmeterData', {
siteId: this.siteId,
startTime,
endTime,
}, `电表报表_${startTime}-${endTime}.xlsx`)
},
// 搜索
onSearch() {
this.pageNum = 1//每次搜索从1开始搜索
this.getData()
},
// 重置
onReset() {
this.dateRange = this.defaultDateRange
this.pageNum = 1//每次搜索从1开始搜索
this.getData()
},
// 分页
handleSizeChange(val) {
this.pageSize = val;
this.$nextTick(() => {
this.getData()
})
},
handleCurrentChange(val) {
this.pageNum = val
this.$nextTick(() => {
this.getData()
})
},
// 获取数据
getData() {
this.loading = true
const {siteId, pageNum, pageSize} = this
const [startTime = '', endTime = ''] = (this.dateRange || [])
getAmmeterData({siteId: siteId, startTime, endTime, pageSize, pageNum}).then(response => {
this.tableData = response?.rows || [];
this.totalSize = response?.total || 0
}).finally(() => {
this.loading = false
})
},
init() {
this.dateRange = []
this.tableData = []
this.totalSize = 0
this.pageSize = 10
this.pageNum = 1
let now = new Date(), lastDay = now.getTime(), firstDay = new Date(now.setDate(1)).getTime();
this.defaultDateRange = [formatDate(firstDay), formatDate(lastDay)];
this.dateRange = [formatDate(firstDay), formatDate(lastDay)];
this.getData()
},
},
}
</script>
<style scoped lang="scss">
::v-deep {
.common-table.el-table .el-table__header-wrapper th, .common-table.el-table .el-table__fixed-header-wrapper th {
border-bottom: 1px solid #dfe6ec;
}
}
</style>

View File

@ -0,0 +1,208 @@
<template>
<el-card
shadow="always"
class="common-card-container time-range-card"
style="margin-top: 20px"
>
<div slot="header" class="time-range-header">
<span class="card-title"> </span>
<date-range-select ref="dateRangeSelect" @updateDate="updateDate"/>
</div>
<div class="card-main" v-loading="loading">
<el-button-group class="ems-btns-group">
<el-button
v-for="(item, index) in btnList"
:key="index + 'dcdqxBtns'"
size="mini"
:class="{ activeBtn: activeBtn === item.id }"
@click="changeDataType(item.id)"
>{{ item.name }}
</el-button
>
</el-button-group>
<div id="dcdEchart" style="height: 310px"></div>
</div>
</el-card>
</template>
<script>
import * as echarts from "echarts";
import resize from "@/mixins/ems/resize";
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getStackData} from "@/api/ems/dzjk";
import {formatDate} from "@/filters/ems";
import DateRangeSelect from "@/components/Ems/DateRangeSelect/index.vue";
export default {
name: "DzjkTjbbDcdqx",
components: {DateRangeSelect},
mixins: [resize, getQuerySiteId],
data() {
return {
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
},
},
dateRange: [],
loading: false,
activeBtn: "1",
btnList: [
{name: "堆平均维度", id: "1", attr: ["temp"], source: ["有功功率"]},
{name: "堆电压", id: "2", attr: ["voltage"], source: ["堆电压"]},
{name: "堆电流", id: "3", attr: ["current"], source: ["堆电流"]},
{name: "堆soc", id: "4", attr: ["soc"], source: ["堆soc"]},
],
};
},
methods: {
changeDataType(id) {
if (id !== this.activeBtn) {
this.activeBtn = id;
this.getData();
}
},
// 更新时间范围 重置图表
updateDate(data) {
this.dateRange = data || [];
this.getData();
},
getData() {
const {siteId, activeBtn} = this;
const [start = "", end = ""] = this.dateRange || [];
//接口调用完成之后 设置图表、结束loading
this.loading = true;
getStackData({
siteId,
startTime: formatDate(start),
endTime: formatDate(end),
dataType: activeBtn,
})
.then((response) => {
this.setOption(response?.data || []);
})
.finally(() => {
this.loading = false;
});
},
compareDate(date1, date2) {
console.log("比较时间", date1, date2);
// 年2025-09/天2025-09-15/时2025-09-15/10:00
if (date1.indexOf(":") > -1 && date2.indexOf(":") > -1) {
return parseInt(date1) - parseInt(date2);
}
const [date1_Y = "", date1_M = "", date1_D = ""] = date1.split("-"); //根据空格区分[年月日,小时]
const [date2_Y = "", date2_M = "", date2_D = ""] = date2.split("-"); //根据空格区分[年月日,小时]
return (
(date1_Y === date2_Y && date1_M === date2_M && date1_D - date2_D) ||
(date1_Y === date2_Y && date1_M - date2_M) ||
date1_Y - date2_Y
);
},
setOption(data) {
const ele = this.btnList.find((item) => {
return item.id === this.activeBtn;
});
const sourceBase = JSON.parse(JSON.stringify(ele.source));
// sourceBase={name:'堆平均维度',id:'1',attr:['temp'],source:['有功功率']},
const source = [];
const sourceTop = ["日期"];
let map = {},
mapArr = [];
// 生成所有{日期:[],日期:[]}格式的对象和所有包含所有日期的[日期1,日期2...]
data.forEach((item) => {
item.dataList.forEach((inner) => {
// 日期格式
// 年2025-09/天2025-09-15/时2025-09-15/10:00
// 所有数据的日期格式一致
if (!map[inner.statisDate]) {
map[inner.statisDate] = [];
mapArr.push(inner.statisDate);
}
});
});
data.forEach((item, itemIndex) => {
const dataTimeList = item.dataList.map((i) => i.statisDate);
const noDataTime = mapArr.filter((i) => !dataTimeList.includes(i));
sourceBase.forEach((outer, outerIndex) => {
sourceTop.push(`${item.deviceId}-${outer}`);
noDataTime.forEach((i) => map[i].push(""));
item.dataList.forEach((inner, innerIndex) => {
map[inner.statisDate].push(inner[ele.attr[outerIndex]]);
});
});
});
mapArr = mapArr.sort((a, b) => this.compareDate(a, b));
mapArr.forEach((item) => {
source.push([item, ...map[item]]);
});
source.unshift(sourceTop);
this.chart.setOption(
{
grid: {
containLabel: true,
},
legend: {
left: "center",
top: "10",
},
tooltip: {
trigger: "axis",
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
},
},
textStyle: {
color: "#333333",
},
xAxis: {
type: "category",
},
yAxis: {
type: "value",
},
dataZoom: [
{
type: "inside",
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
dataset: {source},
series: source[0].slice(1).map((item) => {
return {
type: "line",
smooth: true,
areaStyle: {
opacity: 0.7,
},
};
}),
},
true
);
},
initChart() {
this.chart = echarts.init(document.querySelector("#dcdEchart"));
},
init() {
this.$nextTick(() => {
this.initChart();
this.$refs.dateRangeSelect.init(true);
});
},
},
beforeDestroy() {
if (!this.chart) {
return;
}
this.chart.dispose();
this.chart = null;
},
};
</script>

View File

@ -0,0 +1,203 @@
<template>
<div v-loading="loading">
<!-- 搜索栏-->
<el-form :inline="true" class="select-container">
<el-form-item label="电池堆">
<el-select v-model="search.stackId" placeholder="请选择" @change="changeStackId">
<el-option :label="item.deviceName" :value="item.id" v-for="(item,index) in stackOptions" :key="index+'dcdOptions'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="电池簇">
<el-select v-model="search.clusterId" :no-data-text="!search.stackId && stackOptions.length > 0 ? '请先选择电池堆':'无数据'" placeholder="请选择" :loading="clusterloading" loading-text="正在加载数据">
<el-option :label="item.deviceName" :value="item.id" v-for="(item,index) in clusterOptions" :key="index+'dccOptions'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="时间选择">
<el-date-picker
v-model="search.date"
type="date"
:picker-options="pickerOptions"
:default-value="defaultDate">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearch" native-type="button">搜索</el-button>
</el-form-item>
<el-form-item>
<el-button @click="onReset" native-type="button">重置</el-button>
</el-form-item>
</el-form>
<div style="margin:30px 0;">
<!--表格-->
<el-table
class="common-table"
:data="tableData"
stripe
max-height="500"
style="width: 100%;margin-top:25px;">
<el-table-column
prop="statisDate"
width="100"
label="时间">
</el-table-column>
<el-table-column
prop="maxTemp"
label="最高温(℃)">
</el-table-column>
<el-table-column
prop="maxTempId"
label="单体ID">
</el-table-column>
<el-table-column
prop="minTemp"
label="最低温(℃)">
</el-table-column>
<el-table-column
prop="minTempId"
label="单体ID">
</el-table-column>
<el-table-column
prop="maxVoltage"
label="最高压V">
</el-table-column>
<el-table-column
prop="maxVoltageId"
label="单体ID">
</el-table-column>
<el-table-column
prop="minVoltage"
label="最低压V">
</el-table-column>
<el-table-column
prop="minVoltageId"
label="单体ID">
</el-table-column>
</el-table>
<el-pagination
v-show="tableData.length>0"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-size="pageSize"
:page-sizes="[10, 20, 30, 40]"
layout="total, sizes, prev, pager, next, jumper"
:total="totalSize"
style="margin-top:15px;text-align: center"
>
</el-pagination>
</div>
</div>
</template>
<script>
import {getStackNameList, getClusterNameList, getClusterData} from '@/api/ems/dzjk'
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {formatDate} from "@/utils";
export default {
name:'DzjkTjbbDcwd',
mixins:[getQuerySiteId],
data() {
return {
pickerOptions:{
disabledDate(time) {
return time.getTime() > Date.now();
},
},
defaultDate:'',//默认展示的时间
loading:false,
clusterloading:false,
search:{stackId:'',clusterId:'',date:''},
stackOptions:[],//{id:'',deviceName:''}
clusterOptions:[],//{id:'',deviceName:''}
tableData:[],
pageSize:10,//分页栏当前每个数据总数
pageNum:1,//分页栏当前页数
totalSize:0,//table表格数据总数
}
},
methods:{
// 分页
handleSizeChange(val) {
this.pageSize = val;
this.$nextTick(()=>{
this.getTableData()
})
},
handleCurrentChange(val) {
this.pageNum = val
this.$nextTick(()=>{
this.getTableData()
})
},
// 搜索
onSearch(){
this.pageNum =1//每次搜索从1开始搜索
this.getTableData()
},
// 重置
onReset(){
this.search.date=''
this.pageNum = 1
this.getTableData()
},
changeStackId(val){
if(val){
this.search.clusterId=''
this.getClusterList()
}
},
//表格数据
async getTableData(){
const {stackId,clusterId,date} =this.search
const {siteId,pageNum,pageSize}=this
if(!stackId) return
this.loading=true;
await getClusterData({dateTime:formatDate(date),stackId,clusterId,siteId,pageNum,pageSize}).then(response => {
this.tableData=response?.rows || [];
this.totalSize = response?.total || 0
}).finally(()=>{
this.loading=false;
})
},
async getStackList(){
await getStackNameList(this.siteId).then(response => {
const data = JSON.parse(JSON.stringify(response?.data || []))
this.stackOptions = data
this.search.stackId = data.length > 0 ? data[0].id : ''
})
},
async getClusterList(){
this.clusterloading =true
await getClusterNameList({stackDeviceId: this.search.stackId, siteId: this.siteId}).then(response => {
const data = JSON.parse(JSON.stringify(response?.data || []))
this.clusterOptions = data
this.search.clusterId = data.length > 0 ? data[0].id : ''
}).finally(() => {this.clusterloading =false})
},
async init(){
this.loading = true
this.search={stackId:'',clusterId:'',date:''}
this.stackOptions=[]//{id:'',deviceName:''}
this.clusterOptions=[]//{id:'',deviceName:''}
this.tableData=[]
// 只有页面初次加载或切换站点的时候调用电池堆列表,其他情况不需要
await this.getStackList()
if(this.search.stackId){
await this.getClusterList()
await this.onReset()
}else{
this.loading = false
}
}
},
mounted(){
this.defaultDate = new Date()
}
}
</script>

View File

@ -0,0 +1,168 @@
<template>
<el-card
shadow="always"
class="common-card-container time-range-card"
style="margin-top: 20px"
>
<div slot="header" class="time-range-header">
<span class="card-title">功率曲线</span>
<date-range-select
ref="dateRangeSelect"
@updateDate="updateDate"
/>
</div>
<div class="card-main" v-loading="loading">
<div id="glqxEchart" style="height: 310px"></div>
</div>
</el-card>
</template>
<script>
import * as echarts from "echarts";
import resize from "@/mixins/ems/resize";
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getPowerData} from "@/api/ems/dzjk";
import {formatDate} from "@/filters/ems";
import DateRangeSelect from "@/components/Ems/DateRangeSelect/index.vue";
export default {
name: "DzjkTjbbGlqx",
components: {DateRangeSelect},
mixins: [resize, getQuerySiteId],
data() {
return {
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
},
},
dateRange: [],
loading: false,
};
},
methods: {
// 更新时间范围 重置图表
updateDate(data) {
this.dateRange = data || [];
this.getData();
},
getData() {
const {siteId} = this;
let [start = "", end = ""] = this.dateRange || [];
//接口调用完成之后 设置图表、结束loading
this.loading = true;
getPowerData({
siteId,
startDate: formatDate(start),
endDate: formatDate(end),
})
.then((response) => {
this.setOption(response?.data || []);
})
.finally(() => {
this.loading = false;
});
},
setOption(data) {
const source = [["日期", "电网功率", "负载功率", "储能功率", "光伏功率"]];
data.forEach((item) => {
source.push([
item.statisDate,
item.gridPower,
item.loadPower,
item.storagePower,
item.pvPower,
]);
});
this.chart.setOption(
{
grid: {
containLabel: true,
},
legend: {
left: "center",
top: "10",
},
tooltip: {
trigger: "axis",
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
},
},
textStyle: {
color: "#333333",
},
xAxis: {
type: "category",
},
yAxis: {
type: "value",
},
dataset: {source},
dataZoom: [
{
type: "inside",
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
series: [
{
type: "line",
smooth: true,
areaStyle: {
opacity: 0.7,
},
},
{
type: "line",
smooth: true,
areaStyle: {
opacity: 0.7,
},
},
{
type: "line",
smooth: true,
areaStyle: {
opacity: 0.7,
},
},
{
type: "line",
smooth: true,
areaStyle: {
opacity: 0.7,
},
},
],
},
true
);
},
initChart() {
if (this.chart) return;
this.chart = echarts.init(document.querySelector("#glqxEchart"));
},
init() {
this.$nextTick(() => {
this.initChart();
this.$refs.dateRangeSelect.init();
});
},
},
beforeDestroy() {
if (!this.chart) {
return;
}
this.chart.dispose();
this.chart = null;
},
};
</script>

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