yuan
3 天以前 7273f7e6e1742c8604bff0bed3b414389aa50b21
feat: 重构用电数据展示,优化界面和交互体验
已修改1个文件
313 ■■■■ 文件已修改
src/views/energyManagement/energyStatistics/index.vue 313 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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,13 +17,15 @@
            <el-radio-button value="year">年</el-radio-button>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="时间范围">
        <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'"
@@ -83,10 +48,9 @@
            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: "季度", 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>