zhangwencui
6 小时以前 e95d6f2a9141c05903098065b4356d1158c0c4e2
能耗统计年月日修改
已修改2个文件
642 ■■■■ 文件已修改
src/views/costAccounting/energyCosts/index.vue 614 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/energyConsumptionStatistical/index.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/costAccounting/energyCosts/index.vue
@@ -2,7 +2,8 @@
<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">
@@ -12,20 +13,20 @@
            <span class="card-title">查询条件</span>
          </div>
          <div class="card-head-right">
            <el-radio-group
              v-model="statisticsType"
            <el-radio-group v-model="statisticsType"
              size="small"
              @change="handleTypeChange"
            >
                            @change="handleTypeChange">
              <el-radio-button label="day">按日</el-radio-button>
              <el-radio-button label="month">按月</el-radio-button>
              <el-radio-button label="year">按年</el-radio-button>
            </el-radio-group>
          </div>
        </div>
      </template>
      <div class="filter-layout">
        <el-form :model="searchForm" :inline="true" class="filter-form">
        <el-form :model="searchForm"
                 :inline="true"
                 class="filter-form">
          <!-- <el-form-item label="能耗类型">
            <el-select v-model="searchForm.energyType"
                       placeholder="全部"
@@ -55,8 +56,7 @@
            </el-select>
          </el-form-item> -->
          <el-form-item label="时间范围">
            <el-date-picker
              v-if="statisticsType === 'day'"
            <el-date-picker v-if="statisticsType === 'day'"
              v-model="searchForm.dateRange"
              type="daterange"
              range-separator="至"
@@ -64,10 +64,8 @@
              end-placeholder="结束日期"
              value-format="YYYY-MM-DD"
              class="w-260"
              @change="handleQuery"
            />
            <el-date-picker
              v-else
                            @change="handleQuery" />
            <el-date-picker v-else-if="statisticsType === 'month'"
              v-model="searchForm.monthRange"
              type="monthrange"
              range-separator="至"
@@ -75,62 +73,63 @@
              end-placeholder="结束月份"
              value-format="YYYY-MM"
              class="w-260"
              @change="handleQuery"
            />
                            @change="handleQuery" />
            <el-select v-else-if="statisticsType === 'year'"
                       v-model="searchForm.selectedYear"
                       placeholder="请选择年份"
                       class="w-260"
                       @change="handleYearChange">
              <el-option v-for="year in recentYears"
                         :key="year"
                         :label="year + '年'"
                         :value="year" />
            </el-select>
          </el-form-item>
        </el-form>
        <div class="filter-actions">
          <el-button
            class="lux-btn"
          <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
          >
                     @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"
      <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"
             title="快捷键:Enter 刷新 / Esc 重置 / Alt+E 导出">
          <button class="kpi-item kpi-total"
            type="button"
            :class="{ selected: selectedKpi === 'all' }"
            @click="handleKpiClick('all')"
          >
                  @click="handleKpiClick('all')">
            <div class="kpi-left">
              <div class="kpi-label">总能耗成本</div>
              <div class="kpi-value">
                ¥{{ formatMoney(animatedOverview.totalEnergyCost) }}
              </div>
              <div class="kpi-meta">
                <span
                  class="kpi-chip"
                <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)"
                      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"
                  />
                            stroke-linejoin="round" />
                </svg>
              </div>
            </div>
@@ -139,51 +138,43 @@
                <Money />
              </el-icon>
            </div>
            <div class="kpi-actions" @click.stop>
              <button
                class="kpi-action"
            <div class="kpi-actions"
                 @click.stop>
              <button class="kpi-action"
                type="button"
                @click="copyKpi('totalEnergyCost')"
              >
                      @click="copyKpi('totalEnergyCost')">
                复制
              </button>
              <button
                class="kpi-action"
              <button class="kpi-action"
                type="button"
                @click="viewKpiDetails('all')"
              >
                      @click="viewKpiDetails('all')">
                明细
              </button>
            </div>
          </button>
          <button
            class="kpi-item kpi-production"
          <button class="kpi-item kpi-production"
            type="button"
            :class="{ selected: selectedKpi === 'production' }"
            @click="handleKpiClick('production')"
          >
                  @click="handleKpiClick('production')">
            <div class="kpi-left">
              <div class="kpi-label">生产能耗成本</div>
              <div class="kpi-value">
                ¥{{ formatMoney(animatedOverview.productEnergyCost) }}
              </div>
              <div class="kpi-meta">
                <span
                  class="kpi-chip"
                <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)"
                      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"
                  />
                            stroke-linejoin="round" />
                </svg>
              </div>
            </div>
