zhangwencui
5 小时以前 e95d6f2a9141c05903098065b4356d1158c0c4e2
src/views/costAccounting/energyCosts/index.vue
@@ -18,16 +18,16 @@
                            @change="handleTypeChange">
              <el-radio-button label="day">按日</el-radio-button>
              <el-radio-button label="month">按月</el-radio-button>
              <el-radio-button label="year">按年</el-radio-button>
            </el-radio-group>
          </div>
        </div>
      </template>
      <div class="filter-layout">
        <el-form :model="searchForm"
                 :inline="true"
                 class="filter-form">
          <el-form-item label="能耗类型">
          <!-- <el-form-item label="能耗类型">
            <el-select v-model="searchForm.energyType"
                       placeholder="全部"
                       clearable
@@ -42,21 +42,19 @@
              <el-option label="气"
                         value="气" />
            </el-select>
          </el-form-item>
          <el-form-item label="能耗用途">
            <el-select v-model="searchForm.energyPurpose"
                       placeholder="全部"
                       clearable
                       class="w-140"
                       @change="handleQuery">
              <el-option label="全部"
                         value="全部" />
              <el-option label="生产"
                         value="生产" />
              <el-option label="办公"
                         value="办公" />
          </el-form-item> -->
          <!-- <el-form-item label="能耗用途">
            <el-select
              v-model="searchForm.type"
              placeholder=""
              clearable
              class="w-140"
              @change="handleQuery"
            >
              <el-option label="生产" value="生产" />
              <el-option label="办公" value="办公" />
            </el-select>
          </el-form-item>
          </el-form-item> -->
          <el-form-item label="时间范围">
            <el-date-picker v-if="statisticsType === 'day'"
                            v-model="searchForm.dateRange"
@@ -67,7 +65,7 @@
                            value-format="YYYY-MM-DD"
                            class="w-260"
                            @change="handleQuery" />
            <el-date-picker v-else
            <el-date-picker v-else-if="statisticsType === 'month'"
                            v-model="searchForm.monthRange"
                            type="monthrange"
                            range-separator="至"
@@ -76,6 +74,16 @@
                            value-format="YYYY-MM"
                            class="w-260"
                            @change="handleQuery" />
            <el-select v-else-if="statisticsType === 'year'"
                       v-model="searchForm.selectedYear"
                       placeholder="请选择年份"
                       class="w-260"
                       @change="handleYearChange">
              <el-option v-for="year in recentYears"
                         :key="year"
                         :label="year + '年'"
                         :value="year" />
            </el-select>
          </el-form-item>
        </el-form>
        <div class="filter-actions">
@@ -92,7 +100,6 @@
        </div>
      </div>
    </el-card>
    <!-- 图表区域 -->
    <div class="charts">
      <el-card class="panel-card"
@@ -106,11 +113,14 @@
                  @click="handleKpiClick('all')">
            <div class="kpi-left">
              <div class="kpi-label">总能耗成本</div>
              <div class="kpi-value">¥{{ formatMoney(animatedOverview.totalCost) }}</div>
              <div class="kpi-value">
                ¥{{ formatMoney(animatedOverview.totalEnergyCost) }}
              </div>
              <div class="kpi-meta">
                <span class="kpi-chip"
                      :class="kpiDelta.total.pct >= 0 ? 'up' : 'down'"
                      v-if="kpiDelta.total.valid">{{ kpiDelta.total.pct >= 0 ? '+' : '' }}{{ kpiDelta.total.pct.toFixed(1) }}%</span>
                      v-if="kpiDelta.total.valid">{{ kpiDelta.total.pct >= 0 ? "+" : ""
                  }}{{ kpiDelta.total.pct.toFixed(1) }}%</span>
                <svg class="kpi-spark"
                     viewBox="0 0 72 22"
                     aria-hidden="true">
@@ -132,10 +142,14 @@
                 @click.stop>
              <button class="kpi-action"
                      type="button"
                      @click="copyKpi('totalCost')">复制</button>
                      @click="copyKpi('totalEnergyCost')">
                复制
              </button>
              <button class="kpi-action"
                      type="button"
                      @click="viewKpiDetails('all')">明细</button>
                      @click="viewKpiDetails('all')">
                明细
              </button>
            </div>
          </button>
          <button class="kpi-item kpi-production"
@@ -144,11 +158,14 @@
                  @click="handleKpiClick('production')">
            <div class="kpi-left">
              <div class="kpi-label">生产能耗成本</div>
              <div class="kpi-value">¥{{ formatMoney(animatedOverview.productionCost) }}</div>
              <div class="kpi-value">
                ¥{{ formatMoney(animatedOverview.productEnergyCost) }}
              </div>
              <div class="kpi-meta">
                <span class="kpi-chip"
                      :class="kpiDelta.production.pct >= 0 ? 'up' : 'down'"
                      v-if="kpiDelta.production.valid">{{ kpiDelta.production.pct >= 0 ? '+' : '' }}{{ kpiDelta.production.pct.toFixed(1) }}%</span>
                      v-if="kpiDelta.production.valid">{{ kpiDelta.production.pct >= 0 ? "+" : ""
                  }}{{ kpiDelta.production.pct.toFixed(1) }}%</span>
                <svg class="kpi-spark"
                     viewBox="0 0 72 22"
                     aria-hidden="true">
@@ -170,10 +187,14 @@
                 @click.stop>
              <button class="kpi-action"
                      type="button"
                      @click="copyKpi('productionCost')">复制</button>
                      @click="copyKpi('productEnergyCost')">
                复制
              </button>
              <button class="kpi-action"
                      type="button"
                      @click="viewKpiDetails('production')">明细</button>
                      @click="viewKpiDetails('production')">
                明细
              </button>
            </div>
          </button>
          <button class="kpi-item kpi-office"
@@ -182,11 +203,14 @@
                  @click="handleKpiClick('office')">
            <div class="kpi-left">
              <div class="kpi-label">办公能耗成本</div>
              <div class="kpi-value">¥{{ formatMoney(animatedOverview.officeCost) }}</div>
              <div class="kpi-value">
                ¥{{ formatMoney(animatedOverview.officeEnergyCost) }}
              </div>
              <div class="kpi-meta">
                <span class="kpi-chip"
                      :class="kpiDelta.office.pct >= 0 ? 'up' : 'down'"
                      v-if="kpiDelta.office.valid">{{ kpiDelta.office.pct >= 0 ? '+' : '' }}{{ kpiDelta.office.pct.toFixed(1) }}%</span>
                      v-if="kpiDelta.office.valid">{{ kpiDelta.office.pct >= 0 ? "+" : ""
                  }}{{ kpiDelta.office.pct.toFixed(1) }}%</span>
                <svg class="kpi-spark"
                     viewBox="0 0 72 22"
                     aria-hidden="true">
@@ -208,10 +232,14 @@
                 @click.stop>
              <button class="kpi-action"
                      type="button"
                      @click="copyKpi('officeCost')">复制</button>
                      @click="copyKpi('officeEnergyCost')">
                复制
              </button>
              <button class="kpi-action"
                      type="button"
                      @click="viewKpiDetails('office')">明细</button>
                      @click="viewKpiDetails('office')">
                明细
              </button>
            </div>
          </button>
          <button class="kpi-item kpi-avg"
@@ -219,7 +247,10 @@
                  @click="handleKpiClick('all')">
            <div class="kpi-left">
              <div class="kpi-label">平均成本</div>
              <div class="kpi-value">¥{{ formatMoney(animatedOverview.avgCost) }} <span class="kpi-unit">/{{ statisticsType === 'day' ? '日' : '月' }}</span></div>
              <div class="kpi-value">
                ¥{{ formatMoney(animatedOverview.averageEnergyCost) }}
                <span class="kpi-unit">/{{ statisticsType === "day" ? "日" : statisticsType === "month" ? "月" : "年" }}</span>
              </div>
              <div class="kpi-meta muted">基于当前筛选与明细统计</div>
            </div>
            <div class="kpi-icon">
