1
yyb
2 天以前 0878762ff3d0796e5fcb22fc38103a3079d6ca24
src/views/costAccounting/energyCosts/index.vue
@@ -2,8 +2,7 @@
<template>
  <div class="energy-cost-page">
    <!-- 筛选区域 -->
    <el-card class="filter-card"
             shadow="never">
    <el-card class="filter-card" shadow="never">
      <template #header>
        <div class="card-head">
          <div class="card-head-left">
@@ -13,9 +12,11 @@
            <span class="card-title">查询条件</span>
          </div>
          <div class="card-head-right">
            <el-radio-group v-model="statisticsType"
                            size="small"
                            @change="handleTypeChange">
            <el-radio-group
              v-model="statisticsType"
              size="small"
              @change="handleTypeChange"
            >
              <el-radio-button label="day">按日</el-radio-button>
              <el-radio-button label="month">按月</el-radio-button>
            </el-radio-group>
@@ -24,9 +25,7 @@
      </template>
      <div class="filter-layout">
        <el-form :model="searchForm"
                 :inline="true"
                 class="filter-form">
        <el-form :model="searchForm" :inline="true" class="filter-form">
          <!-- <el-form-item label="能耗类型">
            <el-select v-model="searchForm.energyType"
                       placeholder="全部"
@@ -44,80 +43,94 @@
            </el-select>
          </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
              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 label="时间范围">
            <el-date-picker v-if="statisticsType === 'day'"
                            v-model="searchForm.dateRange"
                            type="daterange"
                            range-separator="至"
                            start-placeholder="开始日期"
                            end-placeholder="结束日期"
                            value-format="YYYY-MM-DD"
                            class="w-260"
                            @change="handleQuery" />
            <el-date-picker v-else
                            v-model="searchForm.monthRange"
                            type="monthrange"
                            range-separator="至"
                            start-placeholder="开始月份"
                            end-placeholder="结束月份"
                            value-format="YYYY-MM"
                            class="w-260"
                            @change="handleQuery" />
            <el-date-picker
              v-if="statisticsType === 'day'"
              v-model="searchForm.dateRange"
              type="daterange"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              value-format="YYYY-MM-DD"
              class="w-260"
              @change="handleQuery"
            />
            <el-date-picker
              v-else
              v-model="searchForm.monthRange"
              type="monthrange"
              range-separator="至"
              start-placeholder="开始月份"
              end-placeholder="结束月份"
              value-format="YYYY-MM"
              class="w-260"
              @change="handleQuery"
            />
          </el-form-item>
        </el-form>
        <div class="filter-actions">
          <el-button class="lux-btn"
                     type="primary"
                     :loading="tableLoading"
                     @click="handleQuery">刷新</el-button>
          <el-button class="lux-btn"
                     @click="handleReset">重置</el-button>
          <el-button class="lux-btn"
                     type="success"
                     plain
                     @click="handleExport">导出</el-button>
          <el-button
            class="lux-btn"
            type="primary"
            :loading="tableLoading"
            @click="handleQuery"
            >刷新</el-button
          >
          <el-button class="lux-btn" @click="handleReset">重置</el-button>
          <el-button class="lux-btn" type="success" plain @click="handleExport"
            >导出</el-button
          >
        </div>
      </div>
    </el-card>
    <!-- 图表区域 -->
    <div class="charts">
      <el-card class="panel-card"
               shadow="never">
        <div class="kpi-strip"
             :class="{ pulse: queryPulse }"
             title="快捷键:Enter 刷新 / Esc 重置 / Alt+E 导出">
          <button class="kpi-item kpi-total"
                  type="button"
                  :class="{ selected: selectedKpi === 'all' }"
                  @click="handleKpiClick('all')">
      <el-card class="panel-card" shadow="never">
        <div
          class="kpi-strip"
          :class="{ pulse: queryPulse }"
          title="快捷键:Enter 刷新 / Esc 重置 / Alt+E 导出"
        >
          <button
            class="kpi-item kpi-total"
            type="button"
            :class="{ selected: selectedKpi === 'all' }"
            @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.totalCost) }}
              </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>
                <svg class="kpi-spark"
                     viewBox="0 0 72 22"
                     aria-hidden="true">
                  <polyline :points="sparklinePoints(kpiSeries.total)"
                            fill="none"
                            stroke="rgba(47, 111, 237, 0.85)"
                            stroke-width="2"
                            stroke-linecap="round"
                            stroke-linejoin="round" />
                <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
                >
                <svg class="kpi-spark" viewBox="0 0 72 22" aria-hidden="true">
                  <polyline
                    :points="sparklinePoints(kpiSeries.total)"
                    fill="none"
                    stroke="rgba(47, 111, 237, 0.85)"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  />
                </svg>
              </div>
            </div>
@@ -126,36 +139,51 @@
                <Money />
              </el-icon>
            </div>
            <div class="kpi-actions"
                 @click.stop>
              <button class="kpi-action"
                      type="button"
                      @click="copyKpi('totalCost')">复制</button>
              <button class="kpi-action"
                      type="button"
                      @click="viewKpiDetails('all')">明细</button>
            <div class="kpi-actions" @click.stop>
              <button
                class="kpi-action"
                type="button"
                @click="copyKpi('totalCost')"
              >
                复制
              </button>
              <button
                class="kpi-action"
                type="button"
                @click="viewKpiDetails('all')"
              >
                明细
              </button>
            </div>
          </button>
          <button class="kpi-item kpi-production"
                  type="button"
                  :class="{ selected: selectedKpi === 'production' }"
                  @click="handleKpiClick('production')">
          <button
            class="kpi-item kpi-production"
            type="button"
            :class="{ selected: selectedKpi === 'production' }"
            @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.productionCost) }}
              </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>
                <svg class="kpi-spark"
                     viewBox="0 0 72 22"
                     aria-hidden="true">
                  <polyline :points="sparklinePoints(kpiSeries.production)"
                            fill="none"
                            stroke="rgba(22, 163, 74, 0.85)"
                            stroke-width="2"
                            stroke-linecap="round"
                            stroke-linejoin="round" />
                <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
                >
                <svg class="kpi-spark" viewBox="0 0 72 22" aria-hidden="true">
                  <polyline
                    :points="sparklinePoints(kpiSeries.production)"
                    fill="none"
                    stroke="rgba(22, 163, 74, 0.85)"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  />
                </svg>
              </div>
            </div>
@@ -164,36 +192,51 @@
                <DataLine />
              </el-icon>
            </div>
            <div class="kpi-actions"
                 @click.stop>
              <button class="kpi-action"
                      type="button"
                      @click="copyKpi('productionCost')">复制</button>
              <button class="kpi-action"
                      type="button"
                      @click="viewKpiDetails('production')">明细</button>
            <div class="kpi-actions" @click.stop>
              <button
                class="kpi-action"
                type="button"
                @click="copyKpi('productionCost')"
              >
                复制
              </button>
              <button
                class="kpi-action"
                type="button"
                @click="viewKpiDetails('production')"
              >
                明细
              </button>
            </div>
          </button>
          <button class="kpi-item kpi-office"
                  type="button"
                  :class="{ selected: selectedKpi === 'office' }"
                  @click="handleKpiClick('office')">
          <button
            class="kpi-item kpi-office"
            type="button"
            :class="{ selected: selectedKpi === 'office' }"
            @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.officeCost) }}
              </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>
                <svg class="kpi-spark"
                     viewBox="0 0 72 22"
                     aria-hidden="true">
                  <polyline :points="sparklinePoints(kpiSeries.office)"
                            fill="none"
                            stroke="rgba(100, 116, 139, 0.90)"
                            stroke-width="2"
                            stroke-linecap="round"
                            stroke-linejoin="round" />
                <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
                >
                <svg class="kpi-spark" viewBox="0 0 72 22" aria-hidden="true">
                  <polyline
                    :points="sparklinePoints(kpiSeries.office)"
                    fill="none"
                    stroke="rgba(100, 116, 139, 0.90)"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  />
                </svg>
              </div>
            </div>
@@ -202,22 +245,36 @@
                <TrendCharts />
              </el-icon>
            </div>
            <div class="kpi-actions"
                 @click.stop>
              <button class="kpi-action"
                      type="button"
                      @click="copyKpi('officeCost')">复制</button>
              <button class="kpi-action"
                      type="button"
                      @click="viewKpiDetails('office')">明细</button>
            <div class="kpi-actions" @click.stop>
              <button
                class="kpi-action"
                type="button"
                @click="copyKpi('officeCost')"
              >
                复制
              </button>
              <button
                class="kpi-action"
                type="button"
                @click="viewKpiDetails('office')"
              >
                明细
              </button>
            </div>
          </button>
          <button class="kpi-item kpi-avg"
                  type="button"
                  @click="handleKpiClick('all')">
          <button
            class="kpi-item kpi-avg"
            type="button"
            @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.avgCost) }}
                <span class="kpi-unit"
                  >/{{ statisticsType === "day" ? "日" : "月" }}</span
                >
              </div>
              <div class="kpi-meta muted">基于当前筛选与明细统计</div>
            </div>
            <div class="kpi-icon">
@@ -225,41 +282,56 @@
                <Histogram />
              </el-icon>
            </div>
            <div class="kpi-actions"
                 @click.stop>
              <button class="kpi-action"
                      type="button"
                      @click="copyKpi('avgCost')">复制</button>
              <button class="kpi-action"
                      type="button"
                      @click="viewKpiDetails('all')">明细</button>
            <div class="kpi-actions" @click.stop>
              <button
                class="kpi-action"
                type="button"
                @click="copyKpi('avgCost')"
              >
                复制
              </button>
              <button
                class="kpi-action"
                type="button"
                @click="viewKpiDetails('all')"
              >
                明细
              </button>
            </div>
          </button>
        </div>
        <div class="panel-head">
          <div class="segmented"
               role="tablist"
               aria-label="分析面板切换"
               :class="{ 'no-active': chartPanel === 'none' }">
            <div class="segmented-indicator"
                 :class="{ hidden: chartPanel === 'none' }"
                 :style="panelIndicatorStyle"></div>
            <button class="segmented-item"
                    type="button"
                    role="tab"
                    :aria-selected="chartPanel === 'core'"
                    :class="{ active: chartPanel === 'core' }"
                    @click="handleChartPanelClick('core')">
          <div
            class="segmented"
            role="tablist"
            aria-label="分析面板切换"
            :class="{ 'no-active': chartPanel === 'none' }"
          >
            <div
              class="segmented-indicator"
              :class="{ hidden: chartPanel === 'none' }"
              :style="panelIndicatorStyle"
            ></div>
            <button
              class="segmented-item"
              type="button"
              role="tab"
              :aria-selected="chartPanel === 'core'"
              :class="{ active: chartPanel === 'core' }"
              @click="handleChartPanelClick('core')"
            >
              <span class="seg-title">核心分析</span>
              <span class="seg-sub">趋势 / 类型占比</span>
            </button>
            <button class="segmented-item"
                    type="button"
                    role="tab"
                    :aria-selected="chartPanel === 'advanced'"
                    :class="{ active: chartPanel === 'advanced' }"
                    @click="handleChartPanelClick('advanced')">
            <button
              class="segmented-item"
              type="button"
              role="tab"
              :aria-selected="chartPanel === 'advanced'"
              :class="{ active: chartPanel === 'advanced' }"
              @click="handleChartPanelClick('advanced')"
            >
              <span class="seg-title">高级分析</span>
              <span class="seg-sub">用途占比 / 单价对比</span>
            </button>
@@ -267,66 +339,81 @@
        </div>
        <transition name="lux-collapse">
          <div v-show="chartPanel === 'core'"
               class="panel-body">
          <div v-show="chartPanel === 'core'" class="panel-body">
            <el-row :gutter="16">
              <el-col :xs="24"
                      :lg="12">
                <el-card class="chart-card"
                         shadow="never">
              <el-col :xs="24" :lg="12">
                <el-card class="chart-card" shadow="never">
                  <template #header>
                    <div class="chart-head">
                      <span class="chart-title">能耗成本趋势</span>
                      <div class="chart-tools"
                           @click.stop>
                        <button class="chart-tool"
                                type="button"
                                @click="downloadChart('cost', '能耗成本趋势')">下载</button>
                        <button class="chart-tool"
                                type="button"
                                @click="openBigChart('cost', '能耗成本趋势')">大图</button>
                      <div class="chart-tools" @click.stop>
                        <button
                          class="chart-tool"
                          type="button"
                          @click="downloadChart('cost', '能耗成本趋势')"
                        >
                          下载
                        </button>
                        <button
                          class="chart-tool"
                          type="button"
                          @click="openBigChart('cost', '能耗成本趋势')"
                        >
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
                  <div ref="costChartWrap"
                       class="chart-wrap"
                       v-loading="tableLoading">
                    <div ref="costChart"
                         class="chart-content"
                         v-show="hasTableData"></div>
                    <div class="chart-empty"
                         v-show="!hasTableData">
                  <div
                    ref="costChartWrap"
                    class="chart-wrap"
                    v-loading="tableLoading"
                  >
                    <div
                      ref="costChart"
                      class="chart-content"
                      v-show="hasTableData"
                    ></div>
                    <div class="chart-empty" v-show="!hasTableData">
                      <el-empty description="暂无数据" />
                    </div>
                  </div>
                </el-card>
              </el-col>
              <el-col :xs="24"
                      :lg="12">
                <el-card class="chart-card"
                         shadow="never">
              <el-col :xs="24" :lg="12">
                <el-card class="chart-card" shadow="never">
                  <template #header>
                    <div class="chart-head">
                      <span class="chart-title">能耗类型成本占比</span>
                      <div class="chart-tools"
                           @click.stop>
                        <button class="chart-tool"
                                type="button"
                                @click="downloadChart('type', '能耗类型成本占比')">下载</button>
                        <button class="chart-tool"
                                type="button"
                                @click="openBigChart('type', '能耗类型成本占比')">大图</button>
                      <div class="chart-tools" @click.stop>
                        <button
                          class="chart-tool"
                          type="button"
                          @click="downloadChart('type', '能耗类型成本占比')"
                        >
                          下载
                        </button>
                        <button
                          class="chart-tool"
                          type="button"
                          @click="openBigChart('type', '能耗类型成本占比')"
                        >
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
                  <div ref="typeChartWrap"
                       class="chart-wrap"
                       v-loading="tableLoading">
                    <div ref="typeChart"
                         class="chart-content"
                         v-show="hasTableData"></div>
                    <div class="chart-empty"
                         v-show="!hasTableData">
                  <div
                    ref="typeChartWrap"
                    class="chart-wrap"
                    v-loading="tableLoading"
                  >
                    <div
                      ref="typeChart"
                      class="chart-content"
                      v-show="hasTableData"
                    ></div>
                    <div class="chart-empty" v-show="!hasTableData">
                      <el-empty description="暂无数据" />
                    </div>
                  </div>
