This commit is contained in:
2026-02-15 16:24:29 +08:00
parent 50c72d6989
commit 41a3ab45b3
36 changed files with 4896 additions and 2089 deletions

View File

@ -1,229 +1,239 @@
<template>
<div v-loading="loading">
<div>
<div class="pcs-tags">
<el-tag
size="small"
:type="selectedSectionKey ? 'info' : 'primary'"
:effect="selectedSectionKey ? 'plain' : 'dark'"
class="pcs-tag-item"
@click="handleTagClick('')"
>
全部
</el-tag>
<el-tag
v-for="(group, index) in sectionGroups"
:key="index + 'dbTag'"
size="small"
:type="selectedSectionKey === group.sectionKey ? 'primary' : 'info'"
:effect="selectedSectionKey === group.sectionKey ? 'dark' : 'plain'"
class="pcs-tag-item"
@click="handleTagClick(group.sectionKey)"
>
{{ group.displayName || "电表" }}
</el-tag>
</div>
<el-card
v-for="(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'
}"
v-for="(group, index) in filteredSectionGroups"
:key="index + 'dbSection'"
class="sbjk-card-container list running-card-container"
shadow="always"
>
<div slot="header">
<span class="large-title">{{ item.deviceName }}</span>
<span class="large-title">{{ group.displayName || "电表" }}</span>
<div class="info">
<div>
{{
communicationStatusOptions[item.emsCommunicationStatus] || '-'
}}
</div>
<div>数据更新时间{{ item.dataUpdateTime || '-' }}</div>
</div>
<div class="alarm">
<el-button type="primary" round size="small" style="margin-right:20px;" @click="pointDetail(item,'point')">
详细
</el-button>
<el-badge :hidden="!item.alarmNum" :value="item.alarmNum || 0" class="item">
<i
class="el-icon-message-solid alarm-icon"
@click="pointDetail(item,'alarmPoint')"
></i>
</el-badge>
<div>状态{{ group.statusText }}</div>
<div>数据更新时间{{ group.updateTimeText }}</div>
</div>
</div>
<el-row class="device-info-row">
<el-col v-for="(tempDataItem,tempDataIndex) in (deviceIdTypeMsg[item.deviceId] || otherTypeMsg)"
:key="tempDataIndex+'dbTempData'"
:span="8" class="device-info-col">
<span class="pointer" @click="showChart(tempDataItem.pointName,item.deviceId)">
<span class="left">{{ tempDataItem.name }}</span> <span class="right">{{ item[tempDataItem.attr] || '-' }}<span
v-html="tempDataItem.unit"></span></span>
</span>
<el-col
v-for="(item, dataIndex) in group.items"
:key="dataIndex + 'dbField'"
:span="8"
class="device-info-col"
>
<span class="left">{{ item.fieldName }}</span>
<span class="right">
<i v-if="isPointLoading(item.fieldValue)" class="el-icon-loading point-loading-icon"></i>
<span v-else>{{ displayValue(item.fieldValue) | formatNumber }}</span>
</span>
</el-col>
</el-row>
</el-card>
<el-empty v-show="list.length<=0" :image-size="200"></el-empty>
<point-chart ref="pointChart" :site-id="siteId"/>
<point-table ref="pointTable"/>
</div>
</template>
<script>
import pointChart from "./../PointChart.vue";
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {getAmmeterDataList} from "@/api/ems/dzjk";
import intervalUpdate from "@/mixins/ems/intervalUpdate";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
import {mapState} from "vuex";
import { getProjectDisplayData } from "@/api/ems/dzjk";
import { getDeviceList } from "@/api/ems/site";
export default {
name: "DzjkSbjkDb",
mixins: [getQuerySiteId, intervalUpdate],
components: {PointTable, pointChart},
computed: {
...mapState({
communicationStatusOptions: state => state?.ems?.communicationStatusOptions || {},
})
},
data() {
return {
loading: false,
list: [],
deviceIdTypeMsg: {
'LOAD': [
{
name: '正向有功电能',
attr: 'forwardActive',
pointName: '正向有功电能',
unit: 'kWh'
},
{
name: '反向有功电能',
attr: 'reverseActive',
pointName: '反向有功电能',
unit: 'kWh'
},
{
name: '正向无功电能',
attr: 'forwardReactive',
pointName: '正向无功电能',
unit: 'kvarh'
},
{
name: '反向无功电能',
attr: 'reverseReactive',
pointName: '反向无功电能',
unit: 'kvarh'
},
{
name: '有功功率',
attr: 'activePower',
pointName: '总有功功率',
unit: 'kW'
},
{
name: '无功功率',
attr: 'reactivePower',
pointName: '总无功功率',
unit: 'kvar'
}
],
'METE': [
{
name: '正向有功电能',
attr: 'forwardActive',
pointName: '正向有功电能',
unit: 'kWh'
},
{
name: '反向有功电能',
attr: 'reverseActive',
pointName: '反向有功电能',
unit: 'kWh'
},
{
name: '正向无功电能',
attr: 'forwardReactive',
pointName: '正向无功电能',
unit: 'kvarh'
},
{
name: '反向无功电能',
attr: 'reverseReactive',
pointName: '反向无功电能',
unit: 'kvarh'
},
{
name: '有功功率',
attr: 'activePower',
pointName: '总有功功率',
unit: 'kW'
},
{
name: '无功功率',
attr: 'reactivePower',
pointName: '总无功功率',
unit: 'kvar'
}
],
'METEGF': [
{
name: '有功电能',
attr: 'activeEnergy',
pointName: '有功电能',
unit: 'kWh'
},
{
name: '无功电能',
attr: 'reactiveEnergy',
pointName: '无功电能',
unit: 'kvarh'
},
{
name: '有功功率',
attr: 'activePower',
pointName: '总有功功率',
unit: 'kW'
},
{
name: '无功功率',
attr: 'reactivePower',
pointName: '总无功功率',
unit: 'kvar'
}
]
},
otherTypeMsg: [
{
name: '正向有功电能',
attr: 'forwardActive',
pointName: '正向有功电能',
unit: 'kWh'
},
{
name: '反向有功电能',
attr: 'reverseActive',
pointName: '反向有功电能',
unit: 'kWh'
},
{
name: '有功功率',
attr: 'activePower',
pointName: '总有功功率',
unit: 'kW'
},
]
displayData: [],
selectedSectionKey: "",
ammeterDeviceList: [],
};
},
methods: {
// 查看设备电位表格
pointDetail(row, dataType) {
const {deviceId} = row
this.$refs.pointTable.showTable({siteId: this.siteId, deviceId, deviceCategory: 'AMMETER'}, dataType)
computed: {
moduleDisplayData() {
return (this.displayData || []).filter((item) => item.menuCode === "SBJK_DB");
},
showChart(pointName, deviceId) {
pointName && this.$refs.pointChart.showChart({pointName, deviceCategory: 'AMMETER', deviceId})
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,
};
});
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: {
handleTagClick(sectionKey) {
this.selectedSectionKey = sectionKey || "";
},
displayValue(value) {
return value === undefined || value === null || value === "" ? "-" : value;
},
isPointLoading(value) {
return this.loading && (value === undefined || value === null || value === "" || value === "-");
},
formatDate(date) {
if (!(date instanceof Date) || isNaN(date.getTime())) {
return "-";
}
const p = (n) => String(n).padStart(2, "0");
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())} ${p(date.getHours())}:${p(
date.getMinutes()
)}:${p(date.getSeconds())}`;
},
resolveDbDisplayName(sectionName) {
const key = String(sectionName || "").trim();
if (!key) {
return "电表";
}
const list = this.ammeterDeviceList || [];
const matched = list.find((item) => {
const deviceId = String(item.deviceId || item.id || "").trim();
const deviceName = String(item.deviceName || item.name || "").trim();
return key === deviceId || key === deviceName;
});
if (matched) {
return matched.deviceName || matched.name || key;
}
return key;
},
getAmmeterDeviceList() {
return getDeviceList(this.siteId)
.then((response) => {
const list = response?.data || [];
this.ammeterDeviceList = list.filter((item) => item.deviceCategory === "AMMETER");
})
.catch(() => {
this.ammeterDeviceList = [];
});
},
updateData() {
this.loading = true;
getAmmeterDataList(this.siteId)
.then((response) => {
this.list = response?.data || []
})
.finally(() => {
this.loading = false;
});
Promise.all([getProjectDisplayData(this.siteId), this.getAmmeterDeviceList()])
.then(([response]) => {
this.displayData = response?.data || [];
})
.finally(() => {
this.loading = false;
});
},
init() {
this.updateData()
this.updateInterval(this.updateData)
this.updateData();
this.updateInterval(this.updateData);
},
},
mounted() {
},
};
</script>
@ -232,6 +242,38 @@ 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;
}
.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>