@@ -231,14 +262,17 @@
                 @click.stop>
              <button class="kpi-action"
                      type="button"
                      @click="copyKpi('avgCost')">复制</button>
                      @click="copyKpi('averageEnergyCost')">
                复制
              </button>
              <button class="kpi-action"
                      type="button"
                      @click="viewKpiDetails('all')">明细</button>
                      @click="viewKpiDetails('all')">
                明细
              </button>
            </div>
          </button>
        </div>
        <div class="panel-head">
          <div class="segmented"
               role="tablist"
@@ -267,7 +301,6 @@
            </button>
          </div>
        </div>
        <transition name="lux-collapse">
          <div v-show="chartPanel === 'core'"
               class="panel-body">
@@ -283,10 +316,14 @@
                           @click.stop>
                        <button class="chart-tool"
                                type="button"
                                @click="downloadChart('cost', '能耗成本趋势')">下载</button>
                                @click="downloadChart('cost', '能耗成本趋势')">
                          下载
                        </button>
                        <button class="chart-tool"
                                type="button"
                                @click="openBigChart('cost', '能耗成本趋势')">大图</button>
                                @click="openBigChart('cost', '能耗成本趋势')">
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
@@ -314,10 +351,14 @@
                           @click.stop>
                        <button class="chart-tool"
                                type="button"
                                @click="downloadChart('type', '能耗类型成本占比')">下载</button>
                                @click="downloadChart('type', '能耗类型成本占比')">
                          下载
                        </button>
                        <button class="chart-tool"
                                type="button"
                                @click="openBigChart('type', '能耗类型成本占比')">大图</button>
                                @click="openBigChart('type', '能耗类型成本占比')">
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
@@ -337,7 +378,6 @@
            </el-row>
          </div>
        </transition>
        <transition name="lux-collapse">
          <div v-show="chartPanel === 'advanced'"
               class="panel-body">
@@ -354,10 +394,14 @@
                           @click.stop>
                        <button class="chart-tool"
                                type="button"
                                @click="downloadChart('purpose', '能耗用途成本占比')">下载</button>
                                @click="downloadChart('purpose', '能耗用途成本占比')">
                          下载
                        </button>
                        <button class="chart-tool"
                                type="button"
                                @click="openBigChart('purpose', '能耗用途成本占比')">大图</button>
                                @click="openBigChart('purpose', '能耗用途成本占比')">
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
@@ -380,22 +424,26 @@
                         shadow="never">
                  <template #header>
                    <div class="chart-head">
                      <span class="chart-title">能耗单价对比</span>
                      <span class="chart-title">能耗用量对比</span>
                      <div class="chart-tools"
                           @click.stop>
                        <button class="chart-tool"
                                type="button"
                                @click="downloadChart('price', '能耗单价对比')">下载</button>
                                @click="downloadChart('unit', '能耗用量对比')">
                          下载
                        </button>
                        <button class="chart-tool"
                                type="button"
                                @click="openBigChart('price', '能耗单价对比')">大图</button>
                                @click="openBigChart('unit', '能耗用量对比')">
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
                  <div ref="priceChartWrap"
                  <div ref="unitChartWrap"
                       class="chart-wrap"
                       v-loading="tableLoading">
                    <div ref="priceChart"
                    <div ref="unitChart"
                         class="chart-content"
                         v-show="hasTableData"></div>
                    <div class="chart-empty"
@@ -410,7 +458,6 @@
        </transition>
      </el-card>
    </div>
    <el-dialog v-model="bigChartVisible"
               :title="bigChartTitle"
               width="92%"
@@ -431,7 +478,6 @@
        </div>
      </template>
    </el-dialog>
    <!-- 数据表格 -->
    <el-card class="table-card"
             shadow="never">
@@ -449,7 +495,6 @@
          </div>
        </div>
      </template>
      <el-table :data="displayTableData"
                v-loading="tableLoading"
                stripe
@@ -463,11 +508,11 @@
                         label="序号"
                         width="60"
                         align="center" />
        <el-table-column prop="timePeriod"
        <el-table-column prop="meterReadingDate"
                         :label="timeColumnLabel"
                         align="center"
                         sortable="custom" />
        <el-table-column prop="energyType"
        <el-table-column prop="energyTyep"
                         label="能耗类型"
                         width="100"
                         align="center"
