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