@@ -337,67 +424,81 @@
        </transition>
        <transition name="lux-collapse">
          <div v-show="chartPanel === 'advanced'"
               class="panel-body">
            <el-row :gutter="16"
                    class="charts-row">
              <el-col :xs="24"
                      :lg="12">
                <el-card class="chart-card"
                         shadow="never">
          <div v-show="chartPanel === 'advanced'" class="panel-body">
            <el-row :gutter="16" class="charts-row">
              <el-col :xs="24" :lg="12">
                <el-card class="chart-card" shadow="never">
                  <template #header>
                    <div class="chart-head">
                      <span class="chart-title">能耗用途成本占比</span>
                      <div class="chart-tools"
                           @click.stop>
                        <button class="chart-tool"
                                type="button"
                                @click="downloadChart('purpose', '能耗用途成本占比')">下载</button>
                        <button class="chart-tool"
                                type="button"
                                @click="openBigChart('purpose', '能耗用途成本占比')">大图</button>
                      <div class="chart-tools" @click.stop>
                        <button
                          class="chart-tool"
                          type="button"
                          @click="downloadChart('purpose', '能耗用途成本占比')"
                        >
                          下载
                        </button>
                        <button
                          class="chart-tool"
                          type="button"
                          @click="openBigChart('purpose', '能耗用途成本占比')"
                        >
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
                  <div ref="purposeChartWrap"
                       class="chart-wrap"
                       v-loading="tableLoading">
                    <div ref="purposeChart"
                         class="chart-content"
                         v-show="hasTableData"></div>
                    <div class="chart-empty"
                         v-show="!hasTableData">
                  <div
                    ref="purposeChartWrap"
                    class="chart-wrap"
                    v-loading="tableLoading"
                  >
                    <div
                      ref="purposeChart"
                      class="chart-content"
                      v-show="hasTableData"
                    ></div>
                    <div class="chart-empty" v-show="!hasTableData">
                      <el-empty description="暂无数据" />
                    </div>
                  </div>
                </el-card>
              </el-col>
              <el-col :xs="24"
                      :lg="12">
                <el-card class="chart-card"
                         shadow="never">
              <el-col :xs="24" :lg="12">
                <el-card class="chart-card" shadow="never">
                  <template #header>
                    <div class="chart-head">
                      <span class="chart-title">能耗单价对比</span>
                      <div class="chart-tools"
                           @click.stop>
                        <button class="chart-tool"
                                type="button"
                                @click="downloadChart('price', '能耗单价对比')">下载</button>
                        <button class="chart-tool"
                                type="button"
                                @click="openBigChart('price', '能耗单价对比')">大图</button>
                      <div class="chart-tools" @click.stop>
                        <button
                          class="chart-tool"
                          type="button"
                          @click="downloadChart('price', '能耗单价对比')"
                        >
                          下载
                        </button>
                        <button
                          class="chart-tool"
                          type="button"
                          @click="openBigChart('price', '能耗单价对比')"
                        >
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
                  <div ref="priceChartWrap"
                       class="chart-wrap"
                       v-loading="tableLoading">
                    <div ref="priceChart"
                         class="chart-content"
                         v-show="hasTableData"></div>
                    <div class="chart-empty"
                         v-show="!hasTableData">
                  <div
                    ref="priceChartWrap"
                    class="chart-wrap"
                    v-loading="tableLoading"
                  >
                    <div
                      ref="priceChart"
                      class="chart-content"
                      v-show="hasTableData"
                    ></div>
                    <div class="chart-empty" v-show="!hasTableData">
                      <el-empty description="暂无数据" />
                    </div>
                  </div>
@@ -409,30 +510,36 @@
      </el-card>
    </div>
    <el-dialog v-model="bigChartVisible"
               :title="bigChartTitle"
               width="92%"
               top="6vh"
               class="big-chart-dialog"
               destroy-on-close
               @opened="handleBigChartOpened"
               @closed="handleBigChartClosed">
      <div ref="bigChartEl"
           class="big-chart-canvas"></div>
    <el-dialog
      v-model="bigChartVisible"
      :title="bigChartTitle"
      width="92%"
      top="6vh"
      class="big-chart-dialog"
      destroy-on-close
      @opened="handleBigChartOpened"
      @closed="handleBigChartClosed"
    >
      <div ref="bigChartEl" class="big-chart-canvas"></div>
      <template #footer>
        <div class="big-chart-footer">
          <el-button class="lux-btn"
                     @click="downloadChart(bigChartKey, bigChartTitle)">下载图片</el-button>
          <el-button class="lux-btn"
                     type="primary"
                     @click="bigChartVisible = false">关闭</el-button>
          <el-button
            class="lux-btn"
            @click="downloadChart(bigChartKey, bigChartTitle)"
            >下载图片</el-button
          >
          <el-button
            class="lux-btn"
            type="primary"
            @click="bigChartVisible = false"
            >关闭</el-button
          >
        </div>
      </template>
    </el-dialog>
    <!-- 数据表格 -->
    <el-card class="table-card"
             shadow="never">
    <el-card class="table-card" shadow="never">
      <div ref="tableAnchor"></div>
      <template #header>
        <div class="card-head">
@@ -448,2214 +555,2190 @@
        </div>
      </template>
      <el-table :data="displayTableData"
                v-loading="tableLoading"
                stripe
                :header-cell-style="{ height: '44px' }"
                class="data-table lux-table"
                @sort-change="handleSortChange">
      <el-table
        :data="displayTableData"
        v-loading="tableLoading"
        stripe
        :header-cell-style="{ height: '44px' }"
        class="data-table lux-table"
        @sort-change="handleSortChange"
      >
        <template #empty>
          <el-empty description="暂无明细数据" />
        </template>
        <el-table-column type="index"
                         label="序号"
                         width="60"
                         align="center" />
        <el-table-column prop="timePeriod"
                         :label="timeColumnLabel"
                         align="center"
                         sortable="custom" />
        <el-table-column prop="energyType"
                         label="能耗类型"
                         width="100"
                         align="center"
                         :filters="energyTypeFilters"
                         :filter-method="filterEnergyType"
                         filter-placement="bottom-end">
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column
          prop="timePeriod"
          :label="timeColumnLabel"
          align="center"
          sortable="custom"
        />
        <el-table-column
          prop="energyType"
          label="能耗类型"
          width="100"
          align="center"
          :filters="energyTypeFilters"
          :filter-method="filterEnergyType"
          filter-placement="bottom-end"
        >
          <template #default="scope">
            <el-tag :type="getEnergyTypeType(scope.row.energyType)">
              {{ scope.row.energyType }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="type"
                         label="能耗用途"
                         width="100"
                         align="center"
                         :filters="energyPurposeFilters"
                         :filter-method="filterEnergyPurpose"
                         filter-placement="bottom-end">
        <el-table-column
          prop="type"
          label="能耗用途"
          width="100"
          align="center"
          :filters="energyPurposeFilters"
          :filter-method="filterEnergyPurpose"
          filter-placement="bottom-end"
        >
          <template #default="scope">
            <el-tag :type="scope.row.type === '生产' ? 'primary' : 'info'">
              {{ scope.row.type }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="consumption"
                         label="用量"
                         align="right">
        <el-table-column prop="consumption" label="用量" align="right">
          <template #default="scope">
            <span class="consumption-value">{{ formatNumber(scope.row.consumption, 2) }}</span>
            <span class="consumption-value">{{
              formatNumber(scope.row.consumption, 2)
            }}</span>
            <span class="consumption-unit">{{ scope.row.unit }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="price"
                         label="单价(元)"
                         align="right"
                         sortable="custom">
        <el-table-column
          prop="price"
          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.price, 2)
            }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="cost"
                         label="成本(元)"
                         align="right"
                         sortable="custom"
                         fixed="right">
        <el-table-column
          prop="cost"
          label="成本(元)"
          align="right"
          sortable="custom"
          fixed="right"
        >
          <template #default="scope">
            <span class="cost-value">¥{{ formatNumber(scope.row.cost, 2) }}</span>
            <span class="cost-value"
              >¥{{ formatNumber(scope.row.cost, 2) }}</span
            >
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination-container">
        <el-pagination v-model:current-page="page.current"
                       v-model:page-size="page.size"
                       :page-sizes="[10, 20, 50, 100]"
                       :total="page.total"
                       layout="total, sizes, prev, pager, next, jumper"
                       @size-change="handleSizeChange"
                       @current-change="handleCurrentChange" />
        <el-pagination
          v-model:current-page="page.current"
          v-model:page-size="page.size"
          :page-sizes="[10, 20, 50, 100]"
          :total="page.total"
          layout="total, sizes, prev, pager, next, jumper"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
    </el-card>
  </div>
</template>
<script setup>
  import { ref, reactive, onMounted, onUnmounted, computed, nextTick, watch } from "vue";
  import { ElMessage } from "element-plus";
  import {
    Money,
    DataLine,
    TrendCharts,
    Histogram,
    List,
    ArrowDown,
  } from "@element-plus/icons-vue";
  import * as echarts from "echarts";
  // import { energyCostStatistics } from "@/api/costAccounting/energyCosts";
  import { energyConsumptionDetailStatistics } from "@/api/energyManagement/energyType";
  // 统计维度:day-按日,month-按月
  const statisticsType = ref("day");
import {
  ref,
  reactive,
  onMounted,
  onUnmounted,
  computed,
  nextTick,
  watch,
} from "vue";
import { ElMessage } from "element-plus";
import {
  Money,
  DataLine,
  TrendCharts,
  Histogram,
  List,
  ArrowDown,
} from "@element-plus/icons-vue";
import * as echarts from "echarts";
// import { energyCostStatistics } from "@/api/costAccounting/energyCosts";
import { energyConsumptionDetailStatistics } from "@/api/energyManagement/energyType";
// 统计维度:day-按日,month-按月
const statisticsType = ref("day");
  // 搜索表单
  const searchForm = reactive({
    // energyType: "",
    type: "",
    dateRange: (() => {
      // 默认最近7天
      const end = new Date();
      const start = new Date();
      start.setDate(start.getDate() - 6);
      return [start.toISOString().split("T")[0], end.toISOString().split("T")[0]];
    })(),
    monthRange: (() => {
      // 默认最近3个月
      const end = new Date();
      const start = new Date();
      start.setMonth(start.getMonth() - 2);
      return [start.toISOString().slice(0, 7), end.toISOString().slice(0, 7)];
    })(),
// 搜索表单
const searchForm = reactive({
  // energyType: "",
  type: "",
  dateRange: (() => {
    // 默认最近7天
    const end = new Date();
    const start = new Date();
    start.setDate(start.getDate() - 6);
    return [start.toISOString().split("T")[0], end.toISOString().split("T")[0]];
  })(),
  monthRange: (() => {
    // 默认最近3个月
    const end = new Date();
    const start = new Date();
    start.setMonth(start.getMonth() - 2);
    return [start.toISOString().slice(0, 7), end.toISOString().slice(0, 7)];
  })(),
});
// 时间列标签
const timeColumnLabel = computed(() => {
  return statisticsType.value === "day" ? "日期" : "月份";
});
// 统计概览
const overview = reactive({
  totalCost: "0.00",
  productionCost: "0.00",
  officeCost: "0.00",
  avgCost: "0.00",
});
const selectedKpi = ref("all"); // all | production | office
const animatedOverview = reactive({
  totalCost: 0,
  productionCost: 0,
  officeCost: 0,
  avgCost: 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,
  });
};
  // 时间列标签
  const timeColumnLabel = computed(() => {
    return statisticsType.value === "day" ? "日期" : "月份";
const formatNumber = (v, digits = 2) => {
  const n = Number.parseFloat(v);
  if (!Number.isFinite(n)) return "--";
  return n.toLocaleString("zh-CN", {
    minimumFractionDigits: digits,
    maximumFractionDigits: digits,
  });
};
  // 统计概览
  const overview = reactive({
    totalCost: "0.00",
    productionCost: "0.00",
    officeCost: "0.00",
    avgCost: "0.00",
  });
const animateNumber = (key, toValue, duration = 420) => {
  const from = animatedOverview[key] || 0;
  const to = Number.isFinite(toValue) ? toValue : 0;
  const start = performance.now();
  const easeOut = (t) => 1 - Math.pow(1 - t, 3);
  const selectedKpi = ref("all"); // all | production | office
  const animatedOverview = reactive({
    totalCost: 0,
    productionCost: 0,
    officeCost: 0,
    avgCost: 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 });
  const tick = (now) => {
    const p = Math.min(1, (now - start) / duration);
    animatedOverview[key] = from + (to - from) * easeOut(p);
    if (p < 1) requestAnimationFrame(tick);
  };
  requestAnimationFrame(tick);
};
  const formatNumber = (v, digits = 2) => {
    const n = Number.parseFloat(v);
    if (!Number.isFinite(n)) return "--";
    return n.toLocaleString("zh-CN", { minimumFractionDigits: digits, maximumFractionDigits: digits });
  };
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));
  },
  { deep: true, immediate: true }
);
  const animateNumber = (key, toValue, duration = 420) => {
    const from = animatedOverview[key] || 0;
    const to = Number.isFinite(toValue) ? toValue : 0;
    const start = performance.now();
    const easeOut = t => 1 - Math.pow(1 - t, 3);
// 表格数据
const tableData = ref([]);
const tableLoading = ref(false);
const hasTableData = computed(
  () => Array.isArray(tableData.value) && tableData.value.length > 0
);
const queryPulse = ref(false);
    const tick = now => {
      const p = Math.min(1, (now - start) / duration);
      animatedOverview[key] = from + (to - from) * easeOut(p);
      if (p < 1) requestAnimationFrame(tick);
    };
    requestAnimationFrame(tick);
  };
  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));
    },
    { deep: true, immediate: true }