@@ -475,12 +520,12 @@
                         :filter-method="filterEnergyType"
                         filter-placement="bottom-end">
          <template #default="scope">
            <el-tag :type="getEnergyTypeType(scope.row.energyType)">
              {{ scope.row.energyType }}
            <el-tag :type="getEnergyTypeType(scope.row.energyTyep)">
              {{ scope.row.energyTyep }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="energyPurpose"
        <el-table-column prop="type"
                         label="能耗用途"
                         width="100"
                         align="center"
@@ -488,25 +533,29 @@
                         :filter-method="filterEnergyPurpose"
                         filter-placement="bottom-end">
          <template #default="scope">
            <el-tag :type="scope.row.energyPurpose === '生产' ? 'primary' : 'info'">
              {{ scope.row.energyPurpose }}
            <el-tag :type="scope.row.type === '生产' ? 'primary' : 'warning'">
              {{ scope.row.type }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="consumption"
        <el-table-column prop="dosage"
                         label="用量"
                         align="right">
          <template #default="scope">
            <span class="consumption-value">{{ formatNumber(scope.row.consumption, 2) }}</span>
            <span class="consumption-unit">{{ scope.row.unit }}</span>
            <span class="unit-value">{{
              formatNumber(scope.row.dosage, 2)
            }}</span>
            <span class="unit-unit">{{ scope.row.unit }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="price"
        <el-table-column prop="unitPrice"
                         label="单价(元)"
                         align="right"
                         sortable="custom">
          <template #default="scope">
            <span class="price-value">{{ formatNumber(scope.row.price, 2) }}</span>
            <span class="price-value">{{
              formatNumber(scope.row.unitPrice, 2)
            }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="cost"
@@ -519,7 +568,6 @@
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination-container">
        <el-pagination v-model:current-page="page.current"
                       v-model:page-size="page.size"
@@ -534,7 +582,15 @@
</template>
<script setup>
  import { ref, reactive, onMounted, onUnmounted, computed, nextTick, watch } from "vue";
  import {
    ref,
    reactive,
    onMounted,
    onUnmounted,
    computed,
    nextTick,
    watch,
  } from "vue";
  import { ElMessage } from "element-plus";
  import {
    Money,
@@ -546,14 +602,14 @@
  } from "@element-plus/icons-vue";
  import * as echarts from "echarts";
  // import { energyCostStatistics } from "@/api/costAccounting/energyCosts";
  import { energyConsumptionDetailStatistics } from "@/api/energyManagement/energyType";
  import { energyConsumptionDetailAccount } from "@/api/energyManagement/energyType";
  // 统计维度:day-按日,month-按月
  const statisticsType = ref("day");
  // 搜索表单
  const searchForm = reactive({
    energyType: "",
    energyPurpose: "",
    // energyType: "",
    // type: "",
    dateRange: (() => {
      // 默认最近7天
      const end = new Date();
@@ -568,39 +624,59 @@
      start.setMonth(start.getMonth() - 2);
      return [start.toISOString().slice(0, 7), end.toISOString().slice(0, 7)];
    })(),
    selectedYear: new Date().getFullYear(), // 默认今年
  });
  // 最近七年
  const recentYears = computed(() => {
    const currentYear = new Date().getFullYear();
    const years = [];
    for (let i = 6; i >= 0; i--) {
      years.push(currentYear - i);
    }
    return years;
  });
  // 时间列标签
  const timeColumnLabel = computed(() => {
    return statisticsType.value === "day" ? "日期" : "月份";
    if (statisticsType.value === "day") return "日期";
    if (statisticsType.value === "month") return "月份";
    if (statisticsType.value === "year") return "年份";
    return "时间";
  });
  // 统计概览
  const overview = reactive({
    totalCost: "0.00",
    productionCost: "0.00",
    officeCost: "0.00",
    avgCost: "0.00",
    totalEnergyCost: "0.00",
    productEnergyCost: "0.00",
    officeEnergyCost: "0.00",
    averageEnergyCost: "0.00",
  });
  const selectedKpi = ref("all"); // all | production | office
  const animatedOverview = reactive({
    totalCost: 0,
    productionCost: 0,
    officeCost: 0,
    avgCost: 0,
    totalEnergyCost: 0,
    productEnergyCost: 0,
    officeEnergyCost: 0,
    averageEnergyCost: 0,
  });
  const formatMoney = v => {
    const n = Number.parseFloat(v);
    const value = Number.isFinite(n) ? n : 0;
    return value.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    return value.toLocaleString("zh-CN", {
      minimumFractionDigits: 2,
      maximumFractionDigits: 2,
    });
  };
  const formatNumber = (v, digits = 2) => {
    const n = Number.parseFloat(v);
    if (!Number.isFinite(n)) return "--";
    return n.toLocaleString("zh-CN", { minimumFractionDigits: digits, maximumFractionDigits: digits });
    return n.toLocaleString("zh-CN", {
      minimumFractionDigits: digits,
      maximumFractionDigits: digits,
    });
  };
  const animateNumber = (key, toValue, duration = 420) => {
@@ -620,10 +696,16 @@
  watch(
    () => ({ ...overview }),
    val => {
      animateNumber("totalCost", Number.parseFloat(val.totalCost));
      animateNumber("productionCost", Number.parseFloat(val.productionCost));
      animateNumber("officeCost", Number.parseFloat(val.officeCost));
      animateNumber("avgCost", Number.parseFloat(val.avgCost));
      animateNumber("totalEnergyCost", Number.parseFloat(val.totalEnergyCost));
      animateNumber(
        "productEnergyCost",
        Number.parseFloat(val.productEnergyCost)
      );
      animateNumber("officeEnergyCost", Number.parseFloat(val.officeEnergyCost));
      animateNumber(
        "averageEnergyCost",
        Number.parseFloat(val.averageEnergyCost)
      );
    },
    { deep: true, immediate: true }
  );
@@ -631,24 +713,28 @@
  // 表格数据
  const tableData = ref([]);
  const tableLoading = ref(false);
  const hasTableData = computed(() => Array.isArray(tableData.value) && tableData.value.length > 0);
  const hasTableData = computed(
    () => Array.isArray(tableData.value) && tableData.value.length > 0
  );
  const queryPulse = ref(false);
  const kpiSeries = computed(() => {
    const rows = Array.isArray(tableData.value) ? tableData.value : [];
    const byTime = new Map();
    for (const r of rows) {
      const t = r?.timePeriod ?? "";
      const t = r?.meterReadingDate ?? "";
      if (!t) continue;
      if (!byTime.has(t)) byTime.set(t, { total: 0, production: 0, office: 0 });
      const bucket = byTime.get(t);
      const c = Number.parseFloat(r?.cost);
      const cost = Number.isFinite(c) ? c : 0;
      bucket.total += cost;
      if (r?.energyPurpose === "生产") bucket.production += cost;
      if (r?.energyPurpose === "办公") bucket.office += cost;
      if (r?.type === "生产") bucket.production += cost;
      if (r?.type === "办公") bucket.office += cost;
    }
    const times = Array.from(byTime.keys()).sort((a, b) => String(a).localeCompare(String(b)));
    const times = Array.from(byTime.keys()).sort((a, b) =>
      String(a).localeCompare(String(b))
    );
    const total = times.map(t => byTime.get(t).total);
    const production = times.map(t => byTime.get(t).production);
    const office = times.map(t => byTime.get(t).office);
@@ -690,9 +776,9 @@
  const handleKpiClick = key => {
    selectedKpi.value = key;
    if (key === "all") searchForm.energyPurpose = "";
    if (key === "production") searchForm.energyPurpose = "生产";
    if (key === "office") searchForm.energyPurpose = "办公";
    if (key === "all") searchForm.type = "";
    if (key === "production") searchForm.type = "生产";
    if (key === "office") searchForm.type = "办公";
    page.current = 1;
    handleQuery();
  };
@@ -701,16 +787,17 @@
    handleKpiClick(key);
    nextTick(() => {
      const el = tableAnchor.value;
      if (el?.scrollIntoView) el.scrollIntoView({ behavior: "smooth", block: "start" });
      if (el?.scrollIntoView)
        el.scrollIntoView({ behavior: "smooth", block: "start" });
    });
  };
  const copyKpi = async field => {
    const map = {
      totalCost: animatedOverview.totalCost,
      productionCost: animatedOverview.productionCost,
      officeCost: animatedOverview.officeCost,
      avgCost: animatedOverview.avgCost,
      totalEnergyCost: animatedOverview.totalEnergyCost,
      productEnergyCost: animatedOverview.productEnergyCost,
      officeEnergyCost: animatedOverview.officeEnergyCost,
      averageEnergyCost: animatedOverview.averageEnergyCost,
    };
    const raw = map[field];
    const text = `¥${formatMoney(raw)}`;
@@ -736,13 +823,13 @@
    if (key === "cost") return costChartInstance;
    if (key === "type") return typeChartInstance;
    if (key === "purpose") return purposeChartInstance;
    if (key === "price") return priceChartInstance;
    if (key === "unit") return unitChartInstance;
    return null;
  };
  const ensurePanelForChart = key => {
    if (key === "cost" || key === "type") chartPanel.value = "core";
    if (key === "purpose" || key === "price") chartPanel.value = "advanced";
    if (key === "purpose" || key === "unit") chartPanel.value = "advanced";
  };
  const downloadChart = (key, title) => {
@@ -755,12 +842,17 @@
      const a = document.createElement("a");
      a.href = url;
      const typePart = searchForm.energyType ? `_${searchForm.energyType}` : "";
      const purposePart = searchForm.energyPurpose ? `_${searchForm.energyPurpose}` : "";
      const purposePart = searchForm.type ? `_${searchForm.type}` : "";
      let rangePart = "";
      if (statisticsType.value === "day") {
        if (searchForm.dateRange?.length === 2) rangePart = `_${searchForm.dateRange[0]}~${searchForm.dateRange[1]}`;
      } else {
        if (searchForm.monthRange?.length === 2) rangePart = `_${searchForm.monthRange[0]}~${searchForm.monthRange[1]}`;
        if (searchForm.dateRange?.length === 2)
          rangePart = `_${searchForm.dateRange[0]}~${searchForm.dateRange[1]}`;
      } else if (statisticsType.value === "month") {
        if (searchForm.monthRange?.length === 2)
          rangePart = `_${searchForm.monthRange[0]}~${searchForm.monthRange[1]}`;
      } else if (statisticsType.value === "year") {
        if (searchForm.yearRange?.length === 2)
          rangePart = `_${searchForm.yearRange[0]}~${searchForm.yearRange[1]}`;
      }
      a.download = `${title || "chart"}${typePart}${purposePart}${rangePart}.png`;
      a.click();
@@ -826,7 +918,7 @@
    const prop = sortState.prop;
    const direction = sortState.order === "ascending" ? 1 : -1;
    const numFields = new Set(["price", "cost", "consumption"]);
    const numFields = new Set(["price", "cost", "unit"]);
    return data.sort((a, b) => {
      const av = a?.[prop];
@@ -840,7 +932,9 @@
        return (aNum - bNum) * direction;
      }
      return String(av ?? "").localeCompare(String(bv ?? ""), "zh-Hans-CN") * direction;
      return (
        String(av ?? "").localeCompare(String(bv ?? ""), "zh-Hans-CN") * direction
      );
    });
  });
@@ -854,8 +948,8 @@
    { text: "办公", value: "办公" },
  ];
  const filterEnergyType = (value, row) => row.energyType === value;
  const filterEnergyPurpose = (value, row) => row.energyPurpose === value;
  const filterEnergyType = (value, row) => row.energyTyep === value;
  const filterEnergyPurpose = (value, row) => row.type === value;
  // 分页
  const page = reactive({
@@ -868,12 +962,12 @@
  const costChart = ref(null);
  const typeChart = ref(null);
  const purposeChart = ref(null);
  const priceChart = ref(null);
  const unitChart = ref(null);
  const costChartWrap = ref(null);
  const typeChartWrap = ref(null);
  const purposeChartWrap = ref(null);
  const priceChartWrap = ref(null);
  const unitChartWrap = ref(null);
  const tableAnchor = ref(null);
@@ -901,24 +995,32 @@
  let costChartInstance = null;
  let typeChartInstance = null;
  let purposeChartInstance = null;
  let priceChartInstance = null;
  let unitChartInstance = null;
  // 图表区切换:core | advanced | none(点击当前选中可收起)
  const chartPanel = ref("core");
  const ensureChartsReady = panel => {
    if (panel === "core") {
      if (costChart.value && !costChartInstance) costChartInstance = echarts.init(costChart.value);
      if (typeChart.value && !typeChartInstance) typeChartInstance = echarts.init(typeChart.value);
      if (costChartInstance) updateCostChart();
      if (typeChartInstance) updateTypeChart();
      if (costChart.value && !costChartInstance) {
        costChartInstance = echarts.init(costChart.value);
        setTimeout(() => costChartInstance?.resize(), 50);
      }
      if (typeChart.value && !typeChartInstance) {
        typeChartInstance = echarts.init(typeChart.value);
        setTimeout(() => typeChartInstance?.resize(), 50);
      }
      return;
    }
    if (panel === "advanced") {
      if (purposeChart.value && !purposeChartInstance) purposeChartInstance = echarts.init(purposeChart.value);
      if (priceChart.value && !priceChartInstance) priceChartInstance = echarts.init(priceChart.value);
      if (purposeChartInstance) updatePurposeChart();
      if (priceChartInstance) updatePriceChart();
      if (purposeChart.value && !purposeChartInstance) {
        purposeChartInstance = echarts.init(purposeChart.value);
        setTimeout(() => purposeChartInstance?.resize(), 50);
      }
      if (unitChart.value && !unitChartInstance) {
        unitChartInstance = echarts.init(unitChart.value);
        setTimeout(() => unitChartInstance?.resize(), 50);
      }
    }
  };
@@ -942,6 +1044,20 @@
  watch(chartPanel, val => {
    if (val !== "none") resizeChartsAfterExpand();
  });
  // 监听表格数据变化,确保数据加载后图表正确渲染
  watch(
    tableData,
    () => {
      nextTick(() => {
        updateCharts();
        nextTick(() => {
          handleResize();
        });
      });
    },
    { deep: true }
  );
  // 获取能耗类型标签类型
  const getEnergyTypeType = type => {
@@ -972,7 +1088,8 @@
        borderColor: "#2f6fed",
        borderWidth: 1,
        textStyle: { color: "rgba(15, 23, 42, 0.92)" },
        extraCssText: "box-shadow: 0 14px 40px rgba(15,23,42,.14); border-radius: 12px;",
        extraCssText:
          "box-shadow: 0 14px 40px rgba(15,23,42,.14); border-radius: 12px;",
      },
      legend: {
        data: ["生产能耗成本", "办公能耗成本"],
@@ -989,7 +1106,7 @@
      },
      xAxis: {
        type: "category",
        data: data.map(item => item.timePeriod),
        data: data.map(item => item.meterReadingDate),
        axisLabel: {
          rotate: statisticsType.value === "day" ? 45 : 0,
          color: "rgba(15, 23, 42, 0.62)",
@@ -1009,7 +1126,7 @@
        {
          name: "生产能耗成本",
          type: "bar",
          data: data.map(item => (item.energyPurpose === "生产" ? item.cost : 0)),
          data: data.map(item => (item.type === "生产" ? item.cost : 0)),
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#409EFF" },
@@ -1024,7 +1141,7 @@
        {
          name: "办公能耗成本",
          type: "bar",
          data: data.map(item => (item.energyPurpose === "办公" ? item.cost : 0)),
          data: data.map(item => (item.type === "办公" ? item.cost : 0)),
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#67C23A" },
@@ -1051,10 +1168,10 @@
    const typeCosts = {};
    data.forEach(item => {
      if (!typeCosts[item.energyType]) {
        typeCosts[item.energyType] = 0;
      if (!typeCosts[item.energyTyep]) {
        typeCosts[item.energyTyep] = 0;
      }
      typeCosts[item.energyType] += parseFloat(item.cost);
      typeCosts[item.energyTyep] += parseFloat(item.cost);
    });
    const chartData = Object.entries(typeCosts).map(([name, value]) => ({
@@ -1070,7 +1187,8 @@
        borderColor: "#2f6fed",
        borderWidth: 1,
        textStyle: { color: "rgba(15, 23, 42, 0.92)" },
        extraCssText: "box-shadow: 0 14px 40px rgba(15,23,42,.14); border-radius: 12px;",
        extraCssText:
          "box-shadow: 0 14px 40px rgba(15,23,42,.14); border-radius: 12px;",
      },
      legend: {
        orient: "horizontal",
@@ -1126,8 +1244,8 @@
    };
    data.forEach(item => {
      if (purposeCosts.hasOwnProperty(item.energyPurpose)) {
        purposeCosts[item.energyPurpose] += parseFloat(item.cost);
      if (purposeCosts.hasOwnProperty(item.type)) {
        purposeCosts[item.type] += parseFloat(item.cost);
      }
    });
@@ -1144,7 +1262,8 @@
        borderColor: "#2f6fed",
        borderWidth: 1,
        textStyle: { color: "rgba(15, 23, 42, 0.92)" },
        extraCssText: "box-shadow: 0 14px 40px rgba(15,23,42,.14); border-radius: 12px;",
        extraCssText:
          "box-shadow: 0 14px 40px rgba(15,23,42,.14); border-radius: 12px;",
      },
      legend: {
        orient: "horizontal",
@@ -1181,26 +1300,26 @@
    purposeChartInstance.setOption(option);
  };
  // 更新能耗单价对比图
  const updatePriceChart = () => {
  // 更新能耗用量对比图
  const updateConsumptionChart = () => {
    const data = tableData.value;
    const priceData = {};
    const unitData = {};
    data.forEach(item => {
      if (!priceData[item.energyType]) {
        priceData[item.energyType] = {
      if (!unitData[item.energyTyep]) {
        unitData[item.energyTyep] = {
          生产: 0,
          办公: 0,
        };
      }
      if (priceData[item.energyType].hasOwnProperty(item.energyPurpose)) {
        priceData[item.energyType][item.energyPurpose] = parseFloat(item.price);
      if (unitData[item.energyTyep].hasOwnProperty(item.type)) {
        unitData[item.energyTyep][item.type] += parseFloat(item.dosage || 0);
      }
    });
    const energyTypes = Object.keys(priceData);
    const productionPrices = energyTypes.map(type => priceData[type].生产);
    const officePrices = energyTypes.map(type => priceData[type].办公);
    const energyTypes = Object.keys(unitData);
    const productionConsumptions = energyTypes.map(type => unitData[type].生产);
    const officeConsumptions = energyTypes.map(type => unitData[type].办公);
    const option = {
      tooltip: {
@@ -1210,10 +1329,11 @@
        borderColor: "#2f6fed",
        borderWidth: 1,
        textStyle: { color: "rgba(15, 23, 42, 0.92)" },
        extraCssText: "box-shadow: 0 14px 40px rgba(15,23,42,.14); border-radius: 12px;",
        extraCssText:
          "box-shadow: 0 14px 40px rgba(15,23,42,.14); border-radius: 12px;",
      },
      legend: {
        data: ["生产能耗单价", "办公能耗单价"],
        data: ["生产能耗用量", "办公能耗用量"],
        top: 0,
        right: 10,
        textStyle: { color: "rgba(15, 23, 42, 0.62)" },
@@ -1234,7 +1354,7 @@
      },
      yAxis: {
        type: "value",
        name: "单价(元)",
        name: "用量",
        nameTextStyle: { color: "rgba(15, 23, 42, 0.58)" },
        axisLabel: { color: "rgba(15, 23, 42, 0.58)" },
        axisLine: { show: false },
@@ -1242,9 +1362,9 @@
      },
      series: [
        {
          name: "生产能耗单价",
          name: "生产能耗用量",
          type: "bar",
          data: productionPrices,
          data: productionConsumptions,
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#2f6fed" },
@@ -1254,9 +1374,9 @@
          },
        },
        {
          name: "办公能耗单价",
          name: "办公能耗用量",
          type: "bar",
          data: officePrices,
          data: officeConsumptions,
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#16a34a" },
@@ -1267,7 +1387,7 @@
        },
      ],
    };
    priceChartInstance.setOption(option);
    unitChartInstance.setOption(option);
  };
  // 统计维度切换
@@ -1281,7 +1401,7 @@
        start.toISOString().split("T")[0],
        end.toISOString().split("T")[0],
      ];
    } else {
    } else if (statisticsType.value === "month") {
      const end = new Date();
      const start = new Date();
      start.setMonth(start.getMonth() - 2);
@@ -1289,7 +1409,15 @@
        start.toISOString().slice(0, 7),
        end.toISOString().slice(0, 7),
      ];
    } else if (statisticsType.value === "year") {
      searchForm.selectedYear = new Date().getFullYear(); // 默认今年
    }
    page.current = 1;
    handleQuery();
  };
  // 年份选择变化
  const handleYearChange = () => {
    page.current = 1;
    handleQuery();
  };
@@ -1304,12 +1432,17 @@
    // 构造请求参数
    const params = {
      type: statisticsType.value,
      energyType: searchForm.energyType || undefined,
      energyPurpose: searchForm.energyPurpose || undefined,
      // 项目内常用分页参数命名
      days: 0,
      // energyType: searchForm.energyType || undefined,
      // type: searchForm.type || undefined,
      pageNum: page.current,
      pageSize: page.size,
      state:
        statisticsType.value === "day"
          ? "日"
          : statisticsType.value === "month"
          ? "月"
          : "年",
    };
    if (statisticsType.value === "day") {
@@ -1317,37 +1450,80 @@
        params.startDate = searchForm.dateRange[0];
        params.endDate = searchForm.dateRange[1];
      }
    } else {
    } else if (statisticsType.value === "month") {
      if (searchForm.monthRange && searchForm.monthRange.length === 2) {
        params.startDate = searchForm.monthRange[0] + "-01";
        params.endDate = searchForm.monthRange[1] + "-01";
        // 结束时间需要取结束月份的最后一天(例如 2026-03 -> 2026-03-31)
        const [endYearStr, endMonthStr] = String(searchForm.monthRange[1]).split(
          "-"
        );
        const endYear = Number(endYearStr);
        const endMonth = Number(endMonthStr); // 1-12
        if (
          !Number.isNaN(endYear) &&
          !Number.isNaN(endMonth) &&
          endMonth >= 1 &&
          endMonth <= 12
        ) {
          const lastDay = new Date(endYear, endMonth, 0).getDate(); // 下个月第0天 = 本月最后一天
          params.endDate = `${endYearStr}-${endMonthStr}-${String(
            lastDay
          ).padStart(2, "0")}`;
        } else {
          params.endDate = searchForm.monthRange[1] + "-01";
        }
      }
    } else if (statisticsType.value === "year") {
      if (searchForm.selectedYear) {
        const year = searchForm.selectedYear;
        params.startDate = year + "-01-01";
        params.endDate = year + "-12-31";
      }
    }
    // 计算开始到结束的天数(包含起止两天)
    if (params.startDate && params.endDate) {
      const start = new Date(params.startDate);
      const end = new Date(params.endDate);
      if (!Number.isNaN(start.getTime()) && !Number.isNaN(end.getTime())) {
        const diffTime = end.getTime() - start.getTime();
        const diffDays = Math.floor(diffTime / (24 * 60 * 60 * 1000)) + 1;
        params.days = diffDays > 0 ? diffDays : 0;
      }
    }
    // 调用接口获取数据
    energyConsumptionDetailStatistics(params)
    energyConsumptionDetailAccount(params)
      .then(res => {
        if (res.code === 200) {
          tableData.value = res.data.records || [];
          page.total = res.data.total || 0;
          const data = res.data;
          overview.totalEnergyCost = data.totalEnergyCost || "0";
          overview.productEnergyCost = data.productEnergyCost || "0";
          overview.officeEnergyCost = data.officeEnergyCost || "0";
          overview.averageEnergyCost = data.averageEnergyCost || "0";
          // 更新统计概览数据
          if (res.data.overview) {
            overview.totalCost = res.data.overview.totalCost || "0.00";
            overview.productionCost = res.data.overview.productionCost || "0.00";
            overview.officeCost = res.data.overview.officeCost || "0.00";
            overview.avgCost = res.data.overview.avgCost || "0.00";
          }
          // 处理表格数据
          tableData.value = data.energyConsumptionDetailDtoList || [];
          page.total = tableData.value.length || 0;
        } else {
          ElMessage.error(res.message || "获取数据失败");
          tableData.value = [];
          page.total = 0;
          overview.totalEnergyCost = "0.00";
          overview.productEnergyCost = "0.00";
          overview.officeEnergyCost = "0.00";
          overview.averageEnergyCost = "0.00";
        }
      })
      .catch(err => {
        console.error("获取数据异常:", err);
        // 生成假数据
        generateMockData();
        ElMessage.error("获取数据异常");
        tableData.value = [];
        page.total = 0;
        overview.totalEnergyCost = "0.00";
        overview.productEnergyCost = "0.00";
        overview.officeEnergyCost = "0.00";
        overview.averageEnergyCost = "0.00";
      })
      .finally(() => {
        tableLoading.value = false;
@@ -1355,174 +1531,29 @@
      });
  };
  // 生成假数据
  const generateMockData = () => {
    if (statisticsType.value === "day") {
      // 生成最近7天的假数据
      const mockData = [];
      const today = new Date();
      for (let i = 6; i >= 0; i--) {
        const date = new Date(today);
        date.setDate(date.getDate() - i);
        const dateStr = date.toISOString().split("T")[0];
        // 生产能耗数据
        mockData.push({
          timePeriod: dateStr,
          energyType: "电",
          energyPurpose: "生产",
          consumption: (Math.random() * 1000 + 500).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 850 + 425).toFixed(2),
        });
        mockData.push({
          timePeriod: dateStr,
          energyType: "水",
          energyPurpose: "生产",
          consumption: (Math.random() * 500 + 200).toFixed(2),
          unit: "m³",
          price: "3.50",
          cost: (Math.random() * 1750 + 700).toFixed(2),
        });
        mockData.push({
          timePeriod: dateStr,
          energyType: "气",
          energyPurpose: "生产",
          consumption: (Math.random() * 300 + 100).toFixed(2),
          unit: "m³",
          price: "2.80",
          cost: (Math.random() * 840 + 280).toFixed(2),
        });
        // 办公能耗数据
        mockData.push({
          timePeriod: dateStr,
          energyType: "电",
          energyPurpose: "办公",
          consumption: (Math.random() * 200 + 100).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 170 + 85).toFixed(2),
        });
        mockData.push({
          timePeriod: dateStr,
          energyType: "水",
          energyPurpose: "办公",
          consumption: (Math.random() * 50 + 20).toFixed(2),
          unit: "m³",
          price: "3.50",
          cost: (Math.random() * 175 + 70).toFixed(2),
        });
      }
      tableData.value = mockData;
      page.total = mockData.length;
    } else {
      // 生成最近3个月的假数据
      const mockData = [];
      const today = new Date();
      for (let i = 2; i >= 0; i--) {
        const date = new Date(today);
        date.setMonth(date.getMonth() - i);
        const monthStr = date.toISOString().slice(0, 7);
        // 生产能耗数据
        mockData.push({
          timePeriod: monthStr,
          energyType: "电",
          energyPurpose: "生产",
          consumption: (Math.random() * 30000 + 15000).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 25500 + 12750).toFixed(2),
        });
        mockData.push({
          timePeriod: monthStr,
          energyType: "水",
          energyPurpose: "生产",
          consumption: (Math.random() * 15000 + 6000).toFixed(2),
          unit: "m³",
          price: "3.50",
          cost: (Math.random() * 52500 + 21000).toFixed(2),
        });
        mockData.push({
          timePeriod: monthStr,
          energyType: "气",
          energyPurpose: "生产",
          consumption: (Math.random() * 9000 + 3000).toFixed(2),
          unit: "m³",
          price: "2.80",
          cost: (Math.random() * 25200 + 8400).toFixed(2),
        });
        // 办公能耗数据
        mockData.push({
          timePeriod: monthStr,
          energyType: "电",
          energyPurpose: "办公",
          consumption: (Math.random() * 6000 + 3000).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 5100 + 2550).toFixed(2),
        });
        mockData.push({
          timePeriod: monthStr,
          energyType: "水",
          energyPurpose: "办公",
          consumption: (Math.random() * 1500 + 600).toFixed(2),
          unit: "m³",
          price: "3.50",
          cost: (Math.random() * 5250 + 2100).toFixed(2),
        });
      }
      tableData.value = mockData;
      page.total = mockData.length;
    }
    // 更新统计概览数据
    calculateOverview();
  };
  // 计算统计概览数据
  const calculateOverview = () => {
    let totalCost = 0;
    let productionCost = 0;
    let officeCost = 0;
    tableData.value.forEach(item => {
      const cost = parseFloat(item.cost);
      totalCost += cost;
      if (item.energyPurpose === "生产") {
        productionCost += cost;
      } else if (item.energyPurpose === "办公") {
        officeCost += cost;
      }
    });
    overview.totalCost = totalCost.toFixed(2);
    overview.productionCost = productionCost.toFixed(2);
    overview.officeCost = officeCost.toFixed(2);
    overview.avgCost = (totalCost / tableData.value.length).toFixed(2);
  };
  // 更新所有图表
  const updateCharts = () => {
    nextTick(() => {
      // 确保 core 面板的图表始终初始化(因为默认显示的是 core)
      ensureChartsReady("core");
      // 同时也初始化当前可见面板的图表
      if (chartPanel.value === "advanced") {
        ensureChartsReady("advanced");
      }
      // 更新所有已初始化的图表
      if (costChartInstance) updateCostChart();
      if (typeChartInstance) updateTypeChart();
      if (purposeChartInstance) updatePurposeChart();
      if (priceChartInstance) updatePriceChart();
      if (unitChartInstance) updateConsumptionChart();
    });
  };
  // 重置
  const handleReset = () => {
    searchForm.energyType = "";
    searchForm.energyPurpose = "";
    // searchForm.energyType = "";
    searchForm.type = "";
    if (statisticsType.value === "day") {
      const end = new Date();
      const start = new Date();
@@ -1531,7 +1562,7 @@
        start.toISOString().split("T")[0],
        end.toISOString().split("T")[0],
      ];
    } else {
    } else if (statisticsType.value === "month") {
      const end = new Date();
      const start = new Date();
      start.setMonth(start.getMonth() - 2);
@@ -1539,6 +1570,8 @@
        start.toISOString().slice(0, 7),
        end.toISOString().slice(0, 7),
      ];
    } else if (statisticsType.value === "year") {
      searchForm.selectedYear = new Date().getFullYear(); // 默认今年
    }
    page.current = 1;
    handleQuery();
@@ -1567,7 +1600,7 @@
    costChartInstance && costChartInstance.resize();
    typeChartInstance && typeChartInstance.resize();
    purposeChartInstance && purposeChartInstance.resize();
    priceChartInstance && priceChartInstance.resize();
    unitChartInstance && unitChartInstance.resize();
  };
  onMounted(() => {
@@ -1637,9 +1670,16 @@
    --lux-radius-sm: 12px;
    padding: 18px 22px 24px;
    background:
      radial-gradient(1200px 420px at 20% 0%, rgba(47, 111, 237, 0.10), transparent 55%),
      radial-gradient(900px 380px at 90% 10%, rgba(22, 163, 74, 0.06), transparent 55%),
    background: radial-gradient(
        1200px 420px at 20% 0%,
        rgba(47, 111, 237, 0.1),
        transparent 55%
      ),
      radial-gradient(
        900px 380px at 90% 10%,
        rgba(22, 163, 74, 0.06),
        transparent 55%
      ),
      linear-gradient(180deg, var(--lux-bg) 0%, #ffffff 58%);
  }
@@ -1675,7 +1715,7 @@
  }
  .filter-form {
    flex: 1 1 auto;
    flex: 0.1 1 auto;
    min-width: 0;
  }
@@ -1689,7 +1729,7 @@
    &:hover {
      transform: translateY(-1px);
      box-shadow: 0 10px 22px rgba(15, 23, 42, 0.10);
      box-shadow: 0 10px 22px rgba(15, 23, 42, 0.1);
      filter: saturate(1.02);
    }
@@ -1710,7 +1750,7 @@
  /* 查询区控件统一皮肤 */
  :deep(.filter-card .el-form-item__label) {
    color: rgba(15, 23, 42, 0.70);
    color: rgba(15, 23, 42, 0.7);
    font-weight: 650;
  }
@@ -1718,20 +1758,21 @@
  :deep(.filter-card .el-select__wrapper) {
    border-radius: 12px;
    box-shadow: none;
    border: 1px solid rgba(15, 23, 42, 0.10);
    border: 1px solid rgba(15, 23, 42, 0.1);
    background: rgba(255, 255, 255, 0.82);
    transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
    transition: border-color 0.18s ease, box-shadow 0.18s ease,
      transform 0.18s ease;
  }
  :deep(.filter-card .el-input__wrapper:hover),
  :deep(.filter-card .el-select__wrapper:hover) {
    border-color: rgba(47, 111, 237, 0.20);
    border-color: rgba(47, 111, 237, 0.2);
    transform: translateY(-1px);
  }
  :deep(.filter-card .is-focus .el-input__wrapper),
  :deep(.filter-card .is-focus .el-select__wrapper) {
    border-color: rgba(47, 111, 237, 0.30);
    border-color: rgba(47, 111, 237, 0.3);
    box-shadow: 0 0 0 3px rgba(47, 111, 237, 0.14);
  }
@@ -1853,7 +1894,7 @@
    background: rgba(255, 255, 255, 0.9);
    backdrop-filter: blur(10px);
    min-height: 78px;
    transition: box-shadow 0.20s ease, transform 0.20s ease, border-color 0.20s ease;
    transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
    &:hover {
      transform: translateY(-1px);
@@ -1902,23 +1943,43 @@
  }
  .metric-total {
    background: linear-gradient(135deg, rgba(47, 111, 237, 0.12), rgba(47, 111, 237, 0.02));
    background: linear-gradient(
      135deg,
      rgba(47, 111, 237, 0.12),
      rgba(47, 111, 237, 0.02)
    );
    .metric-right {
      background: linear-gradient(135deg, var(--lux-primary), var(--lux-primary-2));
      background: linear-gradient(
        135deg,
        var(--lux-primary),
        var(--lux-primary-2)
      );
    }
  }
  .metric-production {
    background: linear-gradient(135deg, rgba(22, 163, 74, 0.12), rgba(22, 163, 74, 0.02));
    background: linear-gradient(
      135deg,
      rgba(22, 163, 74, 0.12),
      rgba(22, 163, 74, 0.02)
    );
    .metric-right {
      background: linear-gradient(135deg, var(--lux-success), rgba(22, 163, 74, 0.65));
      background: linear-gradient(
        135deg,
        var(--lux-success),
        rgba(22, 163, 74, 0.65)
      );
    }
  }
  .metric-office {
    background: linear-gradient(135deg, rgba(144, 147, 153, 0.14), rgba(144, 147, 153, 0.03));
    background: linear-gradient(
      135deg,
      rgba(144, 147, 153, 0.14),
      rgba(144, 147, 153, 0.03)
    );
    .metric-right {
      background: linear-gradient(135deg, #909399, #b1b3b8);
@@ -1926,10 +1987,18 @@
  }
  .metric-avg {
    background: linear-gradient(135deg, rgba(245, 158, 11, 0.12), rgba(245, 158, 11, 0.02));
    background: linear-gradient(
      135deg,
      rgba(245, 158, 11, 0.12),
      rgba(245, 158, 11, 0.02)
    );
    .metric-right {
      background: linear-gradient(135deg, var(--lux-warning), rgba(245, 158, 11, 0.62));
      background: linear-gradient(
        135deg,
        var(--lux-warning),
        rgba(245, 158, 11, 0.62)
      );
    }
  }
@@ -1958,7 +2027,7 @@
      filter: saturate(1.02);
    }
    35% {
      filter: saturate(1.10);
      filter: saturate(1.1);
    }
    100% {
      filter: saturate(1.02);
@@ -1974,7 +2043,8 @@
    border-radius: 14px;
    border: 1px solid rgba(15, 23, 42, 0.08);
    background: rgba(255, 255, 255, 0.86);
    transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
    transition: transform 0.18s ease, box-shadow 0.18s ease,
      border-color 0.18s ease;
    min-height: 68px;
    text-align: left;
    cursor: pointer;
@@ -1986,7 +2056,7 @@
  .kpi-item:hover {
    transform: translateY(-1px);
    box-shadow: 0 16px 40px rgba(15, 23, 42, 0.10);
    box-shadow: 0 16px 40px rgba(15, 23, 42, 0.1);
    border-color: rgba(47, 111, 237, 0.18);
  }
@@ -1994,9 +2064,16 @@
    content: "";
    position: absolute;
    inset: 0;
    background:
      radial-gradient(520px 140px at 20% 0%, rgba(255, 255, 255, 0.65), transparent 60%),
      radial-gradient(620px 180px at 90% 40%, rgba(47, 111, 237, 0.10), transparent 55%);
    background: radial-gradient(
        520px 140px at 20% 0%,
        rgba(255, 255, 255, 0.65),
        transparent 60%
      ),
      radial-gradient(
        620px 180px at 90% 40%,
        rgba(47, 111, 237, 0.1),
        transparent 55%
      );
    opacity: 0;
    transform: translateX(-8%) translateY(-2%);
    transition: opacity 0.22s ease, transform 0.42s cubic-bezier(0.16, 1, 0.3, 1);
@@ -2016,7 +2093,7 @@
    background: linear-gradient(
      135deg,
      rgba(47, 111, 237, 0.18),
      rgba(255, 255, 255, 0.0),
      rgba(255, 255, 255, 0),
      rgba(22, 163, 74, 0.14)
    );
    opacity: 0;
@@ -2034,17 +2111,15 @@
  }
  .kpi-item:focus-visible {
    box-shadow:
      0 16px 44px rgba(15, 23, 42, 0.10),
    box-shadow: 0 16px 44px rgba(15, 23, 42, 0.1),
      0 0 0 3px rgba(47, 111, 237, 0.18);
    border-color: rgba(47, 111, 237, 0.22);
  }
  .kpi-item.selected {
    border-color: rgba(47, 111, 237, 0.22);
    box-shadow:
      0 16px 44px rgba(15, 23, 42, 0.10),
      inset 0 0 0 1px rgba(47, 111, 237, 0.10);
    box-shadow: 0 16px 44px rgba(15, 23, 42, 0.1),
      inset 0 0 0 1px rgba(47, 111, 237, 0.1);
  }
  .kpi-left {
@@ -2096,13 +2171,13 @@
  }
  .kpi-chip.up {
    border-color: rgba(22, 163, 74, 0.20);
    border-color: rgba(22, 163, 74, 0.2);
    color: rgba(22, 163, 74, 0.96);
    background: rgba(22, 163, 74, 0.06);
  }
  .kpi-chip.down {
    border-color: rgba(239, 68, 68, 0.20);
    border-color: rgba(239, 68, 68, 0.2);
    color: rgba(239, 68, 68, 0.96);
    background: rgba(239, 68, 68, 0.06);
  }
@@ -2111,7 +2186,7 @@
    width: 72px;
    height: 22px;
    opacity: 0.9;
    filter: drop-shadow(0 8px 16px rgba(15, 23, 42, 0.10));
    filter: drop-shadow(0 8px 16px rgba(15, 23, 42, 0.1));
  }
  .kpi-actions {
@@ -2138,11 +2213,12 @@
    font-weight: 650;
    padding: 4px 8px;
    border-radius: 999px;
    border: 1px solid rgba(15, 23, 42, 0.10);
    border: 1px solid rgba(15, 23, 42, 0.1);
    background: rgba(255, 255, 255, 0.78);
    color: rgba(15, 23, 42, 0.78);
    cursor: pointer;
    transition: background-color 0.16s ease, border-color 0.16s ease, transform 0.16s ease;
    transition: background-color 0.16s ease, border-color 0.16s ease,
      transform 0.16s ease;
  }
  .kpi-action:hover {
@@ -2165,7 +2241,7 @@
    height: 240px;
    display: grid;
    place-items: center;
    background: rgba(255, 255, 255, 0.70);
    background: rgba(255, 255, 255, 0.7);
    border-radius: 12px;
    position: absolute;
    inset: 0;
@@ -2178,8 +2254,11 @@
  :deep(.big-chart-dialog .el-dialog__header) {
    padding: 14px 16px;
    background:
      radial-gradient(900px 240px at 10% 0%, rgba(47, 111, 237, 0.10), transparent 55%),
    background: radial-gradient(
        900px 240px at 10% 0%,
        rgba(47, 111, 237, 0.1),
        transparent 55%
      ),
      rgba(255, 255, 255, 0.92);
    border-bottom: 1px solid rgba(15, 23, 42, 0.06);
  }
@@ -2213,7 +2292,7 @@
    display: flex;
    align-items: center;
    gap: 8px;
    opacity: 0.0;
    opacity: 0;
    transform: translateY(-2px);
    transition: opacity 0.16s ease, transform 0.16s ease;
  }
@@ -2239,11 +2318,12 @@
    font-weight: 650;
    padding: 4px 8px;
    border-radius: 10px;
    border: 1px solid rgba(15, 23, 42, 0.10);
    border: 1px solid rgba(15, 23, 42, 0.1);
    background: rgba(255, 255, 255, 0.78);
    color: rgba(15, 23, 42, 0.78);
    cursor: pointer;
    transition: background-color 0.16s ease, border-color 0.16s ease, transform 0.16s ease;
    transition: background-color 0.16s ease, border-color 0.16s ease,
      transform 0.16s ease;
  }
  .chart-tool:hover {
@@ -2279,31 +2359,55 @@
  }
  .kpi-total {
    background: linear-gradient(135deg, rgba(47, 111, 237, 0.10), rgba(255, 255, 255, 0.86));
    background: linear-gradient(
      135deg,
      rgba(47, 111, 237, 0.1),
      rgba(255, 255, 255, 0.86)
    );
  }
  .kpi-total .kpi-icon {
    background: linear-gradient(135deg, var(--lux-primary), var(--lux-primary-2));
  }
  .kpi-production {
    background: linear-gradient(135deg, rgba(22, 163, 74, 0.10), rgba(255, 255, 255, 0.86));
    background: linear-gradient(
      135deg,
      rgba(22, 163, 74, 0.1),
      rgba(255, 255, 255, 0.86)
    );
  }
  .kpi-production .kpi-icon {
    background: linear-gradient(135deg, var(--lux-success), rgba(22, 163, 74, 0.65));
    background: linear-gradient(
      135deg,
      var(--lux-success),
      rgba(22, 163, 74, 0.65)
    );
  }
  .kpi-office {
    background: linear-gradient(135deg, rgba(100, 116, 139, 0.10), rgba(255, 255, 255, 0.86));
    background: linear-gradient(
      135deg,
      rgba(100, 116, 139, 0.1),
      rgba(255, 255, 255, 0.86)
    );
  }
  .kpi-office .kpi-icon {
    background: linear-gradient(135deg, #64748b, #94a3b8);
  }
  .kpi-avg {
    background: linear-gradient(135deg, rgba(245, 158, 11, 0.10), rgba(255, 255, 255, 0.86));
    background: linear-gradient(
      135deg,
      rgba(245, 158, 11, 0.1),
      rgba(255, 255, 255, 0.86)
    );
  }
  .kpi-avg .kpi-icon {
    background: linear-gradient(135deg, var(--lux-warning), rgba(245, 158, 11, 0.62));
    background: linear-gradient(
      135deg,
      var(--lux-warning),
      rgba(245, 158, 11, 0.62)
    );
  }
  .panel-card {
@@ -2358,10 +2462,13 @@
  }
  .segmented.no-active {
    background:
      radial-gradient(900px 220px at 20% 0%, rgba(47, 111, 237, 0.06), transparent 55%),
    background: radial-gradient(
        900px 220px at 20% 0%,
        rgba(47, 111, 237, 0.06),
        transparent 55%
      ),
      rgba(15, 23, 42, 0.03);
    border-color: rgba(15, 23, 42, 0.10);
    border-color: rgba(15, 23, 42, 0.1);
  }
  .segmented-indicator {
@@ -2371,14 +2478,15 @@
    width: calc(50% - 4px);
    height: calc(100% - 8px);
    border-radius: 13px;
    background: linear-gradient(180deg, rgba(47, 111, 237, 0.10), rgba(255, 255, 255, 0.82));
    background: linear-gradient(
      180deg,
      rgba(47, 111, 237, 0.1),
      rgba(255, 255, 255, 0.82)
    );
    border: 1px solid rgba(47, 111, 237, 0.18);
    box-shadow:
      0 14px 30px rgba(15, 23, 42, 0.10),
    box-shadow: 0 14px 30px rgba(15, 23, 42, 0.1),
      0 1px 0 rgba(255, 255, 255, 0.65) inset;
    transition:
      transform 0.36s cubic-bezier(0.16, 1, 0.3, 1),
      opacity 0.20s ease;
    transition: transform 0.36s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.2s ease;
    pointer-events: none;
    will-change: transform;
    z-index: 1;
@@ -2401,7 +2509,8 @@
    border: 1px solid transparent;
    background: transparent;
    cursor: pointer;
    transition: transform 0.16s ease, color 0.16s ease, background-color 0.16s ease;
    transition: transform 0.16s ease, color 0.16s ease,
      background-color 0.16s ease;
  }
  .segmented-item:hover {
@@ -2452,7 +2561,8 @@
    background: var(--lux-card);
    backdrop-filter: blur(10px);
    box-shadow: var(--lux-shadow-soft);
    transition: box-shadow 0.22s ease, transform 0.22s ease, border-color 0.22s ease;
    transition: box-shadow 0.22s ease, transform 0.22s ease,
      border-color 0.22s ease;
    &:hover {
      transform: translateY(-2px);
@@ -2483,12 +2593,13 @@
    background: var(--lux-card);
    backdrop-filter: blur(10px);
    box-shadow: var(--lux-shadow-soft);
    transition: box-shadow 0.22s ease, transform 0.22s ease, border-color 0.22s ease;
    transition: box-shadow 0.22s ease, transform 0.22s ease,
      border-color 0.22s ease;
    &:hover {
      transform: translateY(-1px);
      box-shadow: var(--lux-shadow);
      border-color: rgba(15, 23, 42, 0.10);
      border-color: rgba(15, 23, 42, 0.1);
    }
  }
@@ -2496,12 +2607,12 @@
    width: 100%;
  }
  .consumption-value {
  .unit-value {
    font-weight: bold;
    color: var(--lux-primary);
  }
  .consumption-unit {
  .unit-unit {
    font-size: 12px;
    color: var(--lux-muted);
    margin-left: 2px;
@@ -2535,8 +2646,11 @@
  }
  :deep(.lux-table .el-table__header-wrapper) {
    background:
      linear-gradient(180deg, rgba(15, 23, 42, 0.04) 0%, rgba(15, 23, 42, 0.02) 100%);
    background: linear-gradient(
      180deg,
      rgba(15, 23, 42, 0.04) 0%,
      rgba(15, 23, 42, 0.02) 100%
    );
  }
  :deep(.lux-table th.el-table__cell) {
@@ -2560,10 +2674,12 @@
  }
  :deep(.lux-table .el-table__row:hover) {
    box-shadow: inset 3px 0 0 rgba(47, 111, 237, 0.30);
    box-shadow: inset 3px 0 0 rgba(47, 111, 237, 0.3);
  }
  :deep(.lux-table .el-table__body tr.el-table__row--striped > td.el-table__cell) {
  :deep(
      .lux-table .el-table__body tr.el-table__row--striped > td.el-table__cell
    ) {
    background: rgba(15, 23, 42, 0.018);
  }
@@ -2623,4 +2739,4 @@
    max-height: 600px;
    opacity: 1;
  }
</style>
</style>