learshaw 3 месяцев назад
Родитель
Сommit
bf26d73e5e

+ 8 - 5
ems-ui-cloud/src/api/ca/caMeterD.js

@@ -1,6 +1,10 @@
 import request from '@/utils/request'
 
-// 查询碳计量日列表
+/**
+ * 查询碳计量列表(支持时间维度)
+ * @param {Object} query 查询参数
+ * @param {string} query.timeType 时间类型:day-按日, month-按月, year-按年
+ */
 export function listCaMeterD(query) {
   return request({
     url: '/ems/caMeterD/list',
@@ -9,6 +13,9 @@ export function listCaMeterD(query) {
   })
 }
 
+/**
+ * 查询碳计量全部数据(用于统计,支持时间维度)
+ */
 export function listSumCaMeterD(query) {
   return request({
     url: '/ems/caMeterD/list/all',
@@ -17,7 +24,6 @@ export function listSumCaMeterD(query) {
   })
 }
 
-// 查询碳计量日详细
 export function getCaMeterD(id) {
   return request({
     url: '/ems/caMeterD/' + id,
@@ -25,7 +31,6 @@ export function getCaMeterD(id) {
   })
 }
 
-// 新增碳计量日
 export function addCaMeterD(data) {
   return request({
     url: '/ems/caMeterD',
@@ -34,7 +39,6 @@ export function addCaMeterD(data) {
   })
 }
 
-// 修改碳计量日
 export function updateCaMeterD(data) {
   return request({
     url: '/ems/caMeterD',
@@ -43,7 +47,6 @@ export function updateCaMeterD(data) {
   })
 }
 
-// 删除碳计量日
 export function delCaMeterD(id) {
   return request({
     url: '/ems/caMeterD/' + id,

+ 0 - 91
ems-ui-cloud/src/api/mgr/elecUseH.js

@@ -1,91 +0,0 @@
-import request from '@/utils/request'
-
-// 查询用能计量-小时列表
-export function listH(query) {
-  return request({
-    url: '/ems/elec/use/hour/list',
-    method: 'get',
-    params: query
-  })
-}
-
-export function listHSum(query) {
-  return request({
-    url: '/ems/elec/use/hour/listSum',
-    method: 'get',
-    params: query
-  })
-}
-
-// 根据设施查询能耗统计
-export function sumByFacsH(query) {
-  return request({
-    url: '/ems/elec/use/hour/sumByFacs',
-    method: 'get',
-    params: query
-  })
-}
-//能耗总览统计
-export function sumBySubCategoryH(query) {
-  return request({
-    url: '/ems/elec/use/hour/sumBySubCategory',
-    method: 'get',
-    params: query
-  })
-}
-
-// 查询用能计量-小时详细
-export function getH(id) {
-  return request({
-    url: '/ems/elec/use/hour/' + id,
-    method: 'get'
-  })
-}
-
-// 新增用能计量-小时
-export function addH(data) {
-  return request({
-    url: '/ems/elec/use/hour',
-    method: 'post',
-    data: data
-  })
-}
-
-// 修改用能计量-小时
-export function updateH(data) {
-  return request({
-    url: '/ems/elec/use/hour',
-    method: 'put',
-    data: data
-  })
-}
-
-// 删除用能计量-小时
-export function delH(id) {
-  return request({
-    url: '/ems/elec/use/hour/' + id,
-    method: 'delete'
-  })
-}
-
-export function getPowerData(query) {
-  return request({
-    url: '/ems/object/loadIndex/min/15/list',
-    method: 'get',
-    params: query
-  })
-}
-export function getPowerMaxLoad(query) {
-  return request({
-    url: '/ems/object/loadIndex/min/15/getMaxLoad',
-    method: 'get',
-    params: query
-  })
-}
-export function getPowerDayMaxLoad(query) {
-  return request({
-    url: '/ems/object/loadIndex/day/getMaxLoad',
-    method: 'get',
-    params: query
-  })
-}

+ 31 - 0
ems-ui-cloud/src/api/prediction/forecastConsume.js

@@ -43,6 +43,7 @@ export function delForecastConsume(id) {
   })
 }
 
+// 查询每日汇总趋势数据(支持层级汇总)
 export function listForecastConsumeDateRange(query) {
   return request({
     url: '/ems/forecastConsume/cal/dateRange',
@@ -50,3 +51,33 @@ export function listForecastConsumeDateRange(query) {
     params: query
   })
 }
+
+// ============ 新增接口 ============
+
+// 获取预测汇总统计(包含子节点汇总)
+export function getForecastConsumeSummary(query) {
+  return request({
+    url: '/ems/forecastConsume/summary',
+    method: 'get',
+    params: query
+  })
+}
+
+// 获取每日趋势数据
+export function getForecastConsumeDailyTrend(query) {
+  return request({
+    url: '/ems/forecastConsume/dailyTrend',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出预测数据
+export function exportForecastConsume(query) {
+  return request({
+    url: '/ems/forecastConsume/export',
+    method: 'post',
+    params: query,
+    responseType: 'blob'
+  })
+}

Разница между файлами не показана из-за своего большого размера
+ 1477 - 692
ems-ui-cloud/src/views/analysis/power/consume.vue


+ 242 - 224
ems-ui-cloud/src/views/ca/emission.vue

@@ -55,9 +55,9 @@
                   <i class="el-icon-data-analysis"></i>
                 </div>
                 <div class="card-content">
-                  <div class="stat-label">当月累计排放</div>
+                  <div class="stat-label">{{ getStatLabel('total') }}</div>
                   <div class="stat-value">
-                    <span class="number">{{ monthTotal || 0 }}</span>
+                    <span class="number">{{ totalEmission || 0 }}</span>
                     <span class="unit">kg</span>
                   </div>
                 </div>
@@ -69,9 +69,9 @@
                   <i class="el-icon-s-marketing"></i>
                 </div>
                 <div class="card-content">
-                  <div class="stat-label">日均排放量</div>
+                  <div class="stat-label">{{ getStatLabel('average') }}</div>
                   <div class="stat-value">
-                    <span class="number">{{ dailyAverage || 0 }}</span>
+                    <span class="number">{{ avgEmission || 0 }}</span>
                     <span class="unit">kg</span>
                   </div>
                 </div>
@@ -103,10 +103,17 @@
           <div class="content-header">
             <div class="header-left">
               <h3 class="section-title">碳排放明细</h3>
+              <!-- 时间维度切换 -->
+              <el-radio-group v-model="timeType" size="small" @change="handleTimeTypeChange" class="time-type-switch">
+                <el-radio-button label="day">按日</el-radio-button>
+                <el-radio-button label="month">按月</el-radio-button>
+                <el-radio-button label="year">按年</el-radio-button>
+              </el-radio-group>
             </div>
             <div class="header-right">
               <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
-                <el-form-item>
+                <!-- 按日:日期范围选择 -->
+                <el-form-item v-if="timeType === 'day'">
                   <el-date-picker
                     v-model="dateRange"
                     type="daterange"
@@ -114,8 +121,40 @@
                     range-separator="至"
                     start-placeholder="开始日期"
                     end-placeholder="结束日期"
-                    :picker-options="pickerOptions"
-                    @change="handleDateChange"
+                    :picker-options="dayPickerOptions"
+                    @change="handleDateRangeChange"
+                  />
+                </el-form-item>
+                <!-- 按月:月份范围选择 -->
+                <el-form-item v-if="timeType === 'month'">
+                  <el-date-picker
+                    v-model="monthRange"
+                    type="monthrange"
+                    value-format="yyyy-MM"
+                    range-separator="至"
+                    start-placeholder="开始月份"
+                    end-placeholder="结束月份"
+                    @change="handleMonthRangeChange"
+                  />
+                </el-form-item>
+                <!-- 按年:年份范围选择 -->
+                <el-form-item v-if="timeType === 'year'">
+                  <el-date-picker
+                    v-model="startYear"
+                    type="year"
+                    value-format="yyyy"
+                    placeholder="开始年份"
+                    style="width: 120px"
+                    @change="handleYearChange"
+                  />
+                  <span style="margin: 0 5px;">至</span>
+                  <el-date-picker
+                    v-model="endYear"
+                    type="year"
+                    value-format="yyyy"
+                    placeholder="结束年份"
+                    style="width: 120px"
+                    @change="handleYearChange"
                   />
                 </el-form-item>
                 <el-form-item>
@@ -128,13 +167,8 @@
 
           <!-- 数据表格 -->
           <div class="table-container">
-            <el-table
-              v-loading="loading"
-              :data="caMeterDList"
-              class="data-table"
-              :height="tableHeight"
-            >
-              <el-table-column label="序号" type="index" width="60" align="center" />
+            <el-table v-loading="loading" :data="dataList" class="data-table" :height="tableHeight">
+              <el-table-column label="序号" type="index" width="60" align="center"/>
               <el-table-column label="位置" align="center" prop="areaName" min-width="200">
                 <template slot-scope="scope">
                   <div class="area-info">
@@ -143,9 +177,9 @@
                   </div>
                 </template>
               </el-table-column>
-              <el-table-column label="日期" align="center" prop="date" width="120">
+              <el-table-column :label="getDateColumnLabel()" align="center" prop="date" width="140">
                 <template slot-scope="scope">
-                  <span class="date-text">{{ parseTime(scope.row.date, '{y}-{m}-{d}') }}</span>
+                  <span class="date-text">{{ formatDateByType(scope.row.date) }}</span>
                 </template>
               </el-table-column>
               <el-table-column label="碳排放量" align="center" min-width="180">
@@ -153,29 +187,19 @@
                   <div class="emission-value">
                     <span class="value">{{ (scope.row.caEmissionQuantity || 0).toFixed(2) }}</span>
                     <span class="unit">kg</span>
-                    <el-tag
-                      v-if="calcTrend(scope.row)"
-                      :type="getTrendType(scope.row)"
-                      size="mini"
-                      class="trend-tag"
-                    >
-                      {{ calcTrend(scope.row) }}
-                    </el-tag>
                   </div>
                 </template>
               </el-table-column>
-              <el-table-column label="对比平均值" align="center" min-width="150">
+              <el-table-column label="占比" align="center" min-width="150">
                 <template slot-scope="scope">
                   <div class="compare-value">
                     <el-progress
-                      :percentage="getComparePercent(scope.row)"
+                      :percentage="getSharePercent(scope.row)"
                       :color="getProgressColor(scope.row)"
                       :stroke-width="6"
                       :show-text="false"
                     />
-                    <span class="percent-text" :style="{color: getProgressColor(scope.row)}">
-                      {{ getCompareText(scope.row) }}
-                    </span>
+                    <span class="percent-text">{{ getSharePercent(scope.row) }}%</span>
                   </div>
                 </template>
               </el-table-column>
@@ -205,50 +229,46 @@
 </template>
 
 <script>
-import { listCaMeterD, qryAvgCaMeterD } from "@/api/ca/caMeterD"
+import { listCaMeterD, listSumCaMeterD, qryAvgCaMeterD } from '@/api/ca/caMeterD'
 import { areaTreeByFacsCategory } from '@/api/basecfg/area'
-import { array2Map } from "@/utils"
+import { array2Map } from '@/utils'
 
 export default {
-  name: "CaMeterD",
+  name: 'CaEmission',
   data() {
     return {
-      // 遮罩层
       loading: true,
-      // 显示搜索条件
       showSearch: true,
-      // 总条数
       total: 0,
-      // 碳计量日表格数据
-      caMeterDList: [],
-      // 表格高度
+      dataList: [],
       tableHeight: 500,
-      // 树形结构相关
       areaOptions: [],
       areaName: '',
       facsCategory: 'Z',
       facsSubCategory: '',
-      defaultProps: {
-        children: "children",
-        label: "label"
-      },
+      defaultProps: { children: 'children', label: 'label' },
       defaultExpandedKeys: [],
-      selectedLabel: '全部',
       // 统计数据
-      monthTotal: 0,
-      dailyAverage: 0,
+      totalEmission: 0,
+      avgEmission: 0,
       trendPercent: 0,
       areaAvgMap: {},
-      // 日期范围
+      // 时间维度类型 - 默认按年
+      timeType: 'year',
+      // 按日:日期范围
       dateRange: [],
-      pickerOptions: {
+      // 按月:月份范围
+      monthRange: [],
+      // 按年:年份范围
+      startYear: '',
+      endYear: '',
+      dayPickerOptions: {
         shortcuts: [{
           text: '本月',
           onClick(picker) {
             const start = new Date()
             start.setDate(1)
-            const end = new Date()
-            picker.$emit('pick', [start, end])
+            picker.$emit('pick', [start, new Date()])
           }
         }, {
           text: '上月',
@@ -269,13 +289,13 @@ export default {
           }
         }]
       },
-      // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
         areaCode: '-1',
         startRecTime: null,
-        endRecTime: null
+        endRecTime: null,
+        timeType: 'year'
       }
     }
   },
@@ -290,48 +310,107 @@ export default {
     window.removeEventListener('resize', this.calculateTableHeight)
   },
   methods: {
-    // 计算表格高度
     calculateTableHeight() {
       this.$nextTick(() => {
-        const windowHeight = window.innerHeight
-        const tableOffset = 420
-        this.tableHeight = windowHeight - tableOffset
+        this.tableHeight = window.innerHeight - 420
       })
     },
 
-    // 获取树节点图标
     getTreeIcon(data) {
-      if (data.facsCategory === 'Z') {
-        return 'el-icon-s-opportunity'
+      if (data.facsCategory === 'Z') return 'el-icon-s-opportunity'
+      if (data.id === '-1') return 'el-icon-s-home'
+      return 'el-icon-office-building'
+    },
+
+    getStatLabel(type) {
+      const labels = {
+        day: { total: '累计排放', average: '日均排放量' },
+        month: { total: '累计排放', average: '月均排放量' },
+        year: { total: '累计排放', average: '年均排放量' }
       }
-      if (data.id === '-1') {
-        return 'el-icon-s-home'
+      return labels[this.timeType]?.[type] || labels.year[type]
+    },
+
+    getDateColumnLabel() {
+      return { day: '日期', month: '月份', year: '年份' }[this.timeType] || '年份'
+    },
+
+    formatDateByType(date) {
+      if (!date) return ''
+      const d = new Date(date)
+      const year = d.getFullYear()
+      const month = String(d.getMonth() + 1).padStart(2, '0')
+      const day = String(d.getDate()).padStart(2, '0')
+      switch (this.timeType) {
+        case 'year':
+          return `${year}年`
+        case 'month':
+          return `${year}年${month}月`
+        default:
+          return `${year}-${month}-${day}`
       }
-      return 'el-icon-office-building'
     },
 
-    // 初始化日期范围
     initDateRange() {
       const today = new Date()
-      const yesterday = new Date(today)
-      yesterday.setDate(yesterday.getDate() - 1)
+      const currentYear = today.getFullYear()
+
+      this.queryParams.startRecTime = null
+      this.queryParams.endRecTime = null
+
+      if (this.timeType === 'day') {
+        const firstDay = new Date(currentYear, today.getMonth(), 1)
+        this.dateRange = [this.formatDate(firstDay), this.formatDate(today)]
+        this.queryParams.startRecTime = this.dateRange[0]
+        this.queryParams.endRecTime = this.dateRange[1]
+      } else if (this.timeType === 'month') {
+        this.monthRange = [`${currentYear}-01`, `${currentYear}-${String(today.getMonth() + 1).padStart(2, '0')}`]
+        this.queryParams.startRecTime = `${this.monthRange[0]}-01`
+        this.queryParams.endRecTime = `${this.monthRange[1]}-31`
+      } else {
+        // 按年:默认最近3年
+        this.startYear = String(currentYear - 2)
+        this.endYear = String(currentYear)
+        this.queryParams.startRecTime = `${this.startYear}-01-01`
+        this.queryParams.endRecTime = `${this.endYear}-12-31`
+      }
+    },
 
-      const firstDayOfMonth = new Date(yesterday.getFullYear(), yesterday.getMonth(), 1)
+    formatDate(date) {
+      const y = date.getFullYear()
+      const m = String(date.getMonth() + 1).padStart(2, '0')
+      const d = String(date.getDate()).padStart(2, '0')
+      return `${y}-${m}-${d}`
+    },
 
-      this.dateRange = [this.formatDate(firstDayOfMonth), this.formatDate(yesterday)]
-      this.queryParams.startRecTime = this.dateRange[0]
-      this.queryParams.endRecTime = this.dateRange[1]
+    handleTimeTypeChange() {
+      this.queryParams.timeType = this.timeType
+      this.queryParams.pageNum = 1
+      this.initDateRange()
+      this.getList()
     },
 
-    // 格式化日期
-    formatDate(date) {
-      const year = date.getFullYear()
-      const month = String(date.getMonth() + 1).padStart(2, '0')
-      const day = String(date.getDate()).padStart(2, '0')
-      return `${year}-${month}-${day}`
+    handleDateRangeChange(val) {
+      if (val && val.length === 2) {
+        this.queryParams.startRecTime = val[0]
+        this.queryParams.endRecTime = val[1]
+      }
+    },
+
+    handleMonthRangeChange(val) {
+      if (val && val.length === 2) {
+        this.queryParams.startRecTime = `${val[0]}-01`
+        this.queryParams.endRecTime = `${val[1]}-31`
+      }
+    },
+
+    handleYearChange() {
+      if (this.startYear && this.endYear) {
+        this.queryParams.startRecTime = `${this.startYear}-01-01`
+        this.queryParams.endRecTime = `${this.endYear}-12-31`
+      }
     },
 
-    // 查询区域列表
     async getAreaList() {
       try {
         const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
@@ -340,15 +419,9 @@ export default {
           label: '全部',
           children: response.data || []
         }]
-
-        // 设置默认展开第一级
         this.defaultExpandedKeys = ['-1']
-
-        // 默认选中全部
         this.$nextTick(() => {
-          if (this.$refs.tree) {
-            this.$refs.tree.setCurrentKey('-1')
-          }
+          if (this.$refs.tree) this.$refs.tree.setCurrentKey('-1')
         })
       } catch (error) {
         console.error('加载区域树失败', error)
@@ -356,19 +429,29 @@ export default {
       }
     },
 
-    // 查询碳计量日列表
     async getList() {
       this.loading = true
       try {
-        const { data } = await qryAvgCaMeterD()
-        this.areaAvgMap = array2Map(data, 'areaCode')
+        const params = {
+          ...this.queryParams,
+          timeType: this.timeType
+        }
 
-        const { rows, total } = await listCaMeterD(this.queryParams)
-        this.caMeterDList = rows || []
+        // 获取分页列表
+        const { rows, total } = await listCaMeterD(params)
+        this.dataList = rows || []
         this.total = total || 0
 
-        // 计算统计数据
-        this.calculateStatistics()
+        // 获取统计数据
+        const statParams = {
+          areaCode: this.queryParams.areaCode,
+          startRecTime: this.queryParams.startRecTime,
+          endRecTime: this.queryParams.endRecTime,
+          timeType: this.timeType
+        }
+        const statRes = await listSumCaMeterD(statParams)
+        const statData = statRes.data || statRes.rows || []
+        this.calculateStatistics(statData)
       } catch (error) {
         console.error('加载数据失败', error)
         this.$message.error('数据加载失败')
@@ -377,149 +460,85 @@ export default {
       }
     },
 
-    // 计算统计数据
-    calculateStatistics() {
-      if (this.caMeterDList.length > 0) {
-        // 计算当月累计
-        this.monthTotal = this.caMeterDList.reduce((sum, item) => {
-          return sum + (item.caEmissionQuantity || 0)
-        }, 0).toFixed(2)
-
-        // 计算日均
-        this.dailyAverage = (this.monthTotal / this.caMeterDList.length).toFixed(2)
-
-        // 计算趋势(对比上月同期)
-        // 这里简化处理,实际应该对比上月同期数据
-        const avgValue = Object.values(this.areaAvgMap)[0]?.caEmissionQuantity || 0
-        if (avgValue > 0) {
-          this.trendPercent = (((this.dailyAverage - avgValue) / avgValue) * 100).toFixed(1)
+    calculateStatistics(dataList) {
+      if (dataList && dataList.length > 0) {
+        this.totalEmission = dataList.reduce((sum, item) => sum + (item.caEmissionQuantity || 0), 0).toFixed(2)
+        this.avgEmission = (this.totalEmission / dataList.length).toFixed(2)
+
+        if (dataList.length >= 2) {
+          const current = dataList[0]?.caEmissionQuantity || 0
+          const previous = dataList[1]?.caEmissionQuantity || 0
+          this.trendPercent = previous > 0 ? (((current - previous) / previous) * 100).toFixed(1) : 0
+        } else {
+          this.trendPercent = 0
         }
+      } else {
+        this.totalEmission = 0
+        this.avgEmission = 0
+        this.trendPercent = 0
       }
     },
 
-    // 计算趋势
-    calcTrend(row) {
-      const { areaCode, caEmissionQuantity } = row
-      if (!this.areaAvgMap[areaCode] || !this.areaAvgMap[areaCode].caEmissionQuantity || !caEmissionQuantity) {
-        return ''
-      }
-      const avg = this.areaAvgMap[areaCode].caEmissionQuantity
-      if (caEmissionQuantity > avg * 1.1) {
-        return '↑高'
-      }
-      if (caEmissionQuantity > avg) {
-        return '↑'
-      }
-      if (caEmissionQuantity < avg * 0.9) {
-        return '↓低'
-      }
-      if (caEmissionQuantity < avg) {
-        return '↓'
-      }
-      return '→'
-    },
-
-    // 获取趋势类型
-    getTrendType(row) {
-      const trend = this.calcTrend(row)
-      if (trend.includes('↑')) return 'danger'
-      if (trend.includes('↓')) return 'success'
-      return 'info'
-    },
-
-    // 获取对比百分比
-    getComparePercent(row) {
-      const { areaCode, caEmissionQuantity } = row
-      if (!this.areaAvgMap[areaCode] || !this.areaAvgMap[areaCode].caEmissionQuantity) {
-        return 0
-      }
-      const avg = this.areaAvgMap[areaCode].caEmissionQuantity
-      return Math.min(100, (caEmissionQuantity / avg * 100).toFixed(0))
-    },
-
-    // 获取对比文本
-    getCompareText(row) {
-      const percent = this.getComparePercent(row)
-      if (percent > 100) return `+${(percent - 100).toFixed(0)}%`
-      if (percent < 100) return `-${(100 - percent).toFixed(0)}%`
-      return '持平'
+    getSharePercent(row) {
+      if (!this.totalEmission || this.totalEmission == 0) return 0
+      return Math.min(100, ((row.caEmissionQuantity || 0) / this.totalEmission * 100).toFixed(0))
     },
 
-    // 获取进度条颜色
     getProgressColor(row) {
-      const percent = this.getComparePercent(row)
-      if (percent > 110) return '#f56c6c'
-      if (percent > 100) return '#e6a23c'
-      if (percent < 90) return '#67c23a'
+      const percent = this.getSharePercent(row)
+      if (percent > 60) return '#f56c6c'
+      if (percent > 40) return '#e6a23c'
       return '#409eff'
     },
 
-    // 获取状态类型
     getStatusType(row) {
-      const percent = this.getComparePercent(row)
-      if (percent > 110) return 'danger'
-      if (percent < 90) return 'success'
+      const percent = this.getSharePercent(row)
+      if (percent > 60) return 'danger'
+      if (percent < 30) return 'success'
       return 'info'
     },
 
-    // 获取状态文本
     getStatusText(row) {
-      const percent = this.getComparePercent(row)
-      if (percent > 110) return '偏高'
-      if (percent < 90) return '良好'
+      const percent = this.getSharePercent(row)
+      if (percent > 60) return '占比高'
+      if (percent < 30) return '占比低'
       return '正常'
     },
 
-    // 获取趋势样式
     getTrendClass() {
       if (this.trendPercent > 0) return 'trend-up'
       if (this.trendPercent < 0) return 'trend-down'
       return 'trend-stable'
     },
 
-    // 获取趋势图标
     getTrendIcon() {
       if (this.trendPercent > 0) return 'el-icon-top'
       if (this.trendPercent < 0) return 'el-icon-bottom'
       return 'el-icon-minus'
     },
 
-    // 处理日期范围变化
-    handleDateChange(val) {
-      if (val && val.length === 2) {
-        this.queryParams.startRecTime = val[0]
-        this.queryParams.endRecTime = val[1]
-      }
-    },
-
-    // 搜索按钮操作
     handleQuery() {
       this.queryParams.pageNum = 1
       this.getList()
     },
 
-    // 重置按钮操作
     resetQuery() {
       this.initDateRange()
       this.handleQuery()
     },
 
-    // 筛选节点
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
 
-    // 过滤树
     filterTree() {
       this.$refs.tree.filter(this.areaName)
     },
 
-    // 节点单击事件
     handleNodeClick(data) {
       this.queryParams.areaCode = data.id
-      this.selectedLabel = data.label
-      this.getList()  // 这会同时获取分页数据和全部统计数据
+      this.getList()
     }
   }
 }
@@ -531,7 +550,6 @@ export default {
   background: #f5f7fa;
   min-height: calc(100vh - 84px);
 
-  // 左侧树形结构
   .head-container {
     background: #fff;
     padding: 15px;
@@ -590,7 +608,6 @@ export default {
               margin-right: 8px;
               font-size: 16px;
               color: #909399;
-              transition: color 0.3s;
             }
           }
 
@@ -608,7 +625,6 @@ export default {
     }
   }
 
-  // 统计卡片
   .statistic-cards {
     margin-bottom: 20px;
 
@@ -638,25 +654,19 @@ export default {
         margin-right: 20px;
       }
 
-      &.total {
-        .card-icon {
-          background: linear-gradient(135deg, #fef3e7 0%, #ffd4a3 100%);
-          color: #e6a23c;
-        }
+      &.total .card-icon {
+        background: linear-gradient(135deg, #fef3e7 0%, #ffd4a3 100%);
+        color: #e6a23c;
       }
 
-      &.average {
-        .card-icon {
-          background: linear-gradient(135deg, #e7f3ff 0%, #b3d8ff 100%);
-          color: #409eff;
-        }
+      &.average .card-icon {
+        background: linear-gradient(135deg, #e7f3ff 0%, #b3d8ff 100%);
+        color: #409eff;
       }
 
-      &.trend {
-        .card-icon {
-          background: linear-gradient(135deg, #f0f9ff 0%, #c6f0ff 100%);
-          color: #00a0e9;
-        }
+      &.trend .card-icon {
+        background: linear-gradient(135deg, #f0f9ff 0%, #c6f0ff 100%);
+        color: #00a0e9;
       }
 
       .card-content {
@@ -700,7 +710,6 @@ export default {
     }
   }
 
-  // 内容区域
   .content-wrapper {
     background: #fff;
     border-radius: 8px;
@@ -714,12 +723,26 @@ export default {
       margin-bottom: 20px;
       padding-bottom: 15px;
       border-bottom: 1px solid #ebeef5;
+      flex-wrap: wrap;
+      gap: 15px;
+
+      .header-left {
+        display: flex;
+        align-items: center;
+        gap: 20px;
+
+        .section-title {
+          font-size: 18px;
+          font-weight: 600;
+          color: #303133;
+          margin: 0;
+        }
 
-      .section-title {
-        font-size: 18px;
-        font-weight: 600;
-        color: #303133;
-        margin: 0;
+        .time-type-switch {
+          ::v-deep .el-radio-button__inner {
+            padding: 7px 15px;
+          }
+        }
       }
 
       ::v-deep .el-form-item {
@@ -729,12 +752,10 @@ export default {
 
     .table-container {
       .data-table {
-        ::v-deep .el-table__header {
-          th {
-            background-color: #f5f7fa;
-            color: #606266;
-            font-weight: 600;
-          }
+        ::v-deep .el-table__header th {
+          background-color: #f5f7fa;
+          color: #606266;
+          font-weight: 600;
         }
 
         ::v-deep .el-table__body {
@@ -774,10 +795,6 @@ export default {
               font-size: 12px;
               color: #909399;
             }
-
-            .trend-tag {
-              margin-left: 8px;
-            }
           }
 
           .compare-value {
@@ -807,24 +824,25 @@ export default {
   }
 }
 
-// 响应式布局
 @media (max-width: 768px) {
   .app-container {
     padding: 10px;
 
-    .statistic-cards {
-      .el-col {
-        margin-bottom: 15px;
-      }
+    .statistic-cards .el-col {
+      margin-bottom: 15px;
     }
 
-    .content-wrapper {
-      .content-header {
+    .content-wrapper .content-header {
+      flex-direction: column;
+      align-items: flex-start;
+
+      .header-left {
         flex-direction: column;
         align-items: flex-start;
+        width: 100%;
 
         .section-title {
-          margin-bottom: 15px;
+          margin-bottom: 10px;
         }
       }
     }

+ 218 - 316
ems-ui-cloud/src/views/ca/emissionCalc.vue

@@ -1,118 +1,90 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
-      <!-- 左侧树形区域 -->
       <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input
-            v-model="areaName"
-            placeholder="请输入区域名称"
-            clearable
-            size="small"
-            prefix-icon="el-icon-search"
-            style="margin-bottom: 20px"
-            @input="filterTree"
+          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
+                    style="margin-bottom: 20px" @input="filterTree"
           />
         </div>
         <div class="head-container tree-container">
-          <el-tree
-            ref="tree"
-            :data="areaOptions"
-            :props="defaultProps"
-            :expand-on-click-node="false"
-            :filter-node-method="filterNode"
-            node-key="id"
-            :default-expanded-keys="defaultExpandedKeys"
-            highlight-current
-            @node-click="handleNodeClick"
+          <el-tree ref="tree" :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
+                   :filter-node-method="filterNode" node-key="id" :default-expanded-keys="defaultExpandedKeys"
+                   highlight-current @node-click="handleNodeClick"
           >
             <span class="custom-tree-node" slot-scope="{ node, data }">
-              <span class="tree-label">
-                <i :class="getTreeIcon(data)" class="tree-icon"></i>
-                {{ node.label }}
-              </span>
-              <el-tag
-                v-if="data.facsCategory === 'Z'"
-                size="mini"
-                effect="plain"
-                class="tree-tag sink"
-              >
-                碳汇
-              </el-tag>
+              <span class="tree-label"><i :class="getTreeIcon(data)" class="tree-icon"></i>{{ node.label }}</span>
+              <el-tag v-if="data.facsCategory === 'Z'" size="mini" effect="plain" class="tree-tag sink">碳汇</el-tag>
             </span>
           </el-tree>
         </div>
       </el-col>
-
-      <!-- 右侧内容区域 -->
       <el-col :span="19" :xs="24">
-        <!-- 统计卡片区域 -->
         <div class="statistic-cards">
           <el-row :gutter="15">
             <el-col :span="8" :xs="24">
               <div class="stat-card total">
-                <div class="card-icon">
-                  <i class="el-icon-s-data"></i>
-                </div>
+                <div class="card-icon"><i class="el-icon-s-data"></i></div>
                 <div class="card-content">
-                  <div class="stat-label">当月累计碳汇</div>
-                  <div class="stat-value">
-                    <span class="number">{{ monthTotalSink || 0 }}</span>
-                    <span class="unit">kg</span>
+                  <div class="stat-label">{{ getStatLabel('total') }}</div>
+                  <div class="stat-value"><span class="number">{{ totalSink || 0 }}</span><span class="unit">kg</span>
                   </div>
                 </div>
               </div>
             </el-col>
             <el-col :span="8" :xs="24">
               <div class="stat-card average">
-                <div class="card-icon">
-                  <i class="el-icon-s-marketing"></i>
-                </div>
+                <div class="card-icon"><i class="el-icon-s-marketing"></i></div>
                 <div class="card-content">
-                  <div class="stat-label">日均碳汇量</div>
-                  <div class="stat-value">
-                    <span class="number">{{ dailyAverageSink || 0 }}</span>
-                    <span class="unit">kg</span>
+                  <div class="stat-label">{{ getStatLabel('average') }}</div>
+                  <div class="stat-value"><span class="number">{{ avgSink || 0 }}</span><span class="unit">kg</span>
                   </div>
                 </div>
               </div>
             </el-col>
             <el-col :span="8" :xs="24">
               <div class="stat-card efficiency">
-                <div class="card-icon">
-                  <i class="el-icon-s-flag"></i>
-                </div>
+                <div class="card-icon"><i class="el-icon-s-flag"></i></div>
                 <div class="card-content">
                   <div class="stat-label">碳汇效率</div>
-                  <div class="stat-value">
-                    <span class="number">{{ sinkEfficiency || 0 }}</span>
-                    <span class="unit">%</span>
-                  </div>
+                  <div class="stat-value"><span class="number">{{ sinkEfficiency || 0 }}</span><span class="unit"
+                  >%</span></div>
                 </div>
               </div>
             </el-col>
           </el-row>
         </div>
-
-        <!-- 数据表格区域 -->
         <div class="content-wrapper">
-          <!-- 标题和搜索区域 -->
           <div class="content-header">
             <div class="header-left">
               <h3 class="section-title">碳汇明细</h3>
+              <el-radio-group v-model="timeType" size="small" @change="handleTimeTypeChange" class="time-type-switch">
+                <el-radio-button label="day">按日</el-radio-button>
+                <el-radio-button label="month">按月</el-radio-button>
+                <el-radio-button label="year">按年</el-radio-button>
+              </el-radio-group>
             </div>
             <div class="header-right">
               <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
-                <el-form-item>
-                  <el-date-picker
-                    v-model="dateRange"
-                    type="daterange"
-                    value-format="yyyy-MM-dd"
-                    range-separator="至"
-                    start-placeholder="开始日期"
-                    end-placeholder="结束日期"
-                    :picker-options="pickerOptions"
-                    @change="handleDateChange"
+                <el-form-item v-if="timeType === 'day'">
+                  <el-date-picker v-model="dateRange" type="daterange" value-format="yyyy-MM-dd" range-separator="至"
+                                  start-placeholder="开始日期" end-placeholder="结束日期"
+                                  @change="handleDateRangeChange"
+                  />
+                </el-form-item>
+                <el-form-item v-if="timeType === 'month'">
+                  <el-date-picker v-model="monthRange" type="monthrange" value-format="yyyy-MM" range-separator="至"
+                                  start-placeholder="开始月份" end-placeholder="结束月份"
+                                  @change="handleMonthRangeChange"
+                  />
+                </el-form-item>
+                <el-form-item v-if="timeType === 'year'">
+                  <el-date-picker v-model="startYear" type="year" value-format="yyyy" placeholder="开始年份"
+                                  style="width: 120px" @change="handleYearChange"
+                  />
+                  <span style="margin: 0 5px;">至</span>
+                  <el-date-picker v-model="endYear" type="year" value-format="yyyy" placeholder="结束年份"
+                                  style="width: 120px" @change="handleYearChange"
                   />
                 </el-form-item>
                 <el-form-item>
@@ -122,36 +94,23 @@
               </el-form>
             </div>
           </div>
-
-          <!-- 数据表格 -->
           <div class="table-container">
-            <el-table
-              v-loading="loading"
-              :data="caMeterDList"
-              class="data-table"
-              :height="tableHeight"
-            >
-              <el-table-column label="序号" type="index" width="60" align="center" />
+            <el-table v-loading="loading" :data="dataList" class="data-table" :height="tableHeight">
+              <el-table-column label="序号" type="index" width="60" align="center"/>
               <el-table-column label="位置" align="center" prop="areaName" min-width="200">
                 <template slot-scope="scope">
-                  <div class="area-info">
-                    <i class="el-icon-location-outline"></i>
-                    <span class="area-name">{{ scope.row.areaName }}</span>
-                  </div>
+                  <div class="area-info"><i class="el-icon-location-outline"></i><span class="area-name"
+                  >{{ scope.row.areaName }}</span></div>
                 </template>
               </el-table-column>
-              <el-table-column label="日期" align="center" prop="date" width="120">
-                <template slot-scope="scope">
-                  <span class="date-text">{{ parseTime(scope.row.date, '{y}-{m}-{d}') }}</span>
+              <el-table-column :label="getDateColumnLabel()" align="center" prop="date" width="140">
+                <template slot-scope="scope"><span class="date-text">{{ formatDateByType(scope.row.date) }}</span>
                 </template>
               </el-table-column>
               <el-table-column label="碳汇量" align="center" min-width="180">
                 <template slot-scope="scope">
-                  <div class="sink-value">
-                    <i class="el-icon-s-opportunity value-icon"></i>
-                    <span class="value">{{ (scope.row.caSinkQuantity || 0).toFixed(2) }}</span>
-                    <span class="unit">kg</span>
-                  </div>
+                  <div class="sink-value"><i class="el-icon-s-opportunity value-icon"></i><span class="value"
+                  >{{ (scope.row.caSinkQuantity || 0).toFixed(2) }}</span><span class="unit">kg</span></div>
                 </template>
               </el-table-column>
               <el-table-column label="碳汇等级" align="center" width="120">
@@ -164,26 +123,16 @@
               <el-table-column label="贡献度" align="center" min-width="150">
                 <template slot-scope="scope">
                   <div class="contribution-value">
-                    <el-progress
-                      :percentage="getContributionPercent(scope.row)"
-                      :color="getProgressColor(scope.row)"
-                      :stroke-width="6"
-                      :show-text="false"
+                    <el-progress :percentage="getContributionPercent(scope.row)" :color="getProgressColor(scope.row)"
+                                 :stroke-width="6" :show-text="false"
                     />
                     <span class="percent-text">{{ getContributionPercent(scope.row) }}%</span>
                   </div>
                 </template>
               </el-table-column>
             </el-table>
-
-            <!-- 分页 -->
-            <pagination
-              v-show="total > 0"
-              :total="total"
-              :page.sync="queryParams.pageNum"
-              :limit.sync="queryParams.pageSize"
-              @pagination="getList"
-              class="pagination-container"
+            <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
+                        :limit.sync="queryParams.pageSize" @pagination="getList" class="pagination-container"
             />
           </div>
         </div>
@@ -193,77 +142,34 @@
 </template>
 
 <script>
-import { listCaMeterD, listSumCaMeterD } from "@/api/ca/caMeterD"
+import { listCaMeterD, listSumCaMeterD } from '@/api/ca/caMeterD'
 import { areaTreeByFacsCategory } from '@/api/basecfg/area'
 
 export default {
-  name: "CaMeterD",
+  name: 'CaSink',
   data() {
     return {
-      // 遮罩层
       loading: true,
-      // 显示搜索条件
       showSearch: true,
-      // 总条数
       total: 0,
-      // 碳计量日表格数据
-      caMeterDList: [],
-      // 表格高度
+      dataList: [],
       tableHeight: 500,
-      // 树形结构相关
       areaOptions: [],
       areaName: '',
       facsCategory: 'Z',
       facsSubCategory: '',
-      defaultProps: {
-        children: "children",
-        label: "label"
-      },
+      defaultProps: { children: 'children', label: 'label' },
       defaultExpandedKeys: [],
-      selectedLabel: '全部',
-      // 统计数据
-      monthTotalSink: 0,
-      dailyAverageSink: 0,
+      totalSink: 0,
+      avgSink: 0,
       sinkEfficiency: 0,
       maxSinkValue: 0,
-      // 日期范围
+      timeType: 'year',
       dateRange: [],
-      pickerOptions: {
-        shortcuts: [{
-          text: '本月',
-          onClick(picker) {
-            const start = new Date()
-            start.setDate(1)
-            const end = new Date()
-            picker.$emit('pick', [start, end])
-          }
-        }, {
-          text: '上月',
-          onClick(picker) {
-            const start = new Date()
-            start.setMonth(start.getMonth() - 1)
-            start.setDate(1)
-            const end = new Date(start.getFullYear(), start.getMonth() + 1, 0)
-            picker.$emit('pick', [start, end])
-          }
-        }, {
-          text: '最近30天',
-          onClick(picker) {
-            const end = new Date()
-            const start = new Date()
-            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
-            picker.$emit('pick', [start, end])
-          }
-        }]
-      },
-      // 查询参数
-      queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        areaCode: '-1',
-        startRecTime: null,
-        endRecTime: null
-      }
+      monthRange: [],
+      startYear: '',
+      endYear: '',
+      queryParams: { pageNum: 1, pageSize: 10, areaCode: '-1', startRecTime: null, endRecTime: null, timeType: 'year' }
     }
   },
   async created() {
@@ -277,159 +183,171 @@ export default {
     window.removeEventListener('resize', this.calculateTableHeight)
   },
   methods: {
-    // 计算表格高度
     calculateTableHeight() {
       this.$nextTick(() => {
-        const windowHeight = window.innerHeight
-        const tableOffset = 420
-        this.tableHeight = windowHeight - tableOffset
+        this.tableHeight = window.innerHeight - 420
       })
     },
-
-    // 获取树节点图标
     getTreeIcon(data) {
-      if (data.facsCategory === 'Z') {
-        return 'el-icon-s-promotion'
+      if (data.facsCategory === 'Z') return 'el-icon-s-promotion'
+      if (data.id === '-1') return 'el-icon-s-home'
+      return 'el-icon-office-building'
+    },
+    getStatLabel(type) {
+      const labels = {
+        day: { total: '累计碳汇', average: '日均碳汇量' },
+        month: { total: '累计碳汇', average: '月均碳汇量' },
+        year: { total: '累计碳汇', average: '年均碳汇量' }
       }
-      if (data.id === '-1') {
-        return 'el-icon-s-home'
+      return labels[this.timeType]?.[type] || labels.year[type]
+    },
+    getDateColumnLabel() {
+      return { day: '日期', month: '月份', year: '年份' }[this.timeType] || '年份'
+    },
+    formatDateByType(date) {
+      if (!date) return ''
+      const d = new Date(date)
+      const y = d.getFullYear()
+      const m = String(d.getMonth() + 1).padStart(2, '0')
+      const day = String(d.getDate()).padStart(2, '0')
+      switch (this.timeType) {
+        case 'year':
+          return `${y}年`
+        case 'month':
+          return `${y}年${m}月`
+        default:
+          return `${y}-${m}-${day}`
       }
-      return 'el-icon-office-building'
     },
-
-    // 初始化日期范围
     initDateRange() {
       const today = new Date()
-      const yesterday = new Date(today)
-      yesterday.setDate(yesterday.getDate() - 1)
-
-      const firstDayOfMonth = new Date(yesterday.getFullYear(), yesterday.getMonth(), 1)
-
-      this.dateRange = [this.formatDate(firstDayOfMonth), this.formatDate(yesterday)]
-      this.queryParams.startRecTime = this.dateRange[0]
-      this.queryParams.endRecTime = this.dateRange[1]
+      const currentYear = today.getFullYear()
+      this.queryParams.startRecTime = null
+      this.queryParams.endRecTime = null
+      if (this.timeType === 'day') {
+        const firstDay = new Date(currentYear, today.getMonth(), 1)
+        this.dateRange = [this.formatDate(firstDay), this.formatDate(today)]
+        this.queryParams.startRecTime = this.dateRange[0]
+        this.queryParams.endRecTime = this.dateRange[1]
+      } else if (this.timeType === 'month') {
+        this.monthRange = [`${currentYear}-01`, `${currentYear}-${String(today.getMonth() + 1).padStart(2, '0')}`]
+        this.queryParams.startRecTime = `${this.monthRange[0]}-01`
+        this.queryParams.endRecTime = `${this.monthRange[1]}-31`
+      } else {
+        this.startYear = String(currentYear - 2)
+        this.endYear = String(currentYear)
+        this.queryParams.startRecTime = `${this.startYear}-01-01`
+        this.queryParams.endRecTime = `${this.endYear}-12-31`
+      }
     },
-
-    // 格式化日期
     formatDate(date) {
-      const year = date.getFullYear()
-      const month = String(date.getMonth() + 1).padStart(2, '0')
-      const day = String(date.getDate()).padStart(2, '0')
-      return `${year}-${month}-${day}`
+      const y = date.getFullYear()
+      const m = String(date.getMonth() + 1).padStart(2, '0')
+      const d = String(date.getDate()).padStart(2, '0')
+      return `${y}-${m}-${d}`
+    },
+    handleTimeTypeChange() {
+      this.queryParams.timeType = this.timeType
+      this.queryParams.pageNum = 1
+      this.initDateRange()
+      this.getList()
+    },
+    handleDateRangeChange(val) {
+      if (val && val.length === 2) {
+        this.queryParams.startRecTime = val[0]
+        this.queryParams.endRecTime = val[1]
+      }
+    },
+    handleMonthRangeChange(val) {
+      if (val && val.length === 2) {
+        this.queryParams.startRecTime = `${val[0]}-01`
+        this.queryParams.endRecTime = `${val[1]}-31`
+      }
+    },
+    handleYearChange() {
+      if (this.startYear && this.endYear) {
+        this.queryParams.startRecTime = `${this.startYear}-01-01`
+        this.queryParams.endRecTime = `${this.endYear}-12-31`
+      }
     },
-
-    // 查询区域列表
     async getAreaList() {
       try {
         const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
-        this.areaOptions = [{
-          id: '-1',
-          label: '全部',
-          children: response.data || []
-        }]
-
-        // 设置默认展开第一级
+        this.areaOptions = [{ id: '-1', label: '全部', children: response.data || [] }]
         this.defaultExpandedKeys = ['-1']
-
-        // 默认选中全部
         this.$nextTick(() => {
-          if (this.$refs.tree) {
-            this.$refs.tree.setCurrentKey('-1')
-          }
+          if (this.$refs.tree) this.$refs.tree.setCurrentKey('-1')
         })
       } catch (error) {
         console.error('加载区域树失败', error)
         this.$message.error('加载区域树失败')
       }
     },
-
-    // 查询碳汇列表
     async getList() {
       this.loading = true
       try {
-        // 获取分页数据用于表格展示
-        const { rows, total } = await listCaMeterD(this.queryParams)
-        this.caMeterDList = rows || []
+        const params = { ...this.queryParams, timeType: this.timeType }
+        const { rows, total } = await listCaMeterD(params)
+        this.dataList = rows || []
         this.total = total || 0
-
-        // 获取全量数据用于统计计算
-        if (listSumCaMeterD) {
-          const allDataParams = {
-            areaCode: this.queryParams.areaCode,
-            startRecTime: this.queryParams.startRecTime,
-            endRecTime: this.queryParams.endRecTime
-          }
-          const allDataRes = await listSumCaMeterD(allDataParams)
-          const allRows = allDataRes.rows || allDataRes.data || []
-          this.calculateStatistics(allRows)
-        } else {
-          // 降级方案:使用当前分页数据
-          this.calculateStatistics(this.caMeterDList)
+        const statParams = {
+          areaCode: this.queryParams.areaCode,
+          startRecTime: this.queryParams.startRecTime,
+          endRecTime: this.queryParams.endRecTime,
+          timeType: this.timeType
         }
+        const statRes = await listSumCaMeterD(statParams)
+        const statData = statRes.data || statRes.rows || []
+        this.calculateStatistics(statData)
       } catch (error) {
         console.error('加载数据失败', error)
         this.$message.error('数据加载失败')
-        this.caMeterDList = []
-        this.total = 0
-        this.resetStatistics()
       } finally {
         this.loading = false
       }
     },
-
-    // 计算统计数据
     calculateStatistics(dataList) {
-      if (dataList.length > 0) {
-        // 计算当月累计碳汇
-        this.monthTotalSink = dataList.reduce((sum, item) => {
-          return sum + (item.caSinkQuantity || 0)
-        }, 0).toFixed(2)
-
-        // 计算日均碳汇
-        this.dailyAverageSink = (this.monthTotalSink / dataList.length).toFixed(2)
-
-        // 计算最大值用于贡献度计算
+      if (dataList && dataList.length > 0) {
+        this.totalSink = dataList.reduce((sum, item) => sum + (item.caSinkQuantity || 0), 0).toFixed(2)
+        this.avgSink = (this.totalSink / dataList.length).toFixed(2)
         this.maxSinkValue = Math.max(...dataList.map(item => item.caSinkQuantity || 0))
-
-        // 计算碳汇效率(示例:假设有目标值)
-        const targetSink = 1000 // 示例目标值
-        this.sinkEfficiency = Math.min(100, ((this.monthTotalSink / targetSink) * 100).toFixed(1))
+        let targetSink = { day: 1000, month: 3000, year: 10000 }[this.timeType] || 10000
+        this.sinkEfficiency = Math.min(100, ((this.totalSink / targetSink) * 100).toFixed(1))
       } else {
-        this.resetStatistics()
+        this.totalSink = 0
+        this.avgSink = 0
+        this.sinkEfficiency = 0
+        this.maxSinkValue = 0
       }
     },
-
-    // 重置统计数据
-    resetStatistics() {
-      this.monthTotalSink = 0
-      this.dailyAverageSink = 0
-      this.sinkEfficiency = 0
-      this.maxSinkValue = 0
-    },
-
-    // 获取碳汇等级类型
     getSinkLevelType(value) {
-      if (value >= 50) return 'success'
-      if (value >= 30) return 'warning'
-      if (value >= 10) return 'info'
+      const thresholds = {
+        day: { high: 50, medium: 30, low: 10 },
+        month: { high: 500, medium: 300, low: 100 },
+        year: { high: 5000, medium: 3000, low: 1000 }
+      }
+      const t = thresholds[this.timeType] || thresholds.year
+      if (value >= t.high) return 'success'
+      if (value >= t.medium) return 'warning'
+      if (value >= t.low) return 'info'
       return 'danger'
     },
-
-    // 获取碳汇等级文本
     getSinkLevelText(value) {
-      if (value >= 50) return '优秀'
-      if (value >= 30) return '良好'
-      if (value >= 10) return '一般'
+      const thresholds = {
+        day: { high: 50, medium: 30, low: 10 },
+        month: { high: 500, medium: 300, low: 100 },
+        year: { high: 5000, medium: 3000, low: 1000 }
+      }
+      const t = thresholds[this.timeType] || thresholds.year
+      if (value >= t.high) return '优秀'
+      if (value >= t.medium) return '良好'
+      if (value >= t.low) return '一般'
       return '较低'
     },
-
-    // 获取贡献度百分比
     getContributionPercent(row) {
       if (this.maxSinkValue === 0) return 0
       return Math.min(100, ((row.caSinkQuantity || 0) / this.maxSinkValue * 100).toFixed(0))
     },
-
-    // 获取进度条颜色
     getProgressColor(row) {
       const percent = this.getContributionPercent(row)
       if (percent >= 80) return '#67c23a'
@@ -437,42 +355,23 @@ export default {
       if (percent >= 40) return '#e6a23c'
       return '#f56c6c'
     },
-
-    // 处理日期范围变化
-    handleDateChange(val) {
-      if (val && val.length === 2) {
-        this.queryParams.startRecTime = val[0]
-        this.queryParams.endRecTime = val[1]
-      }
-    },
-
-    // 搜索按钮操作
     handleQuery() {
       this.queryParams.pageNum = 1
       this.getList()
     },
-
-    // 重置按钮操作
     resetQuery() {
       this.initDateRange()
       this.handleQuery()
     },
-
-    // 筛选节点
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
-
-    // 过滤树
     filterTree() {
       this.$refs.tree.filter(this.areaName)
     },
-
-    // 节点单击事件
     handleNodeClick(data) {
       this.queryParams.areaCode = data.id
-      this.selectedLabel = data.label
       this.getList()
     }
   }
@@ -485,7 +384,6 @@ export default {
   background: #f5f7fa;
   min-height: calc(100vh - 84px);
 
-  // 左侧树形结构
   .head-container {
     background: #fff;
     padding: 15px;
@@ -544,7 +442,6 @@ export default {
               margin-right: 8px;
               font-size: 16px;
               color: #909399;
-              transition: color 0.3s;
             }
           }
 
@@ -562,7 +459,6 @@ export default {
     }
   }
 
-  // 统计卡片
   .statistic-cards {
     margin-bottom: 20px;
 
@@ -592,25 +488,19 @@ export default {
         margin-right: 20px;
       }
 
-      &.total {
-        .card-icon {
-          background: linear-gradient(135deg, #e6f7ff 0%, #91d5ff 100%);
-          color: #1890ff;
-        }
+      &.total .card-icon {
+        background: linear-gradient(135deg, #e6f7ff 0%, #91d5ff 100%);
+        color: #1890ff;
       }
 
-      &.average {
-        .card-icon {
-          background: linear-gradient(135deg, #f6ffed 0%, #b7eb8f 100%);
-          color: #52c41a;
-        }
+      &.average .card-icon {
+        background: linear-gradient(135deg, #f6ffed 0%, #b7eb8f 100%);
+        color: #52c41a;
       }
 
-      &.efficiency {
-        .card-icon {
-          background: linear-gradient(135deg, #fff7e6 0%, #ffd591 100%);
-          color: #fa8c16;
-        }
+      &.efficiency .card-icon {
+        background: linear-gradient(135deg, #fff7e6 0%, #ffd591 100%);
+        color: #fa8c16;
       }
 
       .card-content {
@@ -642,7 +532,6 @@ export default {
     }
   }
 
-  // 内容区域
   .content-wrapper {
     background: #fff;
     border-radius: 8px;
@@ -656,12 +545,26 @@ export default {
       margin-bottom: 20px;
       padding-bottom: 15px;
       border-bottom: 1px solid #ebeef5;
+      flex-wrap: wrap;
+      gap: 15px;
+
+      .header-left {
+        display: flex;
+        align-items: center;
+        gap: 20px;
 
-      .section-title {
-        font-size: 18px;
-        font-weight: 600;
-        color: #303133;
-        margin: 0;
+        .section-title {
+          font-size: 18px;
+          font-weight: 600;
+          color: #303133;
+          margin: 0;
+        }
+
+        .time-type-switch {
+          ::v-deep .el-radio-button__inner {
+            padding: 7px 15px;
+          }
+        }
       }
 
       ::v-deep .el-form-item {
@@ -671,12 +574,10 @@ export default {
 
     .table-container {
       .data-table {
-        ::v-deep .el-table__header {
-          th {
-            background-color: #f5f7fa;
-            color: #606266;
-            font-weight: 600;
-          }
+        ::v-deep .el-table__header th {
+          background-color: #f5f7fa;
+          color: #606266;
+          font-weight: 600;
         }
 
         ::v-deep .el-table__body {
@@ -751,24 +652,25 @@ export default {
   }
 }
 
-// 响应式布局
 @media (max-width: 768px) {
   .app-container {
     padding: 10px;
 
-    .statistic-cards {
-      .el-col {
-        margin-bottom: 15px;
-      }
+    .statistic-cards .el-col {
+      margin-bottom: 15px;
     }
 
-    .content-wrapper {
-      .content-header {
+    .content-wrapper .content-header {
+      flex-direction: column;
+      align-items: flex-start;
+
+      .header-left {
         flex-direction: column;
         align-items: flex-start;
+        width: 100%;
 
         .section-title {
-          margin-bottom: 15px;
+          margin-bottom: 10px;
         }
       }
     }

+ 1584 - 856
ems-ui-cloud/src/views/mgr/poweruse.vue

@@ -1,987 +1,1715 @@
 <template>
-  <div class="app-container">
-    <el-tabs v-model="activeName" @tab-click="tabClick">
-      <el-tab-pane label="总览" name="summery">
-        <div class="First">
-          <div class="time-range-buttons">
-            <el-button :class="{ 'is-active': selectedTimeRange === 'day' }"
-                       @click="changeTimeRange('day')">今日</el-button>
-            <el-button :class="{ 'is-active': selectedTimeRange === 'month' }"
-                       @click="changeTimeRange('month')">本月</el-button>
-            <el-button :class="{ 'is-active': selectedTimeRange === 'year' }"
-                       @click="changeTimeRange('year')">本年</el-button>
-          </div>
-          <div class="first-content">
-            <div>
-              <SubTitle title="能耗统计" />
-              <!-- 图表容器 -->
-              <div ref="sumBySubCategoryChart" style="height: 380px;" />
+  <div class="power-use-container">
+    <el-tabs v-model="activeName" class="main-tabs" @tab-click="handleTabClick">
+      <!-- 总览 Tab -->
+      <el-tab-pane label="总览" name="summary">
+        <div class="summary-section">
+          <!-- 时间范围切换 -->
+          <div class="time-range-bar">
+            <div class="time-buttons">
+              <el-button
+                v-for="item in timeRangeOptions"
+                :key="item.value"
+                :class="{ 'is-active': selectedTimeRange === item.value }"
+                size="small"
+                @click="changeTimeRange(item.value)"
+              >
+                {{ item.label }}
+              </el-button>
             </div>
-            <div>
-              <SubTitle title="能耗总览" />
-              <el-table border stripe show-summary :data="tableData" style="width: 100%;margin-top: 20px;">
-                <el-table-column prop="name" align="center" label="设施名称">
-                </el-table-column>
-                <el-table-column prop="value" align="center" label="能耗(kW·h)">
-                </el-table-column>
-              </el-table>
+            <div class="time-display">
+              <i class="el-icon-time"></i>
+              <span>{{ formatDateRangeDisplay() }}</span>
             </div>
           </div>
-        </div>
 
-      </el-tab-pane>
-      <el-tab-pane v-for="item in facsCategoryOptions" :key="item.code" :label="item.name" :name="item.code">
-        <el-col :span="4" :xs="24">
-          <div class="head-container">
-            <el-input
-              v-model="areaName"
-              placeholder="请输入区域名称"
-              clearable
-              size="small"
-              prefix-icon="el-icon-search"
-              style="margin-bottom: 20px"
-            />
-          </div>
-          <div class="head-container tree-container">
-            <el-tree
-              :data="areaOptions"
-              :props="defaultProps"
-              :expand-on-click-node="false"
-              :filter-node-method="filterNode"
-              ref="tree"
-              node-key="id"
-              default-expand-all
-              highlight-current
-              @node-click="handleNodeClick"
-            >
-              <template #default="{ node, data }">
+          <!-- 统计卡片 -->
+          <div class="stat-cards">
+            <el-row :gutter="16">
+              <el-col :span="6">
+                <el-tooltip
+                  :disabled="!formattedOverview.totalQuantity.isShortened"
+                  :content="'精确值: ' + formattedOverview.totalQuantity.full + ' kW·h'"
+                  placement="top"
+                >
+                  <div class="stat-card primary-card">
+                    <div class="card-icon"><i class="el-icon-lightning"></i></div>
+                    <div class="card-info">
+                      <span class="card-label">总用电量</span>
+                      <span class="card-value">
+                        {{ formattedOverview.totalQuantity.display }}
+                        <span v-if="formattedOverview.totalQuantity.suffix" class="value-suffix">
+                          {{ formattedOverview.totalQuantity.suffix }}
+                        </span>
+                        <small>kW·h</small>
+                      </span>
+                    </div>
+                  </div>
+                </el-tooltip>
+              </el-col>
+              <el-col :span="6">
+                <el-tooltip
+                  :disabled="!formattedOverview.peakQuantity.isShortened"
+                  :content="'精确值: ' + formattedOverview.peakQuantity.full + ' kW·h'"
+                  placement="top"
+                >
+                  <div class="stat-card warning-card">
+                    <div class="card-icon"><i class="el-icon-sunrise"></i></div>
+                    <div class="card-info">
+                      <span class="card-label">峰时用电</span>
+                      <span class="card-value">
+                        {{ formattedOverview.peakQuantity.display }}
+                        <span v-if="formattedOverview.peakQuantity.suffix" class="value-suffix">
+                          {{ formattedOverview.peakQuantity.suffix }}
+                        </span>
+                        <small>kW·h</small>
+                      </span>
+                    </div>
+                  </div>
+                </el-tooltip>
+              </el-col>
+              <el-col :span="6">
+                <el-tooltip
+                  :disabled="!formattedOverview.valleyQuantity.isShortened"
+                  :content="'精确值: ' + formattedOverview.valleyQuantity.full + ' kW·h'"
+                  placement="top"
+                >
+                  <div class="stat-card info-card">
+                    <div class="card-icon"><i class="el-icon-moon"></i></div>
+                    <div class="card-info">
+                      <span class="card-label">谷时用电</span>
+                      <span class="card-value">
+                        {{ formattedOverview.valleyQuantity.display }}
+                        <span v-if="formattedOverview.valleyQuantity.suffix" class="value-suffix">
+                          {{ formattedOverview.valleyQuantity.suffix }}
+                        </span>
+                        <small>kW·h</small>
+                      </span>
+                    </div>
+                  </div>
+                </el-tooltip>
+              </el-col>
+              <el-col :span="6">
                 <el-tooltip
-                  class="tree-node-tooltip"
-                  effect="dark"
-                  :content="data.label"
-                  placement="right"
-                  :disabled="!isTextOverflow(data.label)"
+                  :disabled="!formattedOverview.totalCost.isShortened"
+                  :content="'精确值: ¥' + formattedOverview.totalCost.full"
+                  placement="top"
                 >
-                  <div class="tree-node">
-                    <i class="el-icon-office-building node-icon"></i>
-                    <span class="node-label">{{ data.label }}</span>
+                  <div class="stat-card success-card">
+                    <div class="card-icon"><i class="el-icon-money"></i></div>
+                    <div class="card-info">
+                      <span class="card-label">总电费</span>
+                      <span class="card-value">
+                        ¥{{ formattedOverview.totalCost.display }}
+                        <span v-if="formattedOverview.totalCost.suffix" class="value-suffix">
+                          {{ formattedOverview.totalCost.suffix }}
+                        </span>
+                      </span>
+                    </div>
                   </div>
                 </el-tooltip>
-              </template>
-            </el-tree>
+              </el-col>
+            </el-row>
           </div>
-        </el-col>
-        <el-col :span="20" :xs="24">
-          <div class="container-block">
-            <div class="ctl-container">
-              <SubTitle :title="`设施汇总电耗【${selectedLabel}】`" />
-              <div>
-                <el-select v-model="objCode" placeholder="选择设施" clearable @visible-change="handleObjSelectClick"
-                           @change="getList">
-                  <el-option v-for="item in objOptions" :label="item.objName" :value="item.objCode"
-                             :key="item.objCode" />
-
-                </el-select>
-                <el-date-picker v-model="dateRange" type="datetimerange" @change="getList"
-                                :picker-options="pickerOptions" value-format="yyyy-MM-dd hh:mm:ss" range-separator="至"
-                                start-placeholder="开始日期" end-placeholder="结束日期" :clearable="false" align="right">
-                </el-date-picker>
+
+          <!-- 图表和表格区域 -->
+          <el-row :gutter="20" class="content-row">
+            <el-col :span="14">
+              <div class="chart-panel">
+                <div class="panel-header">
+                  <h3 class="panel-title"><i class="el-icon-pie-chart"></i> 能耗分布</h3>
+                  <el-radio-group v-model="overviewChartType" size="mini">
+                    <el-radio-button label="pie">饼图</el-radio-button>
+                    <el-radio-button label="bar">柱图</el-radio-button>
+                  </el-radio-group>
+                </div>
+                <div class="chart-container" v-loading="overviewLoading">
+                  <div ref="overviewChartRef" class="chart-canvas" style="height: 340px;"></div>
+                </div>
+              </div>
+            </el-col>
+            <el-col :span="10">
+              <div class="table-panel">
+                <div class="panel-header">
+                  <h3 class="panel-title"><i class="el-icon-document"></i> 能耗排行</h3>
+                </div>
+                <div class="table-container" v-loading="overviewLoading">
+                  <el-table
+                    :data="overviewTableData"
+                    stripe
+                    size="small"
+                    max-height="340"
+                    show-summary
+                    :summary-method="getOverviewSummaries"
+                  >
+                    <el-table-column type="index" label="排名" width="60" align="center">
+                      <template slot-scope="scope">
+                        <span :class="getRankClass(scope.$index)">{{ scope.$index + 1 }}</span>
+                      </template>
+                    </el-table-column>
+                    <el-table-column prop="objName" label="设施名称" show-overflow-tooltip/>
+                    <el-table-column prop="quantity" label="用电量(kW·h)" width="120" align="right">
+                      <template slot-scope="scope">
+                        <span class="quantity-text">{{ formatNumber(scope.row.quantity) }}</span>
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="占比" width="100" align="center">
+                      <template slot-scope="scope">
+                        <el-progress
+                          :percentage="getPercentage(scope.row.quantity)"
+                          :stroke-width="6"
+                          :show-text="false"
+                          :color="getProgressColor(getPercentage(scope.row.quantity))"
+                        />
+                        <span class="percentage-text">{{ getPercentage(scope.row.quantity).toFixed(1) }}%</span>
+                      </template>
+                    </el-table-column>
+                  </el-table>
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+      </el-tab-pane>
+
+      <!-- 设施分类 Tabs (动态生成) - 修复:使用独立的 detail-pane 组件或唯一ref -->
+      <el-tab-pane
+        v-for="item in facsCategoryOptions"
+        :key="item.code"
+        :label="item.name"
+        :name="item.code"
+        :lazy="true"
+      >
+        <el-row :gutter="20">
+          <!-- 左侧区域树 -->
+          <el-col :span="5">
+            <div class="tree-panel">
+              <div class="tree-search">
+                <el-input
+                  v-model="areaKeyword"
+                  placeholder="搜索区域"
+                  clearable
+                  size="small"
+                  prefix-icon="el-icon-search"
+                  @input="filterAreaTree"
+                />
+              </div>
+              <div class="tree-content">
+                <el-tree
+                  :ref="'areaTree_' + item.code"
+                  :data="areaTreeData"
+                  :props="treeProps"
+                  :expand-on-click-node="false"
+                  :filter-node-method="filterNode"
+                  node-key="id"
+                  default-expand-all
+                  highlight-current
+                  @node-click="handleAreaNodeClick"
+                >
+                  <template #default="{ node, data }">
+                    <div class="tree-node-item">
+                      <i :class="getTreeNodeIcon(data)" class="node-icon"></i>
+                      <span class="node-label">{{ node.label }}</span>
+                    </div>
+                  </template>
+                </el-tree>
               </div>
             </div>
-            <!-- 表格  -->
-            <el-table v-loading="loading" :data="sumByFacsList" style="width: 100%; margin-top: 10px">
-              <el-table-column label="设施编码" align="center" prop="objCode">
-                <template slot-scope="scope">
-                  <span>{{ scope.row.objCode }}</span>
-                </template>
-              </el-table-column>
-              <el-table-column label="设施名称" align="center" prop="objName">
-                <template slot-scope="scope">
-                  <span>{{ scope.row.objName }}</span>
-                </template>
-              </el-table-column>
-              <el-table-column label="用电量(kW·h)" align="center" prop="elecQuantity">
-                <template slot-scope="scope">
-                  <span>{{ scope.row.quantity }}</span>
-                </template>
-              </el-table-column>
-            </el-table>
-            <SubTitle title="设施时段电耗" style="margin-top: 20px;" />
-            <!--柱状图-->
-            <div class="container-block" style="margin-top: 20px;">
-              <BaseChart width="100%" height="300px" :option="barChartOptions" />
+          </el-col>
+
+          <!-- 右侧内容区 -->
+          <el-col :span="19">
+            <div class="detail-content">
+              <!-- 筛选条件 -->
+              <div class="filter-section">
+                <div class="filter-left">
+                  <span class="current-area">
+                    <i class="el-icon-location-outline"></i> {{ selectedAreaLabel }}
+                  </span>
+                </div>
+                <div class="filter-right">
+                  <el-select
+                    v-model="selectedObjCode"
+                    placeholder="全部设施"
+                    clearable
+                    size="small"
+                    style="width: 180px"
+                    @change="handleFacsChange"
+                  >
+                    <el-option
+                      v-for="opt in facsOptions"
+                      :key="opt.objCode"
+                      :label="opt.objName"
+                      :value="opt.objCode"
+                    />
+                  </el-select>
+                  <el-date-picker
+                    v-model="detailDateRange"
+                    type="datetimerange"
+                    :picker-options="datePickerOptions"
+                    value-format="yyyy-MM-dd HH:mm:ss"
+                    range-separator="至"
+                    start-placeholder="开始时间"
+                    end-placeholder="结束时间"
+                    size="small"
+                    :clearable="false"
+                    style="width: 340px"
+                    @change="loadDetailData"
+                  />
+                </div>
+              </div>
+
+              <!-- 设施汇总表格 -->
+              <div class="detail-table-section">
+                <div class="section-header">
+                  <h3 class="section-title"><i class="el-icon-s-grid"></i> 设施用电汇总</h3>
+                  <div class="section-summary" v-if="facsSummaryList.length > 0">
+                    <span class="summary-item">
+                      <i class="el-icon-files"></i>
+                      共 <em>{{ facsSummaryList.length }}</em> 个设施
+                    </span>
+                    <span class="summary-item">
+                      <i class="el-icon-lightning"></i>
+                      合计 <em>{{ formatNumber(facsSummaryTotal) }}</em> kW·h
+                    </span>
+                  </div>
+                </div>
+                <el-table
+                  v-loading="detailLoading"
+                  :data="facsSummaryList"
+                  stripe
+                  size="small"
+                  max-height="220"
+                  show-summary
+                  :summary-method="getFacsSummaries"
+                >
+                  <el-table-column prop="objCode" label="设施编码" width="150" align="center"/>
+                  <el-table-column prop="objName" label="设施名称" show-overflow-tooltip>
+                    <template slot-scope="scope">
+                      <span class="facs-name">
+                        <i class="el-icon-cpu"></i>
+                        {{ scope.row.objName }}
+                      </span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="用电量(kW·h)" width="150" align="right">
+                    <template slot-scope="scope">
+                      <span class="quantity-value">{{ formatNumber(scope.row.quantity) }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="占比" width="140" align="center">
+                    <template slot-scope="scope">
+                      <div class="percentage-cell">
+                        <el-progress
+                          :percentage="getFacsPercentage(scope.row.quantity)"
+                          :stroke-width="8"
+                          :show-text="false"
+                          :color="getProgressColor(getFacsPercentage(scope.row.quantity))"
+                        />
+                        <span class="percentage-text">{{ getFacsPercentage(scope.row.quantity).toFixed(1) }}%</span>
+                      </div>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </div>
+
+              <!-- 设施时段电耗图表 - 核心修复:使用动态ref -->
+              <div class="detail-chart-section">
+                <div class="section-header">
+                  <h3 class="section-title"><i class="el-icon-data-line"></i> 设施时段电耗对比</h3>
+                  <div class="chart-controls">
+                    <el-radio-group v-model="detailChartType" size="mini" @change="renderDetailChart">
+                      <el-radio-button label="bar">
+                        <i class="el-icon-s-data"></i> 柱图
+                      </el-radio-button>
+                      <el-radio-button label="line">
+                        <i class="el-icon-data-line"></i> 折线
+                      </el-radio-button>
+                    </el-radio-group>
+                    <el-checkbox
+                      v-model="showStacked"
+                      size="mini"
+                      style="margin-left: 12px;"
+                      @change="renderDetailChart"
+                    >
+                      堆叠显示
+                    </el-checkbox>
+                  </div>
+                </div>
+                <div class="chart-container" v-loading="detailChartLoading">
+                  <!-- 关键修复:每个tab使用独立的ref名称 -->
+                  <div
+                    :ref="'detailChart_' + item.code"
+                    class="chart-canvas"
+                    style="height: 320px; width: 100%;"
+                  ></div>
+                  <div v-if="hourlyDataByFacs.length === 0 && !detailChartLoading" class="empty-chart">
+                    <i class="el-icon-document-delete"></i>
+                    <span>暂无数据</span>
+                  </div>
+                </div>
+              </div>
             </div>
-          </div>
-        </el-col>
+          </el-col>
+        </el-row>
       </el-tab-pane>
-
     </el-tabs>
   </div>
 </template>
 
 <script>
-import * as echarts from 'echarts/core';
-import {listByFacs,listFacsMeter} from '@/api/device/elecMeterH'
-import { getPowerData, getPowerMaxLoad} from '@/api/mgr/elecUseH'
-import {getFacsCategorygetByCode} from '@/api/basecfg/emsfacs'
-import {areaTreeByFacsCategory} from '@/api/basecfg/area'
+import * as echarts from 'echarts'
 import dayjs from 'dayjs'
-import {DateTool} from '@/utils/DateTool'
-import {dateFormat} from '@/utils';
-import BaseChart from '@/components/BaseChart/index.vue'
-import SubTitle from '@/components/SubTitle'
-import Block from '@/components/Block/block.vue'
-import Treeselect from '@riophae/vue-treeselect'
-import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+// API 接口 - 确保路径正确
+import { listByFacs, listFacsMeter } from '@/api/device/elecMeterH'
+import { getFacsCategorygetByCode } from '@/api/basecfg/emsfacs'
+import { areaTreeByFacsCategory } from '@/api/basecfg/area'
 
 export default {
   name: 'PowerUse',
-  components: {
-    Treeselect,
-    BaseChart,
-    Block,
-    SubTitle
-  },
-  data () {
-    const today = dateFormat(new Date(), 'yyyy-MM-dd')
+  data() {
     return {
-      activeName: 'summery',
-      // 遮罩层
-      loading: true,
-      // 选中数组
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
-      // 显示搜索条件
-      showSearch: true,
-      // 总条数
-      total: 0,
-      // 用能计量-小时表格数据
-      hList: [],
-      //查能耗
-      sumByFacsList: [],
-      // 弹出层标题
-      title: '',
-      // 是否显示弹出层
-      open: false,
-      facsCategory: undefined,
-      facsSubCategory: undefined,
-
-      // 区域名称
-      areaName: undefined,
-      selectedLabel: '全部',
-      defaultProps: {
-        children: 'children',
-        label: 'label'
+      // ========== 基础状态 ==========
+      activeName: 'summary',
+      selectedTimeRange: 'day',
+      timeRangeOptions: [
+        { label: '今日', value: 'day' },
+        { label: '本月', value: 'month' },
+        { label: '本年', value: 'year' }
+      ],
+
+      // ========== 总览数据 ==========
+      overviewLoading: false,
+      overviewDateRange: [],
+      overviewSummary: {
+        totalQuantity: 0,
+        peakQuantity: 0,
+        valleyQuantity: 0,
+        normalQuantity: 0,
+        totalCost: 0
       },
-      areaOptions: [],
+      overviewTableData: [],
+      overviewChartType: 'pie',
+      overviewChartInstance: null,
+
+      // ========== 设施分类 ==========
       facsCategoryOptions: [],
-      sumBySubCategoryChartOption: {},
-      // 存储图表配置的变量
-      selectedTimeRange: 'day',
-      chartInstance: null,
-      // 用于存储 ECharts 实例
-      totalElecQuantity: 0,
-      objOptions: [],
-      objCode: undefined,
-      dateRange: [dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00), dayjs().format(DateTool.DateFormat.YYYY_MM_DD_23_59_59)],
-      pickerOptions: {
+
+      // ========== 区域树 ==========
+      areaKeyword: '',
+      areaTreeData: [],
+      treeProps: { children: 'children', label: 'label' },
+      selectedAreaCode: '-1',
+      selectedAreaLabel: '全部',
+
+      // ========== 设施筛选 ==========
+      facsOptions: [],
+      selectedObjCode: null,
+
+      // ========== 详情数据 ==========
+      detailLoading: false,
+      detailChartLoading: false,
+      detailDateRange: [],
+      facsSummaryList: [],
+      hourlyDataByFacs: [],
+
+      // ========== 图表配置 ==========
+      detailChartType: 'bar',
+      showStacked: false,
+      detailChartInstance: null,
+
+      // ========== 日期选择器配置 ==========
+      datePickerOptions: {
         shortcuts: [
           {
-            text: '最近一周',
-            onClick (picker) {
-              const end = new Date()
-              const start = new Date()
-              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
-              picker.$emit('pick', [start, end])
+            text: '今日',
+            onClick(picker) {
+              picker.$emit('pick', [
+                dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+                dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
+              ])
             }
-          }, {
-            text: '最近一个月',
-            onClick (picker) {
-              const end = new Date()
-              const start = new Date()
-              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
-              picker.$emit('pick', [start, end])
+          },
+          {
+            text: '昨日',
+            onClick(picker) {
+              picker.$emit('pick', [
+                dayjs().subtract(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+                dayjs().subtract(1, 'day').endOf('day').format('YYYY-MM-DD HH:mm:ss')
+              ])
             }
-          }, {
-            text: '最近三个月',
-            onClick (picker) {
-              const end = new Date()
-              const start = new Date()
-              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
-              picker.$emit('pick', [start, end])
+          },
+          {
+            text: '最近7天',
+            onClick(picker) {
+              picker.$emit('pick', [
+                dayjs().subtract(7, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+                dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
+              ])
+            }
+          },
+          {
+            text: '本月',
+            onClick(picker) {
+              picker.$emit('pick', [
+                dayjs().startOf('month').format('YYYY-MM-DD HH:mm:ss'),
+                dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
+              ])
             }
           }
         ]
       },
-      // 查询参数
+
+      // ========== 查询参数 ==========
       queryParams: {
-        pageNum: 1,
-        pageSize: 10,
         areaCode: '-1',
-        objType: 2,
-        objCode: null,
         facsCategory: 'Z',
         facsSubCategory: null,
-        date: null,
-        time: null,
-        timeIndex: null,
-      },
-      queryObjParams: {
-        refArea: null,
-        facsCategory: null,
-        subCategory: null
-      },
-      // 表单参数
-      form: {},
-      tableData: [],
-      powerDate: [`${today} 00:00:00`, `${today} 23:59:59`],
-      powerChartData: [],
-      powerMaxLoad: '',
-      equipPowerChartData: [],
-      equipPowerMaxLoad: ''
+        meterCls: 45
+      }
+    }
+  },
+
+  computed: {
+    formattedOverview() {
+      return {
+        totalQuantity: this.formatSmartNumber(this.overviewSummary.totalQuantity),
+        peakQuantity: this.formatSmartNumber(this.overviewSummary.peakQuantity),
+        valleyQuantity: this.formatSmartNumber(this.overviewSummary.valleyQuantity),
+        normalQuantity: this.formatSmartNumber(this.overviewSummary.normalQuantity),
+        totalCost: this.formatSmartNumber(this.overviewSummary.totalCost)
+      }
+    },
+    facsSummaryTotal() {
+      return this.facsSummaryList.reduce((sum, item) => sum + (item.quantity || 0), 0)
     }
   },
+
   watch: {
-    // 根据名称筛选区域树
-    areaName (val) {
-      this.$refs.tree.filter(val)
+    overviewChartType() {
+      this.renderOverviewChart()
     }
   },
-  created () {
-    this.facsCategory = 'Z'
-    this.facsSubCategory = ''
-    this.getFacsCategory(this.facsCategory)
-    this.getAreaTree(this.facsCategory, this.facsSubCategory)
-    this.getList()
-    this.getsumByFacsH()
-    this.getSumBySubCategoryH();
-    this.getPowerChart()
+
+  created() {
+    this.initDateRange()
+    this.loadFacsCategories()
+    this.loadOverviewData()
   },
-  computed: {
-    barChartOptions () {
-      const xAxisData = this.hList.map(item => item.time);
-      const seriesData = this.hList.map(item => item.elecQuantity);
-      return {
-        toolbox: {
-          itemGap: 10,
-          itemSize: 16,
-          right: 10,
-          top: 0,
-          show: true,
-          feature: {
-            magicType: {
-              show: true,
-              type: ['bar', 'line']
-            },
-            saveAsImage: {
-              show: true
-            }
+
+  mounted() {
+    window.addEventListener('resize', this.handleResize)
+  },
+
+  beforeDestroy() {
+    window.removeEventListener('resize', this.handleResize)
+    this.disposeCharts()
+  },
+
+  methods: {
+    // ==================== 工具方法 ====================
+    formatSmartNumber(value) {
+      if (value === null || value === undefined) {
+        return { display: '0', suffix: '', full: '0', isShortened: false }
+      }
+      const num = parseFloat(value)
+      if (isNaN(num)) {
+        return { display: '0', suffix: '', full: '0', isShortened: false }
+      }
+      const fullValue = num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
+      const absNum = Math.abs(num)
+      if (absNum >= 100000000) {
+        return { display: (num / 100000000).toFixed(1), suffix: '亿', full: fullValue, isShortened: true }
+      }
+      if (absNum >= 10000) {
+        return { display: (num / 10000).toFixed(1), suffix: '万', full: fullValue, isShortened: true }
+      }
+      if (absNum >= 1000) {
+        return { display: Math.round(num).toLocaleString('zh-CN'), suffix: '', full: fullValue, isShortened: false }
+      }
+      return { display: num.toFixed(2), suffix: '', full: fullValue, isShortened: false }
+    },
+
+    formatNumber(value) {
+      if (value === null || value === undefined) return '0.00'
+      const num = parseFloat(value)
+      if (isNaN(num)) return '0.00'
+      return num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
+    },
+
+    initDateRange() {
+      const today = dayjs()
+      this.overviewDateRange = [
+        today.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+        today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
+      ]
+      this.detailDateRange = [...this.overviewDateRange]
+    },
+
+    changeTimeRange(rangeType) {
+      this.selectedTimeRange = rangeType
+      const today = dayjs()
+      if (rangeType === 'day') {
+        this.overviewDateRange = [
+          today.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+          today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
+        ]
+      } else if (rangeType === 'month') {
+        this.overviewDateRange = [
+          today.startOf('month').format('YYYY-MM-DD HH:mm:ss'),
+          today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
+        ]
+      } else if (rangeType === 'year') {
+        this.overviewDateRange = [
+          today.startOf('year').format('YYYY-MM-DD HH:mm:ss'),
+          today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
+        ]
+      }
+      this.loadOverviewData()
+    },
+
+    formatDateRangeDisplay() {
+      if (!this.overviewDateRange || this.overviewDateRange.length !== 2) return ''
+      return `${dayjs(this.overviewDateRange[0]).format('MM-DD HH:mm')} 至 ${dayjs(this.overviewDateRange[1]).format('MM-DD HH:mm')}`
+    },
+
+    // ==================== 获取图表DOM(核心修复) ====================
+    getDetailChartDom() {
+      const refName = 'detailChart_' + this.activeName
+      const chartRef = this.$refs[refName]
+
+      console.log('[getDetailChartDom] refName:', refName, 'chartRef:', chartRef)
+
+      // v-for 中的 ref 在 Vue2 中返回数组
+      if (Array.isArray(chartRef)) {
+        return chartRef[0]
+      }
+      return chartRef
+    },
+
+    // ==================== 数据加载 ====================
+    async loadFacsCategories() {
+      try {
+        const response = await getFacsCategorygetByCode('Z')
+        this.facsCategoryOptions = response.data?.subtypeList || []
+        console.log('设施分类:', this.facsCategoryOptions)
+      } catch (error) {
+        console.error('加载设施分类失败', error)
+      }
+    },
+
+    async loadOverviewData() {
+      this.overviewLoading = true
+      try {
+        const params = {
+          startRecTime: this.overviewDateRange[0],
+          endRecTime: this.overviewDateRange[1],
+          areaCode: '-1',
+          meterCls: 45,
+          facsCategory: 'Z'
+        }
+        const response = await listByFacs(params)
+        const data = response.data || []
+
+        this.overviewTableData = data
+          .map(item => ({
+            objCode: item.objCode,
+            objName: item.objName,
+            quantity: item.quantity || 0
+          }))
+          .filter(item => item.quantity > 0)
+          .sort((a, b) => b.quantity - a.quantity)
+
+        const totalQuantity = data.reduce((sum, item) => sum + (item.quantity || 0), 0)
+        this.overviewSummary = {
+          totalQuantity: totalQuantity,
+          peakQuantity: 0,
+          valleyQuantity: 0,
+          normalQuantity: 0,
+          totalCost: 0
+        }
+
+        this.$nextTick(() => this.renderOverviewChart())
+      } catch (error) {
+        console.error('加载总览数据失败', error)
+        this.$message.error('数据加载失败')
+      } finally {
+        this.overviewLoading = false
+      }
+    },
+
+    async loadAreaTree() {
+      try {
+        const response = await areaTreeByFacsCategory('Z', this.queryParams.facsSubCategory, false)
+        this.areaTreeData = [{ id: '-1', label: '全部', children: response.data || [] }]
+      } catch (error) {
+        console.error('加载区域树失败', error)
+      }
+    },
+
+    /**
+     * 加载详情数据 - 核心方法
+     */
+    async loadDetailData() {
+      if (!this.queryParams.facsSubCategory) {
+        console.warn('[loadDetailData] facsSubCategory 未设置')
+        return
+      }
+
+      console.log('[loadDetailData] 开始加载, facsSubCategory:', this.queryParams.facsSubCategory)
+
+      this.detailLoading = true
+      this.detailChartLoading = true
+
+      try {
+        const baseParams = {
+          startRecTime: this.detailDateRange[0],
+          endRecTime: this.detailDateRange[1],
+          meterCls: 45,
+          areaCode: this.selectedAreaCode,
+          facsCategory: 'Z',
+          facsSubCategory: this.queryParams.facsSubCategory
+        }
+
+        console.log('[loadDetailData] 请求参数:', baseParams)
+
+        // 1. 加载设施汇总数据
+        const summaryRes = await listByFacs(baseParams)
+        console.log('[loadDetailData] 设施汇总响应:', summaryRes)
+
+        this.facsSummaryList = (summaryRes.data || []).map(item => ({
+          objCode: item.objCode,
+          objName: item.objName,
+          quantity: item.quantity || 0
+        }))
+        console.log('[loadDetailData] 设施汇总数据:', this.facsSummaryList)
+
+        // 更新设施下拉选项
+        this.facsOptions = this.facsSummaryList.map(item => ({
+          objCode: item.objCode,
+          objName: item.objName
+        }))
+
+        this.detailLoading = false
+
+        // 2. 加载时段电耗数据 - 分别获取每个设施的小时数据
+        await this.loadHourlyDataByFacs(baseParams)
+
+        // 3. 渲染图表 - 使用更长的延迟确保DOM已渲染
+        this.$nextTick(() => {
+          setTimeout(() => {
+            console.log('[loadDetailData] 准备渲染图表')
+            this.renderDetailChart()
+          }, 300)
+        })
+      } catch (error) {
+        console.error('加载详情数据失败', error)
+        this.$message.error('数据加载失败')
+      } finally {
+        this.detailLoading = false
+        this.detailChartLoading = false
+      }
+    },
+
+    /**
+     * 分别加载每个设施的小时数据 - 关键修复
+     */
+    async loadHourlyDataByFacs(baseParams) {
+      console.log('[loadHourlyDataByFacs] 开始加载小时数据')
+
+      // 如果选择了具体设施,只查该设施
+      if (this.selectedObjCode) {
+        const params = { ...baseParams, objCode: this.selectedObjCode }
+        console.log('[loadHourlyDataByFacs] 查询单个设施:', this.selectedObjCode)
+
+        const hourlyRes = await listFacsMeter(params)
+        const rawData = hourlyRes.rows || []
+
+        const facs = this.facsSummaryList.find(f => f.objCode === this.selectedObjCode)
+        const facsName = facs ? facs.objName : this.selectedObjCode
+
+        this.hourlyDataByFacs = [{
+          objCode: this.selectedObjCode,
+          objName: facsName,
+          hourlyData: this.processHourlyData(rawData)
+        }]
+        console.log('[loadHourlyDataByFacs] 单设施时段数据:', this.hourlyDataByFacs)
+        return
+      }
+
+      // 没有选择具体设施时,分别获取每个设施的数据
+      const facsListToQuery = this.facsSummaryList.length > 0 ? this.facsSummaryList : []
+      console.log('[loadHourlyDataByFacs] 需要查询的设施列表:', facsListToQuery)
+
+      if (facsListToQuery.length === 0) {
+        // 没有设施时,尝试按区域获取汇总数据
+        const hourlyRes = await listFacsMeter(baseParams)
+        const rawData = hourlyRes.rows || []
+
+        if (rawData.length > 0) {
+          this.hourlyDataByFacs = this.groupByAreaCode(rawData)
+        } else {
+          this.hourlyDataByFacs = []
+        }
+        console.log('[loadHourlyDataByFacs] 汇总时段数据(按区域分组):', this.hourlyDataByFacs)
+        return
+      }
+
+      // 并行请求所有设施的小时数据
+      const hourlyPromises = facsListToQuery.map(async(facs) => {
+        try {
+          const params = { ...baseParams, objCode: facs.objCode }
+          console.log('[loadHourlyDataByFacs] 请求设施小时数据:', facs.objCode)
+
+          const hourlyRes = await listFacsMeter(params)
+          const rawData = hourlyRes.rows || []
+
+          return {
+            objCode: facs.objCode,
+            objName: facs.objName,
+            hourlyData: this.processHourlyData(rawData)
+          }
+        } catch (error) {
+          console.error(`加载设施 ${facs.objCode} 小时数据失败:`, error)
+          return {
+            objCode: facs.objCode,
+            objName: facs.objName,
+            hourlyData: []
           }
+        }
+      })
+
+      const results = await Promise.all(hourlyPromises)
+
+      // 过滤掉没有数据的设施
+      this.hourlyDataByFacs = results.filter(item => item.hourlyData.length > 0)
+      console.log('[loadHourlyDataByFacs] 多设施时段数据:', this.hourlyDataByFacs)
+    },
+
+    /**
+     * 处理小时数据
+     */
+    processHourlyData(rawData) {
+      if (!rawData || rawData.length === 0) return []
+
+      return rawData
+        .map(item => ({
+          time: item.time ? item.time.substring(0, 5) : '',  // "15:00:00" -> "15:00"
+          value: parseFloat(item.elecQuantity) || 0,
+          recordTime: item.recordTime
+        }))
+        .filter(item => item.time)
+        .sort((a, b) => a.time.localeCompare(b.time))
+    },
+
+    /**
+     * 按区域代码分组数据
+     */
+    groupByAreaCode(rawData) {
+      const grouped = {}
+
+      rawData.forEach(item => {
+        const areaCode = item.areaCode || 'unknown'
+        const areaName = item.deviceName || areaCode
+
+        if (!grouped[areaCode]) {
+          grouped[areaCode] = {
+            objCode: areaCode,
+            objName: areaName,
+            hourlyData: []
+          }
+        }
+
+        grouped[areaCode].hourlyData.push({
+          time: item.time ? item.time.substring(0, 5) : '',
+          value: parseFloat(item.elecQuantity) || 0,
+          recordTime: item.recordTime
+        })
+      })
+
+      Object.values(grouped).forEach(area => {
+        area.hourlyData.sort((a, b) => a.time.localeCompare(b.time))
+      })
+
+      return Object.values(grouped)
+    },
+
+    /**
+     * 设施选择变化
+     */
+    handleFacsChange() {
+      this.detailChartLoading = true
+      this.loadHourlyDataByFacs({
+        startRecTime: this.detailDateRange[0],
+        endRecTime: this.detailDateRange[1],
+        meterCls: 45,
+        areaCode: this.selectedAreaCode,
+        facsCategory: 'Z',
+        facsSubCategory: this.queryParams.facsSubCategory
+      }).then(() => {
+        this.detailChartLoading = false
+        this.$nextTick(() => this.renderDetailChart())
+      })
+    },
+
+    // ==================== 图表渲染 ====================
+    renderOverviewChart() {
+      const chartDom = this.$refs.overviewChartRef
+      if (!chartDom || this.overviewTableData.length === 0) return
+      if (chartDom.offsetWidth === 0 || chartDom.offsetHeight === 0) {
+        setTimeout(() => this.renderOverviewChart(), 100)
+        return
+      }
+      if (this.overviewChartInstance) this.overviewChartInstance.dispose()
+      this.overviewChartInstance = echarts.init(chartDom)
+
+      const data = this.overviewTableData.slice(0, 10).map(item => ({
+        name: item.objName,
+        value: item.quantity || 0
+      }))
+
+      const option = this.overviewChartType === 'pie'
+        ? this.getPieChartOption(data)
+        : this.getBarChartOption(data)
+
+      this.overviewChartInstance.setOption(option)
+    },
+
+    getPieChartOption(data) {
+      const total = data.reduce((sum, item) => sum + item.value, 0)
+      return {
+        tooltip: {
+          trigger: 'item',
+          formatter: params => `
+            <div style="padding:8px;">
+              <div style="margin-bottom:4px;">${params.marker} ${params.name}</div>
+              <div style="font-weight:bold;">${this.formatNumber(params.value)} kW·h</div>
+              <div style="color:#999;">占比: ${((params.value / total) * 100).toFixed(1)}%</div>
+            </div>
+          `
         },
+        legend: { type: 'scroll', orient: 'vertical', right: '5%', top: 'middle', textStyle: { fontSize: 12 } },
+        series: [{
+          name: '用电量',
+          type: 'pie',
+          radius: ['40%', '65%'],
+          center: ['40%', '50%'],
+          avoidLabelOverlap: true,
+          itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
+          label: { show: true, formatter: '{b}\n{d}%', fontSize: 11 },
+          labelLine: { length: 15, length2: 10 },
+          emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.3)' } },
+          data: data.map((item, index) => ({ ...item, itemStyle: { color: this.getChartColor(index) } }))
+        }]
+      }
+    },
+
+    getBarChartOption(data) {
+      return {
         tooltip: {
           trigger: 'axis',
-          axisPointer: {
-            type: 'shadow'
-          },
-          formatter: (params) => {
-            var relVal = params[0].name
-            for (var i = 0, l = params.length; i < l; i++) {
-              relVal =
-                relVal +
-                '<br/>' +
-                params[i].marker +
-                params[i].seriesName +
-                '&nbsp;&nbsp;&nbsp;' +
-                params[i].value +
-                'kW·h'
-            }
-            return relVal
-          }
-        },
-        legend: {
-          data: ['用电量']
+          axisPointer: { type: 'shadow' },
+          formatter: params => `
+            <div style="padding:8px;">
+              <div style="margin-bottom:4px;">${params[0].name}</div>
+              <div style="font-weight:bold;color:#4facfe;">${this.formatNumber(params[0].value)} kW·h</div>
+            </div>
+          `
         },
+        grid: { left: '3%', right: '4%', bottom: '15%', top: '10%', containLabel: true },
         xAxis: {
           type: 'category',
-          data: xAxisData
-        },
-        yAxis: {
-          name: 'kW·h(千瓦时)',
-          type: 'value',
+          data: data.map(item => item.name),
+          axisLabel: { rotate: 30, fontSize: 11, interval: 0 }
         },
+        yAxis: { type: 'value', name: 'kW·h', nameTextStyle: { fontSize: 11 } },
         series: [{
           name: '用电量',
           type: 'bar',
-          data: seriesData,
-          barWidth: 20,
+          data: data.map(item => item.value),
+          barMaxWidth: 40,
           itemStyle: {
-            normal: {
-              color: '#6395FA'
-            }
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: '#4facfe' },
+              { offset: 1, color: '#00f2fe' }
+            ]),
+            borderRadius: [4, 4, 0, 0]
           }
         }]
-      };
+      }
     },
-    powerLineOptions () {
-      const xAxisData = this.powerChartData.map(item => `${item.date.substr(5)} ${item.time.substr(0, 5)} `);
-      const yData1 = this.powerChartData.map(item => parseFloat(item.s).toFixed(2));
-      const yData2 = this.powerChartData.map(item => item.p);
-      const yData3 = this.powerChartData.map(item => item.q);
-      return {
-        toolbox: {
-          itemGap: 10,
-          itemSize: 16,
-          right: 10,
-          top: 0,
-          show: true,
-          feature: {
-            magicType: {
-              show: true,
-              type: ['bar', 'line']
-            },
-            saveAsImage: {
-              show: true
-            }
-          }
-        },
+
+    /**
+     * 渲染详情图表 - 多设施对比(核心修复版)
+     */
+    renderDetailChart() {
+      console.log('[renderDetailChart] 开始渲染, activeName:', this.activeName)
+
+      // 使用修复后的方法获取DOM
+      const chartDom = this.getDetailChartDom()
+
+      if (!chartDom) {
+        console.warn('[renderDetailChart] 图表DOM不存在, activeName:', this.activeName)
+        console.log('[renderDetailChart] 当前所有refs:', Object.keys(this.$refs))
+        return
+      }
+
+      console.log('[renderDetailChart] chartDom:', chartDom)
+      console.log('[renderDetailChart] hourlyDataByFacs.length:', this.hourlyDataByFacs.length)
+
+      if (this.hourlyDataByFacs.length === 0) {
+        console.warn('[renderDetailChart] 无时段数据')
+        if (this.detailChartInstance) {
+          this.detailChartInstance.dispose()
+          this.detailChartInstance = null
+        }
+        return
+      }
+
+      // 检查DOM尺寸
+      const width = chartDom.offsetWidth
+      const height = chartDom.offsetHeight
+      console.log('[renderDetailChart] DOM尺寸:', width, 'x', height)
+
+      if (!width || !height) {
+        console.log('[renderDetailChart] DOM尺寸为0,延迟200ms重试')
+        setTimeout(() => this.renderDetailChart(), 200)
+        return
+      }
+
+      // 销毁旧实例
+      if (this.detailChartInstance) {
+        this.detailChartInstance.dispose()
+        this.detailChartInstance = null
+      }
+
+      // 初始化新实例
+      this.detailChartInstance = echarts.init(chartDom)
+      console.log('[renderDetailChart] ECharts实例已创建')
+
+      // 收集所有时间点并排序
+      const allTimes = new Set()
+      this.hourlyDataByFacs.forEach(facs => {
+        facs.hourlyData.forEach(d => allTimes.add(d.time))
+      })
+      const xAxisData = Array.from(allTimes).sort()
+
+      console.log('[renderDetailChart] X轴时间点:', xAxisData)
+
+      // 构建多系列数据
+      const legendData = this.hourlyDataByFacs.map(f => f.objName)
+      const series = this.hourlyDataByFacs.map((facs, index) => {
+        const baseColor = this.getSeriesColor(index)
+
+        // 将小时数据转为Map
+        const hourlyMap = new Map()
+        facs.hourlyData.forEach(d => hourlyMap.set(d.time, d.value))
+
+        // 按统一时间轴生成数据
+        const seriesData = xAxisData.map(time => hourlyMap.get(time) || 0)
+
+        return {
+          name: facs.objName,
+          type: this.detailChartType,
+          stack: this.showStacked ? 'total' : null,
+          data: seriesData,
+          smooth: true,
+          symbol: 'circle',
+          symbolSize: 6,
+          barMaxWidth: 30,
+          barGap: '10%',
+          itemStyle: {
+            color: this.detailChartType === 'bar'
+              ? new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: baseColor },
+                { offset: 1, color: this.adjustColorAlpha(baseColor, 0.6) }
+              ])
+              : baseColor,
+            borderRadius: this.detailChartType === 'bar' ? [4, 4, 0, 0] : 0
+          },
+          lineStyle: this.detailChartType === 'line' ? { width: 2, color: baseColor } : undefined,
+          areaStyle: (this.detailChartType === 'line' && this.showStacked) ? {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: this.adjustColorAlpha(baseColor, 0.4) },
+              { offset: 1, color: this.adjustColorAlpha(baseColor, 0.05) }
+            ])
+          } : undefined
+        }
+      })
+
+      const option = {
         tooltip: {
           trigger: 'axis',
-          axisPointer: {
-            type: 'shadow'
-          },
+          axisPointer: { type: this.detailChartType === 'bar' ? 'shadow' : 'cross' },
+          backgroundColor: 'rgba(50, 50, 50, 0.9)',
+          borderColor: 'transparent',
+          textStyle: { color: '#fff' },
           formatter: (params) => {
-            var relVal = params[0].name
-            for (var i = 0, l = params.length; i < l; i++) {
-              relVal =
-                relVal +
-                '<br/>' +
-                params[i].marker +
-                params[i].seriesName +
-                '&nbsp;&nbsp;&nbsp;' +
-                params[i].value +
-                'kW'
+            let html = `<div style="padding:10px;">
+              <div style="margin-bottom:8px;font-weight:600;border-bottom:1px solid rgba(255,255,255,0.2);padding-bottom:6px;">
+                ${params[0].axisValue}
+              </div>`
+
+            let total = 0
+            params.forEach(p => {
+              total += p.value || 0
+              html += `<div style="margin:6px 0;display:flex;justify-content:space-between;align-items:center;">
+                <span>${p.marker} ${p.seriesName}</span>
+                <span style="font-weight:600;margin-left:20px;">${this.formatNumber(p.value)} kW·h</span>
+              </div>`
+            })
+
+            if (params.length > 1) {
+              html += `<div style="margin-top:8px;padding-top:6px;border-top:1px solid rgba(255,255,255,0.2);display:flex;justify-content:space-between;">
+                <span>合计</span>
+                <span style="font-weight:700;color:#4facfe;">${this.formatNumber(total)} kW·h</span>
+              </div>`
             }
-            return relVal
+
+            html += '</div>'
+            return html
           }
         },
-        xAxis: {
-          type: 'category',
-          data: xAxisData
-        },
-        yAxis: {
-          name: 'kW(千瓦)',
-          type: 'value',
-        },
-        legend: {},
-        dataZoom: [
-          {
-            type: "slider",
-            start: 0,
-            end: 100,
-            height: 10,
-            bottom: '10%',
-            showDetail: false,
-            showDataShadow: false,
-            borderColor: "transparent"
-          },
-          {
-            type: "inside",
-            start: 0,
-            end: 100,
-          },
-        ],
-        series: [{
-          name: '视在功率',
-          type: 'line',
-          data: yData1,
-          markLine: {
-            symbol: 'none',
-            silent: true,
-            lineStyle: {normal: {type: 'dashed'}},
-            label: {position: 'end'},
-            data: [
-              {
-                yAxis: this.powerMaxLoad,
-                lineStyle: {width: 1.656, color: '#ff6367'},
-                label: {
-                  show: true,
-                  textStyle: {
-                    color: '#ff6367'
-                  }
-                }
-              },
-            ]
-          },
-          markPoint: {
-            silent: true,
-            data: [
-              {
-                yAxis: this.powerMaxLoad,
-                x: '100%',
-                symbolSize: 0.1,
-                symbolOffset: [0, 0],
-                label: {
-                  textStyle: {color: '#ff6367'},
-                  fontSize: 12,
-                  position: 'left',
-                  formatter: '最高负荷'
-                }
-              },
-            ]
-          },
+        legend: {
+          data: legendData,
+          top: 5,
+          textStyle: { fontSize: 12, color: '#606266' },
+          itemWidth: 20,
+          itemHeight: 10
         },
-          {
-            name: '有功功率',
-            type: 'line',
-            data: yData2,
-          },
-          {
-            name: '无功功率',
-            type: 'line',
-            data: yData3,
-          },
-        ]
-      };
-    },
-    equipPowerLineOptions () {
-      const xAxisData = this.equipPowerChartData.map(item => `${item.date.substr(5)} ${item.time.substr(0, 5)} `);
-      const yData1 = this.equipPowerChartData.map(item => parseFloat(item.s).toFixed(2));
-      const yData2 = this.equipPowerChartData.map(item => item.p);
-      const yData3 = this.equipPowerChartData.map(item => item.q);
-      return {
         toolbox: {
-          itemGap: 10,
-          itemSize: 16,
           right: 10,
           top: 0,
-          show: true,
+          itemSize: 16,
           feature: {
-            magicType: {
-              show: true,
-              type: ['bar', 'line']
-            },
-            saveAsImage: {
-              show: true
-            }
-          }
-        },
-        legend: {},
-        tooltip: {
-          trigger: 'axis',
-          axisPointer: {
-            type: 'shadow'
-          },
-          formatter: (params) => {
-            var relVal = params[0].name
-            for (var i = 0, l = params.length; i < l; i++) {
-              relVal =
-                relVal +
-                '<br/>' +
-                params[i].marker +
-                params[i].seriesName +
-                '&nbsp;&nbsp;&nbsp;' +
-                params[i].value +
-                'kW'
-            }
-            return relVal
+            magicType: { show: true, type: ['bar', 'line'] },
+            saveAsImage: { show: true, name: '设施时段电耗对比' }
           }
         },
+        grid: { left: '3%', right: '4%', bottom: '10%', top: '18%', containLabel: true },
         xAxis: {
           type: 'category',
-          data: xAxisData
+          data: xAxisData,
+          axisLine: { lineStyle: { color: '#e0e0e0' } },
+          axisTick: { show: false },
+          axisLabel: { fontSize: 11, color: '#606266', rotate: xAxisData.length > 12 ? 45 : 0 }
         },
         yAxis: {
-          name: 'kW(千瓦)',
+          name: 'kW·h(千瓦时)',
           type: 'value',
+          nameTextStyle: { fontSize: 11, color: '#909399' },
+          axisLine: { show: false },
+          axisTick: { show: false },
+          splitLine: { lineStyle: { color: '#f0f0f0', type: 'dashed' } },
+          axisLabel: { fontSize: 11, color: '#606266' }
         },
-        dataZoom: [
-          {
-            type: "slider",
-            start: 0,
-            end: 100,
-            height: 10,
-            bottom: '10%',
-            showDetail: false,
-            showDataShadow: false,
-            borderColor: "transparent"
-          },
-          {
-            type: "inside",
-            start: 0,
-            end: 100,
-          },
-        ],
-        series: [{
-          name: '视在功率',
-          type: 'line',
-          data: yData1,
-          markLine: {
-            symbol: 'none',
-            silent: true,
-            lineStyle: {normal: {type: 'dashed'}},
-            label: {position: 'end'},
-            data: [
-              {
-                yAxis: this.equipPowerMaxLoad,
-                lineStyle: {width: 1.656, color: '#ff6367'},
-                label: {
-                  show: true,
-                  textStyle: {
-                    color: '#ff6367'
-                  }
-                }
-              },
-            ]
-          },
-          markPoint: {
-            silent: true,
-            data: [
-              {
-                yAxis: this.equipPowerMaxLoad,
-                x: '100%',
-                symbolSize: 0.1,
-                symbolOffset: [0, 0],
-                label: {
-                  textStyle: {color: '#ff6367'},
-                  fontSize: 12,
-                  position: 'left',
-                  formatter: '最高负荷'
-                }
-              },
-            ]
-          },
-        },
-          {
-            name: '有功功率',
-            type: 'line',
-            data: yData2,
-          },
-          {
-            name: '无功功率',
-            type: 'line',
-            data: yData3,
-          },
-        ]
-      };
-    }
-  },
-  methods: {
-    // 新增方法:判断文本是否溢出
-    isTextOverflow(text) {
-      // 简单判断:超过一定字符数就显示tooltip
-      // 可以根据实际情况调整这个数值
-      return text && text.length > 8
-    },
-    /**平均功率-15分钟*/
-    getPowerChart () {
-      this.powerMaxLoad = ''
-      this.powerChartData = []
-      const [startRecTime, endRecTime] = this.powerDate
-      const params = {
-        startRecTime,
-        endRecTime,
-        areaCode: '-1',
-        objType: '2',
+        series: series
       }
-      getPowerData({
-        ...params,
-        pageNum: 1,
-        pageSize: 999
-      }).then(({code, rows}) => {
-        if (code === 200) {
-          this.powerChartData = rows
-        }
-      })
-      getPowerMaxLoad(params).then(({code, data}) => {
-        if (code === 200 && data) {
-          this.powerMaxLoad = parseFloat(data.s).toFixed(2)
-        }
-      })
+
+      this.detailChartInstance.setOption(option)
+      console.log('[renderDetailChart] 图表渲染完成,系列数:', series.length, '时间点:', xAxisData.length)
     },
-    getEquipPowerChart () {
-      this.equipPowerMaxLoad = ''
-      this.equipPowerChartData = []
-      const [startRecTime, endRecTime] = this.dateRange
-      const params = {
-        startRecTime,
-        endRecTime,
-        areaCode: '-1',
-        objType: '2',
-        facsCategory: 'Z',
-        facsSubCategory: this.activeName,
-        objCode: this.objCode
-      }
-      getPowerData({
-        ...params,
-        pageNum: 1,
-        pageSize: 999
-      }).then(({code, rows}) => {
-        if (code === 200) {
-          this.equipPowerChartData = rows
-        }
-      })
-      getPowerMaxLoad(params).then(({code, data}) => {
-        if (code === 200) {
-          this.equipPowerMaxLoad = parseFloat(data).toFixed(2)
-        }
-      })
+
+    // ==================== 颜色相关 ====================
+    getChartColor(index) {
+      const colors = ['#4facfe', '#43e97b', '#fa709a', '#fee140', '#30cfd0', '#667eea', '#f093fb', '#f5576c', '#00f2fe', '#38f9d7', '#f77062', '#fe5196']
+      return colors[index % colors.length]
     },
-    /** 查询用能计量-小时列表 */
-    getList () {
-      this.loading = true
-      this.queryParams.objCode = this.objCode
-      const params = {
-        startRecTime: dayjs(this.dateRange[0]).format('YYYY-MM-DD HH:mm:ss'),
-        endRecTime: dayjs(this.dateRange[1]).format('YYYY-MM-DD HH:mm:ss'),
-        meterCls:45,
-        areaCode: this.queryParams.areaCode,
-        facsCategory:this.queryParams.facsCategory,
-        facsSubCategory: this.activeName,
-      };
-      listFacsMeter(params).then(response => {
-        this.hList = response.rows
-        this.total = response.total
-        this.loading = false
-      })
-      this.getsumByFacsH()
-      this.getEquipPowerChart()
-    },
-    /**根据设施查询能耗统计 */
-    getsumByFacsH () {
-      const params = {
-        startRecTime: dayjs(this.dateRange[0]).format('YYYY-MM-DD HH:mm:ss'),
-        endRecTime: dayjs(this.dateRange[1]).format('YYYY-MM-DD HH:mm:ss'),
-        meterCls:45,
-        areaCode: this.queryParams.areaCode,
-        facsCategory:this.queryParams.facsCategory,
-        facsSubCategory: this.activeName,
-      };
-      listByFacs(params).then(response => {
-        this.sumByFacsList = response.data.map(item => ({
-          objCode: item.objCode,
-          objName: item.objName,
-          quantity: item.quantity || 0,
-        }));
-        console.log(" this.sumByFacsList ", this.sumByFacsList )
-      })
+
+    getSeriesColor(index) {
+      const colors = ['#4facfe', '#43e97b', '#fa709a', '#fee140', '#667eea', '#30cfd0', '#f093fb', '#f5576c']
+      return colors[index % colors.length]
     },
 
-    /**时间范围切换按钮点击事件处理器*/
-    changeTimeRange (rangeType) {
-      this.selectedTimeRange = rangeType;
-      let start = dayjs();
-      let end = dayjs();
+    adjustColorAlpha(hex, alpha) {
+      const r = parseInt(hex.slice(1, 3), 16)
+      const g = parseInt(hex.slice(3, 5), 16)
+      const b = parseInt(hex.slice(5, 7), 16)
+      return `rgba(${r}, ${g}, ${b}, ${alpha})`
+    },
 
-      if (rangeType === 'day') {
-        // 日:当前日期的 00:00:00 到 23:59:59
-        start = start.startOf('day');
-        end = end.endOf('day');
-      } else if (rangeType === 'month') {
-        // 月:当前月份的第一天 00:00:00 到 最后一天 23:59:59
-        start = start.date(1).hour(0).minute(0).second(0);
-        end = end.endOf('month').hour(23).minute(59).second(59);
-      } else if (rangeType === 'year') {
-        // 年:当前年份的第一天 00:00:00 到 最后一天 23:59:59
-        start = start.startOf('year');
-        end = end.endOf('year').hour(23).minute(59).second(59);
-      }
-      this.dateRange = [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')];
-      this.getSumBySubCategoryH(); // 重新获取数据
-    },
-
-    /**总览饼图*/
-    getSumBySubCategoryH () {
-      const params = {
-        startRecTime: dayjs(this.dateRange[0]).format('YYYY-MM-DD HH:mm:ss'),
-        endRecTime: dayjs(this.dateRange[1]).format('YYYY-MM-DD HH:mm:ss'),
-        areaCode: this.queryParams.areaCode,
-        meterCls:45,
-        facsCategory:this.queryParams.facsCategory,
-      };
-      listByFacs(params).then(response => {
-        console.log("pie图", response.data)
-        this.tableData = response.data.map(item => ({
-          name: item.objName,
-          value: item.quantity || 0
-        }))
-        console.log("能耗总览",this.tableData)
-        this.processDataForChart(response.data); // 处理数据并生成图表配置
-      })
+    getProgressColor(percentage) {
+      if (percentage >= 30) return '#f56c6c'
+      if (percentage >= 15) return '#e6a23c'
+      return '#409eff'
+    },
+
+    // ==================== 事件处理 ====================
+    handleTabClick(tab) {
+      console.log('[handleTabClick] 切换Tab:', tab.name)
+
+      // 销毁旧图表实例
+      if (this.detailChartInstance) {
+        this.detailChartInstance.dispose()
+        this.detailChartInstance = null
+      }
+
+      if (tab.name === 'summary') {
+        this.$nextTick(() => this.loadOverviewData())
+      } else {
+        this.queryParams.facsSubCategory = tab.name
+        console.log('[handleTabClick] 切换到设施分类:', tab.name)
+
+        this.resetDetailState()
+        this.loadAreaTree()
 
+        // 增加延迟确保 tab-pane 完全渲染
+        this.$nextTick(() => {
+          setTimeout(() => {
+            console.log('[handleTabClick] 开始加载详情数据')
+            this.loadDetailData()
+          }, 400)
+        })
+      }
     },
-    processDataForChart(data) {
-      console.log("data", data);
-      this.totalElecQuantity = data.reduce((sum, item) => sum + (item.quantity || 0), 0);
-      // 处理数据,生成图表配置
-      const seriesData = data.map(item => ({
-        name: item.objName,
-        value: item.quantity || 0,
-        percent: ((item.quantity || 0) / this.totalElecQuantity * 100).toFixed(2),
-      }));
 
-      console.log("seriesData", seriesData);
+    resetDetailState() {
+      this.selectedAreaCode = '-1'
+      this.selectedAreaLabel = '全部'
+      this.selectedObjCode = null
+      this.facsOptions = []
+      this.facsSummaryList = []
+      this.hourlyDataByFacs = []
+      this.detailDateRange = [
+        dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+        dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
+      ]
+    },
 
-      // 设置图表配置
-      this.sumBySubCategoryChartOption = {
-        tooltip: {
-          trigger: 'item',
-          formatter: function(params) {
-            const { name, value, percent } = params;
-            return `<div><h4>${name}</h4><p>${value}kW·h (${percent}%)</p></div>`;
-          }
-        },
-        legend: {
-          orient: 'vertical',
-          top: '5%',
-          left: '5%'
-        },
-        series: [
-          {
-            name: '能耗',
-            type: 'pie',
-            radius: ['35%', '55%'],
-            data: seriesData,
-            emphasis: {
-              itemStyle: {
-                shadowBlur: 10,
-                shadowOffsetX: 0,
-                shadowColor: 'rgba(0, 0, 0, 0.5)',
-              }
-            },
-            label: {
-              show: true,
-              position: 'outside',
-              formatter: '{b}\n{d}%',
-            },
-            labelLine: {
-              show: true,
-              length: 30, // 标签线长度
-              lineStyle: {
-                width: 1,
-                type: 'dashed', // 设置虚线样式
-              }
-            }
-          }
-        ]
-      };
+    handleAreaNodeClick(data) {
+      this.selectedAreaCode = data.id
+      this.selectedAreaLabel = data.label
+      this.selectedObjCode = null
+      this.loadDetailData()
+    },
 
-      // 渲染图表
-      this.$nextTick(() => {
-        const chart = echarts.init(this.$refs.sumBySubCategoryChart);
-        chart.setOption(this.sumBySubCategoryChartOption);
-      });
+    filterAreaTree() {
+      const refName = 'areaTree_' + this.activeName
+      const treeRef = this.$refs[refName]
+      const tree = Array.isArray(treeRef) ? treeRef[0] : treeRef
+      if (tree) {
+        tree.filter(this.areaKeyword)
+      }
     },
-    async getFacsCategory (code) {
-      getFacsCategorygetByCode(code).then(response => {
-        this.facsCategoryOptions = response.data.subtypeList
-      })
+
+    filterNode(value, data) {
+      if (!value) return true
+      return data.label && data.label.indexOf(value) !== -1
     },
-    async getAreaTree (category, subCategory) {
-      await areaTreeByFacsCategory(category, subCategory, false).then(response => {
-        this.areaOptions = [{
-          id: '-1',
-          label: '全部',
-          children: []
-        }].concat(response.data)
-      })
+
+    // ==================== 表格相关 ====================
+    getRankClass(index) {
+      if (index === 0) return 'rank-first'
+      if (index === 1) return 'rank-second'
+      if (index === 2) return 'rank-third'
+      return 'rank-normal'
+    },
+
+    getPercentage(value) {
+      const total = this.overviewSummary.totalQuantity || 0
+      if (total === 0) return 0
+      return ((value || 0) / total) * 100
     },
-    async getFacsObj (areaCode, category, subCategory) {
-      this.queryObjParams.refArea = areaCode
-      this.queryObjParams.facsCategory = category
-      this.queryObjParams.subCategory = subCategory
-
-      // listAllFacs(this.queryObjParams).then(response => {
-      //   this.objOptions = response.data
-      // })
-      const params = {
-        startRecTime: dayjs(this.dateRange[0]).format('YYYY-MM-DD HH:mm:ss'),
-        endRecTime: dayjs(this.dateRange[1]).format('YYYY-MM-DD HH:mm:ss'),
-        meterCls:45,
-        areaCode: this.queryParams.areaCode,
-        facsCategory:this.queryParams.facsCategory,
-        facsSubCategory: this.activeName,
-      };
-      listByFacs(params).then(response => {
-        this.objOptions = response.data
-        console.log(" this.objOptions ",this.objOptions )
+
+    getFacsPercentage(value) {
+      const total = this.facsSummaryTotal || 0
+      if (total === 0) return 0
+      return ((value || 0) / total) * 100
+    },
+
+    getOverviewSummaries({ columns }) {
+      const sums = []
+      columns.forEach((column, index) => {
+        if (index === 0) {
+          sums[index] = ''
+          return
+        }
+        if (index === 1) {
+          sums[index] = '合计'
+          return
+        }
+        if (column.property === 'quantity') {
+          sums[index] = this.formatNumber(this.overviewSummary.totalQuantity)
+          return
+        }
+        sums[index] = ''
       })
+      return sums
     },
-    tabClick () {
-      this.reset()
-      if (this.activeName !== 'summery') {
-        this.dateRange = [dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00), dayjs().format(DateTool.DateFormat.YYYY_MM_DD_23_59_59)]
-        this.queryParams.facsCategory = this.facsCategory
-        this.queryParams.facsSubCategory = this.activeName
-        this.queryParams.pageNum = 1
-        this.selectedLabel = '全部'
-        this.getList()
-      } else {
 
-      }
+    getFacsSummaries({ columns }) {
+      const sums = []
+      columns.forEach((column, index) => {
+        if (index === 0) {
+          sums[index] = '合计'
+          return
+        }
+        if (index === 1) {
+          sums[index] = `${this.facsSummaryList.length} 个设施`
+          return
+        }
+        if (index === 2) {
+          sums[index] = this.formatNumber(this.facsSummaryTotal)
+          return
+        }
+        sums[index] = ''
+      })
+      return sums
     },
-    /** 搜索按钮操作 */
-    handleQuery () {
-      this.queryParams.pageNum = 1
-      this.getList()
-      this.getsumByFacsH()
+
+    getTreeNodeIcon(data) {
+      if (data.id === '-1') return 'el-icon-s-home'
+      if (data.children && data.children.length > 0) return 'el-icon-folder'
+      return 'el-icon-place'
     },
-    handleObjSelectClick () {
-      this.getFacsObj(this.queryParams.areaCode, this.facsCategory, this.activeName)
+
+    // ==================== 生命周期 ====================
+    handleResize() {
+      if (this.overviewChartInstance) this.overviewChartInstance.resize()
+      if (this.detailChartInstance) this.detailChartInstance.resize()
     },
-    // 筛选节点
-    filterNode (value, data) {
-      if (!value) return true
-      return data.label.indexOf(value) !== -1
-    },
-    // 节点单击事件
-    handleNodeClick (data) {
-      this.queryParams.areaCode = data.id
-      this.selectedLabel = data.label
-      this.objCode = null
-      this.handleQuery()
-    },
-    reset () {
-      this.objCode = null
-      this.queryParams.areaCode = '-1'
-      this.queryParams.facsCategory = 'Z'
-      this.queryParams.facsSubCategory = null
-      this.queryParams.objCode = null
-      this.objOptions = []
+
+    disposeCharts() {
+      if (this.overviewChartInstance) {
+        this.overviewChartInstance.dispose()
+        this.overviewChartInstance = null
+      }
+      if (this.detailChartInstance) {
+        this.detailChartInstance.dispose()
+        this.detailChartInstance = null
+      }
     }
   }
 }
 </script>
+
 <style lang="scss" scoped>
-.time-range-buttons {
-  .is-active {
-    background-color: #409eff;
-    color: white;
+.power-use-container {
+  padding: 20px;
+  background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
+  min-height: calc(100vh - 84px);
+
+  .main-tabs {
+    ::v-deep .el-tabs__header {
+      background: #fff;
+      padding: 0 20px;
+      border-radius: 8px 8px 0 0;
+      margin-bottom: 0;
+    }
+
+    ::v-deep .el-tabs__content {
+      background: #fff;
+      padding: 20px;
+      border-radius: 0 0 8px 8px;
+      min-height: 600px;
+    }
+
+    ::v-deep .el-tabs__item {
+      font-size: 15px;
+      font-weight: 500;
+
+      &.is-active {
+        color: #409eff;
+      }
+    }
   }
-}
 
-.first-content {
-  display: flex;
-  margin-top: 20px;
+  .summary-section {
+    .time-range-bar {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding: 12px 16px;
+      background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%);
+      border-radius: 8px;
+      border: 1px solid #ebeef5;
+
+      .time-buttons .el-button {
+        margin-right: 8px;
+        border-radius: 6px;
+
+        &.is-active {
+          background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
+          border-color: #409eff;
+          color: #fff;
+        }
+      }
+
+      .time-display {
+        color: #909399;
+        font-size: 13px;
+
+        i {
+          margin-right: 6px;
+        }
+      }
+    }
+
+    .stat-cards {
+      margin-bottom: 20px;
+
+      .stat-card {
+        background: #fff;
+        border-radius: 10px;
+        padding: 18px;
+        display: flex;
+        align-items: center;
+        border: 1px solid #ebeef5;
+        transition: all 0.3s ease;
+        cursor: default;
+
+        &:hover {
+          transform: translateY(-3px);
+          box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
+        }
+
+        .card-icon {
+          width: 48px;
+          height: 48px;
+          border-radius: 10px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          margin-right: 14px;
+
+          i {
+            font-size: 22px;
+            color: #fff;
+          }
+        }
+
+        .card-info {
+          flex: 1;
+          display: flex;
+          flex-direction: column;
+
+          .card-label {
+            font-size: 13px;
+            color: #909399;
+            margin-bottom: 4px;
+          }
+
+          .card-value {
+            font-size: 24px;
+            font-weight: 700;
+            color: #303133;
+
+            .value-suffix {
+              font-size: 14px;
+              font-weight: 600;
+              color: #606266;
+              margin: 0 2px;
+              padding: 1px 6px;
+              background: rgba(0, 0, 0, 0.05);
+              border-radius: 4px;
+            }
+
+            small {
+              font-size: 12px;
+              font-weight: 400;
+              color: #909399;
+              margin-left: 4px;
+            }
+          }
+        }
 
-  >div:first-child {
-    flex: 3;
+        &.primary-card .card-icon {
+          background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+        }
+
+        &.warning-card .card-icon {
+          background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
+        }
+
+        &.info-card .card-icon {
+          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+        }
+
+        &.success-card .card-icon {
+          background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
+        }
+      }
+    }
+
+    .content-row {
+      .chart-panel, .table-panel {
+        background: #fff;
+        border-radius: 10px;
+        border: 1px solid #ebeef5;
+        overflow: hidden;
+
+        .panel-header {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          padding: 14px 16px;
+          border-bottom: 1px solid #ebeef5;
+          background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
+
+          .panel-title {
+            font-size: 15px;
+            font-weight: 600;
+            color: #303133;
+            margin: 0;
+
+            i {
+              margin-right: 8px;
+              color: #409eff;
+            }
+          }
+        }
+
+        .chart-container, .table-container {
+          padding: 16px;
+        }
+      }
+    }
   }
 
-  >div:last-child {
-    flex: 2;
+  .tree-panel {
+    background: #fff;
+    border-radius: 10px;
+    border: 1px solid #ebeef5;
+    overflow: hidden;
+
+    .tree-search {
+      padding: 12px;
+      border-bottom: 1px solid #ebeef5;
+    }
+
+    .tree-content {
+      max-height: calc(100vh - 280px);
+      overflow-y: auto;
+      padding: 8px;
+
+      .tree-node-item {
+        display: flex;
+        align-items: center;
+        padding: 4px 0;
+
+        .node-icon {
+          margin-right: 8px;
+          color: #409eff;
+        }
+
+        .node-label {
+          font-size: 13px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+      }
+
+      &::-webkit-scrollbar {
+        width: 6px;
+      }
+
+      &::-webkit-scrollbar-thumb {
+        background: #c1c1c1;
+        border-radius: 3px;
+      }
+    }
   }
-}
 
-.container-block {
-  padding-left: 20px;
-}
+  .detail-content {
+    .filter-section {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 12px 16px;
+      background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%);
+      border-radius: 8px;
+      margin-bottom: 16px;
+      border: 1px solid #ebeef5;
 
-.ctl-container {
-  display: flex;
-  justify-content: space-between;
-}
+      .filter-left .current-area {
+        font-size: 15px;
+        font-weight: 600;
+        color: #409eff;
 
-/* 树容器样式 - 统一美化设计 */
-.tree-container {
-  height: calc(100vh - 200px);
-  overflow-y: auto;
-  border: 1px solid #e8e8e8;
-  border-radius: 4px;
-  padding: 10px;
-  background-color: #fafafa;
-}
+        i {
+          margin-right: 6px;
+        }
+      }
 
-/* 树节点样式 */
-.tree-node {
-  display: flex;
-  align-items: center;
-  width: 100%;
-  padding: 2px 0;
-  transition: all 0.3s;
-  cursor: pointer;
-}
+      .filter-right {
+        display: flex;
+        gap: 12px;
+      }
+    }
 
-.node-icon {
-  margin-right: 8px;
-  font-size: 16px;
-  transition: all 0.3s;
-  color: #409EFF;
-}
+    .detail-table-section, .detail-chart-section {
+      background: #fff;
+      border-radius: 10px;
+      border: 1px solid #ebeef5;
+      margin-bottom: 16px;
+      overflow: hidden;
 
-.node-label {
-  flex: 1;
-  font-size: 14px;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
+      .section-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 12px 16px;
+        border-bottom: 1px solid #ebeef5;
+        background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
 
-/* 节点hover效果 */
-.tree-node:hover {
-  background-color: #f0f7ff;
-  border-radius: 4px;
-  padding-left: 4px;
-}
+        .section-title {
+          font-size: 15px;
+          font-weight: 600;
+          color: #303133;
+          margin: 0;
 
-.tree-node:hover .node-icon {
-  color: #2b7bff;
-  transform: scale(1.1);
-}
+          i {
+            margin-right: 8px;
+            color: #409eff;
+          }
+        }
 
-/* 高亮当前选中的节点 */
-.el-tree-node.is-current > .el-tree-node__content .tree-node {
-  background-color: #e6f7ff;
-  border-radius: 4px;
-  padding-left: 4px;
-}
+        .section-summary {
+          display: flex;
+          gap: 20px;
 
-.el-tree-node.is-current > .el-tree-node__content .node-icon {
-  color: #1890ff;
-}
+          .summary-item {
+            font-size: 13px;
+            color: #606266;
 
-/* 覆盖默认的el-tree样式,确保自定义样式生效 */
-.el-tree-node__content {
-  height: auto;
-  padding: 0 !important;
-}
+            i {
+              margin-right: 4px;
+              color: #909399;
+            }
 
-.el-tree-node__content:hover {
-  background-color: transparent;
-}
+            em {
+              font-style: normal;
+              font-weight: 600;
+              color: #409eff;
+              margin: 0 2px;
+            }
+          }
+        }
 
-.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
-  background-color: transparent;
-}
+        .chart-controls {
+          display: flex;
+          align-items: center;
+        }
+      }
 
-/* 树节点展开图标调整 */
-.el-tree-node__expand-icon {
-  color: #909399;
-  font-size: 14px;
-}
+      .chart-container {
+        padding: 16px;
+        position: relative;
+        min-height: 340px;
 
-.el-tree-node__expand-icon:hover {
-  color: #409EFF;
-}
+        .empty-chart {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          color: #909399;
 
-/* 滚动条美化 */
-.tree-container::-webkit-scrollbar {
-  width: 6px;
-}
+          i {
+            font-size: 48px;
+            margin-bottom: 12px;
+          }
 
-.tree-container::-webkit-scrollbar-track {
-  background: #f1f1f1;
-  border-radius: 3px;
-}
+          span {
+            font-size: 14px;
+          }
+        }
+      }
+    }
+
+    .detail-table-section {
+      ::v-deep .el-table {
+        .facs-name {
+          display: flex;
+          align-items: center;
+
+          i {
+            margin-right: 8px;
+            color: #409eff;
+          }
+        }
+      }
+    }
+  }
+
+  .quantity-value {
+    font-weight: 600;
+    color: #409eff;
+  }
 
-.tree-container::-webkit-scrollbar-thumb {
-  background: #c1c1c1;
-  border-radius: 3px;
+  .quantity-text {
+    font-weight: 500;
+  }
+
+  .percentage-cell {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    ::v-deep .el-progress {
+      flex: 1;
+    }
+  }
+
+  .percentage-text {
+    font-size: 11px;
+    color: #909399;
+    min-width: 40px;
+    text-align: right;
+  }
+
+  .rank-first {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    width: 22px;
+    height: 22px;
+    background: linear-gradient(135deg, #ffd700 0%, #ffb800 100%);
+    color: #fff;
+    border-radius: 50%;
+    font-size: 12px;
+    font-weight: bold;
+  }
+
+  .rank-second {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    width: 22px;
+    height: 22px;
+    background: linear-gradient(135deg, #c0c0c0 0%, #a8a8a8 100%);
+    color: #fff;
+    border-radius: 50%;
+    font-size: 12px;
+    font-weight: bold;
+  }
+
+  .rank-third {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    width: 22px;
+    height: 22px;
+    background: linear-gradient(135deg, #cd7f32 0%, #b87333 100%);
+    color: #fff;
+    border-radius: 50%;
+    font-size: 12px;
+    font-weight: bold;
+  }
+
+  .rank-normal {
+    color: #909399;
+    font-size: 13px;
+  }
 }
 
-.tree-container::-webkit-scrollbar-thumb:hover {
-  background: #a8a8a8;
+@media (max-width: 1200px) {
+  .power-use-container .summary-section .content-row .el-col {
+    margin-bottom: 16px;
+  }
 }
 </style>

+ 1 - 6
ems-ui-cloud/src/views/prediction/ca.vue

@@ -100,12 +100,7 @@
               max-height="600px"
               stripe
             >
-              <el-table-column
-                type="index"
-                label="序号"
-                width="60"
-                align="center"
-              />
+
               <el-table-column
                 label="园区名称"
                 align="center"

Разница между файлами не показана из-за своего большого размера
+ 748 - 215
ems-ui-cloud/src/views/prediction/consume.vue


Некоторые файлы не были показаны из-за большого количества измененных файлов