From 0878762ff3d0796e5fcb22fc38103a3079d6ca24 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期三, 18 三月 2026 15:06:40 +0800
Subject: [PATCH] 1
---
src/views/costAccounting/energyCosts/index.vue | 4755 ++++++++++++++++++++++++++++++-----------------------------
1 files changed, 2,419 insertions(+), 2,336 deletions(-)
diff --git a/src/views/costAccounting/energyCosts/index.vue b/src/views/costAccounting/energyCosts/index.vue
index 5488bcc..655e145 100644
--- a/src/views/costAccounting/energyCosts/index.vue
+++ b/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";
- // 缁熻缁村害锛歞ay-鎸夋棩锛宮onth-鎸夋湀
- 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";
+// 缁熻缁村害锛歞ay-鎸夋棩锛宮onth-鎸夋湀
+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);
- // 銆愬亣鏁版嵁锛圡ock锛夊凡绂佺敤銆戞帴鍙e紓甯告椂涓嶅啀鐢熸垚闅忔満鍋囨暟鎹紝閬垮厤璇敤鍒扮敓浜ф暟鎹摼璺�
- 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();
- });
- };
-
- // 銆愬亣鏁版嵁锛圡ock锛夊凡绂佺敤銆戝巻鍙蹭笂鐢ㄤ簬鎺ュ彛寮傚父鍏滃簳鐨勯殢鏈烘暟鎹敓鎴愰�昏緫锛岀幇宸叉暣浣撴敞閲婏紝閬垮厤璇敤浜庣敓浜с��
- /*
- // 鐢熸垚鍋囨暟鎹�
- 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();
- };
- */
-
- // 銆愬亣鏁版嵁锛圡ock锛夊凡绂佺敤銆戜笌 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);
+ // 銆愬亣鏁版嵁锛圡ock锛夊凡绂佺敤銆戞帴鍙e紓甯告椂涓嶅啀鐢熸垚闅忔満鍋囨暟鎹紝閬垮厤璇敤鍒扮敓浜ф暟鎹摼璺�
+ 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>
\ No newline at end of file
--
Gitblit v1.9.3