2025-06-22 17:22:40 +08:00
|
|
|
|
<template>
|
2026-02-15 16:24:29 +08:00
|
|
|
|
<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>
|
2025-12-12 17:38:26 +08:00
|
|
|
|
<el-card
|
2026-02-15 16:24:29 +08:00
|
|
|
|
v-for="(group, index) in filteredSectionGroups"
|
|
|
|
|
|
:key="index + 'dbSection'"
|
|
|
|
|
|
class="sbjk-card-container list running-card-container"
|
|
|
|
|
|
shadow="always"
|
2025-12-12 17:38:26 +08:00
|
|
|
|
>
|
|
|
|
|
|
<div slot="header">
|
2026-02-15 16:24:29 +08:00
|
|
|
|
<span class="large-title">{{ group.displayName || "电表" }}</span>
|
2025-12-12 17:38:26 +08:00
|
|
|
|
<div class="info">
|
2026-02-15 16:24:29 +08:00
|
|
|
|
<div>状态:{{ group.statusText }}</div>
|
|
|
|
|
|
<div>数据更新时间:{{ group.updateTimeText }}</div>
|
2025-12-12 17:38:26 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-row class="device-info-row">
|
2026-02-15 16:24:29 +08:00
|
|
|
|
<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>
|
2026-01-23 14:18:22 +08:00
|
|
|
|
</el-col>
|
2025-12-12 17:38:26 +08:00
|
|
|
|
</el-row>
|
|
|
|
|
|
</el-card>
|
2025-08-15 14:13:54 +08:00
|
|
|
|
</div>
|
2025-06-22 17:22:40 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
2025-06-30 17:32:04 +08:00
|
|
|
|
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
2025-09-10 09:54:29 +08:00
|
|
|
|
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
2026-02-15 16:24:29 +08:00
|
|
|
|
import { getProjectDisplayData } from "@/api/ems/dzjk";
|
|
|
|
|
|
import { getDeviceList } from "@/api/ems/site";
|
2025-12-12 17:38:26 +08:00
|
|
|
|
|
2025-06-22 17:22:40 +08:00
|
|
|
|
export default {
|
2025-08-15 14:13:54 +08:00
|
|
|
|
name: "DzjkSbjkDb",
|
2025-12-12 17:38:26 +08:00
|
|
|
|
mixins: [getQuerySiteId, intervalUpdate],
|
2025-06-22 17:22:40 +08:00
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
2025-08-15 14:13:54 +08:00
|
|
|
|
loading: false,
|
2026-02-15 16:24:29 +08:00
|
|
|
|
displayData: [],
|
|
|
|
|
|
selectedSectionKey: "",
|
|
|
|
|
|
ammeterDeviceList: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
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;
|
2025-09-28 14:31:24 +08:00
|
|
|
|
}
|
2026-02-15 16:24:29 +08:00
|
|
|
|
});
|
|
|
|
|
|
const fallbackValueMap = {};
|
|
|
|
|
|
fallbackRows.forEach((item) => {
|
|
|
|
|
|
const key = String(item?.fieldName || "").trim();
|
|
|
|
|
|
if (key && fallbackValueMap[key] === undefined) {
|
|
|
|
|
|
fallbackValueMap[key] = item;
|
2025-09-28 14:31:24 +08:00
|
|
|
|
}
|
2026-02-15 16:24:29 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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 [
|
2026-01-23 14:18:22 +08:00
|
|
|
|
{
|
2026-02-15 16:24:29 +08:00
|
|
|
|
sectionName: "电参量",
|
|
|
|
|
|
sectionKey: "电参量",
|
|
|
|
|
|
displayName: "电表",
|
|
|
|
|
|
items: this.fallbackFields.map((fieldName) => ({ fieldName, fieldValue: "-" })),
|
|
|
|
|
|
statusText: "-",
|
|
|
|
|
|
updateTimeText: "-",
|
2026-01-23 14:18:22 +08:00
|
|
|
|
},
|
2026-02-15 16:24:29 +08:00
|
|
|
|
];
|
|
|
|
|
|
},
|
|
|
|
|
|
filteredSectionGroups() {
|
|
|
|
|
|
const groups = this.displaySectionGroups || [];
|
|
|
|
|
|
if (!this.selectedSectionKey) {
|
|
|
|
|
|
return groups;
|
|
|
|
|
|
}
|
|
|
|
|
|
return groups.filter((group) => group.sectionKey === this.selectedSectionKey);
|
|
|
|
|
|
},
|
|
|
|
|
|
fallbackFields() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
"正向有功电能",
|
|
|
|
|
|
"反向有功电能",
|
|
|
|
|
|
"正向无功电能",
|
|
|
|
|
|
"反向无功电能",
|
|
|
|
|
|
"有功功率",
|
|
|
|
|
|
"无功功率",
|
|
|
|
|
|
];
|
|
|
|
|
|
},
|
2025-06-22 17:22:40 +08:00
|
|
|
|
},
|
2025-08-15 14:13:54 +08:00
|
|
|
|
methods: {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
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;
|
2025-11-25 17:56:12 +08:00
|
|
|
|
},
|
2026-02-15 16:24:29 +08:00
|
|
|
|
getAmmeterDeviceList() {
|
|
|
|
|
|
return getDeviceList(this.siteId)
|
|
|
|
|
|
.then((response) => {
|
|
|
|
|
|
const list = response?.data || [];
|
|
|
|
|
|
this.ammeterDeviceList = list.filter((item) => item.deviceCategory === "AMMETER");
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
this.ammeterDeviceList = [];
|
|
|
|
|
|
});
|
2025-09-13 20:36:46 +08:00
|
|
|
|
},
|
2025-12-12 17:38:26 +08:00
|
|
|
|
updateData() {
|
2025-08-15 14:13:54 +08:00
|
|
|
|
this.loading = true;
|
2026-02-15 16:24:29 +08:00
|
|
|
|
Promise.all([getProjectDisplayData(this.siteId), this.getAmmeterDeviceList()])
|
|
|
|
|
|
.then(([response]) => {
|
|
|
|
|
|
this.displayData = response?.data || [];
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
this.loading = false;
|
|
|
|
|
|
});
|
2025-09-10 09:54:29 +08:00
|
|
|
|
},
|
|
|
|
|
|
init() {
|
2026-02-15 16:24:29 +08:00
|
|
|
|
this.updateData();
|
|
|
|
|
|
this.updateInterval(this.updateData);
|
2025-08-15 14:13:54 +08:00
|
|
|
|
},
|
2025-06-22 17:22:40 +08:00
|
|
|
|
},
|
2025-08-15 14:13:54 +08:00
|
|
|
|
};
|
2025-06-22 17:22:40 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
2025-09-22 17:57:30 +08:00
|
|
|
|
.sbjk-card-container {
|
2025-12-12 17:38:26 +08:00
|
|
|
|
&.list:not(:last-child) {
|
2025-09-26 14:47:45 +08:00
|
|
|
|
margin-bottom: 25px;
|
|
|
|
|
|
}
|
2026-02-15 16:24:29 +08:00
|
|
|
|
|
|
|
|
|
|
.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;
|
2025-06-22 17:22:40 +08:00
|
|
|
|
}
|
2025-09-17 14:52:08 +08:00
|
|
|
|
|
2026-02-15 16:24:29 +08:00
|
|
|
|
.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); }
|
|
|
|
|
|
}
|
2025-06-22 17:22:40 +08:00
|
|
|
|
</style>
|