zhangwencui
2026-06-16 19f0e68b3fe3cf5244a2b936bfef4e0712bdf025
src/views/energyManagement/energyStatistics/index.vue
@@ -1,66 +1,31 @@
<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">
          <span>能耗统计分析</span>
          <span class="desc">按天、月、季度、年汇总统计(由小时数据累积计算)</span>
          <span class="desc">周期累计、时段拆分、趋势对比与负荷分析</span>
        </div>
      </template>
      <el-form :inline="true" class="search-form">
        <el-form-item label="统计维度">
          <el-radio-group v-model="queryForm.dimension" @change="handleDimensionChange">
            <el-radio-button value="day">天</el-radio-button>
            <el-radio-button value="day">日</el-radio-button>
            <el-radio-button value="week">周</el-radio-button>
            <el-radio-button value="month">月</el-radio-button>
            <el-radio-button value="quarter">季度</el-radio-button>
            <el-radio-button value="quarter">季</el-radio-button>
            <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">
          <el-date-picker
            v-if="queryForm.dimension === 'day'"
            v-if="queryForm.dimension === 'day' || queryForm.dimension === 'week'"
            v-model="dayRange"
            type="daterange"
            range-separator="至"
            value-format="YYYY-MM-DD"
            :shortcuts="dayShortcuts"
          />
          <el-date-picker
            v-else-if="queryForm.dimension === 'month'"