@@ -192,51 +183,43 @@
                <DataLine />
              </el-icon>
            </div>
            <div class="kpi-actions" @click.stop>
              <button
                class="kpi-action"
            <div class="kpi-actions"
                 @click.stop>
              <button class="kpi-action"
                type="button"
                @click="copyKpi('productEnergyCost')"
              >
                      @click="copyKpi('productEnergyCost')">
                复制
              </button>
              <button
                class="kpi-action"
              <button class="kpi-action"
                type="button"
                @click="viewKpiDetails('production')"
              >
                      @click="viewKpiDetails('production')">
                明细
              </button>
            </div>
          </button>
          <button
            class="kpi-item kpi-office"
          <button class="kpi-item kpi-office"
            type="button"
            :class="{ selected: selectedKpi === 'office' }"
            @click="handleKpiClick('office')"
          >
                  @click="handleKpiClick('office')">
            <div class="kpi-left">
              <div class="kpi-label">办公能耗成本</div>
              <div class="kpi-value">
                ¥{{ formatMoney(animatedOverview.officeEnergyCost) }}
              </div>
              <div class="kpi-meta">
                <span
                  class="kpi-chip"
                <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)"
                      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"
                  />
                            stroke-linejoin="round" />
                </svg>
              </div>
            </div>
@@ -245,35 +228,28 @@
                <TrendCharts />
              </el-icon>
            </div>
            <div class="kpi-actions" @click.stop>
              <button
                class="kpi-action"
            <div class="kpi-actions"
                 @click.stop>
              <button class="kpi-action"
                type="button"
                @click="copyKpi('officeEnergyCost')"
              >
                      @click="copyKpi('officeEnergyCost')">
                复制
              </button>
              <button
                class="kpi-action"
              <button class="kpi-action"
                type="button"
                @click="viewKpiDetails('office')"
              >
                      @click="viewKpiDetails('office')">
                明细
              </button>
            </div>
          </button>
          <button
            class="kpi-item kpi-avg"
          <button class="kpi-item kpi-avg"
            type="button"
            @click="handleKpiClick('all')"
          >
                  @click="handleKpiClick('all')">
            <div class="kpi-left">
              <div class="kpi-label">平均成本</div>
              <div class="kpi-value">
                ¥{{ formatMoney(animatedOverview.averageEnergyCost) }}
                <span class="kpi-unit"
                  >/{{ statisticsType === "day" ? "日" : "月" }}</span
                >
                <span class="kpi-unit">/{{ statisticsType === "day" ? "日" : statisticsType === "month" ? "月" : "年" }}</span>
              </div>
              <div class="kpi-meta muted">基于当前筛选与明细统计</div>
            </div>
