From 7273f7e6e1742c8604bff0bed3b414389aa50b21 Mon Sep 17 00:00:00 2001
From: yuan <123@>
Date: 星期二, 16 六月 2026 13:33:37 +0800
Subject: [PATCH] feat: 重构用电数据展示,优化界面和交互体验
---
src/views/energyManagement/energyStatistics/index.vue | 369 ++++++++++++++++++++++++++++------------------------
1 files changed, 199 insertions(+), 170 deletions(-)
diff --git a/src/views/energyManagement/energyStatistics/index.vue b/src/views/energyManagement/energyStatistics/index.vue
index bb31319..ee4bbd6 100644
--- a/src/views/energyManagement/energyStatistics/index.vue
+++ b/src/views/energyManagement/energyStatistics/index.vue
@@ -1,42 +1,5 @@
<template>
<div class="app-container">
- <!-- 鏄ㄦ棩鐢ㄧ數蹇 -->
- <el-card class="yesterday-card" v-loading="yesterdayLoading">
- <div class="yesterday-header">
- <div>
- <h3>鏄ㄦ棩鐢ㄧ數閲�</h3>
- <p class="sub">{{ yesterdayLabel }}</p>
- </div>
- <el-button type="primary" link @click="viewYesterdayDetail">鏌ョ湅鏄ㄦ棩鏄庣粏</el-button>
- </div>
- <el-row :gutter="16">
- <el-col :span="6">
- <div class="metric-box highlight">
- <div class="metric-label">鎬荤敤鐢甸噺</div>
- <div class="metric-value">{{ formatKwh(yesterdaySummary.totalConsumption) }} <span>kWh</span></div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="metric-box">
- <div class="metric-label">骞冲潎鐢ㄧ數閲�</div>
- <div class="metric-value">{{ formatKwh(yesterdaySummary.avgConsumption) }} <span>kWh</span></div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="metric-box">
- <div class="metric-label">鏈�澶х敤鐢甸噺</div>
- <div class="metric-value">{{ formatKwh(yesterdaySummary.maxConsumption) }} <span>kWh</span></div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="metric-box">
- <div class="metric-label">鏈�灏忕敤鐢甸噺</div>
- <div class="metric-value">{{ formatKwh(yesterdaySummary.minConsumption) }} <span>kWh</span></div>
- </div>
- </el-col>
- </el-row>
- </el-card>
-
<el-card>
<template #header>
<div class="card-header">
@@ -54,39 +17,40 @@
<el-radio-button value="year">骞�</el-radio-button>
</el-radio-group>
</el-form-item>
- <el-form-item label="鏃堕棿鑼冨洿">
- <el-date-picker
- v-if="queryForm.dimension === 'day'"
- v-model="dayRange"
- type="daterange"
- range-separator="鑷�"
- value-format="YYYY-MM-DD"
- />
- <el-date-picker
- v-else-if="queryForm.dimension === 'month'"
- v-model="monthRange"
- type="monthrange"
- range-separator="鑷�"
- value-format="YYYY-MM"
- />
- <el-date-picker
- v-else-if="queryForm.dimension === 'quarter'"
- v-model="quarterRange"
- type="daterange"
- range-separator="鑷�"
- value-format="YYYY-MM-DD"
- />
- <el-date-picker
- v-else
- v-model="yearRange"
- type="yearrange"
- range-separator="鑷�"
- value-format="YYYY"
- />
+ <el-form-item label="鏃堕棿鑼冨洿" class="time-range-item">
+ <div class="time-range-row">
+ <el-date-picker
+ v-if="queryForm.dimension === 'day'"
+ v-model="dayRange"
+ type="daterange"
+ range-separator="鑷�"
+ value-format="YYYY-MM-DD"
+ :shortcuts="dayShortcuts"
+ />
+ <el-date-picker
+ v-else-if="queryForm.dimension === 'month'"
+ v-model="monthRange"
+ type="monthrange"
+ range-separator="鑷�"
+ value-format="YYYY-MM"
+ />
+ <el-date-picker
+ v-else-if="queryForm.dimension === 'quarter'"
+ v-model="quarterRange"
+ type="daterange"
+ range-separator="鑷�"
+ value-format="YYYY-MM-DD"
+ />
+ <el-date-picker
+ v-else
+ v-model="yearRange"
+ type="yearrange"
+ range-separator="鑷�"
+ value-format="YYYY"
+ />
+ </div>
</el-form-item>
<el-form-item>
- <el-button @click="setYesterday">鏄ㄦ棩</el-button>
- <el-button @click="setLast7Days">杩�7澶�</el-button>
<el-button type="primary" :loading="loading" @click="handleQuery">鏌ヨ</el-button>
<el-button @click="handleExport">瀵煎嚭</el-button>
</el-form-item>
@@ -95,33 +59,38 @@
<el-row :gutter="16" class="summary-row">
<el-col :span="6">
<div class="summary-card total">
- <div class="label">鎬荤敤鐢甸噺</div>
+ <div class="label">{{ summaryLabels.total }}</div>
<div class="value">{{ formatKwh(summary.totalConsumption) }} <span>kWh</span></div>
</div>
</el-col>
<el-col :span="6">
<div class="summary-card">
- <div class="label">骞冲潎鐢ㄧ數閲�</div>
+ <div class="label">{{ summaryLabels.avg }}</div>
<div class="value">{{ formatKwh(summary.avgConsumption) }} <span>kWh</span></div>
</div>
</el-col>
<el-col :span="6">
<div class="summary-card">
- <div class="label">鏈�澶х敤鐢甸噺</div>
+ <div class="label">{{ summaryLabels.max }}</div>
<div class="value">{{ formatKwh(summary.maxConsumption) }} <span>kWh</span></div>
</div>
</el-col>
<el-col :span="6">
<div class="summary-card">
- <div class="label">鏈�灏忕敤鐢甸噺</div>
+ <div class="label">{{ summaryLabels.min }}</div>
<div class="value">{{ formatKwh(summary.minConsumption) }} <span>kWh</span></div>
</div>
</el-col>
</el-row>
<div class="chart-toolbar">
- <span>瓒嬪娍鍥�</span>
- <el-radio-group v-model="chartType" size="small" @change="renderChart">
+ <span>{{ chartTitle }}</span>
+ <el-radio-group
+ v-if="!isSingleDay"
+ v-model="chartType"
+ size="small"
+ @change="renderChart"
+ >
<el-radio-button value="line">鎶樼嚎鍥�</el-radio-button>
<el-radio-button value="bar">鏌辩姸鍥�</el-radio-button>
</el-radio-group>
@@ -135,39 +104,71 @@
{{ parseTimeKey(row.timeKey, queryForm.dimension) }}
</template>
</el-table-column>
- <el-table-column prop="meterName" label="鐢佃〃鍚嶇О" min-width="120" show-overflow-tooltip>
- <template #default="{ row }">{{ row.meterName || row.address || row.meterId || "-" }}</template>
+ <el-table-column label="鐢佃〃" min-width="160" show-overflow-tooltip>
+ <template #default="{ row }">
+ <div class="meter-cell">
+ <span class="meter-name">{{ row.meterName || row.address || "-" }}</span>
+ <span v-if="row.meterId" class="meter-id">ID: {{ row.meterId }}</span>
+ </div>
+ </template>
</el-table-column>
- <el-table-column prop="address" label="琛ㄥ湴鍧�" min-width="120" show-overflow-tooltip />
<el-table-column prop="totalConsumption" label="鎬荤數閲�(kWh)" width="120">
<template #default="{ row }">{{ formatKwh(row.totalConsumption) }}</template>
</el-table-column>
- <el-table-column prop="sharpConsumption" label="灏�(kWh)" width="100">
+ <el-table-column v-if="hasPeriodData" prop="sharpConsumption" label="灏�(kWh)" width="100">
<template #default="{ row }">{{ formatKwh(row.sharpConsumption) }}</template>
</el-table-column>
- <el-table-column prop="peakConsumption" label="宄�(kWh)" width="100">
+ <el-table-column v-if="hasPeriodData" prop="peakConsumption" label="宄�(kWh)" width="100">
<template #default="{ row }">{{ formatKwh(row.peakConsumption) }}</template>
</el-table-column>
- <el-table-column prop="flatConsumption" label="骞�(kWh)" width="100">
+ <el-table-column v-if="hasPeriodData" prop="flatConsumption" label="骞�(kWh)" width="100">
<template #default="{ row }">{{ formatKwh(row.flatConsumption) }}</template>
</el-table-column>
- <el-table-column prop="valleyConsumption" label="璋�(kWh)" width="100">
+ <el-table-column v-if="hasPeriodData" prop="valleyConsumption" label="璋�(kWh)" width="100">
<template #default="{ row }">{{ formatKwh(row.valleyConsumption) }}</template>
</el-table-column>
- <el-table-column prop="startTime" label="寮�濮嬫椂闂�" min-width="150" />
- <el-table-column prop="endTime" label="缁撴潫鏃堕棿" min-width="150" />
+ <el-table-column label="鎿嶄綔" width="80" fixed="right">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="showDetail(row)">璇︽儏</el-button>
+ </template>
+ </el-table-column>
</el-table>
</el-card>
+
+ <el-dialog v-model="detailVisible" title="鐢ㄧ數鏄庣粏璇︽儏" width="520px" destroy-on-close>
+ <el-descriptions v-if="detailRow" :column="1" border size="small">
+ <el-descriptions-item label="鏃堕棿">
+ {{ parseTimeKey(detailRow.timeKey, queryForm.dimension) }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鐢佃〃鍚嶇О">{{ detailRow.meterName || "-" }}</el-descriptions-item>
+ <el-descriptions-item label="鐢佃〃ID">{{ detailRow.meterId ?? "-" }}</el-descriptions-item>
+ <el-descriptions-item label="琛ㄥ湴鍧�">{{ detailRow.address || "-" }}</el-descriptions-item>
+ <el-descriptions-item label="鎬荤數閲�(kWh)">{{ formatKwh(detailRow.totalConsumption) }}</el-descriptions-item>
+ <el-descriptions-item v-if="hasPeriodValue(detailRow, 'sharpConsumption')" label="灏�(kWh)">
+ {{ formatKwh(detailRow.sharpConsumption) }}
+ </el-descriptions-item>
+ <el-descriptions-item v-if="hasPeriodValue(detailRow, 'peakConsumption')" label="宄�(kWh)">
+ {{ formatKwh(detailRow.peakConsumption) }}
+ </el-descriptions-item>
+ <el-descriptions-item v-if="hasPeriodValue(detailRow, 'flatConsumption')" label="骞�(kWh)">
+ {{ formatKwh(detailRow.flatConsumption) }}
+ </el-descriptions-item>
+ <el-descriptions-item v-if="hasPeriodValue(detailRow, 'valleyConsumption')" label="璋�(kWh)">
+ {{ formatKwh(detailRow.valleyConsumption) }}
+ </el-descriptions-item>
+ <el-descriptions-item label="寮�濮嬫椂闂�">{{ detailRow.startTime || "-" }}</el-descriptions-item>
+ <el-descriptions-item label="缁撴潫鏃堕棿">{{ detailRow.endTime || "-" }}</el-descriptions-item>
+ </el-descriptions>
+ </el-dialog>
</div>
</template>
<script setup>
-import { computed, getCurrentInstance, onBeforeUnmount, onMounted, reactive, ref } from "vue";
+import { computed, getCurrentInstance, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import { ElMessageBox } from "element-plus";
import * as echarts from "echarts";
import {
summaryStatisticEle,
- getYesterdaySummary,
formatDayPicker,
formatDayTime,
formatMonthTime,
@@ -178,7 +179,6 @@
const { proxy } = getCurrentInstance();
const loading = ref(false);
-const yesterdayLoading = ref(false);
const chartRef = ref(null);
let chartInstance = null;
@@ -187,14 +187,76 @@
const summary = ref({});
const chartRecords = ref([]);
const detailRecords = ref([]);
-const yesterdaySummary = ref({});
+const detailVisible = ref(false);
+const detailRow = ref(null);
const dayRange = ref([]);
const monthRange = ref([]);
const quarterRange = ref([]);
const yearRange = ref([]);
-const yesterdayLabel = computed(() => getYesterdayDayPicker());
+const isSingleDay = computed(() => {
+ if (queryForm.dimension !== "day" || !dayRange.value?.length) return false;
+ return dayRange.value[0] === dayRange.value[1];
+});
+
+const chartDimension = computed(() => (isSingleDay.value ? "hour" : queryForm.dimension));
+
+const chartTitle = computed(() =>
+ isSingleDay.value ? "24灏忔椂鐢ㄧ數瓒嬪娍" : "鐢ㄧ數閲忓姣�"
+);
+
+const summaryLabels = computed(() => {
+ if (isSingleDay.value) {
+ return {
+ total: "鏃ユ�荤敤鐢甸噺",
+ avg: "灏忔椂骞冲潎鐢ㄧ數閲�",
+ max: "灏忔椂鏈�澶х敤鐢甸噺",
+ min: "灏忔椂鏈�灏忕敤鐢甸噺",
+ };
+ }
+ const unitMap = { day: "鏃�", month: "鏈�", quarter: "瀛e害", year: "骞�" };
+ const unit = unitMap[queryForm.dimension] || "鏈�";
+ return {
+ total: "鎬荤敤鐢甸噺",
+ avg: `骞冲潎${unit}鐢ㄧ數閲廯,
+ max: `鏈�澶�${unit}鐢ㄧ數閲廯,
+ min: `鏈�灏�${unit}鐢ㄧ數閲廯,
+ };
+});
+
+const hasPeriodData = computed(() =>
+ detailRecords.value.some((row) =>
+ hasPeriodValue(row, "sharpConsumption")
+ || hasPeriodValue(row, "peakConsumption")
+ || hasPeriodValue(row, "flatConsumption")
+ || hasPeriodValue(row, "valleyConsumption")
+ )
+);
+
+const dayShortcuts = [
+ {
+ text: "鏄ㄦ棩",
+ value: () => {
+ const yesterday = new Date();
+ yesterday.setDate(yesterday.getDate() - 1);
+ return [yesterday, yesterday];
+ },
+ },
+ {
+ text: "杩�7澶�",
+ value: () => {
+ const end = new Date();
+ const start = new Date(end.getTime() - 6 * 86400000);
+ return [start, end];
+ },
+ },
+];
+
+function hasPeriodValue(row, field) {
+ const n = Number(row?.[field]);
+ return Number.isFinite(n) && n !== 0;
+}
function formatKwh(value, digits = 2) {
const n = Number(value);
@@ -243,16 +305,6 @@
};
}
-async function loadYesterday() {
- yesterdayLoading.value = true;
- try {
- const res = await getYesterdaySummary();
- yesterdaySummary.value = res.data || {};
- } finally {
- yesterdayLoading.value = false;
- }
-}
-
async function handleQuery() {
loading.value = true;
try {
@@ -261,10 +313,15 @@
summary.value = res.data || {};
chartRecords.value = res.data?.chartRecords || [];
detailRecords.value = res.data?.records || [];
+ syncChartType();
renderChart();
} finally {
loading.value = false;
}
+}
+
+function syncChartType() {
+ chartType.value = isSingleDay.value ? "line" : "bar";
}
function renderChart() {
@@ -272,52 +329,41 @@
if (!chartInstance) {
chartInstance = echarts.init(chartRef.value);
}
- const labels = chartRecords.value.map((item) =>
- parseTimeKey(item.timeKey, queryForm.dimension)
- );
- const values = chartRecords.value.map((item) =>
- Number(formatKwh(item.totalConsumption))
- );
+ const dim = chartDimension.value;
+ const labels = chartRecords.value.map((item) => parseTimeKey(item.timeKey, dim));
+ const values = chartRecords.value.map((item) => Number(formatKwh(item.totalConsumption)));
+ const type = isSingleDay.value ? "line" : chartType.value;
+
chartInstance.setOption({
tooltip: { trigger: "axis" },
grid: { left: 50, right: 20, top: 30, bottom: 50 },
- xAxis: { type: "category", data: labels, axisLabel: { rotate: 30, fontSize: 11 } },
+ xAxis: {
+ type: "category",
+ data: labels,
+ axisLabel: { rotate: isSingleDay.value ? 45 : 30, fontSize: 11 },
+ },
yAxis: { type: "value", name: "kWh" },
series: [
{
name: "鎬荤敤鐢甸噺",
- type: chartType.value,
+ type,
data: values,
- smooth: chartType.value === "line",
- areaStyle: chartType.value === "line" ? { opacity: 0.12 } : undefined,
+ smooth: type === "line",
+ areaStyle: type === "line" ? { opacity: 0.12 } : undefined,
itemStyle: { color: "#409EFF" },
barMaxWidth: 40,
},
],
- });
-}
-
-function setYesterday() {
- queryForm.dimension = "day";
- const yesterday = getYesterdayDayPicker();
- dayRange.value = [yesterday, yesterday];
- handleQuery();
-}
-
-function setLast7Days() {
- queryForm.dimension = "day";
- const now = new Date();
- const weekAgo = new Date(now.getTime() - 6 * 86400000);
- dayRange.value = [formatDayPicker(weekAgo), formatDayPicker(now)];
- handleQuery();
-}
-
-function viewYesterdayDetail() {
- setYesterday();
+ }, true);
}
function handleDimensionChange() {
handleQuery();
+}
+
+function showDetail(row) {
+ detailRow.value = row;
+ detailVisible.value = true;
}
function handleExport() {
@@ -335,9 +381,13 @@
chartInstance?.resize();
}
+watch(isSingleDay, () => {
+ syncChartType();
+ renderChart();
+});
+
onMounted(() => {
initDefaultRange();
- loadYesterday();
handleQuery();
window.addEventListener("resize", handleResize);
});
@@ -350,47 +400,6 @@
</script>
<style scoped>
-.yesterday-card {
- margin-bottom: 16px;
-}
-.yesterday-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 16px;
-}
-.yesterday-header h3 {
- margin: 0 0 4px;
- font-size: 18px;
-}
-.yesterday-header .sub {
- margin: 0;
- color: #909399;
- font-size: 13px;
-}
-.metric-box {
- background: #f5f7fa;
- border-radius: 8px;
- padding: 16px;
- text-align: center;
-}
-.metric-box.highlight {
- background: linear-gradient(135deg, #409eff22, #409eff11);
-}
-.metric-label {
- font-size: 13px;
- color: #909399;
- margin-bottom: 8px;
-}
-.metric-value {
- font-size: 24px;
- font-weight: 600;
-}
-.metric-value span {
- font-size: 13px;
- font-weight: 400;
- color: #909399;
-}
.card-header {
display: flex;
align-items: center;
@@ -402,6 +411,14 @@
}
.search-form {
margin-bottom: 16px;
+}
+.time-range-item {
+ margin-right: 0;
+}
+.time-range-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
}
.summary-row {
margin-bottom: 16px;
@@ -445,4 +462,16 @@
font-weight: 500;
margin-bottom: 10px;
}
+.meter-cell {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+.meter-name {
+ font-size: 13px;
+}
+.meter-id {
+ font-size: 12px;
+ color: #909399;
+}
</style>
--
Gitblit v1.9.3