Compare commits
148 Commits
763338cc2a
...
main-cloud
| Author | SHA1 | Date | |
|---|---|---|---|
| fd860597de | |||
| f88e9bedc2 | |||
| 9272a0162a | |||
| c7c1b416ee | |||
| 41a3ab45b3 | |||
| 50c72d6989 | |||
| 7fdb6e2ad3 | |||
| 8b8608c1eb | |||
| 29ab53056a | |||
| 5250db915f | |||
| 814103c881 | |||
| 55b7fba021 | |||
| da4ecc4792 | |||
| 498dc117f2 | |||
| 4d29de93a1 | |||
| 445e9dfc9f | |||
| ac1d1ae154 | |||
| 825243e741 | |||
| 0389ed85f3 | |||
| aef94f406a | |||
| 95d69fb7b1 | |||
| 684002ffc8 | |||
| 098dfa05f8 | |||
| d6c9310e50 | |||
| 142de3102b | |||
| 9b5806a2c0 | |||
| 2b6697fa5a | |||
| 3bb859b693 | |||
| 5b3701afd0 | |||
| f531075853 | |||
| 8cb6fbee3e | |||
| f454b02c99 | |||
| bfe72cf2c3 | |||
| fb90d81bb3 | |||
| 823c0949d0 | |||
| c1c411e48a | |||
| 43153a791d | |||
| 11111d035b | |||
| ab9bb1e85d | |||
| 4079c40e5d | |||
| 4ebd5f0988 | |||
| a0095b4054 | |||
| 84bc29410a | |||
| f3fda19c64 | |||
| 9a8247f833 | |||
| c4c79aaa64 | |||
| 0966813c25 | |||
| dd4fa36597 | |||
| 9b14d96e24 | |||
| ac54ce999e | |||
| f49efb5edd | |||
| 97f9b3ff0e | |||
| 674a0c6c33 | |||
| 7f3d02b8fb | |||
| cd8871d45a | |||
| 11d44de513 | |||
| 78eb81549b | |||
| 65f0e92296 | |||
| 9a585e97db | |||
| 7fb6d1aa47 | |||
| 1378947a9e | |||
| 0b2e7d9b86 | |||
| 87ffaca398 | |||
| 06ef1c3d1d | |||
| bc5f2b4470 | |||
| f24241533b | |||
| 87cd27b798 | |||
| 3fd6f812b9 | |||
| 1772c7e0e5 | |||
| eb5abec9ff | |||
| 834bd04d45 | |||
| da0d3d12ef | |||
| 1280b7196c | |||
| 567ddf85a5 | |||
| 3766b2c0fa | |||
| 695fcd0f8f | |||
| 11269efa2d | |||
| 4b8380a6a9 | |||
| 5c150b0d26 | |||
| 96e4f7874f | |||
| bf833fc763 | |||
| f4e6821484 | |||
| fb0eda4565 | |||
| fef1704cbd | |||
| 679f8f2a07 | |||
| 376b50c3b5 | |||
| 7d51c37cad | |||
| ad95debdac | |||
| cceca2af4f | |||
| 887af476ba | |||
| 28a6e525d8 | |||
| 66e2a3daab | |||
| d3558f5ee4 | |||
| 4005b921e6 | |||
| 7d2d49b7e2 | |||
| cc4d6ab347 | |||
| 7497c1d1b9 | |||
| a1fdc958db | |||
| 9dcf2a6c12 | |||
| fb4f9d4abc | |||
| 651a78bceb | |||
| a4ae820e98 | |||
| 466d3a14b6 | |||
| b421d11bc2 | |||
| 408ba489ca | |||
| b5956822ab | |||
| 6600a5db7a | |||
| 506cf28c96 | |||
| 5f7a1c0f4b | |||
| 1f49a3af20 | |||
| a0ea6041cc | |||
| 0e7b54291f | |||
| cbc7a341c9 | |||
| 85cfd204ae | |||
| 2cb4dbcd84 | |||
| 6ad3ef9d7c | |||
| 36b1e19120 | |||
| 902e9a0a03 | |||
| 7f560cd140 | |||
| e3224d37a1 | |||
| 52b6083baa | |||
| 98e2c4c3be | |||
| 4c31eeb837 | |||
| b61a202267 | |||
| c51baa166b | |||
| f005448705 | |||
| ba4df32176 | |||
| ae1c2a5d6c | |||
| f16b92582c | |||
| cc3164a423 | |||
| 2742f874ce | |||
| e7ea8c8f44 | |||
| c502688737 | |||
| 243724c9a6 | |||
| 8808b25682 | |||
| f5fc1d64ec | |||
| a0790b100a | |||
| c3d64e4d04 | |||
| 89cc734595 | |||
| 587094e85e | |||
| 0299d32a52 | |||
| 15196f8d71 | |||
| 8b20c89cb2 | |||
| ba8b86d523 | |||
| 2cd60ea105 | |||
| 10033348d6 | |||
| 9a9198b168 | |||
| 9ad720823a |
@ -1,14 +1,15 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 上动新能源-EMS管理系统
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
# EMS管理系统/开发环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
|
||||
# 路由懒加载
|
||||
VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
||||
|
||||
# 开发环境配置
|
||||
NODE_ENV = 'development'
|
||||
|
||||
# EMS管理系统/开发环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
|
||||
# EMS管理系统/开发环境 图片拼接地址
|
||||
VUE_APP_IMG_URL = '/dev-api'
|
||||
|
||||
|
||||
@ -2,10 +2,11 @@
|
||||
VUE_APP_TITLE = 上动新能源-EMS管理系统
|
||||
|
||||
# 生产环境配置
|
||||
ENV = 'production'
|
||||
NODE_ENV = 'production'
|
||||
|
||||
# EMS管理系统/生产环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
VUE_APP_BASE_API= 'http://1.15.120.242:8089'
|
||||
|
||||
# EMS管理系统/生产环境 图片拼接地址
|
||||
VUE_APP_IMG_URL = 'http://1.15.120.242:8089'
|
||||
|
||||
# EMS管理系统/生产环境 图片拼接地址 todo baseUrl有变更时 请更新
|
||||
VUE_APP_IMG_URL = 'http://110.40.171.179:8089'
|
||||
|
||||
12
.env.staging
@ -1,12 +1,12 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 上动新能源-EMS管理系统
|
||||
|
||||
BABEL_ENV = production
|
||||
|
||||
NODE_ENV = production
|
||||
|
||||
# 测试环境配置
|
||||
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'
|
||||
|
||||
|
||||
@ -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
|
After Width: | Height: | Size: 31 KiB |
42
src/api/ems/powerTariff.js
Normal 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',
|
||||
})
|
||||
}
|
||||
|
||||
40
src/api/ems/search.js
Normal file
@ -0,0 +1,40 @@
|
||||
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',
|
||||
})
|
||||
}
|
||||
|
||||
// 综合查询-按站点获取配置设备列表
|
||||
export function getGeneralQueryDeviceList(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/getDeviceList?siteId=${siteId}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
@ -1,64 +1,498 @@
|
||||
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 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 syncSiteWeatherByDateRange({siteId, startTime, endTime}) {
|
||||
return request({
|
||||
url: `/ems/statsReport/syncWeatherByDateRange`,
|
||||
method: 'post',
|
||||
params: {siteId, startTime, endTime}
|
||||
})
|
||||
}
|
||||
|
||||
// 新增站点
|
||||
export function addSite(data) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/addSite`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑站点
|
||||
export function updateSite(data) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/updateSite`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 设备列表
|
||||
export function getDeviceInfoList({siteId,pageSize,pageNum}) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/getDeviceInfoList?siteId=${siteId}&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'
|
||||
})
|
||||
return request({
|
||||
url: `/ems/siteConfig/getDeviceDetailInfo?id=${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取所有设备类别
|
||||
export function getDeviceCategory() {
|
||||
return request({
|
||||
url: `/ems/siteConfig/getDeviceCategory`,
|
||||
method: 'get'
|
||||
})
|
||||
return request({
|
||||
url: `/ems/siteConfig/getDeviceCategory`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增设备
|
||||
export function addDevice(data) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/addDevice`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
return request({
|
||||
url: `/ems/siteConfig/addDevice`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑设备
|
||||
export function updateDevice(data) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/updateDevice`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
return request({
|
||||
url: `/ems/siteConfig/updateDevice`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除设备
|
||||
export function deleteService(id) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/deleteService/`+id,
|
||||
method: 'delete',
|
||||
})
|
||||
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',
|
||||
})
|
||||
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 getSingleMonitorProjectPointMapping(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/getSingleMonitorProjectPointMapping?siteId=${siteId}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// 保存单站监控项目点位映射
|
||||
export function saveSingleMonitorProjectPointMapping(data) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/saveSingleMonitorProjectPointMapping`,
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取单站监控工作状态枚举映射(PCS)
|
||||
export function getSingleMonitorWorkStatusEnumMappings(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/getSingleMonitorWorkStatusEnumMappings?siteId=${siteId}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// 保存单站监控工作状态枚举映射(PCS)
|
||||
export function saveSingleMonitorWorkStatusEnumMappings(data) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/saveSingleMonitorWorkStatusEnumMappings`,
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//新增设备保护
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
// 按站点导入模板点位配置
|
||||
export function importPointTemplateBySite(data) {
|
||||
return request({
|
||||
url: `/ems/pointConfig/importTemplateBySite`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// CSV导入点位配置
|
||||
export function importPointConfigCsv(data) {
|
||||
return request({
|
||||
url: `/ems/pointConfig/importCsv`,
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点位配置列表
|
||||
export function getPointMatchList(params) {
|
||||
return request({
|
||||
url: `/ems/pointConfig/list`,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 点位配置详情
|
||||
export function getPointMatchDetail(id) {
|
||||
return request({
|
||||
url: `/ems/pointConfig/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// 新增点位配置
|
||||
export function addPointMatch(data) {
|
||||
return request({
|
||||
url: `/ems/pointConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑点位配置
|
||||
export function updatePointMatch(data) {
|
||||
return request({
|
||||
url: `/ems/pointConfig`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除点位配置
|
||||
export function deletePointMatch(ids) {
|
||||
return request({
|
||||
url: `/ems/pointConfig/${ids}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
// 点位配置-批量获取最新值(新接口)
|
||||
export function getPointConfigLatestValues(data) {
|
||||
return request({
|
||||
url: `/ems/pointConfig/latestValues`,
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点位配置-曲线数据(新接口)
|
||||
export function getPointConfigCurve(data) {
|
||||
return request({
|
||||
url: `/ems/pointConfig/curve`,
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点位配置-生成最近7天数据
|
||||
export function generatePointConfigRecent7Days(data) {
|
||||
return request({
|
||||
url: `/ems/pointConfig/generateRecent7Days`,
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 计算点配置列表
|
||||
export function getPointCalcConfigList(params) {
|
||||
return request({
|
||||
url: `/ems/pointCalcConfig/list`,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 计算点配置详情
|
||||
export function getPointCalcConfigDetail(id) {
|
||||
return request({
|
||||
url: `/ems/pointCalcConfig/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// 新增计算点配置
|
||||
export function addPointCalcConfig(data) {
|
||||
return request({
|
||||
url: `/ems/pointCalcConfig`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑计算点配置
|
||||
export function updatePointCalcConfig(data) {
|
||||
return request({
|
||||
url: `/ems/pointCalcConfig`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除计算点配置
|
||||
export function deletePointCalcConfig(ids) {
|
||||
return request({
|
||||
url: `/ems/pointCalcConfig/${ids}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
// 数据修正列表(ems_daily_energy_data)
|
||||
export function getDailyEnergyDataList(params) {
|
||||
return request({
|
||||
url: `/ems/dailyEnergyData/list`,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 数据修正详情
|
||||
export function getDailyEnergyDataDetail(id) {
|
||||
return request({
|
||||
url: `/ems/dailyEnergyData/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// 新增数据修正
|
||||
export function addDailyEnergyData(data) {
|
||||
return request({
|
||||
url: `/ems/dailyEnergyData`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑数据修正
|
||||
export function updateDailyEnergyData(data) {
|
||||
return request({
|
||||
url: `/ems/dailyEnergyData`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除数据修正
|
||||
export function deleteDailyEnergyData(ids) {
|
||||
return request({
|
||||
url: `/ems/dailyEnergyData/${ids}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
// 充放电修正列表(ems_daily_charge_data)
|
||||
export function getDailyChargeDataList(params) {
|
||||
return request({
|
||||
url: `/ems/dailyChargeData/list`,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 充放电修正详情
|
||||
export function getDailyChargeDataDetail(id) {
|
||||
return request({
|
||||
url: `/ems/dailyChargeData/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// 新增充放电修正
|
||||
export function addDailyChargeData(data) {
|
||||
return request({
|
||||
url: `/ems/dailyChargeData`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑充放电修正
|
||||
export function updateDailyChargeData(data) {
|
||||
return request({
|
||||
url: `/ems/dailyChargeData`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除充放电修正
|
||||
export function deleteDailyChargeData(ids) {
|
||||
return request({
|
||||
url: `/ems/dailyChargeData/${ids}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
//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',
|
||||
})
|
||||
}
|
||||
|
||||
BIN
src/assets/images/ems/bigData-1.png
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
src/assets/images/ems/bigData-2.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
src/assets/images/ems/bigData-3.png
Normal file
|
After Width: | Height: | Size: 11 MiB |
BIN
src/assets/images/ems/loginBg/1.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/assets/images/ems/loginBg/2.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
src/assets/images/ems/loginBg/3.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
src/assets/images/ems/loginBg/4.png
Normal file
|
After Width: | Height: | Size: 1021 KiB |
BIN
src/assets/images/ems/logo-icon.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 134 KiB |
BIN
src/assets/images/ems/logo-small.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 509 KiB |
@ -4,101 +4,249 @@
|
||||
|
||||
//右侧内容区域
|
||||
//父元素
|
||||
.ems-dashboard-editor-container{
|
||||
.ems-dashboard-editor-container {
|
||||
background-color: #F1F5FC;
|
||||
padding: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
//除去顶部信息(如搜索栏、站点基本信息等)外的 白色背景内容区域
|
||||
.ems-content-container{
|
||||
.ems-content-container {
|
||||
background-color: #ffffff;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
//需要设置内padding的白色背景区域
|
||||
.ems-content-container-padding{
|
||||
.ems-content-container-padding {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
//card通用样式 标题、body
|
||||
.common-card-container{
|
||||
.el-card__header{
|
||||
padding:14px;
|
||||
border-bottom: none;
|
||||
.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;
|
||||
background: #F1F5FB ;
|
||||
.card-title{
|
||||
font-weight: 500;
|
||||
color:#333333;
|
||||
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;
|
||||
}
|
||||
.large-title{
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
//红色背景颜色标题
|
||||
&.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;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//描述样式 PCS、BMS总览、BMS电池簇页面公共样式
|
||||
.descriptions-main{
|
||||
padding:24px;
|
||||
.descriptions-main {
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
&.descriptions-main-bg-color{
|
||||
background-color:#f1f5fc ;
|
||||
.el-descriptions__body{
|
||||
background-color:#f1f5fc ;
|
||||
|
||||
&.descriptions-main-bg-color {
|
||||
background-color: #f1f5fc;
|
||||
|
||||
.el-descriptions__body {
|
||||
background-color: #f1f5fc;
|
||||
}
|
||||
}
|
||||
.el-descriptions-item__cell[colspan='1']{
|
||||
width:25%
|
||||
|
||||
.el-descriptions-item__cell[colspan='1'] {
|
||||
width: 25%
|
||||
}
|
||||
.el-descriptions__body .el-descriptions__table{
|
||||
.descriptions-direction{
|
||||
|
||||
.el-descriptions__body .el-descriptions__table {
|
||||
.descriptions-direction {
|
||||
line-height: 19px;
|
||||
color: #666666;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.descriptions-label{
|
||||
|
||||
.descriptions-label {
|
||||
line-height: 14px;
|
||||
color: #666666;
|
||||
font-size: 12px;
|
||||
}
|
||||
.danger{
|
||||
color:#FC6B69;
|
||||
|
||||
.danger {
|
||||
color: #FC6B69;
|
||||
}
|
||||
.save{
|
||||
color:#09ADA3;
|
||||
|
||||
.save {
|
||||
color: #09ADA3;
|
||||
}
|
||||
.keep{
|
||||
color:#3C81FF;
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//公共表格样式
|
||||
.common-table.el-table{
|
||||
color:#333333;
|
||||
.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;
|
||||
|
||||
.warning-status {
|
||||
color: #FC6B69;
|
||||
|
||||
&.circle::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
@ -112,45 +260,61 @@
|
||||
}
|
||||
|
||||
//二、三级菜单栏样式
|
||||
.ems-second-menu{
|
||||
width:fit-content;
|
||||
.el-menu-item{
|
||||
.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{
|
||||
border-right: none;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
height: fit-content;
|
||||
.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;
|
||||
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: 140px;
|
||||
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: 0;
|
||||
left: 0;
|
||||
|
||||
.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{
|
||||
.ems-btns-group {
|
||||
.activeBtn {
|
||||
background-color: #0366c1;
|
||||
border-color: #0366c1;
|
||||
color: #ffffff;
|
||||
@ -159,11 +323,12 @@
|
||||
}
|
||||
|
||||
//搜索栏样式
|
||||
.select-container.el-form--inline .el-form-item{
|
||||
.select-container.el-form--inline .el-form-item {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
//红色背景颜色按钮
|
||||
.alarm-btn,.alarm-btn:hover, .alarm-btn:focus{
|
||||
.alarm-btn, .alarm-btn:hover, .alarm-btn:focus {
|
||||
background-color: #FC6B69;
|
||||
border-color: #FC6B69;
|
||||
}
|
||||
|
||||
137
src/components/BigDataPopup/index.vue
Normal 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>
|
||||
@ -1,36 +1,68 @@
|
||||
<template>
|
||||
<div class="time-range">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
value-format="yyyy-MM-dd"
|
||||
:clearable="false"
|
||||
:picker-options="pickerOptions"
|
||||
end-placeholder="结束时间">
|
||||
</el-date-picker>
|
||||
<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>
|
||||
</div>
|
||||
<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 {
|
||||
computed:{
|
||||
disabledNextBtn(){
|
||||
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:{
|
||||
loading: false,
|
||||
dateRange: [],
|
||||
defaultDateRange: [],
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
@ -38,61 +70,81 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(){
|
||||
const now = new Date(),formatNow = formatDate(now);
|
||||
const weekAgo = formatDate(new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000))
|
||||
this.dateRange = [weekAgo, formatNow];
|
||||
this.defaultDateRange=[weekAgo, formatNow];
|
||||
this.$emit('updateDate',this.dateRange)
|
||||
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
|
||||
showBtnLoading(status) {
|
||||
this.loading = status
|
||||
},
|
||||
resetDate(){
|
||||
resetDate() {
|
||||
this.dateRange = this.defaultDateRange
|
||||
},
|
||||
//重置 设置时间范围为初始化时间段
|
||||
reset(){
|
||||
this.resetDate()
|
||||
this.$emit('updateDate',this.dateRange)
|
||||
reset() {
|
||||
this.resetDate()
|
||||
this.$emit('reset')
|
||||
this.$emit('updateDate', this.dateRange)
|
||||
},
|
||||
// 搜索
|
||||
search(){
|
||||
this.$emit('updateDate',this.dateRange)
|
||||
search() {
|
||||
this.$emit('updateDate', this.dateRange)
|
||||
},
|
||||
timeLine(type){
|
||||
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 毫秒数
|
||||
let baseTime = type === 'before' ? new Date(this.dateRange[0]).getTime() - ( 24 * 60 * 60 * 1000) :new Date(this.dateRange[1]).getTime() + ( 24 * 60 * 60 * 1000) ,
|
||||
maxTime = new Date(this.defaultDateRange[1]).getTime()
|
||||
//updateTime 毫秒数
|
||||
let updateTime = type === 'before' ? baseTime - 7 * 24 * 60 * 60 * 1000 : baseTime + 7 * 24 * 60 * 60 * 1000
|
||||
if(type === 'next' && updateTime >= maxTime) updateTime = maxTime
|
||||
const start = formatDate(type === 'before' ? updateTime : baseTime)
|
||||
const end = formatDate(type === 'before' ? baseTime : updateTime)
|
||||
this.dateRange = [start,end]
|
||||
this.$emit('updateDate',this.dateRange)
|
||||
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: 24px;
|
||||
}
|
||||
.el-button--mini{
|
||||
padding:3px 10px;
|
||||
}
|
||||
.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>
|
||||
|
||||
@ -2,7 +2,10 @@
|
||||
<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>
|
||||
<div class="single-square-box-value">
|
||||
<i v-if="data.loading" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ data.value | formatNumber }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@ -14,18 +17,29 @@
|
||||
color:#666666;
|
||||
text-align: left;
|
||||
.single-square-box-title{
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
padding-bottom: 12px;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.single-square-box-value{
|
||||
font-size: 26px;
|
||||
line-height: 26px;
|
||||
font-size: 18px;
|
||||
line-height: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
::v-deep .el-card__body{
|
||||
padding: 12px 10px;
|
||||
.point-loading-icon{
|
||||
color: #409eff;
|
||||
display: inline-block;
|
||||
transform-origin: center;
|
||||
animation: pointLoadingSpinPulse 1.1s linear infinite;
|
||||
}
|
||||
::v-deep .el-card__body{
|
||||
padding: 8px 7px;
|
||||
}
|
||||
}
|
||||
@keyframes pointLoadingSpinPulse {
|
||||
0% { opacity: 0.45; transform: rotate(0deg) scale(0.9); }
|
||||
50% { opacity: 1; transform: rotate(180deg) scale(1.08); }
|
||||
100% { opacity: 0.45; transform: rotate(360deg) scale(0.9); }
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
@ -4,7 +4,10 @@
|
||||
<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>
|
||||
<div class="num">
|
||||
<i v-if="item.loading" class="el-icon-loading"></i>
|
||||
<span v-else>{{item.num | formatNumber}}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-row>
|
||||
</template>
|
||||
@ -18,30 +21,35 @@ export default {
|
||||
title:'站点总数(座)',
|
||||
num:'',
|
||||
color:'#FFBD00',
|
||||
attr:'siteNum'
|
||||
attr:'siteNum',
|
||||
loading: true
|
||||
},{
|
||||
title:'装机功率(MW)',
|
||||
num:'',
|
||||
color:'#3C81FF',
|
||||
attr:'installPower'
|
||||
attr:'installPower',
|
||||
loading: true
|
||||
|
||||
},{
|
||||
title:'装机容量(MW)',
|
||||
num:'',
|
||||
color:'#5AC7C0',
|
||||
attr:'installCapacity'
|
||||
attr:'installCapacity',
|
||||
loading: true
|
||||
|
||||
},{
|
||||
title:'总充电量(MWh)',
|
||||
title:'总充电量(KWh)',
|
||||
num:'',
|
||||
color:'#A696FF',
|
||||
attr:'totalChargedCap'
|
||||
attr:'totalChargedCap',
|
||||
loading: true
|
||||
|
||||
},{
|
||||
title:'总放电量(MWh)',
|
||||
title:'总放电量(KWh)',
|
||||
num:'',
|
||||
color:'#A696FF',
|
||||
attr:'totalDischargedCap'
|
||||
attr:'totalDischargedCap',
|
||||
loading: true
|
||||
|
||||
}]
|
||||
}
|
||||
@ -50,6 +58,7 @@ export default {
|
||||
setData(res = {}){
|
||||
this.data.forEach((item)=>{
|
||||
item.num =res[item.attr]
|
||||
item.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@ -2,20 +2,42 @@
|
||||
<template>
|
||||
<div class="zd-select-container">
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="站点选择">
|
||||
<el-select v-model="id" placeholder="请选择换电站名称" :loading="loading" loading-text="正在加载数据">
|
||||
<el-form-item :label="showLabel ? '站点选择' : ''" :class="{'no-label': !showLabel}">
|
||||
<el-select
|
||||
v-model="id"
|
||||
:size="size"
|
||||
:placeholder="placeholder"
|
||||
:loading="loading"
|
||||
loading-text="正在加载数据"
|
||||
:style="{width: selectWidth}"
|
||||
@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-item>-->
|
||||
<!-- <el-button type="primary" :loading="searchLoading" @click="onSubmit">搜索</el-button>-->
|
||||
<!-- </el-form-item>-->
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.zd-select-container {
|
||||
.el-form {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.no-label ::v-deep .el-form-item__label {
|
||||
display: none;
|
||||
}
|
||||
.no-label ::v-deep .el-form-item__content {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import {getAllSites} from '@/api/ems/zddt'
|
||||
@ -31,6 +53,26 @@ import {mapGetters} from "vuex"
|
||||
type:String,
|
||||
default:'',
|
||||
required:false
|
||||
},
|
||||
showLabel: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: false
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择换电站名称',
|
||||
required: false
|
||||
},
|
||||
selectWidth: {
|
||||
type: String,
|
||||
default: '220px',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@ -44,10 +86,23 @@ import {mapGetters} from "vuex"
|
||||
computed:{
|
||||
...mapGetters(["zdList"]),
|
||||
},
|
||||
watch: {
|
||||
defaultSiteId(newVal) {
|
||||
if (!newVal || !this.siteList || this.siteList.length === 0) {
|
||||
return
|
||||
}
|
||||
if (this.siteList.find(item => item.siteId === newVal) && this.id !== newVal) {
|
||||
this.id = newVal
|
||||
}
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
onSubmit(){
|
||||
this.$emit('submitSite',this.id)
|
||||
},
|
||||
emitSitesLoaded() {
|
||||
this.$emit('sitesLoaded', this.siteList || [])
|
||||
},
|
||||
setDefaultSite(){
|
||||
const defaultSite = this.defaultSiteId
|
||||
if(defaultSite && this.siteList.find(item=>item.siteId === defaultSite)){
|
||||
@ -60,7 +115,7 @@ import {mapGetters} from "vuex"
|
||||
getList(){
|
||||
return getAllSites().then(response => {
|
||||
this.siteList = response.data || []
|
||||
console.log("获取站点列表返回数据",response,this.siteList)
|
||||
this.emitSitesLoaded()
|
||||
this.setDefaultSite()
|
||||
}).finally(() => {this.loading=false;this.searchLoading=false})
|
||||
}
|
||||
@ -71,15 +126,14 @@ import {mapGetters} from "vuex"
|
||||
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)
|
||||
this.getList().then(()=>{
|
||||
this.$store.commit('SET_ZD_LIST', this.siteList)
|
||||
})
|
||||
}else{
|
||||
this.siteList = this.zdList
|
||||
this.emitSitesLoaded()
|
||||
this.loading=false
|
||||
this.searchLoading=false
|
||||
console.log("从store中获取站点列表数据",this.zdList,this.siteList)
|
||||
this.setDefaultSite()
|
||||
}
|
||||
}else{
|
||||
|
||||
@ -17,7 +17,7 @@ export const formatDate = (val,toSeconds = false,onlyTime=false) => {
|
||||
if(!toSeconds){
|
||||
return front
|
||||
} else{
|
||||
return front +''+back
|
||||
return front +' '+back
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,22 +1,37 @@
|
||||
<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>
|
||||
<div v-if="device!=='mobile'" class="site-select-wrap">
|
||||
<zd-select
|
||||
:get-list-by-store="true"
|
||||
:show-label="false"
|
||||
size="mini"
|
||||
select-width="220px"
|
||||
:default-site-id="$route.query.siteId"
|
||||
@submitSite="onSiteChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover">
|
||||
<div class="avatar-wrapper">
|
||||
@ -32,22 +47,24 @@
|
||||
</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'
|
||||
import ZdSelect from '@/components/Ems/ZdSelect/index.vue'
|
||||
|
||||
export default {
|
||||
emits: ['setLayout'],
|
||||
@ -57,7 +74,9 @@ export default {
|
||||
Hamburger,
|
||||
Screenfull,
|
||||
SizeSelect,
|
||||
Search
|
||||
Search,
|
||||
BigDataPopup,
|
||||
ZdSelect
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
@ -78,6 +97,28 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSiteChange(id) {
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
const currentSite = (this.$store.getters.zdList || []).find(item => item.siteId === id)
|
||||
const siteName = currentSite && currentSite.siteName ? currentSite.siteName : ''
|
||||
localStorage.setItem('global_site_id', id)
|
||||
if (id !== this.$route.query.siteId || siteName !== (this.$route.query.siteName || '')) {
|
||||
this.$router.push({
|
||||
path: this.$route.path,
|
||||
query: {
|
||||
...this.$route.query,
|
||||
siteId: id,
|
||||
siteName
|
||||
}
|
||||
})
|
||||
}
|
||||
this.$store.dispatch('getSiteAlarmNum', id)
|
||||
},
|
||||
showBigDataImg() {
|
||||
this.$refs.bigDataPopup.show = true
|
||||
},
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
},
|
||||
@ -93,7 +134,8 @@ export default {
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
location.href = '/index'
|
||||
})
|
||||
}).catch(() => {})
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,7 +147,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 +155,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 +181,33 @@ export default {
|
||||
height: 100%;
|
||||
line-height: 50px;
|
||||
|
||||
.site-select-wrap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 10px 0 14px;
|
||||
vertical-align: top;
|
||||
|
||||
::v-deep .el-form-item__content {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.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 +237,7 @@ export default {
|
||||
.avatar-wrapper {
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
padding-right: 10px;
|
||||
|
||||
.user-avatar {
|
||||
cursor: pointer;
|
||||
@ -176,7 +246,7 @@ export default {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-nickname{
|
||||
.user-nickname {
|
||||
position: relative;
|
||||
bottom: 10px;
|
||||
font-size: 14px;
|
||||
@ -192,6 +262,7 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,48 +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 :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 :src="logo" class="sidebar-logo" />
|
||||
<!-- <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>-->
|
||||
<!-- <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 logo from '@/assets/images/ems/logo.png'
|
||||
import logoLarge from '@/assets/images/ems/logo-large.png'
|
||||
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,
|
||||
logoLarge:logoLarge
|
||||
}
|
||||
}
|
||||
}
|
||||
logo: logo,
|
||||
logoSmall: logoSmall,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -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>
|
||||
24
src/mixins/ems/intervalUpdate.js
Normal 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
|
||||
@ -10,11 +10,16 @@ import { isRelogin } from '@/utils/request'
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
const whiteList = ['/login', '/register']
|
||||
const GLOBAL_SITE_STORAGE_KEY = 'global_site_id'
|
||||
|
||||
const isWhiteList = (path) => {
|
||||
return whiteList.some(pattern => isPathMatch(pattern, path))
|
||||
}
|
||||
|
||||
const shouldAppendSiteId = (path) => {
|
||||
return !['/login', '/register', '/404', '/401'].includes(path) && !path.startsWith('/redirect')
|
||||
}
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start()
|
||||
if (getToken()) {
|
||||
@ -26,6 +31,24 @@ router.beforeEach((to, from, next) => {
|
||||
} else if (isWhiteList(to.path)) {
|
||||
next()
|
||||
} else {
|
||||
const routeSiteId = to.query?.siteId
|
||||
if (routeSiteId) {
|
||||
localStorage.setItem(GLOBAL_SITE_STORAGE_KEY, routeSiteId)
|
||||
} else {
|
||||
const globalSiteId = localStorage.getItem(GLOBAL_SITE_STORAGE_KEY)
|
||||
if (globalSiteId && shouldAppendSiteId(to.path)) {
|
||||
next({
|
||||
path: to.path,
|
||||
query: {
|
||||
...to.query,
|
||||
siteId: globalSiteId
|
||||
},
|
||||
hash: to.hash,
|
||||
replace: true
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if (store.getters.roles.length === 0) {
|
||||
isRelogin.show = true
|
||||
// 判断当前用户是否已拉取完user_info信息
|
||||
|
||||
@ -2,165 +2,323 @@ import Layout from "@/layout/index.vue";
|
||||
|
||||
//单站监控
|
||||
// todo 本地设置了 hidden:true,不会显示在侧边栏,需要在系统管理、菜单管理中手动添加菜单后才会展示在侧边栏
|
||||
export const dzjk=[
|
||||
{
|
||||
path: '/dzjk',
|
||||
component: Layout,
|
||||
redirect: '/dzjk/home',
|
||||
meta: { title: '单站监控', icon: 'dashboard',},
|
||||
alwaysShow: false,
|
||||
name:'Dzjk',
|
||||
hidden:true,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/ems/dzjk/index'),
|
||||
name: 'Dzjk',
|
||||
export const dzjk = [
|
||||
{
|
||||
path: '/dzjk',
|
||||
component: Layout,
|
||||
redirect: '/dzjk/home',
|
||||
meta: {title: '单站监控', icon: 'dashboard',},
|
||||
alwaysShow: false,
|
||||
name: 'DzjkLocal',
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '/dzjk/home',
|
||||
component: () => import('@/views/ems/dzjk/home/index.vue'),
|
||||
name: 'DzjkHome',
|
||||
meta: { title: '站点首页',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkHome' }
|
||||
},
|
||||
{
|
||||
path: '/dzjk/zxlt',
|
||||
component: () => import('@/views/ems/dzjk/zxlt/index.vue'),
|
||||
name: 'DzjkZxlt',
|
||||
meta: { title: '主线路图',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkZxlt' }
|
||||
},
|
||||
{
|
||||
path: '/dzjk/sbjk',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/index.vue'),
|
||||
name: 'DzjkSbjk',
|
||||
meta: { title: '设备监控',breadcrumb: false,activeMenu: '/dzjk'},
|
||||
redirect: '/dzjk/sbjk/ssyx',
|
||||
children: [
|
||||
{
|
||||
path: 'ssyx',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/ssyx/index.vue'),
|
||||
name: 'DzjkSbjkSsyx',
|
||||
meta: { title: '实时运行',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkSbjk'},
|
||||
},
|
||||
{
|
||||
path: 'pcs',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/pcs/index.vue'),
|
||||
name: 'DzjkSbjkPcs',
|
||||
meta: { title: 'PCS',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkSbjk'},
|
||||
},
|
||||
{
|
||||
path: 'bmszl',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/bmszl/index.vue'),
|
||||
name: 'DzjkSbjkBmszl',
|
||||
meta: { title: 'BMS总览',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkSbjk'},
|
||||
},
|
||||
{
|
||||
path: 'bmsdcc',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/bmsdcc/index.vue'),
|
||||
name: 'DzjkSbjkBmsdcc',
|
||||
meta: { title: 'BMS电池簇',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkSbjk'},
|
||||
},
|
||||
{
|
||||
path: 'dtdc',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/dtdc/index.vue'),
|
||||
name: 'DzjkSbjkDtdc',
|
||||
meta: { title: '单体电池',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkSbjk'},
|
||||
},
|
||||
{
|
||||
path: 'db',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/db/index.vue'),
|
||||
name: 'DzjkSbjkDb',
|
||||
meta: { title: '电表',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkSbjk'},
|
||||
},
|
||||
{
|
||||
path: 'yl',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/yl/index.vue'),
|
||||
name: 'DzjkSbjkYl',
|
||||
meta: { title: '液冷',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkSbjk'},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/dzjk/gzgj',
|
||||
component: () => import('@/views/ems/dzjk/gzgj/index.vue'),
|
||||
name: 'DzjkGzgj',
|
||||
meta: { title: '故障告警',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkGzgj' }
|
||||
},
|
||||
{
|
||||
path: '/dzjk/tjbb',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/index.vue'),
|
||||
name: 'DzjkTjbb',
|
||||
meta: {title: '统计报表', breadcrumb: false, activeMenu: '/dzjk'},
|
||||
redirect: '/dzjk/tjbb/gltj',
|
||||
children: [
|
||||
{
|
||||
path: 'gltj',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/gltj/index.vue'),
|
||||
name: 'DzjkTjbbGltj',
|
||||
meta: { title: '概率统计',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkTjbb'},
|
||||
},
|
||||
{
|
||||
path: 'glqx',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/glqx/index.vue'),
|
||||
name: 'DzjkTjbbGlqx',
|
||||
meta: { title: '功率曲线',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkTjbb'},
|
||||
},
|
||||
{
|
||||
path: 'pcsqx',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/pcsqx/index.vue'),
|
||||
name: 'DzjkTjbbPcsqx',
|
||||
meta: { title: 'PCS曲线',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkTjbb'},
|
||||
},
|
||||
{
|
||||
path: 'dcdqx',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/dcdqx/index.vue'),
|
||||
name: 'DzjkTjbbDcdqx',
|
||||
meta: { title: '电池堆曲线',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkTjbb'},
|
||||
},
|
||||
{
|
||||
path: 'dcwd',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/dcwd/index.vue'),
|
||||
name: 'DzjkTjbbDcwd',
|
||||
meta: { title: '电池温度',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkTjbb'},
|
||||
},
|
||||
{
|
||||
path: 'dbbb',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/dbbb/index.vue'),
|
||||
name: 'DzjkTjbbDbbb',
|
||||
meta: { title: '电表报表',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkTjbb'},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/dzjk/clpz',
|
||||
component: () => import('@/views/ems/dzjk/clpz/index.vue'),
|
||||
name: 'DzjkClpz',
|
||||
meta: {title: '策略配置', breadcrumb: false, activeMenu: '/dzjk'},
|
||||
redirect: '/dzjk/clpz/clyx',
|
||||
children: [
|
||||
{
|
||||
path: 'clyx',
|
||||
component: () => import('@/views/ems/dzjk/clpz/clyx/index.vue'),
|
||||
name: 'DzjkClpzClyx',
|
||||
meta: { title: '策略运行',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkClpz'},
|
||||
},
|
||||
// {
|
||||
// path: 'xftg',
|
||||
// component: () => import('@/views/ems/dzjk/clpz/xftg/index.vue'),
|
||||
// hidden:true,
|
||||
// breadcrumb: false,
|
||||
// name: 'DzjkClpzXftg',
|
||||
// meta: { title: '削峰填谷',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkClpz'},
|
||||
// }
|
||||
]
|
||||
}
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/ems/dzjk/index'),
|
||||
name: 'DzjkRoot',
|
||||
redirect: '/dzjk/home',
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '/dzjk/home',
|
||||
component: () => import('@/views/ems/dzjk/home/index.vue'),
|
||||
name: 'DzjkHome',
|
||||
meta: {
|
||||
title: '站点首页',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkHome'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/dzjk/zxlt',
|
||||
component: () => import('@/views/ems/dzjk/zxlt/index.vue'),
|
||||
name: 'DzjkZxlt',
|
||||
meta: {
|
||||
title: '主线路图',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkZxlt'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/dzjk/sbjk',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/index.vue'),
|
||||
name: 'DzjkSbjk',
|
||||
meta: {title: '设备监控', breadcrumb: false, activeMenu: '/dzjk'},
|
||||
redirect: '/dzjk/sbjk/ssyx',
|
||||
children: [
|
||||
{
|
||||
path: 'ssyx',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/ssyx/index.vue'),
|
||||
name: 'DzjkSbjkSsyx',
|
||||
meta: {
|
||||
title: '实时运行',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkSbjk',
|
||||
deviceCategory: 'SSYX'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'ems',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/ems/index.vue'),
|
||||
name: 'DzjkSbjkEms',
|
||||
meta: {
|
||||
title: 'EMS',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkSbjk',
|
||||
deviceCategory: 'EMS'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'pcs',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/pcs/index.vue'),
|
||||
name: 'DzjkSbjkPcs',
|
||||
meta: {
|
||||
title: 'PCS',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkSbjk',
|
||||
deviceCategory: 'PCS'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'bmszl',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/bmszl/index.vue'),
|
||||
name: 'DzjkSbjkBmszl',
|
||||
meta: {
|
||||
title: 'BMS总览',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkSbjk',
|
||||
deviceCategory: 'STACK'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'bmsdcc',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/bmsdcc/index.vue'),
|
||||
name: 'DzjkSbjkBmsdcc',
|
||||
meta: {
|
||||
title: 'BMS电池簇',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkSbjk',
|
||||
deviceCategory: 'CLUSTER'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'dtdc',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/dtdc/index.vue'),
|
||||
name: 'DzjkSbjkDtdc',
|
||||
meta: {
|
||||
title: 'BMS单体电池',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkSbjk',
|
||||
deviceCategory: 'BATTERY'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'db',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/db/index.vue'),
|
||||
name: 'DzjkSbjkDb',
|
||||
meta: {
|
||||
title: '电表',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkSbjk',
|
||||
deviceCategory: 'AMMETER'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'yl',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/yl/index.vue'),
|
||||
name: 'DzjkSbjkYl',
|
||||
meta: {
|
||||
title: '冷却',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkSbjk',
|
||||
deviceCategory: 'COOLING'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'dh',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/dh/index.vue'),
|
||||
name: 'DzjkSbjkDh',
|
||||
meta: {
|
||||
title: '动环',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkSbjk',
|
||||
deviceCategory: 'DH'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'xf',
|
||||
component: () => import('@/views/ems/dzjk/sbjk/xf/index.vue'),
|
||||
name: 'DzjkSbjkXf',
|
||||
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',
|
||||
activeSecondMenuName: 'DzjkGzgj'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/dzjk/tjbb',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/index.vue'),
|
||||
name: 'DzjkTjbb',
|
||||
meta: {title: '统计报表', breadcrumb: false, activeMenu: '/dzjk'},
|
||||
redirect: '/dzjk/tjbb/gltj',
|
||||
children: [
|
||||
{
|
||||
path: 'gltj',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/gltj/index.vue'),
|
||||
name: 'DzjkTjbbGltj',
|
||||
meta: {
|
||||
title: '运行统计',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkTjbb'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'glqx',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/glqx/index.vue'),
|
||||
name: 'DzjkTjbbGlqx',
|
||||
meta: {
|
||||
title: '功率曲线',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkTjbb'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'pcsqx',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/pcsqx/index.vue'),
|
||||
name: 'DzjkTjbbPcsqx',
|
||||
meta: {
|
||||
title: 'PCS曲线',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkTjbb'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'dcdqx',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/dcdqx/index.vue'),
|
||||
name: 'DzjkTjbbDcdqx',
|
||||
meta: {
|
||||
title: '电池堆曲线',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkTjbb'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'dcwd',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/dcwd/index.vue'),
|
||||
name: 'DzjkTjbbDcwd',
|
||||
meta: {
|
||||
title: '电池温度',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkTjbb'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'dbbb',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/dbbb/index.vue'),
|
||||
name: 'DzjkTjbbDbbb',
|
||||
meta: {
|
||||
title: '电表报表',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkTjbb'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'sybb',
|
||||
component: () => import('@/views/ems/dzjk/tjbb/sybb/index.vue'),
|
||||
name: 'DzjkTjbbSybb',
|
||||
meta: {
|
||||
title: '收益报表',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkTjbb'
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/dzjk/clpz',
|
||||
component: () => import('@/views/ems/dzjk/clpz/index.vue'),
|
||||
name: 'DzjkClpz',
|
||||
meta: {title: '策略配置', breadcrumb: false, activeMenu: '/dzjk'},
|
||||
redirect: '/dzjk/clpz/clyx',
|
||||
children: [
|
||||
{
|
||||
path: 'clyx',
|
||||
component: () => import('@/views/ems/dzjk/clpz/clyx/index.vue'),
|
||||
name: 'DzjkClpzClyx',
|
||||
meta: {
|
||||
title: '策略运行',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkClpz'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'runtimeParam',
|
||||
component: () => import('@/views/ems/dzjk/clpz/runtimeParam/index.vue'),
|
||||
name: 'DzjkClpzRuntimeParam',
|
||||
meta: {
|
||||
title: '运行参数',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkClpz'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'sbbh',
|
||||
component: () => import('@/views/ems/site/sbbh/index.vue'),
|
||||
name: 'DzjkClpzSbbh',
|
||||
meta: {
|
||||
title: '设备保护',
|
||||
breadcrumb: false,
|
||||
activeMenu: '/dzjk',
|
||||
activeSecondMenuName: 'DzjkClpz'
|
||||
},
|
||||
},
|
||||
// {
|
||||
// path: 'xftg',
|
||||
// component: () => import('@/views/ems/dzjk/clpz/xftg/index.vue'),
|
||||
// hidden:true,
|
||||
// breadcrumb: false,
|
||||
// name: 'DzjkClpzXftg',
|
||||
// meta: { title: '削峰填谷',breadcrumb: false,activeMenu: '/dzjk',activeSecondMenuName:'DzjkClpz'},
|
||||
// }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
@ -89,6 +89,19 @@ export const constantRoutes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/ems/site/zdlb',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: 'monitor-point-mapping',
|
||||
component: () => import('@/views/ems/site/zdlb/MonitorPointMapping.vue'),
|
||||
name: 'MonitorPointMapping',
|
||||
meta: { title: '单站监控项目点位配置', activeMenu: '/dzjk/clpz/sbbh' }
|
||||
}
|
||||
]
|
||||
},
|
||||
// EMS管理系统routers
|
||||
...dzjk
|
||||
]
|
||||
|
||||
@ -7,12 +7,12 @@ module.exports = {
|
||||
/**
|
||||
* 侧边栏主题 深色主题theme-dark,浅色主题theme-light
|
||||
*/
|
||||
sideTheme: 'theme-dark',
|
||||
sideTheme: 'theme-light',
|
||||
|
||||
/**
|
||||
* 系统布局配置
|
||||
*/
|
||||
showSettings: true,
|
||||
showSettings: false,
|
||||
|
||||
/**
|
||||
* 是否显示顶部导航
|
||||
@ -52,5 +52,5 @@ module.exports = {
|
||||
/**
|
||||
* 底部版权文本内容
|
||||
*/
|
||||
footerContent: 'Copyright © 2018-2025 xzzn. All Rights Reserved.'
|
||||
footerContent: 'Copyright © 2025 上动新能源 版权所有'
|
||||
}
|
||||
|
||||
@ -1,25 +1,72 @@
|
||||
import {getAlarmDetailList, getSiteAllDeviceCategory} from '@/api/ems/dzjk'
|
||||
|
||||
const ems = {
|
||||
state: {
|
||||
zdList:[],
|
||||
workStatusOptions:{'0':'正常','1':'异常','2':'停止'},//工作状态
|
||||
deviceStatusOptions:{'0':'在线','1':'离线','2':'维修中'},//设备状态
|
||||
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:{0:'待处理', 1:'已处理', 2:'处理中'},//工单处理状态
|
||||
strategyStatusOptions:{'0':'未启用', '1':'已运行', '2':'已暂停', '3':'禁用', '4':'删除'},//策略状态
|
||||
chargeStatusOptions:{'1':'充电','2':'待机'},//冲放状态
|
||||
},
|
||||
mutations: {
|
||||
SET_ZD_LIST(state, list) {
|
||||
state.zdList = list || []
|
||||
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: {
|
||||
//查询站点的所有待处理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
|
||||
|
||||
@ -33,7 +33,13 @@ const permission = {
|
||||
GenerateRoutes({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
// 向后端请求路由数据
|
||||
getRouters().then(res => {
|
||||
getRouters().then(res => {
|
||||
let hasDzjk = false
|
||||
if(res?.data){
|
||||
res.data.forEach(i=>{
|
||||
i.children && i.children.find(j=>j.path.indexOf('dzjk')>-1) && (hasDzjk=true)
|
||||
})
|
||||
}
|
||||
const sdata = JSON.parse(JSON.stringify(res.data))
|
||||
const rdata = JSON.parse(JSON.stringify(res.data))
|
||||
const sidebarRoutes = filterAsyncRouter(sdata)
|
||||
@ -41,6 +47,13 @@ const permission = {
|
||||
const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
|
||||
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
|
||||
router.addRoutes(asyncRoutes)
|
||||
// 后端已下发 dzjk 菜单时,移除本地静态 dzjk 路由,避免重名/重复注册
|
||||
if (hasDzjk) {
|
||||
const index = constantRoutes.findIndex(i=>i.path.indexOf('dzjk')>-1)
|
||||
if (index > -1) {
|
||||
constantRoutes.splice(index, 1)
|
||||
}
|
||||
}
|
||||
commit('SET_ROUTES', rewriteRoutes)
|
||||
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
|
||||
commit('SET_DEFAULT_ROUTES', sidebarRoutes)
|
||||
@ -111,11 +124,16 @@ export function filterDynamicRoutes(routes) {
|
||||
}
|
||||
|
||||
export const loadView = (view) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return (resolve) => require([`@/views/${view}`], resolve)
|
||||
const normalizedView = String(view || '')
|
||||
.replace(/^\.\/+/, '')
|
||||
.replace(/^\/+/, '')
|
||||
.replace(/^@\/views\//, '')
|
||||
|
||||
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'staging') {
|
||||
return (resolve) => require([`@/views/${normalizedView}`], resolve)
|
||||
} else {
|
||||
// 使用 import 实现生产环境的路由懒加载
|
||||
return () => import(`@/views/${view}`)
|
||||
return () => import(`@/views/${normalizedView}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container clpz-ems-dashboard-editor-container">
|
||||
<div class="ems-dashboard-editor-container ems-third-menu-container">
|
||||
<el-menu
|
||||
class="ems-third-menu"
|
||||
:default-active="$route.name"
|
||||
@ -43,11 +43,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.clpz-ems-dashboard-editor-container{
|
||||
display: flex;
|
||||
padding:0;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
.clpz-ems-content-container{
|
||||
margin-top:0;
|
||||
padding-top:0;
|
||||
@ -55,4 +50,3 @@ export default {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
248
src/views/ems/dzjk/clpz/runtimeParam/index.vue
Normal file
@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container" v-loading="loading">
|
||||
<el-card shadow="never" class="common-card-container">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="card-title">运行参数配置</span>
|
||||
<span class="site-tag">站点:{{ siteId || '-' }}</span>
|
||||
</div>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="150px">
|
||||
<el-row :gutter="20" class="runtime-grid">
|
||||
<el-col :xs="24" :sm="24" :md="8">
|
||||
<div class="group-card">
|
||||
<div class="group-title">SOC上下限</div>
|
||||
<el-form-item label="SOC下限(%)" prop="socDown">
|
||||
<el-input-number v-model="form.socDown" :min="0" :max="100" :step="1" :precision="2" :controls="false" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SOC上限(%)" prop="socUp">
|
||||
<el-input-number v-model="form.socUp" :min="0" :max="100" :step="1" :precision="2" :controls="false" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="8">
|
||||
<div class="group-card">
|
||||
<div class="group-title">防逆流参数</div>
|
||||
<el-form-item label="防逆流阈值(kW)" prop="antiReverseThreshold">
|
||||
<el-input-number v-model="form.antiReverseThreshold" :min="0" :step="1" :precision="2" :controls="false" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="阈值上浮比例(%)" prop="antiReverseRangePercent">
|
||||
<el-input-number v-model="form.antiReverseRangePercent" :min="0" :step="1" :precision="2" :controls="false" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="恢复上限(kW)" prop="antiReverseUp">
|
||||
<el-input-number v-model="form.antiReverseUp" :min="0" :step="1" :precision="2" :controls="false" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="降功率比例(%)" prop="antiReversePowerDownPercent">
|
||||
<el-input-number v-model="form.antiReversePowerDownPercent" :min="0" :max="100" :step="1" :precision="2" :controls="false" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="硬停阈值(kW)" prop="antiReverseHardStopThreshold">
|
||||
<el-input-number v-model="form.antiReverseHardStopThreshold" :min="0" :step="1" :precision="2" :controls="false" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="8">
|
||||
<div class="group-card">
|
||||
<div class="group-title">功率与保护</div>
|
||||
<el-form-item label="设定功率倍率" prop="powerSetMultiplier">
|
||||
<el-input-number v-model="form.powerSetMultiplier" :min="0.0001" :step="0.1" :precision="4" :controls="false" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="保护介入" prop="protectInterveneEnable">
|
||||
<el-switch v-model="form.protectInterveneEnable" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="一级降额比例(%)" prop="protectL1DeratePercent">
|
||||
<el-input-number v-model="form.protectL1DeratePercent" :min="0" :max="100" :step="1" :precision="2" :controls="false" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="释放稳定时长(s)" prop="protectRecoveryStableSeconds">
|
||||
<el-input-number v-model="form.protectRecoveryStableSeconds" :min="0" :max="3600" :step="1" :precision="0" :controls="false" style="width: 160px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="三级锁存" prop="protectL3LatchEnable">
|
||||
<el-switch v-model="form.protectL3LatchEnable" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="冲突策略" prop="protectConflictPolicy">
|
||||
<el-select v-model="form.protectConflictPolicy" style="width: 160px;">
|
||||
<el-option label="最高等级优先" value="MAX_LEVEL_WIN" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="action-row">
|
||||
<el-button type="primary" :loading="saveLoading" @click="handleSave">保存</el-button>
|
||||
<el-button @click="init">重置</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import getQuerySiteId from '@/mixins/ems/getQuerySiteId'
|
||||
import { getStrategyRuntimeConfig, saveStrategyRuntimeConfig } from '@/api/ems/dzjk'
|
||||
|
||||
const emptyForm = () => ({
|
||||
siteId: '',
|
||||
socDown: 0,
|
||||
socUp: 100,
|
||||
antiReverseThreshold: 30,
|
||||
powerSetMultiplier: 10,
|
||||
antiReverseRangePercent: 20,
|
||||
antiReverseUp: 100,
|
||||
antiReversePowerDownPercent: 10,
|
||||
antiReverseHardStopThreshold: 20,
|
||||
protectInterveneEnable: 1,
|
||||
protectL1DeratePercent: 50,
|
||||
protectRecoveryStableSeconds: 5,
|
||||
protectL3LatchEnable: 1,
|
||||
protectConflictPolicy: 'MAX_LEVEL_WIN'
|
||||
})
|
||||
|
||||
export default {
|
||||
name: 'DzjkClpzRuntimeParam',
|
||||
mixins: [getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
saveLoading: false,
|
||||
form: emptyForm(),
|
||||
rules: {
|
||||
socDown: [
|
||||
{ required: true, message: '请输入SOC下限', trigger: 'change' }
|
||||
],
|
||||
socUp: [
|
||||
{ required: true, message: '请输入SOC上限', trigger: 'change' }
|
||||
],
|
||||
antiReverseThreshold: [
|
||||
{ required: true, message: '请输入防逆流阈值', trigger: 'change' }
|
||||
],
|
||||
powerSetMultiplier: [
|
||||
{ required: true, message: '请输入设定功率倍率', trigger: 'change' }
|
||||
],
|
||||
antiReverseRangePercent: [
|
||||
{ required: true, message: '请输入防逆流阈值上浮比例', trigger: 'change' }
|
||||
],
|
||||
antiReverseUp: [
|
||||
{ required: true, message: '请输入防逆流恢复上限', trigger: 'change' }
|
||||
],
|
||||
antiReversePowerDownPercent: [
|
||||
{ required: true, message: '请输入防逆流降功率比例', trigger: 'change' }
|
||||
],
|
||||
antiReverseHardStopThreshold: [
|
||||
{ required: true, message: '请输入防逆流硬停阈值', trigger: 'change' }
|
||||
],
|
||||
protectInterveneEnable: [
|
||||
{ required: true, message: '请选择是否启用保护介入', trigger: 'change' }
|
||||
],
|
||||
protectL1DeratePercent: [
|
||||
{ required: true, message: '请输入一级降额比例', trigger: 'change' }
|
||||
],
|
||||
protectRecoveryStableSeconds: [
|
||||
{ required: true, message: '请输入释放稳定时长', trigger: 'change' }
|
||||
],
|
||||
protectL3LatchEnable: [
|
||||
{ required: true, message: '请选择三级锁存开关', trigger: 'change' }
|
||||
],
|
||||
protectConflictPolicy: [
|
||||
{ required: true, message: '请选择冲突策略', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
if (!this.siteId) {
|
||||
this.form = emptyForm()
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
getStrategyRuntimeConfig(this.siteId).then(response => {
|
||||
const data = response?.data || {}
|
||||
this.form = {
|
||||
...emptyForm(),
|
||||
...data,
|
||||
siteId: this.siteId
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleSave() {
|
||||
if (!this.siteId) {
|
||||
this.$message.error('缺少站点ID')
|
||||
return
|
||||
}
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
if (Number(this.form.socDown) > Number(this.form.socUp)) {
|
||||
this.$message.error('SOC下限不能大于SOC上限')
|
||||
return
|
||||
}
|
||||
if (Number(this.form.powerSetMultiplier) <= 0) {
|
||||
this.$message.error('设定功率倍率必须大于0')
|
||||
return
|
||||
}
|
||||
if (Number(this.form.protectL1DeratePercent) < 0 || Number(this.form.protectL1DeratePercent) > 100) {
|
||||
this.$message.error('一级降额比例必须在0~100之间')
|
||||
return
|
||||
}
|
||||
if (Number(this.form.protectRecoveryStableSeconds) < 0) {
|
||||
this.$message.error('释放稳定时长不能小于0')
|
||||
return
|
||||
}
|
||||
this.saveLoading = true
|
||||
saveStrategyRuntimeConfig({ ...this.form, siteId: this.siteId }).then(response => {
|
||||
if (response?.code === 200) {
|
||||
this.$message.success('保存成功')
|
||||
this.init()
|
||||
}
|
||||
}).finally(() => {
|
||||
this.saveLoading = false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.site-tag {
|
||||
float: right;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.common-card-container {
|
||||
border: none;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.runtime-grid {
|
||||
max-width: 1180px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.runtime-grid > .el-col {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.group-card {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 14px 14px 2px;
|
||||
min-height: 330px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.action-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
@ -1,124 +1,221 @@
|
||||
<template>
|
||||
<el-dialog :visible.sync="dialogTableVisible" class="ems-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-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>
|
||||
</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="soc下限" prop="sdcDown">
|
||||
<el-input v-model="formData.sdcDown" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
|
||||
<el-input v-model="formInline.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-input v-model="formInline.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: '01:00',
|
||||
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: '01:00',
|
||||
end: '23:00',
|
||||
minTime: formInline.startTime
|
||||
}">
|
||||
</el-time-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="冲放功率" prop="chargeDischargePower">
|
||||
<el-input v-model="formInline.chargeDischargePower" placeholder="请输入"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="充电状态" prop="chargeStatus">
|
||||
<el-select v-model="formInline.chargeStatus" placeholder="请选择">
|
||||
<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
|
||||
<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">
|
||||
<!-- todo 如果要在span-method中使用column.property 在表格中必须定义prop="xxx"属性-->
|
||||
<el-table-column
|
||||
<el-table-column
|
||||
prop="startTime"
|
||||
label="开始时间">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
<template slot-scope="scope">
|
||||
<el-time-select
|
||||
v-if="mode === 'edit'"
|
||||
v-model="scope.row.startTime"
|
||||
placeholder="开始时间"
|
||||
:picker-options="{
|
||||
start: '00:00',
|
||||
step: '00:01',
|
||||
end: '23:59'
|
||||
}"
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
<span v-else>{{ scope.row.startTime || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="endTime"
|
||||
label="结束时间">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
<template slot-scope="scope">
|
||||
<el-time-select
|
||||
v-if="mode === 'edit'"
|
||||
v-model="scope.row.endTime"
|
||||
placeholder="结束时间"
|
||||
:picker-options="{
|
||||
start: '00:00',
|
||||
step: '00:01',
|
||||
end: '23:59',
|
||||
minTime: scope.row.startTime
|
||||
}"
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
<span v-else>{{ scope.row.endTime || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="chargeDischargePower"
|
||||
label="充放功率(kW)">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-if="mode === 'edit'"
|
||||
v-model.trim="scope.row.chargeDischargePower"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
<span v-else>{{ scope.row.chargeDischargePower || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="sdcDown"
|
||||
label="SOC下限">
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-if="mode === 'edit'"
|
||||
v-model.trim="scope.row.sdcDown"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
<span v-else>{{ scope.row.sdcDown === null || scope.row.sdcDown === undefined || scope.row.sdcDown === '' ? '-' : scope.row.sdcDown + '%' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="sdcUp"
|
||||
label="SOC上限">
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-if="mode === 'edit'"
|
||||
v-model.trim="scope.row.sdcUp"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
<span v-else>{{ scope.row.sdcUp === null || scope.row.sdcUp === undefined || scope.row.sdcUp === '' ? '-' : scope.row.sdcUp + '%' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="chargeStatus"
|
||||
label="充电状态">
|
||||
<template slot-scope="scope">
|
||||
{{chargeStatusOptions[scope.row.chargeStatus]}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
<template slot-scope="scope">
|
||||
<el-select
|
||||
v-if="mode === 'edit'"
|
||||
v-model="scope.row.chargeStatus"
|
||||
placeholder="请选择"
|
||||
:style="{width: '100%'}"
|
||||
>
|
||||
<el-option
|
||||
v-for="(value,key) in chargeStatusOptions"
|
||||
:key="key+'chargeStatusEditOptions'"
|
||||
:label="value"
|
||||
:value="key"
|
||||
/>
|
||||
</el-select>
|
||||
<span v-else>{{ chargeStatusOptions[scope.row.chargeStatus] }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
label="操作"
|
||||
width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
<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>
|
||||
删除
|
||||
</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'
|
||||
import {addStrategyTemp, editStrategyTemp, getStrategyTempDetail} from '@/api/ems/dzjk'
|
||||
|
||||
export default {
|
||||
inject:['$home'],
|
||||
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,
|
||||
mode: '',
|
||||
editTempId: '',
|
||||
dialogTableVisible: false,
|
||||
secondRange: range,
|
||||
formData: {
|
||||
templateName: '',
|
||||
sdcLimit: false,
|
||||
sdcDown: '',
|
||||
sdcUp: '',
|
||||
},
|
||||
rules: {
|
||||
templateName: [{
|
||||
@ -126,45 +223,44 @@ export default {
|
||||
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:{
|
||||
startTime:'',endTime:'',chargeDischargePower:'',chargeStatus:''
|
||||
formInline: {
|
||||
timeRange: range,
|
||||
chargeDischargePower: '',
|
||||
sdcDown: '',
|
||||
sdcUp: '',
|
||||
chargeStatus: ''
|
||||
},
|
||||
formInlineRule:{
|
||||
startTime: [{
|
||||
formInlineRule: {
|
||||
timeRange: [{
|
||||
required: true,
|
||||
message: '请选择开始时间',
|
||||
trigger: 'change'
|
||||
}],
|
||||
endTime: [{
|
||||
required: true,
|
||||
message: '请选择结束时间',
|
||||
message: '请选择时间范围',
|
||||
trigger: 'change'
|
||||
}],
|
||||
chargeDischargePower: [{
|
||||
required: true,
|
||||
message: '请输入冲放功率',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{ pattern: /^(0|[1-9]\d*)(\.\d+)?$/, message: '请输入合法数字或小数' }
|
||||
required: true,
|
||||
message: '请输入冲放功率',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{pattern: /^-?\d*\.?\d*$/, message: '请输入合法数字或小数'}
|
||||
],
|
||||
chargeStatus:[{
|
||||
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: '请输入合法数字或小数'}
|
||||
],
|
||||
chargeStatus: [{
|
||||
required: true,
|
||||
message: '请选择充放状态',
|
||||
trigger: ['blur','change']
|
||||
}
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
},
|
||||
tableData:[],
|
||||
tableData: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -172,145 +268,183 @@ export default {
|
||||
chargeStatusOptions: state => state?.ems?.chargeStatusOptions || {},
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
"formInline.startTime":{
|
||||
handler(newVal){
|
||||
if(newVal && this.formInline.endTime){
|
||||
const endTime = parseInt((this.formInline.endTime).split(':')[0] || 0)
|
||||
const startTime =parseInt(newVal.split(':')[0])
|
||||
if(endTime<=startTime){
|
||||
this.formInline.endTime = `${startTime+1 <=9 ? '0'+(startTime+1) : startTime+1}:00`
|
||||
}
|
||||
}
|
||||
},
|
||||
deep:true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changeSiteId(){
|
||||
this.dialogTableVisible=false
|
||||
this.mode=''
|
||||
this.editTempId=''
|
||||
this.formData={
|
||||
changeSiteId() {
|
||||
this.dialogTableVisible = false
|
||||
this.mode = ''
|
||||
this.editTempId = ''
|
||||
this.formData = {
|
||||
templateName: '',
|
||||
sdcLimit: false,
|
||||
sdcDown: '',
|
||||
sdcUp: '',
|
||||
}
|
||||
this.formInline={
|
||||
startTime:'',endTime:'',chargeDischargePower:'',chargeStatus:''
|
||||
}
|
||||
this.formInline = {
|
||||
timeRange: this.secondRange, chargeDischargePower: '', sdcDown: '', sdcUp: '', chargeStatus: ''
|
||||
}//startTime: '', endTime: '',
|
||||
this.showAddTime = false
|
||||
this.tableData=[]
|
||||
this.tableData = []
|
||||
},
|
||||
show({mode = 'add', editTempId = ''}){
|
||||
show({mode = 'add', editTempId = ''}) {
|
||||
this.$nextTick(() => {
|
||||
this.dialogTableVisible = true
|
||||
this.mode = mode
|
||||
this.editTempId=editTempId
|
||||
if(mode === 'edit' && editTempId){
|
||||
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
|
||||
const data = JSON.parse(JSON.stringify(response?.data || []));
|
||||
if (data.length > 0) {
|
||||
const {templateName, sdcLimit} = JSON.parse(JSON.stringify(data[0]));
|
||||
this.formData.templateName = templateName
|
||||
this.formData.sdcLimit = sdcLimit
|
||||
}
|
||||
if(data.length === 1){
|
||||
const {startTime,endTime}=data;
|
||||
if(!startTime || !endTime){
|
||||
if (data.length === 1) {
|
||||
const {startTime, endTime} = data[0];
|
||||
if (!startTime || !endTime) {
|
||||
this.tableData = []
|
||||
}else{
|
||||
this.tableData= data
|
||||
} else {
|
||||
this.tableData = data
|
||||
}
|
||||
}else{
|
||||
this.tableData= data
|
||||
} else {
|
||||
this.tableData = data
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
addTime(){
|
||||
this.showAddTime=true
|
||||
addTime() {
|
||||
this.showAddTime = true
|
||||
},
|
||||
cancelAddTime(){
|
||||
cancelAddTime() {
|
||||
this.$refs.addTimeForm.resetFields()
|
||||
this.showAddTime=false
|
||||
this.formInline = {startTime:'',endTime:'',chargeDischargePower:'',chargeStatus:''}
|
||||
this.showAddTime = false
|
||||
this.formInline = {timeRange: this.secondRange, chargeDischargePower: '', sdcDown: '', sdcUp: '', chargeStatus: ''}//startTime: '', endTime: '',
|
||||
},
|
||||
saveTime(){
|
||||
saveTime() {
|
||||
//表单校验,校验成功,添加到tableData里
|
||||
this.$refs.addTimeForm.validate(valid => {
|
||||
if (!valid) return
|
||||
this.tableData.push(JSON.parse(JSON.stringify(this.formInline)));
|
||||
this.$nextTick(() => {this.cancelAddTime()})
|
||||
const {timeRange: [startTime, endTime], chargeDischargePower, sdcDown, sdcUp, chargeStatus} = this.formInline
|
||||
|
||||
this.tableData.push({startTime, endTime, chargeDischargePower, sdcDown, sdcUp, chargeStatus})
|
||||
this.$nextTick(() => {
|
||||
this.cancelAddTime()
|
||||
})
|
||||
})
|
||||
},
|
||||
deleteRow(index){
|
||||
this.tableData.splice(index,1)
|
||||
deleteRow(index) {
|
||||
this.tableData.splice(index, 1)
|
||||
},
|
||||
saveDialog() {
|
||||
this.$refs.addTempForm.validate(valid => {
|
||||
if (!valid) return
|
||||
//校验时间选择范围是否冲突
|
||||
let status = true
|
||||
this.tableData.forEach((outer,outerIndex)=>{
|
||||
const {startTime, endTime}=outer
|
||||
const outerStart = parseInt(startTime),outerEnd = parseInt(endTime)
|
||||
if(outerStart>outerEnd){
|
||||
status = false
|
||||
}else{
|
||||
this.tableData.forEach((inner,innerIndex)=>{
|
||||
if(innerIndex !== outerIndex){
|
||||
const {startTime:innerStartTime, endTime:innerEndTime}=inner
|
||||
const innerStart = parseInt(innerStartTime),innerEnd = parseInt(innerEndTime)
|
||||
if((innerStart<outerStart && innerEnd>outerEnd) || !((innerStart<outerStart && innerEnd<=outerStart) || (innerStart>=outerEnd && innerEnd>outerEnd))){
|
||||
status=false
|
||||
}
|
||||
}
|
||||
})
|
||||
const {templateName, sdcLimit} = this.formData
|
||||
const {siteId, updateStrategyId} = this.$home
|
||||
const tableData = this.tableData.map(item => ({
|
||||
...item,
|
||||
sdcDown: this.normalizeSocValue(item.sdcDown),
|
||||
sdcUp: this.normalizeSocValue(item.sdcUp)
|
||||
}))
|
||||
if (!this.validateTableData(tableData)) return
|
||||
if (this.mode === 'edit') {
|
||||
editStrategyTemp({
|
||||
siteId,
|
||||
strategyId: updateStrategyId,
|
||||
templateId: this.editTempId,
|
||||
templateName,
|
||||
sdcLimit,
|
||||
timeConfigList: tableData
|
||||
}).then(response => {
|
||||
if (response?.code === 200) {
|
||||
this.closeDialog()
|
||||
this.$emit('update')
|
||||
this.$emit('updateTimeSetting')
|
||||
}
|
||||
})
|
||||
if(!status){
|
||||
return this.$message.error('时间选择范围冲突');
|
||||
}
|
||||
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')
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
addStrategyTemp({
|
||||
siteId,
|
||||
strategyId: updateStrategyId,
|
||||
templateName,
|
||||
sdcLimit,
|
||||
timeConfigList: tableData
|
||||
}).then(response => {
|
||||
if (response?.code === 200) {
|
||||
this.closeDialog()
|
||||
this.$emit('update')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
closeDialog(){
|
||||
normalizeSocValue(value) {
|
||||
if (value === null || value === undefined) return null
|
||||
const normalized = String(value).replace('%', '').trim()
|
||||
return normalized === '' ? null : normalized
|
||||
},
|
||||
toMinutes(timeValue) {
|
||||
if (!timeValue || String(timeValue).indexOf(':') < 0) return -1
|
||||
const [h, m] = String(timeValue).split(':')
|
||||
const hour = Number(h), minute = Number(m)
|
||||
if (!Number.isInteger(hour) || !Number.isInteger(minute)) return -1
|
||||
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return -1
|
||||
return hour * 60 + minute
|
||||
},
|
||||
validateTableData(list = []) {
|
||||
const numberPattern = /^-?\d+(\.\d+)?$/
|
||||
const socPattern = /^(0|[1-9]\d*)(\.\d+)?$/
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const row = list[i]
|
||||
const rowNo = i + 1
|
||||
if (!row.startTime || !row.endTime) {
|
||||
this.$message.error(`第${rowNo}行:开始时间和结束时间不能为空`)
|
||||
return false
|
||||
}
|
||||
const startMinute = this.toMinutes(row.startTime)
|
||||
const endMinute = this.toMinutes(row.endTime)
|
||||
if (startMinute < 0 || endMinute < 0 || startMinute >= endMinute) {
|
||||
this.$message.error(`第${rowNo}行:时间范围不合法`)
|
||||
return false
|
||||
}
|
||||
if (!numberPattern.test(String(row.chargeDischargePower ?? '').trim())) {
|
||||
this.$message.error(`第${rowNo}行:充放功率格式不正确`)
|
||||
return false
|
||||
}
|
||||
if (!socPattern.test(String(row.sdcDown ?? '').trim())) {
|
||||
this.$message.error(`第${rowNo}行:SOC下限格式不正确`)
|
||||
return false
|
||||
}
|
||||
if (!socPattern.test(String(row.sdcUp ?? '').trim())) {
|
||||
this.$message.error(`第${rowNo}行:SOC上限格式不正确`)
|
||||
return false
|
||||
}
|
||||
if (Number(row.sdcDown) > Number(row.sdcUp)) {
|
||||
this.$message.error(`第${rowNo}行:SOC下限不能大于SOC上限`)
|
||||
return false
|
||||
}
|
||||
if (row.chargeStatus === undefined || row.chargeStatus === null || row.chargeStatus === '') {
|
||||
this.$message.error(`第${rowNo}行:请选择充电状态`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
closeDialog() {
|
||||
// 清空所有数据
|
||||
this.$refs.addTempForm.resetFields()
|
||||
this.formData={
|
||||
this.formData = {
|
||||
templateName: '',
|
||||
sdcLimit:0,
|
||||
sdcDown: '',
|
||||
sdcUp: '',
|
||||
sdcLimit: 0,
|
||||
}
|
||||
this.tableData=[]
|
||||
this.tableData = []
|
||||
this.cancelAddTime()
|
||||
this.dialogTableVisible=false
|
||||
this.dialogTableVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.add-template-dialog {
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -69,9 +69,12 @@ export default {
|
||||
})
|
||||
this.chart.setOption({
|
||||
color:['#FFBD00','#3C81FF'],
|
||||
grid: {
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '10',
|
||||
bottom: '15',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
|
||||
@ -39,14 +39,14 @@
|
||||
prop="sdcDown"
|
||||
label="SOC下限">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row.sdcDown ? scope.row. sdcDown + '%' : '-'}}
|
||||
{{scope.row.sdcDown === null || scope.row.sdcDown === undefined || 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 + '%' : '-'}}
|
||||
{{scope.row.sdcUp === null || scope.row.sdcUp === undefined || scope.row.sdcUp === '' ? '-' : scope.row.sdcUp + '%'}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@ -92,7 +92,7 @@ export default {
|
||||
activeBtn:'',
|
||||
tempList:[],
|
||||
tableData:[],
|
||||
mixinPrototype:['templateName','sdcLimit','sdcDown','sdcUp']
|
||||
mixinPrototype:['templateName','sdcLimit']
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
|
||||
@ -1,27 +1,30 @@
|
||||
|
||||
<template>
|
||||
<el-card v-loading="loading" gshadow="always" class="common-card-container common-card-container-no-title-bg">
|
||||
<el-card v-loading="loading" gshadow="always" class="common-card-container common-card-container-no-title-bg">
|
||||
<!-- 搜索栏-->
|
||||
<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 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="正在加载数据">
|
||||
<el-option :label="value" :value="key" v-for="(value,key) in $store.state.ems.alarmLevelOptions" :key="key+'alarmLevelOptions'"></el-option>
|
||||
<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="结束时间">
|
||||
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>
|
||||
@ -36,7 +39,9 @@
|
||||
<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 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>
|
||||
@ -47,64 +52,77 @@
|
||||
stripe
|
||||
max-height="500"
|
||||
style="width: 100%;margin-top:25px;">
|
||||
<el-table-column
|
||||
<el-table-column
|
||||
prop="deviceName"
|
||||
label="设备名称">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
</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
|
||||
>
|
||||
<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
|
||||
</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
|
||||
<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
|
||||
<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
|
||||
<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="toTicket">生成工单</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
width="320"
|
||||
>
|
||||
<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>
|
||||
<el-button
|
||||
v-if="scope.row.status !== '1'"
|
||||
type="success"
|
||||
size="mini"
|
||||
style="margin-left: 8px;"
|
||||
@click="closeAlarmRecord(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"
|
||||
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>
|
||||
@ -113,145 +131,147 @@
|
||||
|
||||
|
||||
<script>
|
||||
import {getAlarmDetailList} from'@/api/ems/dzjk'
|
||||
import {getDeviceList} from'@/api/ems/site'
|
||||
import {closeAlarm, createTicketNo, getAlarmDetailList} from '@/api/ems/dzjk'
|
||||
import {getDeviceList} from '@/api/ems/site'
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import { formatDate } from '@/filters/ems'
|
||||
import {formatDate} from '@/filters/ems'
|
||||
|
||||
export default {
|
||||
name:'DzjkGzgj',
|
||||
mixins:[getQuerySiteId],
|
||||
name: 'DzjkGzgj',
|
||||
mixins: [getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
btnList:[
|
||||
{name:'今日告警',id:'today'},
|
||||
{name:'历史告警',id:'history'},
|
||||
loading: false,
|
||||
btnList: [
|
||||
{name: '未处理告警', id: 'today'},
|
||||
{name: '历史告警', id: 'history'},
|
||||
],
|
||||
deviceOptions:[],//设备列表
|
||||
pickerOptions:{
|
||||
deviceOptions: [],//设备列表
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
defaultDateRange:[],//默认展示的时间
|
||||
dateRange:[],//alarmStartTime,alarmEndTime
|
||||
activeBtn:'today',
|
||||
search:{deviceId:'',alarmLevel:''},
|
||||
defaultDateRange: [],//默认展示的时间
|
||||
dateRange: [],//alarmStartTime,alarmEndTime
|
||||
activeBtn: 'today',
|
||||
search: {deviceId: '', alarmLevel: ''},
|
||||
// 表格、分页
|
||||
tableData:[],
|
||||
pageSize:10,//分页栏当前每个数据总数
|
||||
pageNum:1,//分页栏当前页数
|
||||
totalSize:0,//table表格数据总数
|
||||
tableData: [],
|
||||
pageSize: 10,//分页栏当前每个数据总数
|
||||
pageNum: 1,//分页栏当前页数
|
||||
totalSize: 0,//table表格数据总数
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
methods: {
|
||||
formatDate,
|
||||
toTicket(){
|
||||
this.$router.push({path:'/ticket'})
|
||||
toTicket() {
|
||||
this.$router.push({path: '/ticket'})
|
||||
},
|
||||
//生成工单
|
||||
createTicket(id) {
|
||||
this.loading = true
|
||||
createTicketNo({id}).then(response => {
|
||||
response?.data && this.toTicket()
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
//确认关闭告警
|
||||
closeAlarmRecord(id) {
|
||||
this.$confirm('确认关闭该故障告警吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.loading = true
|
||||
closeAlarm({id}).then(() => {
|
||||
this.$message.success('关闭成功')
|
||||
this.getData()
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
// 判断是否是同一天
|
||||
isSameDay(day1, day2) {
|
||||
const date1 = new Date(day1),date2 = new Date(day2)
|
||||
const date1 = new Date(day1), date2 = new Date(day2)
|
||||
return date1.getFullYear() === date2.getFullYear() &&
|
||||
date1.getMonth() === date2.getMonth() &&
|
||||
date1.getDate() === date2.getDate();
|
||||
date1.getMonth() === date2.getMonth() &&
|
||||
date1.getDate() === date2.getDate();
|
||||
},
|
||||
// 分页
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
this.$nextTick(()=>{
|
||||
this.$nextTick(() => {
|
||||
this.getData()
|
||||
})
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val
|
||||
this.$nextTick(()=>{
|
||||
this.$nextTick(() => {
|
||||
this.getData()
|
||||
})
|
||||
},
|
||||
// 搜索
|
||||
onSearch(){
|
||||
this.pageNum =1//每次搜索从1开始搜索
|
||||
const [alarmStartTime='',alarmEndTime='']=(this.dateRange || [])
|
||||
// 选中了时间范围
|
||||
if(alarmStartTime && alarmStartTime){
|
||||
// 如果选择的时间范围是今天
|
||||
if(this.isSameDay(alarmStartTime,alarmEndTime) && this.isSameDay(alarmStartTime,new Date())){
|
||||
this.activeBtn = 'today'
|
||||
}else {
|
||||
this.activeBtn = 'history'
|
||||
}
|
||||
}else{
|
||||
//没有选择时间范围 还是按照选中的今日告警、历史告警查询
|
||||
}
|
||||
onSearch() {
|
||||
this.pageNum = 1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
// 重置
|
||||
onReset(){
|
||||
this.search={deviceId:'',alarmLevel:''}
|
||||
this.dateRange=[]
|
||||
this.pageNum =1//每次搜索从1开始搜索
|
||||
onReset() {
|
||||
this.search = {deviceId: '', alarmLevel: ''}
|
||||
this.dateRange = []
|
||||
this.pageNum = 1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
// 切换今日、历史告警
|
||||
changeDataType(id){
|
||||
if(id !== this.activeBtn){
|
||||
changeDataType(id) {
|
||||
if (id !== this.activeBtn) {
|
||||
console.log('点击了不同的菜单,更新数据')
|
||||
this.activeBtn=id;
|
||||
const [alarmStartTime,alarmEndTime]=(this.dateRange || [])
|
||||
// 切换到今日告警,如果已经选择了时间范围清空
|
||||
if(alarmStartTime && alarmEndTime){
|
||||
// 如果切换到了今日告警,时间范围不相等或者相等但是不是今天 清空时间选择范围
|
||||
if(id === 'today' && !this.isSameDay(alarmStartTime,alarmEndTime) || (this.isSameDay(alarmStartTime,alarmEndTime) && !this.isSameDay(alarmStartTime,new Date()))){
|
||||
this.dateRange = []
|
||||
}else if(id === 'history' && this.isSameDay(alarmStartTime,alarmEndTime) && this.isSameDay(alarmStartTime,new Date())){
|
||||
// 切换成历史告警,但是选择时间范围是当天,清空时间范围
|
||||
this.dateRange = []
|
||||
}
|
||||
}
|
||||
this.activeBtn = id;
|
||||
this.getData()
|
||||
}
|
||||
},
|
||||
// 获取数据
|
||||
getData(){
|
||||
this.loading=true
|
||||
const {deviceId,alarmLevel} = this.search
|
||||
const {siteId,pageNum,pageSize,activeBtn} =this
|
||||
const [alarmStartTime='',alarmEndTime='']=(this.dateRange || [])
|
||||
let start='',end = '',now =new Date()
|
||||
if(activeBtn === 'today'){
|
||||
start = end = now
|
||||
}else{
|
||||
if(alarmStartTime && alarmEndTime){
|
||||
start = alarmStartTime
|
||||
end = alarmEndTime
|
||||
}else{
|
||||
start=''
|
||||
end = ''
|
||||
// now
|
||||
// end.setDate(end.getDate() - 1);
|
||||
}
|
||||
}
|
||||
getAlarmDetailList({deviceId,alarmLevel,siteId,pageSize,pageNum,alarmStartTime:formatDate(start),alarmEndTime:formatDate(end)}).then(response => {
|
||||
this.tableData=response?.rows || [];
|
||||
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})
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
getDeviceOptions(){
|
||||
getDeviceOptions() {
|
||||
getDeviceList(this.siteId).then(response => {
|
||||
this.deviceOptions = JSON.parse(JSON.stringify(response?.data || []))
|
||||
})
|
||||
},
|
||||
init(){
|
||||
init() {
|
||||
this.getDeviceOptions()
|
||||
this.onReset()
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
mounted() {
|
||||
const now = new Date();
|
||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
this.defaultDateRange = [lastMonth, now];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,29 +1,42 @@
|
||||
|
||||
<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" @updateDate="updateDate"/>
|
||||
</div>
|
||||
<div style="height: 310px" id="activeChart"></div>
|
||||
</el-card>
|
||||
<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 {getPcsNameList, getPowerData} from '@/api/ems/dzjk'
|
||||
import {getPointConfigCurve} from '@/api/ems/site'
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
mixins: [resize, intervalUpdate],
|
||||
components: {DateRangeSelect},
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
timeRange:[],
|
||||
siteId:'',
|
||||
deviceId:''
|
||||
timeRange: [],
|
||||
siteId: '',
|
||||
isInit: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
displayData() {
|
||||
if (this.siteId && this.timeRange.length === 2) {
|
||||
this.getGVQXData()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -40,78 +53,99 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
// 更新时间范围 重置图表
|
||||
updateDate(data){
|
||||
this.timeRange=data
|
||||
this.getGVQXData()
|
||||
updateDate(data) {
|
||||
this.timeRange = data
|
||||
!this.isInit && this.getGVQXData()
|
||||
this.isInit = false
|
||||
},
|
||||
getGVQXData(){
|
||||
this.showLoading()
|
||||
const {siteId,deviceId,timeRange}=this
|
||||
if(!deviceId) return this.hideLoading()
|
||||
getPowerData({siteId,deviceId,startDate:timeRange[0],endDate:timeRange[1],dataType:'1'}).then(response => {
|
||||
this.setOption(response?.data || [])
|
||||
}).finally(()=>this.hideLoading())
|
||||
getGVQXData() {
|
||||
const {siteId, timeRange} = this
|
||||
const displayData = this.displayData || []
|
||||
const sectionRows = displayData.filter(item =>
|
||||
item && item.sectionName === '当日功率曲线' && item.useFixedDisplay !== 1 && item.dataPoint
|
||||
)
|
||||
const tasks = sectionRows.map(row => {
|
||||
const pointId = String(row.dataPoint || '').trim()
|
||||
if (!pointId) return Promise.resolve(null)
|
||||
return getPointConfigCurve({
|
||||
siteId,
|
||||
pointId,
|
||||
pointType: 'data',
|
||||
rangeType: 'custom',
|
||||
startTime: this.normalizeDateTime(timeRange[0], false),
|
||||
endTime: this.normalizeDateTime(timeRange[1], true)
|
||||
}).then(curveResponse => {
|
||||
const list = curveResponse?.data || []
|
||||
return {
|
||||
name: row.fieldName || row.fieldCode || pointId,
|
||||
data: list
|
||||
.map(item => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
|
||||
.filter(item => item[0] && !Number.isNaN(item[1]))
|
||||
}
|
||||
}).catch(() => null)
|
||||
})
|
||||
Promise.all(tasks).then(series => {
|
||||
this.setOption((series || []).filter(Boolean))
|
||||
})
|
||||
},
|
||||
init(siteId){
|
||||
init(siteId) {
|
||||
//初始化 清空数据
|
||||
this.siteId = siteId
|
||||
this.timeRange=[]
|
||||
this.deviceId=''
|
||||
this.$refs.dateRangeSelect.init()
|
||||
this.showLoading()
|
||||
getPcsNameList(siteId).then(response=>{
|
||||
const data=response?.data || [];
|
||||
if(data.length>0){
|
||||
this.deviceId=data[0].id
|
||||
//接口调用完成之后 设置图表、结束loading
|
||||
this.getGVQXData()
|
||||
}else{
|
||||
this.hideLoading()
|
||||
}
|
||||
})
|
||||
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()
|
||||
normalizeDateTime(value, endOfDay) {
|
||||
const raw = String(value || '').trim()
|
||||
if (!raw) return ''
|
||||
if (raw.includes(' ')) return raw
|
||||
return `${raw} ${endOfDay ? '23:59:59' : '00:00:00'}`
|
||||
},
|
||||
hideLoading(){
|
||||
this.chart && this.chart.hideLoading()
|
||||
parseToTimestamp(value) {
|
||||
if (!value) return null
|
||||
const t = new Date(value).getTime()
|
||||
return Number.isNaN(t) ? null : t
|
||||
},
|
||||
setOption(data) {
|
||||
const source = [['日期','电网功率']]
|
||||
this.chart && data.forEach((item)=>{
|
||||
source.push([item.statisDate,item.gridPower])
|
||||
})
|
||||
setOption(seriesData = []) {
|
||||
this.chart.setOption({
|
||||
color:['#FFBD00','#3C81FF'],
|
||||
grid: {
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '15',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
axisPointer: { type: 'cross' }
|
||||
},
|
||||
textStyle:{
|
||||
color:"#333333",
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
type: 'time',
|
||||
},
|
||||
yAxis: {
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
},
|
||||
dataset:{source},
|
||||
series: [
|
||||
{
|
||||
name:'电网功率',
|
||||
}],
|
||||
series: seriesData.map((item) => {
|
||||
return {
|
||||
name: item.name,
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
symbolSize: 2,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
data: item.data
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@ -119,4 +153,3 @@ export default {
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding">
|
||||
<!-- <el-card shadow="always" class="common-card-container common-card-container-body-no-padding">
|
||||
<div slot="header">
|
||||
<span class="card-title">当前报警</span>
|
||||
</div>
|
||||
@ -39,42 +39,48 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
</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 []
|
||||
}
|
||||
}
|
||||
export default {
|
||||
props: {
|
||||
tableData: {
|
||||
require: true,
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
toAlarm() {
|
||||
this.$router.push({ path: "/dzjk/gzgj", query: this.$route.query });
|
||||
},
|
||||
methods:{
|
||||
toTicket(){
|
||||
this.$router.push({path:'/ticket'})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 ;
|
||||
}
|
||||
//实时告警
|
||||
.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>
|
||||
}
|
||||
</style>
|
||||
|
||||
82
src/views/ems/dzjk/home/ClInfo.vue
Normal 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>
|
||||
@ -1,148 +0,0 @@
|
||||
|
||||
<template>
|
||||
<el-row style="background:#fff;margin-top:30px;">
|
||||
<el-col :xs="24" :sm="24" :lg="24">
|
||||
<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: 310px" id="nllzChart"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import resize from '@/mixins/ems/resize'
|
||||
import {formatNumber} from "@/filters/ems";
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.querySelector('#nllzChart'))
|
||||
},
|
||||
setOption(data) {
|
||||
const {siteMonitorDataVo=[],gridNrtPower,loadNrtPower,energyStorageNrtPower,energyStorageAvailElec} = data
|
||||
const source = [['日期','充电量','放电量']]
|
||||
siteMonitorDataVo.forEach(item=>{
|
||||
source.push([item.ammeterDate, item.chargedCap,item.disChargedCap])
|
||||
})
|
||||
this.chart.setOption({
|
||||
title: [
|
||||
// 右上角
|
||||
{
|
||||
text: `电网 实时功率:${formatNumber(gridNrtPower)} kW`,
|
||||
top: 10,
|
||||
right: 10,
|
||||
textStyle:{
|
||||
fontSize:12,
|
||||
color:'#666666'
|
||||
}
|
||||
},
|
||||
// 右下角
|
||||
{
|
||||
text: `负载 实时功率:${formatNumber(loadNrtPower)} kW`,
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
textStyle:{
|
||||
fontSize:12,
|
||||
color:'#666666'
|
||||
}
|
||||
},
|
||||
// 左下角第一行
|
||||
{
|
||||
text: '储能',
|
||||
bottom: 40,
|
||||
left: 10,
|
||||
textStyle:{
|
||||
fontSize:12,
|
||||
color:'#666666'
|
||||
}
|
||||
},
|
||||
// 左下角第二行
|
||||
{
|
||||
text: `实时功率:${formatNumber(energyStorageNrtPower)} kW`,
|
||||
bottom: 26,
|
||||
left: 10,
|
||||
textStyle:{
|
||||
fontSize:12,
|
||||
color:'#666666'
|
||||
}
|
||||
},
|
||||
// 左下角第三行
|
||||
{
|
||||
text: `可用电量:${formatNumber(energyStorageAvailElec)} kWh`,
|
||||
bottom: 10,
|
||||
left: 10,
|
||||
textStyle:{
|
||||
fontSize:12,
|
||||
color:'#666666'
|
||||
}
|
||||
}
|
||||
],
|
||||
grid:{
|
||||
left:200
|
||||
},
|
||||
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
|
||||
// source: [['日期','充电量','放电量']]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name:'充电量',
|
||||
type: 'line',
|
||||
},{
|
||||
name:'放电量',
|
||||
type: 'line',
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@ -1,27 +1,40 @@
|
||||
|
||||
<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" @updateDate="updateDate"/>
|
||||
</div>
|
||||
<div style="height: 310px" id="weekChart"></div>
|
||||
</el-card>
|
||||
<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'
|
||||
import {getPointConfigCurve} from '@/api/ems/site'
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
components: {DateRangeSelect},
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
timeRange:[],
|
||||
siteId:'',
|
||||
timeRange: [],
|
||||
siteId: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
displayData() {
|
||||
if (this.siteId && this.timeRange.length === 2) {
|
||||
this.getWeekKData()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -38,79 +51,94 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
// 更新时间范围 重置图表
|
||||
updateDate(data){
|
||||
this.timeRange=data
|
||||
updateDate(data) {
|
||||
this.timeRange = data
|
||||
this.getWeekKData()
|
||||
},
|
||||
getWeekKData(){
|
||||
this.showLoading()
|
||||
const {siteId,timeRange}=this
|
||||
this.hideLoading()
|
||||
getSevenChargeData({siteId,startDate:timeRange[0],endDate:timeRange[1]}).then(response => {
|
||||
this.setOption(response?.data || [])
|
||||
}).finally(()=>this.hideLoading())
|
||||
getWeekKData() {
|
||||
const {siteId, timeRange} = this
|
||||
const displayData = this.displayData || []
|
||||
const sectionRows = displayData.filter(item =>
|
||||
item && item.sectionName === '一周充放曲线' && item.useFixedDisplay !== 1 && item.dataPoint
|
||||
)
|
||||
const tasks = sectionRows.map(row => {
|
||||
const pointId = String(row.dataPoint || '').trim()
|
||||
if (!pointId) return Promise.resolve(null)
|
||||
return getPointConfigCurve({
|
||||
siteId,
|
||||
pointId,
|
||||
pointType: 'data',
|
||||
rangeType: 'custom',
|
||||
startTime: this.normalizeDateTime(timeRange[0], false),
|
||||
endTime: this.normalizeDateTime(timeRange[1], true)
|
||||
}).then(curveResponse => {
|
||||
const list = curveResponse?.data || []
|
||||
return {
|
||||
name: row.fieldName || row.fieldCode || pointId,
|
||||
data: list
|
||||
.map(item => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
|
||||
.filter(item => item[0] && !Number.isNaN(item[1]))
|
||||
}
|
||||
}).catch(() => null)
|
||||
})
|
||||
Promise.all(tasks).then(series => {
|
||||
this.setOption((series || []).filter(Boolean))
|
||||
})
|
||||
},
|
||||
init(siteId){
|
||||
init(siteId) {
|
||||
//初始化 清空数据
|
||||
this.siteId = siteId
|
||||
this.timeRange=[]
|
||||
this.deviceId=''
|
||||
this.timeRange = []
|
||||
this.deviceId = ''
|
||||
this.$refs.dateRangeSelect.init()
|
||||
},
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.querySelector('#weekChart'))
|
||||
},
|
||||
showLoading(){
|
||||
this.chart && this.chart.showLoading()
|
||||
normalizeDateTime(value, endOfDay) {
|
||||
const raw = String(value || '').trim()
|
||||
if (!raw) return ''
|
||||
if (raw.includes(' ')) return raw
|
||||
return `${raw} ${endOfDay ? '23:59:59' : '00:00:00'}`
|
||||
},
|
||||
hideLoading(){
|
||||
this.chart && this.chart.hideLoading()
|
||||
parseToTimestamp(value) {
|
||||
if (!value) return null
|
||||
const t = new Date(value).getTime()
|
||||
return Number.isNaN(t) ? null : t
|
||||
},
|
||||
setOption(data,unit) {
|
||||
const source = [['日期','充电量','放电量']]
|
||||
data.forEach(item=>{
|
||||
source.push([item.ammeterDate, item.chargedCap,item.disChargedCap])
|
||||
})
|
||||
setOption(seriesData = []) {
|
||||
this.chart && this.chart.setOption({
|
||||
color:['#FFBD00','#3C81FF','#05AEA3'],
|
||||
color: ['#4472c4', '#70ad47'],//所有充放电颜色保持统一
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
axisPointer: { type: 'cross' }
|
||||
},
|
||||
grid: {
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '15',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
name:unit,
|
||||
nameLocation:'center'
|
||||
type: 'time'
|
||||
},
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
name:'充电量/放电量kWh',
|
||||
name: '充电量/放电量kWh',
|
||||
axisLine: {
|
||||
lineStyle:{
|
||||
lineStyle: {
|
||||
color: '#333333',
|
||||
},
|
||||
onZero:false
|
||||
onZero: false
|
||||
}
|
||||
}],
|
||||
dataset:{
|
||||
source
|
||||
},
|
||||
series: [
|
||||
{
|
||||
yAxisIndex:0,
|
||||
type: 'line',
|
||||
},
|
||||
{
|
||||
yAxisIndex:0,
|
||||
type: 'line',
|
||||
},
|
||||
]
|
||||
series: seriesData.map(item => ({
|
||||
name: item.name,
|
||||
yAxisIndex: 0,
|
||||
type: 'bar',
|
||||
data: item.data
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,179 +1,707 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<el-row style="background:#fff;" class="row-container" :gutter="15">
|
||||
<el-col v-if="tableData.length>0" :xs="24" :sm="24" :lg="24">
|
||||
<alarm-table :tableData="tableData"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="6">
|
||||
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding">
|
||||
<div>
|
||||
<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>
|
||||
<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: 250px;padding:20px 15px;" >
|
||||
<el-descriptions class="single-zd-info-container" :column="1" >
|
||||
<el-descriptions-item size="mini" v-for="(item,index) in singleZdInfo" :key="index+'singleZdInfo'" :label="item.title">{{info[item.attr] | formatNumber }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="8">
|
||||
<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="box-sizing: border-box; height: 250px;padding:20px 15px;" >
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" v-for="(item,index) in sjglData" :key="index+'sjglData'" class="sjgl-data">
|
||||
<div class="sjgl-title">{{item.title}}</div>
|
||||
<div class="sjgl-value">{{runningInfo[item.attr] | formatNumber}}</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">
|
||||
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
|
||||
<span v-else>{{ info.siteAddress || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="site-info">
|
||||
<div class="title">
|
||||
<i class="el-icon-date"></i>
|
||||
</div>
|
||||
<div class="value">
|
||||
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
|
||||
<span v-else>{{ info.runningTime || '-' }}</span>
|
||||
</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">装机功率(MWh)</div>
|
||||
<div class="sjgl-value">
|
||||
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
|
||||
<span v-else>{{ info.installPower | formatNumber }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
class="sjgl-col power-col"
|
||||
>
|
||||
<div class="sjgl-wrapper">
|
||||
<div class="sjgl-title">装机容量(MWh)</div>
|
||||
<div class="sjgl-value">
|
||||
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
|
||||
<span v-else>{{ info.installCapacity | formatNumber }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="10">
|
||||
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding">
|
||||
<!-- 总累计运行数据-->
|
||||
<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>
|
||||
<span class="card-title">总累计运行数据</span>
|
||||
<div class="total-count">
|
||||
<span class="title pointer-field" @click="handleTotalRevenueClick">总收入</span>
|
||||
<span
|
||||
class="value pointer-field"
|
||||
:class="{ 'field-disabled': !hasPointId(totalRevenueDisplayItem) }"
|
||||
@click="handleTotalRevenueClick"
|
||||
>
|
||||
<i v-if="isRunningInfoLoading" class="el-icon-loading"></i>
|
||||
<span v-else>{{ totalRevenueDisplayValue | formatNumber }}</span>
|
||||
</span>
|
||||
<span class="unit">元</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="box-sizing: border-box; height: 250px;padding:20px 15px;" >
|
||||
|
||||
<div
|
||||
style="box-sizing: border-box; height: 218px; padding: 20px 15px"
|
||||
>
|
||||
<el-row :gutter="10">
|
||||
<el-col
|
||||
:span="6"
|
||||
v-for="(item, index) in runningDataCards"
|
||||
:key="index + 'sjglData'"
|
||||
class="sjgl-col"
|
||||
>
|
||||
<div
|
||||
class="sjgl-wrapper pointer-field"
|
||||
:class="{ 'field-disabled': !hasPointId(item.raw) }"
|
||||
@click="handleRunningFieldClick(item)"
|
||||
>
|
||||
<div class="sjgl-title">{{ item.title }}</div>
|
||||
<div class="sjgl-value" :style="{color:item.color}">
|
||||
<i v-if="item.loading" class="el-icon-loading"></i>
|
||||
<span v-else>{{ item.value | formatNumber }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="24">
|
||||
<week-chart ref="weekChart"/>
|
||||
<el-col :xs="24" :sm="24" :lg="12">
|
||||
<week-chart ref="weekChart" :display-data="runningDisplayData"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="24">
|
||||
<active-chart ref="activeChart"/>
|
||||
<el-col :xs="24" :sm="24" :lg="12">
|
||||
<active-chart ref="activeChart" :display-data="runningDisplayData"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-dialog
|
||||
:visible.sync="curveDialogVisible"
|
||||
:title="curveDialogTitle"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
class="ems-dialog"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@opened="handleCurveDialogOpened"
|
||||
@closed="handleCurveDialogClosed"
|
||||
>
|
||||
<div class="curve-tools">
|
||||
<el-date-picker
|
||||
v-model="curveCustomRange"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
style="width: 440px"
|
||||
/>
|
||||
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
|
||||
</div>
|
||||
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getSingleSiteBaseInfo} from '@/api/ems/zddt'
|
||||
import {getDzjkHomeView} from '@/api/ems/dzjk'
|
||||
import * as echarts from "echarts";
|
||||
import {getSingleSiteBaseInfo} from "@/api/ems/zddt";
|
||||
import {getDzjkHomeTotalView, getProjectDisplayData} from "@/api/ems/dzjk";
|
||||
import {getPointConfigCurve} from "@/api/ems/site";
|
||||
import WeekChart from "./WeekChart.vue";
|
||||
import ActiveChart from "./ActiveChart.vue";
|
||||
import AlarmTable from "./AlarmTable.vue";
|
||||
import getQuerySiteId from '@/mixins/ems/getQuerySiteId'
|
||||
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},
|
||||
mixins: [getQuerySiteId],
|
||||
name: "DzjkSbjkHome",
|
||||
components: {WeekChart, ActiveChart, AlarmTable, ClInfo},
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
singleZdInfo:[{
|
||||
title:'电站位置',
|
||||
attr:'siteAddress'
|
||||
},{
|
||||
title:'投运时间',
|
||||
attr:'runningTime'
|
||||
},{
|
||||
title:'装机功率(MW)',
|
||||
attr:'installPower'
|
||||
},{
|
||||
title:'装机容量(MW)',
|
||||
attr:'installCapacity',
|
||||
}],
|
||||
sjglData:[{
|
||||
title:'今日充电量(MWh)',
|
||||
attr:'dayChargedCap'
|
||||
},{
|
||||
title:'今日放电量(MWh)',
|
||||
attr:'dayDisChargedCap'
|
||||
},{
|
||||
title:'总充电量(MWh)',
|
||||
attr:'totalChargedCap'
|
||||
},{
|
||||
title:'总放电量(MWh)',
|
||||
attr:'totalDischargedCap'
|
||||
}],
|
||||
info:{},//基本信息
|
||||
runningInfo:{},//总累计运行数据+报警表格
|
||||
loading: false,
|
||||
baseInfoLoading: false,
|
||||
runningInfoLoading: false,
|
||||
runningUpdateSpinning: false,
|
||||
runningUpdateTimer: null,
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
fallbackSjglData: [
|
||||
{
|
||||
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: {}, //总累计运行数据+报警表格
|
||||
runningDisplayData: [], //单站监控项目配置展示数据
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isBaseInfoLoading() {
|
||||
return false;
|
||||
},
|
||||
isRunningInfoLoading() {
|
||||
const state = this.$data || {};
|
||||
return !!(state.runningInfoLoading || state.runningUpdateSpinning || state.loading);
|
||||
},
|
||||
tableData() {
|
||||
return this.runningInfo?.siteMonitorHomeAlarmVo || [];
|
||||
},
|
||||
totalRunningSectionData() {
|
||||
return (this.runningDisplayData || []).filter(item => item.sectionName === "总累计运行数据");
|
||||
},
|
||||
totalRevenueDisplayItem() {
|
||||
const sectionData = this.totalRunningSectionData || [];
|
||||
const byFieldCode = sectionData.find(item => item.fieldCode === "totalRevenue");
|
||||
if (byFieldCode) {
|
||||
return byFieldCode;
|
||||
}
|
||||
return sectionData.find(item => item.fieldName === "总收入");
|
||||
},
|
||||
totalRevenueDisplayValue() {
|
||||
return this.totalRevenueDisplayItem ? this.totalRevenueDisplayItem.fieldValue : this.runningInfo.totalRevenue;
|
||||
},
|
||||
runningDataCards() {
|
||||
const sectionData = this.totalRunningSectionData || [];
|
||||
if (sectionData.length > 0) {
|
||||
const revenueFieldCode = this.totalRevenueDisplayItem ? this.totalRevenueDisplayItem.fieldCode : "";
|
||||
return sectionData
|
||||
.filter(item => item.fieldCode !== revenueFieldCode)
|
||||
.map((item, index) => ({
|
||||
title: item.fieldName,
|
||||
value: item.fieldValue,
|
||||
color: this.getCardColor(index),
|
||||
loading: this.isRunningInfoLoading,
|
||||
raw: item,
|
||||
}));
|
||||
}
|
||||
return this.fallbackSjglData.map(item => ({
|
||||
title: item.title,
|
||||
value: this.runningInfo[item.attr],
|
||||
color: item.color,
|
||||
loading: this.isRunningInfoLoading,
|
||||
raw: item,
|
||||
}));
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.runningUpdateTimer) {
|
||||
clearTimeout(this.runningUpdateTimer);
|
||||
this.runningUpdateTimer = null;
|
||||
}
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
tableData(){
|
||||
console.log('this.runningInfo?.siteMonitorHomeAlarmVo ',this.runningInfo?.siteMonitorHomeAlarmVo )
|
||||
return this.runningInfo?.siteMonitorHomeAlarmVo || []
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
getBaseInfo(){
|
||||
return getSingleSiteBaseInfo(this.siteId).then(response => {
|
||||
this.info = response?.data || {}
|
||||
})
|
||||
methods: {
|
||||
hasPointId(item) {
|
||||
return !!String(item?.dataPoint || "").trim();
|
||||
},
|
||||
getRunningInfo(){
|
||||
return getDzjkHomeView(this.siteId).then(response => {
|
||||
this.runningInfo = response?.data || {}
|
||||
})
|
||||
handleTotalRevenueClick() {
|
||||
const item = this.totalRevenueDisplayItem;
|
||||
const pointId = String(item?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
this.openCurveDialog({
|
||||
pointId,
|
||||
title: item?.fieldName || "总收入",
|
||||
});
|
||||
},
|
||||
init(){
|
||||
this.loading = true
|
||||
handleRunningFieldClick(card) {
|
||||
const item = card?.raw || {};
|
||||
const pointId = String(item?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
this.openCurveDialog({
|
||||
pointId,
|
||||
title: card?.title || item?.fieldName || item?.fieldCode || pointId,
|
||||
});
|
||||
},
|
||||
openCurveDialog({pointId, title}) {
|
||||
const range = this.getDefaultCurveRange();
|
||||
this.curveCustomRange = range;
|
||||
this.curveDialogTitle = `点位曲线 - ${title || pointId}`;
|
||||
this.curveQuery = {
|
||||
siteId: this.siteId,
|
||||
pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: range[0],
|
||||
endTime: range[1],
|
||||
};
|
||||
this.curveDialogVisible = true;
|
||||
},
|
||||
handleCurveDialogOpened() {
|
||||
if (!this.curveChart && this.$refs.curveChartRef) {
|
||||
this.curveChart = echarts.init(this.$refs.curveChartRef);
|
||||
}
|
||||
this.loadCurveData();
|
||||
},
|
||||
handleCurveDialogClosed() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
this.curveLoading = false;
|
||||
},
|
||||
getDefaultCurveRange() {
|
||||
const end = new Date();
|
||||
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return [this.formatDateTime(start), this.formatDateTime(end)];
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const d = new Date(date);
|
||||
const pad = (num) => String(num).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
},
|
||||
formatCurveTime(value) {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return "";
|
||||
}
|
||||
const raw = String(value).trim();
|
||||
const normalized = raw
|
||||
.replace("T", " ")
|
||||
.replace(/\.\d+/, "")
|
||||
.replace(/Z$/, "")
|
||||
.replace(/([+-]\d{2}:?\d{2})$/, "")
|
||||
.trim();
|
||||
const matched = normalized.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
|
||||
if (matched) {
|
||||
return `${matched[1]} ${matched[2]}`;
|
||||
}
|
||||
return normalized.slice(0, 16);
|
||||
},
|
||||
loadCurveData() {
|
||||
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
|
||||
this.$message.warning("点位信息不完整,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
if (!this.curveCustomRange || this.curveCustomRange.length !== 2) {
|
||||
this.$message.warning("请选择查询时间范围");
|
||||
return;
|
||||
}
|
||||
this.curveQuery.startTime = this.curveCustomRange[0];
|
||||
this.curveQuery.endTime = this.curveCustomRange[1];
|
||||
const query = {
|
||||
siteId: this.curveQuery.siteId,
|
||||
pointId: this.curveQuery.pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: this.curveQuery.startTime,
|
||||
endTime: this.curveQuery.endTime,
|
||||
};
|
||||
this.curveLoading = true;
|
||||
getPointConfigCurve(query).then((response) => {
|
||||
const rows = response?.data || [];
|
||||
this.renderCurveChart(rows);
|
||||
}).catch(() => {
|
||||
this.renderCurveChart([]);
|
||||
}).finally(() => {
|
||||
this.curveLoading = false;
|
||||
});
|
||||
},
|
||||
renderCurveChart(rows = []) {
|
||||
if (!this.curveChart) return;
|
||||
const xData = rows.map(item => this.formatCurveTime(item.dataTime));
|
||||
const yData = rows.map(item => item.pointValue);
|
||||
this.curveChart.clear();
|
||||
this.curveChart.setOption({
|
||||
legend: {},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.curveDialogTitle,
|
||||
type: "line",
|
||||
data: yData,
|
||||
connectNulls: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!rows.length) {
|
||||
this.$message.warning("当前时间范围暂无曲线数据");
|
||||
}
|
||||
},
|
||||
setBaseInfoLoading(loading) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.$data, "baseInfoLoading")) {
|
||||
this.baseInfoLoading = loading;
|
||||
return;
|
||||
}
|
||||
this.$set(this.$data, "baseInfoLoading", loading);
|
||||
},
|
||||
setRunningInfoLoading(loading) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.$data, "runningInfoLoading")) {
|
||||
this.runningInfoLoading = loading;
|
||||
return;
|
||||
}
|
||||
this.$set(this.$data, "runningInfoLoading", loading);
|
||||
},
|
||||
getCardColor(index) {
|
||||
const colors = ['#4472c4', '#70ad47', '#4472c4', '#f67438', '#4472c4', '#70ad47', '#70ad47', '#f67438'];
|
||||
return colors[index % colors.length];
|
||||
},
|
||||
toAlarm() {
|
||||
this.$router.push({path: "/dzjk/gzgj", query: this.$route.query});
|
||||
},
|
||||
getBaseInfo() {
|
||||
return getSingleSiteBaseInfo(this.siteId).then((response) => {
|
||||
this.info = response?.data || {};
|
||||
});
|
||||
},
|
||||
getRunningInfo() {
|
||||
const hasOldData = Object.keys(this.runningInfo || {}).length > 0 || (this.runningDisplayData || []).length > 0;
|
||||
if (!hasOldData) {
|
||||
this.setRunningInfoLoading(true);
|
||||
}
|
||||
return Promise.all([
|
||||
getDzjkHomeTotalView(this.siteId),
|
||||
getProjectDisplayData(this.siteId),
|
||||
]).then(([homeResponse, displayResponse]) => {
|
||||
const nextRunningInfo = homeResponse?.data || {};
|
||||
const nextRunningDisplayData = displayResponse?.data || [];
|
||||
const changed = hasOldData && this.hasTotalRunningChanged(nextRunningInfo, nextRunningDisplayData);
|
||||
this.runningInfo = nextRunningInfo;
|
||||
this.runningDisplayData = nextRunningDisplayData;
|
||||
if (changed) {
|
||||
this.triggerRunningUpdateSpinner();
|
||||
}
|
||||
}).finally(() => {
|
||||
if (!hasOldData) {
|
||||
this.setRunningInfoLoading(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
triggerRunningUpdateSpinner() {
|
||||
if (this.runningUpdateTimer) {
|
||||
clearTimeout(this.runningUpdateTimer);
|
||||
}
|
||||
this.runningUpdateSpinning = true;
|
||||
this.runningUpdateTimer = setTimeout(() => {
|
||||
this.runningUpdateSpinning = false;
|
||||
this.runningUpdateTimer = null;
|
||||
}, 800);
|
||||
},
|
||||
hasTotalRunningChanged(nextRunningInfo, nextRunningDisplayData) {
|
||||
const oldSnapshot = this.getTotalRunningSnapshot(this.runningInfo, this.runningDisplayData);
|
||||
const newSnapshot = this.getTotalRunningSnapshot(nextRunningInfo, nextRunningDisplayData);
|
||||
return JSON.stringify(oldSnapshot) !== JSON.stringify(newSnapshot);
|
||||
},
|
||||
getTotalRunningSnapshot(runningInfo, runningDisplayData) {
|
||||
const snapshot = {};
|
||||
const sectionData = (runningDisplayData || []).filter(item => item.sectionName === "总累计运行数据");
|
||||
if (sectionData.length > 0) {
|
||||
sectionData.forEach(item => {
|
||||
const key = item.fieldCode || item.fieldName;
|
||||
if (!key) return;
|
||||
snapshot[`cfg:${key}`] = this.normalizeRunningCompareValue(item.fieldValue);
|
||||
});
|
||||
return snapshot;
|
||||
}
|
||||
this.fallbackSjglData.forEach(item => {
|
||||
snapshot[`fallback:${item.attr}`] = this.normalizeRunningCompareValue(runningInfo?.[item.attr]);
|
||||
});
|
||||
snapshot["fallback:totalRevenue"] = this.normalizeRunningCompareValue(runningInfo?.totalRevenue);
|
||||
return snapshot;
|
||||
},
|
||||
normalizeRunningCompareValue(value) {
|
||||
if (value === null || value === undefined) return "";
|
||||
if (typeof value === "number") return Number.isNaN(value) ? "" : value;
|
||||
const text = String(value).trim();
|
||||
if (text === "") return "";
|
||||
const num = Number(text);
|
||||
return Number.isNaN(num) ? text : num;
|
||||
},
|
||||
init() {
|
||||
// 功率曲线
|
||||
this.$refs.activeChart.init(this.siteId)
|
||||
this.$refs.activeChart.init(this.siteId);
|
||||
// 一周冲放曲线
|
||||
this.$refs.weekChart.init(this.siteId)
|
||||
this.$refs.weekChart.init(this.siteId);
|
||||
// 静态信息 this.getBaseInfo()
|
||||
// 总累计运行数据+故障告警 this.getRunningInfo()
|
||||
Promise.all([this.getBaseInfo(),this.getRunningInfo()]).finally(()=>{
|
||||
this.loading = false
|
||||
})
|
||||
|
||||
}
|
||||
Promise.all([this.getBaseInfo(), this.getRunningInfo()]);
|
||||
// 一分钟循环一次总累计运行数据
|
||||
this.updateInterval(this.getRunningInfo);
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.row-container{
|
||||
&>.el-col{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
//数据概览
|
||||
.sjgl-data{
|
||||
text-align: center;
|
||||
&:nth-child(1),&:nth-child(2){
|
||||
margin-bottom:25px;
|
||||
}
|
||||
.sjgl-title{
|
||||
color: #666666;
|
||||
line-height: 14px;
|
||||
}
|
||||
.sjgl-value{
|
||||
color: rgba(51,51,51,1);
|
||||
font-size: 26px;
|
||||
line-height: 26px;
|
||||
font-weight: 500;
|
||||
margin-top: 14px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
//设备告警
|
||||
.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;
|
||||
}
|
||||
|
||||
</style>
|
||||
.title {
|
||||
color: #1791ff;
|
||||
font-size: 18px;
|
||||
line-height: 20px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
<style lang="scss">
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.pointer-field {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.field-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.curve-tools {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.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>
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
<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"
|
||||
@ -11,10 +10,10 @@
|
||||
active-text-color="#ffffff"
|
||||
mode="horizontal"
|
||||
>
|
||||
<el-menu-item :index="item.name" v-for="(item,index) in childrenRoute" :key="index+'dzjkChildrenRoute'">
|
||||
<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 :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">
|
||||
@ -28,25 +27,18 @@
|
||||
<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:''
|
||||
}
|
||||
},
|
||||
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,'页面地址不发生改变')
|
||||
}
|
||||
}
|
||||
computed:{
|
||||
...mapState({
|
||||
dzjkAlarmLighting:state=>state.ems.dzjkAlarmLighting
|
||||
})
|
||||
},
|
||||
beforeRouteLeave(to,from, next){
|
||||
//从单站监控下面的所有子页面跳出时会触发
|
||||
@ -54,15 +46,30 @@ export default {
|
||||
this.$store.commit('SET_ZD_LIST',[])
|
||||
next()
|
||||
},
|
||||
mounted() {
|
||||
console.log('单站监控一级页面路由',this.$route)
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ems-dashboard-editor-container{
|
||||
padding-top: 12px;
|
||||
}
|
||||
.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>
|
||||
|
||||
420
src/views/ems/dzjk/sbjk/PointChart.vue
Normal 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>
|
||||
@ -2,8 +2,14 @@
|
||||
<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 :xs="12" :sm="8" :lg="4" style="margin-bottom: 10px;" class="single-square-box-container" v-for="(item,index) in displaySquares" :key="index+'singleSquareBox'">
|
||||
<div
|
||||
class="square-click-wrapper"
|
||||
:class="{ 'field-disabled': !item.pointId }"
|
||||
@click="handleSquareClick(item)"
|
||||
>
|
||||
<single-square-box :data="{...item,value:item.value,loading:item.valueLoading}" ></single-square-box>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
@ -11,52 +17,74 @@
|
||||
|
||||
<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 {}}
|
||||
displayData: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
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'
|
||||
}]
|
||||
}
|
||||
computed: {
|
||||
displaySquares() {
|
||||
const sourceList = (this.displayData || []).filter((item) => {
|
||||
if (!item) return false;
|
||||
return item.menuCode === "SBJK_SSYX" || item.sectionName === "运行概览";
|
||||
});
|
||||
const sourceMap = {};
|
||||
sourceList.forEach((item) => {
|
||||
if (!item) return;
|
||||
const key = this.getFieldName(item.fieldCode);
|
||||
if (key) {
|
||||
sourceMap[key] = item;
|
||||
}
|
||||
});
|
||||
const defaults = [
|
||||
{fieldCode: "totalActivePower", fieldName: "实时有功功率(kW)"},
|
||||
{fieldCode: "totalReactivePower", fieldName: "实时无功功率(kVar)"},
|
||||
{fieldCode: "soc", fieldName: "电池堆SOC"},
|
||||
{fieldCode: "soh", fieldName: "电池堆SOH"},
|
||||
{fieldCode: "dayChargedCap_rt", fieldName: "今日充电量(kWh)"},
|
||||
{fieldCode: "dayDisChargedCap_rt", fieldName: "今日放电量(kWh)"},
|
||||
];
|
||||
return defaults.map((def, index) => {
|
||||
const row = sourceMap[def.fieldCode] || {};
|
||||
const pointId = String(row.dataPoint || "").trim();
|
||||
return {
|
||||
title: row.fieldName || def.fieldName,
|
||||
value: row.fieldValue,
|
||||
valueLoading: this.loading && this.isEmptyValue(row.fieldValue),
|
||||
bgColor: this.getBgColor(index),
|
||||
pointId,
|
||||
fieldCode: row.fieldCode || def.fieldCode,
|
||||
raw: row,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleSquareClick(item) {
|
||||
this.$emit("field-click", item || {});
|
||||
},
|
||||
getFieldName(fieldCode) {
|
||||
const raw = String(fieldCode || "").trim();
|
||||
if (!raw) return "";
|
||||
const index = raw.lastIndexOf("__");
|
||||
return index >= 0 ? raw.slice(index + 2) : raw;
|
||||
},
|
||||
getBgColor(index) {
|
||||
const bgColors = ['#FFF2CB', '#CBD6FF', '#DCCBFF', '#FFD4CB', '#FFD6F8', '#E1FFCA'];
|
||||
return bgColors[index % bgColors.length];
|
||||
},
|
||||
isEmptyValue(value) {
|
||||
return value === undefined || value === null || value === "" || value === "-";
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
@ -70,4 +98,13 @@ export default {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
.square-click-wrapper {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.square-click-wrapper.field-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,137 +1,660 @@
|
||||
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div v-for="(baseInfo,index) in baseInfoList" :key="index+'bmsdccContainer'" style="margin-bottom:25px;">
|
||||
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding common-card-container-no-title-bg">
|
||||
<div>
|
||||
<div class="pcs-tags">
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="selectedClusterId ? 'info' : 'primary'"
|
||||
:effect="selectedClusterId ? 'plain' : 'dark'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick('')"
|
||||
>
|
||||
全部
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-for="(item, index) in clusterDeviceList"
|
||||
:key="index + 'clusterTag'"
|
||||
size="small"
|
||||
:type="selectedClusterId === (item.deviceId || item.id) ? 'primary' : 'info'"
|
||||
:effect="selectedClusterId === (item.deviceId || item.id) ? 'dark' : 'plain'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick(item.deviceId || item.id || '')"
|
||||
>
|
||||
{{ item.deviceName || item.name || item.deviceId || item.id || 'BMS电池簇' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div v-for="(baseInfo,index) in filteredBaseInfoList" :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">{{index+1}}#{{baseInfo.deviceName}}</span>
|
||||
</div>
|
||||
<div class="descriptions-main">
|
||||
<el-descriptions direction="vertical" :column="3" :colon="false">
|
||||
<el-descriptions-item labelClassName="descriptions-label" :contentClassName="`descriptions-direction ${baseInfo.workStatus === '0' ? 'save' :'danger'}`" :span="1" label="工作状态" >{{$store.state.ems.workStatusOptions[baseInfo.workStatus]}}</el-descriptions-item>
|
||||
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction" :span="1" label="与PCS通信">{{$store.state.ems.communicationStatusOptions[baseInfo.pcsCommunicationStatus]}}</el-descriptions-item>
|
||||
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction" :span="1" label="与EMS通信">{{$store.state.ems.communicationStatusOptions[baseInfo.emsCommunicationStatus]}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<span
|
||||
class="large-title">{{
|
||||
baseInfo.parentDeviceName ? `${baseInfo.parentDeviceName} -> ` : ''
|
||||
}}{{ baseInfo.deviceName }}</span>
|
||||
<div class="info">
|
||||
<div>数据更新时间:{{ baseInfo.dataUpdateTime || '-' }}</div>
|
||||
</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">{{baseInfo[item.attr] | formatNumber}} <span v-if="item.unit" v-html="item.unit"></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">当前SOC : {{baseInfo.currentSoc}}%</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="工作状态">
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'workStatus') }"
|
||||
@click="handleFieldClick(baseInfo, 'workStatus', '工作状态')"
|
||||
>
|
||||
{{ CLUSTERWorkStatusOptions[baseInfo.workStatus] || '-' }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item contentClassName="descriptions-direction"
|
||||
:span="1" label="与PCS通信">
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'pcsCommunicationStatus') }"
|
||||
@click="handleFieldClick(baseInfo, 'pcsCommunicationStatus', '与PCS通信')"
|
||||
>
|
||||
{{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.pcsCommunicationStatus] || '-' }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item contentClassName="descriptions-direction"
|
||||
:span="1" label="与EMS通信">
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'emsCommunicationStatus') }"
|
||||
@click="handleFieldClick(baseInfo, 'emsCommunicationStatus', '与EMS通信')"
|
||||
>
|
||||
{{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.emsCommunicationStatus] || '-' }}
|
||||
</span>
|
||||
</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"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, item.attr) }"
|
||||
@click="handleFieldClick(baseInfo, item.attr, item.label)"
|
||||
>
|
||||
<i v-if="isPointLoading(baseInfo[item.attr])" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ displayValue(baseInfo[item.attr]) | formatNumber }}</span>
|
||||
<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"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'currentSoc') }"
|
||||
@click="handleFieldClick(baseInfo, 'currentSoc', '当前SOC')"
|
||||
>当前SOC :
|
||||
{{ baseInfo.currentSoc }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
class="common-table"
|
||||
:data="baseInfo.batteryDataList"
|
||||
stripe
|
||||
style="width: 100%;margin-top:25px;">
|
||||
class="common-table"
|
||||
:data="baseInfo.batteryDataList"
|
||||
stripe
|
||||
style="width: 100%;margin-top:25px;">
|
||||
<el-table-column
|
||||
prop="dataName"
|
||||
label="名称">
|
||||
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="单体平均值"
|
||||
prop="avgData"
|
||||
label="单体平均值"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span class="pointer"
|
||||
:class="{ 'field-disabled': !hasTableFieldPointId(baseInfo, scope.row.dataName, scope.column.label) }"
|
||||
@click="handleTableFieldClick(baseInfo, scope.row.dataName, scope.column.label)">{{
|
||||
scope.row.avgData
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="minData"
|
||||
label="单体最小值">
|
||||
prop="minData"
|
||||
label="单体最小值">
|
||||
<template slot-scope="scope">
|
||||
<span class="pointer"
|
||||
:class="{ 'field-disabled': !hasTableFieldPointId(baseInfo, scope.row.dataName, scope.column.label) }"
|
||||
@click="handleTableFieldClick(baseInfo, scope.row.dataName, scope.column.label)">{{
|
||||
scope.row.minData
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="minDataID"
|
||||
label="单体最小值ID">
|
||||
prop="minDataID"
|
||||
label="单体最小值ID">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="maxData"
|
||||
label="单体最大值">
|
||||
prop="maxData"
|
||||
label="单体最大值">
|
||||
<template slot-scope="scope">
|
||||
<span class="pointer "
|
||||
:class="{ 'field-disabled': !hasTableFieldPointId(baseInfo, scope.row.dataName, scope.column.label) }"
|
||||
@click="handleTableFieldClick(baseInfo, scope.row.dataName, scope.column.label)">{{
|
||||
scope.row.maxData
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="maxDataID"
|
||||
label="单体最大值ID">
|
||||
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-table ref="pointTable"/>
|
||||
<el-dialog
|
||||
:visible.sync="curveDialogVisible"
|
||||
:title="curveDialogTitle"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
class="ems-dialog"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@opened="handleCurveDialogOpened"
|
||||
@closed="handleCurveDialogClosed"
|
||||
>
|
||||
<div class="curve-tools">
|
||||
<el-date-picker
|
||||
v-model="curveCustomRange"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
style="width: 440px"
|
||||
/>
|
||||
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
|
||||
</div>
|
||||
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getBMSBatteryCluster} from '@/api/ems/dzjk'
|
||||
import * as echarts from "echarts";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
import {getProjectDisplayData, getStackNameList, getClusterNameList} from '@/api/ems/dzjk'
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import {mapState} from "vuex";
|
||||
import {getPointConfigCurve} from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
name:'DzjkSbjkBmsdcc',
|
||||
mixins:[getQuerySiteId],
|
||||
components:{},
|
||||
name: 'DzjkSbjkBmsdcc',
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
components: {PointTable},
|
||||
computed: {
|
||||
...mapState({
|
||||
CLUSTERWorkStatusOptions: state => state?.ems?.CLUSTERWorkStatusOptions || {},
|
||||
}),
|
||||
filteredBaseInfoList() {
|
||||
if (!this.selectedClusterId) {
|
||||
return this.baseInfoList || [];
|
||||
}
|
||||
return (this.baseInfoList || []).filter(item => item.deviceId === this.selectedClusterId);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
unitObj:{
|
||||
'电压':'V',
|
||||
'温度':'℃',
|
||||
'SOC':'%'
|
||||
loading: false,
|
||||
displayData: [],
|
||||
clusterDeviceList: [],
|
||||
selectedClusterId: "",
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
baseInfoList:[],
|
||||
infoData:[
|
||||
{label:'簇电压',attr:'clusterVoltage',unit:'V'},
|
||||
{label:'可充电量',attr:'chargeableCapacity',unit:'kWh'},
|
||||
{label:'累计充电量',attr:'totalChargedCapacity',unit:'kWh'},
|
||||
{label:'簇电流',attr:'clusterCurrent',unit:'A'},
|
||||
{label:'可放电量',attr:'dischargeableCapacity',unit:'kWh'},
|
||||
{label:'累计放电量',attr:'totalDischargedCapacity',unit:'kWh'},
|
||||
{label:'SOH',attr:'soh',unit:'%'},
|
||||
{label:'平均温度',attr:'averageTemperature',unit:'℃'},
|
||||
{label:'绝缘电阻',attr:'insulationResistance',unit:'Ω'},
|
||||
unitObj: {
|
||||
'电压': 'V',
|
||||
'温度': '℃',
|
||||
'SOC': '%'
|
||||
},
|
||||
tablePointNameMap: {
|
||||
'电压单体最小值': '最低单体电压',
|
||||
'电压单体平均值': '电压平均值',
|
||||
'电压单体最大值': '最高单体电压',
|
||||
'温度单体最小值': '最低单体温度',
|
||||
'温度单体平均值': '平均单体温度',
|
||||
'温度单体最大值': '最高单体温度',
|
||||
'SOC单体最小值': '最低单体SOC',
|
||||
'SOC单体平均值': '当前SOC',
|
||||
'SOC单体最大值': '最高单体SOC',
|
||||
},
|
||||
baseInfoList: [{
|
||||
siteId: "",
|
||||
deviceId: "",
|
||||
parentDeviceName: "",
|
||||
deviceName: "BMS电池簇",
|
||||
dataUpdateTime: "-",
|
||||
alarmNum: 0,
|
||||
batteryDataList: [],
|
||||
}],
|
||||
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: '℃', pointName: '平均温度'},
|
||||
{label: '绝缘电阻', attr: 'insulationResistance', unit: 'Ω', pointName: '绝缘电阻'},
|
||||
],
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
init(){
|
||||
methods: {
|
||||
displayValue(value) {
|
||||
return value === undefined || value === null || value === "" ? "-" : value;
|
||||
},
|
||||
isPointLoading(value) {
|
||||
return this.loading && (value === undefined || value === null || value === "" || value === "-");
|
||||
},
|
||||
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)
|
||||
},
|
||||
hasFieldPointId(baseInfo, fieldName) {
|
||||
const row = this.getFieldRow(baseInfo, fieldName);
|
||||
return !!String(row?.dataPoint || "").trim();
|
||||
},
|
||||
hasTableFieldPointId(baseInfo, dataName, columnLabel) {
|
||||
const pointName = this.tablePointNameMap[String(dataName || "") + String(columnLabel || "")];
|
||||
if (!pointName) {
|
||||
return false;
|
||||
}
|
||||
return this.hasFieldPointId(baseInfo, pointName);
|
||||
},
|
||||
getFieldRow(baseInfo, fieldName) {
|
||||
const key = String(fieldName || "").trim();
|
||||
const map = baseInfo?._fieldRowMap || {};
|
||||
return map[key] || null;
|
||||
},
|
||||
handleFieldClick(baseInfo, fieldName, title) {
|
||||
const row = this.getFieldRow(baseInfo, fieldName);
|
||||
const pointId = String(row?.dataPoint || "").trim();
|
||||
this.openCurveDialogByPointId(pointId, title || fieldName);
|
||||
},
|
||||
handleTableFieldClick(baseInfo, dataName, columnLabel) {
|
||||
const pointName = this.tablePointNameMap[String(dataName || "") + String(columnLabel || "")];
|
||||
if (!pointName) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
this.handleFieldClick(baseInfo, pointName, pointName);
|
||||
},
|
||||
openCurveDialogByPointId(pointId, title) {
|
||||
const normalizedPointId = String(pointId || "").trim();
|
||||
if (!normalizedPointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
const range = this.getDefaultCurveRange();
|
||||
this.curveCustomRange = range;
|
||||
this.curveDialogTitle = `点位曲线 - ${title || normalizedPointId}`;
|
||||
this.curveQuery = {
|
||||
siteId: this.siteId,
|
||||
pointId: normalizedPointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: range[0],
|
||||
endTime: range[1],
|
||||
};
|
||||
this.curveDialogVisible = true;
|
||||
},
|
||||
handleCurveDialogOpened() {
|
||||
if (!this.curveChart && this.$refs.curveChartRef) {
|
||||
this.curveChart = echarts.init(this.$refs.curveChartRef);
|
||||
}
|
||||
this.loadCurveData();
|
||||
},
|
||||
handleCurveDialogClosed() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
this.curveLoading = false;
|
||||
},
|
||||
getDefaultCurveRange() {
|
||||
const end = new Date();
|
||||
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return [this.formatDateTime(start), this.formatDateTime(end)];
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const d = new Date(date);
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
},
|
||||
formatCurveTime(value) {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return "";
|
||||
}
|
||||
const raw = String(value).trim();
|
||||
const normalized = raw
|
||||
.replace("T", " ")
|
||||
.replace(/\.\d+/, "")
|
||||
.replace(/Z$/, "")
|
||||
.replace(/([+-]\d{2}:?\d{2})$/, "")
|
||||
.trim();
|
||||
const matched = normalized.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
|
||||
if (matched) {
|
||||
return `${matched[1]} ${matched[2]}`;
|
||||
}
|
||||
return normalized.slice(0, 16);
|
||||
},
|
||||
loadCurveData() {
|
||||
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
|
||||
this.$message.warning("点位信息不完整,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
if (!this.curveCustomRange || this.curveCustomRange.length !== 2) {
|
||||
this.$message.warning("请选择查询时间范围");
|
||||
return;
|
||||
}
|
||||
this.curveQuery.startTime = this.curveCustomRange[0];
|
||||
this.curveQuery.endTime = this.curveCustomRange[1];
|
||||
const query = {
|
||||
siteId: this.curveQuery.siteId,
|
||||
pointId: this.curveQuery.pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: this.curveQuery.startTime,
|
||||
endTime: this.curveQuery.endTime,
|
||||
};
|
||||
this.curveLoading = true;
|
||||
getPointConfigCurve(query).then((response) => {
|
||||
const rows = response?.data || [];
|
||||
this.renderCurveChart(rows);
|
||||
}).catch(() => {
|
||||
this.renderCurveChart([]);
|
||||
}).finally(() => {
|
||||
this.curveLoading = false;
|
||||
});
|
||||
},
|
||||
renderCurveChart(rows = []) {
|
||||
if (!this.curveChart) return;
|
||||
const xData = rows.map(item => this.formatCurveTime(item.dataTime));
|
||||
const yData = rows.map(item => item.pointValue);
|
||||
this.curveChart.clear();
|
||||
this.curveChart.setOption({
|
||||
legend: {},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.curveDialogTitle,
|
||||
type: "line",
|
||||
data: yData,
|
||||
connectNulls: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!rows.length) {
|
||||
this.$message.warning("当前时间范围暂无曲线数据");
|
||||
}
|
||||
},
|
||||
handleTagClick(deviceId) {
|
||||
this.selectedClusterId = deviceId || "";
|
||||
},
|
||||
init() {
|
||||
this.updateData()
|
||||
this.updateInterval(this.updateData)
|
||||
},
|
||||
getModuleRows(menuCode, sectionName) {
|
||||
return (this.displayData || []).filter(item => item.menuCode === menuCode && item.sectionName === sectionName);
|
||||
},
|
||||
getFieldName(fieldCode) {
|
||||
const raw = String(fieldCode || "").trim();
|
||||
if (!raw) return "";
|
||||
const index = raw.lastIndexOf("__");
|
||||
return index >= 0 ? raw.slice(index + 2) : raw;
|
||||
},
|
||||
getFieldMap(rows = [], deviceId = "") {
|
||||
const rowMap = this.getFieldRowMap(rows, deviceId);
|
||||
return Object.keys(rowMap).reduce((acc, fieldName) => {
|
||||
const row = rowMap[fieldName];
|
||||
if (acc[fieldName] === undefined) {
|
||||
acc[fieldName] = row?.fieldValue;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
getFieldRowMap(rows = [], deviceId = "") {
|
||||
const map = {};
|
||||
const targetDeviceId = String(deviceId || "");
|
||||
rows.forEach(item => {
|
||||
if (!item || !item.fieldCode) return;
|
||||
const itemDeviceId = String(item.deviceId || "");
|
||||
if (itemDeviceId !== targetDeviceId) return;
|
||||
const fieldName = this.getFieldName(item.fieldCode);
|
||||
map[fieldName] = item;
|
||||
const displayName = String(item.fieldName || "").trim();
|
||||
if (displayName && !map[displayName]) {
|
||||
map[displayName] = item;
|
||||
}
|
||||
});
|
||||
rows.forEach(item => {
|
||||
if (!item || !item.fieldCode) return;
|
||||
const itemDeviceId = String(item.deviceId || "");
|
||||
if (itemDeviceId !== "") return;
|
||||
const fieldName = this.getFieldName(item.fieldCode);
|
||||
if (map[fieldName] === undefined || map[fieldName] === null || map[fieldName] === "") {
|
||||
map[fieldName] = item;
|
||||
}
|
||||
const displayName = String(item.fieldName || "").trim();
|
||||
if (displayName && !map[displayName]) {
|
||||
map[displayName] = item;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
},
|
||||
getLatestTime(menuCode) {
|
||||
const times = (this.displayData || [])
|
||||
.filter(item => item.menuCode === menuCode && item.valueTime)
|
||||
.map(item => new Date(item.valueTime).getTime())
|
||||
.filter(ts => !isNaN(ts));
|
||||
if (times.length === 0) {
|
||||
return '-';
|
||||
}
|
||||
const date = new Date(Math.max(...times));
|
||||
const p = (n) => String(n).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())} ${p(date.getHours())}:${p(date.getMinutes())}:${p(date.getSeconds())}`;
|
||||
},
|
||||
getClusterDeviceList() {
|
||||
return getStackNameList(this.siteId)
|
||||
.then(response => {
|
||||
const stackList = response?.data || [];
|
||||
if (!stackList.length) {
|
||||
this.clusterDeviceList = [];
|
||||
return;
|
||||
}
|
||||
const requests = stackList.map(stack => {
|
||||
const stackDeviceId = stack.deviceId || stack.id || '';
|
||||
return getClusterNameList({stackDeviceId, siteId: this.siteId})
|
||||
.then(clusterResponse => {
|
||||
const clusterList = clusterResponse?.data || [];
|
||||
return clusterList.map(cluster => ({
|
||||
...cluster,
|
||||
parentDeviceName: stack.deviceName || stack.name || stackDeviceId || '',
|
||||
}));
|
||||
})
|
||||
.catch(() => []);
|
||||
});
|
||||
return Promise.all(requests).then(results => {
|
||||
this.clusterDeviceList = results.flat();
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.clusterDeviceList = [];
|
||||
});
|
||||
},
|
||||
buildBaseInfoList() {
|
||||
const devices = (this.clusterDeviceList && this.clusterDeviceList.length > 0)
|
||||
? this.clusterDeviceList
|
||||
: [{deviceId: this.siteId, deviceName: 'BMS电池簇', parentDeviceName: ''}];
|
||||
this.baseInfoList = devices.map(device => ({
|
||||
...(() => {
|
||||
const id = device.deviceId || device.id || this.siteId;
|
||||
const infoMap = this.getFieldMap(this.getModuleRows('SBJK_BMSDCC', '簇信息'), id);
|
||||
const statusMap = this.getFieldMap(this.getModuleRows('SBJK_BMSDCC', '状态'), id);
|
||||
const currentSoc = Number(infoMap.currentSoc);
|
||||
return {
|
||||
...infoMap,
|
||||
workStatus: statusMap.workStatus,
|
||||
pcsCommunicationStatus: statusMap.pcsCommunicationStatus,
|
||||
emsCommunicationStatus: statusMap.emsCommunicationStatus,
|
||||
currentSoc: isNaN(currentSoc) ? 0 : currentSoc,
|
||||
_fieldRowMap: {
|
||||
...this.getFieldRowMap(this.getModuleRows('SBJK_BMSDCC', '簇信息'), id),
|
||||
...this.getFieldRowMap(this.getModuleRows('SBJK_BMSDCC', '状态'), id),
|
||||
...this.getFieldRowMap(this.getModuleRows('SBJK_BMSDCC', '单体数据'), id),
|
||||
},
|
||||
};
|
||||
})(),
|
||||
siteId: this.siteId,
|
||||
deviceId: device.deviceId || device.id || this.siteId,
|
||||
parentDeviceName: device.parentDeviceName || '',
|
||||
deviceName: device.deviceName || device.name || device.deviceId || device.id || 'BMS电池簇',
|
||||
dataUpdateTime: this.getLatestTime('SBJK_BMSDCC'),
|
||||
alarmNum: 0,
|
||||
batteryDataList: [],
|
||||
}));
|
||||
},
|
||||
updateData() {
|
||||
this.loading = true
|
||||
getBMSBatteryCluster(this.siteId).then(response => {
|
||||
this.baseInfoList = JSON.parse(JSON.stringify(response?.data || []));
|
||||
}).finally(() => {this.loading = false})
|
||||
|
||||
// 先渲染卡片框架,字段值走单点位 loading
|
||||
this.buildBaseInfoList();
|
||||
Promise.all([
|
||||
getProjectDisplayData(this.siteId),
|
||||
this.getClusterDeviceList(),
|
||||
]).then(([response]) => {
|
||||
this.displayData = response?.data || [];
|
||||
this.buildBaseInfoList();
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pcs-tags {
|
||||
margin: 0 0 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pcs-tag-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
//描述列表样式
|
||||
.descriptions-main{
|
||||
padding:24px 300px 24px 24px;
|
||||
.descriptions-main {
|
||||
padding: 24px 300px 24px 24px;
|
||||
}
|
||||
.descriptions-main-bottom{
|
||||
padding:14px 300px 14px 24px;
|
||||
|
||||
.descriptions-main-bottom {
|
||||
padding: 14px 300px 14px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
// 进度条样式
|
||||
.process-container{
|
||||
width:100px;
|
||||
.process-container {
|
||||
width: 100px;
|
||||
position: absolute;
|
||||
right:70px;
|
||||
top:50%;
|
||||
right: 70px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
.process-line-bg{
|
||||
|
||||
.process-line-bg {
|
||||
position: relative;
|
||||
width:100%;
|
||||
width: 100%;
|
||||
height: 110px;
|
||||
background-color:#fff2cb ;
|
||||
background-color: #fff2cb;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0 10px #fff2cb, 0 0 0 rgba(255, 242, 203, 0.5);
|
||||
.process-line{
|
||||
|
||||
.process-line {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
@ -142,10 +665,23 @@ export default {
|
||||
box-shadow: 0 0 10px #ffbf14, 0 0 0 rgba(255, 191, 20, 0.5);
|
||||
}
|
||||
}
|
||||
.process{
|
||||
margin-top:15px;
|
||||
color:#666666;
|
||||
|
||||
.process {
|
||||
margin-top: 15px;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
.point-loading-icon {
|
||||
color: #409eff;
|
||||
display: inline-block;
|
||||
transform-origin: center;
|
||||
animation: pointLoadingSpinPulse 1.1s linear infinite;
|
||||
}
|
||||
@keyframes pointLoadingSpinPulse {
|
||||
0% { opacity: 0.45; transform: rotate(0deg) scale(0.9); }
|
||||
50% { opacity: 1; transform: rotate(180deg) scale(1.08); }
|
||||
100% { opacity: 0.45; transform: rotate(360deg) scale(0.9); }
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -1,146 +1,532 @@
|
||||
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div v-for="(baseInfo,index) in baseInfoList" :key="index+'bmszlContainer'" style="margin-bottom:25px;">
|
||||
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding common-card-container-no-title-bg">
|
||||
<div>
|
||||
<div class="pcs-tags">
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="selectedStackId ? 'info' : 'primary'"
|
||||
:effect="selectedStackId ? 'plain' : 'dark'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick('')"
|
||||
>
|
||||
全部
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-for="(item, index) in stackDeviceList"
|
||||
:key="index + 'stackTag'"
|
||||
size="small"
|
||||
:type="selectedStackId === (item.deviceId || item.id) ? 'primary' : 'info'"
|
||||
:effect="selectedStackId === (item.deviceId || item.id) ? 'dark' : 'plain'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick(item.deviceId || item.id || '')"
|
||||
>
|
||||
{{ item.deviceName || item.name || item.deviceId || item.id || 'BMS总览' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div v-for="(baseInfo,index) in filteredBaseInfoList" :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">{{index+1}}#{{baseInfo.deviceName}}</span>
|
||||
<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 direction="vertical" :column="3" :colon="false">
|
||||
<el-descriptions-item labelClassName="descriptions-label" :contentClassName="`descriptions-direction ${baseInfo.workStatus === '0' ? 'save' :'danger'}`" :span="1" label="工作状态" >{{$store.state.ems.workStatusOptions[baseInfo.workStatus]}}</el-descriptions-item>
|
||||
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction" :span="1" label="与PCS通信">{{$store.state.ems.communicationStatusOptions[baseInfo.pcsCommunicationStatus]}}</el-descriptions-item>
|
||||
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction" :span="1" label="与EMS通信">{{$store.state.ems.communicationStatusOptions[baseInfo.emsCommunicationStatus]}}</el-descriptions-item>
|
||||
<el-descriptions :colon="false" :column="3" direction="vertical">
|
||||
<el-descriptions-item
|
||||
contentClassName="descriptions-direction work-status"
|
||||
label="工作状态" labelClassName="descriptions-label">
|
||||
<span class="pointer" @click="handleStatusFieldClick(baseInfo, 'workStatus', '工作状态')">
|
||||
{{ STACKWorkStatusOptions[baseInfo.workStatus] || '-' }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" contentClassName="descriptions-direction" label="与PCS通信"
|
||||
labelClassName="descriptions-label">
|
||||
<span class="pointer" @click="handleStatusFieldClick(baseInfo, 'pcsCommunicationStatus', '与PCS通信')">
|
||||
{{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.pcsCommunicationStatus] || '-' }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" contentClassName="descriptions-direction" label="与EMS通信"
|
||||
labelClassName="descriptions-label">
|
||||
<span class="pointer" @click="handleStatusFieldClick(baseInfo, 'emsCommunicationStatus', '与EMS通信')">
|
||||
{{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.emsCommunicationStatus] || '-' }}
|
||||
</span>
|
||||
</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">{{baseInfo[item.attr] | formatNumber}} <span v-if="item.unit" v-html="item.unit"></span></el-descriptions-item>
|
||||
<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="handleStackFieldClick(baseInfo, item)">
|
||||
<i v-if="isPointLoading(baseInfo[item.attr])" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ displayValue(baseInfo[item.attr]) | formatNumber }}</span>
|
||||
<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.stackSoc+'%'}"></div>
|
||||
<div :style="{height:baseInfo.stackSoc+'%'}" class="process-line"></div>
|
||||
</div>
|
||||
<div class="process pointer" @click="handleStackSocClick(baseInfo)">当前SOC :
|
||||
{{ baseInfo.stackSoc }}%
|
||||
</div>
|
||||
<div class="process">当前SOC : {{baseInfo.stackSoc}}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
class="common-table"
|
||||
:data="baseInfo.batteryDataList"
|
||||
stripe
|
||||
max-height="500"
|
||||
style="width: 100%;margin-top:25px;">
|
||||
<el-table-column
|
||||
prop="clusterId"
|
||||
label="簇号">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="簇电压"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.clusterVoltage}} V</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="簇电流">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.clusterCurrent}} A</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="簇SOC">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.currentSoc}} %</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="maxVoltage"
|
||||
label="单体最高电压">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.maxCellVoltage}} V</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="maxCellVoltageId"
|
||||
label="电池号码">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="minVoltage"
|
||||
label="单体最低电压">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.minCellVoltage}} V</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="minCellVoltageId"
|
||||
label="电池号码">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="单体最高温度">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.maxCellTemp}} ℃</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="maxCellTempId"
|
||||
label="电池号码">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="minTemperature"
|
||||
label="单体最低温度">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.minCellTemp}} ℃</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="minCellTempId"
|
||||
label="电池号码">
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-empty v-show="baseInfoList.length<=0" :image-size="200"></el-empty>
|
||||
<el-dialog
|
||||
:visible.sync="curveDialogVisible"
|
||||
:title="curveDialogTitle"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
class="ems-dialog"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@opened="handleCurveDialogOpened"
|
||||
@closed="handleCurveDialogClosed"
|
||||
>
|
||||
<div class="curve-tools">
|
||||
<el-date-picker
|
||||
v-model="curveCustomRange"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
style="width: 440px"
|
||||
/>
|
||||
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
|
||||
</div>
|
||||
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
|
||||
</el-dialog>
|
||||
<point-table ref="pointTable"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getBMSOverView} from '@/api/ems/dzjk'
|
||||
import * as echarts from "echarts";
|
||||
import {getProjectDisplayData, getStackNameList} from '@/api/ems/dzjk'
|
||||
import {getPointConfigCurve} from "@/api/ems/site";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
name:'DzjkSbjkBmszl',
|
||||
mixins:[getQuerySiteId],
|
||||
name: 'DzjkSbjkBmszl',
|
||||
components: {PointTable},
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
computed: {
|
||||
...mapState({
|
||||
STACKWorkStatusOptions: state => state?.ems?.STACKWorkStatusOptions || {},
|
||||
}),
|
||||
filteredBaseInfoList() {
|
||||
if (!this.selectedStackId) {
|
||||
return this.baseInfoList || [];
|
||||
}
|
||||
return (this.baseInfoList || []).filter(item => item.deviceId === this.selectedStackId);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
baseInfoList:[],
|
||||
infoData:[
|
||||
{label:'电池堆总电压',attr:'stackVoltage',unit:'V'},
|
||||
{label:'可充电量',attr:'availableChargeCapacity',unit:'kWh'},
|
||||
{label:'累计充电量',attr:'totalChargeCapacity',unit:'kWh'},
|
||||
{label:'电池堆总电流',attr:'stackCurrent',unit:'A'},
|
||||
{label:'可放电量',attr:'availableDischargeCapacity',unit:'kWh'},
|
||||
{label:'累计放电量',attr:'totalDischargeCapacity',unit:'kWh'},
|
||||
{label:'SOH',attr:'stackSoh',unit:'%'},
|
||||
{label:'平均温度',attr:'operatingTemp',unit:'℃'},
|
||||
{label:'绝缘电阻',attr:'stackInsulationResistance',unit:'Ω'},
|
||||
loading: false,
|
||||
displayData: [],
|
||||
stackDeviceList: [],
|
||||
selectedStackId: "",
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
baseInfoList: [{
|
||||
siteId: "",
|
||||
deviceId: "",
|
||||
deviceName: "BMS总览",
|
||||
dataUpdateTime: "-",
|
||||
alarmNum: 0,
|
||||
batteryDataList: [],
|
||||
}],
|
||||
infoData: [
|
||||
{label: '电池堆总电压', attr: 'stackVoltage', unit: 'V'},
|
||||
{label: '可充电量', attr: 'availableChargeCapacity', unit: 'kWh'},
|
||||
{label: '累计充电量', attr: 'totalChargeCapacity', unit: 'kWh'},
|
||||
{label: '电池堆总电流', attr: 'stackCurrent', unit: 'A'},
|
||||
{label: '可放电量', attr: 'availableDischargeCapacity', unit: 'kWh'},
|
||||
{label: '累计放电量', attr: 'totalDischargeCapacity', unit: 'kWh'},
|
||||
{label: 'SOH', attr: 'stackSoh', unit: '%'},
|
||||
{label: '平均温度', attr: 'operatingTemp', unit: '℃'},
|
||||
{label: '绝缘电阻', attr: 'stackInsulationResistance', unit: 'Ω'},
|
||||
]
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
init(){
|
||||
this.loading=true;
|
||||
getBMSOverView(this.siteId).then(response => {
|
||||
this.baseInfoList = JSON.parse(JSON.stringify(response?.data || []));
|
||||
}).finally(() => {this.loading = false})
|
||||
}
|
||||
methods: {
|
||||
displayValue(value) {
|
||||
return value === undefined || value === null || value === "" ? "-" : value;
|
||||
},
|
||||
isPointLoading(value) {
|
||||
return this.loading && (value === undefined || value === null || value === "" || value === "-");
|
||||
},
|
||||
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)
|
||||
},
|
||||
handleStatusFieldClick(baseInfo, fieldKey, title) {
|
||||
const pointId = this.resolvePointId(baseInfo, fieldKey, "status");
|
||||
this.openCurveDialogByPointId(pointId, title || fieldKey);
|
||||
},
|
||||
handleStackFieldClick(baseInfo, item) {
|
||||
const fieldKey = item?.attr || "";
|
||||
const pointId = this.resolvePointId(baseInfo, fieldKey, "info");
|
||||
this.openCurveDialogByPointId(pointId, item?.label || fieldKey);
|
||||
},
|
||||
handleStackSocClick(baseInfo) {
|
||||
const pointId = this.resolvePointId(baseInfo, "stackSoc", "info");
|
||||
this.openCurveDialogByPointId(pointId, "当前SOC");
|
||||
},
|
||||
handleClusterFieldClick(row = {}, fieldKey = "", title = "") {
|
||||
const directKeys = [
|
||||
"pointId",
|
||||
"dataPoint",
|
||||
`${fieldKey}PointId`,
|
||||
`${fieldKey}DataPoint`,
|
||||
];
|
||||
let pointId = "";
|
||||
directKeys.some((key) => {
|
||||
const value = String(row?.[key] || "").trim();
|
||||
if (value) {
|
||||
pointId = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (!pointId && row?.pointIdMap && fieldKey) {
|
||||
pointId = String(row.pointIdMap[fieldKey] || "").trim();
|
||||
}
|
||||
this.openCurveDialogByPointId(pointId, title || fieldKey);
|
||||
},
|
||||
resolvePointId(baseInfo = {}, fieldKey = "", source = "info") {
|
||||
const mapKey = source === "status" ? "statusPointIdMap" : "pointIdMap";
|
||||
return String(baseInfo?.[mapKey]?.[fieldKey] || "").trim();
|
||||
},
|
||||
openCurveDialogByPointId(pointId, title) {
|
||||
const normalizedPointId = String(pointId || "").trim();
|
||||
if (!normalizedPointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
const range = this.getDefaultCurveRange();
|
||||
this.curveCustomRange = range;
|
||||
this.curveDialogTitle = `点位曲线 - ${title || normalizedPointId}`;
|
||||
this.curveQuery = {
|
||||
siteId: this.siteId,
|
||||
pointId: normalizedPointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: range[0],
|
||||
endTime: range[1],
|
||||
};
|
||||
this.curveDialogVisible = true;
|
||||
},
|
||||
handleCurveDialogOpened() {
|
||||
if (!this.curveChart && this.$refs.curveChartRef) {
|
||||
this.curveChart = echarts.init(this.$refs.curveChartRef);
|
||||
}
|
||||
this.loadCurveData();
|
||||
},
|
||||
handleCurveDialogClosed() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
this.curveLoading = false;
|
||||
},
|
||||
getDefaultCurveRange() {
|
||||
const end = new Date();
|
||||
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return [this.formatDateTime(start), this.formatDateTime(end)];
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const d = new Date(date);
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
},
|
||||
formatCurveTime(value) {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return "";
|
||||
}
|
||||
const raw = String(value).trim();
|
||||
const normalized = raw
|
||||
.replace("T", " ")
|
||||
.replace(/\.\d+/, "")
|
||||
.replace(/Z$/, "")
|
||||
.replace(/([+-]\d{2}:?\d{2})$/, "")
|
||||
.trim();
|
||||
const matched = normalized.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
|
||||
if (matched) {
|
||||
return `${matched[1]} ${matched[2]}`;
|
||||
}
|
||||
return normalized.slice(0, 16);
|
||||
},
|
||||
loadCurveData() {
|
||||
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
|
||||
this.$message.warning("点位信息不完整,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
if (!this.curveCustomRange || this.curveCustomRange.length !== 2) {
|
||||
this.$message.warning("请选择查询时间范围");
|
||||
return;
|
||||
}
|
||||
this.curveQuery.startTime = this.curveCustomRange[0];
|
||||
this.curveQuery.endTime = this.curveCustomRange[1];
|
||||
const query = {
|
||||
siteId: this.curveQuery.siteId,
|
||||
pointId: this.curveQuery.pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: this.curveQuery.startTime,
|
||||
endTime: this.curveQuery.endTime,
|
||||
};
|
||||
this.curveLoading = true;
|
||||
getPointConfigCurve(query).then((response) => {
|
||||
const rows = response?.data || [];
|
||||
this.renderCurveChart(rows);
|
||||
}).catch(() => {
|
||||
this.renderCurveChart([]);
|
||||
}).finally(() => {
|
||||
this.curveLoading = false;
|
||||
});
|
||||
},
|
||||
renderCurveChart(rows = []) {
|
||||
if (!this.curveChart) return;
|
||||
const xData = rows.map((item) => this.formatCurveTime(item.dataTime));
|
||||
const yData = rows.map((item) => item.pointValue);
|
||||
this.curveChart.clear();
|
||||
this.curveChart.setOption({
|
||||
legend: {},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.curveDialogTitle,
|
||||
type: "line",
|
||||
data: yData,
|
||||
connectNulls: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!rows.length) {
|
||||
this.$message.warning("当前时间范围暂无曲线数据");
|
||||
}
|
||||
},
|
||||
init() {
|
||||
this.updateData()
|
||||
this.updateInterval(this.updateData)
|
||||
},
|
||||
getModuleRows(menuCode, sectionName) {
|
||||
return (this.displayData || []).filter(item => item.menuCode === menuCode && item.sectionName === sectionName);
|
||||
},
|
||||
getFieldName(fieldCode) {
|
||||
const raw = String(fieldCode || "").trim();
|
||||
if (!raw) return "";
|
||||
const index = raw.lastIndexOf("__");
|
||||
return index >= 0 ? raw.slice(index + 2) : raw;
|
||||
},
|
||||
isEmptyValue(value) {
|
||||
return value === undefined || value === null || value === "";
|
||||
},
|
||||
getFieldRowMap(rows = [], deviceId = "") {
|
||||
const map = {};
|
||||
const targetDeviceId = String(deviceId || "");
|
||||
rows.forEach(item => {
|
||||
if (!item || !item.fieldCode) return;
|
||||
const itemDeviceId = String(item.deviceId || "");
|
||||
if (itemDeviceId !== targetDeviceId) return;
|
||||
map[this.getFieldName(item.fieldCode)] = item;
|
||||
});
|
||||
rows.forEach(item => {
|
||||
if (!item || !item.fieldCode) return;
|
||||
const itemDeviceId = String(item.deviceId || "");
|
||||
if (itemDeviceId !== "") return;
|
||||
const fieldName = this.getFieldName(item.fieldCode);
|
||||
const existRow = map[fieldName];
|
||||
if (!existRow || this.isEmptyValue(existRow.fieldValue)) {
|
||||
map[fieldName] = item;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
},
|
||||
getFieldMap(rowMap = {}) {
|
||||
const map = {};
|
||||
Object.keys(rowMap || {}).forEach((fieldName) => {
|
||||
map[fieldName] = rowMap[fieldName]?.fieldValue;
|
||||
});
|
||||
return map;
|
||||
},
|
||||
getPointIdMap(rowMap = {}) {
|
||||
const map = {};
|
||||
Object.keys(rowMap || {}).forEach((fieldName) => {
|
||||
map[fieldName] = String(rowMap[fieldName]?.dataPoint || "").trim();
|
||||
});
|
||||
return map;
|
||||
},
|
||||
getLatestTime(menuCode) {
|
||||
const times = (this.displayData || [])
|
||||
.filter(item => item.menuCode === menuCode && item.valueTime)
|
||||
.map(item => new Date(item.valueTime).getTime())
|
||||
.filter(ts => !isNaN(ts));
|
||||
if (times.length === 0) {
|
||||
return '-';
|
||||
}
|
||||
const date = new Date(Math.max(...times));
|
||||
const p = (n) => String(n).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())} ${p(date.getHours())}:${p(date.getMinutes())}:${p(date.getSeconds())}`;
|
||||
},
|
||||
handleTagClick(deviceId) {
|
||||
this.selectedStackId = deviceId || "";
|
||||
},
|
||||
getStackDeviceList() {
|
||||
return getStackNameList(this.siteId).then(response => {
|
||||
this.stackDeviceList = response?.data || [];
|
||||
}).catch(() => {
|
||||
this.stackDeviceList = [];
|
||||
});
|
||||
},
|
||||
buildBaseInfoList() {
|
||||
const devices = (this.stackDeviceList && this.stackDeviceList.length > 0)
|
||||
? this.stackDeviceList
|
||||
: [{deviceId: this.siteId, deviceName: 'BMS总览'}];
|
||||
this.baseInfoList = devices.map(device => ({
|
||||
...(() => {
|
||||
const id = device.deviceId || device.id || this.siteId;
|
||||
const infoRowMap = this.getFieldRowMap(this.getModuleRows('SBJK_BMSZL', '堆信息'), id);
|
||||
const statusRowMap = this.getFieldRowMap(this.getModuleRows('SBJK_BMSZL', '状态'), id);
|
||||
const infoMap = this.getFieldMap(infoRowMap);
|
||||
const statusMap = this.getFieldMap(statusRowMap);
|
||||
const stackSoc = Number(infoMap.stackSoc);
|
||||
return {
|
||||
...infoMap,
|
||||
workStatus: statusMap.workStatus,
|
||||
pcsCommunicationStatus: statusMap.pcsCommunicationStatus,
|
||||
emsCommunicationStatus: statusMap.emsCommunicationStatus,
|
||||
stackSoc: isNaN(stackSoc) ? 0 : stackSoc,
|
||||
pointIdMap: this.getPointIdMap(infoRowMap),
|
||||
statusPointIdMap: this.getPointIdMap(statusRowMap),
|
||||
};
|
||||
})(),
|
||||
siteId: this.siteId,
|
||||
deviceId: device.deviceId || device.id || this.siteId,
|
||||
deviceName: device.deviceName || device.name || device.deviceId || device.id || 'BMS总览',
|
||||
dataUpdateTime: this.getLatestTime('SBJK_BMSZL'),
|
||||
alarmNum: 0,
|
||||
batteryDataList: [],
|
||||
}));
|
||||
},
|
||||
updateData() {
|
||||
this.loading = true
|
||||
// 先渲染卡片框架,字段值走单点位 loading
|
||||
this.buildBaseInfoList();
|
||||
Promise.all([
|
||||
getProjectDisplayData(this.siteId),
|
||||
this.getStackDeviceList(),
|
||||
]).then(([displayResponse]) => {
|
||||
this.displayData = displayResponse?.data || [];
|
||||
this.buildBaseInfoList();
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
}
|
||||
,
|
||||
beforeDestroy() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.pcs-tags {
|
||||
margin: 0 0 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pcs-tag-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.curve-tools {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
//描述列表样式
|
||||
.descriptions-main {
|
||||
@ -151,21 +537,24 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 进度条样式
|
||||
.process-container{
|
||||
width:100px;
|
||||
.process-container {
|
||||
width: 100px;
|
||||
position: absolute;
|
||||
right:70px;
|
||||
top:50%;
|
||||
right: 70px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
.process-line-bg{
|
||||
|
||||
.process-line-bg {
|
||||
position: relative;
|
||||
width:100%;
|
||||
width: 100%;
|
||||
height: 110px;
|
||||
background-color:#fff2cb ;
|
||||
background-color: #fff2cb;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0 10px #fff2cb, 0 0 0 rgba(255, 242, 203, 0.5);
|
||||
.process-line{
|
||||
|
||||
.process-line {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
@ -176,10 +565,23 @@ export default {
|
||||
box-shadow: 0 0 10px rgb(252 108 108), 0 0 0 rgba(252, 108, 108, 0.5);
|
||||
}
|
||||
}
|
||||
.process{
|
||||
margin-top:15px;
|
||||
color:#666666;
|
||||
|
||||
.process {
|
||||
margin-top: 15px;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
.point-loading-icon {
|
||||
color: #409eff;
|
||||
display: inline-block;
|
||||
transform-origin: center;
|
||||
animation: pointLoadingSpinPulse 1.1s linear infinite;
|
||||
}
|
||||
@keyframes pointLoadingSpinPulse {
|
||||
0% { opacity: 0.45; transform: rotate(0deg) scale(0.9); }
|
||||
50% { opacity: 1; transform: rotate(180deg) scale(1.08); }
|
||||
100% { opacity: 0.45; transform: rotate(360deg) scale(0.9); }
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -1,134 +1,497 @@
|
||||
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<el-card shadow="always" class="common-card-container" :class="zbInfo.emsCommunicationStatus === '1' ? 'zb-common-card-container' : 'cnb-common-card-container'">
|
||||
<div slot="header">
|
||||
<span class="large-title">1#{{zbInfo.deviceName}}</span>
|
||||
<div class="status">
|
||||
<div>{{$store.state.ems.communicationStatusOptions[zbInfo.emsCommunicationStatus]}}</div>
|
||||
<div>数据更新时间:{{zbInfo.dataUpdateTime}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
class="common-table"
|
||||
:data="zbInfo.loadDataDetailInfo"
|
||||
stripe
|
||||
style="width: 100%;">
|
||||
<el-table-column
|
||||
prop="category"
|
||||
label="类别">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="totalKwh"
|
||||
label="总/kWh"
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="peakKwh"
|
||||
label="尖/kWh">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="highKwh"
|
||||
label="峰/kWh">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="flatKwh"
|
||||
label="平/kWh">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="valleyKwh"
|
||||
label="谷/kWh">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<el-card shadow="always" class="common-card-container" style="margin-top:20px" :class="cnbInfo.emsCommunicationStatus === '1' ? 'zb-common-card-container' : 'cnb-common-card-container'">
|
||||
<div slot="header">
|
||||
<span class="large-title">2#{{cnbInfo.deviceName}}</span>
|
||||
<div class="status">
|
||||
<div>{{$store.state.ems.communicationStatusOptions[cnbInfo.emsCommunicationStatus]}}</div>
|
||||
<div>数据更新时间:{{cnbInfo.dataUpdateTime}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
class="common-table"
|
||||
:data="cnbInfo.meteDataDetailInfo"
|
||||
stripe
|
||||
style="width: 100%;">
|
||||
<el-table-column
|
||||
prop="category"
|
||||
label="类别">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="activePower"
|
||||
label="有功功率"
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="reactivePower"
|
||||
label="无功功率">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<div>
|
||||
<div class="pcs-tags">
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="selectedSectionKey ? 'info' : 'primary'"
|
||||
:effect="selectedSectionKey ? 'plain' : 'dark'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick('')"
|
||||
>
|
||||
全部
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-for="(group, index) in sectionGroups"
|
||||
:key="index + 'dbTag'"
|
||||
size="small"
|
||||
:type="selectedSectionKey === group.sectionKey ? 'primary' : 'info'"
|
||||
:effect="selectedSectionKey === group.sectionKey ? 'dark' : 'plain'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick(group.sectionKey)"
|
||||
>
|
||||
{{ group.displayName || "电表" }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-card
|
||||
v-for="(group, index) in filteredSectionGroups"
|
||||
:key="index + 'dbSection'"
|
||||
class="sbjk-card-container list running-card-container"
|
||||
shadow="always"
|
||||
>
|
||||
<div slot="header">
|
||||
<span class="large-title">{{ group.displayName || "电表" }}</span>
|
||||
<div class="info">
|
||||
<div>状态:{{ group.statusText }}</div>
|
||||
<div>数据更新时间:{{ group.updateTimeText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-row class="device-info-row">
|
||||
<el-col
|
||||
v-for="(item, dataIndex) in group.items"
|
||||
:key="dataIndex + 'dbField'"
|
||||
:span="8"
|
||||
class="device-info-col"
|
||||
:class="{ 'field-disabled': !item.pointId }"
|
||||
>
|
||||
<div class="field-click-wrapper" @click="handleFieldClick(item)">
|
||||
<span class="left">{{ item.fieldName }}</span>
|
||||
<span class="right">
|
||||
<i v-if="isPointLoading(item.fieldValue)" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ displayValue(item.fieldValue) | formatNumber }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-dialog
|
||||
:visible.sync="curveDialogVisible"
|
||||
:title="curveDialogTitle"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
class="ems-dialog"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@opened="handleCurveDialogOpened"
|
||||
@closed="handleCurveDialogClosed"
|
||||
>
|
||||
<div class="curve-tools">
|
||||
<el-date-picker
|
||||
v-model="curveCustomRange"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
style="width: 440px"
|
||||
/>
|
||||
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
|
||||
</div>
|
||||
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getAmmeterDataList} from '@/api/ems/dzjk'
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import { getProjectDisplayData } from "@/api/ems/dzjk";
|
||||
import { getDeviceList, getPointConfigCurve } from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
name:'DzjkSbjkDb',
|
||||
mixins:[getQuerySiteId],
|
||||
name: "DzjkSbjkDb",
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
zbInfo:{},
|
||||
cnbInfo:{},
|
||||
}
|
||||
loading: false,
|
||||
displayData: [],
|
||||
selectedSectionKey: "",
|
||||
ammeterDeviceList: [],
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
methods:{
|
||||
init(){
|
||||
this.loading = true
|
||||
getAmmeterDataList(this.siteId).then(response => {
|
||||
this.zbInfo =JSON.parse(JSON.stringify(response?.data?.ammeterLoadData || {}));
|
||||
this.cnbInfo =JSON.parse(JSON.stringify(response?.data?.ammeterMeteData || {}));
|
||||
}).finally(() => {this.loading = false})
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
computed: {
|
||||
moduleDisplayData() {
|
||||
return (this.displayData || []).filter((item) => item.menuCode === "SBJK_DB");
|
||||
},
|
||||
dbTemplateFields() {
|
||||
const source = this.moduleDisplayData || [];
|
||||
const result = [];
|
||||
const seen = new Set();
|
||||
source.forEach((item) => {
|
||||
const fieldName = String(item?.fieldName || "").trim();
|
||||
if (!fieldName || seen.has(fieldName)) {
|
||||
return;
|
||||
}
|
||||
seen.add(fieldName);
|
||||
result.push(fieldName);
|
||||
});
|
||||
return result.length > 0 ? result : this.fallbackFields;
|
||||
},
|
||||
sectionGroups() {
|
||||
const source = this.moduleDisplayData || [];
|
||||
const devices = (this.ammeterDeviceList || []).length > 0
|
||||
? this.ammeterDeviceList
|
||||
: [{ deviceId: "", deviceName: "电表" }];
|
||||
|
||||
}
|
||||
}
|
||||
return devices.map((device, index) => {
|
||||
const deviceId = String(device?.deviceId || device?.id || "").trim();
|
||||
const sectionKey = deviceId || `AMMETER_${index}`;
|
||||
const displayName = String(device?.deviceName || device?.name || deviceId || `电表${index + 1}`).trim();
|
||||
const exactRows = source.filter((item) => String(item?.deviceId || "").trim() === deviceId);
|
||||
const fallbackRows = source.filter((item) => !String(item?.deviceId || "").trim());
|
||||
|
||||
const exactValueMap = {};
|
||||
exactRows.forEach((item) => {
|
||||
const key = String(item?.fieldName || "").trim();
|
||||
if (key) {
|
||||
exactValueMap[key] = item;
|
||||
}
|
||||
});
|
||||
const fallbackValueMap = {};
|
||||
fallbackRows.forEach((item) => {
|
||||
const key = String(item?.fieldName || "").trim();
|
||||
if (key && fallbackValueMap[key] === undefined) {
|
||||
fallbackValueMap[key] = item;
|
||||
}
|
||||
});
|
||||
|
||||
const items = (this.dbTemplateFields || []).map((fieldName) => {
|
||||
const row = exactValueMap[fieldName] || fallbackValueMap[fieldName] || {};
|
||||
return {
|
||||
fieldName,
|
||||
fieldValue: row.fieldValue,
|
||||
valueTime: row.valueTime,
|
||||
pointId: String(row?.dataPoint || "").trim(),
|
||||
raw: row,
|
||||
};
|
||||
});
|
||||
|
||||
const statusItem = (items || []).find((it) => String(it.fieldName || "").includes("状态"));
|
||||
const timestamps = [...exactRows, ...fallbackRows]
|
||||
.map((it) => new Date(it?.valueTime).getTime())
|
||||
.filter((ts) => !isNaN(ts));
|
||||
|
||||
return {
|
||||
sectionName: displayName,
|
||||
sectionKey,
|
||||
displayName,
|
||||
deviceId,
|
||||
items,
|
||||
statusText: this.displayValue(statusItem ? statusItem.fieldValue : "-"),
|
||||
updateTimeText: timestamps.length > 0 ? this.formatDate(new Date(Math.max(...timestamps))) : "-",
|
||||
};
|
||||
});
|
||||
},
|
||||
displaySectionGroups() {
|
||||
if (this.sectionGroups.length > 0) {
|
||||
return this.sectionGroups;
|
||||
}
|
||||
return [
|
||||
{
|
||||
sectionName: "电参量",
|
||||
sectionKey: "电参量",
|
||||
displayName: "电表",
|
||||
items: this.fallbackFields.map((fieldName) => ({ fieldName, fieldValue: "-" })),
|
||||
statusText: "-",
|
||||
updateTimeText: "-",
|
||||
},
|
||||
];
|
||||
},
|
||||
filteredSectionGroups() {
|
||||
const groups = this.displaySectionGroups || [];
|
||||
if (!this.selectedSectionKey) {
|
||||
return groups;
|
||||
}
|
||||
return groups.filter((group) => group.sectionKey === this.selectedSectionKey);
|
||||
},
|
||||
fallbackFields() {
|
||||
return [
|
||||
"正向有功电能",
|
||||
"反向有功电能",
|
||||
"正向无功电能",
|
||||
"反向无功电能",
|
||||
"有功功率",
|
||||
"无功功率",
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleFieldClick(item) {
|
||||
const pointId = String(item?.pointId || item?.raw?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
this.openCurveDialog({
|
||||
pointId,
|
||||
title: item?.fieldName || pointId,
|
||||
});
|
||||
},
|
||||
openCurveDialog({ pointId, title }) {
|
||||
const range = this.getDefaultCurveRange();
|
||||
this.curveCustomRange = range;
|
||||
this.curveDialogTitle = `点位曲线 - ${title || pointId}`;
|
||||
this.curveQuery = {
|
||||
siteId: this.siteId,
|
||||
pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: range[0],
|
||||
endTime: range[1],
|
||||
};
|
||||
this.curveDialogVisible = true;
|
||||
},
|
||||
handleCurveDialogOpened() {
|
||||
if (!this.curveChart && this.$refs.curveChartRef) {
|
||||
this.curveChart = echarts.init(this.$refs.curveChartRef);
|
||||
}
|
||||
this.loadCurveData();
|
||||
},
|
||||
handleCurveDialogClosed() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
this.curveLoading = false;
|
||||
},
|
||||
getDefaultCurveRange() {
|
||||
const end = new Date();
|
||||
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return [this.formatDateTime(start), this.formatDateTime(end)];
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const d = new Date(date);
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
},
|
||||
formatCurveTime(value) {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return "";
|
||||
}
|
||||
const raw = String(value).trim();
|
||||
const normalized = raw
|
||||
.replace("T", " ")
|
||||
.replace(/\.\d+/, "")
|
||||
.replace(/Z$/, "")
|
||||
.replace(/([+-]\d{2}:?\d{2})$/, "")
|
||||
.trim();
|
||||
const matched = normalized.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
|
||||
if (matched) {
|
||||
return `${matched[1]} ${matched[2]}`;
|
||||
}
|
||||
return normalized.slice(0, 16);
|
||||
},
|
||||
loadCurveData() {
|
||||
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
|
||||
this.$message.warning("点位信息不完整,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
if (!this.curveCustomRange || this.curveCustomRange.length !== 2) {
|
||||
this.$message.warning("请选择查询时间范围");
|
||||
return;
|
||||
}
|
||||
this.curveQuery.startTime = this.curveCustomRange[0];
|
||||
this.curveQuery.endTime = this.curveCustomRange[1];
|
||||
const query = {
|
||||
siteId: this.curveQuery.siteId,
|
||||
pointId: this.curveQuery.pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: this.curveQuery.startTime,
|
||||
endTime: this.curveQuery.endTime,
|
||||
};
|
||||
this.curveLoading = true;
|
||||
getPointConfigCurve(query)
|
||||
.then((response) => {
|
||||
const rows = response?.data || [];
|
||||
this.renderCurveChart(rows);
|
||||
})
|
||||
.catch(() => {
|
||||
this.renderCurveChart([]);
|
||||
})
|
||||
.finally(() => {
|
||||
this.curveLoading = false;
|
||||
});
|
||||
},
|
||||
renderCurveChart(rows = []) {
|
||||
if (!this.curveChart) return;
|
||||
const xData = rows.map((item) => this.formatCurveTime(item.dataTime));
|
||||
const yData = rows.map((item) => item.pointValue);
|
||||
this.curveChart.clear();
|
||||
this.curveChart.setOption({
|
||||
legend: {},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.curveDialogTitle,
|
||||
type: "line",
|
||||
data: yData,
|
||||
connectNulls: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!rows.length) {
|
||||
this.$message.warning("当前时间范围暂无曲线数据");
|
||||
}
|
||||
},
|
||||
handleTagClick(sectionKey) {
|
||||
this.selectedSectionKey = sectionKey || "";
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === undefined || value === null || value === "" ? "-" : value;
|
||||
},
|
||||
isPointLoading(value) {
|
||||
return this.loading && (value === undefined || value === null || value === "" || value === "-");
|
||||
},
|
||||
formatDate(date) {
|
||||
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
||||
return "-";
|
||||
}
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())} ${p(date.getHours())}:${p(
|
||||
date.getMinutes()
|
||||
)}:${p(date.getSeconds())}`;
|
||||
},
|
||||
resolveDbDisplayName(sectionName) {
|
||||
const key = String(sectionName || "").trim();
|
||||
if (!key) {
|
||||
return "电表";
|
||||
}
|
||||
const list = this.ammeterDeviceList || [];
|
||||
const matched = list.find((item) => {
|
||||
const deviceId = String(item.deviceId || item.id || "").trim();
|
||||
const deviceName = String(item.deviceName || item.name || "").trim();
|
||||
return key === deviceId || key === deviceName;
|
||||
});
|
||||
if (matched) {
|
||||
return matched.deviceName || matched.name || key;
|
||||
}
|
||||
return key;
|
||||
},
|
||||
getAmmeterDeviceList() {
|
||||
return getDeviceList(this.siteId)
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
this.ammeterDeviceList = list.filter((item) => item.deviceCategory === "AMMETER");
|
||||
})
|
||||
.catch(() => {
|
||||
this.ammeterDeviceList = [];
|
||||
});
|
||||
},
|
||||
updateData() {
|
||||
this.loading = true;
|
||||
Promise.all([getProjectDisplayData(this.siteId), this.getAmmeterDeviceList()])
|
||||
.then(([response]) => {
|
||||
this.displayData = response?.data || [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.updateData();
|
||||
this.updateInterval(this.updateData);
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.zb-common-card-container,.cnb-common-card-container{
|
||||
::v-deep{
|
||||
.el-card__header{
|
||||
padding:10px 14px;
|
||||
background-color: #FC6B69;
|
||||
color:#ffffff;
|
||||
position: relative;
|
||||
}
|
||||
.sbjk-card-container {
|
||||
&.list:not(:last-child) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.info {
|
||||
float: right;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
.cnb-common-card-container{
|
||||
margin-top:25px;
|
||||
::v-deep{
|
||||
.el-card__header{
|
||||
background-color: #05AEA3;
|
||||
}
|
||||
}
|
||||
}
|
||||
.status{
|
||||
position: absolute;
|
||||
right:14px;
|
||||
top:50%;
|
||||
transform: translateY(-50%);
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
|
||||
.pcs-tags {
|
||||
margin: 0 0 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pcs-tag-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.device-info-col {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.field-click-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.device-info-col.field-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.curve-tools {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.point-loading-icon {
|
||||
color: #409eff;
|
||||
display: inline-block;
|
||||
transform-origin: center;
|
||||
animation: pointLoadingSpinPulse 1.1s linear infinite;
|
||||
}
|
||||
@keyframes pointLoadingSpinPulse {
|
||||
0% { opacity: 0.45; transform: rotate(0deg) scale(0.9); }
|
||||
50% { opacity: 1; transform: rotate(180deg) scale(1.08); }
|
||||
100% { opacity: 0.45; transform: rotate(360deg) scale(0.9); }
|
||||
}
|
||||
</style>
|
||||
|
||||
140
src/views/ems/dzjk/sbjk/dh/index.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div>
|
||||
<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">
|
||||
<i v-if="isPointLoading(item[tempDataItem.attr])" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ displayValue(item[tempDataItem.attr]) }}<span v-html="tempDataItem.unit"></span></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: '℃'},
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
displayValue(value) {
|
||||
return value === undefined || value === null || value === "" ? "-" : value;
|
||||
},
|
||||
isPointLoading(value) {
|
||||
return this.loading && (value === undefined || value === null || value === "" || value === "-");
|
||||
},
|
||||
// 查看设备电位表格
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.point-loading-icon {
|
||||
color: #409eff;
|
||||
display: inline-block;
|
||||
transform-origin: center;
|
||||
animation: pointLoadingSpinPulse 1.1s linear infinite;
|
||||
}
|
||||
@keyframes pointLoadingSpinPulse {
|
||||
0% { opacity: 0.45; transform: rotate(0deg) scale(0.9); }
|
||||
50% { opacity: 1; transform: rotate(180deg) scale(1.08); }
|
||||
100% { opacity: 0.45; transform: rotate(360deg) scale(0.9); }
|
||||
}
|
||||
</style>
|
||||
@ -7,167 +7,193 @@
|
||||
lock-scroll
|
||||
append-to-body
|
||||
width="700px"
|
||||
class="ems-dialog"
|
||||
class="ems-dialog chart-detail-dialog"
|
||||
:before-close="handleColsed"
|
||||
>
|
||||
<div>
|
||||
<el-form size="medium" label-width="100px" inline>
|
||||
<el-form-item label="时间选择">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
value-format="yyyy-MM-dd"
|
||||
:picker-options="pickerOptions"
|
||||
:default-value="defaultDateRange"
|
||||
end-placeholder="结束时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="getData">搜索</el-button>
|
||||
<el-button @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div id="lineChart" style="height: 360px;width: 100%;"></div>
|
||||
</div>
|
||||
<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 * 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:{
|
||||
siteId: "",
|
||||
deviceId: "",
|
||||
clusterDeviceId: "",
|
||||
dataType: "", //展示的数据类型 空值展示所有数据
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
dialogTableVisible: false,
|
||||
dateRange: [],
|
||||
defaultDateRange:[]
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleColsed(done){
|
||||
// 更新时间范围 重置图表
|
||||
updateDate(data) {
|
||||
this.dateRange = data || [];
|
||||
this.getData();
|
||||
},
|
||||
handleColsed(done) {
|
||||
if (!this.chart) {
|
||||
return done()
|
||||
return done();
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
done()
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
done();
|
||||
},
|
||||
getData(){
|
||||
if(this.loading) return
|
||||
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()
|
||||
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();
|
||||
});
|
||||
},
|
||||
// 重置
|
||||
onReset(){
|
||||
this.dateRange=[]
|
||||
this.getData()
|
||||
},
|
||||
initChart({siteId, clusterDeviceId, deviceId},dataType) {
|
||||
this.siteId=siteId
|
||||
this.clusterDeviceId=clusterDeviceId
|
||||
this.deviceId=deviceId
|
||||
this.dataType=dataType
|
||||
this.dateRange=[]
|
||||
this.dialogTableVisible = true
|
||||
this.$nextTick(()=>{
|
||||
!this.chart && (this.chart = echarts.init(document.querySelector('#lineChart')))
|
||||
this.getData()
|
||||
})
|
||||
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=[
|
||||
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:'电压',
|
||||
type: 'line',
|
||||
|
||||
},{
|
||||
name:'温度',
|
||||
type: 'line',
|
||||
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:'SOC',
|
||||
type: 'line',
|
||||
|
||||
},{
|
||||
name:'SOH',
|
||||
type: 'line',
|
||||
}]
|
||||
name: "温度",
|
||||
type: "line",
|
||||
},
|
||||
{
|
||||
name: "SOC",
|
||||
type: "line",
|
||||
},
|
||||
{
|
||||
name: "SOH",
|
||||
type: "line",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
this.chart && 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
|
||||
},
|
||||
series
|
||||
})
|
||||
}
|
||||
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(){
|
||||
const now = new Date();
|
||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
this.defaultDateRange = [lastMonth, now];
|
||||
mounted() {},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.chart-detail-dialog {
|
||||
::v-deep {
|
||||
.el-dialog__body {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</style>
|
||||
|
||||
192
src/views/ems/dzjk/sbjk/dtdc/List.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<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, 'voltage')">
|
||||
{{ item.voltage }}V
|
||||
</div>
|
||||
<div class="wd pointer" @click="chartDetail(item, 'temperature')">
|
||||
{{ 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, fieldKey = "") {
|
||||
this.$emit("chart", { ...row, fieldKey });
|
||||
},
|
||||
},
|
||||
};
|
||||
</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>
|
||||
117
src/views/ems/dzjk/sbjk/dtdc/Table.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<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, 'voltage')"
|
||||
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, 'temperature')"
|
||||
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>
|
||||
<!-- <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, fieldKey = "") {
|
||||
this.$emit("chart", {...row, fieldKey});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@ -1,224 +1,547 @@
|
||||
|
||||
<template>
|
||||
<el-card v-loading="loading" shadow="always" class="common-card-container common-card-container-no-title-bg">
|
||||
<div slot="header">
|
||||
<span class="large-title">单体电池实时数据</span>
|
||||
</div>
|
||||
<!-- 搜索栏-->
|
||||
<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+'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 style="margin:30px 0;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);">-->
|
||||
<!-- <el-row style="background:#fff;margin:30px 0;">-->
|
||||
<!-- <el-col :xs="24" :sm="24" :lg="24">-->
|
||||
<!-- <bar-chart ref="barChart"/>-->
|
||||
<!-- </el-col>-->
|
||||
<!-- </el-row>-->
|
||||
<!-- </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,'voltage')"
|
||||
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,'temperature')"
|
||||
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="曲线图">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@click="chartDetail(scope.row)"
|
||||
type="text"
|
||||
size="small">
|
||||
展示
|
||||
</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>
|
||||
<chart-detail ref="chartDetail"/>
|
||||
</el-card>
|
||||
<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"
|
||||
></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" />
|
||||
<el-dialog
|
||||
:visible.sync="curveDialogVisible"
|
||||
:title="curveDialogTitle"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
class="ems-dialog"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@opened="handleCurveDialogOpened"
|
||||
@closed="handleCurveDialogClosed"
|
||||
>
|
||||
<div class="curve-tools">
|
||||
<el-date-picker
|
||||
v-model="curveCustomRange"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
style="width: 440px"
|
||||
/>
|
||||
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
|
||||
</div>
|
||||
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import BarChart from './BarChart'
|
||||
import {getStackNameList, getClusterNameList, getClusterDataInfoList} from '@/api/ems/dzjk'
|
||||
import * as echarts from "echarts";
|
||||
import BarChart from "./BarChart";
|
||||
import {
|
||||
getClusterDataInfoList,
|
||||
getClusterNameList,
|
||||
getStackNameList,
|
||||
} from "@/api/ems/dzjk";
|
||||
import { getPointConfigCurve } from "@/api/ems/site";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import ChartDetail from "./ChartDetail.vue";
|
||||
import Table from "./Table.vue";
|
||||
import List from "./List.vue";
|
||||
export default {
|
||||
name:'DzjkSbjkDtdc',
|
||||
mixins:[getQuerySiteId],
|
||||
components:{BarChart, ChartDetail},
|
||||
name: "DzjkSbjkDtdc",
|
||||
mixins: [getQuerySiteId],
|
||||
components: {
|
||||
BarChart,
|
||||
ChartDetail,
|
||||
DtdcTable: Table,
|
||||
DtdcList: List,
|
||||
},
|
||||
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:''},
|
||||
stackOptions:[],//{id:'',deviceName:''}
|
||||
clusterOptions:[],//{id:'',deviceName:''}
|
||||
tableData:[],
|
||||
pageSize:10,//分页栏当前每个数据总数
|
||||
pageNum:1,//分页栏当前页数
|
||||
totalSize:0,//table表格数据总数
|
||||
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",
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
methods: {
|
||||
getFieldPointConfig(fieldKey) {
|
||||
const pointMap = {
|
||||
voltage: { pointIdKey: "voltagePointId", title: "电压 (V)" },
|
||||
temperature: { pointIdKey: "temperaturePointId", title: "温度 (℃)" },
|
||||
soc: { pointIdKey: "socPointId", title: "SOC (%)" },
|
||||
soh: { pointIdKey: "sohPointId", title: "SOH (%)" },
|
||||
};
|
||||
return pointMap[String(fieldKey || "").trim()] || null;
|
||||
},
|
||||
openCurveDialogByPointId(pointId, title) {
|
||||
const normalizedPointId = String(pointId || "").trim();
|
||||
if (!normalizedPointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
const range = this.getDefaultCurveRange();
|
||||
this.curveCustomRange = range;
|
||||
this.curveDialogTitle = `点位曲线 - ${title || normalizedPointId}`;
|
||||
this.curveQuery = {
|
||||
siteId: this.siteId,
|
||||
pointId: normalizedPointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: range[0],
|
||||
endTime: range[1],
|
||||
};
|
||||
this.curveDialogVisible = true;
|
||||
},
|
||||
handleCurveDialogOpened() {
|
||||
if (!this.curveChart && this.$refs.curveChartRef) {
|
||||
this.curveChart = echarts.init(this.$refs.curveChartRef);
|
||||
}
|
||||
this.loadCurveData();
|
||||
},
|
||||
handleCurveDialogClosed() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
this.curveLoading = false;
|
||||
},
|
||||
getDefaultCurveRange() {
|
||||
const end = new Date();
|
||||
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return [this.formatDateTime(start), this.formatDateTime(end)];
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const d = new Date(date);
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(
|
||||
d.getHours()
|
||||
)}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
},
|
||||
formatCurveTime(value) {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return "";
|
||||
}
|
||||
const raw = String(value).trim();
|
||||
const normalized = raw
|
||||
.replace("T", " ")
|
||||
.replace(/\.\d+/, "")
|
||||
.replace(/Z$/, "")
|
||||
.replace(/([+-]\d{2}:?\d{2})$/, "")
|
||||
.trim();
|
||||
const matched = normalized.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
|
||||
if (matched) {
|
||||
return `${matched[1]} ${matched[2]}`;
|
||||
}
|
||||
return normalized.slice(0, 16);
|
||||
},
|
||||
loadCurveData() {
|
||||
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
|
||||
this.$message.warning("点位信息不完整,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
if (!this.curveCustomRange || this.curveCustomRange.length !== 2) {
|
||||
this.$message.warning("请选择查询时间范围");
|
||||
return;
|
||||
}
|
||||
this.curveQuery.startTime = this.curveCustomRange[0];
|
||||
this.curveQuery.endTime = this.curveCustomRange[1];
|
||||
const query = {
|
||||
siteId: this.curveQuery.siteId,
|
||||
pointId: this.curveQuery.pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: this.curveQuery.startTime,
|
||||
endTime: this.curveQuery.endTime,
|
||||
};
|
||||
this.curveLoading = true;
|
||||
getPointConfigCurve(query)
|
||||
.then((response) => {
|
||||
const rows = response?.data || [];
|
||||
this.renderCurveChart(rows);
|
||||
})
|
||||
.catch(() => {
|
||||
this.renderCurveChart([]);
|
||||
})
|
||||
.finally(() => {
|
||||
this.curveLoading = false;
|
||||
});
|
||||
},
|
||||
renderCurveChart(rows = []) {
|
||||
if (!this.curveChart) return;
|
||||
const xData = rows.map((item) => this.formatCurveTime(item.dataTime));
|
||||
const yData = rows.map((item) => item.pointValue);
|
||||
this.curveChart.clear();
|
||||
this.curveChart.setOption({
|
||||
legend: {},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.curveDialogTitle,
|
||||
type: "line",
|
||||
data: yData,
|
||||
connectNulls: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!rows.length) {
|
||||
this.$message.warning("当前时间范围暂无曲线数据");
|
||||
}
|
||||
},
|
||||
changeMenu(menu) {
|
||||
const { activeBtn } = this;
|
||||
activeBtn !== menu && (this.activeBtn = menu);
|
||||
},
|
||||
//查看表格行图表
|
||||
chartDetail(row,dataType = ''){
|
||||
const { clusterDeviceId, deviceId} = row,{siteId} = this
|
||||
this.$refs.chartDetail.initChart({siteId,clusterDeviceId,deviceId},dataType)
|
||||
chartDetail(row = {}) {
|
||||
const config = this.getFieldPointConfig(row.fieldKey);
|
||||
if (!config) return;
|
||||
const pointId = row[config.pointIdKey];
|
||||
this.openCurveDialogByPointId(pointId, config.title);
|
||||
},
|
||||
// 分页
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
this.$nextTick(()=>{
|
||||
this.getTableData()
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.getTableData();
|
||||
});
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val
|
||||
this.$nextTick(()=>{
|
||||
this.getTableData()
|
||||
})
|
||||
this.pageNum = val;
|
||||
this.$nextTick(() => {
|
||||
this.getTableData();
|
||||
});
|
||||
},
|
||||
// 搜索
|
||||
onSearch(){
|
||||
this.pageNum =1//每次搜索从1开始搜索
|
||||
this.getTableData()
|
||||
onSearch() {
|
||||
this.pageNum = 1; //每次搜索从1开始搜索
|
||||
this.getTableData();
|
||||
},
|
||||
// 重置
|
||||
// 清空搜索栏选中数据
|
||||
// 清空电池簇列表,保留电池堆列表
|
||||
onReset(){
|
||||
this.search={stackId:'',clusterId:''}
|
||||
this.clusterOptions=[]
|
||||
this.pageNum = 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()
|
||||
changeStackId(val) {
|
||||
if (val) {
|
||||
console.log(
|
||||
"选择了电池堆,需要获取对应的电池簇",
|
||||
val,
|
||||
this.search.stackId
|
||||
);
|
||||
this.search.clusterId = "";
|
||||
this.getClusterList();
|
||||
} else {
|
||||
this.search.clusterId = "";
|
||||
this.clusterOptions = [];
|
||||
}
|
||||
},
|
||||
//表格数据
|
||||
getTableData(){
|
||||
this.loading=true;
|
||||
const {stackId:stackDeviceId,clusterId:clusterDeviceId} =this.search
|
||||
const {siteId,pageNum,pageSize}=this
|
||||
getClusterDataInfoList({stackDeviceId,clusterDeviceId,siteId,pageNum,pageSize}).then(response => {
|
||||
this.tableData=response?.rows || [];
|
||||
this.totalSize = response?.total || 0
|
||||
}).finally(()=>{
|
||||
this.loading=false;
|
||||
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 || []))
|
||||
getStackList() {
|
||||
getStackNameList(this.siteId).then((response) => {
|
||||
const list = JSON.parse(JSON.stringify(response?.data || []));
|
||||
this.stackOptions = list;
|
||||
});
|
||||
},
|
||||
getClusterList() {
|
||||
const { stackId } = this.search;
|
||||
if (!stackId) {
|
||||
this.clusterOptions = [];
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.clusterloading = true;
|
||||
const currentStackId = String(stackId);
|
||||
return getClusterNameList({
|
||||
stackDeviceId: stackId,
|
||||
siteId: this.siteId,
|
||||
})
|
||||
.then((response) => {
|
||||
// 避免用户快速切换电池堆时旧请求覆盖新数据
|
||||
if (String(this.search.stackId || "") !== currentStackId) return;
|
||||
this.clusterOptions = JSON.parse(JSON.stringify(response?.data || []));
|
||||
})
|
||||
.finally(() => {
|
||||
this.clusterloading = false;
|
||||
});
|
||||
},
|
||||
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(){
|
||||
init() {
|
||||
// 只有页面初次加载或切换站点的时候调用电池堆列表,其他情况不需要
|
||||
this.search={stackId:'',clusterId:''}//保证切换站点时,清空选择项
|
||||
this.getStackList()
|
||||
this.getTableData()
|
||||
}
|
||||
this.search = { stackId: "", clusterId: "", batteryId: "" }; //保证切换站点时,清空选择项
|
||||
this.clusterOptions = [];
|
||||
this.pageNum = 1;
|
||||
this.totalSize = 0;
|
||||
this.getStackList();
|
||||
this.getTableData();
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</style>
|
||||
|
||||
481
src/views/ems/dzjk/sbjk/ems/index.vue
Normal file
@ -0,0 +1,481 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="pcs-tags">
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="selectedSectionKey ? 'info' : 'primary'"
|
||||
:effect="selectedSectionKey ? 'plain' : 'dark'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick('')"
|
||||
>
|
||||
全部
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-for="(group, index) in sectionGroups"
|
||||
:key="index + 'emsTag'"
|
||||
size="small"
|
||||
:type="selectedSectionKey === group.sectionKey ? 'primary' : 'info'"
|
||||
:effect="selectedSectionKey === group.sectionKey ? 'dark' : 'plain'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick(group.sectionKey)"
|
||||
>
|
||||
{{ group.displayName || group.sectionName || "EMS" }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-card
|
||||
v-for="(group, index) in filteredSectionGroups"
|
||||
:key="index + 'emsSection'"
|
||||
class="sbjk-card-container list running-card-container"
|
||||
shadow="always"
|
||||
>
|
||||
<div slot="header">
|
||||
<span class="large-title">{{ group.displayName || group.sectionName || "EMS" }}</span>
|
||||
<div class="info">
|
||||
<div>状态:{{ group.statusText }}</div>
|
||||
<div>数据更新时间:{{ group.updateTimeText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-row class="device-info-row">
|
||||
<el-col
|
||||
v-for="(item, dataIndex) in group.items"
|
||||
:key="dataIndex + 'emsField'"
|
||||
:span="6"
|
||||
class="device-info-col"
|
||||
:class="{ 'field-disabled': !item.pointId }"
|
||||
>
|
||||
<div class="field-click-wrapper" @click="handleFieldClick(item)">
|
||||
<span class="left">{{ item.fieldName }}</span>
|
||||
<span class="right">
|
||||
<i v-if="isPointLoading(item.fieldValue)" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ displayValue(item.fieldValue) | formatNumber }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-dialog
|
||||
:visible.sync="curveDialogVisible"
|
||||
:title="curveDialogTitle"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
class="ems-dialog"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@opened="handleCurveDialogOpened"
|
||||
@closed="handleCurveDialogClosed"
|
||||
>
|
||||
<div class="curve-tools">
|
||||
<el-date-picker
|
||||
v-model="curveCustomRange"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
style="width: 440px"
|
||||
/>
|
||||
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
|
||||
</div>
|
||||
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import { getProjectDisplayData } from "@/api/ems/dzjk";
|
||||
import { getDeviceList, getPointConfigCurve } from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
name: "DzjkSbjkEms",
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
displayData: [],
|
||||
selectedSectionKey: "",
|
||||
emsDeviceList: [],
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
moduleDisplayData() {
|
||||
return (this.displayData || []).filter((item) => item.menuCode === "SBJK_EMS");
|
||||
},
|
||||
emsTemplateFields() {
|
||||
const source = this.moduleDisplayData || [];
|
||||
const result = [];
|
||||
const seen = new Set();
|
||||
source.forEach((item) => {
|
||||
const fieldName = String(item?.fieldName || "").trim();
|
||||
if (!fieldName || seen.has(fieldName)) {
|
||||
return;
|
||||
}
|
||||
seen.add(fieldName);
|
||||
result.push(fieldName);
|
||||
});
|
||||
return result.length > 0 ? result : this.fallbackFields;
|
||||
},
|
||||
sectionGroups() {
|
||||
const source = this.moduleDisplayData || [];
|
||||
const devices = (this.emsDeviceList || []).length > 0
|
||||
? this.emsDeviceList
|
||||
: [{ deviceId: "", deviceName: "EMS" }];
|
||||
|
||||
return devices.map((device, index) => {
|
||||
const deviceId = String(device?.deviceId || device?.id || "").trim();
|
||||
const sectionKey = deviceId || `EMS_${index}`;
|
||||
const displayName = String(device?.deviceName || device?.name || deviceId || `EMS${index + 1}`).trim();
|
||||
const exactRows = source.filter((item) => String(item?.deviceId || "").trim() === deviceId);
|
||||
const fallbackRows = source.filter((item) => !String(item?.deviceId || "").trim());
|
||||
|
||||
const exactValueMap = {};
|
||||
exactRows.forEach((item) => {
|
||||
const key = String(item?.fieldName || "").trim();
|
||||
if (key) {
|
||||
exactValueMap[key] = item;
|
||||
}
|
||||
});
|
||||
const fallbackValueMap = {};
|
||||
fallbackRows.forEach((item) => {
|
||||
const key = String(item?.fieldName || "").trim();
|
||||
if (key && fallbackValueMap[key] === undefined) {
|
||||
fallbackValueMap[key] = item;
|
||||
}
|
||||
});
|
||||
|
||||
const items = (this.emsTemplateFields || []).map((fieldName) => {
|
||||
const row = exactValueMap[fieldName] || fallbackValueMap[fieldName] || {};
|
||||
return {
|
||||
fieldName,
|
||||
fieldValue: row.fieldValue,
|
||||
valueTime: row.valueTime,
|
||||
pointId: String(row?.dataPoint || "").trim(),
|
||||
raw: row,
|
||||
};
|
||||
});
|
||||
|
||||
const statusItem = (items || []).find((it) => String(it.fieldName || "").includes("状态"));
|
||||
const timestamps = [...exactRows, ...fallbackRows]
|
||||
.map((it) => new Date(it?.valueTime).getTime())
|
||||
.filter((ts) => !isNaN(ts));
|
||||
|
||||
return {
|
||||
sectionName: displayName,
|
||||
sectionKey,
|
||||
displayName,
|
||||
deviceId,
|
||||
items,
|
||||
statusText: this.displayValue(statusItem ? statusItem.fieldValue : "-"),
|
||||
updateTimeText: timestamps.length > 0 ? this.formatDate(new Date(Math.max(...timestamps))) : "-",
|
||||
};
|
||||
});
|
||||
},
|
||||
displaySectionGroups() {
|
||||
if (this.sectionGroups.length > 0) {
|
||||
return this.sectionGroups;
|
||||
}
|
||||
return [
|
||||
{
|
||||
sectionName: "EMS",
|
||||
items: this.fallbackFields.map((fieldName) => ({ fieldName, fieldValue: "-" })),
|
||||
statusText: "-",
|
||||
updateTimeText: "-",
|
||||
},
|
||||
];
|
||||
},
|
||||
filteredSectionGroups() {
|
||||
const groups = this.displaySectionGroups || [];
|
||||
if (!this.selectedSectionKey) {
|
||||
return groups;
|
||||
}
|
||||
return groups.filter((group) => group.sectionKey === this.selectedSectionKey);
|
||||
},
|
||||
fallbackFields() {
|
||||
return [
|
||||
"BMS1SOC",
|
||||
"BMS2SOC",
|
||||
"BMS3SOC",
|
||||
"BMS4SOC",
|
||||
"PCS-1有功功率",
|
||||
"PCS-2有功功率",
|
||||
"PCS-3有功功率",
|
||||
"PCS-4有功功率",
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleFieldClick(item) {
|
||||
const pointId = String(item?.pointId || item?.raw?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
this.openCurveDialog({
|
||||
pointId,
|
||||
title: item?.fieldName || pointId,
|
||||
});
|
||||
},
|
||||
openCurveDialog({ pointId, title }) {
|
||||
const range = this.getDefaultCurveRange();
|
||||
this.curveCustomRange = range;
|
||||
this.curveDialogTitle = `点位曲线 - ${title || pointId}`;
|
||||
this.curveQuery = {
|
||||
siteId: this.siteId,
|
||||
pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: range[0],
|
||||
endTime: range[1],
|
||||
};
|
||||
this.curveDialogVisible = true;
|
||||
},
|
||||
handleCurveDialogOpened() {
|
||||
if (!this.curveChart && this.$refs.curveChartRef) {
|
||||
this.curveChart = echarts.init(this.$refs.curveChartRef);
|
||||
}
|
||||
this.loadCurveData();
|
||||
},
|
||||
handleCurveDialogClosed() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
this.curveLoading = false;
|
||||
},
|
||||
getDefaultCurveRange() {
|
||||
const end = new Date();
|
||||
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return [this.formatDateTime(start), this.formatDateTime(end)];
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const d = new Date(date);
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
},
|
||||
formatCurveTime(value) {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return "";
|
||||
}
|
||||
const raw = String(value).trim();
|
||||
const normalized = raw
|
||||
.replace("T", " ")
|
||||
.replace(/\.\d+/, "")
|
||||
.replace(/Z$/, "")
|
||||
.replace(/([+-]\d{2}:?\d{2})$/, "")
|
||||
.trim();
|
||||
const matched = normalized.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
|
||||
if (matched) {
|
||||
return `${matched[1]} ${matched[2]}`;
|
||||
}
|
||||
return normalized.slice(0, 16);
|
||||
},
|
||||
loadCurveData() {
|
||||
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
|
||||
this.$message.warning("点位信息不完整,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
if (!this.curveCustomRange || this.curveCustomRange.length !== 2) {
|
||||
this.$message.warning("请选择查询时间范围");
|
||||
return;
|
||||
}
|
||||
this.curveQuery.startTime = this.curveCustomRange[0];
|
||||
this.curveQuery.endTime = this.curveCustomRange[1];
|
||||
const query = {
|
||||
siteId: this.curveQuery.siteId,
|
||||
pointId: this.curveQuery.pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: this.curveQuery.startTime,
|
||||
endTime: this.curveQuery.endTime,
|
||||
};
|
||||
this.curveLoading = true;
|
||||
getPointConfigCurve(query)
|
||||
.then((response) => {
|
||||
const rows = response?.data || [];
|
||||
this.renderCurveChart(rows);
|
||||
})
|
||||
.catch(() => {
|
||||
this.renderCurveChart([]);
|
||||
})
|
||||
.finally(() => {
|
||||
this.curveLoading = false;
|
||||
});
|
||||
},
|
||||
renderCurveChart(rows = []) {
|
||||
if (!this.curveChart) return;
|
||||
const xData = rows.map((item) => this.formatCurveTime(item.dataTime));
|
||||
const yData = rows.map((item) => item.pointValue);
|
||||
this.curveChart.clear();
|
||||
this.curveChart.setOption({
|
||||
legend: {},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.curveDialogTitle,
|
||||
type: "line",
|
||||
data: yData,
|
||||
connectNulls: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!rows.length) {
|
||||
this.$message.warning("当前时间范围暂无曲线数据");
|
||||
}
|
||||
},
|
||||
handleTagClick(sectionKey) {
|
||||
this.selectedSectionKey = sectionKey || "";
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === undefined || value === null || value === "" ? "-" : value;
|
||||
},
|
||||
isPointLoading(value) {
|
||||
return this.loading && (value === undefined || value === null || value === "" || value === "-");
|
||||
},
|
||||
formatDate(date) {
|
||||
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
||||
return "-";
|
||||
}
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())} ${p(date.getHours())}:${p(
|
||||
date.getMinutes()
|
||||
)}:${p(date.getSeconds())}`;
|
||||
},
|
||||
getEmsDeviceList() {
|
||||
return getDeviceList(this.siteId)
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
this.emsDeviceList = list.filter((item) => item.deviceCategory === "EMS");
|
||||
})
|
||||
.catch(() => {
|
||||
this.emsDeviceList = [];
|
||||
});
|
||||
},
|
||||
updateData() {
|
||||
this.loading = true;
|
||||
Promise.all([getProjectDisplayData(this.siteId), this.getEmsDeviceList()])
|
||||
.then(([response]) => {
|
||||
this.displayData = response?.data || [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.updateData();
|
||||
this.updateInterval(this.updateData);
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sbjk-card-container {
|
||||
&.list:not(:last-child) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.info {
|
||||
float: right;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.pcs-tags {
|
||||
margin: 0 0 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pcs-tag-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.device-info-col {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.field-click-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.device-info-col.field-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.curve-tools {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.point-loading-icon {
|
||||
color: #409eff;
|
||||
display: inline-block;
|
||||
transform-origin: center;
|
||||
animation: pointLoadingSpinPulse 1.1s linear infinite;
|
||||
}
|
||||
@keyframes pointLoadingSpinPulse {
|
||||
0% { opacity: 0.45; transform: rotate(0deg) scale(0.9); }
|
||||
50% { opacity: 1; transform: rotate(180deg) scale(1.08); }
|
||||
100% { opacity: 0.45; transform: rotate(360deg) scale(0.9); }
|
||||
}
|
||||
</style>
|
||||
@ -1,52 +1,70 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container sbjk-ems-dashboard-editor-container">
|
||||
<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 childrenRoute" :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 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[0].children.find(item=> item.name==='DzjkSbjk').children//获取到单站监控-设备监控下面的字路由
|
||||
console.log('设备监控子路由',childrenRoute)
|
||||
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:''
|
||||
activeMenu:'',
|
||||
loading:false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('当前设备监控页面路由',this.$route)
|
||||
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;
|
||||
padding:0;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
.sbjk-ems-content-container{
|
||||
margin-top:0;
|
||||
padding-top:0;
|
||||
@ -54,4 +72,3 @@ export default {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -1,203 +1,709 @@
|
||||
|
||||
<template>
|
||||
<div class="pcs-ems-dashboard-editor-container" v-loading="loading">
|
||||
<!-- 顶部六个方块-->
|
||||
<real-time-base-info :data="runningHeadData"/>
|
||||
<!-- 内容-->
|
||||
<el-container class="pcs-container" v-for="(pcsItem,pcsIndex) in pcsList" :key="pcsIndex+'PcsHome'">
|
||||
<!-- 背景颜色根据工作状态来展示-->
|
||||
<el-header class="pcs-header" :class="pcsItem.workStatus === '1' ? 'warn' : pcsItem.workStatus === '2' ? 'close' : ''">
|
||||
<div class="pcs-title">{{pcsItem.deviceName}}</div>
|
||||
<div class="pcs-status">
|
||||
<div>{{$store.state.ems.communicationStatusOptions[pcsItem.communicationStatus]}}</div>
|
||||
<div>数据更新时间:{{pcsItem.dataUpdateTime}}</div>
|
||||
<div class="pcs-ems-dashboard-editor-container">
|
||||
<div class="pcs-tags">
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="selectedPcsId ? 'info' : 'primary'"
|
||||
:effect="selectedPcsId ? 'plain' : 'dark'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick('')"
|
||||
>
|
||||
全部
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-for="(item, index) in pcsDeviceList"
|
||||
:key="index + 'pcsTag'"
|
||||
size="small"
|
||||
:type="selectedPcsId === (item.deviceId || item.id) ? 'primary' : 'info'"
|
||||
:effect="selectedPcsId === (item.deviceId || item.id) ? 'dark' : 'plain'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick(item.deviceId || item.id || '')"
|
||||
>
|
||||
{{ item.deviceName || item.deviceId || item.id || 'PCS' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div
|
||||
v-for="(pcsItem, pcsIndex) in filteredPcsList"
|
||||
: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 v-if="(($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[pcsItem.communicationStatus]">
|
||||
{{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[pcsItem.communicationStatus] }}
|
||||
</div>
|
||||
<div>数据更新时间:{{ pcsItem.dataUpdateTime }}</div>
|
||||
</div>
|
||||
<div class="alarm">
|
||||
<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="pcs-btns">
|
||||
<el-badge :value="pcsItem.alarmNum || 0" class="item">
|
||||
<i class="el-icon-message-solid" style="font-size: 26px;color: #fff;display: block;"></i>
|
||||
</el-badge>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main style="padding: 0">
|
||||
<div class="descriptions-main">
|
||||
<el-descriptions direction="vertical" :column="4" :colon="false">
|
||||
<el-descriptions-item labelClassName="descriptions-label" :contentClassName="`descriptions-direction ${pcsItem.workStatus === '0' ? 'save' :'danger'}`" :span="1" label="工作状态">{{$store.state.ems.workStatusOptions[pcsItem.workStatus]}}</el-descriptions-item>
|
||||
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction" :span="1" label="并网状态">{{$store.state.ems.gridStatusOptions[pcsItem.gridStatus]}}</el-descriptions-item>
|
||||
<el-descriptions-item labelClassName="descriptions-label" :contentClassName="`descriptions-direction ${pcsItem.deviceStatus === '0' ? 'save' : 'danger'}`" :span="1" label="设备状态">{{$store.state.ems.deviceStatusOptions[pcsItem.deviceStatus]}}</el-descriptions-item>
|
||||
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction" :span="1" label="控制模式">{{$store.state.ems.controlModeOptions[pcsItem.controlMode]}}</el-descriptions-item>
|
||||
<el-descriptions :colon="false" :column="4" direction="vertical">
|
||||
<el-descriptions-item
|
||||
contentClassName="descriptions-direction work-status"
|
||||
:span="1"
|
||||
label="工作状态"
|
||||
labelClassName="descriptions-label"
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'workStatus') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'workStatus', '工作状态')"
|
||||
>
|
||||
{{ formatDictValue((PCSWorkStatusOptions || {}), pcsItem.workStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
:span="1"
|
||||
contentClassName="descriptions-direction"
|
||||
label="并网状态"
|
||||
labelClassName="descriptions-label"
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'gridStatus') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'gridStatus', '并网状态')"
|
||||
>
|
||||
{{ formatDictValue((($store.state.ems && $store.state.ems.gridStatusOptions) || {}), pcsItem.gridStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
:contentClassName="`descriptions-direction ${
|
||||
pcsItem.deviceStatus === '1' ? 'save' : 'danger'
|
||||
}`"
|
||||
:span="1"
|
||||
label="设备状态"
|
||||
labelClassName="descriptions-label"
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'deviceStatus') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'deviceStatus', '设备状态')"
|
||||
>
|
||||
{{ formatDictValue((($store.state.ems && $store.state.ems.deviceStatusOptions) || {}), pcsItem.deviceStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
:span="1"
|
||||
contentClassName="descriptions-direction"
|
||||
label="控制模式"
|
||||
labelClassName="descriptions-label"
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'controlMode') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'controlMode', '控制模式')"
|
||||
>
|
||||
{{ formatDictValue((($store.state.ems && $store.state.ems.controlModeOptions) || {}), pcsItem.controlMode) }}
|
||||
</span>
|
||||
</el-descriptions-item
|
||||
>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div class="descriptions-main descriptions-main-bg-color">
|
||||
<el-descriptions labelClassName="descriptions-label" contentClassName="descriptions-direction" direction="vertical" :column="4" :colon="false">
|
||||
<el-descriptions-item v-for="(item,index) in infoData" :key="index+'pcsInfoData'" :span="1" :label="item.label">{{pcsItem[item.attr] | formatNumber}} <span v-if="item.unit" v-html="item.unit"></span></el-descriptions-item>
|
||||
<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"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, item.attr) }"
|
||||
@click="handlePcsFieldClick(pcsItem, item.attr, item.label)"
|
||||
>
|
||||
<i v-if="isPointLoading(pcsItem[item.attr])" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ displayValue(pcsItem[item.attr]) | formatNumber }}</span>
|
||||
<span v-if="item.unit" v-html="item.unit"></span>
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div class="descriptions-main" v-for="(item,index) in pcsItem.pcsBranchInfoList" :key="index+'pcsBranchInfoList'">
|
||||
<el-descriptions labelClassName="descriptions-label" contentClassName="descriptions-direction keep" direction="vertical" :column="4" :colon="false">
|
||||
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction keep" :span="4" :label="'支路'+(index+1)">{{item.dischargeStatus}}</el-descriptions-item>
|
||||
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction" :span="1" label="直流功率">{{item.dcPower}}kW</el-descriptions-item>
|
||||
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction" :span="1" label="直流电压">{{item.dcVoltage}}V</el-descriptions-item>
|
||||
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction" :span="1" label="直流电流">{{item.dcCurrent}}A</el-descriptions-item>
|
||||
<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"
|
||||
:class="{ 'field-disabled': !item.dcPowerPointId }"
|
||||
@click="openCurveDialogByPointId(item.dcPowerPointId, '直流功率')"
|
||||
>{{ item.dcPower }}kW</span
|
||||
>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item
|
||||
:span="1"
|
||||
contentClassName="descriptions-direction"
|
||||
label="直流电压"
|
||||
labelClassName="descriptions-label"
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !item.dcVoltagePointId }"
|
||||
@click="openCurveDialogByPointId(item.dcVoltagePointId, '直流电压')"
|
||||
>{{ item.dcVoltage }}V</span
|
||||
>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item
|
||||
:span="1"
|
||||
contentClassName="descriptions-direction"
|
||||
label="直流电流"
|
||||
labelClassName="descriptions-label"
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !item.dcCurrentPointId }"
|
||||
@click="openCurveDialogByPointId(item.dcCurrentPointId, '直流电流')"
|
||||
>{{ item.dcCurrent }}A</span
|
||||
>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<el-empty v-show="pcsList.length<=0" :image-size="200"></el-empty>
|
||||
|
||||
</el-card>
|
||||
</div>
|
||||
<point-table ref="pointTable"/>
|
||||
<el-dialog
|
||||
:visible.sync="curveDialogVisible"
|
||||
:title="curveDialogTitle"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
class="ems-dialog"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@opened="handleCurveDialogOpened"
|
||||
@closed="handleCurveDialogClosed"
|
||||
>
|
||||
<div class="curve-tools">
|
||||
<el-date-picker
|
||||
v-model="curveCustomRange"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
style="width: 440px"
|
||||
/>
|
||||
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
|
||||
</div>
|
||||
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RealTimeBaseInfo from "./../RealTimeBaseInfo.vue";
|
||||
import * as echarts from "echarts";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getRunningHeadInfo,getPcsDetailInfo} from '@/api/ems/dzjk'
|
||||
import {getPcsNameList, getProjectDisplayData} from "@/api/ems/dzjk";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import {mapState} from "vuex";
|
||||
import {getPointConfigCurve} from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
name:'DzjkSbjkPcs',
|
||||
components:{RealTimeBaseInfo},
|
||||
mixins:[getQuerySiteId],
|
||||
name: "DzjkSbjkPcs",
|
||||
components: {PointTable},
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
computed: {
|
||||
...mapState({
|
||||
PCSWorkStatusOptions: state => state?.ems?.PCSWorkStatusOptions || {},
|
||||
}),
|
||||
filteredPcsList() {
|
||||
if (!this.selectedPcsId) {
|
||||
return this.pcsList || [];
|
||||
}
|
||||
return (this.pcsList || []).filter(item => item.deviceId === this.selectedPcsId);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
runningHeadData:{},//运行信息
|
||||
pcsList:[],
|
||||
infoData:[
|
||||
{label:'总交流有功电率',attr:'totalActivePower',unit:'kW'},
|
||||
{label:'当天交流充电量',attr:'dailyAcChargeEnergy',unit:'kWh'},
|
||||
{label:'A相电压',attr:'aPhaseVoltage',unit:'V'},
|
||||
{label:'A相电流',attr:'aPhaseCurrent',unit:'A'},
|
||||
{label:'总交流无功电率',attr:'totalReactivePower',unit:'kVar'},
|
||||
{label:'当天交流放电量',attr:'dailyAcDischargeEnergy',unit:'kWh'},
|
||||
{label:'B相电压',attr:'bPhaseVoltage',unit:'V'},
|
||||
{label:'B相电流',attr:'bPhaseCurrent',unit:'A'},
|
||||
{label:'总交流视在功率',attr:'totalApparentPower',unit:'kVA'},
|
||||
{label:'PCS模块温度',attr:'pcsModuleTemperature',unit:'℃'},
|
||||
{label:'C相电压',attr:'cPhaseVoltage',unit:'V'},
|
||||
{label:'C相电流',attr:'cPhaseCurrent',unit:'A'},
|
||||
{label:'总交流功率因数',attr:'totalPowerFactor',unit:''},
|
||||
{label:'PCS环境温度',attr:'pcsEnvironmentTemperature',unit:'℃'},
|
||||
{label:'交流频率',attr:'acFrequency',unit:'Hz'}
|
||||
loading: false,
|
||||
displayData: [],
|
||||
pcsDeviceList: [],
|
||||
selectedPcsId: "",
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
pcsList: [{
|
||||
deviceId: "",
|
||||
deviceName: "PCS",
|
||||
dataUpdateTime: "-",
|
||||
alarmNum: 0,
|
||||
pcsBranchInfoList: [],
|
||||
}],
|
||||
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: "℃",
|
||||
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: "℃",
|
||||
pointName: "",
|
||||
},
|
||||
{
|
||||
label: "交流频率",
|
||||
attr: "acFrequency",
|
||||
unit: "Hz",
|
||||
pointName: "交流频率",
|
||||
},
|
||||
],
|
||||
pcsBranchList:[],//pcs的支路列表
|
||||
}
|
||||
};
|
||||
},
|
||||
methods:{
|
||||
problemSaved(){
|
||||
this.$confirm('确认故障已复位?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
showClose:false,
|
||||
closeOnClickModal:false,
|
||||
type: 'warning',
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
instance.confirmButtonLoading = true;
|
||||
setTimeout(() => {
|
||||
done();
|
||||
instance.confirmButtonLoading = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
//只有在故障复位成功的情况下会走到这里
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '故障复位成功!'
|
||||
});
|
||||
methods: {
|
||||
displayValue(value) {
|
||||
return value === undefined || value === null || value === "" ? "-" : value;
|
||||
},
|
||||
isPointLoading(value) {
|
||||
return this.loading && (value === undefined || value === null || value === "" || value === "-");
|
||||
},
|
||||
normalizeDictKey(value) {
|
||||
const raw = String(value == null ? "" : value).trim();
|
||||
if (!raw) return "";
|
||||
if (/^-?\d+(\.0+)?$/.test(raw)) {
|
||||
return String(parseInt(raw, 10));
|
||||
}
|
||||
return raw;
|
||||
},
|
||||
formatDictValue(options, value) {
|
||||
const dict = (options && typeof options === "object") ? options : {};
|
||||
const key = this.normalizeDictKey(value);
|
||||
if (!key) return "-";
|
||||
return dict[key] || key;
|
||||
},
|
||||
normalizeDeviceId(value) {
|
||||
return String(value == null ? "" : value).trim().toUpperCase();
|
||||
},
|
||||
handleCardClass(item) {
|
||||
const workStatus = this.normalizeDictKey((item && item.workStatus) || "");
|
||||
const statusOptions = (this.PCSWorkStatusOptions && typeof this.PCSWorkStatusOptions === 'object')
|
||||
? this.PCSWorkStatusOptions
|
||||
: {};
|
||||
const hasStatus = Object.prototype.hasOwnProperty.call(statusOptions, workStatus);
|
||||
return workStatus === '1' || !hasStatus
|
||||
? "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)
|
||||
},
|
||||
hasFieldPointId(pcsItem, fieldName) {
|
||||
const row = this.getFieldRow(pcsItem, fieldName);
|
||||
return !!String(row?.dataPoint || "").trim();
|
||||
},
|
||||
getFieldRow(pcsItem, fieldName) {
|
||||
const key = String(fieldName || "").trim();
|
||||
const map = pcsItem?._fieldRowMap || {};
|
||||
return map[key] || null;
|
||||
},
|
||||
handlePcsFieldClick(pcsItem, fieldName, title) {
|
||||
const row = this.getFieldRow(pcsItem, fieldName);
|
||||
const pointId = String(row?.dataPoint || "").trim();
|
||||
this.openCurveDialogByPointId(pointId, title || fieldName);
|
||||
},
|
||||
openCurveDialogByPointId(pointId, title) {
|
||||
const normalizedPointId = String(pointId || "").trim();
|
||||
if (!normalizedPointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
const range = this.getDefaultCurveRange();
|
||||
this.curveCustomRange = range;
|
||||
this.curveDialogTitle = `点位曲线 - ${title || normalizedPointId}`;
|
||||
this.curveQuery = {
|
||||
siteId: this.siteId,
|
||||
pointId: normalizedPointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: range[0],
|
||||
endTime: range[1],
|
||||
};
|
||||
this.curveDialogVisible = true;
|
||||
},
|
||||
handleCurveDialogOpened() {
|
||||
if (!this.curveChart && this.$refs.curveChartRef) {
|
||||
this.curveChart = echarts.init(this.$refs.curveChartRef);
|
||||
}
|
||||
this.loadCurveData();
|
||||
},
|
||||
handleCurveDialogClosed() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
this.curveLoading = false;
|
||||
},
|
||||
getDefaultCurveRange() {
|
||||
const end = new Date();
|
||||
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return [this.formatDateTime(start), this.formatDateTime(end)];
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const d = new Date(date);
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
},
|
||||
formatCurveTime(value) {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return "";
|
||||
}
|
||||
const raw = String(value).trim();
|
||||
const normalized = raw
|
||||
.replace("T", " ")
|
||||
.replace(/\.\d+/, "")
|
||||
.replace(/Z$/, "")
|
||||
.replace(/([+-]\d{2}:?\d{2})$/, "")
|
||||
.trim();
|
||||
const matched = normalized.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
|
||||
if (matched) {
|
||||
return `${matched[1]} ${matched[2]}`;
|
||||
}
|
||||
return normalized.slice(0, 16);
|
||||
},
|
||||
loadCurveData() {
|
||||
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
|
||||
this.$message.warning("点位信息不完整,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
if (!this.curveCustomRange || this.curveCustomRange.length !== 2) {
|
||||
this.$message.warning("请选择查询时间范围");
|
||||
return;
|
||||
}
|
||||
this.curveQuery.startTime = this.curveCustomRange[0];
|
||||
this.curveQuery.endTime = this.curveCustomRange[1];
|
||||
const query = {
|
||||
siteId: this.curveQuery.siteId,
|
||||
pointId: this.curveQuery.pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: this.curveQuery.startTime,
|
||||
endTime: this.curveQuery.endTime,
|
||||
};
|
||||
this.curveLoading = true;
|
||||
getPointConfigCurve(query).then((response) => {
|
||||
const rows = response?.data || [];
|
||||
this.renderCurveChart(rows);
|
||||
}).catch(() => {
|
||||
//取消复位
|
||||
this.renderCurveChart([]);
|
||||
}).finally(() => {
|
||||
this.curveLoading = false;
|
||||
});
|
||||
},
|
||||
machineClosed(){
|
||||
this.$confirm('确认要关机吗?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
showClose:false,
|
||||
closeOnClickModal:false,
|
||||
type: 'warning',
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
instance.confirmButtonLoading = true;
|
||||
setTimeout(() => {
|
||||
done();
|
||||
instance.confirmButtonLoading = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
renderCurveChart(rows = []) {
|
||||
if (!this.curveChart) return;
|
||||
const xData = rows.map(item => this.formatCurveTime(item.dataTime));
|
||||
const yData = rows.map(item => item.pointValue);
|
||||
this.curveChart.clear();
|
||||
this.curveChart.setOption({
|
||||
legend: {},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.curveDialogTitle,
|
||||
type: "line",
|
||||
data: yData,
|
||||
connectNulls: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!rows.length) {
|
||||
this.$message.warning("当前时间范围暂无曲线数据");
|
||||
}
|
||||
},
|
||||
handleTagClick(deviceId) {
|
||||
this.selectedPcsId = deviceId || "";
|
||||
},
|
||||
getModuleRows(menuCode, sectionName) {
|
||||
return (this.displayData || []).filter(item => item.menuCode === menuCode && item.sectionName === sectionName);
|
||||
},
|
||||
getFieldName(fieldCode) {
|
||||
if (!fieldCode) {
|
||||
return "";
|
||||
}
|
||||
const index = fieldCode.lastIndexOf("__");
|
||||
return index >= 0 ? fieldCode.slice(index + 2) : fieldCode;
|
||||
},
|
||||
getFieldMap(rows = [], deviceId = "") {
|
||||
const rowMap = this.getFieldRowMap(rows, deviceId);
|
||||
return Object.keys(rowMap).reduce((acc, fieldName) => {
|
||||
const row = rowMap[fieldName] || {};
|
||||
acc[fieldName] = row.fieldValue;
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
getFieldRowMap(rows = [], deviceId = "") {
|
||||
const map = {};
|
||||
const targetDeviceId = this.normalizeDeviceId(deviceId || "");
|
||||
// 设备维度优先:先吃 device_id 对应值,再用默认值(空 device_id)补齐
|
||||
rows.forEach(item => {
|
||||
if (!item || !item.fieldCode) {
|
||||
return;
|
||||
}
|
||||
}).then(() => {
|
||||
//只有在关机成功的情况下会走到这里
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '关机成功!'
|
||||
});
|
||||
const itemDeviceId = this.normalizeDeviceId(item.deviceId || "");
|
||||
if (itemDeviceId !== targetDeviceId) {
|
||||
return;
|
||||
}
|
||||
map[this.getFieldName(item.fieldCode)] = item;
|
||||
});
|
||||
rows.forEach(item => {
|
||||
if (!item || !item.fieldCode) {
|
||||
return;
|
||||
}
|
||||
const itemDeviceId = this.normalizeDeviceId(item.deviceId || "");
|
||||
if (itemDeviceId !== "") {
|
||||
return;
|
||||
}
|
||||
const fieldName = this.getFieldName(item.fieldCode);
|
||||
if (map[fieldName] === undefined || map[fieldName] === null || map[fieldName] === "") {
|
||||
map[fieldName] = item;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
},
|
||||
getLatestTime(menuCode) {
|
||||
const times = (this.displayData || [])
|
||||
.filter(item => item.menuCode === menuCode && item.valueTime)
|
||||
.map(item => new Date(item.valueTime).getTime())
|
||||
.filter(ts => !isNaN(ts));
|
||||
if (times.length === 0) {
|
||||
return '-';
|
||||
}
|
||||
const date = new Date(Math.max(...times));
|
||||
const p = (n) => String(n).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())} ${p(date.getHours())}:${p(date.getMinutes())}:${p(date.getSeconds())}`;
|
||||
},
|
||||
getPcsDeviceList() {
|
||||
return getPcsNameList(this.siteId).then((response) => {
|
||||
this.pcsDeviceList = response?.data || [];
|
||||
}).catch(() => {
|
||||
//取消关机
|
||||
this.pcsDeviceList = [];
|
||||
});
|
||||
},
|
||||
//6个方块数据
|
||||
getRunningHeadData(){
|
||||
getRunningHeadInfo(this.siteId).then(response => {
|
||||
this.runningHeadData = response?.data || {}
|
||||
})
|
||||
buildPcsList() {
|
||||
const devices = (this.pcsDeviceList && this.pcsDeviceList.length > 0)
|
||||
? this.pcsDeviceList
|
||||
: [{deviceId: this.siteId, deviceName: 'PCS'}];
|
||||
this.pcsList = devices.map((device) => ({
|
||||
...this.getFieldMap(this.getModuleRows('SBJK_PCS', '电参量'), device.deviceId || device.id || this.siteId),
|
||||
deviceId: device.deviceId || device.id || this.siteId,
|
||||
deviceName: device.deviceName || device.name || device.deviceId || device.id || 'PCS',
|
||||
...this.getFieldMap(this.getModuleRows('SBJK_PCS', '状态'), device.deviceId || device.id || this.siteId),
|
||||
_fieldRowMap: {
|
||||
...this.getFieldRowMap(this.getModuleRows('SBJK_PCS', '电参量'), device.deviceId || device.id || this.siteId),
|
||||
...this.getFieldRowMap(this.getModuleRows('SBJK_PCS', '状态'), device.deviceId || device.id || this.siteId),
|
||||
},
|
||||
dataUpdateTime: this.getLatestTime('SBJK_PCS'),
|
||||
alarmNum: 0,
|
||||
pcsBranchInfoList: [],
|
||||
}));
|
||||
},
|
||||
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.loading = true;
|
||||
// 先渲染卡片框架,字段值走单点位 loading
|
||||
this.buildPcsList();
|
||||
Promise.all([
|
||||
getProjectDisplayData(this.siteId),
|
||||
this.getPcsDeviceList(),
|
||||
]).then(([displayResponse]) => {
|
||||
this.displayData = displayResponse?.data || [];
|
||||
this.buildPcsList();
|
||||
}).finally(() => (this.loading = false));
|
||||
},
|
||||
init(){
|
||||
this.getRunningHeadData()
|
||||
this.getPcsList()
|
||||
init() {
|
||||
this.updateData();
|
||||
this.updateInterval(this.updateData);
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.pcs-tags {
|
||||
margin: 0 0 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pcs-container{
|
||||
margin-top: 25px;
|
||||
border:1px solid #eeeeee;
|
||||
border-radius: 6px 6px 0 0;
|
||||
//红色标题
|
||||
.pcs-header{
|
||||
background: #05AEA3;
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
height: 60px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
.pcs-title{
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
padding: 0 50px 0 25px;
|
||||
}
|
||||
.pcs-status{
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.pcs-btns{
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
.pcs-header.warn{
|
||||
background-color:#FC6B69 ;
|
||||
}
|
||||
.pcs-header.close{
|
||||
background-color:#666666 ;
|
||||
}
|
||||
.pcs-tag-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.field-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.curve-tools {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.point-loading-icon {
|
||||
color: #409eff;
|
||||
display: inline-block;
|
||||
transform-origin: center;
|
||||
animation: pointLoadingSpinPulse 1.1s linear infinite;
|
||||
}
|
||||
@keyframes pointLoadingSpinPulse {
|
||||
0% { opacity: 0.45; transform: rotate(0deg) scale(0.9); }
|
||||
50% { opacity: 1; transform: rotate(180deg) scale(1.08); }
|
||||
100% { opacity: 0.45; transform: rotate(360deg) scale(0.9); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,108 +1,151 @@
|
||||
|
||||
<template>
|
||||
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding">
|
||||
<el-card
|
||||
shadow="always"
|
||||
class="common-card-container common-card-container-body-no-padding"
|
||||
>
|
||||
<div slot="header">
|
||||
<span class="card-title">储能功率曲线</span>
|
||||
<span class="card-title">PCS有功功率/PCS无功功率</span>
|
||||
</div>
|
||||
<div style="height: 360px" id="cnglqxChart"/>
|
||||
<div ref="chartRef" style="height: 360px" />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import resize from '@/mixins/ems/resize'
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import {storagePower} from '@/api/ems/dzjk'
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import { getPointConfigCurve } from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.chart = echarts.init(document.querySelector('#cnglqxChart'))
|
||||
this.chart = echarts.init(this.$refs.chartRef);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
methods: {
|
||||
init(siteId){
|
||||
this.chart.showLoading()
|
||||
const x = []
|
||||
const data1 =[],data2 =[]
|
||||
storagePower(siteId).then(response => {
|
||||
const source = response?.data?.energyStoragePowList || []
|
||||
source.forEach(item=>{
|
||||
x.push(formatDate(item.createDate,false,true))
|
||||
data1.push(item.pcsTotalActPower)
|
||||
data2.push(item.pcsTotalReactivePower)
|
||||
init(siteId, timeRange) {
|
||||
const [startTime = "", endTime = ""] = timeRange;
|
||||
const query = {
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true),
|
||||
siteId
|
||||
};
|
||||
const rows = (this.displayData || []).filter(
|
||||
(item) =>
|
||||
item &&
|
||||
item.useFixedDisplay !== 1 &&
|
||||
[
|
||||
"SBJK_SSYX__curvePcsActivePower",
|
||||
"SBJK_SSYX__curvePcsReactivePower"
|
||||
].includes(item.fieldCode) &&
|
||||
item.dataPoint
|
||||
);
|
||||
const tasks = rows.map((row) => {
|
||||
const pointId = String(row.dataPoint || "").trim();
|
||||
if (!pointId) return Promise.resolve(null);
|
||||
return getPointConfigCurve({
|
||||
...query,
|
||||
pointId
|
||||
})
|
||||
this.setOption(x,data1,data2)
|
||||
}).finally(()=>{
|
||||
this.chart.hideLoading()
|
||||
})
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
return {
|
||||
name: row.fieldName || row.fieldCode || pointId,
|
||||
data: list
|
||||
.map((item) => [
|
||||
this.parseToTimestamp(item.dataTime),
|
||||
Number(item.pointValue)
|
||||
])
|
||||
.filter((item) => item[0] && !Number.isNaN(item[1]))
|
||||
};
|
||||
})
|
||||
.catch(() => null);
|
||||
});
|
||||
Promise.all(tasks)
|
||||
.then((series) => {
|
||||
this.setOption((series || []).filter(Boolean));
|
||||
});
|
||||
},
|
||||
setOption(x,data1,data2) {
|
||||
this.chart.setOption({
|
||||
color:['#FFBD00','#3C81FF'],
|
||||
normalizeDateTime(value, endOfDay) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
if (raw.includes(" ")) return raw;
|
||||
return `${raw} ${endOfDay ? "23:59:59" : "00:00:00"}`;
|
||||
},
|
||||
parseToTimestamp(value) {
|
||||
if (!value) return null;
|
||||
const t = new Date(value).getTime();
|
||||
return Number.isNaN(t) ? null : t;
|
||||
},
|
||||
setOption(seriesData = []) {
|
||||
this.chart && this.chart.setOption({
|
||||
legend: {
|
||||
left: 'center',
|
||||
top: '10',
|
||||
left: "center",
|
||||
top: "5",
|
||||
itemWidth: 10,
|
||||
itemHeight: 5,
|
||||
textStyle: {
|
||||
fontSize: 9,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
containLabel: true
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
show: true,
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
}
|
||||
},
|
||||
textStyle:{
|
||||
color:"#333333",
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {type:'category',data:x},
|
||||
xAxis: { type: "time" },
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100
|
||||
}
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
// POC昨日有功功率、POC昨日无功功率
|
||||
series: [
|
||||
{
|
||||
name:'POC实时有功功率',
|
||||
type: 'line',
|
||||
areaStyle: {
|
||||
color:'#FFBD00'
|
||||
},
|
||||
data: data1,
|
||||
},{
|
||||
name:'POC实时无功功率',
|
||||
type: 'line',
|
||||
areaStyle: {
|
||||
color: '#3C81FF'
|
||||
},
|
||||
data: data2
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
series: seriesData.map((item) => ({
|
||||
type: "line",
|
||||
name: item.name,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
},
|
||||
data: item.data
|
||||
})),
|
||||
}, true);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -1,99 +1,138 @@
|
||||
|
||||
<template>
|
||||
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding">
|
||||
<el-card
|
||||
shadow="always"
|
||||
class="common-card-container common-card-container-body-no-padding"
|
||||
>
|
||||
<div slot="header">
|
||||
<span class="card-title">电池平均SOC</span>
|
||||
<span class="card-title">平均SOC</span>
|
||||
</div>
|
||||
<div style="height: 360px" id="dcpjsocChart"/>
|
||||
<div ref="chartRef" style="height: 360px" />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import resize from '@/mixins/ems/resize'
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import {batteryAveSoc} from '@/api/ems/dzjk'
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import { getPointConfigCurve } from "@/api/ems/site";
|
||||
export default {
|
||||
mixins: [resize],
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.chart = echarts.init(document.querySelector('#dcpjsocChart'))
|
||||
this.chart = echarts.init(this.$refs.chartRef);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
methods: {
|
||||
init(siteId){
|
||||
this.chart.showLoading()
|
||||
const x = []
|
||||
const data =[]
|
||||
batteryAveSoc(siteId).then(response => {
|
||||
const source = response?.data?.batteryAveSOCList || []
|
||||
source.forEach(item=>{
|
||||
x.push(formatDate(item.createDate,false,true))
|
||||
data.push(item.batterySOC)
|
||||
})
|
||||
this.setOption(x,data)
|
||||
}).finally(()=>{
|
||||
this.chart.hideLoading()
|
||||
init(siteId,timeRange) {
|
||||
const [startTime='', endTime=''] = timeRange;
|
||||
const row = (this.displayData || []).find(
|
||||
(item) =>
|
||||
item &&
|
||||
item.fieldCode === "SBJK_SSYX__curveBatteryAveSoc" &&
|
||||
item.useFixedDisplay !== 1 &&
|
||||
item.dataPoint
|
||||
);
|
||||
const pointId = String(row?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.setOption([]);
|
||||
return;
|
||||
}
|
||||
getPointConfigCurve({
|
||||
siteId,
|
||||
pointId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
})
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
this.setOption(
|
||||
list
|
||||
.map((item) => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
|
||||
.filter((item) => item[0] && !Number.isNaN(item[1]))
|
||||
);
|
||||
});
|
||||
},
|
||||
setOption(x,data) {
|
||||
this.chart.setOption({
|
||||
color:['#FFBD00','#3C81FF'],
|
||||
// legend: {
|
||||
// left: 'center',
|
||||
// bottom: '10',
|
||||
// },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
normalizeDateTime(value, endOfDay) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
if (raw.includes(" ")) return raw;
|
||||
return `${raw} ${endOfDay ? "23:59:59" : "00:00:00"}`;
|
||||
},
|
||||
parseToTimestamp(value) {
|
||||
if (!value) return null;
|
||||
const t = new Date(value).getTime();
|
||||
return Number.isNaN(t) ? null : t;
|
||||
},
|
||||
setOption(data = []) {
|
||||
this.chart && this.chart.setOption({
|
||||
legend: {
|
||||
left: "center",
|
||||
top: "5",
|
||||
itemWidth: 10,
|
||||
itemHeight: 5,
|
||||
textStyle: {
|
||||
fontSize: 9,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
containLabel: true
|
||||
containLabel: true,
|
||||
},
|
||||
textStyle:{
|
||||
color:"#333333",
|
||||
tooltip: {
|
||||
show:true,
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
}
|
||||
},
|
||||
xAxis: {type:'category',data:x},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: { type: "time" },
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100
|
||||
}
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name:'电池平均SOC',
|
||||
data: data,
|
||||
type: 'line',
|
||||
type: "line",
|
||||
name: `平均SOC`,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color:'#FFBD00'
|
||||
}
|
||||
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opacity: 0.35
|
||||
},
|
||||
data,
|
||||
},
|
||||
],
|
||||
},true);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -1,99 +1,139 @@
|
||||
|
||||
<template>
|
||||
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding">
|
||||
<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"/>
|
||||
<div ref="chartRef" style="height: 360px" />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import resize from '@/mixins/ems/resize'
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import {batteryAveTemp} from '@/api/ems/dzjk'
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import { getPointConfigCurve } from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.chart = echarts.init(document.querySelector('#dcpjwdChart'))
|
||||
this.chart = echarts.init(this.$refs.chartRef);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
methods: {
|
||||
init(siteId){
|
||||
this.chart.showLoading()
|
||||
const x = []
|
||||
const data1 =[],data2 =[]
|
||||
batteryAveTemp(siteId).then(response => {
|
||||
const source = response?.data?.batteryAveTempList || []
|
||||
source.forEach(item=>{
|
||||
x.push(formatDate(item.createDate,false,true))
|
||||
data1.push(item.batteryTemp)
|
||||
})
|
||||
this.setOption(x,data1,data2)
|
||||
}).finally(()=>{
|
||||
this.chart.hideLoading()
|
||||
init(siteId,timeRange) {
|
||||
const [startTime='', endTime=''] = timeRange;
|
||||
const row = (this.displayData || []).find(
|
||||
(item) =>
|
||||
item &&
|
||||
item.fieldCode === "SBJK_SSYX__curveBatteryAveTemp" &&
|
||||
item.useFixedDisplay !== 1 &&
|
||||
item.dataPoint
|
||||
);
|
||||
const pointId = String(row?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.setOption([]);
|
||||
return;
|
||||
}
|
||||
getPointConfigCurve({
|
||||
siteId,
|
||||
pointId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
})
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
this.setOption(
|
||||
list
|
||||
.map((item) => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
|
||||
.filter((item) => item[0] && !Number.isNaN(item[1]))
|
||||
);
|
||||
});
|
||||
},
|
||||
setOption(x,data) {
|
||||
this.chart.setOption({
|
||||
color:['#3C81FF'],
|
||||
// legend: {
|
||||
// left: 'center',
|
||||
// bottom: '10',
|
||||
// },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
normalizeDateTime(value, endOfDay) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
if (raw.includes(" ")) return raw;
|
||||
return `${raw} ${endOfDay ? "23:59:59" : "00:00:00"}`;
|
||||
},
|
||||
parseToTimestamp(value) {
|
||||
if (!value) return null;
|
||||
const t = new Date(value).getTime();
|
||||
return Number.isNaN(t) ? null : t;
|
||||
},
|
||||
setOption(data = []) {
|
||||
this.chart && this.chart.setOption({
|
||||
legend: {
|
||||
left: "center",
|
||||
top: "5",
|
||||
itemWidth: 10,
|
||||
itemHeight: 5,
|
||||
textStyle: {
|
||||
fontSize: 9,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
containLabel: true
|
||||
containLabel: true,
|
||||
},
|
||||
textStyle:{
|
||||
color:"#333333",
|
||||
tooltip: {
|
||||
show:true,
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
}
|
||||
},
|
||||
xAxis: {type:'category',data:x},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: { type: "time" },
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100
|
||||
}
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name:'电池平均温度',
|
||||
data: data,
|
||||
type: 'line',
|
||||
type: "line",
|
||||
name: `电池平均温度`,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color:'#3C81FF'
|
||||
opacity: 0.35
|
||||
},
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data,
|
||||
},
|
||||
],
|
||||
},true);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -1,96 +1,141 @@
|
||||
|
||||
<template>
|
||||
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding">
|
||||
<el-card
|
||||
shadow="always"
|
||||
class="common-card-container common-card-container-body-no-padding"
|
||||
>
|
||||
<div slot="header">
|
||||
<span class="card-title">Poc平均温度</span>
|
||||
<span class="card-title">PCS最高温度</span>
|
||||
</div>
|
||||
<div style="height: 360px" id="pocpjwdChart"/>
|
||||
<div ref="chartRef" style="height: 360px" />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import resize from '@/mixins/ems/resize'
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import {stackAveTemp} from '@/api/ems/dzjk'
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import { getPointConfigCurve } from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
seriesName: "PCS最高温度"
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.chart = echarts.init(document.querySelector('#pocpjwdChart'))
|
||||
this.chart = echarts.init(this.$refs.chartRef);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
methods: {
|
||||
init(siteId){
|
||||
this.chart.showLoading()
|
||||
const x = []
|
||||
const data =[]
|
||||
stackAveTemp(siteId).then(response => {
|
||||
const source = response?.data?.stackAveTempList || []
|
||||
source.forEach(item=>{
|
||||
x.push(formatDate(item.createDate,false,true))
|
||||
data.push(item.temp)
|
||||
})
|
||||
this.setOption(x,data)
|
||||
}).finally(()=>{
|
||||
this.chart.hideLoading()
|
||||
init(siteId,timeRange) {
|
||||
const [startTime='', endTime=''] = timeRange;
|
||||
const row = (this.displayData || []).find(
|
||||
(item) =>
|
||||
item &&
|
||||
item.fieldCode === "SBJK_SSYX__curvePcsMaxTemp" &&
|
||||
item.useFixedDisplay !== 1 &&
|
||||
item.dataPoint
|
||||
);
|
||||
const pointId = String(row?.dataPoint || "").trim();
|
||||
this.seriesName = row?.fieldName || "PCS最高温度";
|
||||
if (!pointId) {
|
||||
this.setOption([]);
|
||||
return;
|
||||
}
|
||||
getPointConfigCurve({
|
||||
siteId,
|
||||
pointId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
})
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
this.setOption(
|
||||
list
|
||||
.map((item) => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
|
||||
.filter((item) => item[0] && !Number.isNaN(item[1]))
|
||||
);
|
||||
});
|
||||
},
|
||||
setOption(x,data) {
|
||||
this.chart.setOption({
|
||||
color:['#FFBD00','#3C81FF'],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
normalizeDateTime(value, endOfDay) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
if (raw.includes(" ")) return raw;
|
||||
return `${raw} ${endOfDay ? "23:59:59" : "00:00:00"}`;
|
||||
},
|
||||
parseToTimestamp(value) {
|
||||
if (!value) return null;
|
||||
const t = new Date(value).getTime();
|
||||
return Number.isNaN(t) ? null : t;
|
||||
},
|
||||
setOption(data = []) {
|
||||
this.chart && this.chart.setOption({
|
||||
legend: {
|
||||
left: "center",
|
||||
top: "5",
|
||||
itemWidth: 10,
|
||||
itemHeight: 5,
|
||||
textStyle: {
|
||||
fontSize: 9,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
containLabel: true
|
||||
containLabel: true,
|
||||
},
|
||||
textStyle:{
|
||||
color:"#333333",
|
||||
tooltip: {
|
||||
show:true,
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
}
|
||||
},
|
||||
xAxis: {type:'category',data:x},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: { type: "time" },
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100
|
||||
}
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name:'Poc平均温度',
|
||||
data: data,
|
||||
type: 'line',
|
||||
type: "line",
|
||||
name: this.seriesName,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color:'#FFBD00'
|
||||
}
|
||||
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opacity: 0.35
|
||||
},
|
||||
data
|
||||
}
|
||||
],
|
||||
},true);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -2,64 +2,284 @@
|
||||
<template>
|
||||
<div class="ssyx-ems-dashboard-editor-container">
|
||||
<!-- 6个方块-->
|
||||
<real-time-base-info :data="runningHeadData"/>
|
||||
<real-time-base-info :display-data="runningDisplayData" :loading="runningHeadLoading" @field-click="handleHeadFieldClick"/>
|
||||
<!-- 时间选择 -->
|
||||
<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'/>
|
||||
<cnglqx-chart ref='cnglqx' :display-data="runningDisplayData"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<pocpjwd-chart ref='pocpjwd'/>
|
||||
<pocpjwd-chart ref='pocpjwd' :display-data="runningDisplayData"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="32" style="margin:30px 0;">
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<dcpjsoc-chart ref="dcpjsoc"/>
|
||||
<dcpjsoc-chart ref="dcpjsoc" :display-data="runningDisplayData"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<dcpjwd-chart ref="dcpjwd"/>
|
||||
<dcpjwd-chart ref="dcpjwd" :display-data="runningDisplayData"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-dialog
|
||||
:visible.sync="curveDialogVisible"
|
||||
:title="curveDialogTitle"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
class="ems-dialog"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@opened="handleCurveDialogOpened"
|
||||
@closed="handleCurveDialogClosed"
|
||||
>
|
||||
<div class="curve-tools">
|
||||
<el-date-picker
|
||||
v-model="curveCustomRange"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
style="width: 440px"
|
||||
/>
|
||||
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
|
||||
</div>
|
||||
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.curve-tools {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
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 {getProjectDisplayData} from '@/api/ems/dzjk'
|
||||
import {getPointConfigCurve} from "@/api/ems/site";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
|
||||
export default {
|
||||
name:'DzjkSbjkSsyx',
|
||||
components:{RealTimeBaseInfo,CnglqxChart,PocpjwdChart,DcpjwdChart,DcpjsocChart},
|
||||
mixins:[getQuerySiteId],
|
||||
components:{RealTimeBaseInfo,CnglqxChart,PocpjwdChart,DcpjwdChart,DcpjsocChart,DateRangeSelect},
|
||||
mixins:[getQuerySiteId,intervalUpdate],
|
||||
data() {
|
||||
return {
|
||||
runningHeadData:{},//运行信息
|
||||
runningDisplayData: [], //单站监控项目配置展示数据
|
||||
timeRange:[],
|
||||
isInit:true,
|
||||
runningHeadLoading: false,
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
handleHeadFieldClick(item) {
|
||||
const pointId = String(item?.pointId || item?.raw?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
this.openCurveDialog({
|
||||
pointId,
|
||||
title: item?.title || item?.raw?.fieldName || pointId,
|
||||
});
|
||||
},
|
||||
openCurveDialog({pointId, title}) {
|
||||
const range = this.getDefaultCurveRange();
|
||||
this.curveCustomRange = range;
|
||||
this.curveDialogTitle = `点位曲线 - ${title || pointId}`;
|
||||
this.curveQuery = {
|
||||
siteId: this.siteId,
|
||||
pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: range[0],
|
||||
endTime: range[1],
|
||||
};
|
||||
this.curveDialogVisible = true;
|
||||
},
|
||||
handleCurveDialogOpened() {
|
||||
if (!this.curveChart && this.$refs.curveChartRef) {
|
||||
this.curveChart = echarts.init(this.$refs.curveChartRef);
|
||||
}
|
||||
this.loadCurveData();
|
||||
},
|
||||
handleCurveDialogClosed() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
this.curveLoading = false;
|
||||
},
|
||||
getDefaultCurveRange() {
|
||||
const end = new Date();
|
||||
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return [this.formatDateTime(start), this.formatDateTime(end)];
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const d = new Date(date);
|
||||
const pad = (num) => String(num).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
},
|
||||
formatCurveTime(value) {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return "";
|
||||
}
|
||||
const raw = String(value).trim();
|
||||
const normalized = raw
|
||||
.replace("T", " ")
|
||||
.replace(/\.\d+/, "")
|
||||
.replace(/Z$/, "")
|
||||
.replace(/([+-]\d{2}:?\d{2})$/, "")
|
||||
.trim();
|
||||
const matched = normalized.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
|
||||
if (matched) {
|
||||
return `${matched[1]} ${matched[2]}`;
|
||||
}
|
||||
return normalized.slice(0, 16);
|
||||
},
|
||||
loadCurveData() {
|
||||
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
|
||||
this.$message.warning("点位信息不完整,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
if (!this.curveCustomRange || this.curveCustomRange.length !== 2) {
|
||||
this.$message.warning("请选择查询时间范围");
|
||||
return;
|
||||
}
|
||||
this.curveQuery.startTime = this.curveCustomRange[0];
|
||||
this.curveQuery.endTime = this.curveCustomRange[1];
|
||||
const query = {
|
||||
siteId: this.curveQuery.siteId,
|
||||
pointId: this.curveQuery.pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: this.curveQuery.startTime,
|
||||
endTime: this.curveQuery.endTime,
|
||||
};
|
||||
this.curveLoading = true;
|
||||
getPointConfigCurve(query).then((response) => {
|
||||
const rows = response?.data || [];
|
||||
this.renderCurveChart(rows);
|
||||
}).catch(() => {
|
||||
this.renderCurveChart([]);
|
||||
}).finally(() => {
|
||||
this.curveLoading = false;
|
||||
});
|
||||
},
|
||||
renderCurveChart(rows = []) {
|
||||
if (!this.curveChart) return;
|
||||
const xData = rows.map(item => this.formatCurveTime(item.dataTime));
|
||||
const yData = rows.map(item => item.pointValue);
|
||||
this.curveChart.clear();
|
||||
this.curveChart.setOption({
|
||||
legend: {},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.curveDialogTitle,
|
||||
type: "line",
|
||||
data: yData,
|
||||
connectNulls: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!rows.length) {
|
||||
this.$message.warning("当前时间范围暂无曲线数据");
|
||||
}
|
||||
},
|
||||
//6个方块数据
|
||||
getRunningHeadData(){
|
||||
getRunningHeadInfo(this.siteId).then(response => {
|
||||
this.runningHeadData = response?.data || {}
|
||||
this.runningHeadLoading = true
|
||||
return getProjectDisplayData(this.siteId).then((displayResponse) => {
|
||||
this.runningDisplayData = displayResponse?.data || []
|
||||
}).finally(() => {
|
||||
this.runningHeadLoading = false
|
||||
})
|
||||
},
|
||||
// 更新时间范围 重置图表
|
||||
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().finally(() => {
|
||||
this.updateChart()
|
||||
})
|
||||
},
|
||||
init(){
|
||||
this.getRunningHeadData()
|
||||
this.$refs.dateRangeSelect.init(true)
|
||||
this.$nextTick(()=>{
|
||||
this.$refs.cnglqx.init(this.siteId)
|
||||
this.$refs.pocpjwd.init(this.siteId)
|
||||
this.$refs.dcpjsoc.init(this.siteId)
|
||||
this.$refs.dcpjwd.init(this.siteId)
|
||||
this.updateData()
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
153
src/views/ems/dzjk/sbjk/xf/index.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div>
|
||||
<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">
|
||||
<i v-if="isPointLoading(item[tempDataItem.attr])" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ displayValue(item[tempDataItem.attr]) }}<span v-html="tempDataItem.unit"></span></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: {
|
||||
displayValue(value) {
|
||||
return value === undefined || value === null || value === "" ? "-" : value;
|
||||
},
|
||||
isPointLoading(value) {
|
||||
return this.loading && (value === undefined || value === null || value === "" || value === "-");
|
||||
},
|
||||
// 查看设备电位表格
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.point-loading-icon {
|
||||
color: #409eff;
|
||||
display: inline-block;
|
||||
transform-origin: center;
|
||||
animation: pointLoadingSpinPulse 1.1s linear infinite;
|
||||
}
|
||||
@keyframes pointLoadingSpinPulse {
|
||||
0% { opacity: 0.45; transform: rotate(0deg) scale(0.9); }
|
||||
50% { opacity: 1; transform: rotate(180deg) scale(1.08); }
|
||||
100% { opacity: 0.45; transform: rotate(360deg) scale(0.9); }
|
||||
}
|
||||
</style>
|
||||
@ -1,117 +1,471 @@
|
||||
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="yl-item-container" :class="{'yl-warn-item-container':item.workMode !== '0'}" v-for="(item,index) in list" :key="index+'ylLise'">
|
||||
<div class="header">
|
||||
<div class="header-title">{{item.systemName}}</div>
|
||||
<div>工作模式:<span class="header-values">{{$store.state.ems.workModeOptions[item.workMode]}}</span></div>
|
||||
<div>当前温度:<span class="header-values">{{item.currentTemperature}}℃</span></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<el-row>
|
||||
<el-col v-for="(tempDataItem,tempDataIndex) in tempData" :key="tempDataIndex+'ylTempData'" :span="8">{{tempDataItem.title}}:{{item[tempDataItem.attr]}}℃</el-col>
|
||||
</el-row>
|
||||
<div>
|
||||
<div class="pcs-tags">
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="selectedSectionKey ? 'info' : 'primary'"
|
||||
:effect="selectedSectionKey ? 'plain' : 'dark'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick('')"
|
||||
>
|
||||
全部
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-for="(group, index) in sectionGroups"
|
||||
:key="index + 'ylTag'"
|
||||
size="small"
|
||||
:type="selectedSectionKey === group.sectionKey ? 'primary' : 'info'"
|
||||
:effect="selectedSectionKey === group.sectionKey ? 'dark' : 'plain'"
|
||||
class="pcs-tag-item"
|
||||
@click="handleTagClick(group.sectionKey)"
|
||||
>
|
||||
{{ group.displayName || group.sectionName || "冷却" }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-card
|
||||
v-for="(group, index) in filteredSectionGroups"
|
||||
:key="index + 'ylSection'"
|
||||
class="sbjk-card-container list running-card-container"
|
||||
shadow="always"
|
||||
>
|
||||
<div slot="header">
|
||||
<span class="large-title">{{ group.displayName || group.sectionName || "冷却" }}</span>
|
||||
<div class="info">
|
||||
<div>状态:{{ group.statusText }}</div>
|
||||
<div>数据更新时间:{{ group.updateTimeText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-show="list.length<=0" :image-size="200"></el-empty>
|
||||
</div>
|
||||
<el-row class="device-info-row">
|
||||
<el-col
|
||||
v-for="(item, dataIndex) in group.items"
|
||||
:key="dataIndex + 'ylField'"
|
||||
:span="8"
|
||||
class="device-info-col"
|
||||
:class="{ 'field-disabled': !item.pointId }"
|
||||
>
|
||||
<div class="field-click-wrapper" @click="handleFieldClick(item)">
|
||||
<span class="left">{{ item.fieldName }}</span>
|
||||
<span class="right">
|
||||
<i v-if="isPointLoading(item.fieldValue)" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ displayValue(item.fieldValue) | formatNumber }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-dialog
|
||||
:visible.sync="curveDialogVisible"
|
||||
:title="curveDialogTitle"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
class="ems-dialog"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@opened="handleCurveDialogOpened"
|
||||
@closed="handleCurveDialogClosed"
|
||||
>
|
||||
<div class="curve-tools">
|
||||
<el-date-picker
|
||||
v-model="curveCustomRange"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
style="width: 440px"
|
||||
/>
|
||||
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
|
||||
</div>
|
||||
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getCoolingDataList} from '@/api/ems/dzjk'
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import { getProjectDisplayData } from "@/api/ems/dzjk";
|
||||
import { getDeviceList, getPointConfigCurve } from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
name:'DzjkSbjkYl',
|
||||
mixins:[getQuerySiteId],
|
||||
name: "DzjkSbjkYl",
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
list:[],
|
||||
tempData:[
|
||||
{title:'制热开启点',attr:'heatingStartPoint'},
|
||||
{title:'制冷开启点',attr:'coolingStartPoint'},
|
||||
{title:'高温告警点',attr:'highTempAlarmPoint'},
|
||||
{title:'制热停止点',attr:'heatingStopPoint'},
|
||||
{title:'制冷停止点',attr:'coolingStopPoint'},
|
||||
{title:'低温告警点',attr:'lowTempAlarmPoint'},
|
||||
loading: false,
|
||||
displayData: [],
|
||||
selectedSectionKey: "",
|
||||
coolingDeviceList: [],
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
moduleDisplayData() {
|
||||
return (this.displayData || []).filter((item) => item.menuCode === "SBJK_YL");
|
||||
},
|
||||
ylTemplateFields() {
|
||||
const source = this.moduleDisplayData || [];
|
||||
const result = [];
|
||||
const seen = new Set();
|
||||
source.forEach((item) => {
|
||||
const fieldName = String(item?.fieldName || "").trim();
|
||||
if (!fieldName || seen.has(fieldName)) {
|
||||
return;
|
||||
}
|
||||
seen.add(fieldName);
|
||||
result.push(fieldName);
|
||||
});
|
||||
return result.length > 0 ? result : this.fallbackFields;
|
||||
},
|
||||
sectionGroups() {
|
||||
const source = this.moduleDisplayData || [];
|
||||
const devices = (this.coolingDeviceList || []).length > 0
|
||||
? this.coolingDeviceList
|
||||
: [{ deviceId: "", deviceName: "冷却" }];
|
||||
return devices.map((device, index) => {
|
||||
const deviceId = String(device?.deviceId || device?.id || "").trim();
|
||||
const sectionKey = deviceId || `COOLING_${index}`;
|
||||
const displayName = String(device?.deviceName || device?.name || deviceId || `冷却${index + 1}`).trim();
|
||||
const exactRows = source.filter((item) => String(item?.deviceId || "").trim() === deviceId);
|
||||
const fallbackRows = source.filter((item) => !String(item?.deviceId || "").trim());
|
||||
|
||||
]
|
||||
const exactValueMap = {};
|
||||
exactRows.forEach((item) => {
|
||||
const key = String(item?.fieldName || "").trim();
|
||||
if (key) {
|
||||
exactValueMap[key] = item;
|
||||
}
|
||||
});
|
||||
const fallbackValueMap = {};
|
||||
fallbackRows.forEach((item) => {
|
||||
const key = String(item?.fieldName || "").trim();
|
||||
if (key && fallbackValueMap[key] === undefined) {
|
||||
fallbackValueMap[key] = item;
|
||||
}
|
||||
});
|
||||
|
||||
const items = (this.ylTemplateFields || []).map((fieldName) => {
|
||||
const row = exactValueMap[fieldName] || fallbackValueMap[fieldName] || {};
|
||||
return {
|
||||
fieldName,
|
||||
fieldValue: row.fieldValue,
|
||||
valueTime: row.valueTime,
|
||||
pointId: String(row?.dataPoint || "").trim(),
|
||||
raw: row,
|
||||
};
|
||||
});
|
||||
|
||||
const statusItem = (items || []).find((it) => String(it.fieldName || "").includes("状态"));
|
||||
const timestamps = [...exactRows, ...fallbackRows]
|
||||
.map((it) => new Date(it?.valueTime).getTime())
|
||||
.filter((ts) => !isNaN(ts));
|
||||
|
||||
return {
|
||||
sectionName: displayName,
|
||||
sectionKey,
|
||||
displayName,
|
||||
deviceId,
|
||||
items,
|
||||
statusText: this.displayValue(statusItem ? statusItem.fieldValue : "-"),
|
||||
updateTimeText: timestamps.length > 0 ? this.formatDate(new Date(Math.max(...timestamps))) : "-",
|
||||
};
|
||||
});
|
||||
},
|
||||
displaySectionGroups() {
|
||||
if (this.sectionGroups.length > 0) {
|
||||
return this.sectionGroups;
|
||||
}
|
||||
return [
|
||||
{
|
||||
sectionName: "冷却参数",
|
||||
items: this.fallbackFields.map((fieldName) => ({ fieldName, fieldValue: "-" })),
|
||||
statusText: "-",
|
||||
updateTimeText: "-",
|
||||
},
|
||||
];
|
||||
},
|
||||
filteredSectionGroups() {
|
||||
const groups = this.displaySectionGroups || [];
|
||||
if (!this.selectedSectionKey) {
|
||||
return groups;
|
||||
}
|
||||
return groups.filter((group) => group.sectionKey === this.selectedSectionKey);
|
||||
},
|
||||
fallbackFields() {
|
||||
return ["供水温度", "回水温度", "供水压力", "回水压力", "冷源水温度", "VB01开度", "VB02开度"];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleFieldClick(item) {
|
||||
const pointId = String(item?.pointId || item?.raw?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
this.openCurveDialog({
|
||||
pointId,
|
||||
title: item?.fieldName || pointId,
|
||||
});
|
||||
},
|
||||
openCurveDialog({ pointId, title }) {
|
||||
const range = this.getDefaultCurveRange();
|
||||
this.curveCustomRange = range;
|
||||
this.curveDialogTitle = `点位曲线 - ${title || pointId}`;
|
||||
this.curveQuery = {
|
||||
siteId: this.siteId,
|
||||
pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: range[0],
|
||||
endTime: range[1],
|
||||
};
|
||||
this.curveDialogVisible = true;
|
||||
},
|
||||
handleCurveDialogOpened() {
|
||||
if (!this.curveChart && this.$refs.curveChartRef) {
|
||||
this.curveChart = echarts.init(this.$refs.curveChartRef);
|
||||
}
|
||||
this.loadCurveData();
|
||||
},
|
||||
handleCurveDialogClosed() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
this.curveLoading = false;
|
||||
},
|
||||
getDefaultCurveRange() {
|
||||
const end = new Date();
|
||||
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
||||
return [this.formatDateTime(start), this.formatDateTime(end)];
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const d = new Date(date);
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
},
|
||||
formatCurveTime(value) {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return "";
|
||||
}
|
||||
const raw = String(value).trim();
|
||||
const normalized = raw
|
||||
.replace("T", " ")
|
||||
.replace(/\.\d+/, "")
|
||||
.replace(/Z$/, "")
|
||||
.replace(/([+-]\d{2}:?\d{2})$/, "")
|
||||
.trim();
|
||||
const matched = normalized.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
|
||||
if (matched) {
|
||||
return `${matched[1]} ${matched[2]}`;
|
||||
}
|
||||
return normalized.slice(0, 16);
|
||||
},
|
||||
loadCurveData() {
|
||||
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
|
||||
this.$message.warning("点位信息不完整,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
if (!this.curveCustomRange || this.curveCustomRange.length !== 2) {
|
||||
this.$message.warning("请选择查询时间范围");
|
||||
return;
|
||||
}
|
||||
this.curveQuery.startTime = this.curveCustomRange[0];
|
||||
this.curveQuery.endTime = this.curveCustomRange[1];
|
||||
const query = {
|
||||
siteId: this.curveQuery.siteId,
|
||||
pointId: this.curveQuery.pointId,
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: this.curveQuery.startTime,
|
||||
endTime: this.curveQuery.endTime,
|
||||
};
|
||||
this.curveLoading = true;
|
||||
getPointConfigCurve(query)
|
||||
.then((response) => {
|
||||
const rows = response?.data || [];
|
||||
this.renderCurveChart(rows);
|
||||
})
|
||||
.catch(() => {
|
||||
this.renderCurveChart([]);
|
||||
})
|
||||
.finally(() => {
|
||||
this.curveLoading = false;
|
||||
});
|
||||
},
|
||||
renderCurveChart(rows = []) {
|
||||
if (!this.curveChart) return;
|
||||
const xData = rows.map((item) => this.formatCurveTime(item.dataTime));
|
||||
const yData = rows.map((item) => item.pointValue);
|
||||
this.curveChart.clear();
|
||||
this.curveChart.setOption({
|
||||
legend: {},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: this.curveDialogTitle,
|
||||
type: "line",
|
||||
data: yData,
|
||||
connectNulls: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!rows.length) {
|
||||
this.$message.warning("当前时间范围暂无曲线数据");
|
||||
}
|
||||
},
|
||||
handleTagClick(sectionKey) {
|
||||
this.selectedSectionKey = sectionKey || "";
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === undefined || value === null || value === "" ? "-" : value;
|
||||
},
|
||||
isPointLoading(value) {
|
||||
return this.loading && (value === undefined || value === null || value === "" || value === "-");
|
||||
},
|
||||
formatDate(date) {
|
||||
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
||||
return "-";
|
||||
}
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())} ${p(date.getHours())}:${p(
|
||||
date.getMinutes()
|
||||
)}:${p(date.getSeconds())}`;
|
||||
},
|
||||
getCoolingDeviceList() {
|
||||
return getDeviceList(this.siteId)
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
this.coolingDeviceList = list.filter((item) => item.deviceCategory === "COOLING");
|
||||
})
|
||||
.catch(() => {
|
||||
this.coolingDeviceList = [];
|
||||
});
|
||||
},
|
||||
updateData() {
|
||||
this.loading = true;
|
||||
Promise.all([getProjectDisplayData(this.siteId), this.getCoolingDeviceList()])
|
||||
.then(([response]) => {
|
||||
this.displayData = response?.data || [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.updateData();
|
||||
this.updateInterval(this.updateData);
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
init(){
|
||||
this.loading = true
|
||||
getCoolingDataList(this.siteId).then(response => {
|
||||
this.list = JSON.parse(JSON.stringify(response?.data || []));
|
||||
}).finally(() => {this.loading = false})
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.yl-item-container{
|
||||
border-radius: 5px;
|
||||
background-color: #EBF6F6;
|
||||
&:not(:last-child){
|
||||
.sbjk-card-container {
|
||||
&.list:not(:last-child) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.header{
|
||||
line-height: 40px;
|
||||
font-size: 14px;
|
||||
>div{
|
||||
display: inline-block;
|
||||
margin-right: 40px;
|
||||
}
|
||||
.header-title{
|
||||
border-radius: 5px 0 5px 0;
|
||||
color:#ffffff;
|
||||
width: 120px;
|
||||
height: 40px;
|
||||
font-size: 16px;
|
||||
background-color: #05AEA3;
|
||||
text-align: center;
|
||||
}
|
||||
.header-values{
|
||||
color: #05AEA3;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
padding:25px;
|
||||
.el-row{
|
||||
background-color: #ffffff;
|
||||
border:1px solid #eeeeee;
|
||||
line-height: 14px;
|
||||
color: #333333;
|
||||
font-size: 12px;
|
||||
.el-col{
|
||||
padding:10px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.el-col:nth-child(-n+3){
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
}
|
||||
.el-col:not(:nth-child(3n)){
|
||||
border-right: 1px solid #eeeeee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.yl-warn-item-container{
|
||||
background-color: #FFF1F0;
|
||||
.header{
|
||||
.header-title{
|
||||
background-color: #FC6B69;
|
||||
}
|
||||
.header-values{
|
||||
color: #FC6B69;
|
||||
}
|
||||
|
||||
.info {
|
||||
float: right;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.pcs-tags {
|
||||
margin: 0 0 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pcs-tag-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.device-info-col {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.field-click-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.device-info-col.field-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.curve-tools {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.point-loading-icon {
|
||||
color: #409eff;
|
||||
display: inline-block;
|
||||
transform-origin: center;
|
||||
animation: pointLoadingSpinPulse 1.1s linear infinite;
|
||||
}
|
||||
@keyframes pointLoadingSpinPulse {
|
||||
0% { opacity: 0.45; transform: rotate(0deg) scale(0.9); }
|
||||
50% { opacity: 1; transform: rotate(180deg) scale(1.08); }
|
||||
100% { opacity: 0.45; transform: rotate(360deg) scale(0.9); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,25 +1,19 @@
|
||||
|
||||
<template>
|
||||
<div style="width:100%" v-loading="loading">
|
||||
<!-- 搜索栏-->
|
||||
<el-form :inline="true" class="select-container">
|
||||
<el-form-item label="电表">
|
||||
<el-select v-model="search.deviceId" placeholder="请选择" loading-text="正在加载数据">
|
||||
<el-option :label="item.deviceName" :value="item.id" v-for="(item,index) in deviceOptions" :key="index+'dbOptions'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="日报">-->
|
||||
<!-- <el-select v-model="search.rb" placeholder="请选择" :loading="loading" loading-text="正在加载数据">-->
|
||||
<!-- <el-option :label="item.name" :value="item.id" v-for="(item,index) in rbOptions" :key="index+'rbOptions'"></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>
|
||||
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>
|
||||
@ -27,157 +21,194 @@
|
||||
<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">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.dataTime}}{{scope.row.dataTime === '汇总' ? '' : ':00'}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<!--充电量列-->
|
||||
<el-table-column label="充电量">
|
||||
<el-table-column
|
||||
prop="activePeakKwh"
|
||||
label="尖">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="activeHighKwh"
|
||||
label="峰">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="activeFlatKwh"
|
||||
label="平">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="activeValleyKwh"
|
||||
label="谷">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="activeTotalKwh"
|
||||
label="总">
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<!--充电量列-->
|
||||
<el-table-column label="放电量">
|
||||
<el-table-column
|
||||
prop="reActivePeakKwh"
|
||||
label="尖">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="reActiveHighKwh"
|
||||
label="峰">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="reActiveFlatKwh"
|
||||
label="平">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="reActiveValleyKwh"
|
||||
label="谷">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="reActiveTotalKwh"
|
||||
label="总">
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<!-- 效率-->
|
||||
<el-table-column label="效率(%)">
|
||||
<el-table-column
|
||||
prop="effect">
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
</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, getLoadNameList} from '@/api/ems/dzjk'
|
||||
import {getAmmeterData} from '@/api/ems/dzjk'
|
||||
import {formatDate} from "@/filters/ems";
|
||||
|
||||
export default {
|
||||
name:'DzjkTjbbDbbb',
|
||||
name: 'DzjkTjbbDbbb',
|
||||
mixins: [getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
pickerOptions:{
|
||||
loading: false,
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
defaultDate:'',//默认展示的时间
|
||||
search:{deviceId:'',date:''},
|
||||
deviceOptions:[],
|
||||
// rbOptions:[
|
||||
// {name:'日报1',id:1},
|
||||
// {name:'日报2',id:2},
|
||||
// ],
|
||||
tableData:[]
|
||||
defaultDateRange: [],//默认展示的时间
|
||||
dateRange: [],
|
||||
tableData: [],
|
||||
pageSize: 10,//分页栏当前每个数据总数
|
||||
pageNum: 1,//分页栏当前页数
|
||||
totalSize: 0,//table表格数据总数
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
methods: {
|
||||
// 导出表格
|
||||
exportTable() {
|
||||
if (!this.dateRange?.length) return
|
||||
const [startTime, endTime] = this.dateRange
|
||||
this.download('ems/statsReport/exportAmmeterDataFromDaily', {
|
||||
siteId: this.siteId,
|
||||
startTime,
|
||||
endTime,
|
||||
}, `电表报表_${startTime}-${endTime}.xlsx`)
|
||||
},
|
||||
// 搜索
|
||||
onSearch(){
|
||||
onSearch() {
|
||||
this.pageNum = 1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
// 重置
|
||||
onReset(){
|
||||
this.search.date = ''
|
||||
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(){
|
||||
if(!this.search.deviceId) return
|
||||
this.loading=true
|
||||
getAmmeterData({siteId:this.siteId,deviceId:this.search.deviceId,dateTime:formatDate(this.search.date)}).then(response=>{
|
||||
this.tableData=response?.data || [];
|
||||
}).finally(()=> {
|
||||
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
|
||||
})
|
||||
},
|
||||
getDbList(){
|
||||
return getLoadNameList(this.siteId).then(response=>{
|
||||
this.deviceOptions=response?.data || [];
|
||||
this.deviceOptions.length > 0 && (this.search.deviceId = this.deviceOptions[0].id);
|
||||
})
|
||||
},
|
||||
init(){
|
||||
this.loading = true
|
||||
this.deviceOptions = []
|
||||
this.search.deviceId=''
|
||||
this.search.date=''
|
||||
this.tableData=[]
|
||||
this.getDbList().then(()=>{
|
||||
if(this.search.deviceId){
|
||||
this.onReset()
|
||||
}else{
|
||||
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()
|
||||
},
|
||||
},
|
||||
|
||||
mounted(){
|
||||
this.defaultDate = new Date()
|
||||
}
|
||||
}
|
||||
</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{
|
||||
::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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,182 +1,208 @@
|
||||
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="select-container">
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="电池堆">
|
||||
<el-select v-model="pcs" placeholder="请选择" :loading="loading" loading-text="正在加载数据">
|
||||
<el-option :label="item.deviceName" :value="item.id" v-for="(item,index) in pcsOptions" :key="index+'pcsListOptions'"></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>
|
||||
<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 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+'dcdqxBtns'" :class="{'activeBtn' : activeBtn === item.id}" @click="changeDataType(item.id)">{{item.name}}</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!--echart-->
|
||||
<div id="dcdEchart" style="height:360px;"></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>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import { getStackData, getStackNameList} from '@/api/ems/dzjk'
|
||||
import {getStackData} from "@/api/ems/dzjk";
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import DateRangeSelect from "@/components/Ems/DateRangeSelect/index.vue";
|
||||
|
||||
export default {
|
||||
name:'DzjkTjbbDcdqx',
|
||||
mixins: [resize,getQuerySiteId],
|
||||
name: "DzjkTjbbDcdqx",
|
||||
components: {DateRangeSelect},
|
||||
mixins: [resize, getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
pickerOptions:{
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
defaultDateRange:[],//默认展示的时间
|
||||
dateRange:[],
|
||||
loading:false,
|
||||
pcs:'',
|
||||
pcsOptions: [],
|
||||
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']]},
|
||||
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()
|
||||
changeDataType(id) {
|
||||
if (id !== this.activeBtn) {
|
||||
this.activeBtn = id;
|
||||
this.getData();
|
||||
}
|
||||
},
|
||||
// 搜索
|
||||
onSearch(){
|
||||
this.getData()
|
||||
// 更新时间范围 重置图表
|
||||
updateDate(data) {
|
||||
this.dateRange = data || [];
|
||||
this.getData();
|
||||
},
|
||||
// 重置
|
||||
onReset(){
|
||||
// this.pcs = this.pcsOptions.length > 0 ?this.pcsOptions[0].id : ''
|
||||
this.dateRange=''
|
||||
this.getData()
|
||||
},
|
||||
getPcsList(){
|
||||
return getStackNameList(this.siteId).then(response => {
|
||||
const data = JSON.parse(JSON.stringify(response?.data || []))
|
||||
this.pcsOptions = data
|
||||
this.pcs = data.length>0?data[0].id:'';
|
||||
})
|
||||
},
|
||||
getData(){
|
||||
const {siteId,pcs,activeBtn}=this;
|
||||
const [start='',end='']=(this.dateRange || [])
|
||||
if(!pcs) return
|
||||
getData() {
|
||||
const {siteId, activeBtn} = this;
|
||||
const [start = "", end = ""] = this.dateRange || [];
|
||||
//接口调用完成之后 设置图表、结束loading
|
||||
this.loading=true;
|
||||
getStackData({siteId,deviceId:pcs,startTime:formatDate(start),endTime:formatDate(end),dataType:activeBtn}).then(response => {
|
||||
this.setOption(response?.data || [])
|
||||
}).finally(()=>{this.loading=false;})
|
||||
|
||||
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 source = JSON.parse(JSON.stringify(ele.source))
|
||||
const length = ele.attr.length
|
||||
const series = []
|
||||
data.forEach((item)=>{
|
||||
const arr = ele.attr.map(key=>item[key])
|
||||
source.push([item.statisDate,...arr])
|
||||
})
|
||||
ele.attr.forEach((item)=>{
|
||||
series.push({
|
||||
name:length>1?item:ele.name,
|
||||
type:ele.type || 'scatter'
|
||||
})
|
||||
})
|
||||
this.chart.setOption({
|
||||
color:['#FFBD00','#3C81FF'],
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '10',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
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);
|
||||
}
|
||||
},
|
||||
textStyle:{
|
||||
color:"#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
dataset: {source},
|
||||
series
|
||||
},true)
|
||||
});
|
||||
});
|
||||
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'));
|
||||
this.chart = echarts.init(document.querySelector("#dcdEchart"));
|
||||
},
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
this.$refs.dateRangeSelect.init(true);
|
||||
});
|
||||
},
|
||||
init(){
|
||||
this.loading = true
|
||||
this.pcs=''
|
||||
this.pcsOptions=[]
|
||||
this.initChart()
|
||||
this.getPcsList().then(()=>{
|
||||
if(this.pcs){
|
||||
this.onReset()
|
||||
}else{
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
const now = new Date();
|
||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
this.defaultDateRange = [lastMonth, now];
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@ -147,6 +147,9 @@ export default {
|
||||
if(val){
|
||||
this.search.clusterId=''
|
||||
this.getClusterList()
|
||||
} else {
|
||||
this.search.clusterId=''
|
||||
this.clusterOptions=[]
|
||||
}
|
||||
},
|
||||
//表格数据
|
||||
@ -170,8 +173,15 @@ export default {
|
||||
})
|
||||
},
|
||||
async getClusterList(){
|
||||
const currentStackId = String(this.search.stackId || '')
|
||||
if (!currentStackId) {
|
||||
this.clusterOptions = []
|
||||
this.search.clusterId = ''
|
||||
return
|
||||
}
|
||||
this.clusterloading =true
|
||||
await getClusterNameList({stackDeviceId: this.search.stackId, siteId: this.siteId}).then(response => {
|
||||
if (String(this.search.stackId || '') !== currentStackId) return
|
||||
const data = JSON.parse(JSON.stringify(response?.data || []))
|
||||
this.clusterOptions = data
|
||||
this.search.clusterId = data.length > 0 ? data[0].id : ''
|
||||
@ -200,4 +210,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,183 +1,168 @@
|
||||
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="select-container">
|
||||
<el-form :inline="true">
|
||||
<!-- <el-form-item label="网点">-->
|
||||
<!-- <el-select v-model="wd" placeholder="请选择" :loading="loading" loading-text="正在加载数据">-->
|
||||
<!-- <el-option :label="item.name" :value="item.id" v-for="(item,index) in wdOptions" :key="index+'sblxOptions'"></el-option>-->
|
||||
<!-- </el-select>-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item label="PCS">
|
||||
<el-select v-model="pcs" placeholder="请选择" :loading="loading" loading-text="正在加载数据">
|
||||
<el-option :label="item.deviceName" :value="item.id" v-for="(item,index) in pcsOptions" :key="index+'pcsListOptions'"></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>
|
||||
<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 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+'flqxcBtns'" :class="{'activeBtn' : activeBtn === item.id}" @click="changeDataType(item.id)">{{item.name}}</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!--echart-->
|
||||
<div id="glqxEchart" style="height:360px;"></div>
|
||||
<div class="card-main" v-loading="loading">
|
||||
<div id="glqxEchart" style="height: 310px"></div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getPcsNameList, getPowerData} from "@/api/ems/dzjk";
|
||||
import {getPowerData} from "@/api/ems/dzjk";
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import DateRangeSelect from "@/components/Ems/DateRangeSelect/index.vue";
|
||||
|
||||
export default {
|
||||
name:'DzjkTjbbGlqx',
|
||||
mixins: [resize,getQuerySiteId],
|
||||
name: "DzjkTjbbGlqx",
|
||||
components: {DateRangeSelect},
|
||||
mixins: [resize, getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
pickerOptions:{
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
defaultDateRange:[],//默认展示的时间
|
||||
dateRange:[],
|
||||
loading:false,
|
||||
pcs:'',
|
||||
pcsOptions: [],
|
||||
activeBtn:'1',
|
||||
btnList:[
|
||||
{name:'电网功率',id:'1',attr:'gridPower'},
|
||||
{name:'负载功率',id:'2',attr:'loadPower'},
|
||||
{name:'储能功率',id:'3',attr:'storagePower'},
|
||||
{name:'光伏功率',id:'4',attr:'pvPower'},
|
||||
],
|
||||
|
||||
}
|
||||
dateRange: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeDataType(id){
|
||||
if(id !== this.activeBtn){
|
||||
console.log('点击了不同的菜单,更新数据')
|
||||
this.activeBtn=id;
|
||||
this.getData()
|
||||
}
|
||||
// 更新时间范围 重置图表
|
||||
updateDate(data) {
|
||||
this.dateRange = data || [];
|
||||
this.getData();
|
||||
},
|
||||
// 搜索
|
||||
onSearch(){
|
||||
this.getData()
|
||||
},
|
||||
// 重置
|
||||
onReset(){
|
||||
this.dateRange=[]
|
||||
this.getData()
|
||||
},
|
||||
getPcsList(){
|
||||
return getPcsNameList(this.siteId).then(response => {
|
||||
const data = response?.data || [];
|
||||
this.pcsOptions = data
|
||||
this.pcs = data.length>0?data[0].id:'';
|
||||
})
|
||||
},
|
||||
getData(){
|
||||
const {siteId,pcs,activeBtn}=this;
|
||||
const [start='',end='']=(this.dateRange || [])
|
||||
if(!pcs) return
|
||||
getData() {
|
||||
const {siteId} = this;
|
||||
let [start = "", end = ""] = this.dateRange || [];
|
||||
//接口调用完成之后 设置图表、结束loading
|
||||
this.loading=true;
|
||||
getPowerData({siteId,deviceId:pcs,startDate:formatDate(start),endDate:formatDate(end),dataType:activeBtn}).then(response => {
|
||||
this.setOption(response?.data || [])
|
||||
}).finally(()=>{this.loading=false;})
|
||||
this.loading = true;
|
||||
getPowerData({
|
||||
siteId,
|
||||
startDate: formatDate(start),
|
||||
endDate: formatDate(end),
|
||||
})
|
||||
.then((response) => {
|
||||
this.setOption(response?.data || []);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
setOption(data) {
|
||||
const {name,attr} =this.btnList.find(item=>item.id===this.activeBtn)
|
||||
const source = [['日期',name]]
|
||||
data.forEach((item,index)=>{
|
||||
source.push([item.statisDate,item[attr]])
|
||||
})
|
||||
this.chart.setOption({
|
||||
color:['#FFBD00','#3C81FF'],
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '10',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
},
|
||||
textStyle:{
|
||||
color:"#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
dataset:{source},
|
||||
series: [
|
||||
const source = [["日期", "电网功率", "负载功率", "储能功率", "光伏功率"]];
|
||||
data.forEach((item) => {
|
||||
source.push([
|
||||
item.statisDate,
|
||||
item.gridPower,
|
||||
item.loadPower,
|
||||
item.storagePower,
|
||||
item.pvPower,
|
||||
]);
|
||||
});
|
||||
this.chart.setOption(
|
||||
{
|
||||
name,
|
||||
type: 'scatter',
|
||||
}
|
||||
]
|
||||
},true)
|
||||
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() {
|
||||
this.chart = echarts.init(document.querySelector('#glqxEchart'));
|
||||
if (this.chart) return;
|
||||
this.chart = echarts.init(document.querySelector("#glqxEchart"));
|
||||
},
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
this.$refs.dateRangeSelect.init();
|
||||
});
|
||||
},
|
||||
init(){
|
||||
this.loading = true
|
||||
this.pcs=''
|
||||
this.pcsOptions=[]
|
||||
this.initChart()
|
||||
this.getPcsList().then(()=>{
|
||||
if(this.pcs){
|
||||
this.onReset()
|
||||
}else{
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
const now = new Date();
|
||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
this.defaultDateRange = [lastMonth, now];
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,37 +1,16 @@
|
||||
<template>
|
||||
<el-card shadow="always" class="common-card-container" style="margin-top:20px">
|
||||
<div slot="header">
|
||||
<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 class="select-container">
|
||||
<el-form :inline="true">
|
||||
<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="getData" 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>
|
||||
<div class="total-data">
|
||||
<div>总充电量:<span class="point">{{totalChargedCap | formatNumber}}kWh</span></div>
|
||||
<div>总放电量:<span class="point">{{totalDisChargedCap | formatNumber}}kWh</span></div>
|
||||
<div>综合效率:<span class="point">{{efficiency | formatNumber}}%</span></div>
|
||||
</div>
|
||||
<div id="dlzbChart" style="height: 310px"></div>
|
||||
<div id="dlzbChart" style="height: 310px;"></div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
@ -42,7 +21,9 @@ import resize from "@/mixins/ems/resize";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getElectricData} from '@/api/ems/dzjk'
|
||||
import {formatDate} from '@/filters/ems'
|
||||
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue'
|
||||
export default {
|
||||
components: {DateRangeSelect},
|
||||
mixins: [resize,getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
@ -51,7 +32,6 @@ export default {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
defaultDateRange:[],//默认展示的时间
|
||||
dateRange:[],
|
||||
loading:false,
|
||||
chart: null,
|
||||
@ -61,9 +41,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 重置
|
||||
onReset(){
|
||||
this.dateRange=[]
|
||||
// 更新时间范围 重置图表
|
||||
updateDate(data){
|
||||
this.dateRange=data || []
|
||||
this.getData()
|
||||
},
|
||||
setOption(data,unit){
|
||||
@ -72,17 +52,26 @@ export default {
|
||||
source.push([item.ammeterDate, item.chargedCap,item.disChargedCap,item.dailyEfficiency])
|
||||
})
|
||||
this.chart.setOption({
|
||||
color:['#FFBD00','#3C81FF','#05AEA3'],
|
||||
// legend: {
|
||||
// left: 'right',
|
||||
// bottom: '10',
|
||||
// },
|
||||
tooltip: {},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
name:unit,
|
||||
nameLocation:'center'
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '15',
|
||||
},
|
||||
grid: {
|
||||
top:40,
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
},
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
name:`单位:${unit}`,
|
||||
nameLocation:'center',
|
||||
nameGap:30
|
||||
}],
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
name:'充电量/放电量kWh',
|
||||
@ -102,22 +91,25 @@ export default {
|
||||
onZero:false
|
||||
}
|
||||
}],
|
||||
grid:{top:40},
|
||||
dataset:{
|
||||
source
|
||||
},
|
||||
//所有充放电颜色保持统一
|
||||
series: [
|
||||
{
|
||||
yAxisIndex:0,
|
||||
type: 'bar',//柱状图
|
||||
color:'#4472c4'
|
||||
},
|
||||
{
|
||||
yAxisIndex:0,
|
||||
type: 'bar',//柱状图
|
||||
color:'#70ad47'
|
||||
},
|
||||
{
|
||||
yAxisIndex:1,
|
||||
type: 'line',//柱状图
|
||||
color:'#FFBD00'
|
||||
},
|
||||
]
|
||||
})
|
||||
@ -133,20 +125,24 @@ export default {
|
||||
this.totalChargedCap=totalChargedCap
|
||||
this.totalDisChargedCap=totalDisChargedCap
|
||||
this.efficiency=efficiency
|
||||
}).catch(() => {
|
||||
this.setOption([], '')
|
||||
this.totalChargedCap=''
|
||||
this.totalDisChargedCap=''
|
||||
this.efficiency=''
|
||||
// 错误提示由全局请求拦截器处理,这里兜底避免出现 Uncaught (in promise)
|
||||
}).finally(() => {
|
||||
this.loading=false;
|
||||
})
|
||||
},
|
||||
init(){
|
||||
this.chart = echarts.init(document.querySelector('#dlzbChart'));
|
||||
this.onReset()
|
||||
this.$nextTick(()=>{
|
||||
this.chart = echarts.init(document.querySelector('#dlzbChart'));
|
||||
this.$refs.dateRangeSelect.init()
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
const now = new Date();
|
||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
this.defaultDateRange = [lastMonth, now];
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
@ -163,7 +159,7 @@ export default {
|
||||
line-height: 18px;
|
||||
color: #333333;
|
||||
font-size: 16px;
|
||||
padding:10px 0;
|
||||
padding:20px 0;
|
||||
>div{
|
||||
display: inline-block;
|
||||
margin-right: 20px;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container tjbb-ems-dashboard-editor-container">
|
||||
<div class="ems-dashboard-editor-container ems-third-menu-container">
|
||||
<el-menu
|
||||
class="ems-third-menu"
|
||||
:default-active="$route.name"
|
||||
@ -43,11 +43,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tjbb-ems-dashboard-editor-container{
|
||||
display: flex;
|
||||
padding:0;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
.tjbb-ems-content-container{
|
||||
margin-top:0;
|
||||
padding-top:0;
|
||||
|
||||
@ -1,188 +1,224 @@
|
||||
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="select-container">
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="PCS">
|
||||
<el-select v-model="pcs" placeholder="请选择" :loading="loading" loading-text="正在加载数据">
|
||||
<el-option :label="item.deviceName" :value="item.id" v-for="(item,index) in pcsOptions" :key="index+'pcsListOptions'"></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>
|
||||
<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 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+'flqxcBtns'" :class="{'activeBtn' : activeBtn === item.id}" @click="changeDataType(item.id)">{{item.name}}</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!--echart-->
|
||||
<div id="pcsEchart" style="height:360px;"></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 + 'flqxcBtns'"
|
||||
size="mini"
|
||||
:class="{ activeBtn: activeBtn === item.id }"
|
||||
@click="changeDataType(item.id)"
|
||||
>{{ item.name }}
|
||||
</el-button
|
||||
>
|
||||
</el-button-group>
|
||||
<div id="pcsEchart" style="height: 310px"></div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import { getPCSData, getPcsNameList} from '@/api/ems/dzjk'
|
||||
import {getPCSData} from "@/api/ems/dzjk";
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import DateRangeSelect from "@/components/Ems/DateRangeSelect/index.vue";
|
||||
|
||||
export default {
|
||||
name:'DzjkTjbbPcsqx',
|
||||
mixins: [resize,getQuerySiteId],
|
||||
name: "DzjkTjbbPcsqx",
|
||||
components: {DateRangeSelect},
|
||||
mixins: [resize, getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
pickerOptions:{
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
defaultDateRange:[],//默认展示的时间
|
||||
dateRange:[],
|
||||
loading:false,
|
||||
pcs:'',
|
||||
pcsOptions: [],
|
||||
activeBtn:'1',
|
||||
btnList:[
|
||||
{name:'有功功率',id:'1',attr:['activePower'],source:[['日期','有功功率']]},
|
||||
{name:'无功功率',id:'2',attr:['reactivePower'],source:[['日期','无功功率']]},
|
||||
// {name:'温度',id:'wd'},
|
||||
// {name:'三相电压',id:'sxdy'},
|
||||
{name:'三相电流',id:'3',attr:['uCurrent','vCurrent','wCurrent'],source:[['日期','u电流','v电流','w电流']],type:'bar'},
|
||||
dateRange: [],
|
||||
loading: false,
|
||||
activeBtn: "1",
|
||||
btnList: [
|
||||
{
|
||||
name: "有功功率",
|
||||
id: "1",
|
||||
attr: ["activePower"],
|
||||
source: ["有功功率"],
|
||||
},
|
||||
{
|
||||
name: "无功功率",
|
||||
id: "2",
|
||||
attr: ["reactivePower"],
|
||||
source: ["无功功率"],
|
||||
},
|
||||
{
|
||||
name: "三相电流",
|
||||
id: "3",
|
||||
attr: ["uCurrent", "vCurrent", "wCurrent"],
|
||||
source: ["u电流", "v电流", "w电流"],
|
||||
type: "bar",
|
||||
},
|
||||
],
|
||||
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeDataType(id){
|
||||
if(id !== this.activeBtn){
|
||||
console.log('点击了不同的菜单,更新数据')
|
||||
this.activeBtn=id;
|
||||
this.getData()
|
||||
changeDataType(id) {
|
||||
if (id !== this.activeBtn) {
|
||||
console.log("点击了不同的菜单,更新数据");
|
||||
this.activeBtn = id;
|
||||
this.getData();
|
||||
}
|
||||
},
|
||||
// 搜索
|
||||
onSearch(){
|
||||
this.getData()
|
||||
// 更新时间范围 重置图表
|
||||
updateDate(data) {
|
||||
this.dateRange = data || [];
|
||||
this.getData();
|
||||
},
|
||||
// 重置
|
||||
onReset(){
|
||||
// this.pcs = this.pcsOptions.length > 0 ?this.pcsOptions[0].id : ''
|
||||
this.dateRange=[]
|
||||
this.getData()
|
||||
},
|
||||
getPcsList(){
|
||||
return getPcsNameList(this.siteId).then(response => {
|
||||
const data = response?.data || [];
|
||||
this.pcsOptions = data
|
||||
this.pcs = data.length>0?data[0].id:'';
|
||||
})
|
||||
},
|
||||
getData(){
|
||||
const {siteId,pcs,activeBtn}=this;
|
||||
const [start='',end='']=(this.dateRange || [])
|
||||
if(!pcs) return
|
||||
this.loading=true;
|
||||
getData() {
|
||||
const {siteId, activeBtn} = this;
|
||||
const [start = "", end = ""] = this.dateRange || [];
|
||||
this.loading = true;
|
||||
//接口调用完成之后 设置图表、结束loading
|
||||
getPCSData({siteId,deviceId:pcs,startTime:formatDate(start),endTime:formatDate(end),dataType:activeBtn}).then(response => {
|
||||
this.setOption(response?.data || [])
|
||||
}).finally(()=>{this.loading=false;})
|
||||
|
||||
getPCSData({
|
||||
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 source = JSON.parse(JSON.stringify(ele.source))
|
||||
const length = ele.attr.length
|
||||
const series = []
|
||||
data.forEach((item)=>{
|
||||
const arr = ele.attr.map(key=>item[key])
|
||||
source.push([item.statisDate,...arr])
|
||||
})
|
||||
ele.attr.forEach((item)=>{
|
||||
series.push({
|
||||
name:length>1?item:ele.name,
|
||||
type:ele.type || 'scatter'
|
||||
})
|
||||
})
|
||||
this.chart.setOption({
|
||||
color:['#FFBD00','#3C81FF','#91cc74'],
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '10',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
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);
|
||||
}
|
||||
},
|
||||
textStyle:{
|
||||
color:"#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
dataset: {source},
|
||||
series
|
||||
|
||||
},true)
|
||||
});
|
||||
});
|
||||
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('#pcsEchart'));
|
||||
this.chart = echarts.init(document.querySelector("#pcsEchart"));
|
||||
},
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
this.$refs.dateRangeSelect.init(true);
|
||||
});
|
||||
},
|
||||
init(){
|
||||
this.loading = true
|
||||
this.pcs=''
|
||||
this.pcsOptions=[]
|
||||
this.dateRange =[]
|
||||
this.initChart()
|
||||
this.getPcsList().then(()=>{
|
||||
if(this.pcs){
|
||||
this.onReset()
|
||||
}else{
|
||||
this.loading=false;
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
const now = new Date();
|
||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
this.defaultDateRange = [lastMonth, now];
|
||||
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
240
src/views/ems/dzjk/tjbb/sybb/index.vue
Normal file
@ -0,0 +1,240 @@
|
||||
<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"
|
||||
show-summary
|
||||
stripe
|
||||
style="width: 100%;margin-top:25px;">
|
||||
<!-- 汇总列-->
|
||||
<el-table-column label="汇总" min-width="100px" align="center">
|
||||
<el-table-column
|
||||
prop="dataTime"
|
||||
label="日期"
|
||||
min-width="100px" align="center">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="dayType"
|
||||
label="日期类型"
|
||||
min-width="100px" align="center">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="weatherDesc"
|
||||
label="天气情况"
|
||||
min-width="180px"
|
||||
align="center">
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<!--充电量列-->
|
||||
<el-table-column label="充电价格" align="center">
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activePeakPrice"
|
||||
label="尖">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeHighPrice"
|
||||
label="峰">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeFlatPrice"
|
||||
label="平">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeValleyPrice"
|
||||
label="谷">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeTotalPrice"
|
||||
label="总">
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<!--充电量列-->
|
||||
<el-table-column label="放电价格" align="center">
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActivePeakPrice"
|
||||
label="尖">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveHighPrice"
|
||||
label="峰">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveFlatPrice"
|
||||
label="平">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveValleyPrice"
|
||||
label="谷">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveTotalPrice"
|
||||
label="总">
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<!-- 实际收益-->
|
||||
<el-table-column label="" align="center" fixed="right">
|
||||
<el-table-column
|
||||
prop="actualRevenue"
|
||||
label="实际收益"
|
||||
align="center">
|
||||
</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 {getAmmeterRevenueData} from '@/api/ems/dzjk'
|
||||
import {formatDate} from "@/filters/ems";
|
||||
|
||||
export default {
|
||||
name: 'DzjkTjbbSybb',
|
||||
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/exportAmmeterRevenueData', {
|
||||
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 || [])
|
||||
getAmmeterRevenueData({siteId: siteId, startTime, endTime, pageSize, pageNum}).then(response => {
|
||||
const rows = response?.rows || [];
|
||||
this.totalSize = response?.total || 0
|
||||
this.tableData = rows
|
||||
}).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;
|
||||
}
|
||||
|
||||
.el-table__footer-wrapper {
|
||||
tbody td.el-table__cell {
|
||||
color: #000;
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -39,9 +39,12 @@ export default {
|
||||
source.push([item.dateMonth,item.chargeEnergy,item.disChargeEnergy])
|
||||
})
|
||||
this.chart.setOption({
|
||||
grid: {
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '10',
|
||||
bottom: '15',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
|
||||
@ -39,9 +39,12 @@ export default {
|
||||
})
|
||||
this.chart.setOption({
|
||||
color:['#3C81FF','#FFBE29'],
|
||||
grid: {
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '10',
|
||||
bottom: '15',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
|
||||
@ -39,9 +39,12 @@ export default {
|
||||
})
|
||||
this.chart.setOption({
|
||||
color:['#F86F70'],
|
||||
grid: {
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '10',
|
||||
bottom: '15',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
|
||||
@ -42,9 +42,12 @@ export default {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
grid: {
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom:'10'
|
||||
bottom: '15',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
|
||||
@ -39,9 +39,12 @@ export default {
|
||||
})
|
||||
this.chart.setOption({
|
||||
color:['#FFBE01'],
|
||||
grid: {
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '10',
|
||||
bottom: '15',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container" v-loading="loading">
|
||||
<div class="ems-dashboard-editor-container">
|
||||
<zd-info></zd-info>
|
||||
<div class="ems-content-container ems-content-container-padding">
|
||||
<div class="content-title">数据概览</div>
|
||||
<el-row :gutter="32" style="background:#fff;margin:30px 0;">
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<el-row :gutter="15" style="background:#fff;margin:30px 0;">
|
||||
<el-col :xs="24" :sm="12" :lg="12" v-loading="chartLoading.dlzbchart">
|
||||
<dlzb-chart ref="dlzbchart"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<el-col :xs="24" :sm="12" :lg="12" v-loading="chartLoading.xtxlchart">
|
||||
<xtxl-chart ref="xtxlchart"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="32" style="background:#fff;margin:0;">
|
||||
<el-col :xs="24" :sm="8" :lg="8">
|
||||
<el-row :gutter="15" style="background:#fff;margin:0;">
|
||||
<el-col :xs="24" :sm="8" :lg="8" v-loading="chartLoading.gjqsChart">
|
||||
<gjqs-chart ref="gjqsChart"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :lg="8">
|
||||
<el-col :xs="24" :sm="8" :lg="8" v-loading="chartLoading.sbgjzbChart">
|
||||
<sbgjzb-chart ref="sbgjzbChart"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :lg="8">
|
||||
<el-col :xs="24" :sm="8" :lg="8" v-loading="chartLoading.gjdjfbChart">
|
||||
<gjdjfb-chart ref="gjdjfbChart"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -47,24 +47,45 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
chartLoading: {
|
||||
dlzbchart: false,
|
||||
xtxlchart: false,
|
||||
gjqsChart: false,
|
||||
sbgjzbChart: false,
|
||||
gjdjfbChart: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
setChartLoading(loading) {
|
||||
this.chartLoading = {
|
||||
dlzbchart: loading,
|
||||
xtxlchart: loading,
|
||||
gjqsChart: loading,
|
||||
sbgjzbChart: loading,
|
||||
gjdjfbChart: loading
|
||||
}
|
||||
},
|
||||
hideChartLoading(key) {
|
||||
this.$set(this.chartLoading, key, false)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loading = true
|
||||
this.setChartLoading(true)
|
||||
dataList().then(response => {
|
||||
const data = JSON.parse(JSON.stringify(response?.data || {}))
|
||||
this.$refs.dlzbchart.initChart(data?.elecDataList || [])
|
||||
this.hideChartLoading('dlzbchart')
|
||||
this.$refs.xtxlchart.initChart(data?.sysEfficList || [])
|
||||
this.hideChartLoading('xtxlchart')
|
||||
this.$refs.gjqsChart.initChart(data?.alarmDataList || [])
|
||||
this.hideChartLoading('gjqsChart')
|
||||
this.$refs.sbgjzbChart.initChart(data?.deviceAlarmList || [])
|
||||
this.hideChartLoading('sbgjzbChart')
|
||||
this.$refs.gjdjfbChart.initChart(data?.alarmLevelList || [])
|
||||
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
this.hideChartLoading('gjdjfbChart')
|
||||
}).catch(() => {
|
||||
this.setChartLoading(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
136
src/views/ems/search/DateTimeSelect.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div class="time-range">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
:type="type"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
:format="valueFormat"
|
||||
:value-format="valueFormat"
|
||||
:clearable="false"
|
||||
:picker-options="pickerOptions"
|
||||
end-placeholder="结束时间">
|
||||
</el-date-picker>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {formatDate} from '@/filters/ems'
|
||||
export default {
|
||||
props:{
|
||||
dataUnit:{
|
||||
type:Number,
|
||||
default:1
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
type(){
|
||||
return this.dataUnit === 3 ? 'daterange' : 'datetimerange'
|
||||
},
|
||||
valueFormat(){
|
||||
return this.dataUnit === 3 ? 'yyyy-MM-dd' : 'yyyy-MM-dd HH:mm:ss'
|
||||
},
|
||||
disabledNextBtn(){
|
||||
if(this.dateRange && this.dateRange.length ===2){
|
||||
return new Date(this.dateRange[1]) >= new Date(this.defaultDateRange[1])
|
||||
}else{
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
dateRange:[],
|
||||
defaultDateRange:[],
|
||||
pickerOptions:{
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(){
|
||||
const {dataUnit} = this
|
||||
const timeDis= dataUnit === 3? 30 * 24 * 60 * 60 * 1000 :dataUnit === 2 ? 24 * 60 * 60 * 1000 : 60 * 60 * 1000
|
||||
const now = new Date(),formatNow = formatDate(now.getTime(),dataUnit !== 3 );
|
||||
const timeAgo = formatDate(new Date(now.getTime() - timeDis),dataUnit !== 3)
|
||||
this.dateRange = [timeAgo, formatNow];
|
||||
this.defaultDateRange=[timeAgo, formatNow];
|
||||
console.log('init',timeAgo,formatNow)
|
||||
this.$emit('initDate',this.dateRange || [])
|
||||
},
|
||||
showBtnLoading(status){
|
||||
this.loading = status
|
||||
},
|
||||
// 切换分、时、天 重置时间可选范围
|
||||
resetDate(){
|
||||
this.dateRange = this.defaultDateRange
|
||||
},
|
||||
//重置 设置时间范围为初始化时间段
|
||||
reset(){
|
||||
this.resetDate()
|
||||
this.$emit('updateDate',this.dateRange || [])
|
||||
},
|
||||
// 搜索
|
||||
search(){
|
||||
if(this.dateRange && this.dateRange.length>0){
|
||||
const {dataUnit} = this
|
||||
const [start,end] = this.dateRange
|
||||
if([1,2].includes(dataUnit)){
|
||||
const startTime = new Date(start),endTime=new Date(end)
|
||||
const timeDis= 7 * 24 * 60 * 60 * 1000
|
||||
if(endTime - startTime > timeDis){
|
||||
this.$message.error(`按分钟或小时查询数据,时间范围不能超过7天`)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.$emit('updateDate',this.dateRange || [])
|
||||
}else{
|
||||
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 = this.dataUnit === 3 ? 24 * 60 * 60 * 1000 :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,this.dataUnit !== 3),formatDate(end,this.dataUnit !== 3)]
|
||||
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: 24px;
|
||||
}
|
||||
.el-button--mini{
|
||||
padding:3px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
946
src/views/ems/search/index.vue
Normal file
@ -0,0 +1,946 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container" style="background-color: #ffffff">
|
||||
<el-form ref="form" :model="form" label-position="top">
|
||||
<div class="query-groups-toolbar">
|
||||
<span class="query-groups-count">当前 {{ form.queryGroups.length }} 个点位</span>
|
||||
<el-button type="primary" size="mini" plain @click="addQueryGroup">新增点位</el-button>
|
||||
</div>
|
||||
<div class="query-groups-row">
|
||||
<div
|
||||
v-for="(group, index) in form.queryGroups"
|
||||
:key="group.key"
|
||||
class="query-group"
|
||||
>
|
||||
<el-form-item :label="`点位 ${index + 1}`" class="group-point-item">
|
||||
<div class="point-select-wrapper">
|
||||
<el-select
|
||||
v-model="group.pointId"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
:disabled="!canSelectPoint(group)"
|
||||
:placeholder="pointSelectPlaceholder(group)"
|
||||
:remote-method="(query) => remotePointSearch(index, query)"
|
||||
:loading="group.pointLoading"
|
||||
:no-data-text="pointNoDataText(group)"
|
||||
class="point-select"
|
||||
@change="(value) => handlePointChange(index, value)"
|
||||
@visible-change="(visible) => handlePointDropdownVisible(index, visible)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in group.pointOptions"
|
||||
:key="`${group.key}-${item.value}`"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="point-select-toolbar">
|
||||
<span class="point-select-tip">{{ group.pointId ? "已选择点位" : "未选择点位" }}</span>
|
||||
<div>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
:disabled="!canSelectPoint(group)"
|
||||
@click="refreshPointOptions(index)"
|
||||
>
|
||||
刷新点位
|
||||
</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
:disabled="!group.pointId"
|
||||
@click="clearPointSelection(index)"
|
||||
>
|
||||
清空选择
|
||||
</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
:disabled="form.queryGroups.length <= 1"
|
||||
@click="removeQueryGroup(index)"
|
||||
>
|
||||
删除点位
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">生成图表</el-button>
|
||||
<el-button style="margin-left: 8px" @click="handleExportData">导出数据</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<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">
|
||||
<el-radio-group v-model="form.dataUnit">
|
||||
<el-radio :label="1">分钟</el-radio>
|
||||
<el-radio :label="2">小时</el-radio>
|
||||
<el-radio :label="3">天</el-radio>
|
||||
</el-radio-group>
|
||||
</span>
|
||||
<date-time-select
|
||||
ref="dateTimeSelect"
|
||||
:data-unit="form.dataUnit"
|
||||
@initDate="(e) => (form.dataRange = e || [])"
|
||||
@updateDate="updateDate"
|
||||
/>
|
||||
</div>
|
||||
<div style="height: 350px" id="searchChart"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import {getPointValueList, pointFuzzyQuery} from "@/api/ems/search";
|
||||
import DateTimeSelect from "./DateTimeSelect.vue";
|
||||
|
||||
export default {
|
||||
name: "Search",
|
||||
mixins: [resize],
|
||||
components: {DateTimeSelect},
|
||||
watch: {
|
||||
"$route.query.siteId": {
|
||||
handler(newVal) {
|
||||
this.syncQuerySiteIds(newVal);
|
||||
},
|
||||
},
|
||||
"form.dataUnit": {
|
||||
handler() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.dateTimeSelect.init();
|
||||
this.getDate();
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
form: {
|
||||
dataRange: [],
|
||||
siteIds: [],
|
||||
queryGroups: [],
|
||||
dataUnit: 1,
|
||||
},
|
||||
queryGroupSeed: 0,
|
||||
lastQueryResult: [],
|
||||
sitePointOptionsCache: {},
|
||||
sitePointRequestId: 0,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.form.queryGroups = [];
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
this.addQueryGroup();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getChartColor(index = 0) {
|
||||
const palette = [
|
||||
"#5470C6",
|
||||
"#91CC75",
|
||||
"#FAC858",
|
||||
"#EE6666",
|
||||
"#73C0DE",
|
||||
"#3BA272",
|
||||
"#FC8452",
|
||||
"#9A60B4",
|
||||
"#EA7CCC",
|
||||
];
|
||||
return palette[index % palette.length];
|
||||
},
|
||||
getSelectedPointAxisKeys() {
|
||||
const keys = [];
|
||||
const seen = new Set();
|
||||
(this.form.queryGroups || []).forEach((group) => {
|
||||
const key = String(this.resolveSelectedPointName(group) || group?.pointId || "").trim();
|
||||
if (!key || seen.has(key)) return;
|
||||
seen.add(key);
|
||||
keys.push(key);
|
||||
});
|
||||
return keys;
|
||||
},
|
||||
buildDynamicYAxisConfig(data = []) {
|
||||
const axisIndexMap = {};
|
||||
const yAxis = [];
|
||||
const orderedAxisKeys = [];
|
||||
const seenAxisKeys = new Set();
|
||||
|
||||
const pushAxisKey = (key) => {
|
||||
const normalized = String(key || "").trim();
|
||||
if (!normalized || seenAxisKeys.has(normalized)) return;
|
||||
seenAxisKeys.add(normalized);
|
||||
orderedAxisKeys.push(normalized);
|
||||
};
|
||||
|
||||
this.getSelectedPointAxisKeys().forEach((key) => pushAxisKey(key));
|
||||
|
||||
data.forEach((item) => {
|
||||
const pointId = String(item?.pointId || "").trim();
|
||||
const pointName = String(item?.pointName || "").trim();
|
||||
pushAxisKey(pointName || pointId);
|
||||
|
||||
(item?.deviceList || []).forEach((inner) => {
|
||||
pushAxisKey(inner?.deviceId);
|
||||
});
|
||||
});
|
||||
|
||||
orderedAxisKeys.forEach((pointKey) => {
|
||||
const axisIndex = yAxis.length;
|
||||
const sideIndex = Math.floor(axisIndex / 2);
|
||||
const position = axisIndex % 2 === 0 ? "left" : "right";
|
||||
const color = this.getChartColor(axisIndex);
|
||||
axisIndexMap[pointKey] = axisIndex;
|
||||
|
||||
yAxis.push({
|
||||
type: "value",
|
||||
name: pointKey || `点位${axisIndex + 1}`,
|
||||
position,
|
||||
offset: sideIndex * 55,
|
||||
alignTicks: true,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {color},
|
||||
},
|
||||
axisLabel: {
|
||||
color,
|
||||
},
|
||||
nameTextStyle: {
|
||||
color,
|
||||
},
|
||||
splitLine: {
|
||||
show: axisIndex === 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (yAxis.length === 0) {
|
||||
yAxis.push({type: "value"});
|
||||
}
|
||||
|
||||
const axisCount = yAxis.length;
|
||||
const leftCount = Math.ceil(axisCount / 2);
|
||||
const rightCount = Math.floor(axisCount / 2);
|
||||
|
||||
return {
|
||||
yAxis,
|
||||
axisIndexMap,
|
||||
grid: {
|
||||
containLabel: true,
|
||||
left: 40 + Math.max(0, leftCount - 1) * 55,
|
||||
right: 40 + Math.max(0, rightCount - 1) * 55,
|
||||
},
|
||||
};
|
||||
},
|
||||
createEmptyQueryGroup(key) {
|
||||
return {
|
||||
key,
|
||||
pointId: "",
|
||||
selectedPointName: "",
|
||||
pointOptions: [],
|
||||
pointOptionsCache: {},
|
||||
pointRequestId: 0,
|
||||
pointLoading: false,
|
||||
};
|
||||
},
|
||||
addQueryGroup() {
|
||||
this.queryGroupSeed += 1;
|
||||
this.form.queryGroups.push(this.createEmptyQueryGroup(this.queryGroupSeed));
|
||||
},
|
||||
removeQueryGroup(groupIndex) {
|
||||
if (this.form.queryGroups.length <= 1) {
|
||||
this.$message.warning("至少保留1个点位");
|
||||
return;
|
||||
}
|
||||
this.form.queryGroups.splice(groupIndex, 1);
|
||||
},
|
||||
getQueryGroup(index) {
|
||||
return this.form.queryGroups[index];
|
||||
},
|
||||
canSelectPoint(group) {
|
||||
return !!(group && this.form.siteIds && this.form.siteIds.length > 0);
|
||||
},
|
||||
pointSelectPlaceholder(group) {
|
||||
return this.canSelectPoint(group)
|
||||
? "支持关键字搜索,展开可查看点位列表"
|
||||
: "暂无可用站点点位";
|
||||
},
|
||||
pointNoDataText(group) {
|
||||
return this.canSelectPoint(group) ? "暂无匹配点位" : "暂无可用站点点位";
|
||||
},
|
||||
showLoading() {
|
||||
this.chart && this.chart.showLoading();
|
||||
},
|
||||
hideLoading() {
|
||||
this.chart && this.chart.hideLoading();
|
||||
},
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.querySelector("#searchChart"));
|
||||
},
|
||||
updateDate(val) {
|
||||
this.form.dataRange = val || [];
|
||||
this.getDate();
|
||||
},
|
||||
setOption(data) {
|
||||
if (!this.chart) return;
|
||||
this.chart.clear();
|
||||
if (!data || data.length <= 0) {
|
||||
this.$message.warning("暂无数据");
|
||||
return;
|
||||
}
|
||||
|
||||
if (data[0].chartType === 2) {
|
||||
this.setBoxOption(data);
|
||||
} else {
|
||||
this.setLineOption(data);
|
||||
}
|
||||
},
|
||||
setLineOption(data) {
|
||||
let dataset = [];
|
||||
const axisConfig = this.buildDynamicYAxisConfig(data);
|
||||
data.forEach((item) => {
|
||||
const pointId = String(item?.pointId || "").trim();
|
||||
const pointName = String(item?.pointName || "").trim();
|
||||
const pointKey = pointId || pointName;
|
||||
item.deviceList.forEach((inner) => {
|
||||
const seriesKey = String(inner?.deviceId || "").trim() || pointKey;
|
||||
const yAxisIndex = axisConfig.axisIndexMap[seriesKey] ?? 0;
|
||||
const axisColor = this.getChartColor(yAxisIndex);
|
||||
dataset.push({
|
||||
name: inner.deviceId,
|
||||
type: "line",
|
||||
yAxisIndex,
|
||||
itemStyle: {
|
||||
color: axisColor,
|
||||
},
|
||||
lineStyle: {
|
||||
color: axisColor,
|
||||
},
|
||||
markPoint: {
|
||||
symbolSize: 30,
|
||||
emphasis: {
|
||||
disabled: false,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: "最大值",
|
||||
coord: [inner.maxDate, inner.maxValue],
|
||||
relativeTo: "coordinate",
|
||||
label: {
|
||||
position: "top",
|
||||
formatter: item.dataType === 2
|
||||
? [
|
||||
`最大值:${inner.maxValue}`,
|
||||
`差值:${inner.diffValue}`,
|
||||
].join("\n")
|
||||
: [`最大值:${inner.maxValue}`].join("\n"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "最小值",
|
||||
coord: [inner.minDate, inner.minValue],
|
||||
relativeTo: "coordinate",
|
||||
label: {
|
||||
position: "top",
|
||||
formatter: item.dataType === 2
|
||||
? [
|
||||
`最小值:${inner.minValue}`,
|
||||
`差值:${inner.diffValue}`,
|
||||
].join("\n")
|
||||
: [`最小值:${inner.minValue}`].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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.chart.setOption({
|
||||
legend: {},
|
||||
grid: axisConfig.grid,
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {type: "category", data: dataset?.[0]?.xdata || []},
|
||||
yAxis: axisConfig.yAxis,
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: dataset,
|
||||
});
|
||||
},
|
||||
setBoxOption(data) {
|
||||
let dataset = [];
|
||||
const axisConfig = this.buildDynamicYAxisConfig(data);
|
||||
data.forEach((item) => {
|
||||
const pointId = String(item?.pointId || "").trim();
|
||||
const pointName = String(item?.pointName || "").trim();
|
||||
const pointKey = pointId || pointName;
|
||||
item.deviceList.forEach((inner) => {
|
||||
const seriesKey = String(inner?.deviceId || "").trim() || pointKey;
|
||||
const yAxisIndex = axisConfig.axisIndexMap[seriesKey] ?? 0;
|
||||
const axisColor = this.getChartColor(yAxisIndex);
|
||||
dataset.push({
|
||||
name: inner.deviceId,
|
||||
type: "boxplot",
|
||||
yAxisIndex,
|
||||
itemStyle: {
|
||||
color: axisColor,
|
||||
},
|
||||
xdata: [],
|
||||
data: [],
|
||||
});
|
||||
const length = dataset.length;
|
||||
inner.pointValueList.forEach((value) => {
|
||||
const {valueDate, min, q1, median, q3, max} = value;
|
||||
dataset[length - 1].xdata.push(valueDate);
|
||||
dataset[length - 1].data.push([min, q1, median, q3, max]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.chart.setOption({
|
||||
legend: {},
|
||||
grid: axisConfig.grid,
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
formatter: function (params) {
|
||||
let itemData = params.data;
|
||||
let result = params.marker + params.name + " " + params.seriesName + "<br/>";
|
||||
result += "最小值: " + itemData[1] + "<br/>";
|
||||
result += "平均值: " + itemData[3] + "<br/>";
|
||||
result += "最大值: " + itemData[5];
|
||||
return result;
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {type: "category", data: dataset?.[0]?.xdata || []},
|
||||
yAxis: axisConfig.yAxis,
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: dataset,
|
||||
});
|
||||
},
|
||||
submitForm() {
|
||||
this.getDate();
|
||||
},
|
||||
buildQueryPayload() {
|
||||
const activeGroups = this.form.queryGroups
|
||||
.map((group) => ({group}))
|
||||
.filter(({group}) => !!group.pointId);
|
||||
|
||||
if (activeGroups.length === 0) {
|
||||
this.$message.error("请至少选择1组点位");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.form.siteIds || this.form.siteIds.length === 0) {
|
||||
this.$message.error("请先在顶部选择站点");
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
siteIds,
|
||||
dataUnit,
|
||||
dataRange: [start = "", end = ""],
|
||||
} = this.form;
|
||||
|
||||
if (!start || !end) {
|
||||
this.$message.error("请选择时间");
|
||||
return null;
|
||||
}
|
||||
|
||||
let startDate = start;
|
||||
let endDate = end;
|
||||
if (start && dataUnit === 3) {
|
||||
startDate = `${start} 00:00:00`;
|
||||
}
|
||||
if (end && dataUnit === 3) {
|
||||
endDate = `${end} 00:00:00`;
|
||||
}
|
||||
|
||||
const selectedPoints = [];
|
||||
const pointIdSet = new Set();
|
||||
activeGroups.forEach(({group}) => {
|
||||
const pointId = String(group.pointId || "").trim();
|
||||
if (!pointId || pointIdSet.has(pointId)) return;
|
||||
pointIdSet.add(pointId);
|
||||
selectedPoints.push({
|
||||
pointId,
|
||||
pointName: this.resolveSelectedPointName(group) || pointId,
|
||||
});
|
||||
});
|
||||
|
||||
if (selectedPoints.length === 0) {
|
||||
this.$message.error("请至少选择1组点位");
|
||||
return null;
|
||||
}
|
||||
|
||||
const pointIds = selectedPoints.map((item) => item.pointId);
|
||||
const pointNames = selectedPoints.map((item) => item.pointName);
|
||||
return {
|
||||
siteIds,
|
||||
dataUnit,
|
||||
pointIds,
|
||||
pointNames,
|
||||
pointId: pointIds.join(","),
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
},
|
||||
requestPointData(payload) {
|
||||
return getPointValueList(payload).then((response) => response?.data || []);
|
||||
},
|
||||
resolveExportPointValue(value = {}) {
|
||||
if (value?.pointValue !== undefined && value?.pointValue !== null && value?.pointValue !== "") {
|
||||
return value.pointValue;
|
||||
}
|
||||
if (value?.median !== undefined && value?.median !== null && value?.median !== "") {
|
||||
return value.median;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
normalizeExportRows(data = [], payload = {}) {
|
||||
const pointIds = payload?.pointIds || [];
|
||||
const pointNames = payload?.pointNames || [];
|
||||
const selectedPoints = pointIds
|
||||
.map((pointId, index) => {
|
||||
const normalizedId = String(pointId || "").trim();
|
||||
if (!normalizedId) return null;
|
||||
return {
|
||||
pointId: normalizedId,
|
||||
pointName: String(pointNames[index] || normalizedId).trim(),
|
||||
pointOrder: index,
|
||||
};
|
||||
})
|
||||
.filter((item) => !!item);
|
||||
const selectedPointById = {};
|
||||
const selectedPointByName = {};
|
||||
selectedPoints.forEach((point) => {
|
||||
selectedPointById[point.pointId] = point;
|
||||
if (point.pointName) {
|
||||
selectedPointByName[point.pointName] = point;
|
||||
}
|
||||
});
|
||||
|
||||
const groups = [];
|
||||
const groupMap = {};
|
||||
const rowMap = {};
|
||||
const rowDates = [];
|
||||
data.forEach((item, itemIndex) => {
|
||||
const itemPointId = String(item?.pointId || "").trim();
|
||||
const itemPointName = String(item?.pointName || "").trim();
|
||||
const pointByItemId = itemPointId ? selectedPointById[itemPointId] : null;
|
||||
const pointByItemName = itemPointName ? selectedPointByName[itemPointName] : null;
|
||||
|
||||
(item?.deviceList || []).forEach((device) => {
|
||||
const deviceId = String(device?.deviceId || "").trim();
|
||||
const pointByDeviceId = deviceId ? selectedPointById[deviceId] : null;
|
||||
const pointByDeviceName = deviceId ? selectedPointByName[deviceId] : null;
|
||||
const pointByOrder = selectedPoints[itemIndex] || null;
|
||||
const matchedPoint = pointByItemId || pointByItemName || pointByDeviceId || pointByDeviceName || pointByOrder || null;
|
||||
|
||||
const pointId = itemPointId || matchedPoint?.pointId || "";
|
||||
const pointName = itemPointName || matchedPoint?.pointName || pointId;
|
||||
const pointOrder = matchedPoint?.pointOrder ?? Number.MAX_SAFE_INTEGER;
|
||||
const pointKey = pointId || pointName || `point_${itemIndex + 1}`;
|
||||
const deviceKey = deviceId || "unknown";
|
||||
const groupKey = `${pointKey}__${deviceKey}`;
|
||||
|
||||
if (!groupMap[groupKey]) {
|
||||
groupMap[groupKey] = true;
|
||||
groups.push({
|
||||
groupKey,
|
||||
pointId,
|
||||
pointName,
|
||||
deviceId,
|
||||
pointOrder,
|
||||
});
|
||||
}
|
||||
(device?.pointValueList || []).forEach((value) => {
|
||||
const valueDate = String(value?.valueDate || "").trim();
|
||||
if (!valueDate) return;
|
||||
if (!rowMap[valueDate]) {
|
||||
rowMap[valueDate] = {valueDate};
|
||||
rowDates.push(valueDate);
|
||||
}
|
||||
rowMap[valueDate][groupKey] = this.resolveExportPointValue(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
groups.sort((a, b) => {
|
||||
const ao = a.pointOrder === -1 ? Number.MAX_SAFE_INTEGER : a.pointOrder;
|
||||
const bo = b.pointOrder === -1 ? Number.MAX_SAFE_INTEGER : b.pointOrder;
|
||||
if (ao !== bo) return ao - bo;
|
||||
if (a.pointId !== b.pointId) return a.pointId.localeCompare(b.pointId);
|
||||
return a.deviceId.localeCompare(b.deviceId);
|
||||
});
|
||||
|
||||
return {
|
||||
groups,
|
||||
rows: rowDates.sort().map((valueDate) => rowMap[valueDate]),
|
||||
};
|
||||
},
|
||||
csvCell(value) {
|
||||
const text = value === undefined || value === null ? "" : String(value);
|
||||
return `"${text.replace(/"/g, '""')}"`;
|
||||
},
|
||||
exportRowsToCsv({groups = [], rows = []} = {}) {
|
||||
const headers = ["时间"];
|
||||
groups.forEach((_, index) => {
|
||||
const order = index + 1;
|
||||
headers.push(`点位ID${order}`, `点位名称${order}`, `设备ID${order}`, `点位值${order}`);
|
||||
});
|
||||
const lines = [headers.map((item) => this.csvCell(item)).join(",")];
|
||||
rows.forEach((row) => {
|
||||
const line = [row.valueDate || ""];
|
||||
groups.forEach((group) => {
|
||||
line.push(group.pointId || "", group.pointName || "", group.deviceId || "", row[group.groupKey] ?? "");
|
||||
});
|
||||
lines.push(line.map((item) => this.csvCell(item)).join(","));
|
||||
});
|
||||
const csv = `\uFEFF${lines.join("\n")}`;
|
||||
this.$download.saveAs(new Blob([csv], {type: "text/csv;charset=utf-8;"}), `综合查询数据_${new Date().getTime()}.csv`);
|
||||
},
|
||||
handleExportData() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) return;
|
||||
const payload = this.buildQueryPayload();
|
||||
if (!payload) return;
|
||||
|
||||
this.requestPointData(payload).then((data) => {
|
||||
if (!data || data.length === 0) {
|
||||
this.$message.warning("暂无可导出数据");
|
||||
return;
|
||||
}
|
||||
const exportData = this.normalizeExportRows(data, payload);
|
||||
if (!exportData?.rows?.length || !exportData?.groups?.length) {
|
||||
this.$message.warning("暂无可导出数据");
|
||||
return;
|
||||
}
|
||||
this.exportRowsToCsv(exportData);
|
||||
this.$message.success("导出成功");
|
||||
}).catch((error) => {
|
||||
if (error?.code === "ECONNABORTED") {
|
||||
this.$message.error("查询超时,请缩短时间范围后重试");
|
||||
return;
|
||||
}
|
||||
this.$message.error("导出失败,请稍后重试");
|
||||
});
|
||||
});
|
||||
},
|
||||
getPointCacheKey(query = "") {
|
||||
return `${this.form.siteIds.join(",")}_${query.trim()}`;
|
||||
},
|
||||
getSitePointCacheKey() {
|
||||
return this.form.siteIds.join(",");
|
||||
},
|
||||
formatPointLabel({pointId = "", pointName = "", dataKey = ""} = {}) {
|
||||
return `${pointId || "-"}-${pointName || "-"}(${dataKey || "-"})`;
|
||||
},
|
||||
normalizePointOptions(data = []) {
|
||||
return (data || []).map((item) => {
|
||||
if (typeof item === "string") {
|
||||
return {value: item, label: this.formatPointLabel({pointName: item}), pointId: "", pointName: item, dataKey: "", pointDesc: ""};
|
||||
}
|
||||
const pointId = item?.pointId || "";
|
||||
const pointName = item?.pointName || item?.value || "";
|
||||
const dataKey = item?.dataKey || "";
|
||||
const pointDesc = item?.pointDesc || "";
|
||||
const label = this.formatPointLabel({pointId, pointName, dataKey});
|
||||
return {
|
||||
value: pointId || pointName,
|
||||
label,
|
||||
pointId,
|
||||
pointName,
|
||||
dataKey,
|
||||
pointDesc,
|
||||
};
|
||||
}).filter((item) => item.value);
|
||||
},
|
||||
fuzzyFilterPointOptions(options = [], query = "") {
|
||||
const keyword = String(query || "").trim().toLowerCase();
|
||||
if (!keyword) return options;
|
||||
return (options || []).filter((item) => {
|
||||
const marker = `${item?.pointId || ""} ${item?.pointName || ""} ${item?.dataKey || ""} ${item?.pointDesc || ""}`.toLowerCase();
|
||||
return marker.includes(keyword);
|
||||
});
|
||||
},
|
||||
setPointOptions(group, data = []) {
|
||||
const normalized = this.normalizePointOptions(data);
|
||||
const selected = group.pointId
|
||||
? (group.pointOptions || []).find((item) => item?.value === group.pointId)
|
||||
|| {
|
||||
value: group.pointId,
|
||||
label: this.formatPointLabel({pointId: group.pointId}),
|
||||
pointId: group.pointId,
|
||||
pointName: group.selectedPointName || "",
|
||||
dataKey: "",
|
||||
pointDesc: "",
|
||||
}
|
||||
: null;
|
||||
const nextOptions = selected ? [...normalized, selected] : normalized;
|
||||
const seen = {};
|
||||
group.pointOptions = nextOptions.filter((item) => {
|
||||
if (!item?.value || seen[item.value]) return false;
|
||||
seen[item.value] = true;
|
||||
return true;
|
||||
});
|
||||
},
|
||||
applyPointOptionsToGroups(pointOptions = []) {
|
||||
(this.form.queryGroups || []).forEach((group) => {
|
||||
if (!group) return;
|
||||
const baseCacheKey = this.getPointCacheKey("");
|
||||
group.pointOptionsCache[baseCacheKey] = pointOptions;
|
||||
this.setPointOptions(group, pointOptions);
|
||||
});
|
||||
},
|
||||
loadSitePointOptions({force = false} = {}) {
|
||||
const siteCacheKey = this.getSitePointCacheKey();
|
||||
if (!siteCacheKey) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
if (!force && this.sitePointOptionsCache[siteCacheKey]) {
|
||||
const cached = this.sitePointOptionsCache[siteCacheKey];
|
||||
this.applyPointOptionsToGroups(cached);
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
const requestId = ++this.sitePointRequestId;
|
||||
(this.form.queryGroups || []).forEach((group) => {
|
||||
group.pointLoading = true;
|
||||
});
|
||||
return pointFuzzyQuery({
|
||||
siteIds: this.form.siteIds,
|
||||
pointName: "",
|
||||
})
|
||||
.then((response) => {
|
||||
if (requestId !== this.sitePointRequestId) return [];
|
||||
const data = this.normalizePointOptions(response?.data || []);
|
||||
this.sitePointOptionsCache[siteCacheKey] = data;
|
||||
this.applyPointOptionsToGroups(data);
|
||||
return data;
|
||||
})
|
||||
.finally(() => {
|
||||
if (requestId !== this.sitePointRequestId) return;
|
||||
(this.form.queryGroups || []).forEach((group) => {
|
||||
group.pointLoading = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
remotePointSearch(groupIndex, query) {
|
||||
const group = this.getQueryGroup(groupIndex);
|
||||
if (!group || !this.canSelectPoint(group)) return;
|
||||
const baseCacheKey = this.getPointCacheKey("");
|
||||
const baseOptions = group.pointOptionsCache?.[baseCacheKey] || group.pointOptions || [];
|
||||
const localFiltered = this.fuzzyFilterPointOptions(baseOptions, query);
|
||||
this.setPointOptions(group, localFiltered);
|
||||
},
|
||||
handlePointDropdownVisible(groupIndex, visible) {
|
||||
const group = this.getQueryGroup(groupIndex);
|
||||
if (visible && group && this.canSelectPoint(group)) {
|
||||
this.loadSitePointOptions();
|
||||
}
|
||||
},
|
||||
refreshPointOptions(groupIndex) {
|
||||
const group = this.getQueryGroup(groupIndex);
|
||||
if (!group || !this.canSelectPoint(group)) return;
|
||||
this.loadSitePointOptions({force: true});
|
||||
},
|
||||
clearPointSelection(groupIndex) {
|
||||
const group = this.getQueryGroup(groupIndex);
|
||||
if (!group) return;
|
||||
group.pointId = "";
|
||||
group.selectedPointName = "";
|
||||
},
|
||||
resolveSelectedPointName(group) {
|
||||
if (!group) return "";
|
||||
if (group.selectedPointName) return String(group.selectedPointName).trim();
|
||||
const selectedOption = (group.pointOptions || []).find((item) => item?.value === group.pointId);
|
||||
if (selectedOption?.pointName) {
|
||||
return String(selectedOption.pointName).trim();
|
||||
}
|
||||
return "";
|
||||
},
|
||||
handlePointChange(groupIndex, value) {
|
||||
const group = this.getQueryGroup(groupIndex);
|
||||
if (!group) return;
|
||||
if (!value) {
|
||||
group.selectedPointName = "";
|
||||
return;
|
||||
}
|
||||
const selectedOption = (group.pointOptions || []).find((item) => item?.value === value);
|
||||
group.selectedPointName = String(selectedOption?.pointName || "").trim();
|
||||
},
|
||||
syncQuerySiteIds(routeSiteId) {
|
||||
const siteId = routeSiteId || this.$route?.query?.siteId;
|
||||
const normalizedSiteId = siteId === undefined || siteId === null ? "" : String(siteId).trim();
|
||||
const prevSiteIds = (this.form.siteIds || []).join(",");
|
||||
this.form.siteIds = normalizedSiteId ? [normalizedSiteId] : [];
|
||||
const nextSiteIds = (this.form.siteIds || []).join(",");
|
||||
if (prevSiteIds !== nextSiteIds) {
|
||||
(this.form.queryGroups || []).forEach((group) => {
|
||||
group.pointOptions = [];
|
||||
group.pointOptionsCache = {};
|
||||
group.pointLoading = false;
|
||||
});
|
||||
}
|
||||
if (nextSiteIds) {
|
||||
this.loadSitePointOptions();
|
||||
}
|
||||
},
|
||||
getDate() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
const payload = this.buildQueryPayload();
|
||||
if (!payload) return;
|
||||
|
||||
this.requestPointData(payload).then((data) => {
|
||||
this.lastQueryResult = data || [];
|
||||
this.setOption(this.lastQueryResult);
|
||||
}).catch((error) => {
|
||||
if (error?.code === "ECONNABORTED") {
|
||||
this.$message.error("查询超时,请缩短时间范围后重试");
|
||||
return;
|
||||
}
|
||||
this.$message.error("查询失败,请稍后重试");
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return;
|
||||
}
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
this.$refs.dateTimeSelect.init();
|
||||
this.syncQuerySiteIds();
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.query-groups-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.query-group {
|
||||
flex: 0 0 calc(20% - 8px);
|
||||
max-width: calc(20% - 8px);
|
||||
}
|
||||
|
||||
.group-point-item {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.query-groups-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.query-groups-count {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.point-select-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.point-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.point-select-toolbar {
|
||||
margin-top: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.point-select-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
@media (max-width: 1600px) {
|
||||
.query-group {
|
||||
flex-basis: calc(33.3% - 8px);
|
||||
max-width: calc(33.3% - 8px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.query-group {
|
||||
flex-basis: calc(50% - 8px);
|
||||
max-width: calc(50% - 8px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.query-groups-row {
|
||||
gap: 0;
|
||||
}
|
||||
.query-group {
|
||||
flex-basis: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
183
src/views/ems/site/dataCorrection/AddChargeDataCorrection.vue
Normal file
@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
append-to-body
|
||||
width="760px"
|
||||
class="ems-dialog"
|
||||
:title="mode === 'add' ? '新增充放电收益修正' : '编辑充放电收益修正'"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="loading"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
size="medium"
|
||||
class="form-grid"
|
||||
>
|
||||
<el-form-item label="站点" prop="siteId">
|
||||
<el-input v-model="formData.siteId" placeholder="请先在顶部选择站点" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据日期" prop="dateTime">
|
||||
<el-date-picker
|
||||
v-model="formData.dateTime"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择数据日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="总充电量" prop="totalChargeData">
|
||||
<el-input-number v-model="formData.totalChargeData" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="总放电量" prop="totalDischargeData">
|
||||
<el-input-number v-model="formData.totalDischargeData" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="当日充电量" prop="chargeData">
|
||||
<el-input-number v-model="formData.chargeData" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="当日放电量" prop="dischargeData">
|
||||
<el-input-number v-model="formData.dischargeData" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="累计收益" prop="totalRevenue">
|
||||
<el-input-number v-model="formData.totalRevenue" :controls="false" :min="-999999999" :max="999999999" :step="0.0001" :precision="4" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="当日收益" prop="dayRevenue">
|
||||
<el-input-number v-model="formData.dayRevenue" :controls="false" :min="-999999999" :max="999999999" :step="0.0001" :precision="4" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remark" class="full-row">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" maxlength="300" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="closeDialog">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="saveDialog">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
addDailyChargeData,
|
||||
getDailyChargeDataDetail,
|
||||
updateDailyChargeData,
|
||||
} from '@/api/ems/site'
|
||||
|
||||
const buildEmptyForm = () => ({
|
||||
id: '',
|
||||
siteId: '',
|
||||
dateTime: '',
|
||||
totalChargeData: null,
|
||||
totalDischargeData: null,
|
||||
chargeData: null,
|
||||
dischargeData: null,
|
||||
totalRevenue: null,
|
||||
dayRevenue: null,
|
||||
remark: '',
|
||||
})
|
||||
|
||||
export default {
|
||||
name: 'AddChargeDataCorrection',
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
mode: 'add',
|
||||
loading: false,
|
||||
saving: false,
|
||||
formData: buildEmptyForm(),
|
||||
rules: {
|
||||
siteId: [{ required: true, message: '请先在顶部选择站点', trigger: 'blur' }],
|
||||
dateTime: [{ required: true, message: '请选择数据日期', trigger: 'change' }],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRouteSiteId() {
|
||||
const siteId = this.$route?.query?.siteId
|
||||
return siteId === undefined || siteId === null ? '' : String(siteId).trim()
|
||||
},
|
||||
showDialog(id, siteId = '') {
|
||||
this.dialogVisible = true
|
||||
if (id) {
|
||||
this.mode = 'edit'
|
||||
this.formData.id = id
|
||||
this.fetchDetail(id)
|
||||
} else {
|
||||
this.mode = 'add'
|
||||
this.formData = buildEmptyForm()
|
||||
this.formData.siteId = siteId || this.getRouteSiteId()
|
||||
}
|
||||
},
|
||||
fetchDetail(id) {
|
||||
this.loading = true
|
||||
getDailyChargeDataDetail(id)
|
||||
.then((res) => {
|
||||
this.formData = Object.assign(buildEmptyForm(), res?.data || {})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
saveDialog() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.saving = true
|
||||
const request = this.mode === 'add' ? addDailyChargeData : updateDailyChargeData
|
||||
const payload = {
|
||||
id: this.formData.id,
|
||||
siteId: this.formData.siteId,
|
||||
dateTime: this.formData.dateTime,
|
||||
totalChargeData: this.formData.totalChargeData,
|
||||
totalDischargeData: this.formData.totalDischargeData,
|
||||
chargeData: this.formData.chargeData,
|
||||
dischargeData: this.formData.dischargeData,
|
||||
totalRevenue: this.formData.totalRevenue,
|
||||
dayRevenue: this.formData.dayRevenue,
|
||||
remark: this.formData.remark,
|
||||
}
|
||||
request(payload)
|
||||
.then((res) => {
|
||||
if (res?.code === 200) {
|
||||
this.$emit('update')
|
||||
this.closeDialog()
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
closeDialog() {
|
||||
this.dialogVisible = false
|
||||
this.formData = buildEmptyForm()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.resetFields()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 16px;
|
||||
|
||||
.full-row {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
218
src/views/ems/site/dataCorrection/AddDataCorrection.vue
Normal file
@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
append-to-body
|
||||
width="760px"
|
||||
class="ems-dialog"
|
||||
:title="mode === 'add' ? '新增数据修正' : '编辑数据修正'"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="loading"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
size="medium"
|
||||
class="form-grid"
|
||||
>
|
||||
<el-form-item label="站点" prop="siteId">
|
||||
<el-input v-model="formData.siteId" placeholder="请先在顶部选择站点" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据日期" prop="dataDate">
|
||||
<el-date-picker
|
||||
v-model="formData.dataDate"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择数据日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据小时" prop="dataHour">
|
||||
<el-input-number
|
||||
v-model="formData.dataHour"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:max="23"
|
||||
:step="1"
|
||||
:precision="0"
|
||||
placeholder="0-23"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="尖充电差值" prop="peakChargeDiff">
|
||||
<el-input-number v-model="formData.peakChargeDiff" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="尖放电差值" prop="peakDischargeDiff">
|
||||
<el-input-number v-model="formData.peakDischargeDiff" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="峰充电差值" prop="highChargeDiff">
|
||||
<el-input-number v-model="formData.highChargeDiff" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="峰放电差值" prop="highDischargeDiff">
|
||||
<el-input-number v-model="formData.highDischargeDiff" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="平充电差值" prop="flatChargeDiff">
|
||||
<el-input-number v-model="formData.flatChargeDiff" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="平放电差值" prop="flatDischargeDiff">
|
||||
<el-input-number v-model="formData.flatDischargeDiff" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="谷充电差值" prop="valleyChargeDiff">
|
||||
<el-input-number v-model="formData.valleyChargeDiff" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="谷放电差值" prop="valleyDischargeDiff">
|
||||
<el-input-number v-model="formData.valleyDischargeDiff" :controls="false" :min="-999999999" :max="999999999" :step="0.001" :precision="3" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="计算时间" prop="calcTime" class="full-row">
|
||||
<el-date-picker
|
||||
v-model="formData.calcTime"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择计算时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remark" class="full-row">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" maxlength="300" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="closeDialog">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="saveDialog">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
addDailyEnergyData,
|
||||
getDailyEnergyDataDetail,
|
||||
updateDailyEnergyData,
|
||||
} from '@/api/ems/site'
|
||||
|
||||
const buildEmptyForm = () => ({
|
||||
id: '',
|
||||
siteId: '',
|
||||
dataDate: '',
|
||||
dataHour: null,
|
||||
peakChargeDiff: null,
|
||||
peakDischargeDiff: null,
|
||||
highChargeDiff: null,
|
||||
highDischargeDiff: null,
|
||||
flatChargeDiff: null,
|
||||
flatDischargeDiff: null,
|
||||
valleyChargeDiff: null,
|
||||
valleyDischargeDiff: null,
|
||||
calcTime: '',
|
||||
remark: '',
|
||||
})
|
||||
|
||||
export default {
|
||||
name: 'AddDataCorrection',
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
mode: 'add',
|
||||
loading: false,
|
||||
saving: false,
|
||||
formData: buildEmptyForm(),
|
||||
rules: {
|
||||
siteId: [{ required: true, message: '请先在顶部选择站点', trigger: 'blur' }],
|
||||
dataDate: [{ required: true, message: '请选择数据日期', trigger: 'change' }],
|
||||
dataHour: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
if (Number.isInteger(value) && value >= 0 && value <= 23) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
callback(new Error('数据小时需为 0-23 的整数'))
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRouteSiteId() {
|
||||
const siteId = this.$route?.query?.siteId
|
||||
return siteId === undefined || siteId === null ? '' : String(siteId).trim()
|
||||
},
|
||||
showDialog(id, siteId = '') {
|
||||
this.dialogVisible = true
|
||||
if (id) {
|
||||
this.mode = 'edit'
|
||||
this.formData.id = id
|
||||
this.fetchDetail(id)
|
||||
} else {
|
||||
this.mode = 'add'
|
||||
this.formData = buildEmptyForm()
|
||||
this.formData.siteId = siteId || this.getRouteSiteId()
|
||||
}
|
||||
},
|
||||
fetchDetail(id) {
|
||||
this.loading = true
|
||||
getDailyEnergyDataDetail(id)
|
||||
.then((res) => {
|
||||
this.formData = Object.assign(buildEmptyForm(), res?.data || {})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
saveDialog() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.saving = true
|
||||
const request = this.mode === 'add' ? addDailyEnergyData : updateDailyEnergyData
|
||||
request(this.formData)
|
||||
.then((res) => {
|
||||
if (res?.code === 200) {
|
||||
this.$emit('update')
|
||||
this.closeDialog()
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
closeDialog() {
|
||||
this.dialogVisible = false
|
||||
this.formData = buildEmptyForm()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.resetFields()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 16px;
|
||||
|
||||
.full-row {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
187
src/views/ems/site/dataCorrection/DailyChargeDataTab.vue
Normal file
@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<div style="background-color: #ffffff" v-loading="loading">
|
||||
<el-form :inline="true" class="select-container" @submit.native.prevent>
|
||||
<el-form-item label="数据日期">
|
||||
<el-date-picker
|
||||
v-model="form.dateTime"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
clearable
|
||||
placeholder="请选择日期"
|
||||
style="width: 180px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button native-type="button" type="primary" @click="onSearch">搜索</el-button>
|
||||
<el-button native-type="button" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-button type="primary" @click="openDialog('')">新增</el-button>
|
||||
|
||||
<el-table
|
||||
:data="tableData"
|
||||
class="common-table"
|
||||
max-height="620px"
|
||||
stripe
|
||||
style="width: 100%; margin-top: 20px"
|
||||
>
|
||||
<el-table-column label="站点" prop="siteId" min-width="120" />
|
||||
<el-table-column label="数据日期" prop="dateTime" width="120" />
|
||||
<el-table-column label="总充电量" prop="totalChargeData" min-width="110" />
|
||||
<el-table-column label="总放电量" prop="totalDischargeData" min-width="110" />
|
||||
<el-table-column label="当日充电量" prop="chargeData" min-width="110" />
|
||||
<el-table-column label="当日放电量" prop="dischargeData" min-width="110" />
|
||||
<el-table-column label="累计收益" prop="totalRevenue" min-width="110" />
|
||||
<el-table-column label="当日收益" prop="dayRevenue" min-width="110" />
|
||||
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column fixed="right" label="操作" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="warning" @click="openDialog(scope.row.id)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</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"
|
||||
/>
|
||||
|
||||
<add-charge-data-correction ref="addChargeDataCorrection" @update="getData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
deleteDailyChargeData,
|
||||
getDailyChargeDataList,
|
||||
} from '@/api/ems/site'
|
||||
import AddChargeDataCorrection from './AddChargeDataCorrection.vue'
|
||||
|
||||
export default {
|
||||
name: 'DailyChargeDataTab',
|
||||
components: { AddChargeDataCorrection },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
siteId: '',
|
||||
dateTime: '',
|
||||
},
|
||||
tableData: [],
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
totalSize: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.query.siteId'(newSiteId) {
|
||||
const normalizedSiteId = this.normalizeSiteId(newSiteId)
|
||||
if (normalizedSiteId === this.form.siteId) {
|
||||
return
|
||||
}
|
||||
this.form.siteId = normalizedSiteId
|
||||
this.onSearch()
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.form.siteId = this.normalizeSiteId(this.$route.query.siteId)
|
||||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
normalizeSiteId(siteId) {
|
||||
return siteId === undefined || siteId === null ? '' : String(siteId).trim()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val
|
||||
this.pageNum = 1
|
||||
this.getData()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val
|
||||
this.getData()
|
||||
},
|
||||
onSearch() {
|
||||
this.pageNum = 1
|
||||
this.getData()
|
||||
},
|
||||
onReset() {
|
||||
this.form = {
|
||||
siteId: this.form.siteId,
|
||||
dateTime: '',
|
||||
}
|
||||
this.pageNum = 1
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
if (!this.form.siteId) {
|
||||
this.tableData = []
|
||||
this.totalSize = 0
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
const params = {
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize,
|
||||
siteId: this.form.siteId,
|
||||
}
|
||||
if (this.form.dateTime) {
|
||||
params.dateTime = this.form.dateTime
|
||||
}
|
||||
getDailyChargeDataList(params)
|
||||
.then((res) => {
|
||||
this.tableData = res?.rows || []
|
||||
this.totalSize = res?.total || 0
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
openDialog(id) {
|
||||
this.$refs.addChargeDataCorrection.showDialog(id, this.form.siteId)
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm('确认要删除该条数据吗?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
showClose: false,
|
||||
closeOnClickModal: false,
|
||||
type: 'warning',
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
instance.confirmButtonLoading = true
|
||||
deleteDailyChargeData(row.id)
|
||||
.then((res) => {
|
||||
if (res?.code === 200) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
instance.confirmButtonLoading = false
|
||||
})
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success('删除成功!')
|
||||
this.getData()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
191
src/views/ems/site/dataCorrection/DailyEnergyDataTab.vue
Normal file
@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div style="background-color: #ffffff" v-loading="loading">
|
||||
<el-form :inline="true" class="select-container" @submit.native.prevent>
|
||||
<el-form-item label="数据日期">
|
||||
<el-date-picker
|
||||
v-model="form.dataDate"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
clearable
|
||||
placeholder="请选择日期"
|
||||
style="width: 180px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button native-type="button" type="primary" @click="onSearch">搜索</el-button>
|
||||
<el-button native-type="button" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-button type="primary" @click="openDialog('')">新增</el-button>
|
||||
|
||||
<el-table
|
||||
:data="tableData"
|
||||
class="common-table"
|
||||
max-height="620px"
|
||||
stripe
|
||||
style="width: 100%; margin-top: 20px"
|
||||
>
|
||||
<el-table-column label="站点" prop="siteId" width="130" />
|
||||
<el-table-column label="数据日期" prop="dataDate" width="120" />
|
||||
<el-table-column label="data_hour" prop="dataHour" width="100" />
|
||||
<el-table-column label="尖充" prop="peakChargeDiff" min-width="90" />
|
||||
<el-table-column label="尖放" prop="peakDischargeDiff" min-width="90" />
|
||||
<el-table-column label="峰充" prop="highChargeDiff" min-width="90" />
|
||||
<el-table-column label="峰放" prop="highDischargeDiff" min-width="90" />
|
||||
<el-table-column label="平充" prop="flatChargeDiff" min-width="90" />
|
||||
<el-table-column label="平放" prop="flatDischargeDiff" min-width="90" />
|
||||
<el-table-column label="谷充" prop="valleyChargeDiff" min-width="90" />
|
||||
<el-table-column label="谷放" prop="valleyDischargeDiff" min-width="90" />
|
||||
<el-table-column label="计算时间" prop="calcTime" min-width="170" />
|
||||
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column fixed="right" label="操作" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="warning" @click="openDialog(scope.row.id)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</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"
|
||||
/>
|
||||
|
||||
<add-data-correction ref="addDataCorrection" @update="getData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
deleteDailyEnergyData,
|
||||
getDailyEnergyDataList,
|
||||
} from '@/api/ems/site'
|
||||
import AddDataCorrection from './AddDataCorrection.vue'
|
||||
|
||||
export default {
|
||||
name: 'DailyEnergyDataTab',
|
||||
components: { AddDataCorrection },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
siteId: '',
|
||||
dataDate: '',
|
||||
},
|
||||
tableData: [],
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
totalSize: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.query.siteId'(newSiteId) {
|
||||
const normalizedSiteId = this.normalizeSiteId(newSiteId)
|
||||
if (normalizedSiteId === this.form.siteId) {
|
||||
return
|
||||
}
|
||||
this.form.siteId = normalizedSiteId
|
||||
this.onSearch()
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.form.siteId = this.normalizeSiteId(this.$route.query.siteId)
|
||||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
normalizeSiteId(siteId) {
|
||||
return siteId === undefined || siteId === null ? '' : String(siteId).trim()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val
|
||||
this.pageNum = 1
|
||||
this.getData()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val
|
||||
this.getData()
|
||||
},
|
||||
onSearch() {
|
||||
this.pageNum = 1
|
||||
this.getData()
|
||||
},
|
||||
onReset() {
|
||||
this.form = {
|
||||
siteId: this.form.siteId,
|
||||
dataDate: '',
|
||||
}
|
||||
this.pageNum = 1
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
if (!this.form.siteId) {
|
||||
this.tableData = []
|
||||
this.totalSize = 0
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
const params = {
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize,
|
||||
siteId: this.form.siteId,
|
||||
}
|
||||
if (this.form.dataDate) {
|
||||
params.dataDate = this.form.dataDate
|
||||
}
|
||||
getDailyEnergyDataList(params)
|
||||
.then((res) => {
|
||||
this.tableData = res?.rows || []
|
||||
this.totalSize = res?.total || 0
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
openDialog(id) {
|
||||
this.$refs.addDataCorrection.showDialog(id, this.form.siteId)
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm('确认要删除该条数据吗?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
showClose: false,
|
||||
closeOnClickModal: false,
|
||||
type: 'warning',
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
instance.confirmButtonLoading = true
|
||||
deleteDailyEnergyData(row.id)
|
||||
.then((res) => {
|
||||
if (res?.code === 200) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
instance.confirmButtonLoading = false
|
||||
})
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success('删除成功!')
|
||||
this.getData()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
30
src/views/ems/site/dataCorrection/index.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container" style="background-color: #ffffff">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="充放电量修正" name="energy" lazy>
|
||||
<daily-energy-data-tab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="充放电收益修正" name="charge" lazy>
|
||||
<daily-charge-data-tab />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DailyChargeDataTab from './DailyChargeDataTab.vue'
|
||||
import DailyEnergyDataTab from './DailyEnergyDataTab.vue'
|
||||
|
||||
export default {
|
||||
name: 'DataCorrection',
|
||||
components: { DailyEnergyDataTab, DailyChargeDataTab },
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'energy',
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
140
src/views/ems/site/mqtt/AddMqtt.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<el-dialog :visible.sync="dialogTableVisible" :close-on-press-escape="false" :close-on-click-modal="false" :show-close="false" destroy-on-close lock-scroll append-to-body width="400px" class="ems-dialog" :title="mode === 'add'?'新增配置':`编辑配置` " >
|
||||
<el-form v-loading="loading>0" ref="addTempForm" :model="formData" :rules="rules" size="medium" label-width="140px">
|
||||
<el-form-item label="站点" prop="siteId">
|
||||
<el-input v-model="formData.siteId" placeholder="请先在顶部选择站点" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="消息等级" prop="qos">
|
||||
<el-select v-model="formData.qos" placeholder="请选择消息等级">
|
||||
<el-option :value="1">1</el-option>
|
||||
<el-option :value="2">2</el-option>
|
||||
<el-option :value="3">3</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="订阅topic" prop="mqttTopic">
|
||||
<el-input v-model="formData.mqttTopic" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="topic描述" prop="topicName">
|
||||
<el-input v-model="formData.topicName" type="textarea" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="closeDialog">取消</el-button>
|
||||
<el-button type="primary" @click="saveDialog">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import {editMqtt,addMqtt,getMqttDetail} from "@/api/ems/site";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading:0,
|
||||
dialogTableVisible:false,
|
||||
mode:'',
|
||||
formData: {
|
||||
id:'',//设备唯一标识
|
||||
siteId:'',
|
||||
qos:'',
|
||||
topicName:'',
|
||||
mqttTopic:''
|
||||
},
|
||||
rules: {
|
||||
siteId:[
|
||||
{ required: true, message: '请先在顶部选择站点', trigger: 'blur'},
|
||||
],
|
||||
qos:[
|
||||
{ required: true, message: '请选择消息等级', trigger: 'blur'},
|
||||
],
|
||||
mqttTopic:[
|
||||
{ required: true, message: '请输入订阅topic', trigger: 'blur'},
|
||||
],
|
||||
topicName:[
|
||||
{ required: true, message: '请输入topic描述', trigger: 'blur'},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRouteSiteId() {
|
||||
const siteId = this.$route?.query?.siteId
|
||||
return siteId === undefined || siteId === null ? '' : String(siteId).trim()
|
||||
},
|
||||
showDialog(id, siteId = ''){
|
||||
this.dialogTableVisible = true
|
||||
if(id){
|
||||
this.mode = 'edit'
|
||||
this.formData.id = id
|
||||
this.getDetail(id)
|
||||
}else{
|
||||
this.mode = 'add'
|
||||
this.formData.siteId = siteId || this.getRouteSiteId()
|
||||
}
|
||||
},
|
||||
getDetail(id){
|
||||
getMqttDetail(id).then(response => {
|
||||
const {topicName='',mqttTopic='',qos='',siteId=''} = JSON.parse(JSON.stringify(response?.data || {}));
|
||||
this.formData.mqttTopic=mqttTopic;
|
||||
this.formData.topicName=topicName;
|
||||
this.formData.qos=qos;
|
||||
this.formData.siteId=siteId;
|
||||
})
|
||||
},
|
||||
saveDialog() {
|
||||
this.$refs.addTempForm.validate(valid => {
|
||||
if (!valid) return
|
||||
this.loading+=1
|
||||
const {
|
||||
id='',
|
||||
siteId='',
|
||||
qos='',
|
||||
mqttTopic='',//站点ID
|
||||
topicName='',//设备id
|
||||
}= this.formData;
|
||||
if(this.mode === 'add'){
|
||||
addMqtt( {mqttTopic,topicName,siteId,qos}).then(response => {
|
||||
if(response.code === 200){
|
||||
//新增成功
|
||||
// 关闭弹窗 更新表格
|
||||
this.$emit('update')
|
||||
this.closeDialog()
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading-=1
|
||||
})
|
||||
}else{
|
||||
editMqtt({mqttTopic,topicName,id,siteId,qos}).then(response => {
|
||||
if(response.code === 200){
|
||||
//新增成功
|
||||
// 关闭弹窗 更新表格
|
||||
this.$emit('update')
|
||||
this.closeDialog()
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading-=1
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
closeDialog(){
|
||||
this.$emit('clear')
|
||||
// 清空所有数据
|
||||
this.formData= {
|
||||
id:'',//设备唯一标识
|
||||
siteId:'',
|
||||
qos:'',
|
||||
mqttTopic:'',
|
||||
topicName:''
|
||||
}
|
||||
this.$refs.addTempForm.resetFields()
|
||||
this.dialogTableVisible=false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
||||
202
src/views/ems/site/mqtt/index.vue
Normal file
@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading">
|
||||
<el-form :inline="true" class="select-container">
|
||||
<el-form-item label="订阅topic">
|
||||
<el-input
|
||||
v-model="form.mqttTopic"
|
||||
clearable
|
||||
placeholder="请输入订阅topic"
|
||||
style="width: 150px"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="topic描述">
|
||||
<el-input
|
||||
v-model="form.topicName"
|
||||
clearable
|
||||
placeholder="请输入topic描述"
|
||||
style="width: 150px"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button native-type="button" type="primary" @click="onSearch">搜索</el-button>
|
||||
<el-button native-type="button" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" @click="addPowerConfig('')">新增</el-button>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
class="common-table"
|
||||
max-height="600px"
|
||||
stripe
|
||||
style="width: 100%;margin-top: 25px">
|
||||
<el-table-column
|
||||
label="站点"
|
||||
prop="siteId">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="消息等级"
|
||||
prop="qos">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="订阅topic"
|
||||
prop="mqttTopic">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="topic描述"
|
||||
prop="topicName">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
style="margin-top:10px;"
|
||||
type="warning"
|
||||
@click="addPowerConfig(scope.row.id)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
style="margin-top:10px;"
|
||||
type="danger"
|
||||
@click="deleteMqtt(scope.row)">
|
||||
删除
|
||||
</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>
|
||||
<add-mqtt ref="addMqtt" @update="getData"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {deleteMqtt,getMqttList} from '@/api/ems/site'
|
||||
import AddMqtt from './AddMqtt.vue'
|
||||
export default {
|
||||
name: "Mqtt",
|
||||
components: {AddMqtt},
|
||||
computed: { },
|
||||
watch: {
|
||||
'$route.query.siteId'(newSiteId) {
|
||||
const normalizedSiteId = this.hasValidSiteId(newSiteId) ? String(newSiteId).trim() : ''
|
||||
if (normalizedSiteId === this.form.siteId) {
|
||||
return
|
||||
}
|
||||
this.form.siteId = normalizedSiteId
|
||||
this.onSearch()
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form:{
|
||||
siteId:"",
|
||||
topicName:'',
|
||||
mqttTopic:''
|
||||
},
|
||||
loading:false,
|
||||
tableData:[],
|
||||
pageSize:10,//分页栏当前每个数据总数
|
||||
pageNum:1,//分页栏当前页数
|
||||
totalSize:0,//table表格数据总数
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
hasValidSiteId(siteId) {
|
||||
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
|
||||
},
|
||||
// 分页
|
||||
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.form={
|
||||
siteId:this.form.siteId,
|
||||
topicName:'',
|
||||
mqttTopic:''
|
||||
}
|
||||
this.pageNum =1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
getData(){
|
||||
if (!this.form.siteId) {
|
||||
this.tableData = []
|
||||
this.totalSize = 0
|
||||
return
|
||||
}
|
||||
this.loading=true;
|
||||
const {mqttTopic,topicName,siteId} = this.form;
|
||||
const {pageNum,pageSize} = this;
|
||||
getMqttList({pageNum,pageSize,mqttTopic,topicName,siteId}).then(response => {
|
||||
this.tableData=response?.rows || [];
|
||||
this.totalSize = response?.total || 0
|
||||
}).finally(() => {this.loading=false})
|
||||
},
|
||||
addPowerConfig(id=''){
|
||||
this.$refs.addMqtt.showDialog(id, this.form.siteId);
|
||||
},
|
||||
deleteMqtt(row){
|
||||
this.$confirm(`确认要删除该配置吗?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
showClose:false,
|
||||
closeOnClickModal:false,
|
||||
type: 'warning',
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
instance.confirmButtonLoading = true;
|
||||
deleteMqtt(row.id).then(response => {
|
||||
response.code === 200 && done();
|
||||
}).finally(() => {
|
||||
instance.confirmButtonLoading = false;
|
||||
})
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
//只有在废弃成功的情况下会走到这里
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
});
|
||||
this.getData()
|
||||
//调用接口 更新表格数据
|
||||
}).catch(() => {
|
||||
//取消关机
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.form.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
|
||||
this.getData()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
1800
src/views/ems/site/pointConfig/index.vue
Normal file
296
src/views/ems/site/powerTariff/AddPowerTariff.vue
Normal file
@ -0,0 +1,296 @@
|
||||
//选择年月 配置尖峰平谷对应的电价 配置24小时选择对应的尖峰平谷
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-loading="loading" width="780px" :visible.sync="dialogTableVisible" class="ems-dialog" title="电价配置" :close-on-click-modal="false" :show-close="false">
|
||||
<div class="items-container">
|
||||
<div class="item-title">站点:</div>
|
||||
<div class="item-content">
|
||||
<el-input v-model="siteId" placeholder="请先在顶部选择站点" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="items-container">
|
||||
<div class="item-title">时间:</div>
|
||||
<div class="item-content">
|
||||
<el-date-picker
|
||||
v-model="powerDate"
|
||||
format="yyyy-MM"
|
||||
value-format="yyyy-MM"
|
||||
type="month"
|
||||
placeholder="请选择月份"
|
||||
:disabled="mode === 'edit'"
|
||||
>
|
||||
</el-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex">
|
||||
<div class="items-container price-types" v-for="item in priceTypeOptions" :key="item.id">
|
||||
<div class="item-title">{{item.name}}:</div>
|
||||
<div class="item-content">
|
||||
<el-input
|
||||
placeholder="请输入价格"
|
||||
v-model="item.price">
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="items-container">
|
||||
<div class="item-title">
|
||||
<el-button
|
||||
@click.native.prevent="addRow"
|
||||
block
|
||||
type="primary"
|
||||
size="mini">
|
||||
新增时间段配置
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<div class="time-lists-container">
|
||||
<div class="time-lists time-lists-title">
|
||||
<div>开始时间</div>
|
||||
<div>结束时间(不包括)</div>
|
||||
<div>电价</div>
|
||||
<div>操作</div>
|
||||
</div>
|
||||
<div class="time-lists" v-for="(item,index) in hoursOptions" :key="'hoursOptions'+index">
|
||||
<div>
|
||||
<el-time-select
|
||||
placeholder="开始时间"
|
||||
v-model="item.startTime"
|
||||
:picker-options="{
|
||||
start: '00:00',
|
||||
step: '01:00',
|
||||
end: '23:00',
|
||||
}">
|
||||
</el-time-select>
|
||||
</div>
|
||||
<div>
|
||||
<el-time-select
|
||||
placeholder="结束时间"
|
||||
v-model="item.endTime"
|
||||
:picker-options="{
|
||||
start: '00:00',
|
||||
step: '01:00',
|
||||
end: '24:00',
|
||||
minTime: item.startTime
|
||||
}">
|
||||
</el-time-select>
|
||||
</div>
|
||||
<div>
|
||||
<el-select v-model="item.costType" placeholder="请选择">
|
||||
<el-option v-for="(value,key) in priceTypeOptions" :key="key+'priceTypeOptions'" :label="value.name" :value="value.id"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
@click.native.prevent="deleteRow(index)"
|
||||
type="warning"
|
||||
size="mini">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<el-button @click="closeDialog">取消</el-button>
|
||||
<el-button type="primary" @click="saveDialog">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {addPriceConfig,editPriceConfig,detailPriceConfig} from '@/api/ems/powerTariff'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
mode:'',
|
||||
id:'',
|
||||
siteId:'',
|
||||
powerDate:'',//时间
|
||||
//尖-peak,峰-high,平-flat,谷=valley
|
||||
priceTypeOptions:[{
|
||||
id:'peak',
|
||||
name:'尖',
|
||||
price:''
|
||||
},{
|
||||
id:'high',
|
||||
name:'峰',
|
||||
price:''
|
||||
},{
|
||||
id:'flat',
|
||||
name:'平',
|
||||
price:''
|
||||
},{
|
||||
id:'valley',
|
||||
name:'谷',
|
||||
price:''
|
||||
}],
|
||||
hoursOptions:[],
|
||||
loading:false,
|
||||
dialogTableVisible:false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRouteSiteId() {
|
||||
const siteId = this.$route?.query?.siteId
|
||||
return siteId === undefined || siteId === null ? '' : String(siteId).trim()
|
||||
},
|
||||
addRow(){
|
||||
this.hoursOptions.push({
|
||||
startTime:'',
|
||||
endTime:'',
|
||||
costType:''
|
||||
})
|
||||
},
|
||||
deleteRow(index){
|
||||
this.hoursOptions.splice(index,1)
|
||||
},
|
||||
showDialog(id, siteId = ''){
|
||||
this.id = id
|
||||
if(id) {
|
||||
this.mode='edit'
|
||||
//获取详情 初始化hoursOptions
|
||||
this.loading = true
|
||||
detailPriceConfig(id).then(response => {
|
||||
const data = response?.data || {}
|
||||
this.siteId = data?.siteId || ''
|
||||
this.hoursOptions = data?.range || []
|
||||
this.powerDate=data?.year && data?.month ? data.year +'-'+data.month : ''
|
||||
this.priceTypeOptions.forEach(item=>{
|
||||
item.price = data[item.id]
|
||||
})
|
||||
}).finally(()=>this.loading = false)
|
||||
}else {
|
||||
this.mode='add'
|
||||
this.siteId = siteId || this.getRouteSiteId()
|
||||
}
|
||||
this.dialogTableVisible=true
|
||||
},
|
||||
saveDialog() {
|
||||
if(this.siteId === '') return this.$message.error('请先在顶部选择站点')
|
||||
if(this.powerDate === '') return this.$message.error('请选择时间')
|
||||
let priceArr=[]
|
||||
this.priceTypeOptions.forEach(item=>{
|
||||
!['0',0].includes(item.price) && !item.price && priceArr.push(item.name)
|
||||
})
|
||||
if(priceArr.length>0) return this.$message.error(`请配置${priceArr.join(',')}的电价`)
|
||||
|
||||
if(this.hoursOptions.length<=0) return this.$message.error(`请配置24小时的电价`)
|
||||
let hours=false,hourDis=false
|
||||
this.hoursOptions.forEach(item=>{
|
||||
if(!item.startTime || !item.endTime || !item.costType ) hours=true
|
||||
if(parseInt(item.startTime)>=parseInt(item.endTime)) hourDis=true
|
||||
})
|
||||
if(hours) return this.$message.error(`时间段电价配置错误`)
|
||||
if(hourDis) return this.$message.error(`结束时间要大于开始时间`)
|
||||
let hoursMap={}
|
||||
this.hoursOptions.forEach(item=>{
|
||||
let s = parseInt(item.startTime),e=parseInt(item.endTime)
|
||||
for(s;s<e;s++){
|
||||
if(!hoursMap[s]) hoursMap[s]=1
|
||||
else hoursMap[s]+=1
|
||||
}
|
||||
})
|
||||
console.log('hoursMap======',hoursMap)
|
||||
if(Object.values(hoursMap).length<24 || Object.values(hoursMap).find(i=>i>1)) return this.$message.error(`请配置24小时的电价且时间不能重复`)
|
||||
const {powerDate,priceTypeOptions,hoursOptions,mode,id,siteId} =this
|
||||
this.loading = true
|
||||
let params={
|
||||
year:powerDate.split('-')[0],
|
||||
month:parseInt(powerDate.split('-')[1]),
|
||||
range:hoursOptions,
|
||||
siteId,
|
||||
}
|
||||
priceTypeOptions.forEach(item=>{params[item.id]=item.price})
|
||||
if(mode === 'edit') params.id = id
|
||||
console.log('参数=======',params)
|
||||
//调接口传数据 区分新增还是修改 成功之后关闭弹窗 更新表格
|
||||
if(mode === 'add'){
|
||||
addPriceConfig(params).then(response => {
|
||||
if(response.code === 200){
|
||||
this.$emit('update')
|
||||
this.closeDialog()
|
||||
}else{
|
||||
this.$message.error('新增失败')
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}else{
|
||||
editPriceConfig(params).then(response => {
|
||||
if(response.code === 200){
|
||||
this.$emit('update')
|
||||
this.closeDialog()
|
||||
}else{
|
||||
this.$message.error('修改失败')
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
closeDialog(){
|
||||
// 清空所有数据
|
||||
this.$emit('clear')
|
||||
this.mode=''
|
||||
this.id=''
|
||||
this.siteId=''
|
||||
this.powerDate=''
|
||||
this.hoursOptions=[]
|
||||
this.priceTypeOptions.forEach(item=>{
|
||||
item.price=''
|
||||
})
|
||||
this.dialogTableVisible=false
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.items-container{
|
||||
margin-bottom: 20px;
|
||||
.item-title{
|
||||
line-height: 16px;
|
||||
padding: 10px 0;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
.price-types{
|
||||
width: 150px;
|
||||
&:not(:last-child){
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
.time-lists-container{
|
||||
width: 100%;
|
||||
border:1px solid #eee;
|
||||
.time-lists{
|
||||
&:not(:last-child){
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
display: flex;
|
||||
&>div{
|
||||
width: 16%;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
padding: 10px 15px;
|
||||
&:not(:last-child){
|
||||
width: 28%;
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
.el-date-editor.el-input, .el-date-editor.el-input__inner {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.time-lists-title{
|
||||
color: #000;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
208
src/views/ems/site/powerTariff/index.vue
Normal file
@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading">
|
||||
<el-form :inline="true" class="select-container">
|
||||
<el-form-item label="年份选择">
|
||||
<el-date-picker
|
||||
v-model="defaultYear"
|
||||
type="year"
|
||||
:clearable="false"
|
||||
placeholder="请选择年份"
|
||||
align="center"
|
||||
format="yyyy年"
|
||||
value-format="yyyy"
|
||||
@change="changeDefaultYear"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="addPowerConfig('')">新增电价配置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="month-lists-container" v-infinite-scroll="getData" infinite-scroll-immediate="false">
|
||||
<el-empty v-show="tableData.length<=0" :image-size="200"></el-empty>
|
||||
<el-card
|
||||
shadow="always"
|
||||
class="common-card-container time-range-card"
|
||||
v-for="item in tableData"
|
||||
:key="item.id"
|
||||
>
|
||||
<div slot="header" class="time-range-header">
|
||||
<span class="card-title">{{item.siteId || ''}}-{{item.month}}月电价时段划分</span>
|
||||
<div>
|
||||
<el-button type="primary" size="mini" @click="addPowerConfig(item.id)">编辑</el-button>
|
||||
<el-button type="warning" size="mini" @click="deletePowerConfig(item)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-table-container">
|
||||
<div class="price-table">
|
||||
<div class="time-list">
|
||||
<div class="time"> </div>
|
||||
<div class="type">时段</div>
|
||||
<div class="price">电价(元/kWh)</div>
|
||||
</div>
|
||||
<div class="time-list" v-for="(rangeItem,rangeIndex) in item.range" :key="rangeIndex+'price'">
|
||||
<div class="time">{{`${rangeItem.startTime}-${rangeItem.endTime}`}}</div>
|
||||
<div class="type">{{priceTypeOptions[rangeItem.costType]}}</div>
|
||||
<div class="price">{{item[rangeItem.costType]}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<add-power-tariff ref="addPowerTariff" @update="getData(true)"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {energyPriceConfig,listPriceConfig} from '@/api/ems/powerTariff'
|
||||
import AddPowerTariff from './AddPowerTariff.vue'
|
||||
import DateTimeSelect from "@/views/ems/search/DateTimeSelect.vue";
|
||||
export default {
|
||||
name: "PowerTariff",
|
||||
components: {DateTimeSelect, AddPowerTariff},
|
||||
computed: { },
|
||||
watch: {
|
||||
'$route.query.siteId'(newSiteId) {
|
||||
const normalizedSiteId = this.hasValidSiteId(newSiteId) ? String(newSiteId).trim() : ''
|
||||
if (normalizedSiteId === this.siteId) {
|
||||
return
|
||||
}
|
||||
this.siteId = normalizedSiteId
|
||||
this.onSearch()
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading:false,
|
||||
pageNum:1,
|
||||
pageSize:40,
|
||||
siteId:'',
|
||||
tableData:[],
|
||||
tableTotal:0,
|
||||
defaultYear:'',
|
||||
pickerOptions:{
|
||||
disabledDate(time) {
|
||||
return time.getFullYear() >= new Date().getFullYear()+1;
|
||||
},
|
||||
},
|
||||
priceTypeOptions:{
|
||||
'peak':'尖',
|
||||
'high':'峰',
|
||||
'flat':'平',
|
||||
'valley':'谷'
|
||||
},
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
hasValidSiteId(siteId) {
|
||||
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
|
||||
},
|
||||
resetTableData(){
|
||||
this.tableData=[]
|
||||
this.tableTotal=0
|
||||
this.pageNum=1
|
||||
},
|
||||
// 搜索
|
||||
onSearch(){
|
||||
this.getData(true)
|
||||
},
|
||||
changeDefaultYear(){
|
||||
this.getData(true)
|
||||
},
|
||||
getData(reset=false){
|
||||
reset && this.resetTableData()
|
||||
if (!this.siteId) {
|
||||
this.tableData = []
|
||||
this.tableTotal = 0
|
||||
return
|
||||
}
|
||||
if(!reset && this.tableData.length>=this.tableTotal) return
|
||||
this.loading=true;
|
||||
const date = new Date(this.defaultYear).getFullYear()
|
||||
const startTime = date+'-01',endTime = date+'-12'
|
||||
listPriceConfig({startTime,endTime,pageNum:this.pageNum,pageSize:this.pageSize,siteId:this.siteId}).then(response => {
|
||||
const data = JSON.parse(JSON.stringify(response?.rows || []))
|
||||
data.length > 0 && (this.pageNum += 1)
|
||||
this.tableData.push(...data)
|
||||
this.tableTotal=response?.total || 0
|
||||
}).finally(() => {this.loading=false})
|
||||
},
|
||||
addPowerConfig(id=''){
|
||||
this.$refs.addPowerTariff.showDialog(id, this.siteId);
|
||||
},
|
||||
deletePowerConfig(row){
|
||||
this.$confirm(`确认要删除${row.month}月的电价配置吗?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
showClose:false,
|
||||
closeOnClickModal:false,
|
||||
type: 'warning',
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
instance.confirmButtonLoading = true;
|
||||
energyPriceConfig(row.id).then(response => {
|
||||
response.code === 200 && done();
|
||||
}).finally(() => {
|
||||
instance.confirmButtonLoading = false;
|
||||
})
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
//只有在废弃成功的情况下会走到这里
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
});
|
||||
this.getData(true)
|
||||
//调用接口 更新表格数据
|
||||
}).catch(() => {
|
||||
//取消关机
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.defaultYear = new Date()
|
||||
this.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
|
||||
this.getData(true)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.month-lists-container{
|
||||
max-height:100vh ;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.common-card-container{
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.price-table-container{
|
||||
overflow-x: auto;
|
||||
.price-table{
|
||||
border:1px solid #eee;
|
||||
width: fit-content;
|
||||
overflow-x: auto;
|
||||
display: flex;
|
||||
.time-list{
|
||||
&:not(:first-child){
|
||||
border-left:1px solid #eee;
|
||||
}
|
||||
text-align: center;
|
||||
width: 140px;
|
||||
&>div{
|
||||
height: 30px;
|
||||
font-size: 12px;
|
||||
line-height: 30px;
|
||||
color:#000;
|
||||
&.time,&.type{
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1090
src/views/ems/site/sbbh/AddPlan.vue
Normal file
300
src/views/ems/site/sbbh/index.vue
Normal file
@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<div class="protect-plan-page" v-loading="loading">
|
||||
<el-card class="query-card" shadow="never">
|
||||
<div class="query-head">
|
||||
<div class="query-title">设备保护方案</div>
|
||||
<el-button type="primary" @click="addPlan" native-type="button">新增方案</el-button>
|
||||
</div>
|
||||
<el-form :inline="true" class="query-form" @submit.native.prevent>
|
||||
<el-form-item label="故障名称">
|
||||
<el-input
|
||||
v-model="form.faultName"
|
||||
clearable
|
||||
placeholder="请输入故障名称"
|
||||
style="width: 220px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSearch" native-type="button">搜索</el-button>
|
||||
<el-button @click="onReset" native-type="button">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="table-card" shadow="never">
|
||||
<el-table class="common-table" :data="tableData" stripe max-height="620px">
|
||||
<el-table-column prop="faultName" label="设备保护名称" min-width="140" />
|
||||
<el-table-column prop="faultLevel" label="故障等级" width="100">
|
||||
<template slot-scope="scope">等级{{ scope.row.faultLevel }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isAlert" label="是否告警" width="100">
|
||||
<template slot-scope="scope">{{ scope.row.isAlert === 1 ? '是' : '否' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="处理方案描述" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="protectionSettings" label="故障/释放保护" min-width="360" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<div class="rich-lines" v-html="handleProtectionSettings(scope.row.protectionSettings)"></div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="faultDelaySeconds" label="前提延时(s)" width="110" />
|
||||
<el-table-column prop="protectionPlan" label="执行保护" min-width="260" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<div class="rich-lines" v-html="handleProtectionPlan(scope.row.protectionPlan)"></div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="releaseDelaySeconds" label="方案延时(s)" width="110" />
|
||||
<el-table-column fixed="right" label="操作" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button @click="editDevice(scope.row)" type="warning" size="mini">编辑</el-button>
|
||||
<el-button type="danger" @click="deleteDevice(scope.row)" size="mini">删除</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"
|
||||
class="pager"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<add-plan ref="addPlan" @update="getData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { protectPlanList, deleteProtectPlan } from "@/api/ems/site";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import AddPlan from "./AddPlan.vue";
|
||||
|
||||
export default {
|
||||
name: "SBBH",
|
||||
components: { AddPlan },
|
||||
mixins: [getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
faultName: "",
|
||||
},
|
||||
loading: false,
|
||||
tableData: [],
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
totalSize: 0,
|
||||
dialogTableVisible: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
},
|
||||
handleProtectionSettings(data) {
|
||||
if (!data) return;
|
||||
let parsed = null;
|
||||
try {
|
||||
parsed = JSON.parse(data);
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
const faultSettings = Array.isArray(parsed) ? parsed : parsed?.faultSettings || [];
|
||||
const releaseSettings = Array.isArray(parsed) ? parsed : parsed?.releaseSettings || [];
|
||||
|
||||
const buildLine = (item, index, total, key, value, relationKey) => {
|
||||
const {
|
||||
categoryName = "",
|
||||
deviceId = "",
|
||||
point = "",
|
||||
[key]: operator = "",
|
||||
[value]: val = "",
|
||||
relationNext = "",
|
||||
} = item;
|
||||
return `<div>${index + 1}、 <span>${categoryName ? categoryName + "-" : ""}${
|
||||
deviceId ? deviceId + "-" : ""
|
||||
}${point || ""}</span> <span>${relationKey}:${operator || ""}${val || ""}</span> ${
|
||||
total[index + 1] ? "<span>关系:" + (relationNext || "") + "</span>" : ""
|
||||
}</div>`;
|
||||
};
|
||||
|
||||
const faultStr = faultSettings.map((item, index) =>
|
||||
buildLine(item, index, faultSettings, "faultOperator", "faultValue", "故障")
|
||||
);
|
||||
const releaseStr = releaseSettings.map((item, index) =>
|
||||
buildLine(item, index, releaseSettings, "releaseOperator", "releaseValue", "释放")
|
||||
);
|
||||
const groups = [];
|
||||
if (faultStr.length) {
|
||||
groups.push(`<div><strong>故障保护</strong></div>${faultStr.join("")}`);
|
||||
}
|
||||
if (releaseStr.length) {
|
||||
groups.push(`<div><strong>释放保护</strong></div>${releaseStr.join("")}`);
|
||||
}
|
||||
return groups.join("");
|
||||
},
|
||||
handleProtectionPlan(data) {
|
||||
if (!data) return;
|
||||
let arr = [];
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
arr = Array.isArray(parsed) ? parsed : parsed ? [parsed] : [];
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
const actionLabelMap = {
|
||||
derate: "降功率",
|
||||
shutdown: "关机/停机/切断",
|
||||
forbid_charge: "禁止充电",
|
||||
allow_discharge: "允许放电",
|
||||
forbid_discharge: "禁止放电",
|
||||
allow_charge: "允许充电",
|
||||
forbid_charge_discharge: "禁止充放电",
|
||||
standby: "待机",
|
||||
};
|
||||
const str = arr.map((item, index) => {
|
||||
const action = item?.action || "";
|
||||
const point = item?.point || "";
|
||||
const pointName = item?.pointName || "";
|
||||
const actionName = item?.actionName || actionLabelMap[action] || pointName || point || "未配置";
|
||||
const value = item?.value;
|
||||
if ((action === "derate" || actionName.includes("降功率")) && value !== null && value !== undefined && value !== "") {
|
||||
return `<div>${index + 1}、 <span>动作:${actionName}</span> <span>比例:${value}%</span></div>`;
|
||||
}
|
||||
return `<div>${index + 1}、 <span>动作:${actionName}</span></div>`;
|
||||
});
|
||||
return str.join("");
|
||||
},
|
||||
addPlan() {
|
||||
if (!this.siteId) {
|
||||
this.$message.warning("请先在顶部选择站点");
|
||||
return;
|
||||
}
|
||||
this.$refs.addPlan.open("", this.siteId);
|
||||
},
|
||||
editDevice(row) {
|
||||
this.$refs.addPlan.open(row.id, this.siteId);
|
||||
},
|
||||
deleteDevice(row) {
|
||||
this.$confirm(`确认要设备保护${row.faultName}吗?`, {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
showClose: false,
|
||||
closeOnClickModal: false,
|
||||
type: "warning",
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === "confirm") {
|
||||
instance.confirmButtonLoading = true;
|
||||
deleteProtectPlan(row.id)
|
||||
.then((response) => {
|
||||
response.code === 200 && done();
|
||||
})
|
||||
.finally(() => {
|
||||
instance.confirmButtonLoading = false;
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: "删除成功!",
|
||||
});
|
||||
this.getData();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
this.$nextTick(() => {
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val;
|
||||
this.$nextTick(() => {
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
onSearch() {
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
},
|
||||
onReset() {
|
||||
this.form = {
|
||||
faultName: "",
|
||||
};
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
},
|
||||
getData() {
|
||||
if (!this.siteId) {
|
||||
this.tableData = [];
|
||||
this.totalSize = 0;
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
const { pageNum, pageSize } = this;
|
||||
const { faultName = "" } = this.form;
|
||||
protectPlanList({ siteId: this.siteId, faultName, pageNum, pageSize })
|
||||
.then((response) => {
|
||||
this.tableData = response?.rows || [];
|
||||
this.totalSize = response?.total || 0;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.protect-plan-page {
|
||||
padding: 16px;
|
||||
background: linear-gradient(180deg, #f5f8ff 0%, #f7f9fc 100%);
|
||||
|
||||
.query-card,
|
||||
.table-card {
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e7edf7;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.query-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.query-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1d2a3a;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.query-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: -18px;
|
||||
}
|
||||
|
||||
.rich-lines {
|
||||
color: #3e4b5a;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.pager {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||