const kpiSeries = computed(() => {
  const rows = Array.isArray(tableData.value) ? tableData.value : [];
  const byTime = new Map();
  for (const r of rows) {
    const t = r?.timePeriod ?? "";
    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?.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 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);
  return { times, total, production, office };
});
  // 表格数据
  const tableData = ref([]);
  const tableLoading = ref(false);
  const hasTableData = computed(() => Array.isArray(tableData.value) && tableData.value.length > 0);
  const queryPulse = ref(false);
const kpiDelta = computed(() => {
  const pick = (arr) => {
    const a = Array.isArray(arr) ? arr : [];
    if (a.length < 2) return { pct: 0, valid: false };
    const prev = a[a.length - 2];
    const cur = a[a.length - 1];
    if (!Number.isFinite(prev) || prev === 0) return { pct: 0, valid: false };
    return { pct: ((cur - prev) / prev) * 100, valid: true };
  };
  return {
    total: pick(kpiSeries.value.total),
    production: pick(kpiSeries.value.production),
    office: pick(kpiSeries.value.office),
  };
});
  const kpiSeries = computed(() => {
    const rows = Array.isArray(tableData.value) ? tableData.value : [];
    const byTime = new Map();
    for (const r of rows) {
      const t = r?.timePeriod ?? "";
      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?.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 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);
    return { times, total, production, office };
const sparklinePoints = (values) => {
  const v = (Array.isArray(values) ? values : []).slice(-12);
  if (v.length < 2) return "";
  const min = Math.min(...v);
  const max = Math.max(...v);
  const range = max - min || 1;
  const w = 72;
  const h = 22;
  return v
    .map((n, i) => {
      const x = (i / (v.length - 1)) * w;
      const y = h - ((n - min) / range) * h;
      return `${x.toFixed(2)},${y.toFixed(2)}`;
    })
    .join(" ");
};
const handleKpiClick = (key) => {
  selectedKpi.value = key;
  if (key === "all") searchForm.type = "";
  if (key === "production") searchForm.type = "生产";
  if (key === "office") searchForm.type = "办公";
  page.current = 1;
  handleQuery();
};
const viewKpiDetails = (key) => {
  handleKpiClick(key);
  nextTick(() => {
    const el = tableAnchor.value;
    if (el?.scrollIntoView)
      el.scrollIntoView({ behavior: "smooth", block: "start" });
  });
};
  const kpiDelta = computed(() => {
    const pick = arr => {
      const a = Array.isArray(arr) ? arr : [];
      if (a.length < 2) return { pct: 0, valid: false };
      const prev = a[a.length - 2];
      const cur = a[a.length - 1];
      if (!Number.isFinite(prev) || prev === 0) return { pct: 0, valid: false };
      return { pct: ((cur - prev) / prev) * 100, valid: true };
    };
    return {
      total: pick(kpiSeries.value.total),
      production: pick(kpiSeries.value.production),
      office: pick(kpiSeries.value.office),
    };
  });
  const sparklinePoints = values => {
    const v = (Array.isArray(values) ? values : []).slice(-12);
    if (v.length < 2) return "";
    const min = Math.min(...v);
    const max = Math.max(...v);
    const range = max - min || 1;
    const w = 72;
    const h = 22;
    return v
      .map((n, i) => {
        const x = (i / (v.length - 1)) * w;
        const y = h - ((n - min) / range) * h;
        return `${x.toFixed(2)},${y.toFixed(2)}`;
      })
      .join(" ");
const copyKpi = async (field) => {
  const map = {
    totalCost: animatedOverview.totalCost,
    productionCost: animatedOverview.productionCost,
    officeCost: animatedOverview.officeCost,
    avgCost: animatedOverview.avgCost,
  };
  const handleKpiClick = key => {
    selectedKpi.value = key;
    if (key === "all") searchForm.type = "";
    if (key === "production") searchForm.type = "生产";
    if (key === "office") searchForm.type = "办公";
    page.current = 1;
    handleQuery();
  };
  const viewKpiDetails = key => {
    handleKpiClick(key);
    nextTick(() => {
      const el = tableAnchor.value;
      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,
    };
    const raw = map[field];
    const text = `¥${formatMoney(raw)}`;
    try {
      if (navigator?.clipboard?.writeText) {
        await navigator.clipboard.writeText(text);
      } else {
        const input = document.createElement("input");
        input.value = text;
        document.body.appendChild(input);
        input.select();
        document.execCommand("copy");
        document.body.removeChild(input);
      }
      ElMessage.success("已复制到剪贴板");
    } catch (e) {
      console.error(e);
      ElMessage.error("复制失败");
  const raw = map[field];
  const text = `¥${formatMoney(raw)}`;
  try {
    if (navigator?.clipboard?.writeText) {
      await navigator.clipboard.writeText(text);
    } else {
      const input = document.createElement("input");
      input.value = text;
      document.body.appendChild(input);
      input.select();
      document.execCommand("copy");
      document.body.removeChild(input);
    }
  };
    ElMessage.success("已复制到剪贴板");
  } catch (e) {
    console.error(e);
    ElMessage.error("复制失败");
  }
};
  const getChartByKey = key => {
    if (key === "cost") return costChartInstance;
    if (key === "type") return typeChartInstance;
    if (key === "purpose") return purposeChartInstance;
    if (key === "price") return priceChartInstance;
    return null;
  };
const getChartByKey = (key) => {
  if (key === "cost") return costChartInstance;
  if (key === "type") return typeChartInstance;
  if (key === "purpose") return purposeChartInstance;
  if (key === "price") return priceChartInstance;
  return null;
};
  const ensurePanelForChart = key => {
    if (key === "cost" || key === "type") chartPanel.value = "core";
    if (key === "purpose" || key === "price") chartPanel.value = "advanced";
  };
const ensurePanelForChart = (key) => {
  if (key === "cost" || key === "type") chartPanel.value = "core";
  if (key === "purpose" || key === "price") chartPanel.value = "advanced";
};
  const downloadChart = (key, title) => {
    ensurePanelForChart(key);
    nextTick(() => {
      ensureChartsReady(chartPanel.value === "none" ? "core" : chartPanel.value);
      const ins = getChartByKey(key);
      if (!ins) return;
      const url = ins.getDataURL({ pixelRatio: 2, backgroundColor: "#ffffff" });
      const a = document.createElement("a");
      a.href = url;
      const typePart = searchForm.energyType ? `_${searchForm.energyType}` : "";
      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]}`;
      }
      a.download = `${title || "chart"}${typePart}${purposePart}${rangePart}.png`;
      a.click();
    });
  };
const downloadChart = (key, title) => {
  ensurePanelForChart(key);
  nextTick(() => {
    ensureChartsReady(chartPanel.value === "none" ? "core" : chartPanel.value);
    const ins = getChartByKey(key);
    if (!ins) return;
    const url = ins.getDataURL({ pixelRatio: 2, backgroundColor: "#ffffff" });
    const a = document.createElement("a");
    a.href = url;
    const typePart = searchForm.energyType ? `_${searchForm.energyType}` : "";
    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]}`;
    }
    a.download = `${title || "chart"}${typePart}${purposePart}${rangePart}.png`;
    a.click();
  });
};
  const openBigChart = (key, title) => {
    bigChartKey.value = key;
    bigChartTitle.value = title || "图表";
    bigChartVisible.value = true;
  };
const openBigChart = (key, title) => {
  bigChartKey.value = key;
  bigChartTitle.value = title || "图表";
  bigChartVisible.value = true;
};
  const handleBigChartOpened = () => {
    nextTick(() => {
      ensurePanelForChart(bigChartKey.value);
      ensureChartsReady(chartPanel.value === "none" ? "core" : chartPanel.value);
      const src = getChartByKey(bigChartKey.value);
      const el = bigChartEl.value;
      if (!src || !el) return;
const handleBigChartOpened = () => {
  nextTick(() => {
    ensurePanelForChart(bigChartKey.value);
    ensureChartsReady(chartPanel.value === "none" ? "core" : chartPanel.value);
    const src = getChartByKey(bigChartKey.value);
    const el = bigChartEl.value;
    if (!src || !el) return;
      try {
        bigChartInstance?.dispose?.();
      } catch (e) {
        // ignore
      }
      bigChartInstance = echarts.init(el);
      const opt = src.getOption();
      bigChartInstance.setOption(opt, true);
      bigChartInstance.resize();
    });
  };
  const handleBigChartClosed = () => {
    try {
      bigChartInstance?.dispose?.();
    } catch (e) {
      // ignore
    }
    bigChartInstance = null;
  };
    bigChartInstance = echarts.init(el);
    const opt = src.getOption();
    bigChartInstance.setOption(opt, true);
    bigChartInstance.resize();
  });
};
  const handleBigChartResize = () => {
    try {
      bigChartInstance?.resize?.();
    } catch (e) {
      // ignore
const handleBigChartClosed = () => {
  try {
    bigChartInstance?.dispose?.();
  } catch (e) {
    // ignore
  }
  bigChartInstance = null;
};
const handleBigChartResize = () => {
  try {
    bigChartInstance?.resize?.();
  } catch (e) {
    // ignore
  }
};
// 表格排序(前端排序:仅影响当前页数据,避免破坏后端分页协议)
const sortState = reactive({
  prop: "",
  order: "",
});
const handleSortChange = ({ prop, order }) => {
  sortState.prop = prop || "";
  sortState.order = order || "";
};
const displayTableData = computed(() => {
  const data = Array.isArray(tableData.value) ? [...tableData.value] : [];
  if (!sortState.prop || !sortState.order) return data;
  const prop = sortState.prop;
  const direction = sortState.order === "ascending" ? 1 : -1;
  const numFields = new Set(["price", "cost", "consumption"]);
  return data.sort((a, b) => {
    const av = a?.[prop];
    const bv = b?.[prop];
    if (numFields.has(prop)) {
      const an = Number.parseFloat(av);
      const bn = Number.parseFloat(bv);
      const aNum = Number.isFinite(an) ? an : -Infinity;
      const bNum = Number.isFinite(bn) ? bn : -Infinity;
      return (aNum - bNum) * direction;
    }
  };
  // 表格排序(前端排序:仅影响当前页数据,避免破坏后端分页协议)
  const sortState = reactive({
    prop: "",
    order: "",
    return (
      String(av ?? "").localeCompare(String(bv ?? ""), "zh-Hans-CN") * direction
    );
  });
});
  const handleSortChange = ({ prop, order }) => {
    sortState.prop = prop || "";
    sortState.order = order || "";
  };
const energyTypeFilters = [
  { text: "水", value: "水" },
  { text: "电", value: "电" },
  { text: "气", value: "气" },
];
const energyPurposeFilters = [
  { text: "生产", value: "生产" },
  { text: "办公", value: "办公" },
];
  const displayTableData = computed(() => {
    const data = Array.isArray(tableData.value) ? [...tableData.value] : [];
    if (!sortState.prop || !sortState.order) return data;
const filterEnergyType = (value, row) => row.energyType === value;
const filterEnergyPurpose = (value, row) => row.type === value;
    const prop = sortState.prop;
    const direction = sortState.order === "ascending" ? 1 : -1;
    const numFields = new Set(["price", "cost", "consumption"]);
// 分页
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
});
    return data.sort((a, b) => {
      const av = a?.[prop];
      const bv = b?.[prop];
// 图表引用
const costChart = ref(null);
const typeChart = ref(null);
const purposeChart = ref(null);
const priceChart = ref(null);
      if (numFields.has(prop)) {
        const an = Number.parseFloat(av);
        const bn = Number.parseFloat(bv);
        const aNum = Number.isFinite(an) ? an : -Infinity;
        const bNum = Number.isFinite(bn) ? bn : -Infinity;
        return (aNum - bNum) * direction;
      }
const costChartWrap = ref(null);
const typeChartWrap = ref(null);
const purposeChartWrap = ref(null);
const priceChartWrap = ref(null);
      return String(av ?? "").localeCompare(String(bv ?? ""), "zh-Hans-CN") * direction;
    });
const tableAnchor = ref(null);
const bigChartVisible = ref(false);
const bigChartKey = ref("cost");
const bigChartTitle = ref("");
const bigChartEl = ref(null);
let bigChartInstance = null;
watch(bigChartVisible, (v) => {
  if (v) window.addEventListener("resize", handleBigChartResize);
  else window.removeEventListener("resize", handleBigChartResize);
});
onUnmounted(() => {
  window.removeEventListener("resize", handleBigChartResize);
  try {
    bigChartInstance?.dispose?.();
  } catch (e) {
    // ignore
  }
});
// 图表实例
let costChartInstance = null;
let typeChartInstance = null;
let purposeChartInstance = null;
let priceChartInstance = 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();
    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();
  }
};
const resizeChartsAfterExpand = () => {
  nextTick(() => {
    ensureChartsReady(chartPanel.value === "none" ? "core" : chartPanel.value);
    handleResize();
    updateCharts();
  });
};
  const energyTypeFilters = [
    { text: "水", value: "水" },
    { text: "电", value: "电" },
    { text: "气", value: "气" },
  ];
  const energyPurposeFilters = [
    { text: "生产", value: "生产" },
    { text: "办公", value: "办公" },
  ];
const handleChartPanelClick = (key) => {
  chartPanel.value = chartPanel.value === key ? "none" : key;
};
  const filterEnergyType = (value, row) => row.energyType === value;
  const filterEnergyPurpose = (value, row) => row.type === value;
const panelIndicatorStyle = computed(() => {
  const x = chartPanel.value === "advanced" ? "calc(100% + 4px)" : "0";
  return { transform: `translateX(${x})` };
});
  // 分页
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
watch(chartPanel, (val) => {
  if (val !== "none") resizeChartsAfterExpand();
});
  // 图表引用
  const costChart = ref(null);
  const typeChart = ref(null);
  const purposeChart = ref(null);
  const priceChart = ref(null);
  const costChartWrap = ref(null);
  const typeChartWrap = ref(null);
  const purposeChartWrap = ref(null);
  const priceChartWrap = ref(null);
  const tableAnchor = ref(null);
  const bigChartVisible = ref(false);
  const bigChartKey = ref("cost");
  const bigChartTitle = ref("");
  const bigChartEl = ref(null);
  let bigChartInstance = null;
  watch(bigChartVisible, v => {
    if (v) window.addEventListener("resize", handleBigChartResize);
    else window.removeEventListener("resize", handleBigChartResize);
  });
  onUnmounted(() => {
    window.removeEventListener("resize", handleBigChartResize);
    try {
      bigChartInstance?.dispose?.();
    } catch (e) {
      // ignore
    }
  });
  // 图表实例
  let costChartInstance = null;
  let typeChartInstance = null;
  let purposeChartInstance = null;
  let priceChartInstance = 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();
      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();
    }
// 获取能耗类型标签类型
const getEnergyTypeType = (type) => {
  const typeMap = {
    水: "primary",
    电: "warning",
    气: "success",
  };
  return typeMap[type] || "info";
};
  const resizeChartsAfterExpand = () => {
    nextTick(() => {
      ensureChartsReady(chartPanel.value === "none" ? "core" : chartPanel.value);
      handleResize();
      updateCharts();
    });
  };
  const handleChartPanelClick = key => {
    chartPanel.value = chartPanel.value === key ? "none" : key;
  };
  const panelIndicatorStyle = computed(() => {
    const x = chartPanel.value === "advanced" ? "calc(100% + 4px)" : "0";
    return { transform: `translateX(${x})` };
// 初始化图表
const initCharts = () => {
  nextTick(() => {
    // 只初始化可见面板,避免隐藏容器初始化为 0 尺寸导致空白
    ensureChartsReady(chartPanel.value === "none" ? "core" : chartPanel.value);
  });
};
  watch(chartPanel, val => {
    if (val !== "none") resizeChartsAfterExpand();
  });
  // 获取能耗类型标签类型
  const getEnergyTypeType = type => {
    const typeMap = {
      水: "primary",
      电: "warning",
      气: "success",
    };
    return typeMap[type] || "info";
  };
  // 初始化图表
  const initCharts = () => {
    nextTick(() => {
      // 只初始化可见面板,避免隐藏容器初始化为 0 尺寸导致空白
      ensureChartsReady(chartPanel.value === "none" ? "core" : chartPanel.value);
    });
  };
  // 更新能耗成本趋势图
  const updateCostChart = () => {
    const data = tableData.value;
    const option = {
      tooltip: {
        trigger: "axis",
        axisPointer: { type: "shadow" },
        backgroundColor: "rgba(255, 255, 255, 0.96)",
        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;",
// 更新能耗成本趋势图
const updateCostChart = () => {
  const data = tableData.value;
  const option = {
    tooltip: {
      trigger: "axis",
      axisPointer: { type: "shadow" },
      backgroundColor: "rgba(255, 255, 255, 0.96)",
      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;",
    },
    legend: {
      data: ["生产能耗成本", "办公能耗成本"],
      top: 0,
      right: 10,
      textStyle: { color: "#606266" },
    },
    grid: {
      left: "3%",
      right: "4%",
      bottom: "10%",
      top: "15%",
      containLabel: true,
    },
    xAxis: {
      type: "category",
      data: data.map((item) => item.timePeriod),
      axisLabel: {
        rotate: statisticsType.value === "day" ? 45 : 0,
        color: "rgba(15, 23, 42, 0.62)",
      },
      legend: {
        data: ["生产能耗成本", "办公能耗成本"],
        top: 0,
        right: 10,
        textStyle: { color: "#606266" },
      axisLine: { lineStyle: { color: "rgba(15, 23, 42, 0.08)" } },
      splitLine: { show: false },
    },
    yAxis: {
      type: "value",
      name: "成本(元)",
      nameTextStyle: { color: "rgba(15, 23, 42, 0.58)" },
      axisLabel: { color: "rgba(15, 23, 42, 0.58)" },
      axisLine: { show: false },
      splitLine: { lineStyle: { color: "rgba(15, 23, 42, 0.06)" } },
    },
    series: [
      {
        name: "生产能耗成本",
        type: "bar",
        data: data.map((item) => (item.type === "生产" ? item.cost : 0)),
        itemStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: "#409EFF" },
            { offset: 1, color: "#66b1ff" },
          ]),
          borderRadius: [4, 4, 0, 0],
        },
        animationDelay: function (idx) {
          return idx * 100;
        },
      },
      grid: {
        left: "3%",
        right: "4%",
        bottom: "10%",
        top: "15%",
        containLabel: true,
      {
        name: "办公能耗成本",
        type: "bar",
        data: data.map((item) => (item.type === "办公" ? item.cost : 0)),
        itemStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: "#67C23A" },
            { offset: 1, color: "#85ce61" },
          ]),
          borderRadius: [4, 4, 0, 0],
        },
        animationDelay: function (idx) {
          return idx * 100 + 100;
        },
      },
      xAxis: {
        type: "category",
        data: data.map(item => item.timePeriod),
        axisLabel: {
          rotate: statisticsType.value === "day" ? 45 : 0,
    ],
    animationEasing: "elasticOut",
    animationDelayUpdate: function (idx) {
      return idx * 5;
    },
  };
  costChartInstance.setOption(option);
};
// 更新能耗类型成本占比图
const updateTypeChart = () => {
  const data = tableData.value;
  const typeCosts = {};
  data.forEach((item) => {
    if (!typeCosts[item.energyType]) {
      typeCosts[item.energyType] = 0;
    }
    typeCosts[item.energyType] += parseFloat(item.cost);
  });
  const chartData = Object.entries(typeCosts).map(([name, value]) => ({
    name,
    value: value.toFixed(2),
  }));
  const option = {
    tooltip: {
      trigger: "item",
      formatter: "{a} <br/>{b}: ¥{c} ({d}%)",
      backgroundColor: "rgba(255, 255, 255, 0.96)",
      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;",
    },
    legend: {
      orient: "horizontal",
      bottom: 0,
      textStyle: { color: "rgba(15, 23, 42, 0.62)" },
    },
    series: [
      {
        name: "能耗类型成本",
        type: "pie",
        radius: ["40%", "70%"],
        center: ["50%", "40%"],
        avoidLabelOverlap: false,
        itemStyle: {
          borderRadius: 4,
          borderColor: "#fff",
          borderWidth: 2,
        },
        label: {
          show: false,
          position: "center",
        },
        emphasis: {
          label: {
            show: true,
            fontSize: "18",
            fontWeight: "bold",
            color: "#303133",
          },
          itemStyle: {
            shadowBlur: 10,
            shadowOffsetX: 0,
            shadowColor: "rgba(0, 0, 0, 0.3)",
          },
        },
        labelLine: {
          show: false,
        },
        data: chartData,
      },
    ],
    color: ["#2f6fed", "#16a34a", "#f59e0b"],
  };
  typeChartInstance.setOption(option);
};
// 更新能耗用途成本占比图
const updatePurposeChart = () => {
  const data = tableData.value;
  const purposeCosts = {
    生产: 0,
    办公: 0,
  };
  data.forEach((item) => {
    if (purposeCosts.hasOwnProperty(item.type)) {
      purposeCosts[item.type] += parseFloat(item.cost);
    }
  });
  const chartData = Object.entries(purposeCosts).map(([name, value]) => ({
    name,
    value: value.toFixed(2),
  }));
  const option = {
    tooltip: {
      trigger: "item",
      formatter: "{a} <br/>{b}: ¥{c} ({d}%)",
      backgroundColor: "rgba(255, 255, 255, 0.96)",
      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;",
    },
    legend: {
      orient: "horizontal",
      bottom: 0,
      textStyle: { color: "rgba(15, 23, 42, 0.62)" },
    },
    series: [
      {
        name: "能耗用途成本",
        type: "pie",
        radius: "60%",
        center: ["50%", "40%"],
        data: chartData,
        emphasis: {
          itemStyle: {
            shadowBlur: 10,
            shadowOffsetX: 0,
            shadowColor: "rgba(0, 0, 0, 0.3)",
          },
        },
        label: {
          show: true,
          formatter: "{b}: {d}%",
          color: "rgba(15, 23, 42, 0.62)",
        },
        axisLine: { lineStyle: { color: "rgba(15, 23, 42, 0.08)" } },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
        name: "成本(元)",
        nameTextStyle: { color: "rgba(15, 23, 42, 0.58)" },
        axisLabel: { color: "rgba(15, 23, 42, 0.58)" },
        axisLine: { show: false },
        splitLine: { lineStyle: { color: "rgba(15, 23, 42, 0.06)" } },
      },
      series: [
        {
          name: "生产能耗成本",
          type: "bar",
          data: data.map(item => (item.type === "生产" ? item.cost : 0)),
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#409EFF" },
              { offset: 1, color: "#66b1ff" },
            ]),
            borderRadius: [4, 4, 0, 0],
          },
          animationDelay: function (idx) {
            return idx * 100;
          },
        labelLine: {
          show: true,
          lineStyle: { color: "rgba(15, 23, 42, 0.10)" },
        },
        {
          name: "办公能耗成本",
          type: "bar",
          data: data.map(item => (item.type === "办公" ? item.cost : 0)),
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#67C23A" },
              { offset: 1, color: "#85ce61" },
            ]),
            borderRadius: [4, 4, 0, 0],
          },
          animationDelay: function (idx) {
            return idx * 100 + 100;
          },
        },
      ],
      animationEasing: "elasticOut",
      animationDelayUpdate: function (idx) {
        return idx * 5;
      },
    };
    costChartInstance.setOption(option);
    ],
    color: ["#2f6fed", "#16a34a"],
  };
  purposeChartInstance.setOption(option);
};
  // 更新能耗类型成本占比图
  const updateTypeChart = () => {
    const data = tableData.value;
    const typeCosts = {};
// 更新能耗单价对比图
const updatePriceChart = () => {
  const data = tableData.value;
  const priceData = {};
    data.forEach(item => {
      if (!typeCosts[item.energyType]) {
        typeCosts[item.energyType] = 0;
      }
      typeCosts[item.energyType] += parseFloat(item.cost);
    });
    const chartData = Object.entries(typeCosts).map(([name, value]) => ({
      name,
      value: value.toFixed(2),
    }));
    const option = {
      tooltip: {
        trigger: "item",
        formatter: "{a} <br/>{b}: ¥{c} ({d}%)",
        backgroundColor: "rgba(255, 255, 255, 0.96)",
        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;",
      },
      legend: {
        orient: "horizontal",
        bottom: 0,
        textStyle: { color: "rgba(15, 23, 42, 0.62)" },
      },
      series: [
        {
          name: "能耗类型成本",
          type: "pie",
          radius: ["40%", "70%"],
          center: ["50%", "40%"],
          avoidLabelOverlap: false,
          itemStyle: {
            borderRadius: 4,
            borderColor: "#fff",
            borderWidth: 2,
          },
          label: {
            show: false,
            position: "center",
          },
          emphasis: {
            label: {
              show: true,
              fontSize: "18",
              fontWeight: "bold",
              color: "#303133",
            },
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: "rgba(0, 0, 0, 0.3)",
            },
          },
          labelLine: {
            show: false,
          },
          data: chartData,
        },
      ],
      color: ["#2f6fed", "#16a34a", "#f59e0b"],
    };
    typeChartInstance.setOption(option);
  };
  // 更新能耗用途成本占比图
  const updatePurposeChart = () => {
    const data = tableData.value;
    const purposeCosts = {
      生产: 0,
      办公: 0,
    };
    data.forEach(item => {
      if (purposeCosts.hasOwnProperty(item.type)) {
        purposeCosts[item.type] += parseFloat(item.cost);
      }
    });
    const chartData = Object.entries(purposeCosts).map(([name, value]) => ({
      name,
      value: value.toFixed(2),
    }));
    const option = {
      tooltip: {
        trigger: "item",
        formatter: "{a} <br/>{b}: ¥{c} ({d}%)",
        backgroundColor: "rgba(255, 255, 255, 0.96)",
        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;",
      },
      legend: {
        orient: "horizontal",
        bottom: 0,
        textStyle: { color: "rgba(15, 23, 42, 0.62)" },
      },
      series: [
        {
          name: "能耗用途成本",
          type: "pie",
          radius: "60%",
          center: ["50%", "40%"],
          data: chartData,
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: "rgba(0, 0, 0, 0.3)",
            },
          },
          label: {
            show: true,
            formatter: "{b}: {d}%",
            color: "rgba(15, 23, 42, 0.62)",
          },
          labelLine: {
            show: true,
            lineStyle: { color: "rgba(15, 23, 42, 0.10)" },
          },
        },
      ],
      color: ["#2f6fed", "#16a34a"],
    };
    purposeChartInstance.setOption(option);
  };
  // 更新能耗单价对比图
  const updatePriceChart = () => {
    const data = tableData.value;
    const priceData = {};
    data.forEach(item => {
      if (!priceData[item.energyType]) {
        priceData[item.energyType] = {
          生产: 0,
          办公: 0,
        };
      }
      if (priceData[item.energyType].hasOwnProperty(item.type)) {
        priceData[item.energyType][item.type] = parseFloat(item.price);
      }
    });
    const energyTypes = Object.keys(priceData);
    const productionPrices = energyTypes.map(type => priceData[type].生产);
    const officePrices = energyTypes.map(type => priceData[type].办公);
    const option = {
      tooltip: {
        trigger: "axis",
        axisPointer: { type: "shadow" },
        backgroundColor: "rgba(255, 255, 255, 0.96)",
        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;",
      },
      legend: {
        data: ["生产能耗单价", "办公能耗单价"],
        top: 0,
        right: 10,
        textStyle: { color: "rgba(15, 23, 42, 0.62)" },
      },
      grid: {
        left: "3%",
        right: "4%",
        bottom: "10%",
        top: "15%",
        containLabel: true,
      },
      xAxis: {
        type: "category",
        data: energyTypes,
        axisLabel: { color: "rgba(15, 23, 42, 0.62)" },
        axisLine: { lineStyle: { color: "rgba(15, 23, 42, 0.08)" } },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
        name: "单价(元)",
        nameTextStyle: { color: "rgba(15, 23, 42, 0.58)" },
        axisLabel: { color: "rgba(15, 23, 42, 0.58)" },
        axisLine: { show: false },
        splitLine: { lineStyle: { color: "rgba(15, 23, 42, 0.06)" } },
      },
      series: [
        {
          name: "生产能耗单价",
          type: "bar",
          data: productionPrices,
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#2f6fed" },
              { offset: 1, color: "#5b8cff" },
            ]),
            borderRadius: [4, 4, 0, 0],
          },
        },
        {
          name: "办公能耗单价",
          type: "bar",
          data: officePrices,
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#16a34a" },
              { offset: 1, color: "rgba(22, 163, 74, 0.65)" },
            ]),
            borderRadius: [4, 4, 0, 0],
          },
        },
      ],
    };
    priceChartInstance.setOption(option);
  };
  // 统计维度切换
  const handleTypeChange = () => {
    // 重置时间范围
    if (statisticsType.value === "day") {
      const end = new Date();
      const start = new Date();
      start.setDate(start.getDate() - 6);
      searchForm.dateRange = [
        start.toISOString().split("T")[0],
        end.toISOString().split("T")[0],
      ];
    } else {
      const end = new Date();
      const start = new Date();
      start.setMonth(start.getMonth() - 2);
      searchForm.monthRange = [
        start.toISOString().slice(0, 7),
        end.toISOString().slice(0, 7),
      ];
  data.forEach((item) => {
    if (!priceData[item.energyType]) {
      priceData[item.energyType] = {
        生产: 0,
        办公: 0,
      };
    }
    page.current = 1;
    handleQuery();
    if (priceData[item.energyType].hasOwnProperty(item.type)) {
      priceData[item.energyType][item.type] = parseFloat(item.price);
    }
  });
  const energyTypes = Object.keys(priceData);
  const productionPrices = energyTypes.map((type) => priceData[type].生产);
  const officePrices = energyTypes.map((type) => priceData[type].办公);
  const option = {
    tooltip: {
      trigger: "axis",
      axisPointer: { type: "shadow" },
      backgroundColor: "rgba(255, 255, 255, 0.96)",
      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;",
    },
    legend: {
      data: ["生产能耗单价", "办公能耗单价"],
      top: 0,
      right: 10,
      textStyle: { color: "rgba(15, 23, 42, 0.62)" },
    },
    grid: {
      left: "3%",
      right: "4%",
      bottom: "10%",
      top: "15%",
      containLabel: true,
    },
    xAxis: {
      type: "category",
      data: energyTypes,
      axisLabel: { color: "rgba(15, 23, 42, 0.62)" },
      axisLine: { lineStyle: { color: "rgba(15, 23, 42, 0.08)" } },
      splitLine: { show: false },
    },
    yAxis: {
      type: "value",
      name: "单价(元)",
      nameTextStyle: { color: "rgba(15, 23, 42, 0.58)" },
      axisLabel: { color: "rgba(15, 23, 42, 0.58)" },
      axisLine: { show: false },
      splitLine: { lineStyle: { color: "rgba(15, 23, 42, 0.06)" } },
    },
    series: [
      {
        name: "生产能耗单价",
        type: "bar",
        data: productionPrices,
        itemStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: "#2f6fed" },
            { offset: 1, color: "#5b8cff" },
          ]),
          borderRadius: [4, 4, 0, 0],
        },
      },
      {
        name: "办公能耗单价",
        type: "bar",
        data: officePrices,
        itemStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: "#16a34a" },
            { offset: 1, color: "rgba(22, 163, 74, 0.65)" },
          ]),
          borderRadius: [4, 4, 0, 0],
        },
      },
    ],
  };
  priceChartInstance.setOption(option);
};
// 统计维度切换
const handleTypeChange = () => {
  // 重置时间范围
  if (statisticsType.value === "day") {
    const end = new Date();
    const start = new Date();
    start.setDate(start.getDate() - 6);
    searchForm.dateRange = [
      start.toISOString().split("T")[0],
      end.toISOString().split("T")[0],
    ];
  } else {
    const end = new Date();
    const start = new Date();
    start.setMonth(start.getMonth() - 2);
    searchForm.monthRange = [
      start.toISOString().slice(0, 7),
      end.toISOString().slice(0, 7),
    ];
  }
  page.current = 1;
  handleQuery();
};
// 查询
const handleQuery = () => {
  queryPulse.value = true;
  window.setTimeout(() => {
    queryPulse.value = false;
  }, 520);
  tableLoading.value = true;
  // 构造请求参数
  const params = {
    days: 0,
    // energyType: searchForm.energyType || undefined,
    type: searchForm.type || undefined,
    pageNum: page.current,
    pageSize: page.size,
  };
  // 查询
  const handleQuery = () => {
    queryPulse.value = true;
    window.setTimeout(() => {
      queryPulse.value = false;
    }, 520);
    tableLoading.value = true;
  if (statisticsType.value === "day") {
    if (searchForm.dateRange && searchForm.dateRange.length === 2) {
      params.startDate = searchForm.dateRange[0];
      params.endDate = searchForm.dateRange[1];
    }
  } else {
    if (searchForm.monthRange && searchForm.monthRange.length === 2) {
      params.startDate = searchForm.monthRange[0] + "-01";
    // 构造请求参数
    const params = {
      days: 0,
      // energyType: searchForm.energyType || undefined,
      type: searchForm.type || undefined,
      // 项目内常用分页参数命名
      pageNum: page.current,
      pageSize: page.size,
    };
    if (statisticsType.value === "day") {
      if (searchForm.dateRange && searchForm.dateRange.length === 2) {
        params.startDate = searchForm.dateRange[0];
        params.endDate = searchForm.dateRange[1];
      }
    } else {
      if (searchForm.monthRange && searchForm.monthRange.length === 2) {
        params.startDate = searchForm.monthRange[0] + "-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";
        }
      // 结束时间需要取结束月份的最后一天(例如 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";
      }
    }
  }
    // 计算开始到结束的天数(包含起止两天)
    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;
      }
  // 计算开始到结束的天数(包含起止两天)
  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)
      .then(res => {
        if (res.code === 200) {
          tableData.value = res.data.records || [];
          page.total = res.data.total || 0;
  // 调用接口获取数据
  energyConsumptionDetailStatistics(params)
    .then((res) => {
      if (res.code === 200) {
        const data = res.data;
        overview.totalConsumption = data.totalEnergyConsumption || "0";
        overview.totalAmount = data.totalEnergyCost || "0";
        overview.avgConsumption = data.averageConsumption || "0";
        overview.compareRate = data.changeVite || 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";
          }
        } else {
          ElMessage.error(res.message || "获取数据失败");
          tableData.value = [];
          page.total = 0;
          overview.totalCost = "0.00";
          overview.productionCost = "0.00";
          overview.officeCost = "0.00";
          overview.avgCost = "0.00";
        }
      })
      .catch(err => {
        console.error("获取数据异常:", err);
        // 【假数据(Mock)已禁用】接口异常时不再生成随机假数据,避免误用到生产数据链路
        ElMessage.error("获取数据异常");
        // 处理表格数据
        tableData.value = data.energyCostDtos || [];
        page.total = tableData.value.length || 0;
      } else {
        ElMessage.error(res.message || "获取数据失败");
        tableData.value = [];
        page.total = 0;
        overview.totalCost = "0.00";
        overview.productionCost = "0.00";
        overview.officeCost = "0.00";
        overview.avgCost = "0.00";
      })
      .finally(() => {
        tableLoading.value = false;
        updateCharts();
      });
  };
  // 【假数据(Mock)已禁用】历史上用于接口异常兜底的随机数据生成逻辑,现已整体注释,避免误用于生产。
  /*
  // 生成假数据
  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: "电",
          type: "生产",
          consumption: (Math.random() * 1000 + 500).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 850 + 425).toFixed(2),
        });
        mockData.push({
          timePeriod: dateStr,
          energyType: "水",
          type: "生产",
          consumption: (Math.random() * 500 + 200).toFixed(2),
          unit: "m³",
          price: "3.50",
          cost: (Math.random() * 1750 + 700).toFixed(2),
        });
        mockData.push({
          timePeriod: dateStr,
          energyType: "气",
          type: "生产",
          consumption: (Math.random() * 300 + 100).toFixed(2),
          unit: "m³",
          price: "2.80",
          cost: (Math.random() * 840 + 280).toFixed(2),
        });
        // 办公能耗数据
        mockData.push({
          timePeriod: dateStr,
          energyType: "电",
          type: "办公",
          consumption: (Math.random() * 200 + 100).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 170 + 85).toFixed(2),
        });
        mockData.push({
          timePeriod: dateStr,
          energyType: "水",
          type: "办公",
          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: "电",
          type: "生产",
          consumption: (Math.random() * 30000 + 15000).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 25500 + 12750).toFixed(2),
        });
        mockData.push({
          timePeriod: monthStr,
          energyType: "水",
          type: "生产",
          consumption: (Math.random() * 15000 + 6000).toFixed(2),
          unit: "m³",
          price: "3.50",
          cost: (Math.random() * 52500 + 21000).toFixed(2),
        });
        mockData.push({
          timePeriod: monthStr,
          energyType: "气",
          type: "生产",
          consumption: (Math.random() * 9000 + 3000).toFixed(2),
          unit: "m³",
          price: "2.80",
          cost: (Math.random() * 25200 + 8400).toFixed(2),
        });
        // 办公能耗数据
        mockData.push({
          timePeriod: monthStr,
          energyType: "电",
          type: "办公",
          consumption: (Math.random() * 6000 + 3000).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 5100 + 2550).toFixed(2),
        });
        mockData.push({
          timePeriod: monthStr,
          energyType: "水",
          type: "办公",
          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();
  };
  */
  // 【假数据(Mock)已禁用】与 generateMockData 配套的前端汇总计算(仅供假数据展示),现已注释
  /*
  // 计算统计概览数据
  const calculateOverview = () => {
    let totalCost = 0;
    let productionCost = 0;
    let officeCost = 0;
    tableData.value.forEach(item => {
      const cost = parseFloat(item.cost);
      totalCost += cost;
      if (item.type === "生产") {
        productionCost += cost;
      } else if (item.type === "办公") {
        officeCost += cost;
      }
    })
    .catch((err) => {
      console.error("获取数据异常:", err);
      // 【假数据(Mock)已禁用】接口异常时不再生成随机假数据,避免误用到生产数据链路
      ElMessage.error("获取数据异常");
      tableData.value = [];
      page.total = 0;
      overview.totalCost = "0.00";
      overview.productionCost = "0.00";
      overview.officeCost = "0.00";
      overview.avgCost = "0.00";
    })
    .finally(() => {
      tableLoading.value = false;
      updateCharts();
    });
};
    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(() => {
      if (costChartInstance) updateCostChart();
      if (typeChartInstance) updateTypeChart();
      if (purposeChartInstance) updatePurposeChart();
      if (priceChartInstance) updatePriceChart();
    });
  };
  // 重置
  const handleReset = () => {
    // searchForm.energyType = "";
    searchForm.type = "";
    if (statisticsType.value === "day") {
      const end = new Date();
      const start = new Date();
      start.setDate(start.getDate() - 6);
      searchForm.dateRange = [
        start.toISOString().split("T")[0],
        end.toISOString().split("T")[0],
      ];
    } else {
      const end = new Date();
      const start = new Date();
      start.setMonth(start.getMonth() - 2);
      searchForm.monthRange = [
        start.toISOString().slice(0, 7),
        end.toISOString().slice(0, 7),
      ];
    }
    page.current = 1;
    handleQuery();
  };
  // 导出
  const handleExport = () => {
    ElMessage.success("报表导出成功");
  };
  // 分页大小变化
  const handleSizeChange = val => {
    page.size = val;
    page.current = 1;
    handleQuery();
  };
  // 页码变化
  const handleCurrentChange = val => {
    page.current = val;
    handleQuery();
  };
  // 窗口大小变化时重新渲染图表
  const handleResize = () => {
    costChartInstance && costChartInstance.resize();
    typeChartInstance && typeChartInstance.resize();
    purposeChartInstance && purposeChartInstance.resize();
    priceChartInstance && priceChartInstance.resize();
  };
  onMounted(() => {
    handleQuery();
    initCharts();
    window.addEventListener("resize", handleResize);
// 更新所有图表
const updateCharts = () => {
  nextTick(() => {
    if (costChartInstance) updateCostChart();
    if (typeChartInstance) updateTypeChart();
    if (purposeChartInstance) updatePurposeChart();
    if (priceChartInstance) updatePriceChart();
  });
};
  const handleGlobalHotkeys = e => {
    // 避免在输入框内误触
    const target = e?.target;
    const tag = target?.tagName?.toLowerCase?.();
    const isTyping =
      tag === "input" ||
      tag === "textarea" ||
      target?.isContentEditable ||
      target?.classList?.contains?.("el-input__inner");
    if (isTyping) return;
// 重置
const handleReset = () => {
  // searchForm.energyType = "";
  searchForm.type = "";
  if (statisticsType.value === "day") {
    const end = new Date();
    const start = new Date();
    start.setDate(start.getDate() - 6);
    searchForm.dateRange = [
      start.toISOString().split("T")[0],
      end.toISOString().split("T")[0],
    ];
  } else {
    const end = new Date();
    const start = new Date();
    start.setMonth(start.getMonth() - 2);
    searchForm.monthRange = [
      start.toISOString().slice(0, 7),
      end.toISOString().slice(0, 7),
    ];
  }
  page.current = 1;
  handleQuery();
};
    // Enter: 刷新查询
    if (e.key === "Enter") {
      e.preventDefault();
      handleQuery();
      return;
    }
// 导出
const handleExport = () => {
  ElMessage.success("报表导出成功");
};
    // Esc: 重置
    if (e.key === "Escape") {
      e.preventDefault();
      handleReset();
      return;
    }
// 分页大小变化
const handleSizeChange = (val) => {
  page.size = val;
  page.current = 1;
  handleQuery();
};
    // Alt+E: 导出
    if (e.altKey && (e.key === "e" || e.key === "E")) {
      e.preventDefault();
      handleExport();
    }
  };
// 页码变化
const handleCurrentChange = (val) => {
  page.current = val;
  handleQuery();
};
  onMounted(() => {
    window.addEventListener("keydown", handleGlobalHotkeys);
  });
// 窗口大小变化时重新渲染图表
const handleResize = () => {
  costChartInstance && costChartInstance.resize();
  typeChartInstance && typeChartInstance.resize();
  purposeChartInstance && purposeChartInstance.resize();
  priceChartInstance && priceChartInstance.resize();
};
  onUnmounted(() => {
    window.removeEventListener("keydown", handleGlobalHotkeys);
  });
onMounted(() => {
  handleQuery();
  initCharts();
  window.addEventListener("resize", handleResize);
});
const handleGlobalHotkeys = (e) => {
  // 避免在输入框内误触
  const target = e?.target;
  const tag = target?.tagName?.toLowerCase?.();
  const isTyping =
    tag === "input" ||
    tag === "textarea" ||
    target?.isContentEditable ||
    target?.classList?.contains?.("el-input__inner");
  if (isTyping) return;
  // Enter: 刷新查询
  if (e.key === "Enter") {
    e.preventDefault();
    handleQuery();
    return;
  }
  // Esc: 重置
  if (e.key === "Escape") {
    e.preventDefault();
    handleReset();
    return;
  }
  // Alt+E: 导出
  if (e.altKey && (e.key === "e" || e.key === "E")) {
    e.preventDefault();
    handleExport();
  }
};
onMounted(() => {
  window.addEventListener("keydown", handleGlobalHotkeys);
});
onUnmounted(() => {
  window.removeEventListener("keydown", handleGlobalHotkeys);
});
</script>
<style scoped lang="scss">
  .energy-cost-page {
    --lux-bg: #f6f7fb;
    --lux-card: rgba(255, 255, 255, 0.86);
    --lux-card-solid: #ffffff;
    --lux-border: rgba(15, 23, 42, 0.08);
    --lux-text: rgba(15, 23, 42, 0.92);
    --lux-subtle: rgba(15, 23, 42, 0.58);
    --lux-muted: rgba(15, 23, 42, 0.38);
    --lux-primary: #2f6fed;
    --lux-primary-2: #5b8cff;
    --lux-success: #16a34a;
    --lux-warning: #f59e0b;
    --lux-danger: #ef4444;
    --lux-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
    --lux-shadow-soft: 0 10px 28px rgba(15, 23, 42, 0.06);
    --lux-radius: 14px;
    --lux-radius-sm: 12px;
.energy-cost-page {
  --lux-bg: #f6f7fb;
  --lux-card: rgba(255, 255, 255, 0.86);
  --lux-card-solid: #ffffff;
  --lux-border: rgba(15, 23, 42, 0.08);
  --lux-text: rgba(15, 23, 42, 0.92);
  --lux-subtle: rgba(15, 23, 42, 0.58);
  --lux-muted: rgba(15, 23, 42, 0.38);
  --lux-primary: #2f6fed;
  --lux-primary-2: #5b8cff;
  --lux-success: #16a34a;
  --lux-warning: #f59e0b;
  --lux-danger: #ef4444;
  --lux-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
  --lux-shadow-soft: 0 10px 28px rgba(15, 23, 42, 0.06);
  --lux-radius: 14px;
  --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%),
      linear-gradient(180deg, var(--lux-bg) 0%, #ffffff 58%);
  padding: 18px 22px 24px;
  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%);
}
.filter-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: nowrap;
  margin-top: 0;
  padding-top: 0;
  border-top: none;
  justify-content: flex-end;
  flex: 0 0 auto;
  white-space: nowrap;
  align-self: flex-start;
  padding-bottom: 0;
  width: 290px;
}
.filter-actions :deep(.el-button) {
  min-width: 78px;
}
.filter-actions :deep(.el-button.is-loading) {
  min-width: 90px;
}
.filter-layout {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}
.filter-form {
  flex: 1 1 auto;
  min-width: 0;
}
.w-260 {
  width: 260px;
  max-width: 100%;
}
.lux-btn {
  transition: transform 0.18s ease, box-shadow 0.18s ease, filter 0.18s ease;
  &:hover {
    transform: translateY(-1px);
    box-shadow: 0 10px 22px rgba(15, 23, 42, 0.1);
    filter: saturate(1.02);
  }
  &:active {
    transform: translateY(0);
    box-shadow: none;
  }
}
.filter-card {
  margin-bottom: 16px;
  border-radius: var(--lux-radius);
  border-color: var(--lux-border);
  background: var(--lux-card);
  backdrop-filter: blur(10px);
  box-shadow: var(--lux-shadow-soft);
}
/* 查询区控件统一皮肤 */
:deep(.filter-card .el-form-item__label) {
  color: rgba(15, 23, 42, 0.7);
  font-weight: 650;
}
:deep(.filter-card .el-input__wrapper),
:deep(.filter-card .el-select__wrapper) {
  border-radius: 12px;
  box-shadow: none;
  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;
}
:deep(.filter-card .el-input__wrapper:hover),
:deep(.filter-card .el-select__wrapper:hover) {
  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.3);
  box-shadow: 0 0 0 3px rgba(47, 111, 237, 0.14);
}
:deep(.filter-card .el-range-editor.el-input__wrapper) {
  border-radius: 12px;
}
.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.card-head-left {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 200px;
}
.card-head-right {
  display: flex;
  align-items: center;
  gap: 10px;
}
.card-icon {
  color: var(--lux-primary);
}
.card-title {
  font-weight: 760;
  color: var(--lux-text);
}
.subtle {
  color: var(--lux-subtle);
  font-size: 12px;
}
.filter-form {
  display: flex;
  flex-wrap: nowrap;
  gap: 10px 14px;
  align-items: flex-end;
  min-width: 0;
}
.filter-form :deep(.el-form-item) {
  margin-right: 0;
  margin-bottom: 0;
  flex: 0 0 auto;
}
.filter-form :deep(.el-form-item__content) {
  min-width: 0;
}
.filter-form :deep(.el-form-item:last-child) {
  flex: 1 1 auto;
}
.filter-form :deep(.el-form-item:last-child .el-form-item__content) {
  width: 100%;
}
.w-140 {
  width: 140px;
}
.w-260 {
  width: 260px;
  max-width: 100%;
}
@media (max-width: 1280px) {
  .filter-form {
    flex-wrap: wrap;
    align-items: flex-start;
  }
}
.section-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 10px 0 12px;
  font-size: 14px;
  font-weight: 700;
  color: var(--lux-text);
}
.section-icon {
  color: var(--lux-primary);
}
.metrics {
  margin-bottom: 10px;
}
.ui-icon {
  font-size: 16px;
  transition: transform 0.18s ease, opacity 0.18s ease;
}
.card-head-left:hover .ui-icon,
.section-title:hover .ui-icon {
  transform: translateY(-1px);
}
.metric-card {
  border-radius: var(--lux-radius-sm);
  padding: 14px 14px 14px 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border: 1px solid var(--lux-border);
  background: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(10px);
  min-height: 78px;
  transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
  &:hover {
    transform: translateY(-1px);
    box-shadow: var(--lux-shadow);
    border-color: rgba(47, 111, 237, 0.18);
  }
}
.metric-left {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.metric-label {
  color: var(--lux-subtle);
  font-size: 12px;
}
.metric-value {
  color: var(--lux-text);
  font-size: 20px;
  font-weight: 800;
  letter-spacing: 0.2px;
}
.metric-unit {
  font-size: 12px;
  font-weight: 500;
  color: var(--lux-muted);
  margin-left: 4px;
}
.metric-right {
  width: 42px;
  height: 42px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.metric-icon {
  font-size: 20px;
  color: #fff;
}
.metric-total {
  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)
    );
  }
}
.metric-production {
  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)
    );
  }
}
.metric-office {
  background: linear-gradient(
    135deg,
    rgba(144, 147, 153, 0.14),
    rgba(144, 147, 153, 0.03)
  );
  .metric-right {
    background: linear-gradient(135deg, #909399, #b1b3b8);
  }
}
.metric-avg {
  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)
    );
  }
}
.charts {
  margin-top: 6px;
  margin-bottom: 12px;
}
.charts-row {
  margin-top: 16px;
}
.kpi-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  padding: 4px 4px 10px;
}
.kpi-strip.pulse {
  animation: kpiPulse 520ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes kpiPulse {
  0% {
    filter: saturate(1.02);
  }
  35% {
    filter: saturate(1.1);
  }
  100% {
    filter: saturate(1.02);
  }
}
.kpi-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 12px 12px 14px;
  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;
  min-height: 68px;
  text-align: left;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  outline: none;
  transform: translateZ(0);
}
.kpi-item:hover {
  transform: translateY(-1px);
  box-shadow: 0 16px 40px rgba(15, 23, 42, 0.1);
  border-color: rgba(47, 111, 237, 0.18);
}
.kpi-item::before {
  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.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);
  pointer-events: none;
}
.kpi-item:hover::before {
  opacity: 1;
  transform: translateX(0) translateY(0);
}
.kpi-item::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 15px;
  background: linear-gradient(
    135deg,
    rgba(47, 111, 237, 0.18),
    rgba(255, 255, 255, 0),
    rgba(22, 163, 74, 0.14)
  );
  opacity: 0;
  transition: opacity 0.22s ease;
  pointer-events: none;
}
.kpi-item:hover::after {
  opacity: 1;
}
.kpi-item:active {
  transform: translateY(0);
  box-shadow: 0 10px 22px rgba(15, 23, 42, 0.08);
}
.kpi-item:focus-visible {
  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.1),
    inset 0 0 0 1px rgba(47, 111, 237, 0.1);
}
.kpi-left {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  position: relative;
  z-index: 1;
}
.kpi-label {
  font-size: 12px;
  color: var(--lux-subtle);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.kpi-value {
  font-size: 18px;
  font-weight: 850;
  letter-spacing: 0.2px;
  color: var(--lux-text);
  line-height: 1.1;
}
.kpi-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
  min-height: 22px;
}
.kpi-meta.muted {
  font-size: 11px;
  color: var(--lux-muted);
}
.kpi-chip {
  font-size: 11px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid rgba(15, 23, 42, 0.08);
  background: rgba(255, 255, 255, 0.72);
  color: rgba(15, 23, 42, 0.72);
}
.kpi-chip.up {
  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.2);
  color: rgba(239, 68, 68, 0.96);
  background: rgba(239, 68, 68, 0.06);
}
.kpi-spark {
  width: 72px;
  height: 22px;
  opacity: 0.9;
  filter: drop-shadow(0 8px 16px rgba(15, 23, 42, 0.1));
}
.kpi-actions {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 6px;
  opacity: 0;
  transform: translateY(-2px);
  pointer-events: none;
  transition: opacity 0.16s ease, transform 0.16s ease;
  z-index: 2;
}
.kpi-item:hover .kpi-actions {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.kpi-action {
  font-size: 11px;
  font-weight: 650;
  padding: 4px 8px;
  border-radius: 999px;
  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;
}
.kpi-action:hover {
  background: rgba(47, 111, 237, 0.08);
  border-color: rgba(47, 111, 237, 0.22);
  transform: translateY(-1px);
}
.kpi-action:active {
  transform: translateY(0);
}
.chart-wrap {
  border-radius: 12px;
  overflow: hidden;
  position: relative;
}
.chart-empty {
  height: 240px;
  display: grid;
  place-items: center;
  background: rgba(255, 255, 255, 0.7);
  border-radius: 12px;
  position: absolute;
  inset: 0;
}
:deep(.big-chart-dialog .el-dialog) {
  border-radius: 16px;
  overflow: hidden;
}
:deep(.big-chart-dialog .el-dialog__header) {
  padding: 14px 16px;
  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);
}
:deep(.big-chart-dialog .el-dialog__body) {
  padding: 14px 16px 8px;
  background: rgba(255, 255, 255, 0.92);
}
:deep(.big-chart-dialog .el-dialog__footer) {
  padding: 10px 16px 14px;
  background: rgba(255, 255, 255, 0.92);
  border-top: 1px solid rgba(15, 23, 42, 0.06);
}
.big-chart-canvas {
  width: 100%;
  height: min(74vh, 760px);
  border-radius: 14px;
  border: 1px solid rgba(15, 23, 42, 0.08);
  background: #ffffff;
}
.big-chart-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
.chart-tools {
  display: flex;
  align-items: center;
  gap: 8px;
  opacity: 0;
  transform: translateY(-2px);
  transition: opacity 0.16s ease, transform 0.16s ease;
}
.chart-card:hover .chart-tools {
  opacity: 1;
  transform: translateY(0);
}
.chart-card:focus-within .chart-tools {
  opacity: 1;
  transform: translateY(0);
}
:deep(.chart-wrap .el-loading-mask) {
  border-radius: 12px;
  backdrop-filter: blur(2px);
  background-color: rgba(255, 255, 255, 0.55);
}
.chart-tool {
  font-size: 11px;
  font-weight: 650;
  padding: 4px 8px;
  border-radius: 10px;
  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;
}
.chart-tool:hover {
  background: rgba(47, 111, 237, 0.08);
  border-color: rgba(47, 111, 237, 0.22);
  transform: translateY(-1px);
}
.kpi-unit {
  font-size: 12px;
  font-weight: 600;
  color: var(--lux-muted);
  margin-left: 4px;
}
.kpi-icon {
  width: 38px;
  height: 38px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  flex: 0 0 auto;
  position: relative;
  z-index: 1;
  transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1), filter 0.28s ease;
}
.kpi-item:hover .kpi-icon {
  transform: translateY(-1px) rotate(-2deg);
  filter: saturate(1.06);
}
.kpi-total {
  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.1),
    rgba(255, 255, 255, 0.86)
  );
}
.kpi-production .kpi-icon {
  background: linear-gradient(
    135deg,
    var(--lux-success),
    rgba(22, 163, 74, 0.65)
  );
}
.kpi-office {
  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.1),
    rgba(255, 255, 255, 0.86)
  );
}
.kpi-avg .kpi-icon {
  background: linear-gradient(
    135deg,
    var(--lux-warning),
    rgba(245, 158, 11, 0.62)
  );
}
.panel-card {
  margin-top: 0;
  border-radius: var(--lux-radius);
  border-color: var(--lux-border);
  background: var(--lux-card);
  backdrop-filter: blur(10px);
  box-shadow: var(--lux-shadow-soft);
  padding-bottom: 8px;
  overflow: hidden;
}
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 4px 6px;
}
.segmented {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  padding: 4px;
  border-radius: 14px;
  border: 1px solid rgba(15, 23, 42, 0.08);
  background: rgba(15, 23, 42, 0.03);
  overflow: hidden;
  flex: 1 1 auto;
  min-width: 0;
}
.segmented::after {
  content: "";
  position: absolute;
  top: 10px;
  bottom: 10px;
  left: 50%;
  width: 2px;
  border-radius: 999px;
  background: linear-gradient(
    180deg,
    rgba(15, 23, 42, 0.06),
    rgba(15, 23, 42, 0.12),
    rgba(15, 23, 42, 0.06)
  );
  transform: translateX(-0.5px);
  pointer-events: none;
  z-index: 0;
}
.segmented.no-active {
  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.1);
}
.segmented-indicator {
  position: absolute;
  top: 4px;
  left: 4px;
  width: calc(50% - 4px);
  height: calc(100% - 8px);
  border-radius: 13px;
  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.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.2s ease;
  pointer-events: none;
  will-change: transform;
  z-index: 1;
}
.segmented-indicator.hidden {
  opacity: 0;
}
.segmented-item {
  position: relative;
  z-index: 2;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  transition: transform 0.16s ease, color 0.16s ease,
    background-color 0.16s ease;
}
.segmented-item:hover {
  transform: translateY(-1px);
}
.segmented-item:active {
  transform: translateY(0);
}
.seg-title {
  font-size: 13px;
  font-weight: 780;
  color: rgba(15, 23, 42, 0.86);
  letter-spacing: 0.2px;
}
.seg-sub {
  font-size: 11px;
  color: rgba(15, 23, 42, 0.46);
}
.segmented-item.active .seg-title {
  color: var(--lux-text);
}
.segmented-item.active .seg-sub {
  color: rgba(15, 23, 42, 0.56);
}
.panel-body {
  padding-top: 4px;
}
.core-kpi {
  font-size: 12px;
  font-weight: 650;
  color: rgba(15, 23, 42, 0.78);
  padding: 2px 10px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.04);
  border: 1px solid rgba(15, 23, 42, 0.06);
}
.chart-card {
  border-radius: var(--lux-radius);
  border-color: var(--lux-border);
  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;
  &:hover {
    transform: translateY(-2px);
    box-shadow: var(--lux-shadow);
    border-color: rgba(47, 111, 237, 0.16);
  }
}
.chart-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.chart-title {
  font-weight: 700;
  color: var(--lux-text);
}
.chart-content {
  height: 240px;
}
.table-card {
  border-radius: var(--lux-radius);
  border-color: var(--lux-border);
  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;
  &:hover {
    transform: translateY(-1px);
    box-shadow: var(--lux-shadow);
    border-color: rgba(15, 23, 42, 0.1);
  }
}
.data-table {
  width: 100%;
}
.consumption-value {
  font-weight: bold;
  color: var(--lux-primary);
}
.consumption-unit {
  font-size: 12px;
  color: var(--lux-muted);
  margin-left: 2px;
}
.price-value {
  font-weight: bold;
  color: var(--lux-success);
}
.cost-value {
  font-weight: bold;
  color: var(--lux-danger);
}
.pagination-container {
  display: flex;
  justify-content: flex-end;
  padding-top: 12px;
}
/* Element Plus 深度样式:卡片式表格质感 */
:deep(.lux-table) {
  border-radius: 12px;
  overflow: hidden;
  font-variant-numeric: tabular-nums;
}
:deep(.lux-table .el-table__inner-wrapper::before) {
  height: 0;
}
: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%
  );
}
:deep(.lux-table th.el-table__cell) {
  background: transparent;
  color: rgba(15, 23, 42, 0.78);
  font-weight: 700;
  letter-spacing: 0.2px;
  border-bottom: 1px solid rgba(15, 23, 42, 0.08);
}
:deep(.lux-table td.el-table__cell) {
  border-bottom: 1px solid rgba(15, 23, 42, 0.06);
}
:deep(.lux-table .el-table__row) {
  transition: background-color 0.18s ease;
}
:deep(.lux-table .el-table__row:hover > td.el-table__cell) {
  background-color: rgba(47, 111, 237, 0.06) !important;
}
:deep(.lux-table .el-table__row:hover) {
  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
  ) {
  background: rgba(15, 23, 42, 0.018);
}
:deep(.el-pagination) {
  --el-pagination-button-color: rgba(15, 23, 42, 0.72);
  --el-pagination-button-bg-color: transparent;
  --el-pagination-hover-color: var(--lux-primary);
}
:deep(.el-pagination .btn-next),
:deep(.el-pagination .btn-prev) {
  border-radius: 10px;
  transition: background-color 0.18s ease, transform 0.18s ease;
}
:deep(.el-pagination .btn-next:hover),
:deep(.el-pagination .btn-prev:hover) {
  background-color: rgba(47, 111, 237, 0.06);
  transform: translateY(-1px);
}
/* 响应式 */
@media (max-width: 960px) {
  .filter-form {
    flex-direction: column;
    align-items: flex-start;
  }
  .filter-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: nowrap;
    margin-top: 0;
    padding-top: 0;
    border-top: none;
    justify-content: flex-end;
    flex: 0 0 auto;
    white-space: nowrap;
    align-self: flex-start;
    padding-bottom: 0;
    width: 290px;
  }
  .filter-actions :deep(.el-button) {
    min-width: 78px;
  }
  .filter-actions :deep(.el-button.is-loading) {
    min-width: 90px;
  }
  .filter-layout {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 14px;
  }
  .filter-form {
    flex: 1 1 auto;
    min-width: 0;
  }
  .w-260 {
    width: 260px;
    max-width: 100%;
  }
  .lux-btn {
    transition: transform 0.18s ease, box-shadow 0.18s ease, filter 0.18s ease;
    &:hover {
      transform: translateY(-1px);
      box-shadow: 0 10px 22px rgba(15, 23, 42, 0.10);
      filter: saturate(1.02);
    }
    &:active {
      transform: translateY(0);
      box-shadow: none;
    }
  }
  .filter-card {
    margin-bottom: 16px;
    border-radius: var(--lux-radius);
    border-color: var(--lux-border);
    background: var(--lux-card);
    backdrop-filter: blur(10px);
    box-shadow: var(--lux-shadow-soft);
  }
  /* 查询区控件统一皮肤 */
  :deep(.filter-card .el-form-item__label) {
    color: rgba(15, 23, 42, 0.70);
    font-weight: 650;
  }
  :deep(.filter-card .el-input__wrapper),
  :deep(.filter-card .el-select__wrapper) {
    border-radius: 12px;
    box-shadow: none;
    border: 1px solid rgba(15, 23, 42, 0.10);
    background: rgba(255, 255, 255, 0.82);
    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);
    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);
    box-shadow: 0 0 0 3px rgba(47, 111, 237, 0.14);
  }
  :deep(.filter-card .el-range-editor.el-input__wrapper) {
    border-radius: 12px;
  }
  .card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
  }
  .card-head-left {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 200px;
  }
  .card-head-right {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .card-icon {
    color: var(--lux-primary);
  }
  .card-title {
    font-weight: 760;
    color: var(--lux-text);
  }
  .subtle {
    color: var(--lux-subtle);
    font-size: 12px;
  }
  .filter-form {
    display: flex;
    flex-wrap: nowrap;
    gap: 10px 14px;
    align-items: flex-end;
    min-width: 0;
  }
  .filter-form :deep(.el-form-item) {
    margin-right: 0;
    margin-bottom: 0;
    flex: 0 0 auto;
  }
  .filter-form :deep(.el-form-item__content) {
    min-width: 0;
  }
  .filter-form :deep(.el-form-item:last-child) {
    flex: 1 1 auto;
  }
  .filter-form :deep(.el-form-item:last-child .el-form-item__content) {
    width: 100%;
  }
  .w-140 {
    width: 140px;
  }
  .w-260 {
    width: 260px;
    max-width: 100%;
  }
  @media (max-width: 1280px) {
    .filter-form {
      flex-wrap: wrap;
      align-items: flex-start;
    }
  }
  .section-title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 10px 0 12px;
    font-size: 14px;
    font-weight: 700;
    color: var(--lux-text);
  }
  .section-icon {
    color: var(--lux-primary);
  }
  .metrics {
    margin-bottom: 10px;
  }
  .ui-icon {
    font-size: 16px;
    transition: transform 0.18s ease, opacity 0.18s ease;
  }
  .card-head-left:hover .ui-icon,
  .section-title:hover .ui-icon {
    transform: translateY(-1px);
  }
  .metric-card {
    border-radius: var(--lux-radius-sm);
    padding: 14px 14px 14px 16px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border: 1px solid var(--lux-border);
    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;
    &:hover {
      transform: translateY(-1px);
      box-shadow: var(--lux-shadow);
      border-color: rgba(47, 111, 237, 0.18);
    }
  }
  .metric-left {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .metric-label {
    color: var(--lux-subtle);
    font-size: 12px;
  }
  .metric-value {
    color: var(--lux-text);
    font-size: 20px;
    font-weight: 800;
    letter-spacing: 0.2px;
  }
  .metric-unit {
    font-size: 12px;
    font-weight: 500;
    color: var(--lux-muted);
    margin-left: 4px;
  }
  .metric-right {
    width: 42px;
    height: 42px;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .metric-icon {
    font-size: 20px;
    color: #fff;
  }
  .metric-total {
    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));
    }
  }
  .metric-production {
    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));
    }
  }
  .metric-office {
    background: linear-gradient(135deg, rgba(144, 147, 153, 0.14), rgba(144, 147, 153, 0.03));
    .metric-right {
      background: linear-gradient(135deg, #909399, #b1b3b8);
    }
  }
  .metric-avg {
    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));
    }
  }
  .charts {
    margin-top: 6px;
    margin-bottom: 12px;
  }
  .charts-row {
    margin-top: 16px;
    justify-content: flex-start;
  }
  .kpi-strip {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
    padding: 4px 4px 10px;
  }
  .kpi-strip.pulse {
    animation: kpiPulse 520ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  @keyframes kpiPulse {
    0% {
      filter: saturate(1.02);
    }
    35% {
      filter: saturate(1.10);
    }
    100% {
      filter: saturate(1.02);
    }
  }
  .kpi-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 12px 12px 14px;
    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;
    min-height: 68px;
    text-align: left;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    outline: none;
    transform: translateZ(0);
  }
  .kpi-item:hover {
    transform: translateY(-1px);
    box-shadow: 0 16px 40px rgba(15, 23, 42, 0.10);
    border-color: rgba(47, 111, 237, 0.18);
  }
  .kpi-item::before {
    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%);
    opacity: 0;
    transform: translateX(-8%) translateY(-2%);
    transition: opacity 0.22s ease, transform 0.42s cubic-bezier(0.16, 1, 0.3, 1);
    pointer-events: none;
  }
  .kpi-item:hover::before {
    opacity: 1;
    transform: translateX(0) translateY(0);
  }
  .kpi-item::after {
    content: "";
    position: absolute;
    inset: -1px;
    border-radius: 15px;
    background: linear-gradient(
      135deg,
      rgba(47, 111, 237, 0.18),
      rgba(255, 255, 255, 0.0),
      rgba(22, 163, 74, 0.14)
    );
    opacity: 0;
    transition: opacity 0.22s ease;
    pointer-events: none;
  }
  .kpi-item:hover::after {
    opacity: 1;
  }
  .kpi-item:active {
    transform: translateY(0);
    box-shadow: 0 10px 22px rgba(15, 23, 42, 0.08);
  }
  .kpi-item:focus-visible {
    box-shadow:
      0 16px 44px rgba(15, 23, 42, 0.10),
      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);
  }
  .kpi-left {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    position: relative;
    z-index: 1;
  }
  .kpi-label {
    font-size: 12px;
    color: var(--lux-subtle);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .kpi-value {
    font-size: 18px;
    font-weight: 850;
    letter-spacing: 0.2px;
    color: var(--lux-text);
    line-height: 1.1;
  }
  .kpi-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 2px;
    min-height: 22px;
  }
  .kpi-meta.muted {
    font-size: 11px;
    color: var(--lux-muted);
  }
  .kpi-chip {
    font-size: 11px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 999px;
    border: 1px solid rgba(15, 23, 42, 0.08);
    background: rgba(255, 255, 255, 0.72);
    color: rgba(15, 23, 42, 0.72);
  }
  .kpi-chip.up {
    border-color: rgba(22, 163, 74, 0.20);
    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);
    color: rgba(239, 68, 68, 0.96);
    background: rgba(239, 68, 68, 0.06);
  }
  .kpi-spark {
    width: 72px;
    height: 22px;
    opacity: 0.9;
    filter: drop-shadow(0 8px 16px rgba(15, 23, 42, 0.10));
  }
  .kpi-actions {
    position: absolute;
    top: 10px;
    right: 10px;
    display: flex;
    gap: 6px;
    opacity: 0;
    transform: translateY(-2px);
    pointer-events: none;
    transition: opacity 0.16s ease, transform 0.16s ease;
    z-index: 2;
  }
  .kpi-item:hover .kpi-actions {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
  }
  .kpi-action {
    font-size: 11px;
    font-weight: 650;
    padding: 4px 8px;
    border-radius: 999px;
    border: 1px solid rgba(15, 23, 42, 0.10);
    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;
  }
  .kpi-action:hover {
    background: rgba(47, 111, 237, 0.08);
    border-color: rgba(47, 111, 237, 0.22);
    transform: translateY(-1px);
  }
  .kpi-action:active {
    transform: translateY(0);
  }
  .chart-wrap {
    border-radius: 12px;
    overflow: hidden;
    position: relative;
  }
  .chart-empty {
    height: 240px;
    display: grid;
    place-items: center;
    background: rgba(255, 255, 255, 0.70);
    border-radius: 12px;
    position: absolute;
    inset: 0;
  }
  :deep(.big-chart-dialog .el-dialog) {
    border-radius: 16px;
    overflow: hidden;
  }
  :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%),
      rgba(255, 255, 255, 0.92);
    border-bottom: 1px solid rgba(15, 23, 42, 0.06);
  }
  :deep(.big-chart-dialog .el-dialog__body) {
    padding: 14px 16px 8px;
    background: rgba(255, 255, 255, 0.92);
  }
  :deep(.big-chart-dialog .el-dialog__footer) {
    padding: 10px 16px 14px;
    background: rgba(255, 255, 255, 0.92);
    border-top: 1px solid rgba(15, 23, 42, 0.06);
  }
  .big-chart-canvas {
    width: 100%;
    height: min(74vh, 760px);
    border-radius: 14px;
    border: 1px solid rgba(15, 23, 42, 0.08);
    background: #ffffff;
  }
  .big-chart-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
  }
  .chart-tools {
    display: flex;
    align-items: center;
    gap: 8px;
    opacity: 0.0;
    transform: translateY(-2px);
    transition: opacity 0.16s ease, transform 0.16s ease;
  }
  .chart-card:hover .chart-tools {
    opacity: 1;
    transform: translateY(0);
  }
  .chart-card:focus-within .chart-tools {
    opacity: 1;
    transform: translateY(0);
  }
  :deep(.chart-wrap .el-loading-mask) {
    border-radius: 12px;
    backdrop-filter: blur(2px);
    background-color: rgba(255, 255, 255, 0.55);
  }
  .chart-tool {
    font-size: 11px;
    font-weight: 650;
    padding: 4px 8px;
    border-radius: 10px;
    border: 1px solid rgba(15, 23, 42, 0.10);
    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;
  }
  .chart-tool:hover {
    background: rgba(47, 111, 237, 0.08);
    border-color: rgba(47, 111, 237, 0.22);
    transform: translateY(-1px);
  }
  .kpi-unit {
    font-size: 12px;
    font-weight: 600;
    color: var(--lux-muted);
    margin-left: 4px;
  }
  .kpi-icon {
    width: 38px;
    height: 38px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    flex: 0 0 auto;
    position: relative;
    z-index: 1;
    transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1), filter 0.28s ease;
  }
  .kpi-item:hover .kpi-icon {
    transform: translateY(-1px) rotate(-2deg);
    filter: saturate(1.06);
  }
  .kpi-total {
    background: linear-gradient(135deg, rgba(47, 111, 237, 0.10), 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));
  }
  .kpi-production .kpi-icon {
    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));
  }
  .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));
  }
  .kpi-avg .kpi-icon {
    background: linear-gradient(135deg, var(--lux-warning), rgba(245, 158, 11, 0.62));
  }
  .panel-card {
    margin-top: 0;
    border-radius: var(--lux-radius);
    border-color: var(--lux-border);
    background: var(--lux-card);
    backdrop-filter: blur(10px);
    box-shadow: var(--lux-shadow-soft);
    padding-bottom: 8px;
    overflow: hidden;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 4px 6px;
    flex-wrap: wrap;
  }
}
  .segmented {
    position: relative;
    display: grid;
    grid-template-columns: 1fr 1fr;
    padding: 4px;
    border-radius: 14px;
    border: 1px solid rgba(15, 23, 42, 0.08);
    background: rgba(15, 23, 42, 0.03);
    overflow: hidden;
    flex: 1 1 auto;
    min-width: 0;
  }
