Compare commits
1 Commits
waibao
...
screen-dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 84f454ea8a |
@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request'
|
||||
import request from '@/utils/request'
|
||||
|
||||
//鑾峰彇鍗曚釜绔欑偣鐨勪俊鎭?
|
||||
//获取单个站点的信息
|
||||
export function getDzjkHomeView(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/homeView?siteId=${siteId}`,
|
||||
@ -8,40 +8,7 @@ export function getDzjkHomeView(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇鍗曚釜绔欑偣鎬荤疮璁¤繍琛屾暟鎹紙鍩轰簬鏃ヨ〃锛?
|
||||
export function getDzjkHomeTotalView(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/homeTotalView?siteId=${siteId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 鍗曠珯鐩戞帶椤圭洰鐐逛綅閰嶇疆锛堜緵鍗曠珯鐩戞帶鍔熻兘鏌ヨ锛?
|
||||
export function getProjectPointMapping(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getProjectPointMapping?siteId=${siteId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 鍗曠珯鐩戞帶椤圭洰灞曠ず鏁版嵁锛堝瓧娈甸厤缃?+ 鏈€鏂板€硷級
|
||||
export function getProjectDisplayData(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getProjectDisplayData?siteId=${siteId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 鍗曠珯鐩戞帶椤圭洰灞曠ず鏁版嵁鍐欏叆锛堟壒閲忥級
|
||||
export function saveProjectDisplayData(data) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/saveProjectDisplayData`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
//绔欑偣棣栭〉 鍐叉斁鏇茬嚎
|
||||
//站点首页 冲放曲线
|
||||
export function getSevenChargeData({siteId, startDate, endDate}) {
|
||||
return request({
|
||||
url: `/ems/siteMap/getSevenChargeData?siteId=${siteId}&startDate=${startDate}&endDate=${endDate}`,
|
||||
@ -49,7 +16,7 @@ export function getSevenChargeData({siteId, startDate, endDate}) {
|
||||
})
|
||||
}
|
||||
|
||||
// 鑾峰彇绔欑偣鍖呭惈鐨勮澶囩绫?鐢ㄦ潵鍒ゆ柇鍗曠珯鐩戞帶璁惧鐩戞帶鐨勮彍鍗曟爮灞曠ず
|
||||
// 获取站点包含的设备种类 用来判断单站监控设备监控的菜单栏展示
|
||||
export function getSiteAllDeviceCategory(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/getSiteAllDeviceCategory?siteId=${siteId}`,
|
||||
@ -65,7 +32,7 @@ export function getEmsDataList(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇pcs銆佸疄鏃惰繍琛屽ご閮ㄧ殑璁惧淇℃伅
|
||||
//获取pcs、实时运行头部的设备信息
|
||||
export function getRunningHeadInfo(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/runningHeadInfo?siteId=${siteId}`,
|
||||
@ -73,7 +40,7 @@ export function getRunningHeadInfo(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇pcs鍒楄〃
|
||||
//获取pcs列表
|
||||
export function getPcsDetailInfo(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getPcsDetailInfo?siteId=${siteId}`,
|
||||
@ -81,7 +48,7 @@ export function getPcsDetailInfo(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇BMS鎬昏鏁版嵁
|
||||
//获取BMS总览数据
|
||||
export function getBMSOverView(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getBMSOverView?siteId=${siteId}`,
|
||||
@ -89,7 +56,7 @@ export function getBMSOverView(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇BMS鐢垫睜绨囨€昏鏁版嵁
|
||||
//获取BMS电池簇总览数据
|
||||
export function getBMSBatteryCluster(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getBMSBatteryCluster?siteId=${siteId}`,
|
||||
@ -97,7 +64,7 @@ export function getBMSBatteryCluster(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇鍗曚綋鐢垫睜 鐢垫睜鍫嗗垪琛ㄦ暟鎹?
|
||||
//获取单体电池 电池堆列表数据
|
||||
export function getStackNameList(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getStackNameList?siteId=${siteId}`,
|
||||
@ -105,7 +72,7 @@ export function getStackNameList(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇鍗曚綋鐢垫睜 鐢垫睜绨囧垪琛ㄦ暟鎹?
|
||||
//获取单体电池 电池簇列表数据
|
||||
export function getClusterNameList({stackDeviceId, siteId}) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getClusterNameList?stackDeviceId=${stackDeviceId}&siteId=${siteId}`,
|
||||
@ -113,7 +80,7 @@ export function getClusterNameList({stackDeviceId, siteId}) {
|
||||
})
|
||||
}
|
||||
|
||||
//鍗曚綋鐢垫睜琛ㄦ牸鏁版嵁
|
||||
//单体电池表格数据
|
||||
export function getClusterDataInfoList({siteId, stackDeviceId, clusterDeviceId, batteryId, pageSize, pageNum}) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getClusterDataInfoList?clusterDeviceId=${clusterDeviceId}&siteId=${siteId}&stackDeviceId=${stackDeviceId}&batteryId=${batteryId}&pageSize=${pageSize}&pageNum=${pageNum}`,
|
||||
@ -121,7 +88,7 @@ export function getClusterDataInfoList({siteId, stackDeviceId, clusterDeviceId,
|
||||
})
|
||||
}
|
||||
|
||||
// 鍗曚綋鐢垫睜鍥捐〃
|
||||
// 单体电池图表
|
||||
//http://localhost:8089/ems/siteMonitor/getSingleBatteryData?clusterDeviceId=BMSC01&siteId=021_FXX_01&deviceId=001&startDate=2025-07-11&endDate=2025-07-18
|
||||
export function getSingleBatteryData({siteId, deviceId, clusterDeviceId, startDate, endDate}) {
|
||||
return request({
|
||||
@ -130,7 +97,7 @@ export function getSingleBatteryData({siteId, deviceId, clusterDeviceId, startDa
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇娑插喎鍒楄〃鏁版嵁
|
||||
//获取液冷列表数据
|
||||
export function getCoolingDataList(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getCoolingDataList?siteId=${siteId}`,
|
||||
@ -138,7 +105,7 @@ export function getCoolingDataList(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇鍔ㄧ幆鏁版嵁
|
||||
//获取动环数据
|
||||
export function getDhDataList(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getDhDataList?siteId=${siteId}`,
|
||||
@ -146,7 +113,7 @@ export function getDhDataList(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇娑堥槻鏁版嵁
|
||||
//获取消防数据
|
||||
export function getXfDataList(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getXfDataList?siteId=${siteId}`,
|
||||
@ -155,7 +122,7 @@ export function getXfDataList(siteId) {
|
||||
}
|
||||
|
||||
|
||||
//鑾峰彇鐢佃〃鏁版嵁
|
||||
//获取电表数据
|
||||
export function getAmmeterDataList(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getAmmeterDataList?siteId=${siteId}`,
|
||||
@ -163,7 +130,7 @@ export function getAmmeterDataList(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
// 鏁呴殰鍛婅
|
||||
// 故障告警
|
||||
export function getAlarmDetailList({
|
||||
status,
|
||||
siteId,
|
||||
@ -180,7 +147,7 @@ export function getAlarmDetailList({
|
||||
})
|
||||
}
|
||||
|
||||
// 鍛婅鐢熸垚宸ュ崟
|
||||
// 告警生成工单
|
||||
export function createTicketNo(data) {
|
||||
return request({
|
||||
url: `/ems/siteAlarm/createTicketNo`,
|
||||
@ -189,312 +156,16 @@ export function createTicketNo(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 鍛婅纭鍏抽棴
|
||||
export function closeAlarm(data) {
|
||||
return request({
|
||||
url: `/ems/siteAlarm/closeAlarm`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
function getFieldNameByCode(fieldCode) {
|
||||
const raw = String(fieldCode || '').trim()
|
||||
if (!raw) return ''
|
||||
const idx = raw.lastIndexOf('__')
|
||||
return idx >= 0 ? raw.slice(idx + 2) : raw
|
||||
}
|
||||
|
||||
function normalizeRows(displayResponse) {
|
||||
const rows = displayResponse?.data || []
|
||||
return Array.isArray(rows) ? rows : []
|
||||
}
|
||||
|
||||
function filterByMenu(rows, menuCode) {
|
||||
return (rows || []).filter(item => item && item.menuCode === menuCode)
|
||||
}
|
||||
|
||||
function toDateLabel(valueTime) {
|
||||
if (!valueTime) return ''
|
||||
const date = new Date(valueTime)
|
||||
if (isNaN(date.getTime())) return ''
|
||||
const p = (n) => String(n).padStart(2, '0')
|
||||
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())}`
|
||||
}
|
||||
|
||||
function toDateTimeLabel(valueTime) {
|
||||
if (!valueTime) return ''
|
||||
const date = new Date(valueTime)
|
||||
if (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())}`
|
||||
}
|
||||
|
||||
function toValueMap(rows) {
|
||||
const map = {}
|
||||
;(rows || []).forEach(item => {
|
||||
const fieldName = getFieldNameByCode(item?.fieldCode)
|
||||
if (fieldName) {
|
||||
map[fieldName] = item?.fieldValue
|
||||
}
|
||||
})
|
||||
return map
|
||||
}
|
||||
|
||||
function groupRowsByDevice(rows) {
|
||||
const map = new Map()
|
||||
;(rows || []).forEach(item => {
|
||||
const deviceId = String(item?.deviceId || '').trim() || 'DEFAULT'
|
||||
if (!map.has(deviceId)) {
|
||||
map.set(deviceId, [])
|
||||
}
|
||||
map.get(deviceId).push(item)
|
||||
})
|
||||
return map
|
||||
}
|
||||
|
||||
function paginateRows(rows, pageNum = 1, pageSize = 10) {
|
||||
const safePageNum = Number(pageNum) > 0 ? Number(pageNum) : 1
|
||||
const safePageSize = Number(pageSize) > 0 ? Number(pageSize) : 10
|
||||
const start = (safePageNum - 1) * safePageSize
|
||||
return (rows || []).slice(start, start + safePageSize)
|
||||
}
|
||||
|
||||
function toNumber(value) {
|
||||
const num = Number(value)
|
||||
return Number.isFinite(num) ? num : null
|
||||
}
|
||||
|
||||
function normalizeDateInput(dateStr) {
|
||||
if (dateStr) return dateStr
|
||||
return toDateLabel(new Date())
|
||||
}
|
||||
|
||||
function resolveElectricUnit(startDate, endDate) {
|
||||
const start = new Date(`${normalizeDateInput(startDate)} 00:00:00`)
|
||||
const end = new Date(`${normalizeDateInput(endDate)} 00:00:00`)
|
||||
if (isNaN(start.getTime()) || isNaN(end.getTime())) return '\u65e5'
|
||||
const diffDays = Math.floor((end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000))
|
||||
if (diffDays <= 0) return '\u65e5'
|
||||
if (diffDays < 30) return '\u65e5'
|
||||
return '\u6708'
|
||||
}
|
||||
|
||||
function formatByUnit(date, unit) {
|
||||
const p = (n) => String(n).padStart(2, '0')
|
||||
if (unit === '\u65e5') return `${p(date.getHours())}:${p(date.getMinutes())}`
|
||||
if (unit === '\u6708') return `${date.getFullYear()}-${p(date.getMonth() + 1)}`
|
||||
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())}`
|
||||
}
|
||||
|
||||
function aggregateCurveByUnit(curveRows, unit) {
|
||||
const result = new Map()
|
||||
;(curveRows || []).forEach(item => {
|
||||
const time = item?.dataTime ? new Date(item.dataTime) : null
|
||||
if (!time || isNaN(time.getTime())) return
|
||||
const value = toNumber(item?.pointValue)
|
||||
if (value == null) return
|
||||
const label = formatByUnit(time, unit)
|
||||
result.set(label, value)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
function getLatestCurveValue(curveRows) {
|
||||
let latestTime = null
|
||||
let latestValue = null
|
||||
;(curveRows || []).forEach(item => {
|
||||
const time = item?.dataTime ? new Date(item.dataTime) : null
|
||||
if (!time || isNaN(time.getTime())) return
|
||||
const value = toNumber(item?.pointValue)
|
||||
if (value == null) return
|
||||
if (latestTime == null || time.getTime() > latestTime) {
|
||||
latestTime = time.getTime()
|
||||
latestValue = value
|
||||
}
|
||||
})
|
||||
return latestValue
|
||||
}
|
||||
|
||||
function findMappingByField(rows, fieldNames = []) {
|
||||
const targetSet = new Set((fieldNames || []).map(name => String(name || '').trim()))
|
||||
return (rows || []).find(item => targetSet.has(getFieldNameByCode(item?.fieldCode)))
|
||||
}
|
||||
|
||||
function getDataPointFromMapping(mapping) {
|
||||
if (!mapping) return ''
|
||||
const useFixedDisplay = Number(mapping?.useFixedDisplay) === 1
|
||||
const fixedDataPoint = String(mapping?.fixedDataPoint || '').trim()
|
||||
const dataPoint = String(mapping?.dataPoint || '').trim()
|
||||
if (useFixedDisplay && fixedDataPoint) return fixedDataPoint
|
||||
return dataPoint || fixedDataPoint
|
||||
}
|
||||
|
||||
function queryPointCurveByPointId({siteId, pointId, startDate, endDate}) {
|
||||
if (!siteId || !pointId) return Promise.resolve([])
|
||||
const start = `${normalizeDateInput(startDate)} 00:00:00`
|
||||
const end = `${normalizeDateInput(endDate)} 23:59:59`
|
||||
return request({
|
||||
url: `/ems/pointConfig/curve`,
|
||||
method: 'post',
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
},
|
||||
data: {
|
||||
siteId,
|
||||
pointId,
|
||||
rangeType: 'custom',
|
||||
startTime: start,
|
||||
endTime: end
|
||||
}
|
||||
}).then(resp => (Array.isArray(resp?.data) ? resp.data : []))
|
||||
}
|
||||
|
||||
function sortCurveRows(curveRows = []) {
|
||||
return [...(curveRows || [])].sort((a, b) => {
|
||||
const ta = new Date(a?.dataTime).getTime()
|
||||
const tb = new Date(b?.dataTime).getTime()
|
||||
return (isNaN(ta) ? 0 : ta) - (isNaN(tb) ? 0 : tb)
|
||||
})
|
||||
}
|
||||
|
||||
function resolveRangeKind(startDate, endDate) {
|
||||
const start = new Date(`${normalizeDateInput(startDate)} 00:00:00`)
|
||||
const end = new Date(`${normalizeDateInput(endDate || startDate)} 00:00:00`)
|
||||
if (isNaN(start.getTime()) || isNaN(end.getTime())) return 'day'
|
||||
const diffDays = Math.floor((end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000))
|
||||
if (diffDays < 31) return 'minute'
|
||||
if (diffDays < 180) return 'day'
|
||||
return 'month'
|
||||
}
|
||||
|
||||
function formatTimeLabelByKind(date, kind = 'day') {
|
||||
const p = (n) => String(n).padStart(2, '0')
|
||||
if (kind === 'minute') return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())} ${p(date.getHours())}:${p(date.getMinutes())}`
|
||||
if (kind === 'month') return `${date.getFullYear()}-${p(date.getMonth() + 1)}`
|
||||
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())}`
|
||||
}
|
||||
|
||||
function buildSortedLabels(labelSet, kind = 'day') {
|
||||
const labels = Array.from(labelSet || [])
|
||||
return labels.sort()
|
||||
}
|
||||
|
||||
function normalizePointValue(raw) {
|
||||
const num = toNumber(raw)
|
||||
if (num != null) return num
|
||||
return raw == null ? '' : raw
|
||||
}
|
||||
|
||||
function resolveAliasByField(aliasMap, fieldName) {
|
||||
const raw = String(fieldName || '').trim()
|
||||
if (!raw) return ''
|
||||
if (aliasMap[raw]) return aliasMap[raw]
|
||||
const withoutStat = raw.replace(/_stat$/, '')
|
||||
return aliasMap[withoutStat] || ''
|
||||
}
|
||||
|
||||
function toFixedNumber(value, digits = 2) {
|
||||
const num = toNumber(value)
|
||||
if (num == null) return null
|
||||
return Number(num.toFixed(digits))
|
||||
}
|
||||
|
||||
function sortDailyAmmeterRows(rows = []) {
|
||||
return [...(rows || [])].sort((a, b) => {
|
||||
const ta = new Date(`${a?.dataTime || ''} 00:00:00`).getTime()
|
||||
const tb = new Date(`${b?.dataTime || ''} 00:00:00`).getTime()
|
||||
return (isNaN(ta) ? 0 : ta) - (isNaN(tb) ? 0 : tb)
|
||||
})
|
||||
}
|
||||
|
||||
function queryAllAmmeterDailyRows({siteId, startTime, endTime, pageSize = 500, pageNum = 1, rows = []}) {
|
||||
return getAmmeterData({siteId, startTime, endTime, pageSize, pageNum}).then((response) => {
|
||||
const currentRows = Array.isArray(response?.rows) ? response.rows : []
|
||||
const allRows = rows.concat(currentRows)
|
||||
const total = Number(response?.total) || 0
|
||||
if (allRows.length >= total || currentRows.length < pageSize) {
|
||||
return allRows
|
||||
}
|
||||
return queryAllAmmeterDailyRows({
|
||||
siteId,
|
||||
startTime,
|
||||
endTime,
|
||||
pageSize,
|
||||
pageNum: pageNum + 1,
|
||||
rows: allRows,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function queryMenuPointCurves({siteId, menuCode, startDate, endDate, mappingFilter}) {
|
||||
return getProjectPointMapping(siteId).then((mappingResp) => {
|
||||
const allMappings = Array.isArray(mappingResp?.data) ? mappingResp.data : []
|
||||
let menuMappings = allMappings.filter(item => item?.menuCode === menuCode)
|
||||
if (typeof mappingFilter === 'function') {
|
||||
menuMappings = menuMappings.filter(mappingFilter)
|
||||
}
|
||||
const tasks = menuMappings.map((mapping) => {
|
||||
const pointId = getDataPointFromMapping(mapping)
|
||||
if (!pointId) return Promise.resolve(null)
|
||||
return queryPointCurveByPointId({siteId, pointId, startDate, endDate})
|
||||
.then(curve => ({
|
||||
deviceId: String(mapping?.deviceId || '').trim(),
|
||||
fieldName: getFieldNameByCode(mapping?.fieldCode),
|
||||
curve: sortCurveRows(curve || []),
|
||||
}))
|
||||
.catch(() => ({
|
||||
deviceId: String(mapping?.deviceId || '').trim(),
|
||||
fieldName: getFieldNameByCode(mapping?.fieldCode),
|
||||
curve: [],
|
||||
}))
|
||||
})
|
||||
return Promise.all(tasks).then(rows => rows.filter(Boolean))
|
||||
})
|
||||
}
|
||||
|
||||
// 鐢甸噺鎸囨爣
|
||||
// 概率统计
|
||||
//获取概率统计 电量指标接口
|
||||
export function getElectricData({siteId, startDate, endDate}) {
|
||||
return queryAllAmmeterDailyRows({
|
||||
siteId,
|
||||
startTime: startDate,
|
||||
endTime: endDate,
|
||||
}).then((rows) => {
|
||||
const sortedRows = sortDailyAmmeterRows(rows)
|
||||
const sevenDayDisChargeStats = sortedRows.map((item) => {
|
||||
const chargedCap = toNumber(item?.activeTotalKwh)
|
||||
const disChargedCap = toNumber(item?.reActiveTotalKwh)
|
||||
const rowEffect = toNumber(item?.effect)
|
||||
const dailyEfficiency = rowEffect != null
|
||||
? rowEffect
|
||||
: (chargedCap > 0 && disChargedCap != null ? toFixedNumber((disChargedCap / chargedCap) * 100) : null)
|
||||
return {
|
||||
ammeterDate: item?.dataTime || '',
|
||||
chargedCap: chargedCap == null ? '' : chargedCap,
|
||||
disChargedCap: disChargedCap == null ? '' : disChargedCap,
|
||||
dailyEfficiency: dailyEfficiency == null ? '' : dailyEfficiency,
|
||||
}
|
||||
})
|
||||
|
||||
const totalChargedCap = toFixedNumber(sevenDayDisChargeStats.reduce((acc, item) => acc + (toNumber(item.chargedCap) || 0), 0))
|
||||
const totalDisChargedCap = toFixedNumber(sevenDayDisChargeStats.reduce((acc, item) => acc + (toNumber(item.disChargedCap) || 0), 0))
|
||||
const efficiency = totalChargedCap > 0
|
||||
? toFixedNumber((totalDisChargedCap / totalChargedCap) * 100)
|
||||
: 0
|
||||
|
||||
return {
|
||||
data: {
|
||||
totalChargedCap: totalChargedCap == null ? 0 : totalChargedCap,
|
||||
totalDisChargedCap: totalDisChargedCap == null ? 0 : totalDisChargedCap,
|
||||
efficiency: efficiency == null ? 0 : efficiency,
|
||||
unit: '\u65e5',
|
||||
sevenDayDisChargeStats,
|
||||
}
|
||||
}
|
||||
return request({
|
||||
url: `/ems/statsReport/getElectricData?siteId=${siteId}&startDate=${startDate}&endDate=${endDate}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇pcs鍒楄〃
|
||||
//获取pcs列表
|
||||
export function getPcsNameList(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getPcsNameList?siteId=${siteId}`,
|
||||
@ -502,153 +173,33 @@ export function getPcsNameList(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
// pcs鏇茬嚎
|
||||
export function getPCSData({siteId, startTime, endTime}) {
|
||||
const kind = resolveRangeKind(startTime, endTime)
|
||||
const aliasMap = {
|
||||
activePower_stat: 'activePower',
|
||||
activePower: 'activePower',
|
||||
reactivePower_stat: 'reactivePower',
|
||||
reactivePower: 'reactivePower',
|
||||
uCurrent: 'uCurrent',
|
||||
vCurrent: 'vCurrent',
|
||||
wCurrent: 'wCurrent',
|
||||
}
|
||||
return queryMenuPointCurves({
|
||||
siteId,
|
||||
menuCode: 'TJBB_PCSQX',
|
||||
startDate: startTime,
|
||||
endDate: endTime,
|
||||
}).then((records) => {
|
||||
const byDevice = new Map()
|
||||
records.forEach((record) => {
|
||||
const alias = resolveAliasByField(aliasMap, record.fieldName)
|
||||
if (!alias) return
|
||||
const deviceId = record.deviceId || ''
|
||||
if (!byDevice.has(deviceId)) byDevice.set(deviceId, new Map())
|
||||
const rowMap = byDevice.get(deviceId)
|
||||
;(record.curve || []).forEach((point) => {
|
||||
const time = point?.dataTime ? new Date(point.dataTime) : null
|
||||
if (!time || isNaN(time.getTime())) return
|
||||
const label = formatTimeLabelByKind(time, kind)
|
||||
if (!rowMap.has(label)) rowMap.set(label, {statisDate: label})
|
||||
rowMap.get(label)[alias] = normalizePointValue(point?.pointValue)
|
||||
})
|
||||
})
|
||||
const data = []
|
||||
byDevice.forEach((rowMap, deviceId) => {
|
||||
const labels = buildSortedLabels(new Set(Array.from(rowMap.keys())), kind)
|
||||
data.push({
|
||||
deviceId,
|
||||
dataList: labels.map(label => rowMap.get(label)),
|
||||
})
|
||||
})
|
||||
return {data}
|
||||
//pcs曲线
|
||||
export function getPCSData({siteId, startTime, endTime, dataType}) {
|
||||
return request({
|
||||
url: `/ems/statsReport/getPCSData?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}&dataType=${dataType}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 鐢垫睜鍫嗘洸绾?
|
||||
export function getStackData({siteId, startTime, endTime}) {
|
||||
const kind = resolveRangeKind(startTime, endTime)
|
||||
const aliasMap = {
|
||||
temp: 'temp',
|
||||
voltage_stat: 'voltage',
|
||||
voltage: 'voltage',
|
||||
current: 'current',
|
||||
soc_stat: 'soc',
|
||||
soc: 'soc',
|
||||
}
|
||||
return queryMenuPointCurves({
|
||||
siteId,
|
||||
menuCode: 'TJBB_DCDQX',
|
||||
startDate: startTime,
|
||||
endDate: endTime,
|
||||
}).then((records) => {
|
||||
const byDevice = new Map()
|
||||
records.forEach((record) => {
|
||||
const alias = resolveAliasByField(aliasMap, record.fieldName)
|
||||
if (!alias) return
|
||||
const deviceId = record.deviceId || ''
|
||||
if (!byDevice.has(deviceId)) byDevice.set(deviceId, new Map())
|
||||
const rowMap = byDevice.get(deviceId)
|
||||
;(record.curve || []).forEach((point) => {
|
||||
const time = point?.dataTime ? new Date(point.dataTime) : null
|
||||
if (!time || isNaN(time.getTime())) return
|
||||
const label = formatTimeLabelByKind(time, kind)
|
||||
if (!rowMap.has(label)) rowMap.set(label, {statisDate: label})
|
||||
rowMap.get(label)[alias] = normalizePointValue(point?.pointValue)
|
||||
})
|
||||
})
|
||||
const data = []
|
||||
byDevice.forEach((rowMap, deviceId) => {
|
||||
const labels = buildSortedLabels(new Set(Array.from(rowMap.keys())), kind)
|
||||
data.push({
|
||||
deviceId,
|
||||
dataList: labels.map(label => rowMap.get(label)),
|
||||
})
|
||||
})
|
||||
return {data}
|
||||
//电池堆曲线
|
||||
export function getStackData({siteId, startTime, endTime, dataType}) {
|
||||
return request({
|
||||
url: `/ems/statsReport/getStackData?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}&dataType=${dataType}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 鐢垫睜娓╁害
|
||||
//电池温度
|
||||
export function getClusterData({siteId, stackId, clusterId, dateTime, pageNum, pageSize}) {
|
||||
const startDate = dateTime || normalizeDateInput('')
|
||||
const endDate = dateTime || normalizeDateInput('')
|
||||
const kind = 'minute'
|
||||
const aliasMap = {
|
||||
maxTemp: 'maxTemp',
|
||||
maxTempId: 'maxTempId',
|
||||
minTemp: 'minTemp',
|
||||
minTempId: 'minTempId',
|
||||
maxVoltage: 'maxVoltage',
|
||||
maxVoltageId: 'maxVoltageId',
|
||||
minVoltage: 'minVoltage',
|
||||
minVoltageId: 'minVoltageId',
|
||||
}
|
||||
const queryClusterCurves = (withClusterFilter) => queryMenuPointCurves({
|
||||
siteId,
|
||||
menuCode: 'TJBB_DCWD',
|
||||
startDate,
|
||||
endDate,
|
||||
mappingFilter: withClusterFilter
|
||||
? (item) => {
|
||||
if (!clusterId) return true
|
||||
return String(item?.deviceId || '').trim() === String(clusterId || '').trim()
|
||||
}
|
||||
: undefined
|
||||
})
|
||||
|
||||
return queryClusterCurves(true).then((records) => {
|
||||
if (clusterId && (!records || records.length === 0)) {
|
||||
return queryClusterCurves(false)
|
||||
}
|
||||
return records
|
||||
}).then((records) => {
|
||||
const rowMap = new Map()
|
||||
records.forEach((record) => {
|
||||
const alias = resolveAliasByField(aliasMap, record.fieldName)
|
||||
if (!alias) return
|
||||
;(record.curve || []).forEach((point) => {
|
||||
const time = point?.dataTime ? new Date(point.dataTime) : null
|
||||
if (!time || isNaN(time.getTime())) return
|
||||
const label = formatTimeLabelByKind(time, kind)
|
||||
if (!rowMap.has(label)) rowMap.set(label, {statisDate: label})
|
||||
rowMap.get(label)[alias] = normalizePointValue(point?.pointValue)
|
||||
})
|
||||
})
|
||||
const labels = buildSortedLabels(new Set(Array.from(rowMap.keys())), kind)
|
||||
const fullRows = labels.map(label => rowMap.get(label))
|
||||
return {
|
||||
rows: paginateRows(fullRows, pageNum, pageSize),
|
||||
total: fullRows.length,
|
||||
}
|
||||
return request({
|
||||
url: `/ems/statsReport/getClusterData?siteId=${siteId}&stackId=${stackId}&clusterId=${clusterId}&dateTime=${dateTime}&pageNum=${pageNum}&pageSize=${pageSize}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 瀹炴椂杩愯
|
||||
//鍌ㄨ兘
|
||||
// 实时运行
|
||||
//储能
|
||||
export function storagePower(siteId, startTime, endTime) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/runningGraph/storagePower?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}`,
|
||||
@ -656,7 +207,7 @@ export function storagePower(siteId, startTime, endTime) {
|
||||
})
|
||||
}
|
||||
|
||||
//poc娓╁害
|
||||
//poc温度
|
||||
export function pcsMaxTemp(siteId, startTime, endTime) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/runningGraph/pcsMaxTemp?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}`,
|
||||
@ -664,7 +215,7 @@ export function pcsMaxTemp(siteId, startTime, endTime) {
|
||||
})
|
||||
}
|
||||
|
||||
// 鐢垫睜骞冲潎soc
|
||||
// 电池平均soc
|
||||
export function batteryAveSoc(siteId, startTime, endTime) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/runningGraph/batteryAveSoc?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}`,
|
||||
@ -672,7 +223,7 @@ export function batteryAveSoc(siteId, startTime, endTime) {
|
||||
})
|
||||
}
|
||||
|
||||
// 鐢垫睜骞冲潎娓╁害
|
||||
// 电池平均温度
|
||||
export function batteryAveTemp(siteId, startTime, endTime) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/runningGraph/batteryAveTemp?siteId=${siteId}&startDate=${startTime}&endDate=${endTime}`,
|
||||
@ -680,43 +231,15 @@ export function batteryAveTemp(siteId, startTime, endTime) {
|
||||
})
|
||||
}
|
||||
|
||||
// 鍔熺巼鏇茬嚎
|
||||
// 功率曲线
|
||||
export function getPowerData({siteId, startDate, endDate}) {
|
||||
const kind = resolveRangeKind(startDate, endDate)
|
||||
const aliasMap = {
|
||||
gridPower_stat: 'gridPower',
|
||||
gridPower: 'gridPower',
|
||||
loadPower_stat: 'loadPower',
|
||||
loadPower: 'loadPower',
|
||||
storagePower_stat: 'storagePower',
|
||||
storagePower: 'storagePower',
|
||||
pvPower_stat: 'pvPower',
|
||||
pvPower: 'pvPower',
|
||||
}
|
||||
return queryMenuPointCurves({
|
||||
siteId,
|
||||
menuCode: 'TJBB_GLQX',
|
||||
startDate,
|
||||
endDate,
|
||||
}).then((records) => {
|
||||
const rowMap = new Map()
|
||||
records.forEach((record) => {
|
||||
const alias = resolveAliasByField(aliasMap, record.fieldName)
|
||||
if (!alias) return
|
||||
;(record.curve || []).forEach((point) => {
|
||||
const time = point?.dataTime ? new Date(point.dataTime) : null
|
||||
if (!time || isNaN(time.getTime())) return
|
||||
const label = formatTimeLabelByKind(time, kind)
|
||||
if (!rowMap.has(label)) rowMap.set(label, {statisDate: label})
|
||||
rowMap.get(label)[alias] = normalizePointValue(point?.pointValue)
|
||||
})
|
||||
})
|
||||
const labels = buildSortedLabels(new Set(Array.from(rowMap.keys())), kind)
|
||||
return {data: labels.map(label => rowMap.get(label))}
|
||||
return request({
|
||||
url: `/ems/statsReport/getPowerData?siteId=${siteId}&startDate=${startDate}&endDate=${endDate}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
//鐢佃〃鍒楄〃
|
||||
//电表列表
|
||||
export function getLoadNameList(siteId) {
|
||||
return request({
|
||||
url: `/ems/statsReport/getLoadNameList?siteId=${siteId}`,
|
||||
@ -724,22 +247,15 @@ export function getLoadNameList(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
// 鐢佃〃鎶ヨ〃
|
||||
// 电表报表
|
||||
export function getAmmeterData({siteId, startTime, endTime, pageSize, pageNum}) {
|
||||
return request({
|
||||
url: `/ems/statsReport/getAmmeterDataFromDaily`,
|
||||
method: 'get',
|
||||
params: {
|
||||
siteId,
|
||||
startTime,
|
||||
endTime,
|
||||
pageSize,
|
||||
pageNum,
|
||||
}
|
||||
url: `/ems/statsReport/getAmmeterData?siteId=${siteId}&startTime=${startTime}&endTime=${endTime}&pageSize=${pageSize}&pageNum=${pageNum}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 鐢典环鎶ヨ〃
|
||||
// 电价报表
|
||||
export function getAmmeterRevenueData(data) {
|
||||
return request({
|
||||
url: `/ems/statsReport/getAmmeterRevenueData`,
|
||||
@ -748,23 +264,7 @@ export function getAmmeterRevenueData(data) {
|
||||
})
|
||||
}
|
||||
|
||||
export function batchGetBizRemark(data) {
|
||||
return request({
|
||||
url: `/system/bizRemark/batchGet`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function saveBizRemark(data) {
|
||||
return request({
|
||||
url: `/system/bizRemark/save`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
//绛栫暐鍒楄〃
|
||||
//策略列表
|
||||
export function strategyRunningList(siteId) {
|
||||
return request({
|
||||
url: `/system/strategyRunning/list?siteId=${siteId}`,
|
||||
@ -772,7 +272,7 @@ export function strategyRunningList(siteId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鍋滄绛栫暐
|
||||
//停止策略
|
||||
export function stopStrategyRunning(id) {
|
||||
return request({
|
||||
url: `/system/strategyRunning/stop?id=${id}`,
|
||||
@ -780,7 +280,7 @@ export function stopStrategyRunning(id) {
|
||||
})
|
||||
}
|
||||
|
||||
// 鑾峰彇鎵€鏈変富绛栫暐
|
||||
// 获取所有主策略
|
||||
export function getMainStrategyList() {
|
||||
return request({
|
||||
url: `/system/strategyRunning/getMainStrategyList`,
|
||||
@ -788,7 +288,7 @@ export function getMainStrategyList() {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇鎵€鏈夎緟鍔╃瓥鐣?
|
||||
//获取所有辅助策略
|
||||
export function getAuxStrategyList() {
|
||||
return request({
|
||||
url: `/system/strategyRunning/getAuxStrategyList`,
|
||||
@ -796,7 +296,7 @@ export function getAuxStrategyList() {
|
||||
})
|
||||
}
|
||||
|
||||
//閰嶇疆绛栫暐
|
||||
//配置策略
|
||||
export function configStrategy(data) {
|
||||
return request({
|
||||
url: `/system/strategyRunning/configStrategy`,
|
||||
@ -805,25 +305,8 @@ export function configStrategy(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 鑾峰彇绛栫暐杩愯鍙傛暟閰嶇疆锛堟寜绔欑偣锛?
|
||||
export function getStrategyRuntimeConfig(siteId) {
|
||||
return request({
|
||||
url: `/system/strategyRuntimeConfig/getBySiteId?siteId=${siteId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 淇濆瓨绛栫暐杩愯鍙傛暟閰嶇疆锛堟寜绔欑偣锛?
|
||||
export function saveStrategyRuntimeConfig(data) {
|
||||
return request({
|
||||
url: `/system/strategyRuntimeConfig/save`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
//http://localhost:8089/strategy/temp/getTempNameList?strategyId=1&siteId=021_FXX_01
|
||||
//鑾峰彇绛栫暐涓嬬殑鎵€鏈夋ā鏉垮垪琛?
|
||||
//获取策略下的所有模板列表
|
||||
export function getTempNameList({siteId, strategyId}) {
|
||||
return request({
|
||||
url: `/strategy/temp/getTempNameList?siteId=${siteId}&strategyId=${strategyId}`,
|
||||
@ -831,7 +314,7 @@ export function getTempNameList({siteId, strategyId}) {
|
||||
})
|
||||
}
|
||||
|
||||
//鑾峰彇妯℃澘璇︽儏
|
||||
//获取模板详情
|
||||
///strategy/temp/list?templateId=1
|
||||
export function getStrategyTempDetail(templateId) {
|
||||
return request({
|
||||
@ -840,7 +323,7 @@ export function getStrategyTempDetail(templateId) {
|
||||
})
|
||||
}
|
||||
|
||||
//鏂板妯℃澘
|
||||
//新增模板
|
||||
export function addStrategyTemp(data) {
|
||||
return request({
|
||||
url: `/strategy/temp`,
|
||||
@ -873,7 +356,7 @@ export function timeConfigList({siteId, strategyId}) {
|
||||
})
|
||||
}
|
||||
|
||||
//淇濆瓨鏃堕棿閰嶇疆
|
||||
//保存时间配置
|
||||
// http://localhost:8089/strategy/timeConfig
|
||||
export function setTimeConfigList(data) {
|
||||
return request({
|
||||
@ -883,7 +366,7 @@ export function setTimeConfigList(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 绛栫暐鏇茬嚎鍥?
|
||||
// 策略曲线图
|
||||
//http://localhost:8089/strategy/curve/curveList?strategyId=1&siteId=021_FXX_01
|
||||
export function curveList({siteId, strategyId}) {
|
||||
return request({
|
||||
@ -892,7 +375,7 @@ export function curveList({siteId, strategyId}) {
|
||||
})
|
||||
}
|
||||
|
||||
//鍗曠珯鐩戞帶 棣栭〉 褰撴棩鍔熺巼鏇茬嚎
|
||||
//单站监控 首页 当日功率曲线
|
||||
export function getPointData({siteId, startDate, endDate}) {
|
||||
return request({
|
||||
url: `/ems/siteMonitor/getPointData?siteId=${siteId}&startDate=${startDate}&endDate=${endDate}`,
|
||||
|
||||
@ -29,12 +29,4 @@ export function getAllBatteryIdsBySites(data) {
|
||||
url: `/ems/generalQuery/getAllBatteryIdsBySites/${data}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// 综合查询-按站点获取配置设备列表
|
||||
export function getGeneralQueryDeviceList(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/getDeviceList?siteId=${siteId}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -8,33 +8,6 @@ export function getSiteInfoList({siteName, startTime, endTime, pageSize, pageNum
|
||||
})
|
||||
}
|
||||
|
||||
// 手动同步站点天气(收益报表)
|
||||
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(data) {
|
||||
return request({
|
||||
@ -129,46 +102,6 @@ export function getDeviceListBySiteAndCategory({siteId, deviceCategory}) {
|
||||
})
|
||||
}
|
||||
|
||||
// 获取单站监控项目点位映射
|
||||
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({
|
||||
@ -230,256 +163,6 @@ export function importPointList(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 downloadSingleBatteryMonitorImportTemplate(siteId) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/downloadSingleBatteryMonitorImportTemplate`,
|
||||
method: 'get',
|
||||
params: { siteId },
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// 导入单体电池与监控点位映射
|
||||
export function importSingleBatteryMonitorMappings(data) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/importSingleBatteryMonitorMappings`,
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点位配置详情
|
||||
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}) {
|
||||
@ -518,11 +201,3 @@ export function deleteMqtt(id) {
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function initializeSingleBatteryMonitorMappings(data) {
|
||||
return request({
|
||||
url: `/ems/siteConfig/initializeSingleBatteryMonitorMappings`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 MiB After Width: | Height: | Size: 11 MiB |
@ -1,32 +1,8 @@
|
||||
<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">
|
||||
<el-dialog :fullscreen="true" :append-to-body="true" :visible.sync="show" :show-close="false" top="0"
|
||||
custom-class="big-data-dialog">
|
||||
<img src="@/assets/images/ems/bigData.png" alt="">
|
||||
<div class="close-btn" @click="show=false">
|
||||
<i class="el-icon-close"></i>
|
||||
</div>
|
||||
</el-dialog>
|
||||
@ -38,54 +14,15 @@
|
||||
top: 10px;
|
||||
font-size: 23px;
|
||||
line-height: 20px;
|
||||
color: rgba(217, 242, 255, 1);
|
||||
color: rgba(176, 228, 255, 0.7);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
@ -105,33 +42,8 @@
|
||||
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>
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -2,10 +2,7 @@
|
||||
<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">
|
||||
<i v-if="data.loading" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ data.value | formatNumber }}</span>
|
||||
</div>
|
||||
<div class="single-square-box-value">{{ data.value | formatNumber }}</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@ -17,30 +14,19 @@
|
||||
color:#666666;
|
||||
text-align: left;
|
||||
.single-square-box-title{
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
padding-bottom: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.single-square-box-value{
|
||||
font-size: 18px;
|
||||
line-height: 18px;
|
||||
font-size: 26px;
|
||||
line-height: 26px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.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;
|
||||
padding: 12px 10px;
|
||||
}
|
||||
}
|
||||
@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>
|
||||
export default {
|
||||
|
||||
@ -4,10 +4,7 @@
|
||||
<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">
|
||||
<i v-if="item.loading" class="el-icon-loading"></i>
|
||||
<span v-else>{{item.num | formatNumber}}</span>
|
||||
</div>
|
||||
<div class="num">{{item.num | formatNumber}}</div>
|
||||
</el-card>
|
||||
</el-row>
|
||||
</template>
|
||||
@ -21,35 +18,30 @@ export default {
|
||||
title:'站点总数(座)',
|
||||
num:'',
|
||||
color:'#FFBD00',
|
||||
attr:'siteNum',
|
||||
loading: true
|
||||
attr:'siteNum'
|
||||
},{
|
||||
title:'装机功率(MW)',
|
||||
num:'',
|
||||
color:'#3C81FF',
|
||||
attr:'installPower',
|
||||
loading: true
|
||||
attr:'installPower'
|
||||
|
||||
},{
|
||||
title:'装机容量(MW)',
|
||||
num:'',
|
||||
color:'#5AC7C0',
|
||||
attr:'installCapacity',
|
||||
loading: true
|
||||
attr:'installCapacity'
|
||||
|
||||
},{
|
||||
title:'总充电量(KWh)',
|
||||
num:'',
|
||||
color:'#A696FF',
|
||||
attr:'totalChargedCap',
|
||||
loading: true
|
||||
attr:'totalChargedCap'
|
||||
|
||||
},{
|
||||
title:'总放电量(KWh)',
|
||||
num:'',
|
||||
color:'#A696FF',
|
||||
attr:'totalDischargedCap',
|
||||
loading: true
|
||||
attr:'totalDischargedCap'
|
||||
|
||||
}]
|
||||
}
|
||||
@ -58,7 +50,6 @@ export default {
|
||||
setData(res = {}){
|
||||
this.data.forEach((item)=>{
|
||||
item.num =res[item.attr]
|
||||
item.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@ -2,16 +2,8 @@
|
||||
<template>
|
||||
<div class="zd-select-container">
|
||||
<el-form :inline="true">
|
||||
<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-form-item label="站点选择">
|
||||
<el-select v-model="id" placeholder="请选择换电站名称" :loading="loading" loading-text="正在加载数据" @change="onSubmit">
|
||||
<el-option :label="item.siteName" :value="item.siteId" v-for="(item,index) in siteList" :key="index+'zdxeSelect'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@ -23,21 +15,7 @@
|
||||
</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'
|
||||
@ -53,26 +31,6 @@ 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() {
|
||||
@ -86,23 +44,10 @@ 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)){
|
||||
@ -115,7 +60,7 @@ import {mapGetters} from "vuex"
|
||||
getList(){
|
||||
return getAllSites().then(response => {
|
||||
this.siteList = response.data || []
|
||||
this.emitSitesLoaded()
|
||||
console.log("获取站点列表返回数据",response,this.siteList)
|
||||
this.setDefaultSite()
|
||||
}).finally(() => {this.loading=false;this.searchLoading=false})
|
||||
}
|
||||
@ -126,14 +71,15 @@ 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)
|
||||
this.getList().then(()=>{
|
||||
this.$store.commit('SET_ZD_LIST', this.siteList)
|
||||
console.log("从store中获取站点列表数据,但是store中的zdList=[],所以从接口获取数据",this.zdList,this.siteList)
|
||||
})
|
||||
}else{
|
||||
this.siteList = this.zdList
|
||||
this.emitSitesLoaded()
|
||||
this.loading=false
|
||||
this.searchLoading=false
|
||||
console.log("从store中获取站点列表数据",this.zdList,this.siteList)
|
||||
this.setDefaultSite()
|
||||
}
|
||||
}else{
|
||||
|
||||
@ -22,16 +22,6 @@
|
||||
</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">
|
||||
@ -51,7 +41,6 @@
|
||||
<svg-icon icon-class="more-up"/>
|
||||
</div>
|
||||
</div>
|
||||
<BigDataPopup ref="bigDataPopup"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -63,8 +52,6 @@ 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'],
|
||||
@ -74,9 +61,7 @@ export default {
|
||||
Hamburger,
|
||||
Screenfull,
|
||||
SizeSelect,
|
||||
Search,
|
||||
BigDataPopup,
|
||||
ZdSelect
|
||||
Search
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
@ -97,27 +82,11 @@ 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
|
||||
const routeUrl = this.$router.resolve({
|
||||
path: '/screen'
|
||||
})
|
||||
window.open(routeUrl.href, '_blank')
|
||||
},
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
@ -181,22 +150,6 @@ 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;
|
||||
@ -262,7 +215,6 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -10,16 +10,11 @@ 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()) {
|
||||
@ -31,24 +26,6 @@ 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信息
|
||||
|
||||
@ -9,13 +9,13 @@ export const dzjk = [
|
||||
redirect: '/dzjk/home',
|
||||
meta: {title: '单站监控', icon: 'dashboard',},
|
||||
alwaysShow: false,
|
||||
name: 'DzjkLocal',
|
||||
name: 'Dzjk',
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/ems/dzjk/index'),
|
||||
name: 'DzjkRoot',
|
||||
name: 'Dzjk',
|
||||
redirect: '/dzjk/home',
|
||||
hidden: true,
|
||||
children: [
|
||||
@ -285,28 +285,6 @@ export const 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'),
|
||||
@ -322,3 +300,6 @@ export const dzjk = [
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
@ -52,6 +52,11 @@ export const constantRoutes = [
|
||||
component: () => import('@/views/register'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/screen',
|
||||
component: () => import('@/views/screen/index'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
component: () => import('@/views/error/404'),
|
||||
@ -89,19 +94,6 @@ 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
|
||||
]
|
||||
|
||||
@ -47,12 +47,9 @@ const permission = {
|
||||
const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
|
||||
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
|
||||
router.addRoutes(asyncRoutes)
|
||||
// 后端已下发 dzjk 菜单时,移除本地静态 dzjk 路由,避免重名/重复注册
|
||||
if (hasDzjk) {
|
||||
if(!hasDzjk){
|
||||
const index = constantRoutes.findIndex(i=>i.path.indexOf('dzjk')>-1)
|
||||
if (index > -1) {
|
||||
constantRoutes.splice(index, 1)
|
||||
}
|
||||
constantRoutes.splice(index,1)
|
||||
}
|
||||
commit('SET_ROUTES', rewriteRoutes)
|
||||
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
|
||||
@ -124,16 +121,11 @@ export function filterDynamicRoutes(routes) {
|
||||
}
|
||||
|
||||
export const loadView = (view) => {
|
||||
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)
|
||||
return (resolve) => require([`@/views/${view}`], resolve)
|
||||
} else {
|
||||
// 使用 import 实现生产环境的路由懒加载
|
||||
return () => import(`@/views/${normalizedView}`)
|
||||
return () => import(`@/views/${view}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -50,3 +50,4 @@ export default {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -1,248 +0,0 @@
|
||||
<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>
|
||||
@ -9,6 +9,14 @@
|
||||
<el-form-item label="soc限制" prop="sdcLimit" required>
|
||||
<el-switch :active-value="1" :inactive-value="0" v-model="formData.sdcLimit"></el-switch>
|
||||
</el-form-item>
|
||||
<!-- <template v-if="formData.sdcLimit === 1">-->
|
||||
<el-form-item label="soc下限" prop="sdcDown">
|
||||
<el-input v-model="formData.sdcDown" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="soc上限" prop="sdcUp">
|
||||
<el-input v-model="formData.sdcUp" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
|
||||
</el-form-item>
|
||||
<!-- </template>-->
|
||||
</el-form>
|
||||
<el-button type="primary" size="mini" @click="addTime">新增</el-button>
|
||||
<!-- 新增时间段表单-->
|
||||
@ -56,12 +64,6 @@
|
||||
<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="formInline.sdcDown" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="soc上限" prop="sdcUp">
|
||||
<el-input v-model="formInline.sdcUp" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="充电状态" prop="chargeStatus">
|
||||
<el-select v-model="formInline.chargeStatus" placeholder="请选择" :style="{width: '100%'}">
|
||||
<el-option v-for="(value,key) in chargeStatusOptions" :key="key+'chargeStatusOptions'" :label="value"
|
||||
@ -82,100 +84,20 @@
|
||||
<el-table-column
|
||||
prop="startTime"
|
||||
label="开始时间">
|
||||
<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="结束时间">
|
||||
<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)">
|
||||
<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">
|
||||
<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>
|
||||
{{ chargeStatusOptions[scope.row.chargeStatus] }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@ -216,6 +138,8 @@ export default {
|
||||
formData: {
|
||||
templateName: '',
|
||||
sdcLimit: false,
|
||||
sdcDown: '',
|
||||
sdcUp: '',
|
||||
},
|
||||
rules: {
|
||||
templateName: [{
|
||||
@ -223,13 +147,19 @@ 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: {
|
||||
timeRange: range,
|
||||
chargeDischargePower: '',
|
||||
sdcDown: '',
|
||||
sdcUp: '',
|
||||
chargeStatus: ''
|
||||
},
|
||||
formInlineRule: {
|
||||
@ -245,14 +175,6 @@ export default {
|
||||
},
|
||||
{pattern: /^-?\d*\.?\d*$/, message: '请输入合法数字或小数'}
|
||||
],
|
||||
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: '请选择充放状态',
|
||||
@ -276,9 +198,11 @@ export default {
|
||||
this.formData = {
|
||||
templateName: '',
|
||||
sdcLimit: false,
|
||||
sdcDown: '',
|
||||
sdcUp: '',
|
||||
}
|
||||
this.formInline = {
|
||||
timeRange: this.secondRange, chargeDischargePower: '', sdcDown: '', sdcUp: '', chargeStatus: ''
|
||||
timeRange: this.secondRange, chargeDischargePower: '', chargeStatus: ''
|
||||
}//startTime: '', endTime: '',
|
||||
this.showAddTime = false
|
||||
this.tableData = []
|
||||
@ -292,12 +216,14 @@ export default {
|
||||
getStrategyTempDetail(this.editTempId).then(response => {
|
||||
const data = JSON.parse(JSON.stringify(response?.data || []));
|
||||
if (data.length > 0) {
|
||||
const {templateName, sdcLimit} = JSON.parse(JSON.stringify(data[0]));
|
||||
const {templateName, sdcLimit, sdcDown, sdcUp} = JSON.parse(JSON.stringify(data[0]));
|
||||
this.formData.templateName = templateName
|
||||
this.formData.sdcLimit = sdcLimit
|
||||
this.formData.sdcDown = sdcDown
|
||||
this.formData.sdcUp = sdcUp
|
||||
}
|
||||
if (data.length === 1) {
|
||||
const {startTime, endTime} = data[0];
|
||||
const {startTime, endTime} = data;
|
||||
if (!startTime || !endTime) {
|
||||
this.tableData = []
|
||||
} else {
|
||||
@ -316,15 +242,15 @@ export default {
|
||||
cancelAddTime() {
|
||||
this.$refs.addTimeForm.resetFields()
|
||||
this.showAddTime = false
|
||||
this.formInline = {timeRange: this.secondRange, chargeDischargePower: '', sdcDown: '', sdcUp: '', chargeStatus: ''}//startTime: '', endTime: '',
|
||||
this.formInline = {timeRange: this.secondRange, chargeDischargePower: '', chargeStatus: ''}//startTime: '', endTime: '',
|
||||
},
|
||||
saveTime() {
|
||||
//表单校验,校验成功,添加到tableData里
|
||||
this.$refs.addTimeForm.validate(valid => {
|
||||
if (!valid) return
|
||||
const {timeRange: [startTime, endTime], chargeDischargePower, sdcDown, sdcUp, chargeStatus} = this.formInline
|
||||
const {timeRange: [startTime, endTime], chargeDischargePower, chargeStatus} = this.formInline
|
||||
|
||||
this.tableData.push({startTime, endTime, chargeDischargePower, sdcDown, sdcUp, chargeStatus})
|
||||
this.tableData.push({startTime, endTime, chargeDischargePower, chargeStatus})
|
||||
this.$nextTick(() => {
|
||||
this.cancelAddTime()
|
||||
})
|
||||
@ -336,14 +262,9 @@ export default {
|
||||
saveDialog() {
|
||||
this.$refs.addTempForm.validate(valid => {
|
||||
if (!valid) return
|
||||
const {templateName, sdcLimit} = this.formData
|
||||
const {templateName, sdcLimit, sdcDown, sdcUp} = 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
|
||||
const {tableData} = this
|
||||
if (this.mode === 'edit') {
|
||||
editStrategyTemp({
|
||||
siteId,
|
||||
@ -351,6 +272,8 @@ export default {
|
||||
templateId: this.editTempId,
|
||||
templateName,
|
||||
sdcLimit,
|
||||
sdcDown,
|
||||
sdcUp,
|
||||
timeConfigList: tableData
|
||||
}).then(response => {
|
||||
if (response?.code === 200) {
|
||||
@ -365,6 +288,8 @@ export default {
|
||||
strategyId: updateStrategyId,
|
||||
templateName,
|
||||
sdcLimit,
|
||||
sdcDown,
|
||||
sdcUp,
|
||||
timeConfigList: tableData
|
||||
}).then(response => {
|
||||
if (response?.code === 200) {
|
||||
@ -375,64 +300,14 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
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 = {
|
||||
templateName: '',
|
||||
sdcLimit: 0,
|
||||
sdcDown: '',
|
||||
sdcUp: '',
|
||||
}
|
||||
this.tableData = []
|
||||
this.cancelAddTime()
|
||||
@ -447,4 +322,4 @@ export default {
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@ -39,14 +39,14 @@
|
||||
prop="sdcDown"
|
||||
label="SOC下限">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row.sdcDown === null || scope.row.sdcDown === undefined || scope.row.sdcDown === '' ? '-' : scope.row.sdcDown + '%'}}
|
||||
{{scope.row.sdcDown ? scope.row. sdcDown + '%' : '-'}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="sdcUp"
|
||||
label="SOC上限">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row.sdcUp === null || scope.row.sdcUp === undefined || scope.row.sdcUp === '' ? '-' : scope.row.sdcUp + '%'}}
|
||||
{{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']
|
||||
mixinPrototype:['templateName','sdcLimit','sdcDown','sdcUp']
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
|
||||
@ -1,30 +1,27 @@
|
||||
|
||||
<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="正在加载数据" 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 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>
|
||||
</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>
|
||||
@ -39,9 +36,7 @@
|
||||
<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>
|
||||
@ -52,77 +47,64 @@
|
||||
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="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>
|
||||
width="250"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" v-if="scope.row.ticketNo" @click="toTicket">已生成工单(工单号:{{scope.row.ticketNo}})</el-button>
|
||||
<el-button type="primary" size="mini" v-else @click="createTicket(scope.row.id)">生成工单</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-show="tableData.length>0"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 40]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="totalSize"
|
||||
style="margin-top:15px;text-align: center"
|
||||
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>
|
||||
@ -131,147 +113,117 @@
|
||||
|
||||
|
||||
<script>
|
||||
import {closeAlarm, createTicketNo, getAlarmDetailList} from '@/api/ems/dzjk'
|
||||
import {getDeviceList} from '@/api/ems/site'
|
||||
import {getAlarmDetailList,createTicketNo} 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) {
|
||||
createTicket(id){
|
||||
this.loading = true
|
||||
createTicketNo({id}).then(response => {
|
||||
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(() => {
|
||||
})
|
||||
}).finally(()=>{this.loading = false})
|
||||
},
|
||||
// 判断是否是同一天
|
||||
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开始搜索
|
||||
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;
|
||||
this.activeBtn=id;
|
||||
this.getData()
|
||||
}
|
||||
},
|
||||
// 获取数据
|
||||
getData() {
|
||||
this.$store.dispatch('getSiteAlarmNum', this.siteId)
|
||||
this.loading = true
|
||||
const {deviceId, alarmLevel} = this.search
|
||||
const {siteId, pageNum, pageSize, activeBtn} = this
|
||||
const [alarmStartTime = '', alarmEndTime = ''] = (this.dateRange || [])
|
||||
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 || [];
|
||||
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>
|
||||
|
||||
|
||||
@ -4,9 +4,7 @@
|
||||
<span class="card-title">当日功率曲线</span>
|
||||
<date-range-select ref="dateRangeSelect" :showIcon="true" :mini-time-picker="true" @updateDate="updateDate"/>
|
||||
</div>
|
||||
<div class="card-main">
|
||||
<div id="activeChart" class="active-chart-canvas"></div>
|
||||
</div>
|
||||
<div style="height: 310px" id="activeChart"></div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@ -14,18 +12,12 @@
|
||||
import * as echarts from 'echarts'
|
||||
import resize from '@/mixins/ems/resize'
|
||||
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue'
|
||||
import {getPointConfigCurve} from '@/api/ems/site'
|
||||
import {getPointData} from '@/api/ems/dzjk'
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
|
||||
export default {
|
||||
mixins: [resize, intervalUpdate],
|
||||
components: {DateRangeSelect},
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
@ -34,13 +26,6 @@ export default {
|
||||
isInit: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
displayData() {
|
||||
if (this.siteId && this.timeRange.length === 2) {
|
||||
this.getGVQXData()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart()
|
||||
@ -61,34 +46,11 @@ export default {
|
||||
this.isInit = false
|
||||
},
|
||||
getGVQXData() {
|
||||
this.showLoading()
|
||||
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))
|
||||
})
|
||||
getPointData({siteId, startDate: timeRange[0], endDate: timeRange[1]}).then(response => {
|
||||
this.setOption(response?.data || [])
|
||||
}).finally(() => this.hideLoading())
|
||||
},
|
||||
init(siteId) {
|
||||
//初始化 清空数据
|
||||
@ -102,18 +64,18 @@ export default {
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.querySelector('#activeChart'))
|
||||
},
|
||||
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'}`
|
||||
showLoading() {
|
||||
this.chart && this.chart.showLoading()
|
||||
},
|
||||
parseToTimestamp(value) {
|
||||
if (!value) return null
|
||||
const t = new Date(value).getTime()
|
||||
return Number.isNaN(t) ? null : t
|
||||
hideLoading() {
|
||||
this.chart && this.chart.hideLoading()
|
||||
},
|
||||
setOption(seriesData = []) {
|
||||
setOption(data) {
|
||||
const source = [['日期', '电网功率', '负载功率', '储能功率', '光伏功率', 'soc平均值', 'soh平均值', '电池平均温度平均值']]
|
||||
console.log('source.slice(1)', source[0].slice(1))
|
||||
this.chart && data.forEach((item) => {
|
||||
source.push([item.statisDate, item.gridPower, item.loadPower, item.storagePower, item.pvPower, item.avgSoc, item.avgSoh, item.avgTemp])
|
||||
})
|
||||
this.chart.setOption({
|
||||
grid: {
|
||||
containLabel: true
|
||||
@ -124,28 +86,35 @@ export default {
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross' }
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
}],
|
||||
series: seriesData.map((item) => {
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
dataset: {source},
|
||||
series: source[0].slice(1).map((item, index) => {
|
||||
return {
|
||||
name: item.name,
|
||||
type: 'line',
|
||||
type: 'line',//index === 5 ? 'bar' : 'line',
|
||||
showSymbol: false,
|
||||
symbolSize: 2,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
data: item.data
|
||||
yAxisIndex: index <= 4 ? 0 : 1
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -156,12 +125,3 @@ export default {
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card-main {
|
||||
padding: 0 16px 12px;
|
||||
}
|
||||
|
||||
.active-chart-canvas {
|
||||
height: 310px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
<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">{{ cardTitle }}</span>
|
||||
<date-range-select ref="dateRangeSelect" :showIcon="true" :mini-time-picker="true" @updateDate="updateDate" />
|
||||
</div>
|
||||
<div class="card-main">
|
||||
<div ref="weekChartRef" class="week-chart-canvas"></div>
|
||||
<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>
|
||||
|
||||
@ -14,51 +12,16 @@
|
||||
import * as echarts from 'echarts'
|
||||
import resize from '@/mixins/ems/resize'
|
||||
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue'
|
||||
import { getPointConfigCurve } from '@/api/ems/site'
|
||||
|
||||
const DAY = 24 * 60 * 60 * 1000
|
||||
const TEXT = {
|
||||
cardTitle: '\u4e00\u5468\u5145\u653e\u67f1\u72b6\u56fe',
|
||||
sectionName: '\u4e00\u5468\u5145\u653e\u66f2\u7ebf',
|
||||
empty: '\u6682\u65e0\u6570\u636e',
|
||||
date: '\u65e5\u671f',
|
||||
charge: '\u5145\u7535\u91cf',
|
||||
discharge: '\u653e\u7535\u91cf',
|
||||
yAxis: '\u5145\u7535\u91cf/\u653e\u7535\u91cf\uff08kWh\uff09',
|
||||
xAxis: '\u5355\u4f4d\uff1a\u65e5'
|
||||
}
|
||||
|
||||
function createEmptySummary() {
|
||||
return {
|
||||
totalChargedCap: '',
|
||||
totalDisChargedCap: '',
|
||||
efficiency: ''
|
||||
}
|
||||
}
|
||||
import {getSevenChargeData} from '@/api/ems/dzjk'
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
components: { DateRangeSelect },
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
components: {DateRangeSelect},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
timeRange: [],
|
||||
siteId: '',
|
||||
summary: createEmptySummary(),
|
||||
cardTitle: TEXT.cardTitle
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
displayData() {
|
||||
if (this.siteId && this.timeRange.length === 2) {
|
||||
this.getWeekKData()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -74,298 +37,84 @@ export default {
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
// 更新时间范围 重置图表
|
||||
updateDate(data) {
|
||||
this.timeRange = data
|
||||
this.getWeekKData()
|
||||
},
|
||||
getWeekKData() {
|
||||
const { siteId, timeRange } = this
|
||||
const displayData = this.displayData || []
|
||||
const sectionRows = displayData.filter(item =>
|
||||
item && item.sectionName === TEXT.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,
|
||||
fieldCode: row.fieldCode || '',
|
||||
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))
|
||||
})
|
||||
this.showLoading()
|
||||
const {siteId, timeRange} = this
|
||||
getSevenChargeData({siteId, startDate: timeRange[0], endDate: timeRange[1]}).then(response => {
|
||||
this.setOption(response?.data || [])
|
||||
}).finally(() => this.hideLoading())
|
||||
},
|
||||
init(siteId) {
|
||||
//初始化 清空数据
|
||||
this.siteId = siteId
|
||||
this.timeRange = []
|
||||
this.summary = createEmptySummary()
|
||||
this.deviceId = ''
|
||||
this.$refs.dateRangeSelect.init()
|
||||
},
|
||||
initChart() {
|
||||
this.chart = echarts.init(this.$refs.weekChartRef)
|
||||
this.chart = echarts.init(document.querySelector('#weekChart'))
|
||||
},
|
||||
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'}`
|
||||
showLoading() {
|
||||
this.chart && this.chart.showLoading()
|
||||
},
|
||||
parseToTimestamp(value) {
|
||||
if (!value) return null
|
||||
const t = new Date(value).getTime()
|
||||
return Number.isNaN(t) ? null : t
|
||||
hideLoading() {
|
||||
this.chart && this.chart.hideLoading()
|
||||
},
|
||||
startOfDay(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
date.setHours(0, 0, 0, 0)
|
||||
return date.getTime()
|
||||
},
|
||||
formatNumber(value) {
|
||||
const num = Number(value)
|
||||
if (Number.isNaN(num)) return '--'
|
||||
return num.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2
|
||||
setOption(data, unit) {
|
||||
const source = [['日期', '充电量', '放电量']]
|
||||
data.forEach(item => {
|
||||
source.push([item.ammeterDate, item.chargedCap, item.disChargedCap])
|
||||
})
|
||||
},
|
||||
formatDateLabel(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${month}-${day}`
|
||||
},
|
||||
formatTooltipDate(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
},
|
||||
buildDatasetSource(labels = [], chargeData = [], dischargeData = []) {
|
||||
const source = [[TEXT.date, TEXT.charge, TEXT.discharge]]
|
||||
labels.forEach((label, index) => {
|
||||
source.push([
|
||||
label,
|
||||
Number(chargeData[index]?.value || 0),
|
||||
Number(dischargeData[index]?.value || 0)
|
||||
])
|
||||
})
|
||||
return source
|
||||
},
|
||||
resolveSeriesType(item = {}) {
|
||||
const text = `${item?.name || ''} ${item?.fieldCode || ''}`.toLowerCase()
|
||||
if (text.includes('\u653e') || text.includes('discharge') || text.includes('discharged')) {
|
||||
return 'discharge'
|
||||
}
|
||||
if (text.includes('\u5145') || text.includes('charge') || text.includes('charged')) {
|
||||
return 'charge'
|
||||
}
|
||||
return ''
|
||||
},
|
||||
buildDailyChartData(seriesData = []) {
|
||||
const normalizedRange = this.timeRange || []
|
||||
const startTime = this.parseToTimestamp(this.normalizeDateTime(normalizedRange[0], false))
|
||||
const endTime = this.parseToTimestamp(this.normalizeDateTime(normalizedRange[1], true))
|
||||
if (!startTime || !endTime || endTime < startTime) {
|
||||
return {
|
||||
labels: [],
|
||||
chargeData: [],
|
||||
dischargeData: [],
|
||||
summary: createEmptySummary()
|
||||
}
|
||||
}
|
||||
|
||||
const bucketStarts = []
|
||||
for (let cursor = this.startOfDay(startTime); cursor <= this.startOfDay(endTime); cursor += DAY) {
|
||||
bucketStarts.push(cursor)
|
||||
}
|
||||
|
||||
const chargeMap = {}
|
||||
const dischargeMap = {}
|
||||
|
||||
;(seriesData || []).forEach(item => {
|
||||
const seriesType = this.resolveSeriesType(item)
|
||||
if (!seriesType) return
|
||||
|
||||
;(item?.data || []).forEach(([timestamp, pointValue]) => {
|
||||
if (!timestamp || Number.isNaN(pointValue)) return
|
||||
if (timestamp < startTime || timestamp > endTime) return
|
||||
|
||||
const bucketStart = this.startOfDay(timestamp)
|
||||
const normalizedValue = Math.abs(Number(pointValue) || 0)
|
||||
if (seriesType === 'charge') {
|
||||
chargeMap[bucketStart] = (chargeMap[bucketStart] || 0) + normalizedValue
|
||||
} else if (seriesType === 'discharge') {
|
||||
dischargeMap[bucketStart] = (dischargeMap[bucketStart] || 0) + normalizedValue
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const labels = []
|
||||
const chargeData = []
|
||||
const dischargeData = []
|
||||
|
||||
bucketStarts.forEach(bucketStart => {
|
||||
const chargedCap = Number(chargeMap[bucketStart] || 0)
|
||||
const disChargedCap = Number(dischargeMap[bucketStart] || 0)
|
||||
|
||||
labels.push(this.formatDateLabel(bucketStart))
|
||||
chargeData.push({
|
||||
value: chargedCap,
|
||||
bucketStart
|
||||
})
|
||||
dischargeData.push({
|
||||
value: disChargedCap,
|
||||
bucketStart
|
||||
})
|
||||
})
|
||||
|
||||
const totalChargedCap = chargeData.reduce((sum, item) => sum + Number(item.value || 0), 0)
|
||||
const totalDisChargedCap = dischargeData.reduce((sum, item) => sum + Number(item.value || 0), 0)
|
||||
const efficiency = totalChargedCap > 0
|
||||
? Number(((totalDisChargedCap / totalChargedCap) * 100).toFixed(2))
|
||||
: 0
|
||||
|
||||
return {
|
||||
labels,
|
||||
chargeData,
|
||||
dischargeData,
|
||||
summary: {
|
||||
totalChargedCap,
|
||||
totalDisChargedCap,
|
||||
efficiency
|
||||
}
|
||||
}
|
||||
},
|
||||
renderEmptyState(message = TEXT.empty) {
|
||||
if (!this.chart) return
|
||||
this.chart.clear()
|
||||
this.chart.setOption({
|
||||
graphic: {
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'middle',
|
||||
style: {
|
||||
text: message,
|
||||
fill: '#909399',
|
||||
fontSize: 14
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
setOption(seriesData = []) {
|
||||
if (!this.chart) return
|
||||
|
||||
const { labels, chargeData, dischargeData, summary } = this.buildDailyChartData(seriesData)
|
||||
const hasValue = chargeData.some(item => Number(item?.value || 0) > 0) || dischargeData.some(item => Number(item?.value || 0) > 0)
|
||||
|
||||
if (!labels.length || !hasValue) {
|
||||
this.summary = createEmptySummary()
|
||||
this.renderEmptyState()
|
||||
return
|
||||
}
|
||||
|
||||
this.summary = summary
|
||||
const source = this.buildDatasetSource(labels, chargeData, dischargeData)
|
||||
|
||||
this.chart.clear()
|
||||
this.chart.setOption({
|
||||
color: ['#4472c4', '#70ad47'],
|
||||
this.chart && this.chart.setOption({
|
||||
color: ['#4472c4', '#70ad47'],//所有充放电颜色保持统一
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
formatter: (params = []) => {
|
||||
if (!params.length) return ''
|
||||
const dataIndex = Number(params[0]?.dataIndex)
|
||||
const bucketStart = chargeData[dataIndex]?.bucketStart
|
||||
const lines = [this.formatTooltipDate(bucketStart)]
|
||||
params.forEach(item => {
|
||||
const rawValue = Array.isArray(item?.value) ? item.value[item.seriesIndex + 1] : item?.value
|
||||
const value = Number(rawValue || 0)
|
||||
lines.push(`${item.marker}${item.seriesName}: ${this.formatNumber(value)}kWh`)
|
||||
})
|
||||
return lines.join('<br/>')
|
||||
},
|
||||
extraCssText: 'max-width: 420px; white-space: normal;'
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
containLabel: true
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: 15
|
||||
bottom: '15',
|
||||
},
|
||||
grid: {
|
||||
top: 40,
|
||||
containLabel: true,
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 60
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
name: unit,
|
||||
nameLocation: 'center'
|
||||
},
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
name: TEXT.yAxis,
|
||||
name: '充电量/放电量kWh',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#333333'
|
||||
color: '#333333',
|
||||
},
|
||||
onZero: false
|
||||
}
|
||||
}],
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
name: TEXT.xAxis,
|
||||
nameLocation: 'center',
|
||||
nameGap: 30,
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
}],
|
||||
dataset: {
|
||||
source
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: TEXT.charge,
|
||||
yAxisIndex: 0,
|
||||
type: 'bar',
|
||||
color: '#4472c4',
|
||||
barMaxWidth: 22
|
||||
},
|
||||
{
|
||||
name: TEXT.discharge,
|
||||
yAxisIndex: 0,
|
||||
type: 'bar',
|
||||
color: '#70ad47',
|
||||
barMaxWidth: 22
|
||||
}
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card-main {
|
||||
padding: 0 16px 12px;
|
||||
}
|
||||
|
||||
.week-chart-canvas {
|
||||
height: 310px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-loading="loading">
|
||||
<el-row style="background: #fff" class="row-container" :gutter="15">
|
||||
<el-col :xs="24" :sm="24" :lg="5">
|
||||
<!-- 站点信息-->
|
||||
@ -21,19 +21,13 @@
|
||||
<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 class="value">{{ info.siteAddress }}</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 class="value">{{ info.runningTime || '-' }}</div>
|
||||
</div>
|
||||
<!-- 装机功率、容量 -->
|
||||
<el-row :gutter="10" style="margin-top:20px;">
|
||||
@ -42,10 +36,9 @@
|
||||
class="sjgl-col power-col"
|
||||
>
|
||||
<div class="sjgl-wrapper">
|
||||
<div class="sjgl-title">装机功率(MWh)</div>
|
||||
<div class="sjgl-title">装机功率(MW)</div>
|
||||
<div class="sjgl-value">
|
||||
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
|
||||
<span v-else>{{ info.installPower | formatNumber }}</span>
|
||||
{{ info.installPower | formatNumber }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
@ -54,10 +47,9 @@
|
||||
class="sjgl-col power-col"
|
||||
>
|
||||
<div class="sjgl-wrapper">
|
||||
<div class="sjgl-title">装机容量(MWh)</div>
|
||||
<div class="sjgl-title">装机容量(MW)</div>
|
||||
<div class="sjgl-value">
|
||||
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
|
||||
<span v-else>{{ info.installCapacity | formatNumber }}</span>
|
||||
{{ info.installCapacity | formatNumber }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
@ -74,15 +66,8 @@
|
||||
<div slot="header">
|
||||
<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="title">总收入</span>
|
||||
<span class="value">{{ runningInfo.totalRevenue | formatNumber }}</span>
|
||||
<span class="unit">元</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -92,19 +77,14 @@
|
||||
<el-row :gutter="10">
|
||||
<el-col
|
||||
:span="6"
|
||||
v-for="(item, index) in runningDataCards"
|
||||
v-for="(item, index) in sjglData"
|
||||
:key="index + 'sjglData'"
|
||||
class="sjgl-col"
|
||||
>
|
||||
<div
|
||||
class="sjgl-wrapper pointer-field"
|
||||
:class="{ 'field-disabled': !hasPointId(item.raw) }"
|
||||
@click="handleRunningFieldClick(item)"
|
||||
>
|
||||
<div class="sjgl-wrapper">
|
||||
<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>
|
||||
{{ runningInfo[item.attr] | formatNumber }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
@ -113,46 +93,18 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="12">
|
||||
<week-chart ref="weekChart" :display-data="runningDisplayData"/>
|
||||
<week-chart ref="weekChart"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="12">
|
||||
<active-chart ref="activeChart" :display-data="runningDisplayData"/>
|
||||
<active-chart ref="activeChart"/>
|
||||
</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 * as echarts from "echarts";
|
||||
import {getSingleSiteBaseInfo} from "@/api/ems/zddt";
|
||||
import {getAmmeterData, getDzjkHomeTotalView, getProjectDisplayData} from "@/api/ems/dzjk";
|
||||
import {getPointConfigCurve} from "@/api/ems/site";
|
||||
import {getDzjkHomeView} from "@/api/ems/dzjk";
|
||||
import WeekChart from "./WeekChart.vue";
|
||||
import ActiveChart from "./ActiveChart.vue";
|
||||
import AlarmTable from "./AlarmTable.vue";
|
||||
@ -167,24 +119,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
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: [
|
||||
sjglData: [
|
||||
{
|
||||
title: "今日充电量(kWh)",
|
||||
attr: "dayChargedCap",
|
||||
@ -228,372 +163,14 @@ export default {
|
||||
],
|
||||
info: {}, //基本信息
|
||||
runningInfo: {}, //总累计运行数据+报警表格
|
||||
runningDisplayData: [], //单站监控项目配置展示数据
|
||||
ammeterDailySummary: {},
|
||||
};
|
||||
},
|
||||
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: this.getRunningCardValue(item),
|
||||
color: this.getCardColor(index),
|
||||
loading: this.isRunningInfoLoading,
|
||||
raw: item,
|
||||
}));
|
||||
}
|
||||
return this.fallbackSjglData.map(item => ({
|
||||
title: item.title,
|
||||
value: this.getRunningCardValue(item),
|
||||
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;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hasPointId(item) {
|
||||
return !!String(item?.dataPoint || "").trim();
|
||||
},
|
||||
handleTotalRevenueClick() {
|
||||
const item = this.totalRevenueDisplayItem;
|
||||
const pointId = String(item?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.$message.warning("该字段未配置点位,无法查询曲线");
|
||||
return;
|
||||
}
|
||||
this.openCurveDialog({
|
||||
pointId,
|
||||
title: item?.fieldName || "总收入",
|
||||
});
|
||||
},
|
||||
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];
|
||||
},
|
||||
formatDateOnly(date) {
|
||||
const value = new Date(date);
|
||||
if (isNaN(value.getTime())) {
|
||||
return "";
|
||||
}
|
||||
const pad = (num) => String(num).padStart(2, "0");
|
||||
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
|
||||
},
|
||||
normalizeDateOnly(value) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
const raw = String(value).trim();
|
||||
const matched = raw.match(/\d{4}-\d{2}-\d{2}/);
|
||||
if (matched) {
|
||||
return matched[0];
|
||||
}
|
||||
return this.formatDateOnly(raw);
|
||||
},
|
||||
toDisplayNumber(value) {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return value;
|
||||
}
|
||||
const num = Number(value);
|
||||
return Number.isNaN(num) ? value : num;
|
||||
},
|
||||
getAmmeterSummaryAttr(item) {
|
||||
const fieldCode = String(item?.fieldCode || item?.attr || "").trim();
|
||||
const fieldName = String(item?.fieldName || item?.title || "").trim();
|
||||
if (["dayChargedCap", "今日充电量(kWh)"].includes(fieldCode) || fieldName === "今日充电量(kWh)") {
|
||||
return "dayChargedCap";
|
||||
}
|
||||
if (["dayDisChargedCap", "今日放电量(kWh)"].includes(fieldCode) || fieldName === "今日放电量(kWh)") {
|
||||
return "dayDisChargedCap";
|
||||
}
|
||||
if (["yesterdayChargedCap", "昨日充电量(kWh)"].includes(fieldCode) || fieldName === "昨日充电量(kWh)") {
|
||||
return "yesterdayChargedCap";
|
||||
}
|
||||
if (["yesterdayDisChargedCap", "昨日放电量(kWh)"].includes(fieldCode) || fieldName === "昨日放电量(kWh)") {
|
||||
return "yesterdayDisChargedCap";
|
||||
}
|
||||
if (["totalChargedCap", "总充电量(kWh)"].includes(fieldCode) || fieldName === "总充电量(kWh)") {
|
||||
return "totalChargedCap";
|
||||
}
|
||||
if (["totalDischargedCap", "总放电量(kWh)"].includes(fieldCode) || fieldName === "总放电量(kWh)") {
|
||||
return "totalDischargedCap";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
getRunningCardValue(item) {
|
||||
const summaryAttr = this.getAmmeterSummaryAttr(item);
|
||||
if (summaryAttr) {
|
||||
const summaryValue = this.ammeterDailySummary?.[summaryAttr];
|
||||
if (summaryValue !== undefined && summaryValue !== null && summaryValue !== "") {
|
||||
return summaryValue;
|
||||
}
|
||||
}
|
||||
const rawValue = item?.fieldValue !== undefined ? item.fieldValue : this.runningInfo?.[item?.attr];
|
||||
return this.toDisplayNumber(rawValue);
|
||||
},
|
||||
queryAllAmmeterDailyRows({ startTime = "", endTime = "", pageSize = 500, pageNum = 1, rows = [] } = {}) {
|
||||
return getAmmeterData({
|
||||
siteId: this.siteId,
|
||||
startTime,
|
||||
endTime,
|
||||
pageSize,
|
||||
pageNum,
|
||||
}).then((response) => {
|
||||
const currentRows = Array.isArray(response?.rows) ? response.rows : [];
|
||||
const allRows = rows.concat(currentRows);
|
||||
const total = Number(response?.total) || 0;
|
||||
if (allRows.length >= total || currentRows.length < pageSize) {
|
||||
return allRows;
|
||||
}
|
||||
return this.queryAllAmmeterDailyRows({
|
||||
startTime,
|
||||
endTime,
|
||||
pageSize,
|
||||
pageNum: pageNum + 1,
|
||||
rows: allRows,
|
||||
});
|
||||
});
|
||||
},
|
||||
getAmmeterDailySummary() {
|
||||
const today = new Date();
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const startTime = this.formatDateOnly(yesterday);
|
||||
const endTime = this.formatDateOnly(today);
|
||||
return Promise.all([
|
||||
this.queryAllAmmeterDailyRows({
|
||||
startTime,
|
||||
endTime,
|
||||
pageSize: 20,
|
||||
pageNum: 1,
|
||||
}),
|
||||
this.queryAllAmmeterDailyRows(),
|
||||
]).then(([recentRows, allRows]) => {
|
||||
const rowMap = recentRows.reduce((result, row) => {
|
||||
const dateKey = this.normalizeDateOnly(row?.dataTime);
|
||||
if (dateKey) {
|
||||
result[dateKey] = row;
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
const todayKey = this.formatDateOnly(today);
|
||||
const yesterdayKey = this.formatDateOnly(yesterday);
|
||||
const todayRow = rowMap[todayKey] || {};
|
||||
const yesterdayRow = rowMap[yesterdayKey] || {};
|
||||
const totalChargedCap = allRows.reduce((result, row) => {
|
||||
return result + (Number(row?.activeTotalKwh) || 0);
|
||||
}, 0);
|
||||
const totalDischargedCap = allRows.reduce((result, row) => {
|
||||
return result + (Number(row?.reActiveTotalKwh) || 0);
|
||||
}, 0);
|
||||
return {
|
||||
dayChargedCap: this.toDisplayNumber(todayRow.activeTotalKwh),
|
||||
dayDisChargedCap: this.toDisplayNumber(todayRow.reActiveTotalKwh),
|
||||
yesterdayChargedCap: this.toDisplayNumber(yesterdayRow.activeTotalKwh),
|
||||
yesterdayDisChargedCap: this.toDisplayNumber(yesterdayRow.reActiveTotalKwh),
|
||||
totalChargedCap: this.toDisplayNumber(totalChargedCap),
|
||||
totalDischargedCap: this.toDisplayNumber(totalDischargedCap),
|
||||
};
|
||||
}).catch(() => ({}));
|
||||
},
|
||||
toAlarm() {
|
||||
this.$router.push({path: "/dzjk/gzgj", query: this.$route.query});
|
||||
},
|
||||
@ -603,84 +180,21 @@ export default {
|
||||
});
|
||||
},
|
||||
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),
|
||||
this.getAmmeterDailySummary(),
|
||||
]).then(([homeResponse, displayResponse, ammeterDailySummary]) => {
|
||||
const nextRunningInfo = homeResponse?.data || {};
|
||||
const nextRunningDisplayData = displayResponse?.data || [];
|
||||
const changed = hasOldData && this.hasTotalRunningChanged(nextRunningInfo, nextRunningDisplayData, ammeterDailySummary || {});
|
||||
this.runningInfo = nextRunningInfo;
|
||||
this.runningDisplayData = nextRunningDisplayData;
|
||||
this.ammeterDailySummary = ammeterDailySummary || {};
|
||||
if (changed) {
|
||||
this.triggerRunningUpdateSpinner();
|
||||
}
|
||||
}).finally(() => {
|
||||
if (!hasOldData) {
|
||||
this.setRunningInfoLoading(false);
|
||||
}
|
||||
return getDzjkHomeView(this.siteId).then((response) => {
|
||||
this.runningInfo = response?.data || {};
|
||||
});
|
||||
},
|
||||
triggerRunningUpdateSpinner() {
|
||||
if (this.runningUpdateTimer) {
|
||||
clearTimeout(this.runningUpdateTimer);
|
||||
}
|
||||
this.runningUpdateSpinning = true;
|
||||
this.runningUpdateTimer = setTimeout(() => {
|
||||
this.runningUpdateSpinning = false;
|
||||
this.runningUpdateTimer = null;
|
||||
}, 800);
|
||||
},
|
||||
hasTotalRunningChanged(nextRunningInfo, nextRunningDisplayData, nextAmmeterDailySummary = {}) {
|
||||
const oldSnapshot = this.getTotalRunningSnapshot(this.runningInfo, this.runningDisplayData, this.ammeterDailySummary || {});
|
||||
const newSnapshot = this.getTotalRunningSnapshot(nextRunningInfo, nextRunningDisplayData, nextAmmeterDailySummary);
|
||||
return JSON.stringify(oldSnapshot) !== JSON.stringify(newSnapshot);
|
||||
},
|
||||
getTotalRunningSnapshot(runningInfo, runningDisplayData, ammeterDailySummary = {}) {
|
||||
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;
|
||||
const summaryAttr = this.getAmmeterSummaryAttr(item);
|
||||
const value = summaryAttr ? ammeterDailySummary?.[summaryAttr] : item.fieldValue;
|
||||
snapshot[`cfg:${key}`] = this.normalizeRunningCompareValue(value);
|
||||
});
|
||||
return snapshot;
|
||||
}
|
||||
this.fallbackSjglData.forEach(item => {
|
||||
const summaryAttr = this.getAmmeterSummaryAttr(item);
|
||||
const value = summaryAttr
|
||||
? ammeterDailySummary?.[summaryAttr]
|
||||
: runningInfo?.[item.attr];
|
||||
snapshot[`fallback:${item.attr}`] = this.normalizeRunningCompareValue(value);
|
||||
});
|
||||
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.loading = true;
|
||||
// 功率曲线
|
||||
this.$refs.activeChart.init(this.siteId);
|
||||
// 一周冲放曲线
|
||||
this.$refs.weekChart.init(this.siteId);
|
||||
// 静态信息 this.getBaseInfo()
|
||||
// 总累计运行数据+故障告警 this.getRunningInfo()
|
||||
Promise.all([this.getBaseInfo(), this.getRunningInfo()]);
|
||||
Promise.all([this.getBaseInfo(), this.getRunningInfo()]).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
// 一分钟循环一次总累计运行数据
|
||||
this.updateInterval(this.getRunningInfo);
|
||||
},
|
||||
@ -756,22 +270,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
<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"
|
||||
@ -27,8 +28,11 @@
|
||||
<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,
|
||||
@ -40,6 +44,18 @@ export default {
|
||||
dzjkAlarmLighting:state=>state.ems.dzjkAlarmLighting
|
||||
})
|
||||
},
|
||||
methods:{
|
||||
submitSite(id){
|
||||
if(id !== this.$route.query.siteId){
|
||||
// console.log('单站监控选择了其他的站点id=',id,'并更新页面地址参数')
|
||||
this.$router.push({query:{...this.$route.query,siteId:id}})
|
||||
}else{
|
||||
// console.log('单站监控选择了相同的其他的站点id=',id,'页面地址不发生改变')
|
||||
}
|
||||
//获取告警列表数据
|
||||
this.$store.dispatch('getSiteAlarmNum',id)
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to,from, next){
|
||||
//从单站监控下面的所有子页面跳出时会触发
|
||||
// 清空store中的zdList 保障下次进入到单站监控会重新调用接口获取数据
|
||||
@ -50,9 +66,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ems-dashboard-editor-container{
|
||||
padding-top: 12px;
|
||||
}
|
||||
.dzjk-ems-content-container{
|
||||
margin-top:0;
|
||||
min-height: 60vh;
|
||||
|
||||
@ -2,14 +2,8 @@
|
||||
<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 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 :xs="12" :sm="8" :lg="4" style="margin-bottom: 10px;" class="single-square-box-container" v-for="(item,index) in singleZdSqaure" :key="index+'singleSquareBox'">
|
||||
<single-square-box :data="{...item,value:formatNumber(data[item.attr])}" ></single-square-box>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
@ -17,74 +11,52 @@
|
||||
|
||||
<script>
|
||||
import SingleSquareBox from "@/components/Ems/SingleSquareBox/index.vue";
|
||||
import {formatNumber} from '@/filters/ems'
|
||||
export default {
|
||||
components:{SingleSquareBox},
|
||||
props:{
|
||||
displayData: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
data:{
|
||||
type:Object,
|
||||
required:false,
|
||||
default:()=>{return {}}
|
||||
},
|
||||
},
|
||||
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 === "-";
|
||||
},
|
||||
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'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
@ -98,13 +70,4 @@ export default {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
.square-click-wrapper {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.square-click-wrapper.field-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,28 +1,6 @@
|
||||
<template>
|
||||
<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;">
|
||||
<div v-loading="loading">
|
||||
<div v-for="(baseInfo,index) in baseInfoList" :key="index+'bmsdccContainer'" style="margin-bottom:25px;">
|
||||
<el-card shadow="always"
|
||||
class="sbjk-card-container common-card-container-body-no-padding common-card-container-no-title-bg"
|
||||
:class="handleCardClass(baseInfo)">
|
||||
@ -51,33 +29,15 @@
|
||||
<el-descriptions-item
|
||||
contentClassName="descriptions-direction work-status"
|
||||
:span="1" label="工作状态">
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'workStatus') }"
|
||||
@click="handleFieldClick(baseInfo, 'workStatus', '工作状态')"
|
||||
>
|
||||
{{ formatDictValue(clusterWorkStatusOptions, baseInfo.workStatus) }}
|
||||
</span>
|
||||
{{ CLUSTERWorkStatusOptions[baseInfo.workStatus] }}
|
||||
</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通信')"
|
||||
>
|
||||
{{ formatDictValue(clusterPcsCommunicationStatusOptions, baseInfo.pcsCommunicationStatus) }}
|
||||
</span>
|
||||
{{ $store.state.ems.communicationStatusOptions[baseInfo.pcsCommunicationStatus] }}
|
||||
</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通信')"
|
||||
>
|
||||
{{ formatDictValue(clusterEmsCommunicationStatusOptions, baseInfo.emsCommunicationStatus) }}
|
||||
</span>
|
||||
{{ $store.state.ems.communicationStatusOptions[baseInfo.emsCommunicationStatus] }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@ -86,14 +46,8 @@
|
||||
<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 class="pointer" @click="showChart(item.pointName || '',baseInfo.deviceId)">
|
||||
{{ baseInfo[item.attr] | formatNumber }} <span v-if="item.unit" v-html="item.unit"></span>
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
@ -102,11 +56,7 @@
|
||||
<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 :
|
||||
<div class="process pointer" @click="showChart( '当前SOC',baseInfo.deviceId)">当前SOC :
|
||||
{{ baseInfo.currentSoc }}%
|
||||
</div>
|
||||
</div>
|
||||
@ -129,8 +79,7 @@
|
||||
>
|
||||
<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)">{{
|
||||
@click="showChart( tablePointNameMap[scope.row.dataName+scope.column.label],baseInfo.deviceId)">{{
|
||||
scope.row.avgData
|
||||
}}</span>
|
||||
</template>
|
||||
@ -140,8 +89,7 @@
|
||||
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)">{{
|
||||
@click="showChart( tablePointNameMap[scope.row.dataName+scope.column.label],baseInfo.deviceId)">{{
|
||||
scope.row.minData
|
||||
}}</span>
|
||||
</template>
|
||||
@ -155,8 +103,7 @@
|
||||
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)">{{
|
||||
@click="showChart( tablePointNameMap[scope.row.dataName+scope.column.label],baseInfo.deviceId)">{{
|
||||
scope.row.maxData
|
||||
}}</span>
|
||||
</template>
|
||||
@ -168,89 +115,33 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-empty v-show="baseInfoList.length<=0" :image-size="200"></el-empty>
|
||||
<point-chart ref="pointChart" :site-id="siteId"/>
|
||||
<point-table ref="pointTable"/>
|
||||
<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 pointChart from "./../PointChart.vue";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
import {getProjectDisplayData, getStackNameList, getClusterNameList} from '@/api/ems/dzjk'
|
||||
import {getBMSBatteryCluster} from '@/api/ems/dzjk'
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import {mapState} from "vuex";
|
||||
import {getPointConfigCurve, getSingleMonitorWorkStatusEnumMappings} from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
name: 'DzjkSbjkBmsdcc',
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
components: {PointTable},
|
||||
components: {PointTable, pointChart},
|
||||
computed: {
|
||||
...mapState({
|
||||
CLUSTERWorkStatusOptions: state => state?.ems?.CLUSTERWorkStatusOptions || {},
|
||||
}),
|
||||
clusterWorkStatusOptions() {
|
||||
return this.getEnumOptions("CLUSTER", "workStatus", this.CLUSTERWorkStatusOptions || {});
|
||||
},
|
||||
clusterPcsCommunicationStatusOptions() {
|
||||
return this.getEnumOptions("CLUSTER", "pcsCommunicationStatus", (this.$store.state.ems && this.$store.state.ems.communicationStatusOptions) || {});
|
||||
},
|
||||
clusterEmsCommunicationStatusOptions() {
|
||||
return this.getEnumOptions("CLUSTER", "emsCommunicationStatus", (this.$store.state.ems && this.$store.state.ems.communicationStatusOptions) || {});
|
||||
},
|
||||
filteredBaseInfoList() {
|
||||
if (!this.selectedClusterId) {
|
||||
return this.baseInfoList || [];
|
||||
}
|
||||
return (this.baseInfoList || []).filter(item => item.deviceId === this.selectedClusterId);
|
||||
},
|
||||
})
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
displayData: [],
|
||||
clusterDeviceList: [],
|
||||
siteEnumOptionMap: {},
|
||||
selectedClusterId: "",
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
unitObj: {
|
||||
'电压': 'V',
|
||||
'温度': '℃',
|
||||
@ -267,15 +158,7 @@ export default {
|
||||
'SOC单体平均值': '当前SOC',
|
||||
'SOC单体最大值': '最高单体SOC',
|
||||
},
|
||||
baseInfoList: [{
|
||||
siteId: "",
|
||||
deviceId: "",
|
||||
parentDeviceName: "",
|
||||
deviceName: "BMS电池簇",
|
||||
dataUpdateTime: "-",
|
||||
alarmNum: 0,
|
||||
batteryDataList: [],
|
||||
}],
|
||||
baseInfoList: [],
|
||||
infoData: [
|
||||
{label: '簇电压', attr: 'clusterVoltage', unit: 'V', pointName: '簇电压'},
|
||||
{label: '可充电量', attr: 'chargeableCapacity', unit: 'kWh', pointName: '可充电量'},
|
||||
@ -290,414 +173,35 @@ export default {
|
||||
}
|
||||
},
|
||||
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;
|
||||
},
|
||||
buildEnumScopeKey(deviceCategory, matchField) {
|
||||
return `${String(deviceCategory || "").trim()}|${String(matchField || "").trim()}`;
|
||||
},
|
||||
buildSiteEnumOptionMap(mappings = []) {
|
||||
return (mappings || []).reduce((acc, item) => {
|
||||
const scopeKey = this.buildEnumScopeKey(item?.deviceCategory, item?.matchField);
|
||||
const dataEnumCode = this.normalizeDictKey(item?.dataEnumCode);
|
||||
const enumCode = this.normalizeDictKey(item?.enumCode);
|
||||
const enumName = String(item?.enumName || "").trim();
|
||||
const optionKey = dataEnumCode || enumCode;
|
||||
if (!scopeKey || !optionKey || !enumName) {
|
||||
return acc;
|
||||
}
|
||||
if (!acc[scopeKey]) {
|
||||
acc[scopeKey] = {};
|
||||
}
|
||||
acc[scopeKey][optionKey] = enumName;
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
loadSiteEnumOptions() {
|
||||
if (!this.siteId) {
|
||||
this.siteEnumOptionMap = {};
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return getSingleMonitorWorkStatusEnumMappings(this.siteId).then(response => {
|
||||
const optionMap = this.buildSiteEnumOptionMap(response?.data || []);
|
||||
this.siteEnumOptionMap = optionMap;
|
||||
return optionMap;
|
||||
}).catch(() => {
|
||||
this.siteEnumOptionMap = {};
|
||||
return {};
|
||||
});
|
||||
},
|
||||
getEnumOptions(deviceCategory, matchField, fallback = {}) {
|
||||
const scopeKey = this.buildEnumScopeKey(deviceCategory, matchField);
|
||||
const siteOptions = this.siteEnumOptionMap[scopeKey];
|
||||
if (siteOptions && Object.keys(siteOptions).length > 0) {
|
||||
return siteOptions;
|
||||
}
|
||||
return fallback || {};
|
||||
},
|
||||
handleCardClass(item) {
|
||||
const workStatus = this.normalizeDictKey((item && item.workStatus) || "");
|
||||
const statusOptions = (this.clusterWorkStatusOptions && typeof this.clusterWorkStatusOptions === "object")
|
||||
? this.clusterWorkStatusOptions
|
||||
: {};
|
||||
const hasStatus = Object.prototype.hasOwnProperty.call(statusOptions, workStatus);
|
||||
return !hasStatus ? "timing-card-container" : workStatus === '9' ? 'warning-card-container' : 'running-card-container';
|
||||
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();
|
||||
showChart(pointName, deviceId) {
|
||||
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory: 'CLUSTER', deviceId})
|
||||
},
|
||||
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([]);
|
||||
updateData() {
|
||||
this.loading = true
|
||||
getBMSBatteryCluster(this.siteId).then(response => {
|
||||
this.baseInfoList = JSON.parse(JSON.stringify(response?.data || []));
|
||||
}).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 || "";
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
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
|
||||
// 先渲染卡片框架,字段值走单点位 loading
|
||||
this.buildBaseInfoList();
|
||||
Promise.all([
|
||||
getProjectDisplayData(this.siteId),
|
||||
this.getClusterDeviceList(),
|
||||
this.loadSiteEnumOptions(),
|
||||
]).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 {
|
||||
@ -743,16 +247,4 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.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,28 +1,6 @@
|
||||
<template>
|
||||
<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;">
|
||||
<div v-loading="loading">
|
||||
<div v-for="(baseInfo,index) in baseInfoList" :key="index+'bmszlContainer'" style="margin-bottom:25px;">
|
||||
<el-card
|
||||
:class="handleCardClass(baseInfo)"
|
||||
class="sbjk-card-container common-card-container-body-no-padding common-card-container-no-title-bg"
|
||||
@ -49,21 +27,15 @@
|
||||
<el-descriptions-item
|
||||
contentClassName="descriptions-direction work-status"
|
||||
label="工作状态" labelClassName="descriptions-label">
|
||||
<span class="pointer" @click="handleStatusFieldClick(baseInfo, 'workStatus', '工作状态')">
|
||||
{{ formatDictValue(stackWorkStatusOptions, baseInfo.workStatus) }}
|
||||
</span>
|
||||
{{ STACKWorkStatusOptions[baseInfo.workStatus] }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" contentClassName="descriptions-direction" label="与PCS通信"
|
||||
labelClassName="descriptions-label">
|
||||
<span class="pointer" @click="handleStatusFieldClick(baseInfo, 'pcsCommunicationStatus', '与PCS通信')">
|
||||
{{ formatDictValue(stackPcsCommunicationStatusOptions, baseInfo.pcsCommunicationStatus) }}
|
||||
</span>
|
||||
{{ $store.state.ems.communicationStatusOptions[baseInfo.pcsCommunicationStatus] }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" contentClassName="descriptions-direction" label="与EMS通信"
|
||||
labelClassName="descriptions-label">
|
||||
<span class="pointer" @click="handleStatusFieldClick(baseInfo, 'emsCommunicationStatus', '与EMS通信')">
|
||||
{{ formatDictValue(stackEmsCommunicationStatusOptions, baseInfo.emsCommunicationStatus) }}
|
||||
</span>
|
||||
{{ $store.state.ems.communicationStatusOptions[baseInfo.emsCommunicationStatus] }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@ -72,10 +44,8 @@
|
||||
<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 class="pointer" @click="showChart(item.pointName || '',baseInfo.deviceId)">
|
||||
{{ baseInfo[item.attr] | formatNumber }}<span v-if="item.unit" v-html="item.unit"></span>
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
@ -84,186 +54,146 @@
|
||||
<div class="process-line-bg">
|
||||
<div :style="{height:baseInfo.stackSoc+'%'}" class="process-line"></div>
|
||||
</div>
|
||||
<div class="process pointer" @click="handleStackSocClick(baseInfo)">当前SOC :
|
||||
<div class="process pointer" @click="showChart('当前SOC',baseInfo.deviceId)">当前SOC :
|
||||
{{ baseInfo.stackSoc }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
:data="baseInfo.batteryDataList"
|
||||
class="common-table"
|
||||
max-height="500"
|
||||
stripe
|
||||
style="width: 100%;margin-top:25px;">
|
||||
<el-table-column
|
||||
label="簇号"
|
||||
prop="clusterId">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="簇电压"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span class="pointer"
|
||||
@click="showChart('簇电压',scope.row.clusterId,'CLUSTER')">{{ scope.row.clusterVoltage }} V</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="簇电流">
|
||||
<template slot-scope="scope">
|
||||
<span class="pointer"
|
||||
@click="showChart('簇电流',scope.row.clusterId,'CLUSTER')">{{ scope.row.clusterCurrent }} A</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="簇SOC">
|
||||
<template slot-scope="scope">
|
||||
<span class="pointer"
|
||||
@click="showChart('当前SOC',scope.row.clusterId,'CLUSTER')">{{ scope.row.currentSoc }} %</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="单体最高电压"
|
||||
prop="maxVoltage">
|
||||
<template slot-scope="scope">
|
||||
<span class="pointer"
|
||||
@click="showChart('最高单体电压',scope.row.clusterId,'CLUSTER')">{{
|
||||
scope.row.maxCellVoltage
|
||||
}} V</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="电池号码"
|
||||
prop="maxCellVoltageId">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="单体最低电压"
|
||||
prop="minVoltage">
|
||||
<template slot-scope="scope">
|
||||
<span class="pointer"
|
||||
@click="showChart('最低单体电压',scope.row.clusterId,'CLUSTER')">{{
|
||||
scope.row.minCellVoltage
|
||||
}} V</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="电池号码"
|
||||
prop="minCellVoltageId">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="单体最高温度">
|
||||
<template slot-scope="scope">
|
||||
<span class="pointer"
|
||||
@click="showChart('最高单体温度',scope.row.clusterId,'CLUSTER')">{{
|
||||
scope.row.maxCellTemp
|
||||
}} ℃</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="电池号码"
|
||||
prop="maxCellTempId">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="单体最低温度"
|
||||
prop="minTemperature">
|
||||
<template slot-scope="scope">
|
||||
<span class="pointer"
|
||||
@click="showChart('最低单体温度',scope.row.clusterId,'CLUSTER')">{{
|
||||
scope.row.minCellTemp
|
||||
}} ℃</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="电池号码"
|
||||
prop="minCellTempId">
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
<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-empty v-show="baseInfoList.length<=0" :image-size="200"></el-empty>
|
||||
<point-chart ref="pointChart" :site-id="siteId"/>
|
||||
<point-table ref="pointTable"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import {getProjectDisplayData, getStackNameList} from '@/api/ems/dzjk'
|
||||
import {getPointConfigCurve, getSingleMonitorWorkStatusEnumMappings} from "@/api/ems/site";
|
||||
import {getBMSOverView} from '@/api/ems/dzjk'
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import pointChart from "./../PointChart.vue";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'DzjkSbjkBmszl',
|
||||
components: {PointTable},
|
||||
components: {pointChart, PointTable},
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
computed: {
|
||||
...mapState({
|
||||
STACKWorkStatusOptions: state => state?.ems?.STACKWorkStatusOptions || {},
|
||||
}),
|
||||
stackWorkStatusOptions() {
|
||||
return this.getEnumOptions("STACK", "workStatus", this.STACKWorkStatusOptions || {});
|
||||
},
|
||||
stackPcsCommunicationStatusOptions() {
|
||||
return this.getEnumOptions("STACK", "pcsCommunicationStatus", (this.$store.state.ems && this.$store.state.ems.communicationStatusOptions) || {});
|
||||
},
|
||||
stackEmsCommunicationStatusOptions() {
|
||||
return this.getEnumOptions("STACK", "emsCommunicationStatus", (this.$store.state.ems && this.$store.state.ems.communicationStatusOptions) || {});
|
||||
},
|
||||
filteredBaseInfoList() {
|
||||
if (!this.selectedStackId) {
|
||||
return this.baseInfoList || [];
|
||||
}
|
||||
return (this.baseInfoList || []).filter(item => item.deviceId === this.selectedStackId);
|
||||
},
|
||||
})
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
displayData: [],
|
||||
stackDeviceList: [],
|
||||
siteEnumOptionMap: {},
|
||||
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: [],
|
||||
}],
|
||||
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: 'Ω'},
|
||||
{label: '电池堆总电压', attr: 'stackVoltage', unit: 'V', pointName: '电池堆电压'},
|
||||
{label: '可充电量', attr: 'availableChargeCapacity', unit: 'kWh', pointName: '可充电量'},
|
||||
{label: '累计充电量', attr: 'totalChargeCapacity', unit: 'kWh', pointName: '累计充电量'},
|
||||
{label: '电池堆总电流', attr: 'stackCurrent', unit: 'A', pointName: '电池堆总电流'},
|
||||
{label: '可放电量', attr: 'availableDischargeCapacity', unit: 'kWh', pointName: '可放电量'},
|
||||
{label: '累计放电量', attr: 'totalDischargeCapacity', unit: 'kWh', pointName: '累计放电量'},
|
||||
{label: 'SOH', attr: 'stackSoh', unit: '%', pointName: 'SOH'},
|
||||
{label: '平均温度', attr: 'operatingTemp', unit: '℃', pointName: '平均温度'},
|
||||
{label: '绝缘电阻', attr: 'stackInsulationResistance', unit: 'Ω', pointName: '绝缘电阻'},
|
||||
]
|
||||
}
|
||||
},
|
||||
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;
|
||||
},
|
||||
buildEnumScopeKey(deviceCategory, matchField) {
|
||||
return `${String(deviceCategory || "").trim()}|${String(matchField || "").trim()}`;
|
||||
},
|
||||
buildSiteEnumOptionMap(mappings = []) {
|
||||
return (mappings || []).reduce((acc, item) => {
|
||||
const scopeKey = this.buildEnumScopeKey(item?.deviceCategory, item?.matchField);
|
||||
const dataEnumCode = this.normalizeDictKey(item?.dataEnumCode);
|
||||
const enumCode = this.normalizeDictKey(item?.enumCode);
|
||||
const enumName = String(item?.enumName || "").trim();
|
||||
const optionKey = dataEnumCode || enumCode;
|
||||
if (!scopeKey || !optionKey || !enumName) {
|
||||
return acc;
|
||||
}
|
||||
if (!acc[scopeKey]) {
|
||||
acc[scopeKey] = {};
|
||||
}
|
||||
acc[scopeKey][optionKey] = enumName;
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
loadSiteEnumOptions() {
|
||||
if (!this.siteId) {
|
||||
this.siteEnumOptionMap = {};
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return getSingleMonitorWorkStatusEnumMappings(this.siteId).then(response => {
|
||||
const optionMap = this.buildSiteEnumOptionMap(response?.data || []);
|
||||
this.siteEnumOptionMap = optionMap;
|
||||
return optionMap;
|
||||
}).catch(() => {
|
||||
this.siteEnumOptionMap = {};
|
||||
return {};
|
||||
});
|
||||
},
|
||||
getEnumOptions(deviceCategory, matchField, fallback = {}) {
|
||||
const scopeKey = this.buildEnumScopeKey(deviceCategory, matchField);
|
||||
const siteOptions = this.siteEnumOptionMap[scopeKey];
|
||||
if (siteOptions && Object.keys(siteOptions).length > 0) {
|
||||
return siteOptions;
|
||||
}
|
||||
return fallback || {};
|
||||
},
|
||||
handleCardClass(item) {
|
||||
const workStatus = this.normalizeDictKey((item && item.workStatus) || "");
|
||||
const statusOptions = (this.stackWorkStatusOptions && typeof this.stackWorkStatusOptions === "object")
|
||||
? this.stackWorkStatusOptions
|
||||
: {};
|
||||
const hasStatus = Object.prototype.hasOwnProperty.call(statusOptions, workStatus);
|
||||
return !hasStatus ? "timing-card-container" : workStatus === '9' ? 'warning-card-container' : 'running-card-container';
|
||||
const {workStatus = ''} = item
|
||||
return !Object.keys(this.STACKWorkStatusOptions).find(i => i === workStatus) ? "timing-card-container" : workStatus === '9' ? 'warning-card-container' : 'running-card-container'
|
||||
},
|
||||
|
||||
// 查看设备电位表格
|
||||
@ -271,333 +201,27 @@ export default {
|
||||
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: [],
|
||||
}));
|
||||
showChart(pointName, deviceId, deviceCategory = 'STACK') {
|
||||
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory, deviceId})
|
||||
},
|
||||
updateData() {
|
||||
this.loading = true
|
||||
// 先渲染卡片框架,字段值走单点位 loading
|
||||
this.buildBaseInfoList();
|
||||
Promise.all([
|
||||
getProjectDisplayData(this.siteId),
|
||||
this.getStackDeviceList(),
|
||||
this.loadSiteEnumOptions(),
|
||||
]).then(([displayResponse]) => {
|
||||
this.displayData = displayResponse?.data || [];
|
||||
this.buildBaseInfoList();
|
||||
getBMSOverView(this.siteId).then(response => {
|
||||
this.baseInfoList = JSON.parse(JSON.stringify(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 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 {
|
||||
@ -643,16 +267,4 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.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,436 +1,228 @@
|
||||
<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 + '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>
|
||||
<div v-loading="loading">
|
||||
<el-card
|
||||
v-for="(group, index) in filteredSectionGroups"
|
||||
:key="index + 'dbSection'"
|
||||
class="sbjk-card-container list running-card-container"
|
||||
shadow="always"
|
||||
v-for="(item,index) in list"
|
||||
:key="index+'dbList'"
|
||||
shadow="always"
|
||||
class="sbjk-card-container list"
|
||||
:class="{
|
||||
'timing-card-container':!['0','2'].includes(item.emsCommunicationStatus),
|
||||
'warning-card-container':item.emsCommunicationStatus === '2',
|
||||
'running-card-container':item.emsCommunicationStatus === '0'
|
||||
}"
|
||||
>
|
||||
<div slot="header">
|
||||
<span class="large-title">{{ group.displayName || "电表" }}</span>
|
||||
<span class="large-title">{{ item.deviceName }}</span>
|
||||
<div class="info">
|
||||
<div>状态:{{ group.statusText }}</div>
|
||||
<div>数据更新时间:{{ group.updateTimeText }}</div>
|
||||
<div>
|
||||
{{
|
||||
communicationStatusOptions[item.emsCommunicationStatus] || '-'
|
||||
}}
|
||||
</div>
|
||||
<div>数据更新时间:{{ item.dataUpdateTime || '-' }}</div>
|
||||
</div>
|
||||
<div class="alarm">
|
||||
<el-button type="primary" round size="small" style="margin-right:20px;" @click="pointDetail(item,'point')">
|
||||
详细
|
||||
</el-button>
|
||||
<el-badge :hidden="!item.alarmNum" :value="item.alarmNum || 0" class="item">
|
||||
<i
|
||||
class="el-icon-message-solid alarm-icon"
|
||||
@click="pointDetail(item,'alarmPoint')"
|
||||
></i>
|
||||
</el-badge>
|
||||
</div>
|
||||
</div>
|
||||
<el-row class="device-info-row">
|
||||
<el-col
|
||||
v-for="(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>
|
||||
<el-col v-for="(tempDataItem,tempDataIndex) in (deviceIdTypeMsg[item.deviceId] || otherTypeMsg)"
|
||||
:key="tempDataIndex+'dbTempData'"
|
||||
:span="8" class="device-info-col">
|
||||
<span class="pointer" @click="showChart(tempDataItem.pointName,item.deviceId)">
|
||||
<span class="left">{{ tempDataItem.name }}</span> <span class="right">{{ item[tempDataItem.attr] || '-' }}<span
|
||||
v-html="tempDataItem.unit"></span></span>
|
||||
</span>
|
||||
</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>
|
||||
<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 * as echarts from "echarts";
|
||||
import pointChart from "./../PointChart.vue";
|
||||
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";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
name: "DzjkSbjkDb",
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
components: {PointTable, pointChart},
|
||||
computed: {
|
||||
|
||||
...mapState({
|
||||
communicationStatusOptions: state => state?.ems?.communicationStatusOptions || {},
|
||||
})
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
displayData: [],
|
||||
selectedSectionKey: "",
|
||||
ammeterDeviceList: [],
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
curveChart: null,
|
||||
curveLoading: false,
|
||||
curveCustomRange: [],
|
||||
curveQuery: {
|
||||
siteId: "",
|
||||
pointId: "",
|
||||
pointType: "data",
|
||||
rangeType: "custom",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
list: [],
|
||||
deviceIdTypeMsg: {
|
||||
'LOAD': [
|
||||
{
|
||||
name: '正向有功电能',
|
||||
attr: 'forwardActive',
|
||||
pointName: '正向有功电能',
|
||||
unit: 'kWh'
|
||||
},
|
||||
{
|
||||
name: '反向有功电能',
|
||||
attr: 'reverseActive',
|
||||
pointName: '反向有功电能',
|
||||
unit: 'kWh'
|
||||
},
|
||||
{
|
||||
name: '正向无功电能',
|
||||
attr: 'forwardReactive',
|
||||
pointName: '正向无功电能',
|
||||
unit: 'kvarh'
|
||||
},
|
||||
{
|
||||
name: '反向无功电能',
|
||||
attr: 'reverseReactive',
|
||||
pointName: '反向无功电能',
|
||||
unit: 'kvarh'
|
||||
},
|
||||
{
|
||||
name: '有功功率',
|
||||
attr: 'activePower',
|
||||
pointName: '总有功功率',
|
||||
unit: 'kW'
|
||||
},
|
||||
{
|
||||
name: '无功功率',
|
||||
attr: 'reactivePower',
|
||||
pointName: '总无功功率',
|
||||
unit: 'kvar'
|
||||
}
|
||||
],
|
||||
'METE': [
|
||||
{
|
||||
name: '正向有功电能',
|
||||
attr: 'forwardActive',
|
||||
pointName: '正向有功电能',
|
||||
unit: 'kWh'
|
||||
},
|
||||
{
|
||||
name: '反向有功电能',
|
||||
attr: 'reverseActive',
|
||||
pointName: '反向有功电能',
|
||||
unit: 'kWh'
|
||||
},
|
||||
{
|
||||
name: '正向无功电能',
|
||||
attr: 'forwardReactive',
|
||||
pointName: '正向无功电能',
|
||||
unit: 'kvarh'
|
||||
},
|
||||
{
|
||||
name: '反向无功电能',
|
||||
attr: 'reverseReactive',
|
||||
pointName: '反向无功电能',
|
||||
unit: 'kvarh'
|
||||
},
|
||||
{
|
||||
name: '有功功率',
|
||||
attr: 'activePower',
|
||||
pointName: '总有功功率',
|
||||
unit: 'kW'
|
||||
},
|
||||
{
|
||||
name: '无功功率',
|
||||
attr: 'reactivePower',
|
||||
pointName: '总无功功率',
|
||||
unit: 'kvar'
|
||||
}
|
||||
],
|
||||
'METEGF': [
|
||||
{
|
||||
name: '有功电能',
|
||||
attr: 'activeEnergy',
|
||||
pointName: '有功电能',
|
||||
unit: 'kWh'
|
||||
},
|
||||
{
|
||||
name: '无功电能',
|
||||
attr: 'reactiveEnergy',
|
||||
pointName: '无功电能',
|
||||
unit: 'kvarh'
|
||||
},
|
||||
{
|
||||
name: '有功功率',
|
||||
attr: 'activePower',
|
||||
pointName: '总有功功率',
|
||||
unit: 'kW'
|
||||
},
|
||||
{
|
||||
name: '无功功率',
|
||||
attr: 'reactivePower',
|
||||
pointName: '总无功功率',
|
||||
unit: 'kvar'
|
||||
}
|
||||
]
|
||||
},
|
||||
otherTypeMsg: [
|
||||
{
|
||||
name: '正向有功电能',
|
||||
attr: 'forwardActive',
|
||||
pointName: '正向有功电能',
|
||||
unit: 'kWh'
|
||||
},
|
||||
{
|
||||
name: '反向有功电能',
|
||||
attr: 'reverseActive',
|
||||
pointName: '反向有功电能',
|
||||
unit: 'kWh'
|
||||
},
|
||||
{
|
||||
name: '有功功率',
|
||||
attr: 'activePower',
|
||||
pointName: '总有功功率',
|
||||
unit: 'kW'
|
||||
},
|
||||
]
|
||||
};
|
||||
},
|
||||
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,
|
||||
});
|
||||
// 查看设备电位表格
|
||||
pointDetail(row, dataType) {
|
||||
const {deviceId} = row
|
||||
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'AMMETER'}, dataType)
|
||||
},
|
||||
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 = [];
|
||||
});
|
||||
showChart(pointName, deviceId) {
|
||||
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory: 'AMMETER', deviceId})
|
||||
},
|
||||
updateData() {
|
||||
this.loading = true;
|
||||
Promise.all([getProjectDisplayData(this.siteId), this.getAmmeterDeviceList()])
|
||||
.then(([response]) => {
|
||||
this.displayData = response?.data || [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
getAmmeterDataList(this.siteId)
|
||||
.then((response) => {
|
||||
this.list = response?.data || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.updateData();
|
||||
this.updateInterval(this.updateData);
|
||||
this.updateData()
|
||||
this.updateInterval(this.updateData)
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.curveChart) {
|
||||
this.curveChart.dispose();
|
||||
this.curveChart = null;
|
||||
}
|
||||
mounted() {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -440,58 +232,6 @@ export default {
|
||||
&.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,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-loading="loading">
|
||||
<el-card
|
||||
v-for="(item,index) in list"
|
||||
:key="index+'ylLise'"
|
||||
@ -26,11 +26,9 @@
|
||||
<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 class="left">{{ tempDataItem.title }}</span> <span
|
||||
class="right">{{ item[tempDataItem.attr] || '-' }}<span
|
||||
v-html="tempDataItem.unit"></span></span>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -64,12 +62,6 @@ export default {
|
||||
}
|
||||
},
|
||||
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
|
||||
@ -125,16 +117,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
|
||||
@ -15,10 +15,10 @@
|
||||
{{ item.clusterDeviceId }}
|
||||
</div>
|
||||
<div>#{{ item.deviceId }}</div>
|
||||
<div class="dy pointer" @click="chartDetail(item, 'voltage')">
|
||||
<div class="dy pointer" @click="chartDetail(item, '电压 (V)')">
|
||||
{{ item.voltage }}V
|
||||
</div>
|
||||
<div class="wd pointer" @click="chartDetail(item, 'temperature')">
|
||||
<div class="wd pointer" @click="chartDetail(item, '温度 (℃)')">
|
||||
{{ item.temperature }}℃
|
||||
</div>
|
||||
</div>
|
||||
@ -102,8 +102,9 @@ export default {
|
||||
return className;
|
||||
},
|
||||
//查看表格行图表
|
||||
chartDetail(row, fieldKey = "") {
|
||||
this.$emit("chart", { ...row, fieldKey });
|
||||
chartDetail(row, dataType = "") {
|
||||
const { clusterDeviceId, deviceId } = row;
|
||||
this.$emit("chart", { ...row, dataType });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<el-table-column prop="voltage" label="电压 (V)">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@click="chartDetail(scope.row, 'voltage')"
|
||||
@click="chartDetail(scope.row, '电压 (V)')"
|
||||
type="text"
|
||||
size="small"
|
||||
>
|
||||
@ -22,7 +22,7 @@
|
||||
<el-table-column prop="temperature" label="温度 (℃)">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@click="chartDetail(scope.row, 'temperature')"
|
||||
@click="chartDetail(scope.row, '温度 (℃)')"
|
||||
type="text"
|
||||
size="small"
|
||||
>
|
||||
@ -33,7 +33,7 @@
|
||||
<el-table-column prop="soc" label="SOC (%)">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@click="chartDetail(scope.row, 'soc')"
|
||||
@click="chartDetail(scope.row, 'SOC (%)')"
|
||||
type="text"
|
||||
size="small"
|
||||
>
|
||||
@ -44,7 +44,7 @@
|
||||
<el-table-column prop="soh" label="SOH (%)">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@click="chartDetail(scope.row, 'soh')"
|
||||
@click="chartDetail(scope.row, 'SOH (%)')"
|
||||
type="text"
|
||||
size="small"
|
||||
>
|
||||
@ -52,6 +52,16 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160">
|
||||
<template slot-scope="scope">
|
||||
<el-button @click="$emit('pointDetail',scope.row,'point')" type="text" size="small">
|
||||
详细
|
||||
</el-button>
|
||||
<el-button @click="$emit('pointDetail',scope.row,'alarmPoint')" type="text" size="small">
|
||||
报警点位详细
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- <el-pagination
|
||||
v-show="tableData.length > 0"
|
||||
@ -107,8 +117,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
//查看表格行图表
|
||||
chartDetail(row, fieldKey = "") {
|
||||
this.$emit("chart", {...row, fieldKey});
|
||||
chartDetail(row, dataType = "") {
|
||||
this.$emit("chart", {...row, dataType});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -88,6 +88,7 @@
|
||||
:totalSize="totalSize"
|
||||
:pointIdList="pointIdList"
|
||||
@chart="chartDetail"
|
||||
@pointDetail="pointDetail"
|
||||
></component>
|
||||
<el-pagination
|
||||
v-show="tableData.length > 0"
|
||||
@ -103,55 +104,34 @@
|
||||
>
|
||||
</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>
|
||||
<point-chart ref="pointChart" :site-id="siteId" />
|
||||
<point-table ref="pointTable"/>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import BarChart from "./BarChart";
|
||||
import {
|
||||
getClusterDataInfoList,
|
||||
getClusterNameList,
|
||||
getStackNameList,
|
||||
getClusterNameList,
|
||||
getClusterDataInfoList,
|
||||
} 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";
|
||||
import pointChart from "./../PointChart.vue";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
export default {
|
||||
name: "DzjkSbjkDtdc",
|
||||
mixins: [getQuerySiteId],
|
||||
components: {
|
||||
PointTable,
|
||||
BarChart,
|
||||
ChartDetail,
|
||||
DtdcTable: Table,
|
||||
DtdcList: List,
|
||||
pointChart,
|
||||
},
|
||||
computed: {
|
||||
pointIdList() {
|
||||
@ -186,190 +166,27 @@ export default {
|
||||
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: {
|
||||
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("当前时间范围暂无曲线数据");
|
||||
}
|
||||
// 查看设备电位表格
|
||||
pointDetail(row,dataType){
|
||||
const {deviceId,clusterDeviceId} = row
|
||||
this.$refs.pointTable.showTable({siteId:this.siteId,deviceId,deviceCategory:'BATTERY',parentId:clusterDeviceId},dataType)
|
||||
},
|
||||
changeMenu(menu) {
|
||||
const { activeBtn } = this;
|
||||
activeBtn !== menu && (this.activeBtn = menu);
|
||||
},
|
||||
//查看表格行图表
|
||||
chartDetail(row = {}) {
|
||||
const config = this.getFieldPointConfig(row.fieldKey);
|
||||
if (!config) return;
|
||||
const pointId = row[config.pointIdKey];
|
||||
this.openCurveDialogByPointId(pointId, config.title);
|
||||
chartDetail({ deviceId, clusterDeviceId, dataType = "" }) {
|
||||
dataType &&
|
||||
this.$refs.pointChart.showChart({
|
||||
pointName: dataType,
|
||||
deviceCategory:'BATTERY',
|
||||
deviceId: clusterDeviceId,
|
||||
child: [deviceId],
|
||||
});
|
||||
},
|
||||
// 分页
|
||||
handleSizeChange(val) {
|
||||
@ -407,9 +224,6 @@ export default {
|
||||
);
|
||||
this.search.clusterId = "";
|
||||
this.getClusterList();
|
||||
} else {
|
||||
this.search.clusterId = "";
|
||||
this.clusterOptions = [];
|
||||
}
|
||||
},
|
||||
//表格数据
|
||||
@ -440,26 +254,19 @@ export default {
|
||||
},
|
||||
getStackList() {
|
||||
getStackNameList(this.siteId).then((response) => {
|
||||
const list = JSON.parse(JSON.stringify(response?.data || []));
|
||||
this.stackOptions = list;
|
||||
this.stackOptions = JSON.parse(JSON.stringify(response?.data || []));
|
||||
});
|
||||
},
|
||||
getClusterList() {
|
||||
const { stackId } = this.search;
|
||||
if (!stackId) {
|
||||
this.clusterOptions = [];
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.clusterloading = true;
|
||||
const currentStackId = String(stackId);
|
||||
return getClusterNameList({
|
||||
stackDeviceId: stackId,
|
||||
getClusterNameList({
|
||||
stackDeviceId: this.search.stackId,
|
||||
siteId: this.siteId,
|
||||
})
|
||||
.then((response) => {
|
||||
// 避免用户快速切换电池堆时旧请求覆盖新数据
|
||||
if (String(this.search.stackId || "") !== currentStackId) return;
|
||||
this.clusterOptions = JSON.parse(JSON.stringify(response?.data || []));
|
||||
this.clusterOptions = JSON.parse(
|
||||
JSON.stringify(response?.data || [])
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
this.clusterloading = false;
|
||||
|
||||
@ -1,481 +1,133 @@
|
||||
<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>
|
||||
<div v-loading="loading" class="ems">
|
||||
<el-card
|
||||
v-for="(group, index) in filteredSectionGroups"
|
||||
:key="index + 'emsSection'"
|
||||
class="sbjk-card-container list running-card-container"
|
||||
shadow="always"
|
||||
v-for="(item,index) in list"
|
||||
:key="index+'emsList'"
|
||||
class="sbjk-card-container list running-card-container"
|
||||
shadow="always"
|
||||
>
|
||||
<div slot="header">
|
||||
<span class="large-title">{{ group.displayName || group.sectionName || "EMS" }}</span>
|
||||
<span class="large-title">{{ item.deviceName }}</span>
|
||||
<div class="info">
|
||||
<div>状态:{{ group.statusText }}</div>
|
||||
<div>数据更新时间:{{ group.updateTimeText }}</div>
|
||||
<div>
|
||||
EMS控制模式: {{
|
||||
item.emsStatus === 0 ? '自动' : '手动'
|
||||
}}
|
||||
</div>
|
||||
<div>数据更新时间:{{ item.dataUpdateTime }}</div>
|
||||
</div>
|
||||
<div class="alarm">
|
||||
<el-button size="small" round style="margin-right:20px;" type="primary" @click="pointDetail(item,'point')">详细
|
||||
</el-button>
|
||||
<el-badge :hidden="!item.alarmNum" :value="item.alarmNum || 0" class="item">
|
||||
<i
|
||||
class="el-icon-message-solid alarm-icon"
|
||||
@click="pointDetail(item,'alarmPoint')"
|
||||
></i>
|
||||
</el-badge>
|
||||
</div>
|
||||
</div>
|
||||
<el-row class="device-info-row">
|
||||
<el-col
|
||||
v-for="(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>
|
||||
<el-col v-for="(tempDataItem,tempDataIndex) in bmsDataList" :key="tempDataIndex+'bmsTempData'"
|
||||
:span="6" class="device-info-col">
|
||||
<span class="pointer" @click="showChart(tempDataItem.name,item.deviceId)">
|
||||
<span class="left">{{ tempDataItem.name }}</span> <span class="right">{{ item[tempDataItem.attr] }}<span
|
||||
v-html="tempDataItem.unit"></span></span>
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col v-for="(tempDataItem,tempDataIndex) in pcsDataList" :key="tempDataIndex+'pcsTempData'"
|
||||
:span="6" class="device-info-col">
|
||||
<span class="pointer" @click="showChart(tempDataItem.name,item.deviceId)">
|
||||
<span class="left">{{ tempDataItem.name }}</span> <span class="right">{{ item[tempDataItem.attr] }}<span
|
||||
v-html="tempDataItem.unit"></span></span>
|
||||
</span>
|
||||
</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>
|
||||
<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 * as echarts from "echarts";
|
||||
import pointChart from "./../PointChart.vue";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getEmsDataList} from "@/api/ems/dzjk";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import { getProjectDisplayData } from "@/api/ems/dzjk";
|
||||
import { getDeviceList, getPointConfigCurve } from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
name: "DzjkSbjkEms",
|
||||
components: {pointChart, PointTable},
|
||||
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: "",
|
||||
list: [],
|
||||
bmsDataList: [{
|
||||
name: 'BMS1SOC',
|
||||
attr: 'bms1Soc'
|
||||
},
|
||||
{
|
||||
name: 'BMS2SOC',
|
||||
attr: 'bms2Soc'
|
||||
},
|
||||
{
|
||||
name: 'BMS3SOC',
|
||||
attr: 'bms3Soc'
|
||||
},
|
||||
{
|
||||
name: 'BMS4SOC',
|
||||
attr: 'bms4Soc'
|
||||
}],
|
||||
pcsDataList: [{
|
||||
name: 'PCS-1有功功率',
|
||||
attr: 'pcs1Yggl'
|
||||
},
|
||||
{
|
||||
name: 'PCS-2有功功率',
|
||||
attr: 'pcs2Yggl'
|
||||
},
|
||||
{
|
||||
name: 'PCS-3有功功率',
|
||||
attr: 'pcs3Yggl'
|
||||
},
|
||||
{
|
||||
name: 'PCS-4有功功率',
|
||||
attr: 'pcs4Yggl'
|
||||
}]
|
||||
};
|
||||
},
|
||||
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,
|
||||
});
|
||||
// 查看设备电位表格
|
||||
pointDetail(row, dataType) {
|
||||
const {deviceId} = row
|
||||
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'EMS'}, dataType)
|
||||
},
|
||||
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;
|
||||
showChart(pointName, deviceId) {
|
||||
pointName &&
|
||||
this.$refs.pointChart.showChart({pointName, deviceCategory: 'EMS', deviceId});
|
||||
},
|
||||
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 = [];
|
||||
});
|
||||
getData() {
|
||||
this.loading = true;
|
||||
getEmsDataList(this.siteId)
|
||||
.then((response) => {
|
||||
const data = response?.data || {};
|
||||
this.list = JSON.parse(JSON.stringify(data));
|
||||
})
|
||||
.finally(() => (this.loading = false));
|
||||
},
|
||||
updateData() {
|
||||
this.loading = true;
|
||||
Promise.all([getProjectDisplayData(this.siteId), this.getEmsDeviceList()])
|
||||
.then(([response]) => {
|
||||
this.displayData = response?.data || [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
this.getData();
|
||||
},
|
||||
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>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -72,3 +72,4 @@ export default {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -1,29 +1,9 @@
|
||||
<template>
|
||||
<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-loading="loading" class="pcs-ems-dashboard-editor-container">
|
||||
<!-- 顶部六个方块-->
|
||||
<real-time-base-info :data="runningHeadData"/>
|
||||
<div
|
||||
v-for="(pcsItem, pcsIndex) in filteredPcsList"
|
||||
v-for="(pcsItem, pcsIndex) in pcsList"
|
||||
:key="pcsIndex + 'PcsHome'"
|
||||
style="margin-bottom: 25px"
|
||||
>
|
||||
@ -37,12 +17,20 @@
|
||||
>{{ 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>
|
||||
{{
|
||||
$store.state.ems.communicationStatusOptions[
|
||||
pcsItem.communicationStatus
|
||||
]
|
||||
}}
|
||||
</div>
|
||||
<div>数据更新时间:{{ pcsItem.dataUpdateTime }}</div>
|
||||
</div>
|
||||
<div class="alarm">
|
||||
<el-button type="primary" round size="small" style="margin-right:20px;"
|
||||
@click="pointDetail(pcsItem,'point')">
|
||||
详细
|
||||
</el-button>
|
||||
<el-badge :hidden="!pcsItem.alarmNum" :value="pcsItem.alarmNum || 0" class="item">
|
||||
<i
|
||||
class="el-icon-message-solid alarm-icon"
|
||||
@ -58,14 +46,9 @@
|
||||
:span="1"
|
||||
label="工作状态"
|
||||
labelClassName="descriptions-label"
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'workStatus') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'workStatus', '工作状态')"
|
||||
>
|
||||
{{ formatDictValue(pcsWorkStatusOptions, pcsItem.workStatus) }}
|
||||
</span>
|
||||
>{{
|
||||
PCSWorkStatusOptions[pcsItem.workStatus]
|
||||
}}
|
||||
</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
@ -73,14 +56,9 @@
|
||||
contentClassName="descriptions-direction"
|
||||
label="并网状态"
|
||||
labelClassName="descriptions-label"
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'gridStatus') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'gridStatus', '并网状态')"
|
||||
>
|
||||
{{ formatDictValue(pcsGridStatusOptions, pcsItem.gridStatus) }}
|
||||
</span>
|
||||
>{{
|
||||
$store.state.ems.gridStatusOptions[pcsItem.gridStatus]
|
||||
}}
|
||||
</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
@ -90,14 +68,9 @@
|
||||
:span="1"
|
||||
label="设备状态"
|
||||
labelClassName="descriptions-label"
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'deviceStatus') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'deviceStatus', '设备状态')"
|
||||
>
|
||||
{{ formatDictValue(pcsDeviceStatusOptions, pcsItem.deviceStatus) }}
|
||||
</span>
|
||||
>{{
|
||||
$store.state.ems.deviceStatusOptions[pcsItem.deviceStatus]
|
||||
}}
|
||||
</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
@ -105,14 +78,9 @@
|
||||
contentClassName="descriptions-direction"
|
||||
label="控制模式"
|
||||
labelClassName="descriptions-label"
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'controlMode') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'controlMode', '控制模式')"
|
||||
>
|
||||
{{ formatDictValue(pcsControlModeOptions, pcsItem.controlMode) }}
|
||||
</span>
|
||||
>{{
|
||||
$store.state.ems.controlModeOptions[pcsItem.controlMode]
|
||||
}}
|
||||
</el-descriptions-item
|
||||
>
|
||||
</el-descriptions>
|
||||
@ -133,11 +101,11 @@
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, item.attr) }"
|
||||
@click="handlePcsFieldClick(pcsItem, item.attr, item.label)"
|
||||
@click="
|
||||
showChart(item.pointName || '', pcsItem.deviceId)
|
||||
"
|
||||
>
|
||||
<i v-if="isPointLoading(pcsItem[item.attr])" class="el-icon-loading point-loading-icon"></i>
|
||||
<span v-else>{{ displayValue(pcsItem[item.attr]) | formatNumber }}</span>
|
||||
{{ pcsItem[item.attr] | formatNumber }}
|
||||
<span v-if="item.unit" v-html="item.unit"></span>
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
@ -171,8 +139,7 @@
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !item.dcPowerPointId }"
|
||||
@click="openCurveDialogByPointId(item.dcPowerPointId, '直流功率')"
|
||||
@click="showChart('直流功率', item.deviceId,true)"
|
||||
>{{ item.dcPower }}kW</span
|
||||
>
|
||||
</el-descriptions-item>
|
||||
@ -184,8 +151,7 @@
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !item.dcVoltagePointId }"
|
||||
@click="openCurveDialogByPointId(item.dcVoltagePointId, '直流电压')"
|
||||
@click="showChart('直流电压', item.deviceId,true)"
|
||||
>{{ item.dcVoltage }}V</span
|
||||
>
|
||||
</el-descriptions-item>
|
||||
@ -197,8 +163,7 @@
|
||||
>
|
||||
<span
|
||||
class="pointer"
|
||||
:class="{ 'field-disabled': !item.dcCurrentPointId }"
|
||||
@click="openCurveDialogByPointId(item.dcCurrentPointId, '直流电流')"
|
||||
@click="showChart('直流电流', item.deviceId,true)"
|
||||
>{{ item.dcCurrent }}A</span
|
||||
>
|
||||
</el-descriptions-item>
|
||||
@ -206,98 +171,35 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-empty v-show="pcsList.length <= 0" :image-size="200"></el-empty>
|
||||
<point-chart ref="pointChart" :site-id="siteId"/>
|
||||
<point-table ref="pointTable"/>
|
||||
<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 pointChart from "./../PointChart.vue";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
import RealTimeBaseInfo from "./../RealTimeBaseInfo.vue";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getPcsNameList, getProjectDisplayData} from "@/api/ems/dzjk";
|
||||
import {getPcsDetailInfo, getRunningHeadInfo} from "@/api/ems/dzjk";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import {mapState} from "vuex";
|
||||
import {getPointConfigCurve, getSingleMonitorWorkStatusEnumMappings} from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
name: "DzjkSbjkPcs",
|
||||
components: {PointTable},
|
||||
components: {RealTimeBaseInfo, pointChart, PointTable},
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
computed: {
|
||||
...mapState({
|
||||
PCSWorkStatusOptions: state => state?.ems?.PCSWorkStatusOptions || {},
|
||||
}),
|
||||
pcsWorkStatusOptions() {
|
||||
return this.getEnumOptions("PCS", "workStatus", this.PCSWorkStatusOptions || {});
|
||||
},
|
||||
pcsGridStatusOptions() {
|
||||
return this.getEnumOptions("PCS", "gridStatus", (this.$store.state.ems && this.$store.state.ems.gridStatusOptions) || {});
|
||||
},
|
||||
pcsDeviceStatusOptions() {
|
||||
return this.getEnumOptions("PCS", "deviceStatus", (this.$store.state.ems && this.$store.state.ems.deviceStatusOptions) || {});
|
||||
},
|
||||
pcsControlModeOptions() {
|
||||
return this.getEnumOptions("PCS", "controlMode", (this.$store.state.ems && this.$store.state.ems.controlModeOptions) || {});
|
||||
},
|
||||
filteredPcsList() {
|
||||
if (!this.selectedPcsId) {
|
||||
return this.pcsList || [];
|
||||
}
|
||||
return (this.pcsList || []).filter(item => item.deviceId === this.selectedPcsId);
|
||||
},
|
||||
})
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
displayData: [],
|
||||
pcsDeviceList: [],
|
||||
siteEnumOptionMap: {},
|
||||
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: [],
|
||||
}],
|
||||
runningHeadData: {}, //运行信息
|
||||
pcsList: [],
|
||||
infoData: [
|
||||
{
|
||||
label: "总交流有功功率",
|
||||
@ -375,391 +277,48 @@ export default {
|
||||
pointName: "交流频率",
|
||||
},
|
||||
],
|
||||
pcsBranchList: [], //pcs的支路列表
|
||||
};
|
||||
},
|
||||
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();
|
||||
},
|
||||
buildEnumScopeKey(deviceCategory, matchField) {
|
||||
return `${String(deviceCategory || "").trim()}|${String(matchField || "").trim()}`;
|
||||
},
|
||||
buildSiteEnumOptionMap(mappings = []) {
|
||||
return (mappings || []).reduce((acc, item) => {
|
||||
const scopeKey = this.buildEnumScopeKey(item?.deviceCategory, item?.matchField);
|
||||
const dataEnumCode = this.normalizeDictKey(item?.dataEnumCode);
|
||||
const enumCode = this.normalizeDictKey(item?.enumCode);
|
||||
const enumName = String(item?.enumName || "").trim();
|
||||
const optionKey = dataEnumCode || enumCode;
|
||||
if (!scopeKey || !optionKey || !enumName) {
|
||||
return acc;
|
||||
}
|
||||
if (!acc[scopeKey]) {
|
||||
acc[scopeKey] = {};
|
||||
}
|
||||
acc[scopeKey][optionKey] = enumName;
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
loadSiteEnumOptions() {
|
||||
if (!this.siteId) {
|
||||
this.siteEnumOptionMap = {};
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return getSingleMonitorWorkStatusEnumMappings(this.siteId).then(response => {
|
||||
const optionMap = this.buildSiteEnumOptionMap(response?.data || []);
|
||||
this.siteEnumOptionMap = optionMap;
|
||||
return optionMap;
|
||||
}).catch(() => {
|
||||
this.siteEnumOptionMap = {};
|
||||
return {};
|
||||
});
|
||||
},
|
||||
getEnumOptions(deviceCategory, matchField, fallback = {}) {
|
||||
const scopeKey = this.buildEnumScopeKey(deviceCategory, matchField);
|
||||
const siteOptions = this.siteEnumOptionMap[scopeKey];
|
||||
if (siteOptions && Object.keys(siteOptions).length > 0) {
|
||||
return siteOptions;
|
||||
}
|
||||
return fallback || {};
|
||||
},
|
||||
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';
|
||||
const {workStatus = ''} = item
|
||||
return workStatus === '1' || !Object.keys(this.PCSWorkStatusOptions).find(i => i === workStatus) ? "timing-card-container" : workStatus === '2' ? 'warning-card-container' : 'running-card-container'
|
||||
},
|
||||
// 查看设备电位表格
|
||||
pointDetail(row, dataType) {
|
||||
const {deviceId} = row
|
||||
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'PCS'}, dataType)
|
||||
},
|
||||
hasFieldPointId(pcsItem, fieldName) {
|
||||
const row = this.getFieldRow(pcsItem, fieldName);
|
||||
return !!String(row?.dataPoint || "").trim();
|
||||
showChart(pointName, deviceId, isBranch = false) {
|
||||
pointName &&
|
||||
this.$refs.pointChart.showChart({pointName, deviceCategory: isBranch ? 'BRANCH' : 'PCS', deviceId});
|
||||
},
|
||||
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;
|
||||
//6个方块数据
|
||||
getRunningHeadData() {
|
||||
getRunningHeadInfo(this.siteId).then((response) => {
|
||||
this.runningHeadData = response?.data || {};
|
||||
});
|
||||
},
|
||||
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;
|
||||
}
|
||||
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 = [];
|
||||
});
|
||||
},
|
||||
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(),
|
||||
this.loadSiteEnumOptions(),
|
||||
]).then(([displayResponse]) => {
|
||||
this.displayData = displayResponse?.data || [];
|
||||
this.buildPcsList();
|
||||
}).finally(() => (this.loading = false));
|
||||
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;
|
||||
}
|
||||
|
||||
.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>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<div slot="header">
|
||||
<span class="card-title">PCS有功功率/PCS无功功率</span>
|
||||
</div>
|
||||
<div ref="chartRef" style="height: 360px" />
|
||||
<div style="height: 360px" id="cnglqxChart"/>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@ -14,23 +14,17 @@
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import { getPointConfigCurve } from "@/api/ems/site";
|
||||
import {storagePower} from "@/api/ems/dzjk";
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.chart = echarts.init(this.$refs.chartRef);
|
||||
this.chart = echarts.init(document.querySelector("#cnglqxChart"));
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
@ -41,61 +35,56 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
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.chart.showLoading();
|
||||
const [startTime = '', endTime = ''] = timeRange;
|
||||
storagePower(siteId, startTime, endTime)
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
return {
|
||||
name: (row.deviceName || "") + (row.fieldName || row.fieldCode || pointId),
|
||||
data: list
|
||||
.map((item) => [
|
||||
this.parseToTimestamp(item.dataTime),
|
||||
Number(item.pointValue)
|
||||
])
|
||||
.filter((item) => item[0] && !Number.isNaN(item[1]))
|
||||
};
|
||||
this.setOption(response?.data?.pcsPowerList || []);
|
||||
})
|
||||
.catch(() => null);
|
||||
.finally(() => {
|
||||
this.chart.hideLoading();
|
||||
});
|
||||
},
|
||||
setOption(data) {
|
||||
let xdata = [],
|
||||
series = [];
|
||||
data.forEach((element, index) => {
|
||||
if (index === 0) {
|
||||
xdata = (element.energyStoragePowList || []).map((i) => i.createDate);
|
||||
}
|
||||
series.push(
|
||||
{
|
||||
type: "line",
|
||||
name: `${element.deviceId}有功功率`,
|
||||
areaStyle: {
|
||||
// color:'#FFBD00'
|
||||
},
|
||||
data: (element.energyStoragePowList || []).map(
|
||||
(i) => {
|
||||
return {
|
||||
value: i.pcsTotalActPower,
|
||||
year: i.dateDay || ''
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
{
|
||||
type: "line",
|
||||
name: `${element.deviceId}无功功率`,
|
||||
areaStyle: {
|
||||
// color:'#FFBD00'
|
||||
},
|
||||
data: (element.energyStoragePowList || []).map(
|
||||
(i) => {
|
||||
return {
|
||||
value: i.pcsTotalReactivePower,
|
||||
year: i.dateDay || ''
|
||||
}
|
||||
}
|
||||
),
|
||||
}
|
||||
);
|
||||
});
|
||||
Promise.all(tasks)
|
||||
.then((series) => {
|
||||
this.setOption((series || []).filter(Boolean));
|
||||
});
|
||||
},
|
||||
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",
|
||||
@ -113,13 +102,26 @@ export default {
|
||||
show: true,
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
// 坐标轴指示器,坐标轴触发有效
|
||||
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
|
||||
},
|
||||
formatter: (params) => {
|
||||
if (params.length <= 0) return
|
||||
let result = (params[0].data.year || '') + ' ' + params[0].name + '<div>'
|
||||
params.forEach(item => {
|
||||
const {color, seriesName, value} = item
|
||||
result += `<div style="position: relative;padding-left:20px;line-height: 20px;">
|
||||
<div style="position: absolute;top:50%;left:0;width:12px;height:12px;border-radius:100%;background: ${color};transform: translateY(-50%)"></div>
|
||||
<span>${seriesName}</span><span style="margin-left:20px;font-weight: 700">${value}</span></div>`
|
||||
})
|
||||
result += '</div>'
|
||||
return result
|
||||
}
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: { type: "time" },
|
||||
xAxis: {type: "category", data: xdata},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
@ -134,16 +136,7 @@ export default {
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: seriesData.map((item) => ({
|
||||
type: "line",
|
||||
name: item.name,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
},
|
||||
data: item.data
|
||||
})),
|
||||
series,
|
||||
}, true);
|
||||
},
|
||||
},
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<div slot="header">
|
||||
<span class="card-title">平均SOC</span>
|
||||
</div>
|
||||
<div ref="chartRef" style="height: 360px" />
|
||||
<div style="height: 360px" id="dcpjsocChart" />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@ -14,22 +14,16 @@
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import { getPointConfigCurve } from "@/api/ems/site";
|
||||
import { batteryAveSoc } from "@/api/ems/dzjk";
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.chart = echarts.init(this.$refs.chartRef);
|
||||
this.chart = echarts.init(document.querySelector("#dcpjsocChart"));
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
@ -40,52 +34,26 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
init(siteId,timeRange) {
|
||||
this.chart.showLoading();
|
||||
const [startTime='', endTime=''] = timeRange;
|
||||
const query = {
|
||||
siteId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
};
|
||||
const rows = (this.displayData || []).filter(
|
||||
(item) =>
|
||||
item &&
|
||||
item.fieldCode === "SBJK_SSYX__curveBatteryAveSoc" &&
|
||||
item.useFixedDisplay !== 1 &&
|
||||
item.dataPoint
|
||||
);
|
||||
const tasks = rows.map((row) => {
|
||||
const pointId = String(row.dataPoint || "").trim();
|
||||
if(!pointId) return Promise.resolve(null);
|
||||
return getPointConfigCurve({
|
||||
...query,
|
||||
pointId
|
||||
}).then((response) => {
|
||||
const list = response?.data || [];
|
||||
return {
|
||||
name: (row.deviceName || "") + (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);
|
||||
batteryAveSoc(siteId,startTime,endTime)
|
||||
.then((response) => {
|
||||
this.setOption(response?.data?.batteryAveSOCList || []);
|
||||
})
|
||||
.finally(() => {
|
||||
this.chart.hideLoading();
|
||||
});
|
||||
},
|
||||
setOption(data) {
|
||||
let xdata = [],
|
||||
ydata = [];
|
||||
data.forEach((element) => {
|
||||
xdata.push(element.createDate);
|
||||
ydata.push({
|
||||
value:element.batterySOC,
|
||||
year:element.dateDay,
|
||||
});
|
||||
});
|
||||
Promise.all(tasks).then((series) => {
|
||||
this.setOption((series || []).filter(Boolean));
|
||||
});
|
||||
},
|
||||
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",
|
||||
@ -103,13 +71,26 @@ export default {
|
||||
show:true,
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
// 坐标轴指示器,坐标轴触发有效
|
||||
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
|
||||
},
|
||||
formatter :(params)=>{
|
||||
if(params.length <= 0) return
|
||||
let result = (params[0].data.year || '')+' '+params[0].name + '<div>'
|
||||
params.forEach(item=>{
|
||||
const {color,seriesName,value} = item
|
||||
result += `<div style="position: relative;padding-left:20px;line-height: 20px;">
|
||||
<div style="position: absolute;top:50%;left:0;width:12px;height:12px;border-radius:100%;background: ${color};transform: translateY(-50%)"></div>
|
||||
<span>${seriesName}</span><span style="margin-left:20px;font-weight: 700">${value}</span></div>`
|
||||
})
|
||||
result+='</div>'
|
||||
return result
|
||||
}
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: { type: "time" },
|
||||
xAxis: { type: "category", data: xdata },
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
@ -124,16 +105,16 @@ export default {
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: seriesData.map(item => ({
|
||||
type: "line",
|
||||
name: item.name,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
series: [
|
||||
{
|
||||
type: "line",
|
||||
name: `平均SOC`,
|
||||
areaStyle: {
|
||||
// color:'#FFBD00'
|
||||
},
|
||||
data: ydata,
|
||||
},
|
||||
data: item.data
|
||||
})),
|
||||
],
|
||||
},true);
|
||||
},
|
||||
},
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<div slot="header">
|
||||
<span class="card-title">电池平均温度</span>
|
||||
</div>
|
||||
<div ref="chartRef" style="height: 360px" />
|
||||
<div style="height: 360px" id="dcpjwdChart" />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@ -14,23 +14,17 @@
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import { getPointConfigCurve } from "@/api/ems/site";
|
||||
import { batteryAveTemp } from "@/api/ems/dzjk";
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.chart = echarts.init(this.$refs.chartRef);
|
||||
this.chart = echarts.init(document.querySelector("#dcpjwdChart"));
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
@ -41,52 +35,28 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
init(siteId,timeRange) {
|
||||
this.chart.showLoading();
|
||||
const [startTime='', endTime=''] = timeRange;
|
||||
const query = {
|
||||
siteId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
};
|
||||
const rows = (this.displayData || []).filter(
|
||||
(item) =>
|
||||
item &&
|
||||
item.fieldCode === "SBJK_SSYX__curveBatteryAveTemp" &&
|
||||
item.useFixedDisplay !== 1 &&
|
||||
item.dataPoint
|
||||
);
|
||||
const tasks = rows.map((row) => {
|
||||
const pointId = String(row.dataPoint || "").trim();
|
||||
if(!pointId) return Promise.resolve(null);
|
||||
return getPointConfigCurve({
|
||||
...query,
|
||||
pointId
|
||||
}).then((response) => {
|
||||
const list = response?.data || [];
|
||||
return {
|
||||
name: (row.deviceName || "") + (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);
|
||||
batteryAveTemp(siteId,startTime,endTime)
|
||||
.then((response) => {
|
||||
this.setOption(response?.data?.batteryAveTempList || []);
|
||||
})
|
||||
.finally(() => {
|
||||
this.chart.hideLoading();
|
||||
});
|
||||
},
|
||||
setOption(data) {
|
||||
let xdata = [],
|
||||
ydata = [];
|
||||
data.forEach((element) => {
|
||||
xdata.push(element.createDate);
|
||||
ydata.push(
|
||||
{
|
||||
value: element.batteryTemp,
|
||||
year: element.dateDay
|
||||
}
|
||||
);
|
||||
});
|
||||
Promise.all(tasks).then((series) => {
|
||||
this.setOption((series || []).filter(Boolean));
|
||||
});
|
||||
},
|
||||
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",
|
||||
@ -104,13 +74,26 @@ export default {
|
||||
show:true,
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
// 坐标轴指示器,坐标轴触发有效
|
||||
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
|
||||
},
|
||||
formatter :(params)=>{
|
||||
if(params.length <= 0) return
|
||||
let result = (params[0].data.year || '')+' '+params[0].name + '<div>'
|
||||
params.forEach(item=>{
|
||||
const {color,seriesName,value} = item
|
||||
result += `<div style="position: relative;padding-left:20px;line-height: 20px;">
|
||||
<div style="position: absolute;top:50%;left:0;width:12px;height:12px;border-radius:100%;background: ${color};transform: translateY(-50%)"></div>
|
||||
<span>${seriesName}</span><span style="margin-left:20px;font-weight: 700">${value}</span></div>`
|
||||
})
|
||||
result+='</div>'
|
||||
return result
|
||||
}
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: { type: "time" },
|
||||
xAxis: { type: "category", data: xdata },
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
@ -125,16 +108,16 @@ export default {
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: seriesData.map(item => ({
|
||||
type: "line",
|
||||
name: item.name,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
series: [
|
||||
{
|
||||
type: "line",
|
||||
name: `电池平均温度`,
|
||||
areaStyle: {
|
||||
// color:'#FFBD00'
|
||||
},
|
||||
data: ydata,
|
||||
},
|
||||
data: item.data
|
||||
})),
|
||||
],
|
||||
},true);
|
||||
},
|
||||
},
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<div slot="header">
|
||||
<span class="card-title">PCS最高温度</span>
|
||||
</div>
|
||||
<div ref="chartRef" style="height: 360px" />
|
||||
<div style="height: 360px" id="pocpjwdChart" />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@ -14,23 +14,17 @@
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import { getPointConfigCurve } from "@/api/ems/site";
|
||||
import { pcsMaxTemp } from "@/api/ems/dzjk";
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.chart = echarts.init(this.$refs.chartRef);
|
||||
this.chart = echarts.init(document.querySelector("#pocpjwdChart"));
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
@ -41,52 +35,37 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
init(siteId,timeRange) {
|
||||
this.chart.showLoading();
|
||||
const [startTime='', endTime=''] = timeRange;
|
||||
const query = {
|
||||
siteId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
};
|
||||
const rows = (this.displayData || []).filter(
|
||||
(item) =>
|
||||
item &&
|
||||
item.fieldCode === "SBJK_SSYX__curvePcsMaxTemp" &&
|
||||
item.useFixedDisplay !== 1 &&
|
||||
item.dataPoint
|
||||
);
|
||||
const tasks = rows.map((row) => {
|
||||
const pointId = String(row.dataPoint || "").trim();
|
||||
if(!pointId) return Promise.resolve(null);
|
||||
return getPointConfigCurve({
|
||||
...query,
|
||||
pointId
|
||||
}).then((response) => {
|
||||
const list = response?.data || [];
|
||||
return {
|
||||
name: (row.deviceName || "") + (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);
|
||||
pcsMaxTemp(siteId,startTime,endTime)
|
||||
.then((response) => {
|
||||
this.setOption(response?.data?.pcsMaxTempList || []);
|
||||
})
|
||||
.finally(() => {
|
||||
this.chart.hideLoading();
|
||||
});
|
||||
},
|
||||
setOption(data) {
|
||||
let xdata = [],
|
||||
series = [];
|
||||
data.forEach((element, index) => {
|
||||
if (index === 0) {
|
||||
xdata = (element.maxTempVoList || []).map((i) => i.createDate);
|
||||
}
|
||||
series.push({
|
||||
type: "line",
|
||||
name: `${element.deviceId}最高温度`,
|
||||
areaStyle: {
|
||||
// color:'#FFBD00'
|
||||
},
|
||||
data: (element.maxTempVoList || []).map((i) => {
|
||||
return {
|
||||
value: i.temp,
|
||||
year: i.dateDay
|
||||
}
|
||||
}),
|
||||
});
|
||||
});
|
||||
Promise.all(tasks).then((series) => {
|
||||
this.setOption((series || []).filter(Boolean));
|
||||
});
|
||||
},
|
||||
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",
|
||||
@ -104,13 +83,26 @@ export default {
|
||||
show:true,
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
// 坐标轴指示器,坐标轴触发有效
|
||||
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
|
||||
},
|
||||
formatter :(params)=>{
|
||||
if(params.length <= 0) return
|
||||
let result = (params[0].data.year || '')+' '+params[0].name + '<div>'
|
||||
params.forEach(item=>{
|
||||
const {color,seriesName,value} = item
|
||||
result += `<div style="position: relative;padding-left:20px;line-height: 20px;">
|
||||
<div style="position: absolute;top:50%;left:0;width:12px;height:12px;border-radius:100%;background: ${color};transform: translateY(-50%)"></div>
|
||||
<span>${seriesName}</span><span style="margin-left:20px;font-weight: 700">${value}</span></div>`
|
||||
})
|
||||
result+='</div>'
|
||||
return result
|
||||
}
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: { type: "time" },
|
||||
xAxis: { type: "category", data: xdata },
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
@ -125,16 +117,7 @@ export default {
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: seriesData.map(item => ({
|
||||
type: "line",
|
||||
name: item.name,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
},
|
||||
data: item.data
|
||||
})),
|
||||
series,
|
||||
},true);
|
||||
},
|
||||
},
|
||||
|
||||
@ -2,65 +2,33 @@
|
||||
<template>
|
||||
<div class="ssyx-ems-dashboard-editor-container">
|
||||
<!-- 6个方块-->
|
||||
<real-time-base-info :display-data="runningDisplayData" :loading="runningHeadLoading" @field-click="handleHeadFieldClick"/>
|
||||
<real-time-base-info :data="runningHeadData"/>
|
||||
<!-- 时间选择 -->
|
||||
<date-range-select ref="dateRangeSelect" @updateDate="updateDate" style="margin-top:20px;"/>
|
||||
<!-- echart图表-->
|
||||
<el-row :gutter="32" style="background:#fff;margin:30px 0;">
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<cnglqx-chart ref='cnglqx' :display-data="runningDisplayData"/>
|
||||
<cnglqx-chart ref='cnglqx'/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<pocpjwd-chart ref='pocpjwd' :display-data="runningDisplayData"/>
|
||||
<pocpjwd-chart ref='pocpjwd'/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="32" style="margin:30px 0;">
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<dcpjsoc-chart ref="dcpjsoc" :display-data="runningDisplayData"/>
|
||||
<dcpjsoc-chart ref="dcpjsoc"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<dcpjwd-chart ref="dcpjwd" :display-data="runningDisplayData"/>
|
||||
<dcpjwd-chart ref="dcpjwd"/>
|
||||
</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'
|
||||
@ -68,8 +36,7 @@ import PocpjwdChart from './PocpjwdChart.vue'
|
||||
import DcpjwdChart from './DcpjwdChart.vue'
|
||||
import DcpjsocChart from './DcpjsocChart.vue'
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getProjectDisplayData} from '@/api/ems/dzjk'
|
||||
import {getPointConfigCurve} from "@/api/ems/site";
|
||||
import {getRunningHeadInfo} from '@/api/ems/dzjk'
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
|
||||
export default {
|
||||
@ -78,183 +45,16 @@ export default {
|
||||
mixins:[getQuerySiteId,intervalUpdate],
|
||||
data() {
|
||||
return {
|
||||
runningDisplayData: [], //单站监控项目配置展示数据
|
||||
runningHeadData:{},//运行信息
|
||||
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;
|
||||
isInit:true
|
||||
}
|
||||
},
|
||||
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(){
|
||||
this.runningHeadLoading = true
|
||||
return getProjectDisplayData(this.siteId).then((displayResponse) => {
|
||||
this.runningDisplayData = displayResponse?.data || []
|
||||
}).finally(() => {
|
||||
this.runningHeadLoading = false
|
||||
getRunningHeadInfo(this.siteId).then(response => {
|
||||
this.runningHeadData = response?.data || {}
|
||||
})
|
||||
},
|
||||
// 更新时间范围 重置图表
|
||||
@ -271,9 +71,8 @@ export default {
|
||||
this.updateInterval(this.updateData)
|
||||
},
|
||||
updateData(){
|
||||
this.getRunningHeadData().finally(() => {
|
||||
this.updateChart()
|
||||
})
|
||||
this.getRunningHeadData()
|
||||
this.updateChart()
|
||||
},
|
||||
init(){
|
||||
this.$refs.dateRangeSelect.init(true)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-loading="loading">
|
||||
<el-card
|
||||
v-for="(item,index) in list"
|
||||
:key="index+'ylLise'"
|
||||
@ -37,11 +37,10 @@
|
||||
<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 class="left">{{ tempDataItem.title }}</span> <span class="right">{{
|
||||
item[tempDataItem.attr] || '-'
|
||||
}}<span
|
||||
v-html="tempDataItem.unit"></span></span>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -77,12 +76,6 @@ export default {
|
||||
}
|
||||
},
|
||||
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
|
||||
@ -138,16 +131,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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,471 +1,103 @@
|
||||
<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 + '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>
|
||||
<div v-loading="loading">
|
||||
<el-card
|
||||
v-for="(group, index) in filteredSectionGroups"
|
||||
:key="index + 'ylSection'"
|
||||
class="sbjk-card-container list running-card-container"
|
||||
shadow="always"
|
||||
>
|
||||
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">{{ group.displayName || group.sectionName || "冷却" }}</span>
|
||||
<span class="large-title">{{ item.deviceName }}</span>
|
||||
<div class="info">
|
||||
<div>状态:{{ group.statusText }}</div>
|
||||
<div>数据更新时间:{{ group.updateTimeText }}</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="(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>
|
||||
<el-col v-for="(tempDataItem,tempDataIndex) in tempData" :key="tempDataIndex+'ylTempData'" :span="8"
|
||||
class="device-info-col">
|
||||
<span class="pointer" @click="showChart(tempDataItem.title,item.deviceId)">
|
||||
<span class="left">{{ tempDataItem.title }}</span> <span
|
||||
class="right">{{ item[tempDataItem.attr] || '-' }}<span
|
||||
v-html="tempDataItem.unit"></span></span>
|
||||
</span>
|
||||
</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>
|
||||
<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 * 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";
|
||||
import pointChart from "./../PointChart.vue";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
|
||||
export default {
|
||||
name: "DzjkSbjkYl",
|
||||
name: 'DzjkSbjkYl',
|
||||
mixins: [getQuerySiteId, intervalUpdate],
|
||||
components: {pointChart, PointTable},
|
||||
data() {
|
||||
return {
|
||||
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;
|
||||
list: [],
|
||||
tempData: [
|
||||
{title: '供水温度', attr: 'gsTemp', unit: '℃'},
|
||||
{title: '回水温度', attr: 'hsTemp', unit: '℃'},
|
||||
{title: '供水压力', attr: 'gsPressure', unit: 'bar'},
|
||||
{title: '回水压力', attr: 'hsPressure', unit: 'bar'},
|
||||
{title: '冷源水温度', attr: 'lysTemp', unit: '℃'},
|
||||
{title: 'VB01开度', attr: 'vb01Kd', unit: '%'},
|
||||
{title: 'VB02开度', attr: 'vb02Kd', unit: '%'},
|
||||
]
|
||||
}
|
||||
},
|
||||
};
|
||||
methods: {
|
||||
// 查看设备电位表格
|
||||
pointDetail(row, dataType) {
|
||||
const {deviceId} = row
|
||||
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'COOLING'}, dataType)
|
||||
},
|
||||
showChart(pointName, deviceId) {
|
||||
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory: 'COOLING', deviceId})
|
||||
},
|
||||
updateData() {
|
||||
this.loading = true
|
||||
getCoolingDataList(this.siteId).then(response => {
|
||||
this.list = JSON.parse(JSON.stringify(response?.data || []));
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
init() {
|
||||
this.updateData()
|
||||
this.updateInterval(this.updateData)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sbjk-card-container {
|
||||
&.list:not(:last-child) {
|
||||
&: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,17 +1,19 @@
|
||||
|
||||
<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"
|
||||
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>
|
||||
@ -20,221 +22,183 @@
|
||||
<el-form-item>
|
||||
<el-button @click="onReset" native-type="button">重置</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="exportTable" native-type="button">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!--表格-->
|
||||
<el-table
|
||||
class="common-table"
|
||||
:data="tableData"
|
||||
stripe
|
||||
style="width: 100%; margin-top: 25px;"
|
||||
>
|
||||
<el-table-column label="汇总">
|
||||
<el-table-column prop="dataTime" label="日期" width="120"></el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="充电量" align="center">
|
||||
<el-table-column align="center" prop="activePeakKwh" label="尖"></el-table-column>
|
||||
<el-table-column align="center" prop="activeHighKwh" label="峰"></el-table-column>
|
||||
<el-table-column align="center" prop="activeFlatKwh" label="平"></el-table-column>
|
||||
<el-table-column align="center" prop="activeValleyKwh" label="谷"></el-table-column>
|
||||
<el-table-column align="center" prop="activeTotalKwh" label="总"></el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="放电量" align="center">
|
||||
<el-table-column align="center" prop="reActivePeakKwh" label="尖"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveHighKwh" label="峰"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveFlatKwh" label="平"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveValleyKwh" label="谷"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveTotalKwh" label="总"></el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="效率(%)" align="center">
|
||||
<el-table-column align="center" prop="effect"></el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="备注" align="center" fixed="right" min-width="260">
|
||||
<template slot-scope="scope">
|
||||
<div class="remark-cell">
|
||||
<span class="remark-text">{{ scope.row.remark || "-" }}</span>
|
||||
<el-button type="text" @click="editRemark(scope.row)">编辑</el-button>
|
||||
</div>
|
||||
</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>
|
||||
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 { batchGetBizRemark, getAmmeterData, saveBizRemark } from "@/api/ems/dzjk";
|
||||
import { formatDate } from "@/filters/ems";
|
||||
|
||||
const BIZ_TYPE = "stats_report";
|
||||
const REPORT_KEY = "DBBB";
|
||||
|
||||
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();
|
||||
},
|
||||
},
|
||||
defaultDateRange: [],
|
||||
dateRange: [],
|
||||
tableData: [],
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
totalSize: 0,
|
||||
};
|
||||
defaultDateRange:[],//默认展示的时间
|
||||
dateRange:[],
|
||||
tableData:[],
|
||||
pageSize:10,//分页栏当前每个数据总数
|
||||
pageNum:1,//分页栏当前页数
|
||||
totalSize:0,//table表格数据总数
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buildRemarkKey(dataTime) {
|
||||
return `${this.siteId}_${dataTime || ""}`;
|
||||
methods:{
|
||||
// 搜索
|
||||
onSearch(){
|
||||
this.pageNum =1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
loadRemarks(rows) {
|
||||
if (!rows.length) return Promise.resolve({});
|
||||
return batchGetBizRemark({
|
||||
bizType: BIZ_TYPE,
|
||||
bizKey1: REPORT_KEY,
|
||||
bizKey2List: rows.map(row => this.buildRemarkKey(row.dataTime)),
|
||||
}).then(response => response?.data || {});
|
||||
},
|
||||
applyRemarks(rows, remarkMap) {
|
||||
rows.forEach(row => {
|
||||
this.$set(row, "remark", remarkMap[this.buildRemarkKey(row.dataTime)] || "");
|
||||
});
|
||||
},
|
||||
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() {
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
},
|
||||
onReset() {
|
||||
this.dateRange = this.defaultDateRange;
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
// 重置
|
||||
onReset(){
|
||||
this.dateRange=this.defaultDateRange
|
||||
this.pageNum =1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
// 分页
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
this.$nextTick(() => {
|
||||
this.getData();
|
||||
});
|
||||
this.$nextTick(()=>{
|
||||
this.getData()
|
||||
})
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val;
|
||||
this.$nextTick(() => {
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
editRemark(row) {
|
||||
this.$prompt("请输入备注", "编辑备注", {
|
||||
inputValue: row.remark || "",
|
||||
inputType: "textarea",
|
||||
inputPlaceholder: "可输入该日报表备注",
|
||||
confirmButtonText: "保存",
|
||||
cancelButtonText: "取消",
|
||||
this.pageNum = val
|
||||
this.$nextTick(()=>{
|
||||
this.getData()
|
||||
})
|
||||
.then(({ value }) => {
|
||||
return saveBizRemark({
|
||||
bizType: BIZ_TYPE,
|
||||
bizKey1: REPORT_KEY,
|
||||
bizKey2: this.buildRemarkKey(row.dataTime),
|
||||
remark: value || "",
|
||||
}).then(() => {
|
||||
this.$set(row, "remark", value || "");
|
||||
this.$message.success("备注保存成功");
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
getData() {
|
||||
this.loading = true;
|
||||
const { siteId, pageNum, pageSize } = this;
|
||||
const [startTime = "", endTime = ""] = this.dateRange || [];
|
||||
getAmmeterData({ siteId, startTime, endTime, pageSize, pageNum })
|
||||
.then(response => {
|
||||
const rows = response?.rows || [];
|
||||
this.tableData = rows;
|
||||
this.totalSize = response?.total || 0;
|
||||
return this.loadRemarks(rows);
|
||||
})
|
||||
.then(remarkMap => {
|
||||
this.applyRemarks(this.tableData, remarkMap || {});
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
// 获取数据
|
||||
getData(){
|
||||
this.loading=true
|
||||
const {siteId,pageNum,pageSize} =this
|
||||
const [startTime='',endTime='']=(this.dateRange || [])
|
||||
getAmmeterData({siteId:siteId,startTime,endTime,pageSize,pageNum}).then(response=>{
|
||||
this.tableData=response?.rows || [];
|
||||
this.totalSize = response?.total || 0
|
||||
}).finally(()=> {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
init() {
|
||||
this.dateRange = [];
|
||||
this.tableData = [];
|
||||
this.totalSize = 0;
|
||||
this.pageSize = 10;
|
||||
this.pageNum = 1;
|
||||
const now = new Date();
|
||||
const lastDay = now.getTime();
|
||||
const firstDay = new Date(new Date().setDate(1)).getTime();
|
||||
this.defaultDateRange = [formatDate(firstDay), formatDate(lastDay)];
|
||||
this.dateRange = [formatDate(firstDay), formatDate(lastDay)];
|
||||
this.getData();
|
||||
init(){
|
||||
this.dateRange=[]
|
||||
this.tableData=[]
|
||||
this.totalSize=0
|
||||
this.pageSize=10
|
||||
this.pageNum = 1
|
||||
const now = new Date().getTime();
|
||||
const lastMonth = new Date(now-30 * 24 * 60 * 60 * 1000).getTime();
|
||||
this.defaultDateRange = [formatDate(lastMonth), formatDate(now)];
|
||||
this.dateRange=[formatDate(lastMonth), formatDate(now)];
|
||||
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 {
|
||||
::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;
|
||||
}
|
||||
}
|
||||
|
||||
.remark-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.remark-text {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,23 +1,22 @@
|
||||
<template>
|
||||
<el-card
|
||||
shadow="always"
|
||||
class="common-card-container time-range-card"
|
||||
style="margin-top: 20px"
|
||||
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"/>
|
||||
<date-range-select ref="dateRangeSelect" @updateDate="updateDate" />
|
||||
</div>
|
||||
<div class="card-main" v-loading="loading">
|
||||
<el-button-group class="ems-btns-group">
|
||||
<el-button
|
||||
v-for="(item, index) in btnList"
|
||||
:key="index + 'dcdqxBtns'"
|
||||
size="mini"
|
||||
:class="{ activeBtn: activeBtn === item.id }"
|
||||
@click="changeDataType(item.id)"
|
||||
>{{ item.name }}
|
||||
</el-button
|
||||
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>
|
||||
@ -29,13 +28,12 @@
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getStackData} from "@/api/ems/dzjk";
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import { getStackData, getStackNameList } from "@/api/ems/dzjk";
|
||||
import { formatDate } from "@/filters/ems";
|
||||
import DateRangeSelect from "@/components/Ems/DateRangeSelect/index.vue";
|
||||
|
||||
export default {
|
||||
name: "DzjkTjbbDcdqx",
|
||||
components: {DateRangeSelect},
|
||||
components: { DateRangeSelect },
|
||||
mixins: [resize, getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
@ -48,10 +46,10 @@ export default {
|
||||
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"]},
|
||||
{ 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"] },
|
||||
],
|
||||
};
|
||||
},
|
||||
@ -68,7 +66,7 @@ export default {
|
||||
this.getData();
|
||||
},
|
||||
getData() {
|
||||
const {siteId, activeBtn} = this;
|
||||
const { siteId, activeBtn } = this;
|
||||
const [start = "", end = ""] = this.dateRange || [];
|
||||
//接口调用完成之后 设置图表、结束loading
|
||||
this.loading = true;
|
||||
@ -78,12 +76,12 @@ export default {
|
||||
endTime: formatDate(end),
|
||||
dataType: activeBtn,
|
||||
})
|
||||
.then((response) => {
|
||||
this.setOption(response?.data || []);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
.then((response) => {
|
||||
this.setOption(response?.data || []);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
compareDate(date1, date2) {
|
||||
console.log("比较时间", date1, date2);
|
||||
@ -94,9 +92,9 @@ export default {
|
||||
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
|
||||
(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) {
|
||||
@ -108,7 +106,7 @@ export default {
|
||||
const source = [];
|
||||
const sourceTop = ["日期"];
|
||||
let map = {},
|
||||
mapArr = [];
|
||||
mapArr = [];
|
||||
// 生成所有{日期:[],日期:[]}格式的对象和所有包含所有日期的[日期1,日期2...]
|
||||
data.forEach((item) => {
|
||||
item.dataList.forEach((inner) => {
|
||||
@ -138,54 +136,38 @@ export default {
|
||||
});
|
||||
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,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
};
|
||||
}),
|
||||
{
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
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: source[0].slice(1).map((item) => {
|
||||
return {
|
||||
type: "line",
|
||||
};
|
||||
}),
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
initChart() {
|
||||
@ -194,7 +176,7 @@ export default {
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
this.$refs.dateRangeSelect.init(true);
|
||||
this.$refs.dateRangeSelect.init();
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@ -147,9 +147,6 @@ export default {
|
||||
if(val){
|
||||
this.search.clusterId=''
|
||||
this.getClusterList()
|
||||
} else {
|
||||
this.search.clusterId=''
|
||||
this.clusterOptions=[]
|
||||
}
|
||||
},
|
||||
//表格数据
|
||||
@ -173,15 +170,8 @@ 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 : ''
|
||||
@ -210,3 +200,4 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<el-card
|
||||
shadow="always"
|
||||
class="common-card-container time-range-card"
|
||||
style="margin-top: 20px"
|
||||
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"
|
||||
ref="dateRangeSelect"
|
||||
@reset="resetTime"
|
||||
@updateDate="updateDate"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-main" v-loading="loading">
|
||||
@ -21,13 +22,12 @@
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getPowerData} from "@/api/ems/dzjk";
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import { getPcsNameList, getPowerData } from "@/api/ems/dzjk";
|
||||
import { formatDate } from "@/filters/ems";
|
||||
import DateRangeSelect from "@/components/Ems/DateRangeSelect/index.vue";
|
||||
|
||||
export default {
|
||||
name: "DzjkTjbbGlqx",
|
||||
components: {DateRangeSelect},
|
||||
components: { DateRangeSelect },
|
||||
mixins: [resize, getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
@ -38,6 +38,7 @@ export default {
|
||||
},
|
||||
dateRange: [],
|
||||
loading: false,
|
||||
dateRangeInit: true,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@ -46,22 +47,30 @@ export default {
|
||||
this.dateRange = data || [];
|
||||
this.getData();
|
||||
},
|
||||
resetTime() {
|
||||
this.dateRangeInit = true;
|
||||
},
|
||||
getData() {
|
||||
const {siteId} = this;
|
||||
const { siteId } = this;
|
||||
let [start = "", end = ""] = this.dateRange || [];
|
||||
//接口调用完成之后 设置图表、结束loading
|
||||
this.loading = true;
|
||||
if (this.dateRangeInit) {
|
||||
start = "";
|
||||
end = "";
|
||||
this.dateRangeInit = false;
|
||||
}
|
||||
getPowerData({
|
||||
siteId,
|
||||
startDate: formatDate(start),
|
||||
endDate: formatDate(end),
|
||||
})
|
||||
.then((response) => {
|
||||
this.setOption(response?.data || []);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
.then((response) => {
|
||||
this.setOption(response?.data || []);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
setOption(data) {
|
||||
const source = [["日期", "电网功率", "负载功率", "储能功率", "光伏功率"]];
|
||||
@ -75,78 +84,47 @@ export default {
|
||||
]);
|
||||
});
|
||||
this.chart.setOption(
|
||||
{
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {
|
||||
left: "center",
|
||||
top: "10",
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
// 坐标轴指示器,坐标轴触发有效
|
||||
type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: "#333333",
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
dataset: {source},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: data.length > 500 ? 90 : 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: data.length > 500 ? 90 : 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: "line",
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "line",
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "line",
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "line",
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
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: [
|
||||
{
|
||||
type: "line",
|
||||
},
|
||||
{
|
||||
type: "line",
|
||||
},
|
||||
{
|
||||
type: "line",
|
||||
},
|
||||
{
|
||||
type: "line",
|
||||
},
|
||||
],
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
initChart() {
|
||||
@ -155,6 +133,7 @@ export default {
|
||||
},
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.dateRangeInit = true;
|
||||
this.initChart();
|
||||
this.$refs.dateRangeSelect.init();
|
||||
});
|
||||
|
||||
@ -125,12 +125,6 @@ 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;
|
||||
})
|
||||
|
||||
@ -1,23 +1,22 @@
|
||||
<template>
|
||||
<el-card
|
||||
shadow="always"
|
||||
class="common-card-container time-range-card"
|
||||
style="margin-top: 20px"
|
||||
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"/>
|
||||
<date-range-select ref="dateRangeSelect" @updateDate="updateDate" />
|
||||
</div>
|
||||
<div class="card-main" v-loading="loading">
|
||||
<el-button-group class="ems-btns-group">
|
||||
<el-button
|
||||
v-for="(item, index) in btnList"
|
||||
:key="index + 'flqxcBtns'"
|
||||
size="mini"
|
||||
:class="{ activeBtn: activeBtn === item.id }"
|
||||
@click="changeDataType(item.id)"
|
||||
>{{ item.name }}
|
||||
</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>
|
||||
@ -29,13 +28,12 @@
|
||||
import * as echarts from "echarts";
|
||||
import resize from "@/mixins/ems/resize";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getPCSData} from "@/api/ems/dzjk";
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import { getPCSData, getPcsNameList } from "@/api/ems/dzjk";
|
||||
import { formatDate } from "@/filters/ems";
|
||||
import DateRangeSelect from "@/components/Ems/DateRangeSelect/index.vue";
|
||||
|
||||
export default {
|
||||
name: "DzjkTjbbPcsqx",
|
||||
components: {DateRangeSelect},
|
||||
components: { DateRangeSelect },
|
||||
mixins: [resize, getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
@ -84,7 +82,7 @@ export default {
|
||||
this.getData();
|
||||
},
|
||||
getData() {
|
||||
const {siteId, activeBtn} = this;
|
||||
const { siteId, activeBtn } = this;
|
||||
const [start = "", end = ""] = this.dateRange || [];
|
||||
this.loading = true;
|
||||
//接口调用完成之后 设置图表、结束loading
|
||||
@ -94,12 +92,12 @@ export default {
|
||||
endTime: formatDate(end),
|
||||
dataType: activeBtn,
|
||||
})
|
||||
.then((response) => {
|
||||
this.setOption(response?.data || []);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
.then((response) => {
|
||||
this.setOption(response?.data || []);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
compareDate(date1, date2) {
|
||||
console.log("比较时间", date1, date2);
|
||||
@ -110,9 +108,9 @@ export default {
|
||||
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
|
||||
(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) {
|
||||
@ -124,7 +122,7 @@ export default {
|
||||
const source = [];
|
||||
const sourceTop = ["日期"];
|
||||
let map = {},
|
||||
mapArr = [];
|
||||
mapArr = [];
|
||||
// 生成所有{日期:[],日期:[]}格式的对象和所有包含所有日期的[日期1,日期2...]
|
||||
data.forEach((item) => {
|
||||
item.dataList.forEach((inner) => {
|
||||
@ -154,54 +152,38 @@ export default {
|
||||
});
|
||||
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,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
};
|
||||
}),
|
||||
{
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
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: source[0].slice(1).map((item) => {
|
||||
return {
|
||||
type: "line",
|
||||
};
|
||||
}),
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
initChart() {
|
||||
@ -210,7 +192,7 @@ export default {
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
this.$refs.dateRangeSelect.init(true);
|
||||
this.$refs.dateRangeSelect.init();
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
<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"
|
||||
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>
|
||||
@ -20,81 +21,111 @@
|
||||
<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"
|
||||
:summary-method="getSummaries"
|
||||
show-summary
|
||||
stripe
|
||||
style="width: 100%; margin-top: 25px;"
|
||||
>
|
||||
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
|
||||
prop="dataTime"
|
||||
label="日期"
|
||||
min-width="100px" 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
|
||||
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
|
||||
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">
|
||||
<el-table-column prop="actualRevenue" label="实际收益" align="center"></el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="备注" align="center" fixed="right" min-width="260">
|
||||
<template slot-scope="scope">
|
||||
<div class="remark-cell">
|
||||
<span class="remark-text">{{ scope.row.remark || "-" }}</span>
|
||||
<el-button type="text" @click="editRemark(scope.row)">编辑</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 实际收益-->
|
||||
<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"
|
||||
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 { batchGetBizRemark, getAmmeterRevenueData, saveBizRemark } from "@/api/ems/dzjk";
|
||||
import { formatDate } from "@/filters/ems";
|
||||
|
||||
const BIZ_TYPE = "stats_report";
|
||||
const REPORT_KEY = "SYBB";
|
||||
import {getAmmeterRevenueData} from '@/api/ems/dzjk'
|
||||
import {formatDate} from "@/filters/ems";
|
||||
|
||||
export default {
|
||||
name: "DzjkTjbbSybb",
|
||||
name: 'DzjkTjbbSybb',
|
||||
mixins: [getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
@ -104,189 +135,70 @@ export default {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
defaultDateRange: [],
|
||||
defaultDateRange: [],//默认展示的时间
|
||||
dateRange: [],
|
||||
tableData: [],
|
||||
summaryTotals: {},
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
totalSize: 0,
|
||||
};
|
||||
pageSize: 10,//分页栏当前每个数据总数
|
||||
pageNum: 1,//分页栏当前页数
|
||||
totalSize: 0,//table表格数据总数
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buildRemarkKey(dataTime) {
|
||||
return `${this.siteId}_${dataTime || ""}`;
|
||||
},
|
||||
loadRemarks(rows) {
|
||||
if (!rows.length) return Promise.resolve({});
|
||||
return batchGetBizRemark({
|
||||
bizType: BIZ_TYPE,
|
||||
bizKey1: REPORT_KEY,
|
||||
bizKey2List: rows.map(row => this.buildRemarkKey(row.dataTime)),
|
||||
}).then(response => response?.data || {});
|
||||
},
|
||||
applyRemarks(rows, remarkMap) {
|
||||
rows.forEach(row => {
|
||||
this.$set(row, "remark", remarkMap[this.buildRemarkKey(row.dataTime)] || "");
|
||||
});
|
||||
},
|
||||
toScaledInt(value) {
|
||||
const num = Number(value);
|
||||
return Number.isFinite(num) ? Math.round(num * 1000) : 0;
|
||||
},
|
||||
sumRowsByProp(rows, prop) {
|
||||
const total = (rows || []).reduce((sum, row) => sum + this.toScaledInt(row?.[prop]), 0);
|
||||
return total / 1000;
|
||||
},
|
||||
formatSummaryNumber(value) {
|
||||
const num = Number(value);
|
||||
if (!Number.isFinite(num)) return "";
|
||||
return num.toFixed(3).replace(/\.?0+$/, "");
|
||||
},
|
||||
buildSummaryTotals(rows) {
|
||||
const numericProps = [
|
||||
"activePeakPrice",
|
||||
"activeHighPrice",
|
||||
"activeFlatPrice",
|
||||
"activeValleyPrice",
|
||||
"activeTotalPrice",
|
||||
"reActivePeakPrice",
|
||||
"reActiveHighPrice",
|
||||
"reActiveFlatPrice",
|
||||
"reActiveValleyPrice",
|
||||
"reActiveTotalPrice",
|
||||
"actualRevenue",
|
||||
];
|
||||
return numericProps.reduce((result, prop) => {
|
||||
result[prop] = this.sumRowsByProp(rows, prop);
|
||||
return result;
|
||||
}, {});
|
||||
},
|
||||
getSummaries({ columns, data }) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) return "合计";
|
||||
const prop = column.property;
|
||||
if (!prop) return "";
|
||||
if (Object.prototype.hasOwnProperty.call(this.summaryTotals, prop)) {
|
||||
return this.formatSummaryNumber(this.summaryTotals[prop]);
|
||||
}
|
||||
const hasNumericValue = (data || []).some(item => Number.isFinite(Number(item?.[prop])));
|
||||
return hasNumericValue ? this.formatSummaryNumber(this.sumRowsByProp(data, prop)) : "";
|
||||
});
|
||||
},
|
||||
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;
|
||||
this.getData();
|
||||
this.pageNum = 1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
// 重置
|
||||
onReset() {
|
||||
this.dateRange = this.defaultDateRange;
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
this.dateRange = this.defaultDateRange
|
||||
this.pageNum = 1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
// 分页
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
this.$nextTick(() => {
|
||||
this.getData();
|
||||
});
|
||||
this.getData()
|
||||
})
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val;
|
||||
this.pageNum = val
|
||||
this.$nextTick(() => {
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
editRemark(row) {
|
||||
this.$prompt("请输入备注", "编辑备注", {
|
||||
inputValue: row.remark || "",
|
||||
inputType: "textarea",
|
||||
inputPlaceholder: "可输入该日报表备注",
|
||||
confirmButtonText: "保存",
|
||||
cancelButtonText: "取消",
|
||||
this.getData()
|
||||
})
|
||||
.then(({ value }) => {
|
||||
return saveBizRemark({
|
||||
bizType: BIZ_TYPE,
|
||||
bizKey1: REPORT_KEY,
|
||||
bizKey2: this.buildRemarkKey(row.dataTime),
|
||||
remark: value || "",
|
||||
}).then(() => {
|
||||
this.$set(row, "remark", value || "");
|
||||
this.$message.success("备注保存成功");
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
// 获取数据
|
||||
getData() {
|
||||
this.loading = true;
|
||||
const { siteId, pageNum, pageSize } = this;
|
||||
const [startTime = "", endTime = ""] = this.dateRange || [];
|
||||
|
||||
getAmmeterRevenueData({ siteId, startTime, endTime, pageSize, pageNum })
|
||||
.then(pageResponse => {
|
||||
const rows = pageResponse?.rows || [];
|
||||
const total = Number(pageResponse?.total || 0);
|
||||
this.totalSize = total;
|
||||
this.tableData = rows;
|
||||
|
||||
const tasks = [this.loadRemarks(rows)];
|
||||
if (total > 0) {
|
||||
tasks.push(
|
||||
getAmmeterRevenueData({
|
||||
siteId,
|
||||
startTime,
|
||||
endTime,
|
||||
pageNum: 1,
|
||||
pageSize: total,
|
||||
})
|
||||
);
|
||||
}
|
||||
return Promise.all(tasks);
|
||||
})
|
||||
.then(([remarkMap, allResponse]) => {
|
||||
this.applyRemarks(this.tableData, remarkMap || {});
|
||||
const allRows = allResponse?.rows || allResponse?.data || [];
|
||||
this.summaryTotals = this.buildSummaryTotals(allRows);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
this.loading = true
|
||||
const {siteId, pageNum, pageSize} = this
|
||||
const [startTime = '', endTime = ''] = (this.dateRange || [])
|
||||
getAmmeterRevenueData({siteId: siteId, startTime, endTime, pageSize, pageNum}).then(response => {
|
||||
this.tableData = response?.rows || [];
|
||||
this.totalSize = response?.total || 0
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
init() {
|
||||
this.dateRange = [];
|
||||
this.tableData = [];
|
||||
this.summaryTotals = {};
|
||||
this.totalSize = 0;
|
||||
this.pageSize = 10;
|
||||
this.pageNum = 1;
|
||||
const now = new Date();
|
||||
const lastDay = now.getTime();
|
||||
const firstDay = new Date(new Date().setDate(1)).getTime();
|
||||
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();
|
||||
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 {
|
||||
.el-table__header-wrapper th, .common-table.el-table .el-table__fixed-header-wrapper th {
|
||||
border-bottom: 1px solid #dfe6ec;
|
||||
}
|
||||
|
||||
@ -297,20 +209,7 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.remark-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.remark-text {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container">
|
||||
<div class="ems-dashboard-editor-container" v-loading="loading">
|
||||
<zd-info></zd-info>
|
||||
<div class="ems-content-container ems-content-container-padding">
|
||||
<div class="content-title">数据概览</div>
|
||||
<el-row :gutter="15" style="background:#fff;margin:30px 0;">
|
||||
<el-col :xs="24" :sm="12" :lg="12" v-loading="chartLoading.dlzbchart">
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<dlzb-chart ref="dlzbchart"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :lg="12" v-loading="chartLoading.xtxlchart">
|
||||
<el-col :xs="24" :sm="12" :lg="12">
|
||||
<xtxl-chart ref="xtxlchart"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="15" style="background:#fff;margin:0;">
|
||||
<el-col :xs="24" :sm="8" :lg="8" v-loading="chartLoading.gjqsChart">
|
||||
<el-col :xs="24" :sm="8" :lg="8">
|
||||
<gjqs-chart ref="gjqsChart"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :lg="8" v-loading="chartLoading.sbgjzbChart">
|
||||
<el-col :xs="24" :sm="8" :lg="8">
|
||||
<sbgjzb-chart ref="sbgjzbChart"/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :lg="8" v-loading="chartLoading.gjdjfbChart">
|
||||
<el-col :xs="24" :sm="8" :lg="8">
|
||||
<gjdjfb-chart ref="gjdjfbChart"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -47,45 +47,24 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartLoading: {
|
||||
dlzbchart: false,
|
||||
xtxlchart: false,
|
||||
gjqsChart: false,
|
||||
sbgjzbChart: false,
|
||||
gjdjfbChart: false
|
||||
}
|
||||
loading: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.setChartLoading(true)
|
||||
this.loading = 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 || [])
|
||||
this.hideChartLoading('gjdjfbChart')
|
||||
}).catch(() => {
|
||||
this.setChartLoading(false)
|
||||
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,183 +0,0 @@
|
||||
<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>
|
||||
@ -1,218 +0,0 @@
|
||||
<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>
|
||||
@ -1,187 +0,0 @@
|
||||
<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>
|
||||
@ -1,191 +0,0 @@
|
||||
<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>
|
||||
@ -1,30 +0,0 @@
|
||||
<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>
|
||||
@ -2,7 +2,9 @@
|
||||
<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-select v-model="formData.siteId" :disabled="mode === 'edit'" placeholder="请选择站点" :loading="searchLoading" loading-text="正在加载数据">
|
||||
<el-option v-for="(item,index) in siteList" :key="index+'zdxeSelect'" :label="item.siteName" :value="item.siteId" ></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="消息等级" prop="qos">
|
||||
<el-select v-model="formData.qos" placeholder="请选择消息等级">
|
||||
@ -28,10 +30,13 @@
|
||||
</template>
|
||||
<script>
|
||||
import {editMqtt,addMqtt,getMqttDetail} from "@/api/ems/site";
|
||||
import {getAllSites} from '@/api/ems/zddt'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading:0,
|
||||
siteList:[],
|
||||
searchLoading:false,
|
||||
dialogTableVisible:false,
|
||||
mode:'',
|
||||
formData: {
|
||||
@ -43,7 +48,7 @@ export default {
|
||||
},
|
||||
rules: {
|
||||
siteId:[
|
||||
{ required: true, message: '请先在顶部选择站点', trigger: 'blur'},
|
||||
{ required: true, message: '请选择站点', trigger: 'blur'},
|
||||
],
|
||||
qos:[
|
||||
{ required: true, message: '请选择消息等级', trigger: 'blur'},
|
||||
@ -58,20 +63,23 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRouteSiteId() {
|
||||
const siteId = this.$route?.query?.siteId
|
||||
return siteId === undefined || siteId === null ? '' : String(siteId).trim()
|
||||
},
|
||||
showDialog(id, siteId = ''){
|
||||
showDialog(id){
|
||||
this.dialogTableVisible = true
|
||||
this.getZdList()
|
||||
if(id){
|
||||
this.mode = 'edit'
|
||||
this.formData.id = id
|
||||
this.getDetail(id)
|
||||
}else{
|
||||
this.mode = 'add'
|
||||
this.formData.siteId = siteId || this.getRouteSiteId()
|
||||
}
|
||||
},
|
||||
//获取站点列表
|
||||
getZdList(){
|
||||
this.searchLoading=true
|
||||
getAllSites().then(response => {
|
||||
this.siteList = response?.data || []
|
||||
}).finally(() => {this.searchLoading=false})
|
||||
},
|
||||
getDetail(id){
|
||||
getMqttDetail(id).then(response => {
|
||||
@ -124,8 +132,6 @@ export default {
|
||||
// 清空所有数据
|
||||
this.formData= {
|
||||
id:'',//设备唯一标识
|
||||
siteId:'',
|
||||
qos:'',
|
||||
mqttTopic:'',
|
||||
topicName:''
|
||||
}
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
<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-select v-model="form.siteId" placeholder="请选择换电站名称" :loading="searchLoading" loading-text="正在加载数据" clearable>
|
||||
<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 label="订阅topic">
|
||||
<el-input
|
||||
v-model="form.mqttTopic"
|
||||
@ -85,21 +90,12 @@
|
||||
|
||||
<script>
|
||||
import {deleteMqtt,getMqttList} from '@/api/ems/site'
|
||||
import {getAllSites} from '@/api/ems/zddt'
|
||||
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:{
|
||||
@ -107,6 +103,8 @@ export default {
|
||||
topicName:'',
|
||||
mqttTopic:''
|
||||
},
|
||||
siteList:[],
|
||||
searchLoading:false,
|
||||
loading:false,
|
||||
tableData:[],
|
||||
pageSize:10,//分页栏当前每个数据总数
|
||||
@ -115,9 +113,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
hasValidSiteId(siteId) {
|
||||
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
|
||||
},
|
||||
// 分页
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
@ -138,19 +133,22 @@ export default {
|
||||
},
|
||||
onReset(){
|
||||
this.form={
|
||||
siteId:this.form.siteId,
|
||||
siteId:'',
|
||||
topicName:'',
|
||||
mqttTopic:''
|
||||
}
|
||||
this.pageNum =1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
//获取站点列表
|
||||
getZdList(){
|
||||
this.searchLoading=true
|
||||
return getAllSites().then(response => {
|
||||
this.siteList = response?.data || []
|
||||
// if( this.siteList.length>0 ) this.siteId = this.siteList[0].siteId
|
||||
}).finally(() => {this.searchLoading=false})
|
||||
},
|
||||
getData(){
|
||||
if (!this.form.siteId) {
|
||||
this.tableData = []
|
||||
this.totalSize = 0
|
||||
return
|
||||
}
|
||||
this.loading=true;
|
||||
const {mqttTopic,topicName,siteId} = this.form;
|
||||
const {pageNum,pageSize} = this;
|
||||
@ -160,7 +158,7 @@ export default {
|
||||
}).finally(() => {this.loading=false})
|
||||
},
|
||||
addPowerConfig(id=''){
|
||||
this.$refs.addMqtt.showDialog(id, this.form.siteId);
|
||||
this.$refs.addMqtt.showDialog(id);
|
||||
},
|
||||
deleteMqtt(row){
|
||||
this.$confirm(`确认要删除该配置吗?`, {
|
||||
@ -195,7 +193,8 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.form.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
|
||||
this.loading=true
|
||||
this.getZdList()
|
||||
this.getData()
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,9 @@
|
||||
<div class="items-container">
|
||||
<div class="item-title">站点:</div>
|
||||
<div class="item-content">
|
||||
<el-input v-model="siteId" placeholder="请先在顶部选择站点" disabled />
|
||||
<el-select v-model="siteId" :disabled="mode === 'edit'" placeholder="请选择站点" :loading="searchLoading" loading-text="正在加载数据">
|
||||
<el-option :label="item.siteName" :value="item.siteId" v-for="(item,index) in siteList" :key="index+'zdxeSelect'"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="items-container">
|
||||
@ -101,12 +103,15 @@
|
||||
</template>
|
||||
<script>
|
||||
import {addPriceConfig,editPriceConfig,detailPriceConfig} from '@/api/ems/powerTariff'
|
||||
import {getAllSites} from '@/api/ems/zddt'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
mode:'',
|
||||
id:'',
|
||||
searchLoading:false,
|
||||
siteId:'',
|
||||
siteList:[],
|
||||
powerDate:'',//时间
|
||||
//尖-peak,峰-high,平-flat,谷=valley
|
||||
priceTypeOptions:[{
|
||||
@ -132,10 +137,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRouteSiteId() {
|
||||
const siteId = this.$route?.query?.siteId
|
||||
return siteId === undefined || siteId === null ? '' : String(siteId).trim()
|
||||
},
|
||||
addRow(){
|
||||
this.hoursOptions.push({
|
||||
startTime:'',
|
||||
@ -146,7 +147,15 @@ export default {
|
||||
deleteRow(index){
|
||||
this.hoursOptions.splice(index,1)
|
||||
},
|
||||
showDialog(id, siteId = ''){
|
||||
//获取站点列表
|
||||
getZdList(){
|
||||
this.searchLoading=true
|
||||
getAllSites().then(response => {
|
||||
this.siteList = response?.data || []
|
||||
}).finally(() => {this.searchLoading=false})
|
||||
},
|
||||
showDialog(id){
|
||||
this.getZdList()
|
||||
this.id = id
|
||||
if(id) {
|
||||
this.mode='edit'
|
||||
@ -163,12 +172,11 @@ export default {
|
||||
}).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.siteId === '') return this.$message.error('请选择站点')
|
||||
if(this.powerDate === '') return this.$message.error('请选择时间')
|
||||
let priceArr=[]
|
||||
this.priceTypeOptions.forEach(item=>{
|
||||
@ -236,6 +244,8 @@ export default {
|
||||
this.mode=''
|
||||
this.id=''
|
||||
this.siteId=''
|
||||
this.siteList=[]
|
||||
this.searchLoading=false
|
||||
this.powerDate=''
|
||||
this.hoursOptions=[]
|
||||
this.priceTypeOptions.forEach(item=>{
|
||||
@ -293,4 +303,4 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
@ -1,6 +1,11 @@
|
||||
<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-select v-model="siteId" placeholder="请选择换电站名称" :loading="searchLoading" loading-text="正在加载数据" @change="onSearch" clearable>
|
||||
<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 label="年份选择">
|
||||
<el-date-picker
|
||||
v-model="defaultYear"
|
||||
@ -28,7 +33,7 @@
|
||||
:key="item.id"
|
||||
>
|
||||
<div slot="header" class="time-range-header">
|
||||
<span class="card-title">{{item.siteId || ''}}-{{item.month}}月电价时段划分</span>
|
||||
<span class="card-title">{{siteList.find(i=>i.siteId===item.siteId).siteName || 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>
|
||||
@ -56,28 +61,21 @@
|
||||
|
||||
<script>
|
||||
import {energyPriceConfig,listPriceConfig} from '@/api/ems/powerTariff'
|
||||
import {getAllSites} from '@/api/ems/zddt'
|
||||
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,
|
||||
searchLoading:false,
|
||||
siteId:'',
|
||||
siteList:[],
|
||||
tableData:[],
|
||||
tableTotal:0,
|
||||
defaultYear:'',
|
||||
@ -95,9 +93,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
hasValidSiteId(siteId) {
|
||||
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
|
||||
},
|
||||
resetTableData(){
|
||||
this.tableData=[]
|
||||
this.tableTotal=0
|
||||
@ -107,16 +102,19 @@ export default {
|
||||
onSearch(){
|
||||
this.getData(true)
|
||||
},
|
||||
//获取站点列表
|
||||
getZdList(){
|
||||
this.searchLoading=true
|
||||
return getAllSites().then(response => {
|
||||
this.siteList = response?.data || []
|
||||
if( this.siteList.length>0 ) this.siteId = this.siteList[0].siteId
|
||||
}).finally(() => {this.searchLoading=false})
|
||||
},
|
||||
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()
|
||||
@ -129,7 +127,7 @@ export default {
|
||||
}).finally(() => {this.loading=false})
|
||||
},
|
||||
addPowerConfig(id=''){
|
||||
this.$refs.addPowerTariff.showDialog(id, this.siteId);
|
||||
this.$refs.addPowerTariff.showDialog(id);
|
||||
},
|
||||
deletePowerConfig(row){
|
||||
this.$confirm(`确认要删除${row.month}月的电价配置吗?`, {
|
||||
@ -165,8 +163,10 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.defaultYear = new Date()
|
||||
this.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
|
||||
this.getData(true)
|
||||
this.loading=true
|
||||
this.getZdList().then(()=>{
|
||||
this.getData(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
815
src/views/ems/site/sbbh/AddDevice.vue
Normal file
815
src/views/ems/site/sbbh/AddDevice.vue
Normal file
@ -0,0 +1,815 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-loading="loading"
|
||||
width="90%"
|
||||
:visible.sync="dialogTableVisible"
|
||||
class="ems-dialog"
|
||||
title="保护方案"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<el-form
|
||||
v-loading="loading > 0"
|
||||
ref="addTempForm"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
size="medium"
|
||||
label-width="140px"
|
||||
>
|
||||
<el-form-item label="站点" prop="siteId">
|
||||
<el-select
|
||||
v-model="formData.siteId"
|
||||
placeholder="请选择"
|
||||
:style="{ width: '50%' }"
|
||||
@change="changeType"
|
||||
>
|
||||
<el-option
|
||||
:label="item.siteName"
|
||||
:value="item.siteId"
|
||||
v-for="(item, index) in siteList"
|
||||
:key="index + 'siteOptions'"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备保护名称" prop="faultName">
|
||||
<el-input
|
||||
v-model="formData.faultName"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
:style="{ width: '50%' }"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="处理方案描述" prop="description">
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
:style="{ width: '50%' }"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否告警" prop="isAlert">
|
||||
<el-checkbox
|
||||
v-model="formData.isAlert"
|
||||
:true-label="1"
|
||||
:false-label="0"
|
||||
></el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="告警等级" prop="faultLevel">
|
||||
<el-radio-group v-model="formData.faultLevel" :style="{ width: '50%' }" :disabled="mode === 'edit'">
|
||||
<el-radio :label="1">等级1</el-radio>
|
||||
<el-radio :label="2">等级2</el-radio>
|
||||
<el-radio :label="3">等级3</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div class="items-container">
|
||||
<div class="item-title">
|
||||
保护前提:
|
||||
<div style="display: inline-block; margin-left: 20px">
|
||||
<el-form-item label="延时" prop="faultDelaySeconds">
|
||||
<el-input
|
||||
v-model="formData.faultDelaySeconds"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
:style="{ width: '200px', display: 'inline-block' }"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
@click.native.prevent="addRow('protectionSettings')"
|
||||
block
|
||||
type="primary"
|
||||
size="mini"
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
新增保护前提
|
||||
</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>
|
||||
<div>释放值</div>
|
||||
<div>关系</div>
|
||||
<div>操作</div>
|
||||
</div>
|
||||
<div
|
||||
class="time-lists"
|
||||
v-for="(item, index) in protectionSettings"
|
||||
:key="'protectionSettings' + index"
|
||||
>
|
||||
<div>
|
||||
<el-cascader
|
||||
v-model="item.deviceId"
|
||||
:options="childOptions"
|
||||
:props="props"
|
||||
:show-all-levels="false"
|
||||
:ref="'protectionSettings'+index"
|
||||
@change="(v)=>handleChange(v,'protectionSettings',index)"
|
||||
></el-cascader>
|
||||
</div>
|
||||
<div>
|
||||
<el-autocomplete
|
||||
v-model="item.point"
|
||||
placeholder="请输入点位"
|
||||
clearable
|
||||
:fetch-suggestions="
|
||||
(q, c) =>
|
||||
querySearchAsync(q, c, index, 'protectionSettings')
|
||||
"
|
||||
@select="(v) => handleSelect(v, index, 'protectionSettings')"
|
||||
></el-autocomplete>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-select v-model="item.faultOperator" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(value, key) in comparisonOperatorOptions"
|
||||
:key="key + 'faultOperator'"
|
||||
:label="key"
|
||||
:value="value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div>
|
||||
<el-input placeholder="请输入故障值" v-model="item.faultValue">
|
||||
</el-input>
|
||||
</div>
|
||||
<div>
|
||||
<el-select v-model="item.releaseOperator" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(value, key) in comparisonOperatorOptions"
|
||||
:key="key + 'releaseOperator'"
|
||||
:label="key"
|
||||
:value="value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div>
|
||||
<el-input
|
||||
placeholder="请输入释放值"
|
||||
v-model="item.releaseValue"
|
||||
>
|
||||
</el-input>
|
||||
</div>
|
||||
<div>
|
||||
<el-select v-model="item.relationNext" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(value, key) in relationWithPoint"
|
||||
:key="key + 'relation'"
|
||||
:label="key"
|
||||
:value="value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
@click.native.prevent="deleteRow(index,'protectionSettings')"
|
||||
type="warning"
|
||||
size="mini"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="items-container">
|
||||
<div class="item-title">
|
||||
保护方案:
|
||||
<div style="display: inline-block; margin-left: 20px">
|
||||
<el-form-item label="延时" prop="releaseDelaySeconds">
|
||||
<el-input
|
||||
v-model="formData.releaseDelaySeconds"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
:style="{ width: '200px', display: 'inline-block' }"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
@click.native.prevent="addRow('protectionPlan')"
|
||||
block
|
||||
type="primary"
|
||||
size="mini"
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
新增保护方案
|
||||
</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>
|
||||
</div>
|
||||
<div
|
||||
class="time-lists"
|
||||
v-for="(item, index) in protectionPlan"
|
||||
:key="'protectionPlan' + index"
|
||||
>
|
||||
<div>
|
||||
<el-cascader
|
||||
v-model="item.deviceId"
|
||||
:show-all-levels="false"
|
||||
:options="childOptions"
|
||||
:props="props"
|
||||
:ref="'protectionPlan'+index"
|
||||
@change="(v)=>handleChange(v,'protectionPlan',index)"
|
||||
></el-cascader>
|
||||
</div>
|
||||
<div>
|
||||
<el-autocomplete
|
||||
v-model="item.point"
|
||||
placeholder="请输入点位"
|
||||
clearable
|
||||
:fetch-suggestions="
|
||||
(q, c) => querySearchAsync(q, c, index, 'protectionPlan')
|
||||
"
|
||||
@select="(v) => handleSelect(v, index, 'protectionPlan')"
|
||||
></el-autocomplete>
|
||||
</div>
|
||||
|
||||
<div>=</div>
|
||||
<div>
|
||||
<el-input placeholder="请输入故障值" v-model="item.value">
|
||||
</el-input>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
@click.native.prevent="deleteRow(index,'protectionPlan')"
|
||||
type="warning"
|
||||
size="mini"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 {mapState} from "vuex";
|
||||
import {getAllSites} from "@/api/ems/zddt";
|
||||
import {validText} from "@/utils/validate";
|
||||
import {addProtectPlan, getDeviceListBySiteAndCategory, getProtectPlan, updateProtectPlan} from "@/api/ems/site";
|
||||
import {getAllDeviceCategory, pointFuzzyQuery} from "@/api/ems/search";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const validateText = (rule, value, callback) => {
|
||||
if (value !== "" && !validText(value)) {
|
||||
callback(new Error("只能输入中文、英文、数字和特殊字符!"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
props: {
|
||||
// emitPath: false,//在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值
|
||||
lazy: true,
|
||||
lazyLoad: (node, resolve) => {
|
||||
console.log('---------node', node)
|
||||
this.getDeviceList(node, resolve)
|
||||
}
|
||||
},
|
||||
mode: '',
|
||||
loading: 0,
|
||||
childOptions: [],
|
||||
protectionSettings: [],
|
||||
protectionPlan: [],
|
||||
dialogTableVisible: false,
|
||||
siteList: [], //站点列表 从接口获取数据
|
||||
formData: {
|
||||
id: "", //设备唯一标识
|
||||
siteId: "", //站点ID
|
||||
faultName: "", //设备保护名称
|
||||
isAlert: 0, //是否告警
|
||||
faultLevel: 1, //告警等级
|
||||
faultDelaySeconds: "", //故障延时
|
||||
releaseDelaySeconds: "", //释放延时
|
||||
description: '',//方案描述
|
||||
},
|
||||
rules: {
|
||||
siteId: [
|
||||
{
|
||||
required: true,
|
||||
message: "请选择站点",
|
||||
trigger: ["blur", "change"],
|
||||
},
|
||||
],
|
||||
faultName: [
|
||||
{required: true, message: "请输入设备保护名称", trigger: "blur"},
|
||||
],
|
||||
isAlert: [
|
||||
{required: true, message: "请选择是否告警", trigger: "blur"},
|
||||
],
|
||||
description: [
|
||||
{required: true, message: "请输入设备描述", trigger: "blur"},
|
||||
{validator: validateText, trigger: "blur"},
|
||||
],
|
||||
faultDelaySeconds: [
|
||||
{required: true, message: "请输入保护前提延时", trigger: "blur"},
|
||||
{validator: validateText, trigger: "blur"},
|
||||
],
|
||||
releaseDelaySeconds: [
|
||||
{required: true, message: "请输入保护方案延时", trigger: "blur"},
|
||||
{validator: validateText, trigger: "blur"},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
communicationStatusOptions: (state) =>
|
||||
state?.ems?.communicationStatusOptions || {},
|
||||
deviceTypeOptions: (state) => state?.ems?.deviceTypeOptions || {},
|
||||
comparisonOperatorOptions: (state) =>
|
||||
state?.ems?.comparisonOperatorOptions || {},
|
||||
relationWithPoint: (state) => state?.ems?.relationWithPoint || {},
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
open(id, siteId) {
|
||||
this.dialogTableVisible = true
|
||||
this.getZdList();
|
||||
this.getDeviceCategoryList().then(() => {
|
||||
if (id && siteId) {
|
||||
// this.getDeviceList('PCS', siteId)
|
||||
// this.getDeviceList('STACK', siteId)
|
||||
}
|
||||
});
|
||||
if (id) {
|
||||
this.formData.id = id
|
||||
this.mode = 'edit'
|
||||
getProtectPlan(id).then(response => {
|
||||
const data = response?.data || {}
|
||||
this.formData = {
|
||||
id,
|
||||
siteId: data?.siteId || '', //站点ID
|
||||
faultName: data?.faultName || '', //设备保护名称
|
||||
isAlert: data?.isAlert || 0, //是否告警
|
||||
faultLevel: data?.faultLevel || 1, //告警等级
|
||||
faultDelaySeconds: data?.faultDelaySeconds || "", //故障延时
|
||||
releaseDelaySeconds: data?.releaseDelaySeconds || "", //释放延时
|
||||
description: data?.description || '',//方案描述
|
||||
}
|
||||
const plan = (JSON.parse(data?.protectionPlan || [])).map(item => {
|
||||
const index = this.childOptions.findIndex(i => i.value === item.deviceCategory)
|
||||
if (index > -1) {
|
||||
!this.childOptions[index].children.find(i => i.value === item.deviceId) && this.childOptions[index].children.push({
|
||||
value: item.deviceId,
|
||||
label: item.deviceName,
|
||||
leaf: true
|
||||
})
|
||||
}
|
||||
return Object.assign({}, item, {
|
||||
deviceId: [item.deviceCategory || '', item.deviceId || ''],
|
||||
deviceName: [item.categoryName || '', item.deviceName || ''],
|
||||
})
|
||||
})
|
||||
const settings = (JSON.parse(data?.protectionSettings || [])).map(item => {
|
||||
const index = this.childOptions.findIndex(i => i.value === item.deviceCategory)
|
||||
if (index > -1) {
|
||||
!this.childOptions[index].children.find(i => i.value === item.deviceId) && this.childOptions[index].children.push({
|
||||
value: item.deviceId,
|
||||
label: item.deviceName,
|
||||
leaf: true
|
||||
})
|
||||
}
|
||||
return Object.assign({}, item, {
|
||||
deviceId: [item.deviceCategory || '', item.deviceId || ''],
|
||||
deviceName: [item.categoryName || '', item.deviceName || ''],
|
||||
})
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.protectionPlan.splice(0, 0, ...plan)
|
||||
this.protectionSettings.splice(0, 0, ...settings)
|
||||
})
|
||||
console.log('获取设备保护详情并初始化', this.formData, this.protectionPlan, this.protectionSettings, this.childOptions)
|
||||
|
||||
})
|
||||
} else {
|
||||
this.mode = 'add'
|
||||
}
|
||||
},
|
||||
// 新增设备保护前提、设备保护方案
|
||||
addRow(type) {
|
||||
const item = type === 'protectionSettings' ? {
|
||||
deviceId: [],//设备ID
|
||||
deviceName: [],
|
||||
deviceCategory: "",//设备类型 英文
|
||||
categoryName: '',//设备类型名称 中文
|
||||
point: "",//点位 英文
|
||||
pointName: "",//点位 中文
|
||||
faultValue: "",//故障值
|
||||
releaseValue: "",//释放值
|
||||
faultOperator: "",//故障值比较关系
|
||||
releaseOperator: "",//释放值比较关系
|
||||
relationNext: "",//与下一个点位的关系
|
||||
} : {
|
||||
deviceId: [],
|
||||
deviceName: [],
|
||||
deviceCategory: "",//设备类型 英文
|
||||
categoryName: '',//设备类型名称 中文
|
||||
point: "",
|
||||
pointName: "",
|
||||
value: "",//设置值
|
||||
}
|
||||
// this.$set(this[type], this[type].length, item);
|
||||
this[type].splice(this[type].length, 0, item)
|
||||
console.log('新增设备保护前提、方案', type, this[type])
|
||||
},
|
||||
// 删除设备保护前提、设备保护方案
|
||||
deleteRow(index, type) {
|
||||
this[type].splice(index, 1);
|
||||
},
|
||||
|
||||
// 设备保护前提、设备保护方案点位选择
|
||||
querySearchAsync(query, cb, index, type) {
|
||||
console.log("查询数据", query, index);
|
||||
if (!this.formData.siteId || !this[type][index].deviceCategory) {
|
||||
this.$message({
|
||||
type: "warning",
|
||||
message: "请先选择站点和设备",
|
||||
});
|
||||
return cb([]);
|
||||
}
|
||||
pointFuzzyQuery({
|
||||
siteIds: [this.formData.siteId],
|
||||
deviceCategory: this[type][index].deviceCategory,
|
||||
pointName: query,
|
||||
}).then((response) => {
|
||||
const data = response?.data || [];
|
||||
cb(
|
||||
data.map((item) => {
|
||||
return {name: item, value: item};
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
// 点位选择
|
||||
handleSelect(data, index, type) {
|
||||
console.log('选择点位', data, index, type)
|
||||
// this.$set(this[type], index, Object.assign({},this[type][index],{
|
||||
// point:data.value,
|
||||
// pointName:data.value,
|
||||
// }));
|
||||
let line = Object.assign({}, this[type][index], {
|
||||
point: data.value,
|
||||
pointName: data.value,
|
||||
})
|
||||
this[type].splice(index, 1, line);
|
||||
console.log('选择点位配置完成', this[type][index])
|
||||
},
|
||||
|
||||
// 获取设备类别-不区分站点
|
||||
getDeviceCategoryList() {
|
||||
this.loading += 1;
|
||||
return getAllDeviceCategory()
|
||||
.then((response) => {
|
||||
const data = (response?.data || []);
|
||||
this.$set(this, 'childOptions', [])
|
||||
let arr = []
|
||||
data.forEach((item) => {
|
||||
arr.push({
|
||||
value: item.code,
|
||||
label: item.name,
|
||||
leaf: false,
|
||||
children: []
|
||||
})
|
||||
})
|
||||
this.childOptions.splice(0, 0, ...arr)
|
||||
console.log('获取设备类型', data, this.childOptions)
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading -= 1;
|
||||
});
|
||||
},
|
||||
//获取设备列表-区分站点
|
||||
getDeviceList(node, resolve) {
|
||||
if (!this.formData.siteId) {
|
||||
this.$message({
|
||||
type: "warning",
|
||||
message: "请先选择站点",
|
||||
});
|
||||
return resolve([])
|
||||
}
|
||||
getDeviceListBySiteAndCategory({
|
||||
siteId: this.formData.siteId,
|
||||
deviceCategory: node.value
|
||||
}).then((response) => {
|
||||
const data = (response?.data || []).map(item => {
|
||||
return {
|
||||
label: item.deviceName,
|
||||
value: item.id,
|
||||
leaf: true
|
||||
}
|
||||
})
|
||||
resolve(data)
|
||||
}).catch(() => {
|
||||
resolve([])
|
||||
})
|
||||
},
|
||||
//更新站点下面的设备列表
|
||||
updateSiteDeviceList() {
|
||||
this.childOptions.forEach(item => {
|
||||
const length = item.children.length
|
||||
item.children.splice(0, length)
|
||||
})
|
||||
},
|
||||
//选中设备类型、设备
|
||||
handleChange(data, type, index) {
|
||||
console.log('设置选中设备类型、设备', data, type, index)
|
||||
const {label = '', parent = {}} = this.$refs[type + index][0].getCheckedNodes()[0]
|
||||
console.log('选中设备的名称', parent.label, label)
|
||||
const item = Object.assign({}, this[type][index], {
|
||||
deviceId: data,
|
||||
deviceName: [parent.label, label],
|
||||
deviceCategory: data[0],
|
||||
categoryName: parent.label,
|
||||
pointName: '',
|
||||
point: ''
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
// this.$set(this[type], index, item);
|
||||
this[type].splice(index, 1, item);
|
||||
})
|
||||
console.log('设置选中设备类型、设备配置完成', this[type][index])
|
||||
},
|
||||
|
||||
//获取站点列表
|
||||
getZdList() {
|
||||
this.loading += 1;
|
||||
getAllSites()
|
||||
.then((response) => {
|
||||
this.siteList = response?.data || [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading -= 1;
|
||||
});
|
||||
},
|
||||
// 切换站点
|
||||
// 重新获取设备列表
|
||||
// 清空选中的设备、点位信息
|
||||
changeType() {
|
||||
//获取当前站点下的pcs和bms
|
||||
this.updateSiteDeviceList()
|
||||
if (this.protectionSettings.length > 0) {
|
||||
const list = this.protectionSettings
|
||||
list.forEach((item) => {
|
||||
item.point = ""
|
||||
item.pointName = ""
|
||||
item.deviceId = []
|
||||
item.deviceName = []
|
||||
item.categoryName = ''
|
||||
item.deviceCategory = ''
|
||||
});
|
||||
// this.$set(this,'protectionSettings',list)
|
||||
this.$nextTick(() => {
|
||||
this.protectionSettings.splice(0, this.protectionSettings.length, ...list)
|
||||
})
|
||||
}
|
||||
if (this.protectionPlan.length > 0) {
|
||||
const list = this.protectionPlan
|
||||
list.forEach((item) => {
|
||||
item.point = ""
|
||||
item.pointName = ""
|
||||
item.deviceId = []
|
||||
item.deviceName = []
|
||||
item.categoryName = ''
|
||||
item.deviceCategory = ''
|
||||
});
|
||||
// this.$set(this,'protectionPlan',list)
|
||||
this.$nextTick(() => {
|
||||
this.protectionPlan.splice(0, this.protectionPlan.length, ...list)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
saveDialog() {
|
||||
function getToastMsg(name, type, index) {
|
||||
return {
|
||||
protectionSettings: {
|
||||
deviceId: `请选择保护前提第${index}行的设备`,//设备ID
|
||||
deviceCategory: `请选择保护前提第${index}行的设备类型`,//设备类型 英文
|
||||
categoryName: `请选择保护前提第${index}行的设备类型`,//设备类型名称 中文
|
||||
point: `请选择保护前提第${index}行的点位`,//点位 英文
|
||||
pointName: `请选择保护前提第${index}行的点位`,//点位 中文
|
||||
faultValue: `请输入保护前提第${index}行的故障值`,//故障值
|
||||
releaseValue: `请输入保护前提第${index}行的释放值`,//释放值
|
||||
faultOperator: `请选择保护前提第${index}行的故障值比较关系`,//故障值比较关系
|
||||
releaseOperator: `请选择保护前提第${index}行的释放值比较关系`,//释放值比较关系
|
||||
relationNext: `请选择保护前提第${index}行与下一个点位的关系`,//与下一个点位的关系
|
||||
},
|
||||
protectionPlan: {
|
||||
deviceId: `请选择保护方案第${index}行的设备`,
|
||||
deviceCategory: `请选择保护方案第${index}行的设备类型`,//设备类型 英文
|
||||
categoryName: `请选择保护方案第${index}行的设备类型`,//设备类型名称 中文
|
||||
point: `请选择保护方案第${index}行的点位`,
|
||||
pointName: `请选择保护方案第${index}行的点位`,
|
||||
value: `请输入保护方案第${index}行的故障值`,//设置值
|
||||
}
|
||||
}[type][name]
|
||||
}
|
||||
|
||||
this.$refs.addTempForm.validate((valid) => {
|
||||
if (!valid) return;
|
||||
const {
|
||||
id = "", //设备唯一标识
|
||||
siteId = "", //站点ID
|
||||
faultName = "", //设备保护名称
|
||||
isAlert = 0, //是否告警
|
||||
faultLevel = 1, //告警等级
|
||||
faultDelaySeconds = "", //故障延时
|
||||
releaseDelaySeconds = "", //释放延时
|
||||
description = "",//方案描述
|
||||
} = this.formData;
|
||||
const {protectionSettings, protectionPlan} = this
|
||||
let protectionSettingsValidateStatus = true, protectionPlanValidateStatus = true
|
||||
for (let i = 0; i < protectionSettings.length; i++) {
|
||||
let valueMap = Object.entries(protectionSettings[i]);
|
||||
for (let inner = 0; inner < valueMap.length; inner++) {
|
||||
const key = valueMap[inner][0], value = valueMap[inner][1]
|
||||
if (key === 'relationNext') {
|
||||
if (protectionSettings[i + 1] && !value) {//有下一个点位
|
||||
this.$message.error(getToastMsg(key, 'protectionSettings', i + 1))
|
||||
protectionSettingsValidateStatus = false
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if (![0, '0'].includes(value) && !value) {
|
||||
this.$message.error(getToastMsg(key, 'protectionSettings', i + 1))
|
||||
protectionSettingsValidateStatus = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!protectionSettingsValidateStatus) break
|
||||
}
|
||||
|
||||
for (let i = 0; i < protectionPlan.length; i++) {
|
||||
let valueMap = Object.entries(protectionPlan[i]);
|
||||
for (let inner = 0; inner < valueMap.length; inner++) {
|
||||
const key = valueMap[inner][0], value = valueMap[inner][1]
|
||||
if (key === 'relationNext') {
|
||||
if (protectionPlan[i + 1] && !value) {//有下一个点位
|
||||
this.$message.error(getToastMsg(key, 'protectionPlan', i + 1))
|
||||
protectionPlanValidateStatus = false
|
||||
break
|
||||
} else {
|
||||
// protectionPlan[i][key] = ''//清空选择的关系
|
||||
}
|
||||
} else {
|
||||
if (![0, '0'].includes(value) && !value) {
|
||||
this.$message.error(getToastMsg(key, 'protectionPlan', i + 1))
|
||||
protectionPlanValidateStatus = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!protectionPlanValidateStatus) break
|
||||
}
|
||||
|
||||
if (!protectionSettingsValidateStatus || !protectionPlanValidateStatus) return
|
||||
const settings = protectionSettings.map(item => {
|
||||
return Object.assign({}, item, {
|
||||
deviceId: item.deviceId[1],
|
||||
deviceName: item.deviceName[1],
|
||||
})
|
||||
})
|
||||
const plan = protectionPlan.map(item => {
|
||||
return Object.assign({}, item, {
|
||||
deviceId: item.deviceId[1],
|
||||
deviceName: item.deviceName[1],
|
||||
})
|
||||
})
|
||||
this.loading += 1;
|
||||
const params = {
|
||||
siteId,
|
||||
faultName,
|
||||
isAlert,
|
||||
faultLevel,
|
||||
faultDelaySeconds,
|
||||
releaseDelaySeconds,
|
||||
description,
|
||||
protectionSettings: JSON.stringify(settings),
|
||||
protectionPlan: JSON.stringify(plan),
|
||||
}
|
||||
if (this.mode === "add") {
|
||||
addProtectPlan(params)
|
||||
.then((response) => {
|
||||
if (response.code === 200) {
|
||||
//新增成功
|
||||
// 关闭弹窗 更新表格
|
||||
this.$emit("update");
|
||||
this.closeDialog();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading -= 1;
|
||||
});
|
||||
} else {
|
||||
params.id = id
|
||||
updateProtectPlan(params)
|
||||
.then((response) => {
|
||||
if (response.code === 200) {
|
||||
//新增成功
|
||||
// 关闭弹窗 更新表格
|
||||
this.$emit("update");
|
||||
this.closeDialog();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading -= 1;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
closeDialog() {
|
||||
this.$emit("clear");
|
||||
// 清空所有数据
|
||||
for (let key in this.formData) {
|
||||
this.formData[key] = key === 'isAlert' ? 0 : key === 'faultLevel' ? 1 : ''
|
||||
}
|
||||
this.$refs.addTempForm.resetFields();
|
||||
this.$set(this, 'protectionSettings', [])
|
||||
this.$set(this, 'protectionPlan', [])
|
||||
this.$set(this, 'childOptions', [])
|
||||
this.dialogTableVisible = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.items-container {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.item-title {
|
||||
line-height: 16px;
|
||||
padding: 10px 0;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,185 +1,160 @@
|
||||
<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
|
||||
<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-select
|
||||
v-model="form.siteId"
|
||||
placeholder="请选择换电站名称"
|
||||
:loading="searchLoading"
|
||||
loading-text="正在加载数据"
|
||||
clearable
|
||||
>
|
||||
<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 label="故障名称">
|
||||
<el-input
|
||||
v-model="form.faultName"
|
||||
clearable
|
||||
placeholder="请输入故障名称"
|
||||
style="width: 220px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
style="width: 150px"
|
||||
></el-input>
|
||||
</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" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" @click="addDevice" native-type="button"
|
||||
>新增设备</el-button
|
||||
>
|
||||
<el-table
|
||||
class="common-table"
|
||||
:data="tableData"
|
||||
stripe
|
||||
max-height="600px"
|
||||
style="width: 100%; margin-top: 25px"
|
||||
>
|
||||
<el-table-column prop="siteId" label="站点" width="100"> </el-table-column>
|
||||
<el-table-column prop="faultName" label="设备保护名称" width="100"> </el-table-column>
|
||||
<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="处理方案描述" width="200" show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
<el-table-column prop="protectionSettings" label="保护前提" show-overflow-tooltip width="400">
|
||||
<template slot-scope="scope">
|
||||
<div v-html="handleProtectionSettings(scope.row.protectionSettings)"></div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="faultDelaySeconds" label="保护前提延时(s)" width="120">
|
||||
</el-table-column>
|
||||
<el-table-column prop="protectionPlan" label="保护方案" show-overflow-tooltip width="200">
|
||||
<template slot-scope="scope">
|
||||
<div v-html="handleProtectionPlan(scope.row.protectionPlan)"></div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="releaseDelaySeconds" label="保护方案延时(s)" width="120">
|
||||
</el-table-column>
|
||||
<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"
|
||||
style="margin-top: 15px; text-align: center"
|
||||
>
|
||||
</el-pagination>
|
||||
<add-device
|
||||
ref="addDevice"
|
||||
@update="getData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { protectPlanList, deleteProtectPlan } from "@/api/ems/site";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import AddPlan from "./AddPlan.vue";
|
||||
|
||||
import {
|
||||
protectPlanList,
|
||||
deleteProtectPlan,
|
||||
} from "@/api/ems/site";
|
||||
import { getAllSites } from "@/api/ems/zddt";
|
||||
import AddDevice from "./AddDevice.vue";
|
||||
export default {
|
||||
name: "SBBH",
|
||||
components: { AddPlan },
|
||||
mixins: [getQuerySiteId],
|
||||
components: { AddDevice },
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
faultName: "",
|
||||
form:{
|
||||
siteId:'',
|
||||
faultName:''
|
||||
},
|
||||
loading: false,
|
||||
searchLoading: false,
|
||||
siteList: [],
|
||||
tableData: [],
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
totalSize: 0,
|
||||
pageSize: 10, //分页栏当前每个数据总数
|
||||
pageNum: 1, //分页栏当前页数
|
||||
totalSize: 0, //table表格数据总数
|
||||
dialogTableVisible: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
handleProtectionSettings(data){
|
||||
if(!data || !JSON.parse(data)) return
|
||||
const arr = JSON.parse(data),
|
||||
str= arr.map((item,index)=>{
|
||||
const {categoryName='',deviceId='',point='',faultOperator='',faultValue='',releaseOperator='',releaseValue='',relationNext=''} = item
|
||||
return `<div>${index+1}、 <span>${categoryName ? categoryName + '-' : ''}${deviceId ? deviceId + '-' : ''}${ point || ''}</span> <span>故障:${faultOperator || ''}${ faultValue || ''}</span> <span>释放:${releaseOperator || ''}${releaseValue || ''}</span> ${arr[index+1] ? '<span>关系:'+(relationNext || '')+'</span>' : ''}</div>`
|
||||
})
|
||||
return str.join('')
|
||||
},
|
||||
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 || !JSON.parse(data)) return
|
||||
const arr = JSON.parse(data),
|
||||
str= arr.map((item,index)=>{
|
||||
const {categoryName='',deviceId='',point='',value=''} = item
|
||||
return `<div>${index+1}、 <span>${categoryName ? categoryName + '-' : ''}${deviceId ? deviceId + '-' : ''}${ point || ''}</span> <span>故障:=${ value || ''}</span> </div>`
|
||||
})
|
||||
return str.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);
|
||||
// 新增设备 展示弹窗
|
||||
addDevice() {
|
||||
this.$refs.addDevice.open()
|
||||
},
|
||||
// 编辑设备
|
||||
editDevice(row) {
|
||||
this.$refs.addPlan.open(row.id, this.siteId);
|
||||
this.$refs.addDevice.open(row.id,row.siteId)
|
||||
},
|
||||
//删除设备
|
||||
deleteDevice(row) {
|
||||
console.log('删除')
|
||||
this.$confirm(`确认要设备保护${row.faultName}吗?`, {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
@ -202,14 +177,19 @@ export default {
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
//只有在废弃成功的情况下会走到这里
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: "删除成功!",
|
||||
});
|
||||
this.getData();
|
||||
//调用接口 更新表格数据
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => {
|
||||
//取消关机
|
||||
});
|
||||
},
|
||||
// 分页
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
this.$nextTick(() => {
|
||||
@ -222,27 +202,25 @@ export default {
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
// 搜索
|
||||
onSearch() {
|
||||
this.pageNum = 1;
|
||||
this.pageNum = 1; //每次搜索从1开始搜索
|
||||
this.getData();
|
||||
},
|
||||
// 重置
|
||||
onReset() {
|
||||
this.form = {
|
||||
this.form={
|
||||
siteId: "",
|
||||
faultName: "",
|
||||
};
|
||||
this.pageNum = 1;
|
||||
}
|
||||
this.pageNum = 1; //每次搜索从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 })
|
||||
const { pageNum, pageSize } = this,{siteId,faultName=''}=this.form;
|
||||
protectPlanList({ siteId, faultName,pageNum, pageSize })
|
||||
.then((response) => {
|
||||
this.tableData = response?.rows || [];
|
||||
this.totalSize = response?.total || 0;
|
||||
@ -251,50 +229,31 @@ export default {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
//获取站点列表
|
||||
getZdList() {
|
||||
this.searchLoading = true;
|
||||
return getAllSites()
|
||||
.then((response) => {
|
||||
this.siteList = response?.data || [];
|
||||
if (this.siteList.length > 0) this.form.siteId = this.siteList[0].siteId;
|
||||
})
|
||||
.finally(() => {
|
||||
this.searchLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loading = true;
|
||||
this.form = {
|
||||
siteId: "",
|
||||
faultName: "",
|
||||
};
|
||||
this.pageNum = 1; //每次搜索从1开始搜索
|
||||
this.getZdList().then(() => {
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
};
|
||||
</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>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@ -3,124 +3,133 @@
|
||||
:show-close="false" destroy-on-close lock-scroll append-to-body width="800px" class="ems-dialog"
|
||||
:title="mode === 'add'?'新增设备':`编辑设备` ">
|
||||
<div v-loading="loading>0">
|
||||
<div class="form-layout" :class="{ 'has-pcs': isPcs }">
|
||||
<el-form v-loading="loading>0" ref="addTempForm" inline :model="formData" :rules="rules" size="medium"
|
||||
label-width="120px" class="device-form base-form">
|
||||
<el-form-item label="站点" prop="siteId">
|
||||
<el-input
|
||||
v-model="formData.siteId"
|
||||
placeholder="请先在顶部选择站点"
|
||||
disabled
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备id" prop="deviceId">
|
||||
<el-input v-model="formData.deviceId" placeholder="请输入" maxlength="60" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称" prop="deviceName">
|
||||
<el-input v-model="formData.deviceName" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备描述" prop="description">
|
||||
<el-input v-model="formData.description" type="textarea" placeholder="请输入" clearable
|
||||
<el-form v-loading="loading>0" ref="addTempForm" inline :model="formData" :rules="rules" size="medium"
|
||||
label-width="120px" class="device-form">
|
||||
<el-form-item label="站点" prop="siteId">
|
||||
<el-select v-model="formData.siteId" placeholder="请选择" :style="{width: '100%'}" @change="changeType">
|
||||
<el-option :label="item.siteName" :value="item.siteId" v-for="(item,index) in siteList"
|
||||
:key="index+'siteOptions'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备id" prop="deviceId">
|
||||
<el-input v-model="formData.deviceId" placeholder="请输入" maxlength="60" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称" prop="deviceName">
|
||||
<el-input v-model="formData.deviceName" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备描述" prop="description">
|
||||
<el-input v-model="formData.description" type="textarea" placeholder="请输入" clearable
|
||||
:style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="工作状态" prop="communicationStatus">
|
||||
<el-select v-model="formData.communicationStatus" placeholder="请选择" :style="{width: '100%'}">
|
||||
<el-option :label="value" :value="key" v-for="(value,key) in communicationStatusOptions"
|
||||
:key="key+'communicationStatusOptions'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备类型" prop="deviceType">
|
||||
<el-select v-model="formData.deviceType" placeholder="请选择" :style="{width: '100%'}">
|
||||
<el-option :label="value" :value="key" v-for="(value,key) in deviceTypeOptions"
|
||||
:key="key+'deviceTypeOptions'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备类别" prop="deviceCategory">
|
||||
<el-select v-model="formData.deviceCategory" placeholder="请选择" :style="{width: '100%'}"
|
||||
@change="changeType">
|
||||
<el-option :label="item.name" :value="item.code" v-for="(item,index) in deviceCategoryList"
|
||||
:key="index+'deviceCategoryList'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="上级设备" prop="parentId" v-if="dccDeviceCategoryList.includes(formData.deviceCategory)">
|
||||
<el-select v-model="formData.parentId"
|
||||
:placeholder="parentDeviceList.length === 0 && !formData.siteId ? '请先选择站点' : '请选择'"
|
||||
:style="{width: '100%'}">
|
||||
<el-option :label="item.deviceName" :value="item.id" v-for="(item,index) in parentDeviceList"
|
||||
:key="index+'parentDeviceList'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="TCP设备的ip地址" prop="ipAddress" v-if="formData.deviceType === 'TCP'">
|
||||
<el-input v-model="formData.ipAddress" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="TCP设备的端口号" prop="ipPort" v-if="formData.deviceType === 'TCP'">
|
||||
<el-input v-model="formData.ipPort" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="从站地址" prop="slaveId" v-if="formData.deviceType === 'TCP'">
|
||||
<el-input v-model="formData.slaveId" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="串口路径" prop="serialPort">
|
||||
<el-input v-model="formData.serialPort" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="波特率" prop="baudRate">
|
||||
<el-input v-model="formData.baudRate" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据位" prop="dataBits">
|
||||
<el-input v-model="formData.dataBits" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="停止位" prop="stopBits">
|
||||
<el-input v-model="formData.stopBits" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="校验位" prop="parity">
|
||||
<el-input v-model="formData.parity" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="图片" prop="pictureUrl">
|
||||
<image-upload :limit="1" :drag="false" @input="uploadImage" :value="formData.pictureUrl"/>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<!-- pcs配置-->
|
||||
<el-form v-if="isPcs" ref="pcsSettingForm" inline :model="pcsSetting" size="medium"
|
||||
label-width="120px" class="device-form" :rules="pcsSettingRules">
|
||||
<div style="font-size: 14px;padding: 10px 0 20px;font-weight: 600;">PCS配置</div>
|
||||
<el-form-item label="开关机地址" prop="pointAddress">
|
||||
<el-input v-model="pcsSetting.pointAddress" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="功率地址" prop="powerAddress">
|
||||
<el-input v-model="pcsSetting.powerAddress" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="开机指令" prop="startCommand">
|
||||
<el-input v-model="pcsSetting.startCommand" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="开机目标功率" prop="startPower">
|
||||
<el-input v-model="pcsSetting.startPower" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="关机指令" prop="stopCommand">
|
||||
<el-input v-model="pcsSetting.stopCommand" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="关机目标功率" prop="stopPower">
|
||||
<el-input v-model="pcsSetting.stopPower" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="电池簇数" prop="clusterNum">
|
||||
<el-input v-model="pcsSetting.clusterNum" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<template v-for="index in parseInt(pcsSetting.clusterNum) || 0">
|
||||
<el-form-item :label="'电池簇'+(index)+'地址'"
|
||||
prop="clusterPointAddress">
|
||||
<el-input v-model="pcsSetting.clusterPointAddress[index-1]" placeholder="请输入" clearable
|
||||
:style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备类型" prop="deviceType">
|
||||
<el-select v-model="formData.deviceType" placeholder="请选择" :style="{width: '100%'}">
|
||||
<el-option :label="value" :value="key" v-for="(value,key) in deviceTypeOptions"
|
||||
:key="key+'deviceTypeOptions'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备类别" prop="deviceCategory">
|
||||
<el-select v-model="formData.deviceCategory" placeholder="请选择" :style="{width: '100%'}"
|
||||
@change="changeType">
|
||||
<el-option :label="item.name" :value="item.code" v-for="(item,index) in deviceCategoryList"
|
||||
:key="index+'deviceCategoryList'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="上级设备" prop="parentId" v-if="dccDeviceCategoryList.includes(formData.deviceCategory)">
|
||||
<el-select v-model="formData.parentId"
|
||||
:placeholder="parentDeviceList.length === 0 && !formData.siteId ? '请先在顶部选择站点' : '请选择'"
|
||||
:style="{width: '100%'}">
|
||||
<el-option :label="item.deviceName" :value="item.id" v-for="(item,index) in parentDeviceList"
|
||||
:key="index+'parentDeviceList'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="TCP设备的ip地址" prop="ipAddress" v-if="formData.deviceType === 'TCP'">
|
||||
<el-input v-model="formData.ipAddress" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="TCP设备的端口号" prop="ipPort" v-if="formData.deviceType === 'TCP'">
|
||||
<el-input v-model="formData.ipPort" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="从站地址" prop="slaveId" v-if="formData.deviceType === 'TCP'">
|
||||
<el-input v-model="formData.slaveId" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="串口路径" prop="serialPort">
|
||||
<el-input v-model="formData.serialPort" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="波特率" prop="baudRate">
|
||||
<el-input v-model="formData.baudRate" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据位" prop="dataBits">
|
||||
<el-input v-model="formData.dataBits" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="停止位" prop="stopBits">
|
||||
<el-input v-model="formData.stopBits" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="校验位" prop="parity">
|
||||
<el-input v-model="formData.parity" placeholder="请输入" clearable :style="{width: '100%'}">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- pcs配置-->
|
||||
<el-form v-if="isPcs" ref="pcsSettingForm" :model="pcsSetting" size="medium"
|
||||
label-position="top" class="pcs-form" :rules="pcsSettingRules">
|
||||
<div class="pcs-form__title">PCS配置</div>
|
||||
<div class="pcs-form__grid">
|
||||
<el-form-item label="开关机地址" prop="pointAddress" class="pcs-form__item">
|
||||
<el-input v-model="pcsSetting.pointAddress" placeholder="请输入" clearable :style="{width: '100%'}" />
|
||||
</el-form-item>
|
||||
<el-form-item label="功率地址" prop="powerAddress" class="pcs-form__item">
|
||||
<el-input v-model="pcsSetting.powerAddress" placeholder="请输入" clearable :style="{width: '100%'}" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开机指令" prop="startCommand" class="pcs-form__item">
|
||||
<el-input v-model="pcsSetting.startCommand" placeholder="请输入" clearable :style="{width: '100%'}" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关机指令" prop="stopCommand" class="pcs-form__item">
|
||||
<el-input v-model="pcsSetting.stopCommand" placeholder="请输入" clearable :style="{width: '100%'}" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开机目标功率" prop="startPower" class="pcs-form__item">
|
||||
<el-input v-model="pcsSetting.startPower" placeholder="请输入" clearable :style="{width: '100%'}" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关机目标功率" prop="stopPower" class="pcs-form__item">
|
||||
<el-input v-model="pcsSetting.stopPower" placeholder="请输入" clearable :style="{width: '100%'}" />
|
||||
</el-form-item>
|
||||
<el-form-item label="倍率" prop="powerMultiplier" class="pcs-form__item">
|
||||
<el-input v-model="pcsSetting.powerMultiplier" placeholder="请输入" clearable :style="{width: '100%'}" />
|
||||
</el-form-item>
|
||||
<el-form-item label="电池簇数" prop="clusterNum" class="pcs-form__item">
|
||||
<el-input v-model="pcsSetting.clusterNum" placeholder="请输入" clearable :style="{width: '100%'}" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="(parseInt(pcsSetting.clusterNum) || 0) > 0" class="pcs-form__cluster">
|
||||
<div class="pcs-form__cluster-title">电池簇地址</div>
|
||||
<template v-for="index in parseInt(pcsSetting.clusterNum) || 0">
|
||||
<el-form-item :key="'clusterAddress' + index" :label="'电池簇' + index + '地址'" prop="clusterPointAddress">
|
||||
<el-input v-model="pcsSetting.clusterPointAddress[index - 1]" placeholder="请输入" clearable :style="{width: '100%'}" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
</el-form>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<el-button @click="closeDialog">取消</el-button>
|
||||
@ -130,6 +139,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import {getAllSites} from '@/api/ems/zddt'
|
||||
import {validText} from '@/utils/validate'
|
||||
import {addDevice, getDeviceDetailInfo, getParentDeviceId, updateDevice} from "@/api/ems/site";
|
||||
import {getAllDeviceCategory} from '@/api/ems/search'
|
||||
@ -167,18 +177,12 @@ export default {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
const validateDecimal = (rule, value, callback) => {
|
||||
if (value !== '' && !/^(0|[1-9]\d*)(\.\d+)?$/.test(value)) {
|
||||
callback(new Error('只能输入非负数字!'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
return {
|
||||
loading: 0,
|
||||
dccDeviceCategoryList: ['CLUSTER', 'BATTERY'],//需要展示上级设备的设备类型
|
||||
dialogTableVisible: false,
|
||||
parentDeviceList: [],//上级设备列表 从接口获取数据
|
||||
siteList: [],//站点列表 从接口获取数据
|
||||
deviceCategoryList: [],//设备类别列表 从接口获取数据
|
||||
formData: {
|
||||
id: '',//设备唯一标识
|
||||
@ -186,6 +190,7 @@ export default {
|
||||
deviceId: '',//设备id
|
||||
deviceName: '',//设备名称
|
||||
description: '',//设备描述
|
||||
communicationStatus: '',//工作状态
|
||||
deviceType: '',//设备类型
|
||||
deviceCategory: '',//设备类别
|
||||
parentId: '',//上级设备id
|
||||
@ -196,6 +201,7 @@ export default {
|
||||
dataBits: '',//数据位
|
||||
stopBits: '',//停止位
|
||||
parity: '',//校验位
|
||||
pictureUrl: '',//设备图片
|
||||
slaveId: '',//从站地址
|
||||
},
|
||||
pcsSetting: {
|
||||
@ -206,13 +212,12 @@ export default {
|
||||
stopCommand: "",//关机指令
|
||||
startPower: '',//开机目标功率
|
||||
stopPower: '',//关机目标功率
|
||||
powerMultiplier: '',//目标功率倍率
|
||||
clusterNum: '',//电池簇数
|
||||
clusterPointAddress: []//电池簇地址
|
||||
},
|
||||
rules: {
|
||||
siteId: [
|
||||
{required: true, message: '请先在顶部选择站点', trigger: ['blur', 'change']}
|
||||
{required: true, message: '请选择站点', trigger: ['blur', 'change']}
|
||||
],
|
||||
deviceId: [
|
||||
{required: true, message: '请输入设备id', trigger: 'blur'},
|
||||
@ -226,6 +231,9 @@ export default {
|
||||
{required: true, message: '请输入设备描述', trigger: 'blur'},
|
||||
{validator: validateText, trigger: 'blur'}
|
||||
],
|
||||
communicationStatus: [
|
||||
{required: true, message: '请选择工作状态', trigger: ['blur', 'change']}
|
||||
],
|
||||
deviceType: [
|
||||
{required: true, message: '请选择设备类型', trigger: ['blur', 'change']}
|
||||
],
|
||||
@ -256,6 +264,9 @@ export default {
|
||||
parity: [
|
||||
{validator: validateText, trigger: 'blur'}
|
||||
],
|
||||
// pictureUrl:[
|
||||
// { required: true, message: '请上传图片', trigger: ['blur', 'change']}
|
||||
// ],
|
||||
},
|
||||
pcsSettingRules: {
|
||||
pointAddress: [
|
||||
@ -279,9 +290,6 @@ export default {
|
||||
stopPower: [
|
||||
{validator: validateText, trigger: 'blur'}
|
||||
],
|
||||
powerMultiplier: [
|
||||
{validator: validateDecimal, trigger: 'blur'}
|
||||
],
|
||||
clusterNum: [
|
||||
{required: true, message: '请输入电池簇数', trigger: 'blur'},
|
||||
{validator: validateNumber, trigger: 'blur'}
|
||||
@ -295,6 +303,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
communicationStatusOptions: state => state?.ems?.communicationStatusOptions || {},
|
||||
deviceTypeOptions: state => state?.ems?.deviceTypeOptions || {}
|
||||
}),
|
||||
isPcs() {
|
||||
@ -306,22 +315,12 @@ export default {
|
||||
handler(newVal) {
|
||||
//打开弹窗
|
||||
if (newVal) {
|
||||
if (this.mode === 'add') {
|
||||
this.syncSiteFromRoute(true)
|
||||
}
|
||||
this.getZdList()
|
||||
this.getDeviceCategoryList()
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
'$route.query.siteId': {
|
||||
handler() {
|
||||
if (!this.dialogTableVisible || this.mode !== 'add') {
|
||||
return
|
||||
}
|
||||
this.syncSiteFromRoute(true)
|
||||
}
|
||||
},
|
||||
id: {
|
||||
handler(newVal) {
|
||||
if ((newVal || newVal === 0) && this.mode !== 'add') {
|
||||
@ -347,24 +346,23 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
syncSiteFromRoute(force = false) {
|
||||
const routeSiteId = this.$route?.query?.siteId
|
||||
const normalizedSiteId = routeSiteId === undefined || routeSiteId === null ? '' : String(routeSiteId).trim()
|
||||
if (!normalizedSiteId) {
|
||||
if (force) {
|
||||
this.formData.siteId = ''
|
||||
}
|
||||
return
|
||||
}
|
||||
if (force || !this.formData.siteId) {
|
||||
this.formData.siteId = normalizedSiteId
|
||||
}
|
||||
},
|
||||
changeType() {
|
||||
if (this.dccDeviceCategoryList.includes(this.formData.deviceCategory)) {
|
||||
this.getParentDeviceList()
|
||||
}
|
||||
},
|
||||
uploadImage(data) {
|
||||
this.formData.pictureUrl = data
|
||||
},
|
||||
//获取站点列表
|
||||
getZdList() {
|
||||
this.loading += 1
|
||||
getAllSites().then(response => {
|
||||
this.siteList = response?.data || []
|
||||
}).finally(() => {
|
||||
this.loading -= 1
|
||||
})
|
||||
},
|
||||
// 获取设备类别
|
||||
getDeviceCategoryList() {
|
||||
this.loading += 1
|
||||
@ -377,8 +375,7 @@ export default {
|
||||
//获取上级id列表
|
||||
getParentDeviceList(init = false) {
|
||||
if (!this.formData.siteId) {
|
||||
this.$message.warning('请先在顶部选择站点')
|
||||
return
|
||||
return console.log('请先选择站点')
|
||||
}
|
||||
!init && (this.formData.parentId = '')
|
||||
this.loading = this.loading + 1
|
||||
@ -396,6 +393,7 @@ export default {
|
||||
deviceId = '',//设备id
|
||||
deviceName = '',//设备名称
|
||||
description = '',//设备描述
|
||||
communicationStatus = '',//工作状态
|
||||
deviceType = '',//设备类型
|
||||
deviceCategory = '',//设备类别
|
||||
parentId = '',//上级设备id
|
||||
@ -406,6 +404,7 @@ export default {
|
||||
dataBits = '',//数据位
|
||||
stopBits = '',//停止位
|
||||
parity = '',//校验位
|
||||
pictureUrl = '',//设备图片
|
||||
slaveId = '',//从站地址
|
||||
} = this.formData;
|
||||
const {
|
||||
@ -416,7 +415,6 @@ export default {
|
||||
stopCommand,
|
||||
startPower,
|
||||
stopPower,
|
||||
powerMultiplier,
|
||||
clusterNum,
|
||||
clusterPointAddress
|
||||
} = this.pcsSetting
|
||||
@ -425,6 +423,7 @@ export default {
|
||||
deviceId,
|
||||
deviceName,
|
||||
description,
|
||||
communicationStatus,
|
||||
deviceType,
|
||||
deviceCategory,
|
||||
parentId,
|
||||
@ -435,6 +434,7 @@ export default {
|
||||
dataBits,
|
||||
stopBits,
|
||||
parity,
|
||||
pictureUrl,
|
||||
slaveId,
|
||||
}
|
||||
if (this.isPcs) {
|
||||
@ -445,7 +445,6 @@ export default {
|
||||
stopCommand,
|
||||
startPower,
|
||||
stopPower,
|
||||
powerMultiplier,
|
||||
clusterNum,
|
||||
clusterPointAddress: JSON.stringify(!clusterNum ? [] : (clusterPointAddress || []).slice(0, clusterNum))
|
||||
}
|
||||
@ -498,6 +497,7 @@ export default {
|
||||
deviceId: '',//设备id
|
||||
deviceName: '',//设备名称
|
||||
description: '',//设备描述
|
||||
communicationStatus: '',//工作状态
|
||||
deviceType: '',//设备类型
|
||||
deviceCategory: '',//设备类别
|
||||
parentId: '',//上级设备id
|
||||
@ -508,9 +508,9 @@ export default {
|
||||
dataBits: '',//数据位
|
||||
stopBits: '',//停止位
|
||||
parity: '',//校验位
|
||||
pictureUrl: '',//设备图片
|
||||
slaveId: '',//从站地址
|
||||
}
|
||||
this.parentDeviceList = []
|
||||
this.pcsSetting = {
|
||||
deviceSettingId: '',
|
||||
powerAddress: '',//功率地址
|
||||
@ -519,7 +519,6 @@ export default {
|
||||
stopCommand: "",//关机指令
|
||||
startPower: '',//开机目标功率
|
||||
stopPower: '',//关机目标功率
|
||||
powerMultiplier: '',//目标功率倍率
|
||||
clusterNum: '',//电池簇数
|
||||
clusterPointAddress: []//电池簇地址
|
||||
}
|
||||
@ -532,18 +531,6 @@ export default {
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.form-layout {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-layout.has-pcs {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.base-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.device-form {
|
||||
::v-deep .el-form-item--medium .el-form-item__content {
|
||||
width: 260px;
|
||||
@ -554,93 +541,4 @@ export default {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.pcs-form {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -360px;
|
||||
width: 340px;
|
||||
max-height: 520px;
|
||||
overflow-y: auto;
|
||||
padding: 14px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
box-shadow: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.pcs-form__title {
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.pcs-form__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0 10px;
|
||||
}
|
||||
|
||||
.pcs-form__item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.pcs-form__cluster {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.pcs-form__cluster-title {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.pcs-form {
|
||||
::v-deep .el-form-item {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__label {
|
||||
line-height: 20px;
|
||||
padding-bottom: 4px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__content {
|
||||
width: 100%;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__error {
|
||||
position: static;
|
||||
line-height: 1.2;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__label:before {
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.ems-dialog {
|
||||
::v-deep .el-dialog,
|
||||
::v-deep .el-dialog__body {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -110,11 +110,6 @@
|
||||
sortable="custom"
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="selectable" label="操作" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="selectPoint(scope.row)">选择</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-show="tableData.length > 0"
|
||||
@ -152,7 +147,6 @@ export default {
|
||||
this.pageNum = 1;
|
||||
this.totalSize = 0;
|
||||
this.dataType = '';
|
||||
this.selectable = false;
|
||||
this.form = {
|
||||
sortMethod: "desc", //升序不传或者asc、降序desc)
|
||||
sortData: this.defaultSort.prop,
|
||||
@ -197,14 +191,9 @@ export default {
|
||||
pageSize: 10, //分页栏当前每个数据总数
|
||||
pageNum: 1, //分页栏当前页数
|
||||
totalSize: 0, //table表格数据总数
|
||||
selectable: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
selectPoint(row) {
|
||||
this.$emit('select-point', row || {})
|
||||
this.show = false
|
||||
},
|
||||
showChart({pointName}) {
|
||||
if (pointName) {
|
||||
const {deviceCategory, deviceId} = this;
|
||||
@ -246,13 +235,12 @@ export default {
|
||||
this.getData()
|
||||
});
|
||||
},
|
||||
showTable({deviceCategory, siteId, deviceId, parentId = ""}, dataType, options = {}) {
|
||||
showTable({deviceCategory, siteId, deviceId, parentId = ""}, dataType) {
|
||||
this.dataType = dataType;
|
||||
this.deviceCategory = deviceCategory;
|
||||
this.siteId = siteId;
|
||||
this.deviceId = deviceId;
|
||||
this.parentId = deviceCategory === "BATTERY" ? parentId : ""; //只有单体电池需要这个值
|
||||
this.selectable = !!options.selectable
|
||||
this.show = true;
|
||||
this.getData()
|
||||
},
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
<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-select v-model="siteId" placeholder="请选择换电站名称" :loading="searchLoading" loading-text="正在加载数据"
|
||||
@change="onSearch" clearable>
|
||||
<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 label="设备类型">
|
||||
<el-select v-model="deviceCategory" placeholder="请选择设备类型" @change="onSearch" clearable>
|
||||
<el-option
|
||||
@ -17,6 +24,34 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" @click="addDevice" native-type="button">新增设备</el-button>
|
||||
<el-dropdown @command="(val)=>downloadPointDetail(val,false)">
|
||||
<el-button
|
||||
style="margin-left:10px;"
|
||||
type="primary"
|
||||
plain>
|
||||
下载点位清单
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item v-for="(item,index) in deviceCategoryList" :key="index+'deviceCategoryList'"
|
||||
:command="item">
|
||||
{{ item.name }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<!-- <el-dropdown @command="(val)=>uploadPointDetail(val,false)">-->
|
||||
<!-- <el-button-->
|
||||
<!-- style="margin-left:10px;"-->
|
||||
<!-- type="success"-->
|
||||
<!-- plain>-->
|
||||
<!-- 上传点位清单-->
|
||||
<!-- </el-button>-->
|
||||
<!-- <el-dropdown-menu slot="dropdown">-->
|
||||
<!-- <el-dropdown-item v-for="(item,index) in deviceCategoryList" :key="index+'deviceCategoryList'"-->
|
||||
<!-- :command="item">-->
|
||||
<!-- {{ item.name }}-->
|
||||
<!-- </el-dropdown-item>-->
|
||||
<!-- </el-dropdown-menu>-->
|
||||
<!-- </el-dropdown>-->
|
||||
<el-table
|
||||
class="common-table"
|
||||
:data="tableData"
|
||||
@ -27,6 +62,10 @@
|
||||
prop="siteId"
|
||||
label="站点ID">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="siteName"
|
||||
label="站点名称">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="deviceId"
|
||||
label="设备ID"
|
||||
@ -40,11 +79,52 @@
|
||||
prop="categoryName"
|
||||
label="设备类别">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="deviceStatus"
|
||||
label="在线状态">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ $store.state.ems.deviceStatusOptions[scope.row.deviceStatus] }}</span>
|
||||
<!-- <pcs-switch v-if="scope.row.deviceCategory === 'PCS' && ![null,'',undefined].includes(scope.row.deviceStatus)"-->
|
||||
<!-- style="margin-left:5px;"-->
|
||||
<!-- :data="{siteId:scope.row.siteId,deviceStatus:scope.row.deviceStatus,deviceId:scope.row.deviceId,deviceName:scope.row.deviceName}"-->
|
||||
<!-- @updateSuccess="getData"/>-->
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
label="操作"
|
||||
width="180">
|
||||
width="250">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@click="pointDetail(scope.row,'point')"
|
||||
type="primary"
|
||||
size="mini">
|
||||
点位清单
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="pointDetail(scope.row,'alarmPoint')"
|
||||
type="primary"
|
||||
size="mini">
|
||||
报警点位清单
|
||||
</el-button>
|
||||
<br>
|
||||
<el-button
|
||||
@click="downloadPointDetail(scope.row,true)"
|
||||
style="margin-top:10px;"
|
||||
type="primary"
|
||||
plain
|
||||
size="mini">
|
||||
下载点位清单
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="uploadPointDetail(scope.row,true)"
|
||||
style="margin-top:10px;"
|
||||
type="success"
|
||||
plain
|
||||
size="mini">
|
||||
上传点位清单
|
||||
</el-button>
|
||||
<br>
|
||||
<el-button
|
||||
@click="editDevice(scope.row)"
|
||||
style="margin-top:10px;"
|
||||
@ -87,46 +167,32 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
<add-device ref="addDevice" :mode="mode" :id="editDeviceId" @update="getData" @clear="clearEditDeviceData"/>
|
||||
<point-table ref="pointTable"/>
|
||||
<point-upload ref="pointUpload" @update="getData"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {deleteService, getDeviceDetailInfo, getDeviceInfoList} from '@/api/ems/site'
|
||||
import {getAllSites} from '@/api/ems/zddt'
|
||||
import {formatNumber} from "@/filters/ems";
|
||||
import {getAllDeviceCategory} from '@/api/ems/search'
|
||||
import PointTable from './PointTable.vue'
|
||||
import AddDevice from "./AddDevice.vue";
|
||||
import PointUpload from "./PointUpload.vue";
|
||||
// import PcsSwitch from "./PcsSwitch.vue";
|
||||
|
||||
export default {
|
||||
name: "Sblb",
|
||||
components: {AddDevice},
|
||||
watch: {
|
||||
'$route.query.siteId'(newSiteId) {
|
||||
const normalizedSiteId = this.hasValidSiteId(newSiteId) ? String(newSiteId).trim() : ''
|
||||
if (normalizedSiteId === this.siteId) {
|
||||
return
|
||||
}
|
||||
this.siteId = normalizedSiteId
|
||||
this.onSearch()
|
||||
},
|
||||
'$route.query.siteName'(newSiteName) {
|
||||
const normalizedSiteName = this.getSelectedSiteName(newSiteName)
|
||||
if (normalizedSiteName === this.selectedSiteName) {
|
||||
return
|
||||
}
|
||||
this.selectedSiteName = normalizedSiteName
|
||||
this.tableData = (this.tableData || []).map(item => ({
|
||||
...item,
|
||||
siteName: normalizedSiteName
|
||||
}))
|
||||
}
|
||||
},
|
||||
components: {AddDevice, PointTable, PointUpload},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
searchLoading: false,
|
||||
mode: '',//新增、编辑设备
|
||||
editDeviceId: '',//编辑设备id
|
||||
siteId: '',
|
||||
selectedSiteName: '',
|
||||
siteList: [],
|
||||
deviceCategory: '',//搜索栏设备类型
|
||||
deviceCategoryList: [],//设备类别
|
||||
tableData: [],
|
||||
@ -158,26 +224,38 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hasValidSiteId(siteId) {
|
||||
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
|
||||
},
|
||||
getSelectedSiteName(routeSiteName) {
|
||||
const name = routeSiteName === undefined || routeSiteName === null ? '' : String(routeSiteName).trim()
|
||||
if (name) {
|
||||
return name
|
||||
}
|
||||
const matchedSite = (this.$store.getters.zdList || []).find(item => item.siteId === this.siteId)
|
||||
if (matchedSite && matchedSite.siteName) {
|
||||
return matchedSite.siteName
|
||||
}
|
||||
return this.siteId || ''
|
||||
},
|
||||
// 获取设备类别
|
||||
getDeviceCategoryList() {
|
||||
getAllDeviceCategory().then(response => {
|
||||
this.deviceCategoryList = response?.data || []
|
||||
})
|
||||
},
|
||||
// 查看设备电位表格
|
||||
pointDetail(row, dataType) {
|
||||
this.$refs.pointTable.showTable(row, dataType)
|
||||
},
|
||||
// 下载点位清单
|
||||
downloadPointDetail(command, isDetail = false) {
|
||||
const siteId = isDetail ? command.siteId : this.siteId
|
||||
const deviceCategory = isDetail ? command.deviceCategory : command.code
|
||||
const categoryName = isDetail ? command.categoryName : command.name
|
||||
const deviceId = isDetail ? command.deviceId : null
|
||||
console.log('下载', command, isDetail)
|
||||
this.download('ems/pointMatch/export', {
|
||||
siteId,
|
||||
deviceCategory,
|
||||
deviceId,
|
||||
}, `点位清单_${categoryName}_${new Date().getTime()}.xlsx`)
|
||||
},
|
||||
// 上传点位清单
|
||||
uploadPointDetail(command, isDetail = false) {
|
||||
const siteId = isDetail ? command.siteId : this.siteId
|
||||
const deviceCategory = isDetail ? command.deviceCategory : command.code
|
||||
const categoryName = isDetail ? command.categoryName : command.name
|
||||
const deviceId = isDetail ? command.deviceId : ''
|
||||
console.log('上传', command, isDetail)
|
||||
this.$refs.pointUpload.showDialog({siteId, deviceCategory, categoryName, deviceId})
|
||||
},
|
||||
clearEditDeviceData() {
|
||||
this.mode = '';
|
||||
this.editDeviceId = ''
|
||||
@ -278,24 +356,31 @@ export default {
|
||||
this.loading = true
|
||||
const {siteId, deviceCategory, pageNum, pageSize} = this
|
||||
getDeviceInfoList({siteId, deviceCategory, pageNum, pageSize}).then(response => {
|
||||
const selectedSiteName = this.getSelectedSiteName(this.$route.query.siteName)
|
||||
this.selectedSiteName = selectedSiteName
|
||||
this.tableData = (response?.rows || []).map(item => ({
|
||||
...item,
|
||||
siteName: selectedSiteName
|
||||
}));
|
||||
this.tableData = response?.rows || [];
|
||||
this.totalSize = response?.total || 0
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
//获取站点列表
|
||||
getZdList() {
|
||||
this.searchLoading = true
|
||||
return getAllSites().then(response => {
|
||||
this.siteList = response?.data || []
|
||||
if (this.siteList.length > 0) this.siteId = this.siteList[0].siteId
|
||||
}).finally(() => {
|
||||
this.searchLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
|
||||
this.selectedSiteName = this.getSelectedSiteName(this.$route.query.siteName)
|
||||
this.loading = true
|
||||
this.siteId = ''
|
||||
this.pageNum = 1//每次搜索从1开始搜索
|
||||
this.getDeviceCategoryList()
|
||||
this.getData()
|
||||
this.getZdList().then(() => {
|
||||
this.getData()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
||||
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading">
|
||||
<el-form :inline="true" class="select-container">
|
||||
@ -23,7 +24,6 @@
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSearch" native-type="button">搜索</el-button>
|
||||
<el-button @click="onReset" native-type="button">重置</el-button>
|
||||
<el-button type="success" @click="openAddDialog" native-type="button">新增站点</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
@ -40,10 +40,6 @@
|
||||
prop="siteName"
|
||||
label="站点名称">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="siteShortName"
|
||||
label="站点简称">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="siteAddress"
|
||||
label="站点地址"
|
||||
@ -61,21 +57,6 @@
|
||||
prop="installCapacity"
|
||||
label="装机容量">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="220"
|
||||
fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="openEditDialog(scope.row)">编辑</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
:loading="syncingSiteId === scope.row.siteId"
|
||||
:disabled="syncingSiteId === scope.row.siteId"
|
||||
@click="syncWeather(scope.row)"
|
||||
>同步天气</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-show="tableData.length>0"
|
||||
@ -90,250 +71,67 @@
|
||||
style="margin-top:15px;text-align: center"
|
||||
>
|
||||
</el-pagination>
|
||||
|
||||
<el-dialog
|
||||
:title="isEdit ? '编辑站点' : '新增站点'"
|
||||
:visible.sync="dialogVisible"
|
||||
width="640px"
|
||||
:close-on-click-modal="false">
|
||||
<el-form ref="siteForm" :model="siteForm" :rules="siteRules" label-width="100px">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="站点ID" prop="siteId">
|
||||
<el-input v-model.trim="siteForm.siteId" :disabled="isEdit" placeholder="仅支持字母/数字/下划线" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="站点名称" prop="siteName">
|
||||
<el-input v-model.trim="siteForm.siteName" placeholder="请输入站点名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="站点简称" prop="siteShortName">
|
||||
<el-input v-model.trim="siteForm.siteShortName" placeholder="请输入站点简称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="运营时间" prop="runningTime">
|
||||
<el-date-picker
|
||||
v-model="siteForm.runningTime"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择运营时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="站点地址" prop="siteAddress">
|
||||
<el-input v-model.trim="siteForm.siteAddress" placeholder="请输入站点地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="装机功率" prop="installPower">
|
||||
<el-input v-model="siteForm.installPower" placeholder="请输入装机功率" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="装机容量" prop="installCapacity">
|
||||
<el-input v-model="siteForm.installCapacity" placeholder="请输入装机容量" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="纬度" prop="latitude">
|
||||
<el-input v-model="siteForm.latitude" placeholder="请输入纬度" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="经度" prop="longitude">
|
||||
<el-input v-model="siteForm.longitude" placeholder="请输入经度" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model.trim="siteForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitSite">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {addSite, getSiteInfoList, syncSiteWeatherByDateRange, updateSite} from '@/api/ems/site'
|
||||
import {getSiteInfoList} from '@/api/ems/site'
|
||||
import { formatDate } from '@/filters/ems'
|
||||
|
||||
const emptySiteForm = () => ({
|
||||
id: undefined,
|
||||
siteId: '',
|
||||
siteName: '',
|
||||
siteShortName: '',
|
||||
siteAddress: '',
|
||||
runningTime: '',
|
||||
installPower: '',
|
||||
installCapacity: '',
|
||||
latitude: '',
|
||||
longitude: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
export default {
|
||||
name: 'Zdlb',
|
||||
name: "Zdlb",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
submitLoading: false,
|
||||
siteName: '',
|
||||
pickerOptions: {
|
||||
loading:false,
|
||||
siteName:"",
|
||||
pickerOptions:{
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
defaultDateRange: [],
|
||||
dateRange: [],
|
||||
tableData: [],
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
totalSize: 0,
|
||||
syncingSiteId: '',
|
||||
dialogVisible: false,
|
||||
isEdit: false,
|
||||
siteForm: emptySiteForm(),
|
||||
siteRules: {
|
||||
siteId: [
|
||||
{ required: true, message: '请输入站点ID', trigger: 'blur' },
|
||||
{ pattern: /^[A-Za-z0-9_]+$/, message: '站点ID仅支持字母、数字、下划线', trigger: 'blur' }
|
||||
],
|
||||
siteName: [
|
||||
{ required: true, message: '请输入站点名称', trigger: 'blur' }
|
||||
],
|
||||
runningTime: [
|
||||
{ required: true, message: '请选择运营时间', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
defaultDateRange:[],//默认展示的时间
|
||||
dateRange:[],//startTime,endTime
|
||||
tableData:[],
|
||||
pageSize:10,//分页栏当前每个数据总数
|
||||
pageNum:1,//分页栏当前页数
|
||||
totalSize:0,//table表格数据总数
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
methods:{
|
||||
// 分页
|
||||
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
|
||||
// 搜索
|
||||
onSearch(){
|
||||
this.pageNum =1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
onReset() {
|
||||
this.siteName = ''
|
||||
this.dateRange = []
|
||||
this.pageNum = 1
|
||||
// 重置
|
||||
onReset(){
|
||||
this.siteName=''
|
||||
this.dateRange=[]
|
||||
this.pageNum =1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
},
|
||||
getSyncDateRange() {
|
||||
const now = new Date()
|
||||
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
return [formatDate(monthStart), formatDate(now)]
|
||||
},
|
||||
syncWeather(row) {
|
||||
const siteId = row?.siteId
|
||||
if (!siteId) {
|
||||
this.$message.warning('站点ID为空,无法同步天气')
|
||||
return
|
||||
}
|
||||
const [startTime, endTime] = this.getSyncDateRange()
|
||||
this.$confirm(`将同步站点 ${siteId} 在 ${startTime} 至 ${endTime} 的天气数据,是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.syncingSiteId = siteId
|
||||
return syncSiteWeatherByDateRange({siteId, startTime, endTime})
|
||||
}).then((response) => {
|
||||
const result = response?.data || {}
|
||||
const successDays = result.successDays ?? 0
|
||||
const totalDays = result.totalDays ?? 0
|
||||
this.$message.success(`天气同步完成(${successDays}/${totalDays}天)`)
|
||||
}).catch((err) => {
|
||||
if (err !== 'cancel') {
|
||||
this.$message.error('天气同步失败')
|
||||
}
|
||||
}).finally(() => {
|
||||
this.syncingSiteId = ''
|
||||
})
|
||||
},
|
||||
getData() {
|
||||
this.loading = true
|
||||
const {siteName, pageNum, pageSize} = this
|
||||
const [startTime = '', endTime = ''] = (this.dateRange || [])
|
||||
getSiteInfoList({siteName, pageSize, pageNum, startTime: formatDate(startTime), endTime: formatDate(endTime)}).then(response => {
|
||||
this.tableData = response?.rows || [];
|
||||
// 获取数据
|
||||
getData(){
|
||||
this.loading=true
|
||||
const {siteName,pageNum,pageSize} =this
|
||||
const [startTime='',endTime='']=(this.dateRange || [])
|
||||
getSiteInfoList({siteName,pageSize,pageNum,startTime:formatDate(startTime),endTime:formatDate(endTime)}).then(response => {
|
||||
this.tableData=response?.rows || [];
|
||||
this.totalSize = response?.total || 0
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}).finally(() => {this.loading=false})
|
||||
},
|
||||
openAddDialog() {
|
||||
this.isEdit = false
|
||||
this.siteForm = emptySiteForm()
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.siteForm && this.$refs.siteForm.clearValidate()
|
||||
})
|
||||
},
|
||||
openEditDialog(row) {
|
||||
this.isEdit = true
|
||||
this.siteForm = {
|
||||
id: row.id,
|
||||
siteId: row.siteId || '',
|
||||
siteName: row.siteName || '',
|
||||
siteShortName: row.siteShortName || '',
|
||||
siteAddress: row.siteAddress || '',
|
||||
runningTime: row.runningTime || '',
|
||||
installPower: row.installPower || '',
|
||||
installCapacity: row.installCapacity || '',
|
||||
latitude: row.latitude || '',
|
||||
longitude: row.longitude || '',
|
||||
remark: row.remark || ''
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.siteForm && this.$refs.siteForm.clearValidate()
|
||||
})
|
||||
},
|
||||
submitSite() {
|
||||
this.$refs.siteForm.validate(valid => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
this.submitLoading = true
|
||||
const request = this.isEdit ? updateSite : addSite
|
||||
request(this.siteForm).then(() => {
|
||||
this.$message.success(this.isEdit ? '编辑成功' : '新增成功')
|
||||
this.dialogVisible = false
|
||||
this.getData()
|
||||
}).finally(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.onReset()
|
||||
|
||||
@ -1,188 +1,95 @@
|
||||
|
||||
<template>
|
||||
<div class="map-wrapper">
|
||||
<div ref="mapRef" class="map-canvas"></div>
|
||||
<div v-if="selectedAddress" class="map-center-address">{{ selectedAddress }}</div>
|
||||
<div v-if="!hasPoint" class="map-empty">暂无站点坐标</div>
|
||||
</div>
|
||||
<div id="zddtChart" style="height: 100%;width:100%"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const TDT_SCRIPT_ID = 'tianditu-js-sdk'
|
||||
let tdtScriptLoading = null
|
||||
|
||||
function loadTdtScript(tk) {
|
||||
if (window.T) return Promise.resolve(window.T)
|
||||
if (tdtScriptLoading) return tdtScriptLoading
|
||||
tdtScriptLoading = new Promise((resolve, reject) => {
|
||||
const oldScript = document.getElementById(TDT_SCRIPT_ID)
|
||||
if (oldScript) {
|
||||
oldScript.addEventListener('load', () => resolve(window.T))
|
||||
oldScript.addEventListener('error', () => reject(new Error('天地图脚本加载失败')))
|
||||
return
|
||||
}
|
||||
const script = document.createElement('script')
|
||||
script.id = TDT_SCRIPT_ID
|
||||
script.src = `https://api.tianditu.gov.cn/api?v=4.0&tk=${tk}`
|
||||
script.async = true
|
||||
script.onload = () => {
|
||||
if (window.T) resolve(window.T)
|
||||
else reject(new Error('天地图对象未初始化'))
|
||||
}
|
||||
script.onerror = () => reject(new Error('天地图脚本加载失败'))
|
||||
document.body.appendChild(script)
|
||||
})
|
||||
return tdtScriptLoading
|
||||
}
|
||||
import * as echarts from 'echarts'
|
||||
import resize from '@/mixins/ems/resize'
|
||||
import china from '@/data/ems/china.json'//中国地图数据
|
||||
import 'echarts/lib/chart/map';
|
||||
echarts.registerMap('china', { geoJSON: china }); //注册可用地图
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
data() {
|
||||
return {
|
||||
hasPoint: false,
|
||||
selectedAddress: '',
|
||||
map: null,
|
||||
overlays: [],
|
||||
pendingPayload: null,
|
||||
mapConfig: {
|
||||
zoom: 12,
|
||||
selectedZoom: 15,
|
||||
tk: '01e99ab4472430e1c7dbfe4b5db99787'
|
||||
}
|
||||
chart: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initMap()
|
||||
this.$nextTick(() => {
|
||||
this.initChart()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearOverlays()
|
||||
this.map = null
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
async initMap() {
|
||||
try {
|
||||
await loadTdtScript(this.mapConfig.tk)
|
||||
if (!this.$refs.mapRef || !window.T) return
|
||||
this.map = new window.T.Map(this.$refs.mapRef)
|
||||
const defaultCenter = new window.T.LngLat(104.1, 35.9)
|
||||
this.map.centerAndZoom(defaultCenter, 5)
|
||||
if (this.pendingPayload) {
|
||||
this.renderPayload(this.pendingPayload)
|
||||
}
|
||||
} catch (e) {
|
||||
// 页面可继续使用,地图只显示空态
|
||||
this.hasPoint = false
|
||||
}
|
||||
initChart() {
|
||||
// ECharts 默认有提供了一个简单的加载动画。只需要调用 showLoading 方法显示。数据加载完成后再调用 hideLoading 方法隐藏加载动画。
|
||||
this.chart = echarts.init(document.querySelector('#zddtChart'))
|
||||
},
|
||||
normalizePoint(site = {}) {
|
||||
const name = site.siteName || site.name || ''
|
||||
const address = site.siteAddress || site.address || ''
|
||||
const value = site.value || site.siteLocation || []
|
||||
const lonSource = site.longitude !== undefined && site.longitude !== null ? site.longitude : value[0]
|
||||
const latSource = site.latitude !== undefined && site.latitude !== null ? site.latitude : value[1]
|
||||
const lon = Number(lonSource)
|
||||
const lat = Number(latSource)
|
||||
if (!lon || !lat) return null
|
||||
return { name, address, lon, lat }
|
||||
},
|
||||
clearOverlays() {
|
||||
if (!this.map || !this.overlays.length) return
|
||||
this.overlays.forEach(item => this.map.removeOverLay(item))
|
||||
this.overlays = []
|
||||
},
|
||||
renderPayload(payload = {}) {
|
||||
const isArrayPayload = Array.isArray(payload)
|
||||
const selectedRaw = isArrayPayload ? ((payload || [])[0] || {}) : (payload.selected || {})
|
||||
const sitesRaw = isArrayPayload ? [] : (payload.sites || [])
|
||||
const selected = this.normalizePoint(selectedRaw)
|
||||
const points = (Array.isArray(sitesRaw) ? sitesRaw : [])
|
||||
.map(item => this.normalizePoint(item))
|
||||
.filter(Boolean)
|
||||
this.selectedAddress = selected?.address || ''
|
||||
if (selected && !points.find(item => item.lon === selected.lon && item.lat === selected.lat)) {
|
||||
points.push(selected)
|
||||
}
|
||||
this.clearOverlays()
|
||||
this.hasPoint = points.length > 0
|
||||
if (!this.map || !points.length || !window.T) return
|
||||
|
||||
const viewPoints = []
|
||||
points.forEach(item => {
|
||||
const lngLat = new window.T.LngLat(item.lon, item.lat)
|
||||
const marker = new window.T.Marker(lngLat)
|
||||
this.map.addOverLay(marker)
|
||||
this.overlays.push(marker)
|
||||
viewPoints.push(lngLat)
|
||||
setOption(data) {
|
||||
this.chart.setOption({
|
||||
color:['#FFBD00'],
|
||||
backgroundColor: 'transparent', //背景色
|
||||
geo: { //地理坐标系组件 地理坐标系组件用于地图的绘制,支持在地理坐标系上绘制
|
||||
map: 'china', //地图类型 这儿展示的是中国地图
|
||||
aspectScale: 0.85,
|
||||
selectedMode: "single",// 开启单选
|
||||
label: {
|
||||
show: true, //是否显示标签 此处指是否显示地图上的地区名字
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
},
|
||||
roam: true, //是否开启鼠标缩放和平移漫游
|
||||
itemStyle: {
|
||||
areaColor: "#03365b",
|
||||
borderColor: "#4bf3f9",
|
||||
shadowColor: '#03365b', //阴影颜色
|
||||
shadowOffsetX: 0, //阴影偏移量
|
||||
shadowOffsetY: 0, //阴影偏移量
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
color: '#ffffff',
|
||||
},
|
||||
itemStyle: {
|
||||
areaColor: "#0f5d9d",
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: "effectScatter",
|
||||
coordinateSystem: "geo",
|
||||
showEffectOn: "render",
|
||||
data,
|
||||
rippleEffect: {
|
||||
brushType: "stroke",
|
||||
scale: 5,
|
||||
period: 2, // 秒数
|
||||
},
|
||||
symbolSize: 12,
|
||||
clickable: false,
|
||||
zlevel: 1,
|
||||
label: {
|
||||
formatter: "{b}",
|
||||
position: "right",
|
||||
show: true,
|
||||
},
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
if (selected && selected.name) {
|
||||
const label = new window.T.Label({
|
||||
text: selected.name,
|
||||
position: new window.T.LngLat(selected.lon, selected.lat),
|
||||
offset: new window.T.Point(8, -34)
|
||||
})
|
||||
this.map.addOverLay(label)
|
||||
this.overlays.push(label)
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
this.map.centerAndZoom(new window.T.LngLat(selected.lon, selected.lat), this.mapConfig.selectedZoom)
|
||||
} else if (viewPoints.length === 1) {
|
||||
this.map.centerAndZoom(viewPoints[0], this.mapConfig.zoom)
|
||||
} else {
|
||||
this.map.setViewport(viewPoints)
|
||||
}
|
||||
},
|
||||
setOption(payload = {}) {
|
||||
this.pendingPayload = payload
|
||||
if (!this.map) return
|
||||
this.renderPayload(payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.map-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.map-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.map-empty {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
background: rgba(245, 247, 250, 0.9);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.map-center-address {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, calc(-50% - 26px));
|
||||
max-width: min(70%, 520px);
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #ffffff;
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,55 +1,33 @@
|
||||
<template>
|
||||
<div class="ems-dashboard-editor-container" v-loading="loading">
|
||||
<zd-info></zd-info>
|
||||
<div class="ems-content-container">
|
||||
<div class="ems-content-container ">
|
||||
<div class="map-container">
|
||||
<div class="site-cards-wrapper" v-if="allSites.length > 0">
|
||||
<button
|
||||
class="site-cards-arrow site-cards-arrow--left"
|
||||
type="button"
|
||||
:disabled="!canScrollLeft"
|
||||
@click="scrollSiteCards('left')"
|
||||
>
|
||||
<i class="el-icon-arrow-left"></i>
|
||||
</button>
|
||||
<div ref="siteCards" class="site-cards" @scroll="updateScrollButtons">
|
||||
<div
|
||||
v-for="item in allSites"
|
||||
:key="item.siteId"
|
||||
class="site-card"
|
||||
:class="{ active: isSameSite(item.siteId, singleSiteId) }"
|
||||
@click="submitSite(item.siteId)"
|
||||
>
|
||||
<div class="site-card-name">{{ item.siteName || '-' }}</div>
|
||||
<div class="site-card-info-row">
|
||||
<span class="site-card-label">电站位置</span>
|
||||
<span class="site-card-value site-card-value--address">{{ formatSiteCardField(item.siteAddress) }}</span>
|
||||
</div>
|
||||
<div class="site-card-info-row">
|
||||
<span class="site-card-label">投运时间</span>
|
||||
<span class="site-card-value">{{ formatSiteCardDate(item.runningTime) }}</span>
|
||||
</div>
|
||||
<div class="site-card-info-row">
|
||||
<span class="site-card-label">装机功率(MWh)</span>
|
||||
<span class="site-card-value">{{ formatSiteCardField(item.installPower) }}</span>
|
||||
</div>
|
||||
<div class="site-card-info-row">
|
||||
<span class="site-card-label">装机容量(MWh)</span>
|
||||
<span class="site-card-value">{{ formatSiteCardField(item.installCapacity) }}</span>
|
||||
</div>
|
||||
<map-chart ref="mapChart"/>
|
||||
</div>
|
||||
<div class="zd-msg-container">
|
||||
<div class="zd-msg-top">
|
||||
<zd-select ref="zdSelect" @submitSite="submitSite"></zd-select>
|
||||
<el-card class="common-card-container">
|
||||
<div slot="header">
|
||||
<span class="card-title">基本信息</span>
|
||||
<el-button style="float: right; padding: 3px 0" type="text" size="small" @click="toDzjk">查看详情</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="site-cards-arrow site-cards-arrow--right"
|
||||
type="button"
|
||||
:disabled="!canScrollRight"
|
||||
@click="scrollSiteCards('right')"
|
||||
>
|
||||
<i class="el-icon-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="map-view">
|
||||
<map-chart ref="mapChart"/>
|
||||
<div class="single-zd-name">{{singleSiteName}}</div>
|
||||
<!-- 四个方块-->
|
||||
<el-row :gutter="14">
|
||||
<el-col :span="12" class="single-square-box-container" v-for="(item,index) in singleZdSqaure" :key="index+'singleSquareBox'">
|
||||
<single-square-box :data="item"></single-square-box>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 基本信息 -->
|
||||
<el-descriptions class="single-zd-info-container" :column="1" >
|
||||
<el-descriptions-item v-for="(item,index) in singleZdInfo" :key="index+'singleZdInfo'" :label="item.title">{{item.value | formatNumber }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<!-- echarts柱状图-->
|
||||
<bar-chart ref="barChart"></bar-chart>
|
||||
</el-card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -58,103 +36,94 @@
|
||||
|
||||
<script>
|
||||
import ZdInfo from '@/components/Ems/ZdBaseInfo/index.vue'
|
||||
import ZdSelect from '@/components/Ems/ZdSelect/index.vue'
|
||||
import SingleSquareBox from '@/components/Ems/SingleSquareBox/index.vue'
|
||||
import BarChart from './BarChart.vue'
|
||||
import MapChart from './MapChart.vue'
|
||||
import { getAllSites } from '@/api/ems/zddt'
|
||||
import {getSingleSiteBaseInfo} from '@/api/ems/zddt'
|
||||
export default {
|
||||
components: { ZdInfo, MapChart },
|
||||
components:{ZdSelect,ZdInfo,SingleSquareBox,BarChart,MapChart},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
singleSiteId: '',
|
||||
singleSiteName: '',
|
||||
singleSiteAddress: '',
|
||||
singleSiteLocation: [],
|
||||
allSites: [],
|
||||
canScrollLeft: false,
|
||||
canScrollRight: false
|
||||
loading:false,
|
||||
singleSiteId:'',
|
||||
singleSiteName:'',
|
||||
singleSiteLocation:[],
|
||||
// 单个电站 四个方块数据
|
||||
singleZdSqaure:[
|
||||
{
|
||||
title:'今日充电(kWh)',
|
||||
value:'',
|
||||
bgColor:'#FFE5E5',
|
||||
attr:'dayChargedCap'
|
||||
},{
|
||||
title:'累计充电(kWh)',
|
||||
value:'',
|
||||
bgColor:'#FFE5E5',
|
||||
attr:'totalChargedCap'
|
||||
},{
|
||||
title:'今日放电(kWh)',
|
||||
value:'',
|
||||
bgColor:'#EEEBFF',
|
||||
attr:'dayDisChargedCap'
|
||||
},{
|
||||
title:'累计放电(kWh)',
|
||||
value:'',
|
||||
bgColor:'#EEEBFF',
|
||||
attr:'totalDisChargedCap'
|
||||
}
|
||||
],
|
||||
|
||||
// 单个电站 基本信息
|
||||
singleZdInfo:[{
|
||||
title:'电站位置',
|
||||
value:'',
|
||||
attr:'siteAddress'
|
||||
},{
|
||||
title:'投运时间',
|
||||
value:'',
|
||||
attr:'runningTime'
|
||||
},{
|
||||
title:'装机功率(MW)',
|
||||
value:'',
|
||||
attr:'installPower'
|
||||
},{
|
||||
title:'装机容量(MW)',
|
||||
value:'',
|
||||
attr:'installCapacity',
|
||||
}]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadSites()
|
||||
window.addEventListener('resize', this.updateScrollButtons)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.updateScrollButtons)
|
||||
},
|
||||
methods:{
|
||||
isSameSite(siteId, selectedId) {
|
||||
return String(siteId) === String(selectedId)
|
||||
},
|
||||
formatSiteCardField(value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
}
|
||||
return value
|
||||
},
|
||||
formatSiteCardDate(value) {
|
||||
if (!value) {
|
||||
return '-'
|
||||
}
|
||||
const text = String(value)
|
||||
if (text.includes('T')) {
|
||||
return text.slice(0, 10)
|
||||
}
|
||||
return text.length > 10 ? text.slice(0, 10) : text
|
||||
},
|
||||
loadSites() {
|
||||
this.loading = true
|
||||
getAllSites().then(response => {
|
||||
this.allSites = response?.data || []
|
||||
if (this.allSites.length > 0) {
|
||||
this.submitSite(this.allSites[0].siteId)
|
||||
} else {
|
||||
this.updateMapMarkers()
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.updateScrollButtons()
|
||||
})
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
updateScrollButtons() {
|
||||
const container = this.$refs.siteCards
|
||||
if (!container) {
|
||||
this.canScrollLeft = false
|
||||
this.canScrollRight = false
|
||||
return
|
||||
}
|
||||
const maxScrollLeft = container.scrollWidth - container.clientWidth
|
||||
this.canScrollLeft = container.scrollLeft > 0
|
||||
this.canScrollRight = maxScrollLeft > 0 && container.scrollLeft < maxScrollLeft - 1
|
||||
},
|
||||
scrollSiteCards(direction) {
|
||||
const container = this.$refs.siteCards
|
||||
if (!container) {
|
||||
return
|
||||
}
|
||||
const amount = Math.max(container.clientWidth * 0.8, 240)
|
||||
const delta = direction === 'left' ? -amount : amount
|
||||
container.scrollBy({ left: delta, behavior: 'smooth' })
|
||||
},
|
||||
updateMapMarkers(){
|
||||
this.$refs.mapChart && this.$refs.mapChart.setOption({
|
||||
selected: {name:this.singleSiteName,address:this.singleSiteAddress,value:this.singleSiteLocation},
|
||||
sites:this.allSites
|
||||
})
|
||||
},
|
||||
// 站点选中
|
||||
submitSite(id){
|
||||
if(this.singleSiteId === id){return console.log(`点击搜索按钮 搜索相同的站点id= ${id}不再调用获取基本信息接口`)}
|
||||
this.loading=true
|
||||
console.log('点击搜索按钮 选中的站点id',id)
|
||||
this.singleSiteId = id
|
||||
const currentSite = this.allSites.find(item => this.isSameSite(item.siteId, id)) || {}
|
||||
this.singleSiteName = currentSite.siteName || ''
|
||||
this.singleSiteAddress = currentSite.siteAddress || ''
|
||||
this.singleSiteLocation = currentSite.siteLocation || []
|
||||
if (!this.singleSiteLocation.length) {
|
||||
this.singleSiteLocation = [currentSite.longitude, currentSite.latitude].filter(item => item !== undefined && item !== null)
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.updateMapMarkers()
|
||||
this.$refs.zdSelect.searchLoading = true
|
||||
getSingleSiteBaseInfo(id).then(response => {
|
||||
console.log('单个站点详情数据',response)
|
||||
const res = response?.data || {}
|
||||
this.singleSiteName = res?.siteName || ''//站点名称
|
||||
this.singleSiteLocation = res?.siteLocation || []//站点坐标
|
||||
this.singleZdSqaure.forEach(item=>{
|
||||
item.value =res[item.attr]
|
||||
})
|
||||
this.singleZdInfo.forEach(item=>{
|
||||
item.value = res[item.attr]
|
||||
})
|
||||
this.$refs.barChart.setOption(res?.sevenDayDisChargeStats || [])
|
||||
this.$refs.mapChart.setOption([{name:this.singleSiteName,value:this.singleSiteLocation}])
|
||||
}).finally(() => {this.$refs.zdSelect.searchLoading = false;this.loading=false})
|
||||
},
|
||||
//跳转单站监控页面
|
||||
toDzjk(){
|
||||
this.$router.push({
|
||||
path:'/dzjk',
|
||||
query:{
|
||||
siteId:this.singleSiteId,
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
@ -165,119 +134,29 @@ export default {
|
||||
.ems-content-container{
|
||||
display: flex;
|
||||
padding:24px;
|
||||
padding-right: 0;
|
||||
.map-container{
|
||||
flex:auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
.site-cards-wrapper{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.site-cards-arrow{
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 0 1px #dcdfe6 inset;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: all .2s ease;
|
||||
&:hover:not(:disabled){
|
||||
color: #2b74ff;
|
||||
box-shadow: 0 0 0 1px #2b74ff inset;
|
||||
}
|
||||
&:disabled{
|
||||
cursor: not-allowed;
|
||||
color: #c0c4cc;
|
||||
box-shadow: 0 0 0 1px #ebeef5 inset;
|
||||
}
|
||||
}
|
||||
}
|
||||
.zd-msg-container{
|
||||
width: 500px;
|
||||
padding: 24px;
|
||||
.single-zd-name{
|
||||
font-weight: 500;
|
||||
line-height: 23px;
|
||||
color: #FFBD00;
|
||||
font-size: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.site-cards{
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 2px;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
&::-webkit-scrollbar{
|
||||
display: none;
|
||||
}
|
||||
.site-card{
|
||||
flex: 0 0 360px;
|
||||
height: 166px;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e4e7ed;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
transition: all .2s ease;
|
||||
.site-card-name{
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
line-height: 22px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.site-card-info-row{
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.site-card-label{
|
||||
flex: 0 0 84px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.site-card-value{
|
||||
flex: 1;
|
||||
color: #606266;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.site-card-value--address{
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: unset;
|
||||
min-height: 36px;
|
||||
}
|
||||
&:hover{
|
||||
border-color: #c0d3ff;
|
||||
}
|
||||
&.active{
|
||||
border-color: #2b74ff;
|
||||
box-shadow: 0 0 0 1px rgba(43,116,255,.15) inset;
|
||||
background: #f5f9ff;
|
||||
.site-card-name{
|
||||
color: #2b74ff;
|
||||
}
|
||||
.site-card-value{
|
||||
color: #5f8ee3;
|
||||
}
|
||||
}
|
||||
}
|
||||
.single-square-box-container{
|
||||
height: 78px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.map-view{
|
||||
height: 70vh;
|
||||
min-height: 520px;
|
||||
.single-zd-info-container{
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
color:#666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<transition :name="bgTransitionName">
|
||||
<img :key="bgNum" :src="loginBg" alt="" srcset="" class="login-bg" />
|
||||
</transition>
|
||||
<img :src="loginBg" alt="" srcset="" class="login-bg" />
|
||||
<el-form
|
||||
ref="loginForm"
|
||||
:model="loginForm"
|
||||
@ -109,8 +107,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
bgNum: 1,
|
||||
bgTransitionName: "bg-slide",
|
||||
bgTransitionNames: ["bg-slide", "bg-zoom", "bg-blur"],
|
||||
codeUrl: "",
|
||||
loginForm: {
|
||||
username: "admin",
|
||||
@ -152,12 +148,7 @@ export default {
|
||||
this.updateInterval(this.updateBgNum, 5000);
|
||||
},
|
||||
methods: {
|
||||
randomBgTransitionName() {
|
||||
const index = Math.floor(Math.random() * this.bgTransitionNames.length);
|
||||
return this.bgTransitionNames[index];
|
||||
},
|
||||
updateBgNum() {
|
||||
this.bgTransitionName = this.randomBgTransitionName();
|
||||
if (this.bgNum >= 4) this.bgNum = 0;
|
||||
this.bgNum += 1;
|
||||
},
|
||||
@ -219,10 +210,6 @@ export default {
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss">
|
||||
.login {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
padding-left: 180px;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
@ -237,60 +224,6 @@ export default {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
backface-visibility: hidden;
|
||||
will-change: opacity, transform, filter;
|
||||
}
|
||||
.bg-fade-enter-active,
|
||||
.bg-fade-leave-active {
|
||||
transition: opacity 0.9s ease, transform 0.9s ease;
|
||||
}
|
||||
.bg-fade-enter {
|
||||
opacity: 0;
|
||||
transform: scale(1.04);
|
||||
}
|
||||
.bg-fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
.bg-slide-enter-active,
|
||||
.bg-slide-leave-active {
|
||||
transition: opacity 0.85s ease, transform 0.85s ease;
|
||||
}
|
||||
.bg-slide-enter {
|
||||
opacity: 0;
|
||||
transform: translate3d(3.5%, 0, 0) scale(1.01);
|
||||
}
|
||||
.bg-slide-leave-to {
|
||||
opacity: 0;
|
||||
transform: translate3d(-3.5%, 0, 0) scale(0.99);
|
||||
}
|
||||
.bg-zoom-enter-active,
|
||||
.bg-zoom-leave-active {
|
||||
transition: opacity 1s ease, transform 1s ease;
|
||||
}
|
||||
.bg-zoom-enter {
|
||||
opacity: 0;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
.bg-zoom-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.94);
|
||||
}
|
||||
.bg-blur-enter-active,
|
||||
.bg-blur-leave-active {
|
||||
transition: opacity 0.9s ease, filter 0.9s ease, transform 0.9s ease;
|
||||
}
|
||||
.bg-blur-enter {
|
||||
opacity: 0;
|
||||
filter: blur(8px);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
.bg-blur-leave-to {
|
||||
opacity: 0;
|
||||
filter: blur(6px);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
.login-logo {
|
||||
display: block;
|
||||
|
||||
BIN
src/views/screen/background.png
Normal file
BIN
src/views/screen/background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 MiB |
418
src/views/screen/index.vue
Normal file
418
src/views/screen/index.vue
Normal file
@ -0,0 +1,418 @@
|
||||
<template>
|
||||
<div class="big-screen-container">
|
||||
<!-- 背景容器 -->
|
||||
<div class="bg-wrapper">
|
||||
<!-- 右侧功率曲线图表 -->
|
||||
<div class="chart-box power-chart">
|
||||
<div ref="powerChart" class="chart-content"></div>
|
||||
</div>
|
||||
|
||||
<!-- 底部中间电力需求曲线图表(width:980px, bottom:26px) -->
|
||||
<div class="chart-box demand-chart">
|
||||
<div ref="demandChart" class="chart-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import { getPointData } from '@/api/ems/dzjk'
|
||||
|
||||
export default {
|
||||
name: 'PowerCurveScreen',
|
||||
data() {
|
||||
return {
|
||||
powerChartInstance: null, // 功率曲线实例
|
||||
demandChartInstance: null, // 电力需求曲线实例
|
||||
timer: null,
|
||||
siteId: '021_DDS_01',
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initPowerChart() // 初始化功率曲线
|
||||
this.initDemandChart() // 初始化电力需求曲线
|
||||
|
||||
// Initial fetch
|
||||
this.fetchData()
|
||||
// Polling every 5 seconds
|
||||
this.timer = setInterval(() => {
|
||||
this.fetchData()
|
||||
}, 5000)
|
||||
|
||||
window.addEventListener('resize', this.resizeAllCharts)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
// 销毁两个图表实例
|
||||
if (this.powerChartInstance) {
|
||||
this.powerChartInstance.dispose()
|
||||
this.powerChartInstance = null
|
||||
}
|
||||
if (this.demandChartInstance) {
|
||||
this.demandChartInstance.dispose()
|
||||
this.demandChartInstance = null
|
||||
}
|
||||
window.removeEventListener('resize', this.resizeAllCharts)
|
||||
},
|
||||
methods: {
|
||||
getTodayDate() {
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(now.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
},
|
||||
fetchData() {
|
||||
const today = this.getTodayDate()
|
||||
getPointData({
|
||||
siteId: this.siteId,
|
||||
startDate: today,
|
||||
endDate: today
|
||||
}).then(response => {
|
||||
if (response.code === 200 && response.data) {
|
||||
this.updateCharts(response.data)
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Failed to fetch point data:', error)
|
||||
})
|
||||
},
|
||||
updateCharts(data) {
|
||||
if (!data || !Array.isArray(data)) return
|
||||
const source = [['日期', '电网功率', '负载功率', '储能功率', '光伏功率']];
|
||||
const demandSource = [['日期', '电网功率', '负载功率']]
|
||||
|
||||
data.forEach(item => {
|
||||
source.push([
|
||||
item.statisDate,
|
||||
item.gridPower,
|
||||
item.loadPower,
|
||||
item.storagePower,
|
||||
item.pvPower
|
||||
]);
|
||||
demandSource.push([
|
||||
item.statisDate,
|
||||
item.gridPower, // Mapping Grid Power to Plan Demand (or just as 2nd series)
|
||||
item.loadPower, // Mapping Load Power to Actual Demand
|
||||
])
|
||||
})
|
||||
|
||||
if (this.powerChartInstance) {
|
||||
this.powerChartInstance.setOption({
|
||||
dataset: {
|
||||
source: source
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (this.demandChartInstance) {
|
||||
this.demandChartInstance.setOption({
|
||||
dataset: {
|
||||
source: demandSource
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 初始化右侧功率曲线
|
||||
initPowerChart() {
|
||||
this.powerChartInstance = echarts.init(this.$refs.powerChart)
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
confine: true,
|
||||
backgroundColor: 'rgba(0, 30, 60, 0.9)',
|
||||
borderColor: '#00ccff',
|
||||
textStyle: { color: '#fff' },
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: 'rgba(0, 204, 255, 0.5)',
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
textStyle: { color: '#00ccff', fontSize: 10 },
|
||||
bottom: 0,
|
||||
left: 'center',
|
||||
itemWidth: 9,
|
||||
itemHeight: 9,
|
||||
itemGap: 8,
|
||||
padding: [0, 0, 0, 0]
|
||||
},
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
top: '15%',
|
||||
bottom: '12%',
|
||||
containLabel: true
|
||||
},
|
||||
dataset: {
|
||||
source: [] // Initial empty source
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisLabel: {
|
||||
color: '#00ccff',
|
||||
fontSize: 10,
|
||||
rotate: 0, // 取消旋转,因为标签较少
|
||||
margin: 10,
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: '#006699', width: 1.5 }
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisTick: { show: true }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 0, // Remove fixed min/max to let it auto-scale
|
||||
max: 350,
|
||||
interval: 50,
|
||||
axisLabel: {
|
||||
color: '#00ccff',
|
||||
fontSize: 12,
|
||||
margin: 10,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
lineStyle: { color: '#006699', width: 1.5 }
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 100, 150, 0.2)' } },
|
||||
axisTick: { show: false }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// name: '电网功率', // Removed, auto-matched from dataset header
|
||||
type: 'line',
|
||||
// data: [], // Removed
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: '#0099ff', width: 2 },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(0, 153, 255, 0.4)' },
|
||||
{ offset: 1, color: 'rgba(0, 153, 255, 0.05)' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
// name: '负载功率',
|
||||
type: 'line',
|
||||
// data: [],
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: '#00cc66', width: 2 },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(0, 204, 102, 0.4)' },
|
||||
{ offset: 1, color: 'rgba(0, 204, 102, 0.05)' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
// name: '储能功率',
|
||||
type: 'line',
|
||||
// data: [],
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: '#ffcc00', width: 2 },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(255, 204, 0, 0.4)' },
|
||||
{ offset: 1, color: 'rgba(255, 204, 0, 0.05)' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
// name: '光伏功率',
|
||||
type: 'line',
|
||||
// data: [],
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: '#ff3333', width: 2 },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(255, 51, 51, 0.4)' },
|
||||
{ offset: 1, color: 'rgba(255, 51, 51, 0.05)' }
|
||||
])
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
this.powerChartInstance.setOption(option)
|
||||
},
|
||||
|
||||
// 初始化底部中间电力需求曲线(适配980px宽度+130px高度+bottom:26px,两条曲线区分配色)
|
||||
initDemandChart() {
|
||||
this.demandChartInstance = echarts.init(this.$refs.demandChart)
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent', // 透明背景
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0, 30, 60, 0.9)',
|
||||
borderColor: '#00ccff', // 匹配图片主色
|
||||
textStyle: { color: '#fff' },
|
||||
axisPointer: { type: 'shadow' },
|
||||
confine: true,
|
||||
},
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
top: '5%',
|
||||
bottom: '8%',
|
||||
containLabel: true
|
||||
},
|
||||
dataset: {
|
||||
source: [] // Initial empty
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
// data: [], // Handled by dataset
|
||||
axisLabel: {
|
||||
color: '#00ccff', // 匹配图片主色
|
||||
fontSize: 10,
|
||||
margin: 10,
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: { color: '#006699', width: 1.5 }
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisTick: { show: true }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 300,
|
||||
interval: 50,
|
||||
axisLabel: {
|
||||
color: '#00ccff', // 匹配图片主色
|
||||
fontSize: 12,
|
||||
margin: 10,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
// lineStyle: { color: '#006699', width: 1.5 }
|
||||
},
|
||||
splitLine: { lineStyle: { color: 'rgba(0, 100, 150, 0.2)' } },
|
||||
axisTick: { show: false }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
// data: [],
|
||||
smooth: true, // 平滑曲线
|
||||
symbol: 'none', // 无数据点
|
||||
// lineStyle: { color: '#00F2FF', width: 2.5 }, // 匹配图片主题色
|
||||
// areaStyle: {
|
||||
// // 匹配图片的线性渐变:#00F2FF 从100%不透明到0%不透明
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// { offset: 0, color: 'rgba(0, 242, 255, 1)' }, // 0%位置:#00F2FF 100%不透明
|
||||
// { offset: 1, color: 'rgba(0, 242, 255, 0)' } // 100%位置:#00F2FF 0%不透明
|
||||
// ])
|
||||
// }
|
||||
lineStyle: { color: '#0099ff', width: 2 },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(0, 153, 255, 0.4)' },
|
||||
{ offset: 1, color: 'rgba(0, 153, 255, 0.05)' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
// name: '负载功率',
|
||||
type: 'line',
|
||||
// data: [],
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: '#00cc66', width: 2 },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(0, 204, 102, 0.4)' },
|
||||
{ offset: 1, color: 'rgba(0, 204, 102, 0.05)' }
|
||||
])
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
this.demandChartInstance.setOption(option)
|
||||
},
|
||||
|
||||
// 统一处理窗口缩放(适配两个图表)
|
||||
resizeAllCharts() {
|
||||
if (this.powerChartInstance) this.powerChartInstance.resize()
|
||||
if (this.demandChartInstance) this.demandChartInstance.resize()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 大屏容器:固定1920x1080尺寸 */
|
||||
.big-screen-container {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 背景容器:相对路径 ./background.png */
|
||||
.bg-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("./background.png");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 通用图表容器样式 */
|
||||
.chart-box {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 右侧功率曲线图表定位+尺寸 */
|
||||
.power-chart {
|
||||
position: absolute;
|
||||
top: 143px;
|
||||
right: 40px;
|
||||
width: 380px;
|
||||
height: 270px;
|
||||
}
|
||||
|
||||
/* 底部中间电力需求曲线图表(核心修改:width:980px, bottom:26px, height:130px) */
|
||||
.demand-chart {
|
||||
position: absolute;
|
||||
bottom: 26px; /* 修改为26px底部间距 */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 980px; /* 修改为980px宽度 */
|
||||
height: 130px; /* 保持130px高度不变 */
|
||||
}
|
||||
|
||||
/* 图表内容区:填满容器 */
|
||||
.chart-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -47,7 +47,7 @@ module.exports = {
|
||||
// }
|
||||
// 当请求前缀是/dev-api时,使用下面的代理
|
||||
'/dev-api': {
|
||||
target: 'http://localhost:8089',
|
||||
target: 'http://110.40.171.179:8089',
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/dev-api': ''
|
||||
|
||||
Reference in New Issue
Block a user