@@ -282,138 +258,118 @@
                <Histogram />
              </el-icon>
            </div>
            <div class="kpi-actions" @click.stop>
              <button
                class="kpi-action"
            <div class="kpi-actions"
                 @click.stop>
              <button class="kpi-action"
                type="button"
                @click="copyKpi('averageEnergyCost')"
              >
                      @click="copyKpi('averageEnergyCost')">
                复制
              </button>
              <button
                class="kpi-action"
              <button class="kpi-action"
                type="button"
                @click="viewKpiDetails('all')"
              >
                      @click="viewKpiDetails('all')">
                明细
              </button>
            </div>
          </button>
        </div>
        <div class="panel-head">
          <div
            class="segmented"
          <div class="segmented"
            role="tablist"
            aria-label="分析面板切换"
            :class="{ 'no-active': chartPanel === 'none' }"
          >
            <div
              class="segmented-indicator"
               :class="{ 'no-active': chartPanel === 'none' }">
            <div class="segmented-indicator"
              :class="{ hidden: chartPanel === 'none' }"
              :style="panelIndicatorStyle"
            ></div>
            <button
              class="segmented-item"
                 :style="panelIndicatorStyle"></div>
            <button class="segmented-item"
              type="button"
              role="tab"
              :aria-selected="chartPanel === 'core'"
              :class="{ active: chartPanel === 'core' }"
              @click="handleChartPanelClick('core')"
            >
                    @click="handleChartPanelClick('core')">
              <span class="seg-title">核心分析</span>
              <span class="seg-sub">趋势 / 类型占比</span>
            </button>
            <button
              class="segmented-item"
            <button class="segmented-item"
              type="button"
              role="tab"
              :aria-selected="chartPanel === 'advanced'"
              :class="{ active: chartPanel === 'advanced' }"
              @click="handleChartPanelClick('advanced')"
            >
                    @click="handleChartPanelClick('advanced')">
              <span class="seg-title">高级分析</span>
              <span class="seg-sub">用途占比 / 单价对比</span>
            </button>
          </div>
        </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"
                      <div class="chart-tools"
                           @click.stop>
                        <button class="chart-tool"
                          type="button"
                          @click="downloadChart('cost', '能耗成本趋势')"
                        >
                                @click="downloadChart('cost', '能耗成本趋势')">
                          下载
                        </button>
                        <button
                          class="chart-tool"
                        <button class="chart-tool"
                          type="button"
                          @click="openBigChart('cost', '能耗成本趋势')"
                        >
                                @click="openBigChart('cost', '能耗成本趋势')">
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
                  <div
                    ref="costChartWrap"
                  <div ref="costChartWrap"
                    class="chart-wrap"
                    v-loading="tableLoading"
                  >
                    <div
                      ref="costChart"
                       v-loading="tableLoading">
                    <div ref="costChart"
                      class="chart-content"
                      v-show="hasTableData"
                    ></div>
                    <div class="chart-empty" v-show="!hasTableData">
                         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"
                      <div class="chart-tools"
                           @click.stop>
                        <button class="chart-tool"
                          type="button"
                          @click="downloadChart('type', '能耗类型成本占比')"
                        >
                                @click="downloadChart('type', '能耗类型成本占比')">
                          下载
                        </button>
                        <button
                          class="chart-tool"
                        <button class="chart-tool"
                          type="button"
                          @click="openBigChart('type', '能耗类型成本占比')"
                        >
                                @click="openBigChart('type', '能耗类型成本占比')">
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
                  <div
                    ref="typeChartWrap"
                  <div ref="typeChartWrap"
                    class="chart-wrap"
                    v-loading="tableLoading"
                  >
                    <div
                      ref="typeChart"
                       v-loading="tableLoading">
                    <div ref="typeChart"
                      class="chart-content"
                      v-show="hasTableData"
                    ></div>
                    <div class="chart-empty" v-show="!hasTableData">
                         v-show="hasTableData"></div>
                    <div class="chart-empty"
                         v-show="!hasTableData">
                      <el-empty description="暂无数据" />
                    </div>
                  </div>
@@ -422,83 +378,76 @@
            </el-row>
          </div>
        </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"
                      <div class="chart-tools"
                           @click.stop>
                        <button class="chart-tool"
                          type="button"
                          @click="downloadChart('purpose', '能耗用途成本占比')"
                        >
                                @click="downloadChart('purpose', '能耗用途成本占比')">
                          下载
                        </button>
                        <button
                          class="chart-tool"
                        <button class="chart-tool"
                          type="button"
                          @click="openBigChart('purpose', '能耗用途成本占比')"
                        >
                                @click="openBigChart('purpose', '能耗用途成本占比')">
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
                  <div
                    ref="purposeChartWrap"
                  <div ref="purposeChartWrap"
                    class="chart-wrap"
                    v-loading="tableLoading"
                  >
                    <div
                      ref="purposeChart"
                       v-loading="tableLoading">
                    <div ref="purposeChart"
                      class="chart-content"
                      v-show="hasTableData"
                    ></div>
                    <div class="chart-empty" v-show="!hasTableData">
                         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"
                      <div class="chart-tools"
                           @click.stop>
                        <button class="chart-tool"
                          type="button"
                          @click="downloadChart('unit', '能耗用量对比')"
                        >
                                @click="downloadChart('unit', '能耗用量对比')">
                          下载
                        </button>
                        <button
                          class="chart-tool"
                        <button class="chart-tool"
                          type="button"
                          @click="openBigChart('unit', '能耗用量对比')"
                        >
                                @click="openBigChart('unit', '能耗用量对比')">
                          大图
                        </button>
                      </div>
                    </div>
                  </template>
                  <div
                    ref="unitChartWrap"
                  <div ref="unitChartWrap"
                    class="chart-wrap"
                    v-loading="tableLoading"
                  >
                    <div
                      ref="unitChart"
                       v-loading="tableLoading">
                    <div ref="unitChart"
                      class="chart-content" 
                      v-show="hasTableData"
                    ></div>
                    <div class="chart-empty" v-show="!hasTableData">
                         v-show="hasTableData"></div>
                    <div class="chart-empty"
                         v-show="!hasTableData">
                      <el-empty description="暂无数据" />
                    </div>
                  </div>