/* 折叠动画 */
.lux-collapse-enter-active,
.lux-collapse-leave-active {
  transition: max-height 0.22s ease, opacity 0.18s ease;
  overflow: hidden;
}
  .segmented::after {
    content: "";
    position: absolute;
    top: 10px;
    bottom: 10px;
    left: 50%;
    width: 2px;
    border-radius: 999px;
    background: linear-gradient(
      180deg,
      rgba(15, 23, 42, 0.06),
      rgba(15, 23, 42, 0.12),
      rgba(15, 23, 42, 0.06)
    );
    transform: translateX(-0.5px);
    pointer-events: none;
    z-index: 0;
  }
.lux-collapse-enter-from,
.lux-collapse-leave-to {
  max-height: 0;
  opacity: 0;
}
  .segmented.no-active {
    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);
  }
  .segmented-indicator {
    position: absolute;
    top: 4px;
    left: 4px;
    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));
    border: 1px solid rgba(47, 111, 237, 0.18);
    box-shadow:
      0 14px 30px rgba(15, 23, 42, 0.10),
      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;
    pointer-events: none;
    will-change: transform;
    z-index: 1;
  }
  .segmented-indicator.hidden {
    opacity: 0;
  }
  .segmented-item {
    position: relative;
    z-index: 2;
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 2px;
    text-align: left;
    padding: 10px 12px;
    border-radius: 12px;
    border: 1px solid transparent;
    background: transparent;
    cursor: pointer;
    transition: transform 0.16s ease, color 0.16s ease, background-color 0.16s ease;
  }
  .segmented-item:hover {
    transform: translateY(-1px);
  }
  .segmented-item:active {
    transform: translateY(0);
  }
  .seg-title {
    font-size: 13px;
    font-weight: 780;
    color: rgba(15, 23, 42, 0.86);
    letter-spacing: 0.2px;
  }
  .seg-sub {
    font-size: 11px;
    color: rgba(15, 23, 42, 0.46);
  }
  .segmented-item.active .seg-title {
    color: var(--lux-text);
  }
  .segmented-item.active .seg-sub {
    color: rgba(15, 23, 42, 0.56);
  }
  .panel-body {
    padding-top: 4px;
  }
  .core-kpi {
    font-size: 12px;
    font-weight: 650;
    color: rgba(15, 23, 42, 0.78);
    padding: 2px 10px;
    border-radius: 999px;
    background: rgba(15, 23, 42, 0.04);
    border: 1px solid rgba(15, 23, 42, 0.06);
  }
  .chart-card {
    border-radius: var(--lux-radius);
    border-color: var(--lux-border);
    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;
    &:hover {
      transform: translateY(-2px);
      box-shadow: var(--lux-shadow);
      border-color: rgba(47, 111, 237, 0.16);
    }
  }
  .chart-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
  }
  .chart-title {
    font-weight: 700;
    color: var(--lux-text);
  }
  .chart-content {
    height: 240px;
  }
  .table-card {
    border-radius: var(--lux-radius);
    border-color: var(--lux-border);
    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;
    &:hover {
      transform: translateY(-1px);
      box-shadow: var(--lux-shadow);
      border-color: rgba(15, 23, 42, 0.10);
    }
  }
  .data-table {
    width: 100%;
  }
  .consumption-value {
    font-weight: bold;
    color: var(--lux-primary);
  }
  .consumption-unit {
    font-size: 12px;
    color: var(--lux-muted);
    margin-left: 2px;
  }
  .price-value {
    font-weight: bold;
    color: var(--lux-success);
  }
  .cost-value {
    font-weight: bold;
    color: var(--lux-danger);
  }
  .pagination-container {
    display: flex;
    justify-content: flex-end;
    padding-top: 12px;
  }
  /* Element Plus 深度样式:卡片式表格质感 */
  :deep(.lux-table) {
    border-radius: 12px;
    overflow: hidden;
    font-variant-numeric: tabular-nums;
  }
  :deep(.lux-table .el-table__inner-wrapper::before) {
    height: 0;
  }
  :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%);
  }
  :deep(.lux-table th.el-table__cell) {
    background: transparent;
    color: rgba(15, 23, 42, 0.78);
    font-weight: 700;
    letter-spacing: 0.2px;
    border-bottom: 1px solid rgba(15, 23, 42, 0.08);
  }
  :deep(.lux-table td.el-table__cell) {
    border-bottom: 1px solid rgba(15, 23, 42, 0.06);
  }
  :deep(.lux-table .el-table__row) {
    transition: background-color 0.18s ease;
  }
  :deep(.lux-table .el-table__row:hover > td.el-table__cell) {
    background-color: rgba(47, 111, 237, 0.06) !important;
  }
  :deep(.lux-table .el-table__row:hover) {
    box-shadow: inset 3px 0 0 rgba(47, 111, 237, 0.30);
  }
  :deep(.lux-table .el-table__body tr.el-table__row--striped > td.el-table__cell) {
    background: rgba(15, 23, 42, 0.018);
  }
  :deep(.el-pagination) {
    --el-pagination-button-color: rgba(15, 23, 42, 0.72);
    --el-pagination-button-bg-color: transparent;
    --el-pagination-hover-color: var(--lux-primary);
  }
  :deep(.el-pagination .btn-next),
  :deep(.el-pagination .btn-prev) {
    border-radius: 10px;
    transition: background-color 0.18s ease, transform 0.18s ease;
  }
  :deep(.el-pagination .btn-next:hover),
  :deep(.el-pagination .btn-prev:hover) {
    background-color: rgba(47, 111, 237, 0.06);
    transform: translateY(-1px);
  }
  /* 响应式 */
  @media (max-width: 960px) {
    .filter-form {
      flex-direction: column;
      align-items: flex-start;
    }
    .filter-actions {
      justify-content: flex-start;
    }
    .kpi-strip {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .panel-head {
      flex-wrap: wrap;
    }
  }
  /* 折叠动画 */
  .lux-collapse-enter-active,
  .lux-collapse-leave-active {
    transition: max-height 0.22s ease, opacity 0.18s ease;
    overflow: hidden;
  }
  .lux-collapse-enter-from,
  .lux-collapse-leave-to {
    max-height: 0;
    opacity: 0;
  }
  .lux-collapse-enter-to,
  .lux-collapse-leave-from {
    max-height: 600px;
    opacity: 1;
  }
.lux-collapse-enter-to,
.lux-collapse-leave-from {
  max-height: 600px;
  opacity: 1;
}
</style>