842 lines
26 KiB
Vue
842 lines
26 KiB
Vue
<template>
|
||
<div>
|
||
<el-row style="background: #fff" class="row-container" :gutter="15">
|
||
<el-col :xs="24" :sm="24" :lg="5">
|
||
<!-- 站点信息-->
|
||
<el-card
|
||
shadow="always"
|
||
class="common-card-container common-card-container-body-no-padding"
|
||
>
|
||
<div slot="header">
|
||
<span class="card-title">站点信息</span>
|
||
<div class="alarm-msg" v-if="tableData.length > 0" @click="toAlarm">
|
||
<i class="el-icon-message-solid"></i> 设备告警
|
||
</div>
|
||
</div>
|
||
<div
|
||
style="box-sizing: border-box; height: 218px; padding: 20px 15px"
|
||
>
|
||
<!-- 地址、运行时间-->
|
||
<div class="site-info site-info-address">
|
||
<div class="title">
|
||
<i class="el-icon-location"></i>
|
||
</div>
|
||
<div class="value">
|
||
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
|
||
<span v-else>{{ info.siteAddress || '-' }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="site-info">
|
||
<div class="title">
|
||
<i class="el-icon-date"></i>
|
||
</div>
|
||
<div class="value">
|
||
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
|
||
<span v-else>{{ info.runningTime || '-' }}</span>
|
||
</div>
|
||
</div>
|
||
<!-- 装机功率、容量 -->
|
||
<el-row :gutter="10" style="margin-top:20px;">
|
||
<el-col
|
||
:span="12"
|
||
class="sjgl-col power-col"
|
||
>
|
||
<div class="sjgl-wrapper">
|
||
<div class="sjgl-title">装机功率(MWh)</div>
|
||
<div class="sjgl-value">
|
||
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
|
||
<span v-else>{{ info.installPower | formatNumber }}</span>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
<el-col
|
||
:span="12"
|
||
class="sjgl-col power-col"
|
||
>
|
||
<div class="sjgl-wrapper">
|
||
<div class="sjgl-title">装机容量(MWh)</div>
|
||
<div class="sjgl-value">
|
||
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
|
||
<span v-else>{{ info.installCapacity | formatNumber }}</span>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<!-- 总累计运行数据-->
|
||
<el-col :xs="24" :sm="24" :lg="19">
|
||
<el-card
|
||
shadow="always"
|
||
class="common-card-container common-card-container-body-no-padding"
|
||
>
|
||
<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="unit">元</span>
|
||
</div>
|
||
</div>
|
||
<div
|
||
style="box-sizing: border-box; height: 218px; padding: 20px 15px"
|
||
>
|
||
<el-row :gutter="10">
|
||
<el-col
|
||
:span="6"
|
||
v-for="(item, index) in runningDataCards"
|
||
:key="index + 'sjglData'"
|
||
class="sjgl-col"
|
||
>
|
||
<div
|
||
class="sjgl-wrapper pointer-field"
|
||
:class="{ 'field-disabled': !hasPointId(item.raw) }"
|
||
@click="handleRunningFieldClick(item)"
|
||
>
|
||
<div class="sjgl-title">{{ item.title }}</div>
|
||
<div class="sjgl-value" :style="{color:item.color}">
|
||
<i v-if="item.loading" class="el-icon-loading"></i>
|
||
<span v-else>{{ item.value | formatNumber }}</span>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :xs="24" :sm="24" :lg="12">
|
||
<week-chart ref="weekChart" :display-data="runningDisplayData"/>
|
||
</el-col>
|
||
<el-col :xs="24" :sm="24" :lg="12">
|
||
<active-chart ref="activeChart" :display-data="runningDisplayData"/>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-dialog
|
||
:visible.sync="curveDialogVisible"
|
||
:title="curveDialogTitle"
|
||
width="1000px"
|
||
append-to-body
|
||
class="ems-dialog"
|
||
:close-on-click-modal="false"
|
||
destroy-on-close
|
||
@opened="handleCurveDialogOpened"
|
||
@closed="handleCurveDialogClosed"
|
||
>
|
||
<div class="curve-tools">
|
||
<el-date-picker
|
||
v-model="curveCustomRange"
|
||
type="datetimerange"
|
||
value-format="yyyy-MM-dd HH:mm:ss"
|
||
range-separator="至"
|
||
start-placeholder="开始时间"
|
||
end-placeholder="结束时间"
|
||
style="width: 440px"
|
||
/>
|
||
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
|
||
</div>
|
||
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import * 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 WeekChart from "./WeekChart.vue";
|
||
import ActiveChart from "./ActiveChart.vue";
|
||
import AlarmTable from "./AlarmTable.vue";
|
||
import ClInfo from "./ClInfo.vue";
|
||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||
|
||
export default {
|
||
name: "DzjkSbjkHome",
|
||
components: {WeekChart, ActiveChart, AlarmTable, ClInfo},
|
||
mixins: [getQuerySiteId, intervalUpdate],
|
||
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: [
|
||
{
|
||
title: "今日充电量(kWh)",
|
||
attr: "dayChargedCap",
|
||
color: '#4472c4'
|
||
},
|
||
{
|
||
title: "今日放电量(kWh)",
|
||
attr: "dayDisChargedCap",
|
||
color: '#70ad47'
|
||
},
|
||
{
|
||
title: "总充电量(kWh)",
|
||
attr: "totalChargedCap",
|
||
color: '#4472c4'
|
||
},
|
||
{
|
||
title: "今日实时收入(元)",
|
||
attr: "dayRevenue",
|
||
color: '#f67438'
|
||
},
|
||
{
|
||
title: "昨日充电量(kWh)",
|
||
attr: "yesterdayChargedCap",
|
||
color: '#4472c4'
|
||
},
|
||
{
|
||
title: "昨日放电量(kWh)",
|
||
attr: "yesterdayDisChargedCap",
|
||
color: '#70ad47'
|
||
},
|
||
{
|
||
title: "总放电量(kWh)",
|
||
attr: "totalDischargedCap",
|
||
color: '#70ad47'
|
||
},
|
||
{
|
||
title: "昨日实时收入(元)",
|
||
attr: "yesterdayRevenue",
|
||
color: '#f67438'
|
||
},
|
||
],
|
||
info: {}, //基本信息
|
||
runningInfo: {}, //总累计运行数据+报警表格
|
||
runningDisplayData: [], //单站监控项目配置展示数据
|
||
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});
|
||
},
|
||
getBaseInfo() {
|
||
return getSingleSiteBaseInfo(this.siteId).then((response) => {
|
||
this.info = response?.data || {};
|
||
});
|
||
},
|
||
getRunningInfo() {
|
||
const hasOldData = Object.keys(this.runningInfo || {}).length > 0 || (this.runningDisplayData || []).length > 0;
|
||
if (!hasOldData) {
|
||
this.setRunningInfoLoading(true);
|
||
}
|
||
return Promise.all([
|
||
getDzjkHomeTotalView(this.siteId),
|
||
getProjectDisplayData(this.siteId),
|
||
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);
|
||
}
|
||
});
|
||
},
|
||
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.$refs.activeChart.init(this.siteId);
|
||
// 一周冲放曲线
|
||
this.$refs.weekChart.init(this.siteId);
|
||
// 静态信息 this.getBaseInfo()
|
||
// 总累计运行数据+故障告警 this.getRunningInfo()
|
||
Promise.all([this.getBaseInfo(), this.getRunningInfo()]);
|
||
// 一分钟循环一次总累计运行数据
|
||
this.updateInterval(this.getRunningInfo);
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
//设备告警
|
||
.alarm-msg {
|
||
background: #ff4949;
|
||
padding: 2px 5px;
|
||
font-size: 10px;
|
||
font-weight: bolder;
|
||
border-radius: 3px;
|
||
line-height: 20px;
|
||
cursor: pointer;
|
||
color: #fff;
|
||
position: absolute;
|
||
right: 10px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
}
|
||
|
||
//基本信息-地址 运行️时间
|
||
.site-info {
|
||
display: flex;
|
||
font-size: 12px;
|
||
line-height: 20px;
|
||
margin-bottom: 10px;
|
||
align-items: center;
|
||
|
||
&.site-info-address {
|
||
height: 40px;
|
||
}
|
||
|
||
.title {
|
||
color: #1791ff;
|
||
font-size: 18px;
|
||
line-height: 20px;
|
||
margin-right: 7px;
|
||
}
|
||
|
||
.value {
|
||
color: #000;
|
||
font-size: 12px;
|
||
max-height: 40px;
|
||
overflow: hidden;
|
||
}
|
||
}
|
||
|
||
//总收入
|
||
.total-count {
|
||
position: absolute;
|
||
right: 10px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
font-size: 12px;
|
||
font-weight: bolder;
|
||
color: #333;
|
||
line-height: 14px;
|
||
|
||
.unit {
|
||
font-style: italic;
|
||
}
|
||
|
||
.value {
|
||
font-size: 22px;
|
||
font-weight: bolder;
|
||
color: #ed2f1d;
|
||
font-style: italic;
|
||
padding: 0 5px;
|
||
}
|
||
}
|
||
|
||
.pointer-field {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.field-disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.75;
|
||
}
|
||
|
||
.curve-tools {
|
||
margin-bottom: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.row-container {
|
||
& > .el-col {
|
||
margin-bottom: 20px;
|
||
}
|
||
}
|
||
|
||
//数据概览
|
||
.sjgl-col {
|
||
.sjgl-wrapper {
|
||
text-align: left;
|
||
padding: 15px 20px;
|
||
background-color: #f2f7fb;
|
||
}
|
||
|
||
&.power-col {
|
||
.sjgl-wrapper {
|
||
padding: 10px;
|
||
|
||
.sjgl-value {
|
||
color: #c44444;
|
||
}
|
||
}
|
||
}
|
||
|
||
&:nth-child(4),
|
||
&:nth-child(2),
|
||
&:nth-child(3),
|
||
&:nth-child(4) {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.sjgl-title {
|
||
color: #717171;
|
||
line-height: 14px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.sjgl-value {
|
||
color: rgba(51, 51, 51, 1);
|
||
font-size: 22px;
|
||
line-height: 26px;
|
||
font-weight: bolder;
|
||
font-style: italic;
|
||
margin-top: 14px;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<style lang="scss">
|
||
.home-normal-info {
|
||
font-size: 12px;
|
||
|
||
.el-descriptions-item__container {
|
||
.el-descriptions-item__label {
|
||
color: #666666;
|
||
}
|
||
|
||
.el-descriptions-item__content {
|
||
color: #333333;
|
||
}
|
||
}
|
||
}
|
||
</style>
|