@@ -84,49 +49,154 @@
            value-format="YYYY"
          />
        </el-form-item>
        <el-form-item label="趋势粒度">
          <el-radio-group v-model="queryForm.trendGranularity" size="small" @change="handleQuery">
            <el-radio-button value="hour">小时</el-radio-button>
            <el-radio-button value="day">日</el-radio-button>
            <el-radio-button value="week">周</el-radio-button>
            <el-radio-button value="month">月</el-radio-button>
          </el-radio-group>
        </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>
      </el-form>
      <el-row :gutter="16" class="summary-row">
        <el-col :span="6">
      <!-- 一、基础用量统计 -->
      <div class="section-title">基础用量统计</div>
      <el-row :gutter="12" class="summary-row">
        <el-col :span="4">
          <div class="summary-card total">
            <div class="label">总用电量</div>
            <div class="value">{{ formatKwh(summary.totalConsumption) }} <span>kWh</span></div>
            <div class="label">周期累计电量</div>
            <div class="value">{{ formatKwh(analytics.totalConsumption) }} <span>kWh</span></div>
          </div>
        </el-col>
        <el-col :span="6">
        <el-col :span="5">
          <div class="summary-card">
            <div class="label">平均用电量</div>
            <div class="value">{{ formatKwh(summary.avgConsumption) }} <span>kWh</span></div>
            <div class="label">小时平均用电量</div>
            <div class="value">{{ formatKwh(analytics.avgConsumption) }} <span>kWh</span></div>
          </div>
        </el-col>
        <el-col :span="6">
        <el-col :span="5">
          <div class="summary-card">
            <div class="label">最大用电量</div>
            <div class="value">{{ formatKwh(summary.maxConsumption) }} <span>kWh</span></div>
            <div class="label">小时最大用电量</div>
            <div class="value">{{ formatKwh(analytics.maxConsumption) }} <span>kWh</span></div>
          </div>
        </el-col>
        <el-col :span="6">
        <el-col :span="5">
          <div class="summary-card">
            <div class="label">最小用电量</div>
            <div class="value">{{ formatKwh(summary.minConsumption) }} <span>kWh</span></div>
            <div class="label">小时最小用电量</div>
            <div class="value">{{ formatKwh(analytics.minConsumption) }} <span>kWh</span></div>
          </div>
        </el-col>
        <el-col :span="5">
          <div class="summary-card load">
            <div class="label">负荷率</div>
            <div class="value">{{ formatKwh(analytics.loadRate, 1) }} <span>%</span></div>
            <div class="hint">平均÷最大×100</div>
          </div>
        </el-col>
      </el-row>
      <div class="chart-toolbar">
        <span>趋势图</span>
        <el-radio-group 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>
      </div>
      <div ref="chartRef" class="chart-container"></div>
      <!-- 二、同比环比 -->
      <div class="section-title">同比 / 环比分析</div>
      <el-row :gutter="16" class="compare-row">
        <el-col :span="12">
          <div class="compare-card">
            <div class="compare-label">{{ analytics.chainComparison?.label || "环比上期" }}</div>
            <div class="compare-body">
              <div>
                <span class="sub">上期</span>
                <strong>{{ formatKwh(analytics.chainComparison?.compareTotal) }} kWh</strong>
              </div>
              <div>
                <span class="sub">本期</span>
                <strong>{{ formatKwh(analytics.chainComparison?.currentTotal) }} kWh</strong>
              </div>
              <div :class="deltaClass(analytics.chainComparison?.delta)">
                {{ formatDelta(analytics.chainComparison) }}
              </div>
            </div>
          </div>
        </el-col>
        <el-col :span="12">
          <div class="compare-card">
            <div class="compare-label">{{ analytics.yoyComparison?.label || "同比去年同期" }}</div>
            <div class="compare-body">
              <div>
                <span class="sub">去年同期</span>
                <strong>{{ formatKwh(analytics.yoyComparison?.compareTotal) }} kWh</strong>
              </div>
              <div>
                <span class="sub">本期</span>
                <strong>{{ formatKwh(analytics.yoyComparison?.currentTotal) }} kWh</strong>
              </div>
              <div :class="deltaClass(analytics.yoyComparison?.delta)">
                {{ formatDelta(analytics.yoyComparison) }}
              </div>
            </div>
          </div>
        </el-col>
      </el-row>
      <!-- 三、趋势与拆分 -->
      <div class="section-title">趋势与时段分析</div>
      <el-row :gutter="16">
        <el-col :span="14">
          <div class="chart-panel">
            <div class="chart-toolbar">
              <span>用电趋势({{ trendGranularityLabel }})</span>
              <el-radio-group
                v-if="!isSingleDay"
                v-model="chartType"
                size="small"
                @change="renderAllCharts"
              >
                <el-radio-button value="line">折线</el-radio-button>
                <el-radio-button value="bar">柱状</el-radio-button>
              </el-radio-group>
            </div>
            <div ref="trendChartRef" class="chart-container" />
          </div>
        </el-col>
        <el-col :span="10">
          <div class="chart-panel">
            <div class="chart-toolbar"><span>时段拆分(峰平谷)</span></div>
            <div ref="periodChartRef" class="chart-container short" />
          </div>
        </el-col>
      </el-row>
      <el-row :gutter="16" class="sub-chart-row">
        <el-col :span="8">
          <div class="chart-panel">
            <div class="chart-toolbar"><span>班次用电对比</span></div>
            <div ref="shiftChartRef" class="chart-container short" />
          </div>
        </el-col>
        <el-col :span="8">
          <div class="chart-panel">
            <div class="chart-toolbar"><span>工作日 / 休息日</span></div>
            <div ref="dayTypeChartRef" class="chart-container short" />
          </div>
        </el-col>
        <el-col :span="8">
          <div class="split-table-panel">
            <div class="chart-toolbar"><span>拆分占比明细</span></div>
            <el-table :data="splitTableRows" size="small" border max-height="280">
              <el-table-column prop="category" label="类别" width="80" />
              <el-table-column prop="name" label="项" min-width="80" />
              <el-table-column label="电量(kWh)" width="100">
                <template #default="{ row }">{{ formatKwh(row.consumption) }}</template>
              </el-table-column>
              <el-table-column label="占比" width="70">
                <template #default="{ row }">{{ formatKwh(row.ratio, 1) }}%</template>
              </el-table-column>
            </el-table>
          </div>
        </el-col>
      </el-row>
      <div class="detail-title">用电明细</div>
      <el-table v-loading="loading" :data="detailRecords" border stripe max-height="360">