@@ -509,37 +458,29 @@
        </transition>
      </el-card>
    </div>
    <el-dialog
      v-model="bigChartVisible"
    <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>
               @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"
          <el-button class="lux-btn"
                     @click="downloadChart(bigChartKey, bigChartTitle)">下载图片</el-button>
          <el-button class="lux-btn"
            type="primary"
            @click="bigChartVisible = false"
            >关闭</el-button
          >
                     @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">
@@ -554,56 +495,52 @@
          </div>
        </div>
      </template>
      <el-table
        :data="displayTableData"
      <el-table :data="displayTableData"
        v-loading="tableLoading"
        stripe
        :header-cell-style="{ height: '44px' }"
        class="data-table lux-table"
        @sort-change="handleSortChange"
      >
                @sort-change="handleSortChange">
        <template #empty>
          <el-empty description="暂无明细数据" />
        </template>
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column
          prop="meterReadingDate"
        <el-table-column type="index"
                         label="序号"
                         width="60"
                         align="center" />
        <el-table-column prop="meterReadingDate"
          :label="timeColumnLabel"
          align="center"
          sortable="custom"
        />
        <el-table-column
          prop="energyTyep"
                         sortable="custom" />
        <el-table-column prop="energyTyep"
          label="能耗类型"
          width="100"
          align="center"
          :filters="energyTypeFilters"
          :filter-method="filterEnergyType"
          filter-placement="bottom-end"
        >
                         filter-placement="bottom-end">
          <template #default="scope">
            <el-tag :type="getEnergyTypeType(scope.row.energyTyep)">
              {{ scope.row.energyTyep }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          prop="type"
        <el-table-column prop="type"
          label="能耗用途"
          width="100"
          align="center"
          :filters="energyPurposeFilters"
          :filter-method="filterEnergyPurpose"
          filter-placement="bottom-end"
        >
                         filter-placement="bottom-end">
          <template #default="scope">
            <el-tag :type="scope.row.type === '生产' ? 'primary' : 'warning'">
              {{ scope.row.type }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="dosage" label="用量" align="right">
        <el-table-column prop="dosage"
                         label="用量"
                         align="right">
          <template #default="scope">
            <span class="unit-value">{{
              formatNumber(scope.row.dosage, 2)
@@ -611,43 +548,34 @@
            <span class="unit-unit">{{ scope.row.unit }}</span>
          </template>
        </el-table-column>
        <el-table-column
          prop="unitPrice"
        <el-table-column prop="unitPrice"
          label="单价(元)"
          align="right"
          sortable="custom"
        >
                         sortable="custom">
          <template #default="scope">
            <span class="price-value">{{
              formatNumber(scope.row.unitPrice, 2)
            }}</span>
          </template>
        </el-table-column>
        <el-table-column
          prop="cost"
        <el-table-column prop="cost"
          label="成本(元)"
          align="right"
          sortable="custom"
          fixed="right"
        >
                         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"
        <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"
        />
                       @current-change="handleCurrentChange" />
      </div>
    </el-card>
  </div>
@@ -696,11 +624,25 @@
    start.setMonth(start.getMonth() - 2);
    return [start.toISOString().slice(0, 7), end.toISOString().slice(0, 7)];
  })(),
    selectedYear: new Date().getFullYear(), // 默认今年
  });
  // 最近七年
  const recentYears = computed(() => {
    const currentYear = new Date().getFullYear();
    const years = [];
    for (let i = 6; i >= 0; i--) {
      years.push(currentYear - i);
    }
    return years;
});
// 时间列标签
const timeColumnLabel = computed(() => {
  return statisticsType.value === "day" ? "日期" : "月份";
    if (statisticsType.value === "day") return "日期";
    if (statisticsType.value === "month") return "月份";
    if (statisticsType.value === "year") return "年份";
    return "时间";
});
// 统计概览
@@ -719,7 +661,7 @@
  averageEnergyCost: 0,
});
const formatMoney = (v) => {
  const formatMoney = v => {
  const n = Number.parseFloat(v);
  const value = Number.isFinite(n) ? n : 0;
  return value.toLocaleString("zh-CN", {
@@ -741,9 +683,9 @@
  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 easeOut = t => 1 - Math.pow(1 - t, 3);
  const tick = (now) => {
    const tick = now => {
    const p = Math.min(1, (now - start) / duration);
    animatedOverview[key] = from + (to - from) * easeOut(p);
    if (p < 1) requestAnimationFrame(tick);
@@ -753,11 +695,17 @@
watch(
  () => ({ ...overview }),
  (val) => {
    val => {
    animateNumber("totalEnergyCost", Number.parseFloat(val.totalEnergyCost));
    animateNumber("productEnergyCost", Number.parseFloat(val.productEnergyCost));
      animateNumber(
        "productEnergyCost",
        Number.parseFloat(val.productEnergyCost)
      );
    animateNumber("officeEnergyCost", Number.parseFloat(val.officeEnergyCost));
    animateNumber("averageEnergyCost", Number.parseFloat(val.averageEnergyCost));
      animateNumber(
        "averageEnergyCost",
        Number.parseFloat(val.averageEnergyCost)
      );
  },
  { deep: true, immediate: true }
);
@@ -787,14 +735,14 @@
  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);
    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 kpiDelta = computed(() => {
  const pick = (arr) => {
    const pick = arr => {
    const a = Array.isArray(arr) ? arr : [];
    if (a.length < 2) return { pct: 0, valid: false };
    const prev = a[a.length - 2];
@@ -809,7 +757,7 @@
  };
});
const sparklinePoints = (values) => {
  const sparklinePoints = values => {
  const v = (Array.isArray(values) ? values : []).slice(-12);
  if (v.length < 2) return "";
  const min = Math.min(...v);
@@ -826,7 +774,7 @@
    .join(" ");
};
const handleKpiClick = (key) => {
  const handleKpiClick = key => {
  selectedKpi.value = key;
  if (key === "all") searchForm.type = "";
  if (key === "production") searchForm.type = "生产";
@@ -835,7 +783,7 @@
  handleQuery();
};
const viewKpiDetails = (key) => {
  const viewKpiDetails = key => {
  handleKpiClick(key);
  nextTick(() => {
    const el = tableAnchor.value;
@@ -844,7 +792,7 @@
  });
};
const copyKpi = async (field) => {
  const copyKpi = async field => {
  const map = {
    totalEnergyCost: animatedOverview.totalEnergyCost,
    productEnergyCost: animatedOverview.productEnergyCost,
@@ -871,7 +819,7 @@
  }
};
const getChartByKey = (key) => {
  const getChartByKey = key => {
  if (key === "cost") return costChartInstance;
  if (key === "type") return typeChartInstance;
  if (key === "purpose") return purposeChartInstance;
@@ -879,7 +827,7 @@
  return null;
};
const ensurePanelForChart = (key) => {
  const ensurePanelForChart = key => {
  if (key === "cost" || key === "type") chartPanel.value = "core";
  if (key === "purpose" || key === "unit") chartPanel.value = "advanced";
};
@@ -899,9 +847,12 @@
    if (statisticsType.value === "day") {
      if (searchForm.dateRange?.length === 2)
        rangePart = `_${searchForm.dateRange[0]}~${searchForm.dateRange[1]}`;
    } else {
      } else if (statisticsType.value === "month") {
      if (searchForm.monthRange?.length === 2)
        rangePart = `_${searchForm.monthRange[0]}~${searchForm.monthRange[1]}`;
      } else if (statisticsType.value === "year") {
        if (searchForm.yearRange?.length === 2)
          rangePart = `_${searchForm.yearRange[0]}~${searchForm.yearRange[1]}`;
    }
    a.download = `${title || "chart"}${typePart}${purposePart}${rangePart}.png`;
    a.click();
@@ -1026,7 +977,7 @@
const bigChartEl = ref(null);
let bigChartInstance = null;
watch(bigChartVisible, (v) => {
  watch(bigChartVisible, v => {
  if (v) window.addEventListener("resize", handleBigChartResize);
  else window.removeEventListener("resize", handleBigChartResize);
});
@@ -1049,7 +1000,7 @@
// 图表区切换:core | advanced | none(点击当前选中可收起)
const chartPanel = ref("core");
const ensureChartsReady = (panel) => {
  const ensureChartsReady = panel => {
  if (panel === "core") {
    if (costChart.value && !costChartInstance) {
      costChartInstance = echarts.init(costChart.value);
@@ -1081,7 +1032,7 @@
  });
};
const handleChartPanelClick = (key) => {
  const handleChartPanelClick = key => {
  chartPanel.value = chartPanel.value === key ? "none" : key;
};
@@ -1090,22 +1041,26 @@
  return { transform: `translateX(${x})` };
});
watch(chartPanel, (val) => {
  watch(chartPanel, val => {
  if (val !== "none") resizeChartsAfterExpand();
});
// 监听表格数据变化,确保数据加载后图表正确渲染
watch(tableData, () => {
  watch(
    tableData,
    () => {
  nextTick(() => {
    updateCharts();
    nextTick(() => {
      handleResize();
    });
  });
}, { deep: true });
    },
    { deep: true }
  );
// 获取能耗类型标签类型
const getEnergyTypeType = (type) => {
  const getEnergyTypeType = type => {
  const typeMap = {
    水: "primary",
    电: "warning",
@@ -1151,7 +1106,7 @@
    },
    xAxis: {
      type: "category",
      data: data.map((item) => item.meterReadingDate),
        data: data.map(item => item.meterReadingDate),
      axisLabel: {
        rotate: statisticsType.value === "day" ? 45 : 0,
        color: "rgba(15, 23, 42, 0.62)",
@@ -1171,7 +1126,7 @@
      {
        name: "生产能耗成本",
        type: "bar",
        data: data.map((item) => (item.type === "生产" ? item.cost : 0)),
          data: data.map(item => (item.type === "生产" ? item.cost : 0)),
        itemStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: "#409EFF" },
@@ -1186,7 +1141,7 @@
      {
        name: "办公能耗成本",
        type: "bar",
        data: data.map((item) => (item.type === "办公" ? item.cost : 0)),
          data: data.map(item => (item.type === "办公" ? item.cost : 0)),
        itemStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: "#67C23A" },
@@ -1212,7 +1167,7 @@
  const data = tableData.value;
  const typeCosts = {};
  data.forEach((item) => {
    data.forEach(item => {
    if (!typeCosts[item.energyTyep]) {
      typeCosts[item.energyTyep] = 0;
    }
@@ -1288,7 +1243,7 @@
    办公: 0,
  };
  data.forEach((item) => {
    data.forEach(item => {
    if (purposeCosts.hasOwnProperty(item.type)) {
      purposeCosts[item.type] += parseFloat(item.cost);
    }
@@ -1350,7 +1305,7 @@
  const data = tableData.value;
  const unitData = {};
  data.forEach((item) => {
    data.forEach(item => {
    if (!unitData[item.energyTyep]) {
      unitData[item.energyTyep] = {
        生产: 0,
@@ -1358,19 +1313,13 @@
      };
    }
    if (unitData[item.energyTyep].hasOwnProperty(item.type)) {
      unitData[item.energyTyep][item.type] += parseFloat(
        item.dosage || 0
      );
        unitData[item.energyTyep][item.type] += parseFloat(item.dosage || 0);
    }
  });
  const energyTypes = Object.keys(unitData);
  const productionConsumptions = energyTypes.map(
    (type) => unitData[type].生产
  );
  const officeConsumptions = energyTypes.map(
    (type) => unitData[type].办公
  );
    const productionConsumptions = energyTypes.map(type => unitData[type].生产);
    const officeConsumptions = energyTypes.map(type => unitData[type].办公);
  const option = {
    tooltip: {
@@ -1452,7 +1401,7 @@
      start.toISOString().split("T")[0],
      end.toISOString().split("T")[0],
    ];
  } else {
    } else if (statisticsType.value === "month") {
    const end = new Date();
    const start = new Date();
    start.setMonth(start.getMonth() - 2);
@@ -1460,14 +1409,21 @@
      start.toISOString().slice(0, 7),
      end.toISOString().slice(0, 7),
    ];
    } else if (statisticsType.value === "year") {
      searchForm.selectedYear = new Date().getFullYear(); // 默认今年
  }
    page.current = 1;
    handleQuery();
  };
  // 年份选择变化
  const handleYearChange = () => {
  page.current = 1;
  handleQuery();
};
// 查询
const handleQuery = () => {
  queryPulse.value = true;
  window.setTimeout(() => {
    queryPulse.value = false;
@@ -1481,6 +1437,12 @@
    // type: searchForm.type || undefined,
    pageNum: page.current,
    pageSize: page.size,
      state:
        statisticsType.value === "day"
          ? "日"
          : statisticsType.value === "month"
          ? "月"
          : "年",
  };
  if (statisticsType.value === "day") {
@@ -1488,7 +1450,7 @@
      params.startDate = searchForm.dateRange[0];
      params.endDate = searchForm.dateRange[1];
    }
  } else {
    } else if (statisticsType.value === "month") {
    if (searchForm.monthRange && searchForm.monthRange.length === 2) {
      params.startDate = searchForm.monthRange[0] + "-01";
@@ -1512,6 +1474,12 @@
        params.endDate = searchForm.monthRange[1] + "-01";
      }
    }
    } else if (statisticsType.value === "year") {
      if (searchForm.selectedYear) {
        const year = searchForm.selectedYear;
        params.startDate = year + "-01-01";
        params.endDate = year + "-12-31";
      }
  }
  // 计算开始到结束的天数(包含起止两天)
@@ -1527,7 +1495,7 @@
  // 调用接口获取数据
  energyConsumptionDetailAccount(params)
    .then((res) => {
      .then(res => {
      if (res.code === 200) {
        const data = res.data;
        overview.totalEnergyCost = data.totalEnergyCost || "0";
@@ -1548,7 +1516,7 @@
        overview.averageEnergyCost = "0.00";
      }
    })
    .catch((err) => {
      .catch(err => {
      ElMessage.error("获取数据异常");
      tableData.value = [];
      page.total = 0;
@@ -1594,7 +1562,7 @@
      start.toISOString().split("T")[0],
      end.toISOString().split("T")[0],
    ];
  } else {
    } else if (statisticsType.value === "month") {
    const end = new Date();
    const start = new Date();
    start.setMonth(start.getMonth() - 2);
@@ -1602,6 +1570,8 @@
      start.toISOString().slice(0, 7),
      end.toISOString().slice(0, 7),
    ];
    } else if (statisticsType.value === "year") {
      searchForm.selectedYear = new Date().getFullYear(); // 默认今年
  }
  page.current = 1;
  handleQuery();
@@ -1613,14 +1583,14 @@
};
// 分页大小变化
const handleSizeChange = (val) => {
  const handleSizeChange = val => {
  page.size = val;
  page.current = 1;
  handleQuery();
};
// 页码变化
const handleCurrentChange = (val) => {
  const handleCurrentChange = val => {
  page.current = val;
  handleQuery();
};
@@ -1639,7 +1609,7 @@
  window.addEventListener("resize", handleResize);
});
const handleGlobalHotkeys = (e) => {
  const handleGlobalHotkeys = e => {
  // 避免在输入框内误触
  const target = e?.target;
  const tag = target?.tagName?.toLowerCase?.();
src/views/energyManagement/energyConsumptionStatistical/index.vue
@@ -689,6 +689,12 @@
    tableLoading.value = true;
    const params = {
      type: "",
      state:
        statisticsType.value === "day"
          ? "日"
          : statisticsType.value === "month"
          ? "月"
          : "年",
    };
    // 构造请求参数
@@ -708,14 +714,20 @@
    } else if (statisticsType.value === "month") {
      if (searchForm.monthRange && searchForm.monthRange.length === 2) {
        params.startDate = searchForm.monthRange[0] + "-01";
        params.endDate = searchForm.monthRange[1] + "-01";
        // 计算月数
        const start = new Date(searchForm.monthRange[0] + "-01");
        const end = new Date(searchForm.monthRange[1] + "-01");
        params.days =
          (end.getFullYear() - start.getFullYear()) * 12 +
          (end.getMonth() - start.getMonth()) +
          1;
        const [endYearStr, endMonthStr] = String(searchForm.monthRange[1]).split(
          "-"
        );
        const endYear = Number(endYearStr);
        const endMonth = Number(endMonthStr);
        const lastDay = new Date(endYear, endMonth, 0).getDate();
        params.endDate = `${endYearStr}-${endMonthStr}-${String(lastDay).padStart(
          2,
          "0"
        )}`;
        // 计算天数
        const start = new Date(params.startDate);
        const end = new Date(params.endDate);
        params.days = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1;
      }
    } else if (statisticsType.value === "year") {
      params.startDate = searchForm.year + "-01-01";