Files
emsfront/src/views/ems/dzjk/sbjk/db/index.vue

498 lines
14 KiB
Vue
Raw Normal View History

<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"
2026-02-16 13:41:41 +08:00
:class="{ 'field-disabled': !item.pointId }"
2026-02-15 16:24:29 +08:00
>
2026-02-16 13:41:41 +08:00
<div class="field-click-wrapper" @click="handleFieldClick(item)">
<span class="left">{{ item.fieldName }}</span>
<span class="right">
<i v-if="isPointLoading(item.fieldValue)" class="el-icon-loading point-loading-icon"></i>
<span v-else>{{ displayValue(item.fieldValue) | formatNumber }}</span>
</span>
</div>
2026-01-23 14:18:22 +08:00
</el-col>
2025-12-12 17:38:26 +08:00
</el-row>
</el-card>
2026-02-16 13:41:41 +08:00
<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>
2025-08-15 14:13:54 +08:00
</div>
</template>
<script>
2026-02-16 13:41:41 +08:00
import * as echarts from "echarts";
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";
2026-02-16 13:41:41 +08:00
import { getDeviceList, getPointConfigCurve } from "@/api/ems/site";
2025-12-12 17:38:26 +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],
data() {
return {
2025-08-15 14:13:54 +08:00
loading: false,
2026-02-15 16:24:29 +08:00
displayData: [],
selectedSectionKey: "",
ammeterDeviceList: [],
2026-02-16 13:41:41 +08:00
curveDialogVisible: false,
curveDialogTitle: "点位曲线",
curveChart: null,
curveLoading: false,
curveCustomRange: [],
curveQuery: {
siteId: "",
pointId: "",
pointType: "data",
rangeType: "custom",
startTime: "",
endTime: "",
},
2026-02-15 16:24:29 +08:00
};
},
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;
}
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;
}
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,
2026-02-16 13:41:41 +08:00
pointId: String(row?.dataPoint || "").trim(),
raw: row,
2026-02-15 16:24:29 +08:00
};
});
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-08-15 14:13:54 +08:00
methods: {
2026-02-16 13:41:41 +08:00
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("当前时间范围暂无曲线数据");
}
},
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
},
},
2026-02-16 13:41:41 +08:00
beforeDestroy() {
if (this.curveChart) {
this.curveChart.dispose();
this.curveChart = null;
}
},
2025-08-15 14:13:54 +08:00
};
</script>
<style scoped lang="scss">
.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-09-17 14:52:08 +08:00
2026-02-15 16:24:29 +08:00
.pcs-tag-item {
cursor: pointer;
}
2026-02-16 13:41:41 +08:00
.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;
}
2026-02-15 16:24:29 +08:00
.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>