@@ -135,41 +205,57 @@
            {{ 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">
        <el-table-column label="总电量(kWh)" width="120">
          <template #default="{ row }">{{ formatKwh(row.totalConsumption) }}</template>
        </el-table-column>
        <el-table-column 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" label="峰(kWh)" width="90">
          <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" label="平(kWh)" width="90">
          <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" label="谷(kWh)" width="90">
          <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 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, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import { ElMessageBox } from "element-plus";
import * as echarts from "echarts";
import {
  summaryStatisticEle,
  getYesterdaySummary,
  analyticsStatisticEle,
  formatDayPicker,
  formatDayTime,
  formatMonthTime,
  getYesterdayDayPicker,
  parseTimeKey,
@@ -178,30 +264,104 @@
const { proxy } = getCurrentInstance();
const loading = ref(false);
const yesterdayLoading = ref(false);
const chartRef = ref(null);
let chartInstance = null;
const trendChartRef = ref(null);
const periodChartRef = ref(null);
const shiftChartRef = ref(null);
const dayTypeChartRef = ref(null);
const chartInstances = {};
const queryForm = reactive({ dimension: "day" });
const chartType = ref("bar");
const summary = ref({});
const chartRecords = ref([]);
const queryForm = reactive({ dimension: "day", trendGranularity: "hour" });
const chartType = ref("line");
const analytics = 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 (!["day", "week"].includes(queryForm.dimension) || !dayRange.value?.length) return false;
  return dayRange.value[0] === dayRange.value[1];
});
const trendGranularityLabel = computed(() => {
  const map = { hour: "小时", day: "日", week: "周", month: "月", year: "年" };
  return map[analytics.value.trendGranularity || queryForm.trendGranularity] || "日";
});
const hasPeriodData = computed(() =>
  (analytics.value.periodSplits || []).length > 0
  || detailRecords.value.some((row) =>
    hasPeriodValue(row, "peakConsumption")
    || hasPeriodValue(row, "flatConsumption")
    || hasPeriodValue(row, "valleyConsumption")
  )
);
const splitTableRows = computed(() => {
  const rows = [];
  const push = (category, list) => {
    (list || []).forEach((item) => rows.push({ category, ...item }));
  };
  push("峰平谷", analytics.value.periodSplits);
  push("班次", analytics.value.shiftSplits);
  push("日类型", analytics.value.dayTypeSplits);
  return rows;
});
const dayShortcuts = [
  {
    text: "昨日",
    value: () => {
      const d = new Date();
      d.setDate(d.getDate() - 1);
      return [d, d];
    },
  },
  {
    text: "近7天",
    value: () => {
      const end = new Date();
      const start = new Date(end.getTime() - 6 * 86400000);
      return [start, end];
    },
  },
  {
    text: "近30天",
    value: () => {
      const end = new Date();
      const start = new Date(end.getTime() - 29 * 86400000);
      return [start, end];
    },
  },
];
const PIE_COLORS = ["#409EFF", "#67C23A", "#E6A23C", "#F56C6C", "#909399"];
function hasPeriodValue(row, field) {
  const n = Number(row?.[field]);
  return Number.isFinite(n) && n !== 0;
}
function formatKwh(value, digits = 2) {
  const n = Number(value);
  if (!Number.isFinite(n)) {
    return (0).toFixed(digits);
  }
  if (!Number.isFinite(n)) return (0).toFixed(digits);
  return n.toFixed(digits);
}
function formatDelta(comp) {
  if (!comp) return "-";
  const sign = comp.delta >= 0 ? "+" : "";
  return `${sign}${formatKwh(comp.delta)} kWh (${sign}${formatKwh(comp.changeRate, 1)}%)`;
}
function deltaClass(delta) {
  if (delta > 0) return "delta up";
  if (delta < 0) return "delta down";
  return "delta";
}
function initDefaultRange() {
@@ -219,7 +379,7 @@
function buildTimeParams() {
  const dim = queryForm.dimension;
  if (dim === "day") {
  if (dim === "day" || dim === "week") {
    return {
      startTime: dayRange.value[0].replace(/-/g, ""),
      endTime: dayRange.value[1].replace(/-/g, ""),
@@ -237,87 +397,131 @@
      endTime: quarterRange.value[1].replace(/-/g, ""),
    };
  }
  return {
    startTime: yearRange.value[0],
    endTime: yearRange.value[1],
  };
  return { startTime: yearRange.value[0], endTime: yearRange.value[1] };
}
async function loadYesterday() {
  yesterdayLoading.value = true;
  try {
    const res = await getYesterdaySummary();
    yesterdaySummary.value = res.data || {};
  } finally {
    yesterdayLoading.value = false;
function syncTrendGranularity() {
  if (isSingleDay.value) {
    queryForm.trendGranularity = "hour";
    chartType.value = "line";
    return;
  }
  if (queryForm.trendGranularity === "hour") {
    queryForm.trendGranularity = "day";
  }
  chartType.value = "bar";
}
async function handleQuery() {
  syncTrendGranularity();
  loading.value = true;
  try {
    const params = { dimension: queryForm.dimension, ...buildTimeParams() };
    const res = await summaryStatisticEle(params);
    summary.value = res.data || {};
    chartRecords.value = res.data?.chartRecords || [];
    const params = {
      dimension: queryForm.dimension,
      trendGranularity: queryForm.trendGranularity,
      ...buildTimeParams(),
    };
    const res = await analyticsStatisticEle(params);
    analytics.value = res.data || {};
    detailRecords.value = res.data?.records || [];
    renderChart();
    await nextTick();
    renderAllCharts();
  } finally {
    loading.value = false;
  }
}
function renderChart() {
  if (!chartRef.value) return;
  if (!chartInstance) {
    chartInstance = echarts.init(chartRef.value);
function getChart(key, refEl) {
  if (!refEl) return null;
  if (!chartInstances[key]) {
    chartInstances[key] = echarts.init(refEl);
  }
  const labels = chartRecords.value.map((item) =>
    parseTimeKey(item.timeKey, queryForm.dimension)
  );
  const values = chartRecords.value.map((item) =>
    Number(formatKwh(item.totalConsumption))
  );
  chartInstance.setOption({
  return chartInstances[key];
}
function renderTrendChart() {
  const chart = getChart("trend", trendChartRef.value);
  if (!chart) return;
  const gran = analytics.value.trendGranularity || queryForm.trendGranularity;
  const records = analytics.value.trendRecords || analytics.value.chartRecords || [];
  const labels = records.map((item) => parseTimeKey(item.timeKey, gran));
  const values = records.map((item) => Number(formatKwh(item.totalConsumption)));
  const type = isSingleDay.value ? "line" : chartType.value;
  chart.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: gran === "hour" ? 45 : 30, fontSize: 11 } },
    yAxis: { type: "value", name: "kWh" },
    series: [
      {
        name: "总用电量",
        type: chartType.value,
        data: values,
        smooth: chartType.value === "line",
        areaStyle: chartType.value === "line" ? { opacity: 0.12 } : undefined,
        itemStyle: { color: "#409EFF" },
        barMaxWidth: 40,
      },
    ],
  });
    series: [{
      name: "用电量",
      type,
      data: values,
      smooth: type === "line",
      areaStyle: type === "line" ? { opacity: 0.1 } : undefined,
      itemStyle: { color: "#409EFF" },
      barMaxWidth: 36,
    }],
  }, true);
}
function setYesterday() {
  queryForm.dimension = "day";
  const yesterday = getYesterdayDayPicker();
  dayRange.value = [yesterday, yesterday];
  handleQuery();
function renderPieChart(key, refEl, items, title) {
  const chart = getChart(key, refEl);
  if (!chart) return;
  const data = (items || []).map((item, i) => ({
    name: item.name,
    value: Number(formatKwh(item.consumption)),
    itemStyle: { color: PIE_COLORS[i % PIE_COLORS.length] },
  }));
  chart.setOption({
    title: data.length ? undefined : { text: "暂无数据", left: "center", top: "center", textStyle: { color: "#909399", fontSize: 13 } },
    tooltip: { trigger: "item", formatter: "{b}: {c} kWh ({d}%)" },
    legend: { bottom: 0, type: "scroll" },
    series: [{
      name: title,
      type: "pie",
      radius: ["40%", "65%"],
      center: ["50%", "45%"],
      data,
      label: { formatter: "{b}\n{d}%" },
    }],
  }, true);
}
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 renderBarChart(key, refEl, items, title) {
  const chart = getChart(key, refEl);
  if (!chart) return;
  const list = items || [];
  chart.setOption({
    title: list.length ? undefined : { text: "暂无数据", left: "center", top: "center", textStyle: { color: "#909399", fontSize: 13 } },
    tooltip: { trigger: "axis" },
    grid: { left: 50, right: 16, top: 20, bottom: 30 },
    xAxis: { type: "category", data: list.map((i) => i.name) },
    yAxis: { type: "value", name: "kWh" },
    series: [{
      name: title,
      type: "bar",
      data: list.map((i) => Number(formatKwh(i.consumption))),
      itemStyle: { color: "#409EFF" },
      barMaxWidth: 40,
    }],
  }, true);
}
function viewYesterdayDetail() {
  setYesterday();
function renderAllCharts() {
  renderTrendChart();
  renderPieChart("period", periodChartRef.value, analytics.value.periodSplits, "峰平谷");
  renderBarChart("shift", shiftChartRef.value, analytics.value.shiftSplits, "班次");
  renderPieChart("dayType", dayTypeChartRef.value, analytics.value.dayTypeSplits, "日类型");
}
function handleDimensionChange() {
  syncTrendGranularity();
  handleQuery();
}
function showDetail(row) {
  detailRow.value = row;
  detailVisible.value = true;
}
function handleExport() {
@@ -332,117 +536,84 @@
}
function handleResize() {
  chartInstance?.resize();
  Object.values(chartInstances).forEach((c) => c?.resize());
}
watch(isSingleDay, () => {
  syncTrendGranularity();
  renderAllCharts();
});
onMounted(() => {
  initDefaultRange();
  loadYesterday();
  handleQuery();
  window.addEventListener("resize", handleResize);
});
onBeforeUnmount(() => {
  window.removeEventListener("resize", handleResize);
  chartInstance?.dispose();
  chartInstance = null;
  Object.values(chartInstances).forEach((c) => c?.dispose());
});
</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;
.card-header { display: flex; align-items: center; gap: 12px; }
.card-header .desc { font-size: 13px; color: #909399; }
.search-form { margin-bottom: 12px; }
.time-range-item { margin-right: 0; }
.section-title {
  font-weight: 600;
  font-size: 14px;
  margin: 16px 0 10px;
  padding-left: 8px;
  border-left: 3px solid #409eff;
}
.metric-value span {
  font-size: 13px;
  font-weight: 400;
  color: #909399;
}
.card-header {
  display: flex;
  align-items: center;
  gap: 12px;
}
.card-header .desc {
  font-size: 13px;
  color: #909399;
}
.search-form {
  margin-bottom: 16px;
}
.summary-row {
  margin-bottom: 16px;
}
.summary-row { margin-bottom: 8px; }
.summary-card {
  background: #f5f7fa;
  border-radius: 8px;
  padding: 20px;
  padding: 16px 12px;
  text-align: center;
  min-height: 96px;
}
.summary-card.total {
  background: linear-gradient(135deg, #409eff22, #409eff11);
.summary-card.total { background: linear-gradient(135deg, #409eff22, #409eff11); }
.summary-card.load { background: linear-gradient(135deg, #67c23a22, #67c23a11); }
.summary-card .label { font-size: 12px; color: #909399; margin-bottom: 6px; }
.summary-card .value { font-size: 22px; font-weight: 600; }
.summary-card .value span { font-size: 12px; font-weight: 400; color: #909399; }
.summary-card .hint { font-size: 11px; color: #c0c4cc; margin-top: 4px; }
.compare-row { margin-bottom: 8px; }
.compare-card {
  background: #fafafa;
  border: 1px solid #ebeef5;
  border-radius: 8px;
  padding: 14px 16px;
}
.summary-card .label {
  font-size: 13px;
  color: #909399;
  margin-bottom: 8px;
.compare-label { font-weight: 500; margin-bottom: 10px; }
.compare-body { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.compare-body .sub { display: block; font-size: 12px; color: #909399; margin-bottom: 2px; }
.delta { font-weight: 600; font-size: 13px; }
.delta.up { color: #f56c6c; }
.delta.down { color: #67c23a; }
.chart-panel, .split-table-panel {
  border: 1px solid #ebeef5;
  border-radius: 8px;
  padding: 12px;
  margin-bottom: 12px;
}
.summary-card .value {
  font-size: 26px;
  font-weight: 600;
}
.summary-card .value span {
  font-size: 13px;
  font-weight: 400;
  color: #909399;
}
.sub-chart-row { margin-top: 0; }
.chart-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
  font-weight: 500;
  font-size: 13px;
}
.chart-container {
  width: 100%;
  height: 380px;
  margin-bottom: 20px;
}
.detail-title {
  font-weight: 500;
  margin-bottom: 10px;
}
.chart-container { width: 100%; height: 320px; }
.chart-container.short { height: 280px; }
.detail-title { font-weight: 500; margin: 8px 0 10px; }
.meter-cell { display: flex; flex-direction: column; gap: 2px; }
.meter-name { font-size: 13px; }
.meter-id { font-size: 12px; color: #909399; }
</style>