Procházet zdrojové kódy

计量报表重构

learshaw před 1 dnem
rodič
revize
529938a356

+ 562 - 279
ems-ui-cloud/src/views/analysis/report/statement-consume.vue

@@ -1,435 +1,718 @@
 <template>
   <div class="app-container">
-    <el-tabs v-model="activeName" @tab-click="tabClick">
+    <!-- Tab切换 -->
+    <el-tabs v-model="activeName" @tab-click="handleTabClick">
       <el-tab-pane label="区域用能" name="areaConsume">
       </el-tab-pane>
       <el-tab-pane label="设施用能" name="facsConsume">
       </el-tab-pane>
     </el-tabs>
+
     <el-row :gutter="20">
+      <!-- 左侧树形选择器 -->
       <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"
+          <el-input
+            v-model="filterText"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
           />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="objOptions" :props="defaultProps" :expand-on-click-node="false"
-                   :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                   @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;"
+        <div class="head-container tree-container">
+          <el-tree
+            :data="treeOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            ref="tree"
+            node-key="id"
+            default-expand-all
+            highlight-current
+            @node-click="handleNodeClick"
           />
         </div>
       </el-col>
+
+      <!-- 右侧内容区域 -->
       <el-col :span="20" :xs="24">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
-          <!-- 开始时间选择器 -->
+        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px">
+          <!-- 时间范围选择 -->
           <el-form-item label="开始时间" prop="startRecTime">
             <el-date-picker
-                v-model="queryParams.startRecTime"
-                type="datetime"
-                value-format="yyyy-MM-dd HH:00:00"
-                :picker-options="startPickerOptions"
-                placeholder="请选择开始时间"
-                @change="handleTimeChange('startRecTime')"
-            >
-            </el-date-picker>
+              v-model="queryParams.startRecTime"
+              type="datetime"
+              value-format="yyyy-MM-dd HH:mm:ss"
+              :picker-options="startPickerOptions"
+              placeholder="请选择开始时间"
+              @change="handleTimeChange('startRecTime')"
+            />
           </el-form-item>
 
-          <!-- 结束时间选择器 -->
           <el-form-item label="结束时间" prop="endRecTime">
             <el-date-picker
-                v-model="queryParams.endRecTime"
-                type="datetime"
-                value-format="yyyy-MM-dd HH:00:00"
-                :picker-options="endPickerOptions"
-                placeholder="请选择结束时间"
-                @change="handleTimeChange('endRecTime')"
-            >
-            </el-date-picker>
+              v-model="queryParams.endRecTime"
+              type="datetime"
+              value-format="yyyy-MM-dd HH:mm:ss"
+              :picker-options="endPickerOptions"
+              placeholder="请选择结束时间"
+              @change="handleTimeChange('endRecTime')"
+            />
           </el-form-item>
-          <el-form-item>
-            <el-radio-group v-model="tabPosition">
+
+          <!-- 时间维度选择 -->
+          <el-form-item label="统计维度">
+            <el-radio-group v-model="queryParams.timeDimension" @change="handleTimeDimensionChange">
               <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>
           </el-form-item>
+
+          <!-- 操作按钮 -->
           <el-form-item>
             <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
             <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
             <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
           </el-form-item>
-
-          <el-table v-loading="loading" :data="consumeList">
-            <el-table-column label="对象名称" align="center" prop="deviceName"/>
-            <el-table-column label="日期" align="center" prop="date" width="180">
-              <template slot-scope="scope">
-                <span>{{ parseTime(scope.row.date, '{y}-{m}-{d}') }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column label="时间" align="center" prop="time">
-              <template slot-scope="scope">
-                <span>{{ scope.row.time }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column label="用电量(kW·h)" align="center" prop="elecQuantity"/>
-            <el-table-column label="用电花费(元)" align="center" prop="useElecCost"/>
-          </el-table>
-
-          <pagination
-            v-show="total>0"
-            :total="total"
-            :page.sync="queryParams.pageNum"
-            :limit.sync="queryParams.pageSize"
-            @pagination="getConsumeList"
-          />
-
         </el-form>
 
+        <!-- 统计汇总卡片 -->
+        <el-row :gutter="20" style="margin-bottom: 20px">
+          <el-col :span="8">
+            <el-card class="summary-card">
+              <div slot="header">
+                <span>总用电量</span>
+              </div>
+              <div class="summary-value">
+                {{ summary.elecQuantity || 0 }} <span class="unit">kWh</span>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="8">
+            <el-card class="summary-card">
+              <div slot="header">
+                <span>总电费</span>
+              </div>
+              <div class="summary-value">
+                {{ summary.elecCost || 0 }} <span class="unit">元</span>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="8">
+            <el-card class="summary-card">
+              <div slot="header">
+                <span>平均单价</span>
+              </div>
+              <div class="summary-value">
+                {{ calculateAveragePrice() }} <span class="unit">元/kWh</span>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+
+        <!-- 数据表格 -->
+        <el-table v-loading="loading" :data="consumptionList">
+          <el-table-column label="对象名称" align="center" prop="objName" />
+
+          <el-table-column label="统计时间" align="center" width="180">
+            <template slot-scope="scope">
+              <span v-if="queryParams.timeDimension === 'day'">
+                {{ formatDateForDisplay(scope.row.statisticDate) }}
+              </span>
+              <span v-else-if="queryParams.timeDimension === 'month'">
+                {{ scope.row.statisticMonth }}
+              </span>
+              <span v-else-if="queryParams.timeDimension === 'year'">
+                {{ scope.row.statisticYear }}
+              </span>
+            </template>
+          </el-table-column>
+
+          <el-table-column label="用电量(kWh)" align="center" prop="elecQuantity">
+            <template slot-scope="scope">
+              <span>{{ formatNumber(scope.row.elecQuantity) }}</span>
+            </template>
+          </el-table-column>
+
+          <el-table-column label="用电费用(元)" align="center" prop="elecCost">
+            <template slot-scope="scope">
+              <span>{{ formatNumber(scope.row.elecCost, 2) }}</span>
+            </template>
+          </el-table-column>
+
+          <el-table-column label="单价(元/kWh)" align="center">
+            <template slot-scope="scope">
+              <span>{{ calculateUnitPrice(scope.row) }}</span>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 分页 -->
+        <pagination
+          v-show="total > 0"
+          :total="total"
+          :page.sync="queryParams.pageNum"
+          :limit.sync="queryParams.pageSize"
+          @pagination="getConsumptionList"
+        />
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-
 import { getFacsCategoryTree } from '@/api/basecfg/emsfacs'
 import { areaTreeSelect } from '@/api/basecfg/area'
-import Treeselect from '@riophae/vue-treeselect'
-import '@riophae/vue-treeselect/dist/vue-treeselect.css'
-import { listAreaMeter, listFacsMeter } from '@/api/device/elecMeterH'
-import {DateTool} from "@/utils/DateTool";
+import {
+  getAreaConsumptionList,
+  getFacsConsumptionList,
+  getAreaConsumptionSummary,
+  getFacsConsumptionSummary,
+  exportAreaConsumption,
+  exportFacsConsumption,
+  formatQueryParams
+} from '@/api/device/energyConsumption'
 
 export default {
-  name: 'consume',
-  components: { Treeselect },
+  name: 'EnergyConsumption',
   data() {
     return {
-      // 遮罩层
+      // 界面控制
       loading: true,
       activeName: 'areaConsume',
-      // 表单参数
-      areaOptions: [],
-      objOptions: [],
-      areaName: undefined,
+      filterText: '',
+
+      // 树形选择器
+      treeOptions: [],
       defaultProps: {
         children: 'children',
         label: 'label'
       },
-      // 总条数
+
+      // 数据
+      consumptionList: [],
+      summary: {},
       total: 0,
-      tabPosition: 'month',
-      consumeList: [],
+
       // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        areaCode: -1,
-        objType: '1',
+        areaCode: null,
         objCode: null,
+        objType: 1,
         facsCategory: 'Z',
-        startRecTime: this.getFirstDayOfMonth(), // 本月1号
-        endRecTime: this.getTodayEndTime(),    // 当天结束时间
+        timeDimension: 'month',
+        startRecTime: this.getDefaultStartTime(),
+        endRecTime: this.getDefaultEndTime(),
+        orderFlag: 'desc'
       },
+
       // 时间选择器配置
       startPickerOptions: {
         disabledDate: (time) => {
-          // 禁用未来时间和超过90天前的时间
-          const ninetyDaysAgo = new Date();
-          ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
-          return time.getTime() > Date.now() - 8.64e7 || time.getTime() < ninetyDaysAgo.getTime();
-        },
-        selectableRange: this.generateHourRanges()
+          const ninetyDaysAgo = new Date()
+          ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90)
+          return time.getTime() > Date.now() - 8.64e7 || time.getTime() < ninetyDaysAgo.getTime()
+        }
       },
       endPickerOptions: {
         disabledDate: (time) => {
           if (this.queryParams.startRecTime) {
-            // 结束时间不能早于开始时间,不能晚于今天,且不能超过开始时间90天后
-            const startDate = new Date(this.queryParams.startRecTime);
-            const endDateLimit = new Date(startDate);
-            endDateLimit.setDate(endDateLimit.getDate() + 90);
-            endDateLimit.setHours(23, 59, 59);
+            const startDate = new Date(this.queryParams.startRecTime)
+            const endDateLimit = new Date(startDate)
+            endDateLimit.setDate(endDateLimit.getDate() + 90)
 
             return time.getTime() < startDate.getTime() ||
               time.getTime() > endDateLimit.getTime() ||
-              time.getTime() > Date.now() - 8.64e7;
+              time.getTime() > Date.now() - 8.64e7
           }
-          // 禁用未来时间和超过90天前的时间
-          const ninetyDaysAgo = new Date();
-          ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
-          return time.getTime() > Date.now() - 8.64e7 || time.getTime() < ninetyDaysAgo.getTime();
-        },
-        selectableRange: this.generateHourRanges()
+          const ninetyDaysAgo = new Date()
+          ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90)
+          return time.getTime() > Date.now() - 8.64e7 || time.getTime() < ninetyDaysAgo.getTime()
+        }
       }
     }
   },
-  created() {
-    // 初始化时间格式
-    if (this.queryParams.startRecTime) {
-      this.queryParams.startRecTime = this.formatDateTime(this.queryParams.startRecTime);
-    }
-    if (this.queryParams.endRecTime) {
-      this.queryParams.endRecTime = this.formatDateTime(this.queryParams.endRecTime);
-    }
-    this.getAreaList()
-    this.getConsumeList()
-  },
+
   watch: {
-    // 根据名称筛选区域树
-    tabPosition(val) {
-      if (!val) {
-        return;
-      }
-      if (val === 'day') {
-        this.queryParams.startRecTime = DateTool.now(DateTool.DateFormat.YYYY_MM_DD_00_00_00);
-        this.queryParams.endRecTime = DateTool.now(DateTool.DateFormat.YYYY_MM_DD_23_59_59);
-      }
-      if (val === 'month') {
-        this.queryParams.startRecTime = DateTool.thisMonth(DateTool.DateFormat.YYYY_MM_01_00_00_00);
-        this.queryParams.endRecTime = DateTool.now(DateTool.DateFormat.YYYY_MM_DD_23_59_59);
-      }
-      if (val === 'year') {
-        this.queryParams.startRecTime = DateTool.thisYear(DateTool.DateFormat.YYYY_01_01_00_00_00);
-        this.queryParams.endRecTime = DateTool.now(DateTool.DateFormat.YYYY_MM_DD_23_59_59);
-      }
+    filterText(val) {
+      this.$refs.tree.filter(val)
     }
   },
+
+  created() {
+    this.initializeData()
+  },
+
   methods: {
-    tabClick() {
-      this.clear()
+    // 初始化数据
+    async initializeData() {
+      await this.loadTreeData()
+      await this.getConsumptionList()
+      await this.getConsumptionSummary()
+    },
+
+    // Tab切换处理
+    async handleTabClick(tab) {
+      this.clearData()
+
       if (this.activeName === 'areaConsume') {
         this.queryParams.objType = 1
-        this.getAreaList()
-        this.getConsumeList() // 初始化区域用能数据
+        await this.loadAreaTreeData()
       } else if (this.activeName === 'facsConsume') {
         this.queryParams.objType = 2
-        this.getFacsList()
-        this.getConsumeList() // 初始化设施用能数据
+        await this.loadFacsTreeData()
       }
+
+      await this.getConsumptionList()
+      await this.getConsumptionSummary()
     },
 
-    // 表单重置
-    clear() {
-      this.queryParams = {
-        pageNum: 1,
-        pageSize: 10,
-        areaCode: -1,
-        objCode: null,
-        facsCategory: 'Z',
-        startRecTime: this.getFirstDayOfMonth(), // 本月1号
-        endRecTime: this.getTodayEndTime(),    // 当天结束时间
+    // 加载树形数据
+    async loadTreeData() {
+      if (this.activeName === 'areaConsume') {
+        await this.loadAreaTreeData()
+      } else {
+        await this.loadFacsTreeData()
       }
-      this.total = 0
-      this.consumeList = []
     },
-    /** 查询能源区域树列表 */
-    getAreaList() {
-      areaTreeSelect(0, 3).then(response => {
-        this.objOptions = [{
+
+    // 加载区域树数据
+    async loadAreaTreeData() {
+      try {
+        const response = await areaTreeSelect(0, 3)
+        this.treeOptions = [{
           id: '-1',
-          label: '全部',
-          children: []
-        }].concat(response.data)
-      })
+          label: '全部区域',
+          children: response.data || []
+        }]
+      } catch (error) {
+        this.$modal.msgError('加载区域数据失败')
+      }
     },
-    /** 查询能源设施树列表 */
-    getFacsList() {
-      getFacsCategoryTree().then(response => {
-        this.objOptions = this.flattenTreeData(response.data)
-      })
+
+    // 加载设施树数据
+    async loadFacsTreeData() {
+      try {
+        const response = await getFacsCategoryTree()
+        this.treeOptions = this.flattenFacsTreeData(response.data || [])
+      } catch (error) {
+        this.$modal.msgError('加载设施数据失败')
+      }
     },
-    // 核心处理函数:压缩层级并扁平化树形结构
-    flattenTreeData(regions) {
+
+    // 处理设施树数据
+    flattenFacsTreeData(regions) {
       if (!Array.isArray(regions)) return []
 
       return regions.map(region => {
-        // 防御性处理children
         const children = region.children || []
-        if (!Array.isArray(children)) return null
-
-        // 查找Z用能设施节点
         const zFacility = children.find(child => child.id === 'Z')
 
-        // 仅当存在Z节点时处理该区域
         if (zFacility) {
           return {
             ...region,
-            // 直接使用Z节点的子节点作为区域的子节点
             children: (zFacility.children || []).map(child => ({ ...child }))
           }
         }
         return null
       }).filter(Boolean)
     },
-    getConsumeList() {
-      if (this.activeName === 'areaConsume') {
-        this.getAreaConsumeList();
-      } else if (this.activeName === 'facsConsume') {
-        this.getFacsConsumeList();
-      }
-    },
-    getAreaConsumeList() {
+
+    // 获取用能数据列表
+    async getConsumptionList() {
       this.loading = true
-      listAreaMeter(this.queryParams).then(response => {
-        this.consumeList = response.rows
-        this.total = response.total
+
+      try {
+        const query = formatQueryParams(this.queryParams)
+        let response
+
+        if (this.activeName === 'areaConsume') {
+          response = await getAreaConsumptionList(query)
+        } else {
+          response = await getFacsConsumptionList(query)
+        }
+
+        this.consumptionList = response.rows || []
+        this.total = response.total || 0
+      } catch (error) {
+        this.$modal.msgError('获取用能数据失败')
+        this.consumptionList = []
+        this.total = 0
+      } finally {
         this.loading = false
-      })
+      }
     },
-    // 获取设施用能数据
-    getFacsConsumeList() {
-      this.loading = true
-      listFacsMeter(this.queryParams).then(response => {
-        this.consumeList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      })
+
+    // 获取用能汇总数据
+    async getConsumptionSummary() {
+      try {
+        // 为汇总接口构建查询参数
+        const summaryQuery = { ...this.queryParams }
+
+        // 在设施用能模式下,如果选择了具体设施,汇总接口也需要传递objCode
+        // 这样可以显示该设施在时间维度上的汇总值,而不是整个区域的汇总值
+        const query = formatQueryParams(summaryQuery)
+        let response
+
+        if (this.activeName === 'areaConsume') {
+          response = await getAreaConsumptionSummary(query)
+        } else {
+          response = await getFacsConsumptionSummary(query)
+        }
+
+        this.summary = response.data || {}
+      } catch (error) {
+        this.$modal.msgError('获取汇总数据失败')
+        this.summary = {}
+      }
+    },
+
+    // 修复:节点点击处理 - 根据设施树结构处理点击逻辑
+    handleNodeClick(data, node) {
+      // 在设施用能模式下
+      if (this.activeName === 'facsConsume') {
+        // 判断是否为顶级区域节点(如:常泰高速服务区(北区))
+        const isTopLevelAreaNode = !node.parent || node.level === 1
+
+        if (isTopLevelAreaNode) {
+          // 点击顶级区域节点:设置区域代码,清空设施代码
+          // 这样会返回该区域下所有设施的汇总数据
+          this.queryParams.areaCode = data.id
+          this.queryParams.objCode = null
+        } else {
+          // 点击具体设施节点:设置区域代码和设施代码
+          // 区域代码取顶级节点的id
+          this.queryParams.areaCode = this.getTopLevelAreaId(node)
+          this.queryParams.objCode = data.id
+        }
+      } else {
+        // 区域用能模式保持原有逻辑
+        this.queryParams.areaCode = this.getTopLevelId(node)
+        this.queryParams.objCode = data.id === '-1' ? null : data.id
+      }
+
+      this.queryParams.pageNum = 1
+      this.getConsumptionList()
+      this.getConsumptionSummary()
+    },
+
+    // 获取顶级区域节点ID(专门用于设施树)
+    getTopLevelAreaId(node) {
+      // 向上遍历找到顶级区域节点(level=1的节点)
+      let currentNode = node
+      while (currentNode.parent && currentNode.level > 1) {
+        currentNode = currentNode.parent
+      }
+      return currentNode.data.id
+    },
+
+    // 获取顶级节点ID(原有方法,用于区域用能)
+    getTopLevelId(node) {
+      let currentNode = node
+      while (currentNode.parent && currentNode.parent.data.id !== 0 && currentNode.parent.data.id !== '-1') {
+        currentNode = currentNode.parent
+      }
+      return currentNode.data.id === '-1' ? null : currentNode.data.id
+    },
+
+    // 时间维度变化处理
+    handleTimeDimensionChange() {
+      this.adjustTimeRange()
+      this.queryParams.pageNum = 1
+      this.getConsumptionList()
+      this.getConsumptionSummary()
+    },
+
+    // 调整时间范围
+    adjustTimeRange() {
+      const now = new Date()
+
+      switch (this.queryParams.timeDimension) {
+        case 'day':
+          // 当天
+          this.queryParams.startRecTime = this.formatDateTime(this.getTodayStart())
+          this.queryParams.endRecTime = this.formatDateTime(now)
+          break
+        case 'month':
+          // 本月
+          this.queryParams.startRecTime = this.formatDateTime(this.getMonthStart())
+          this.queryParams.endRecTime = this.formatDateTime(now)
+          break
+        case 'year':
+          // 本年
+          this.queryParams.startRecTime = this.formatDateTime(this.getYearStart())
+          this.queryParams.endRecTime = this.formatDateTime(now)
+          break
+      }
+    },
+
+    // 时间变化处理
+    handleTimeChange(field) {
+      if (field === 'startRecTime' && this.queryParams.endRecTime) {
+        const startDate = new Date(this.queryParams.startRecTime)
+        const endDate = new Date(this.queryParams.endRecTime)
+
+        if (endDate < startDate) {
+          this.queryParams.endRecTime = this.formatDateTime(startDate)
+        }
+      }
     },
 
-    /** 搜索按钮操作 */
+    // 搜索处理
     handleQuery() {
       this.queryParams.pageNum = 1
-      this.getConsumeList()
+      this.getConsumptionList()
+      this.getConsumptionSummary()
     },
-    /** 重置按钮操作 */
+
+    // 重置处理
     resetQuery() {
       this.resetForm('queryForm')
+      this.queryParams = {
+        ...this.queryParams,
+        areaCode: null,
+        objCode: null,
+        startRecTime: this.getDefaultStartTime(),
+        endRecTime: this.getDefaultEndTime(),
+        pageNum: 1
+      }
       this.handleQuery()
     },
-    /** 导出按钮操作 */
-    handleExport() {
-      if (this.activeName === 'areaConsume') {
-        this.download('ems/elecMeterH/exportAreaMeter', {
-          ...this.queryParams
-        }, `区域用能_${new Date().getTime()}.xlsx`)
-      } else if (this.activeName === 'facsConsume') {
-        this.download('ems/elecMeterH/exportFacsMeter', {
-          ...this.queryParams
-        }, `设施用能_${new Date().getTime()}.xlsx`)
+
+    // 导出处理
+    async handleExport() {
+      try {
+        this.$modal.loading('正在导出数据,请稍候...')
+        const query = formatQueryParams(this.queryParams)
+
+        let response
+        let filename = ''
+
+        if (this.activeName === 'areaConsume') {
+          response = await exportAreaConsumption(query)
+          filename = `区域用能报表_${this.getCurrentTimeStr()}.xlsx`
+        } else {
+          response = await exportFacsConsumption(query)
+          filename = `设施用能报表_${this.getCurrentTimeStr()}.xlsx`
+        }
+
+        // 处理文件下载
+        this.downloadExcelFile(response, filename)
+
+        this.$modal.closeLoading()
+        this.$modal.msgSuccess('导出成功')
+      } catch (error) {
+        this.$modal.closeLoading()
+        this.$modal.msgError('导出失败:' + (error.message || '未知错误'))
+      }
+    },
+
+    // 下载Excel文件
+    downloadExcelFile(data, filename) {
+      try {
+        let blob
+
+        // 如果返回的是Blob对象,直接使用
+        if (data instanceof Blob) {
+          blob = data
+        }
+        // 如果返回的是ArrayBuffer,转换为Blob
+        else if (data instanceof ArrayBuffer) {
+          blob = new Blob([data], {
+            type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+          })
+        }
+        // 如果是其他格式,尝试转换
+        else {
+          blob = new Blob([data], {
+            type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+          })
+        }
+
+        // 创建下载链接
+        const url = window.URL.createObjectURL(blob)
+        const link = document.createElement('a')
+        link.style.display = 'none'
+        link.href = url
+        link.download = filename
+
+        // 添加到页面并点击下载
+        document.body.appendChild(link)
+        link.click()
+
+        // 清理
+        document.body.removeChild(link)
+        window.URL.revokeObjectURL(url)
+      } catch (error) {
+        console.error('下载文件失败:', error)
+        throw new Error('文件下载失败')
       }
     },
-    // 筛选节点
+
+    // 获取当前时间字符串(用于文件名)
+    getCurrentTimeStr() {
+      const now = new Date()
+      const year = now.getFullYear()
+      const month = String(now.getMonth() + 1).padStart(2, '0')
+      const day = String(now.getDate()).padStart(2, '0')
+      const hours = String(now.getHours()).padStart(2, '0')
+      const minutes = String(now.getMinutes()).padStart(2, '0')
+      const seconds = String(now.getSeconds()).padStart(2, '0')
+
+      return `${year}${month}${day}_${hours}${minutes}${seconds}`
+    },
+
+    // 树过滤
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
-    // 节点点击事件处理
-    handleNodeClick(data, node) {
-      this.queryParams.areaCode = this.getTopLevelId(node)
-      this.queryParams.objCode = data.id
+
+    // 清空数据
+    clearData() {
+      this.consumptionList = []
+      this.summary = {}
+      this.total = 0
+      this.queryParams.areaCode = null
+      this.queryParams.objCode = null
       this.queryParams.pageNum = 1
+    },
 
-      if (this.activeName === 'areaConsume') {
-        this.getAreaConsumeList()
-      } else if (this.activeName === 'facsConsume') {
-        this.getFacsConsumeList()
+    // 计算平均单价
+    calculateAveragePrice() {
+      if (!this.summary.elecQuantity || !this.summary.elecCost) {
+        return '0.00'
       }
+
+      const price = this.summary.elecCost / this.summary.elecQuantity
+      return this.formatNumber(price, 3)
     },
-    // 追溯顶级节点ID的辅助函数
-    getTopLevelId(node) {
-      let currentNode = node;
 
-      while (currentNode.parent && currentNode.parent.id !== 0) {
-        currentNode = currentNode.parent;
+    // 计算单价
+    calculateUnitPrice(row) {
+      if (!row.elecQuantity || !row.elecCost) {
+        return '0.00'
       }
-      return currentNode.data.id;
+
+      const price = row.elecCost / row.elecQuantity
+      return this.formatNumber(price, 3)
     },
-    // 获取本月1号 00:00:00
-    getFirstDayOfMonth() {
-      const date = new Date();
-      date.setDate(1);
-      date.setHours(0, 0, 0, 0);
-      return this.formatDateTime(date);
+
+    // 修复:专门用于显示的日期格式化方法
+    formatDateForDisplay(dateStr) {
+      if (!dateStr) return ''
+
+      // 如果是完整的日期时间字符串,提取日期部分
+      if (dateStr.includes(' ')) {
+        return dateStr.split(' ')[0]
+      }
+
+      // 如果已经是日期格式,直接返回
+      if (dateStr.match(/^\d{4}-\d{2}-\d{2}$/)) {
+        return dateStr
+      }
+
+      // 尝试解析并格式化
+      try {
+        const date = new Date(dateStr)
+        if (isNaN(date.getTime())) return dateStr
+
+        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}`
+      } catch (error) {
+        return dateStr
+      }
     },
 
-    // 获取当天 23:59:59
-    getTodayEndTime() {
-      const date = new Date();
-      date.setHours(23, 59, 59, 999);
-      return this.formatDateTime(date);
+    // 工具方法
+    getDefaultStartTime() {
+      const date = new Date()
+      date.setDate(1)
+      date.setHours(0, 0, 0, 0)
+      return this.formatDateTime(date)
     },
 
-    // 格式化日期时间为 yyyy-MM-dd HH:mm:ss
-    formatDateTime(date) {
-      if (!date) return '';
-      if (typeof date === 'string') {
-        date = new Date(date);
-      }
+    getDefaultEndTime() {
+      return this.formatDateTime(new Date())
+    },
 
-      const year = date.getFullYear();
-      const month = String(date.getMonth() + 1).padStart(2, '0');
-      const day = String(date.getDate()).padStart(2, '0');
-      const hours = String(date.getHours()).padStart(2, '0');
-      const minutes = String(date.getMinutes()).padStart(2, '0');
-      const seconds = String(date.getSeconds()).padStart(2, '0');
+    getTodayStart() {
+      const date = new Date()
+      date.setHours(0, 0, 0, 0)
+      return date
+    },
 
-      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+    getMonthStart() {
+      const date = new Date()
+      date.setDate(1)
+      date.setHours(0, 0, 0, 0)
+      return date
     },
 
-    // 时间选择处理(增强版)
-    handleTimeChange(field) {
-      this.tabPosition = ""
-      if (this.queryParams[field]) {
-        // 格式化时间
-        this.queryParams[field] = this.formatDateTime(this.queryParams[field]);
-
-        // 自动调整另一个时间选择器的范围
-        if (field === 'startRecTime' && this.queryParams.endRecTime) {
-          // 结束时间不能早于开始时间
-          const startDate = new Date(this.queryParams.startRecTime);
-          const endDate = new Date(this.queryParams.endRecTime);
-
-          if (endDate < startDate) {
-            endDate.setTime(startDate.getTime());
-            this.queryParams.endRecTime = this.formatDateTime(endDate);
-          }
+    getYearStart() {
+      const date = new Date()
+      date.setMonth(0, 1)
+      date.setHours(0, 0, 0, 0)
+      return date
+    },
 
-          // 结束时间不能超过开始时间90天后
-          const endDateLimit = new Date(startDate);
-          endDateLimit.setDate(endDateLimit.getDate() + 90);
-          endDateLimit.setHours(23, 59, 59);
+    formatDateTime(date) {
+      if (!date) return ''
 
-          if (endDate > endDateLimit) {
-            this.queryParams.endRecTime = this.formatDateTime(endDateLimit);
-          }
+      const year = date.getFullYear()
+      const month = String(date.getMonth() + 1).padStart(2, '0')
+      const day = String(date.getDate()).padStart(2, '0')
+      const hours = String(date.getHours()).padStart(2, '0')
+      const minutes = String(date.getMinutes()).padStart(2, '0')
+      const seconds = String(date.getSeconds()).padStart(2, '0')
 
-          this.$nextTick(() => {
-            this.$refs.queryForm.validateField('endRecTime');
-          });
-        } else if (field === 'endRecTime' && this.queryParams.startRecTime) {
-          // 开始时间不能晚于结束时间
-          const startDate = new Date(this.queryParams.startRecTime);
-          const endDate = new Date(this.queryParams.endRecTime);
-
-          if (startDate > endDate) {
-            startDate.setTime(endDate.getTime());
-            this.queryParams.startRecTime = this.formatDateTime(startDate);
-          }
+      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+    },
 
-          this.$nextTick(() => {
-            this.$refs.queryForm.validateField('startRecTime');
-          });
-        }
-      }
+    formatDate(date, format) {
+      if (!date) return ''
+      return this.parseTime(date, format)
     },
 
-    // 生成整点时间范围(优化版)
-    generateHourRanges() {
-      const ranges = [];
-      for (let i = 0; i < 24; i++) {
-        const start = `${String(i).padStart(2, '0')}:00:00`;
-        const end = `${String(i).padStart(2, '0')}:59:59`;
-        ranges.push(`${start} - ${end}`);
+    formatNumber(num, decimals = 2) {
+      if (num === null || num === undefined || isNaN(num)) {
+        return '0.' + '0'.repeat(decimals)
       }
-      return ranges;
+      return Number(num).toFixed(decimals)
     }
   }
 }
 </script>
+
 <style scoped>
+.tree-container {
+  height: calc(100vh - 200px);
+  overflow-y: auto;
+}
+
+.summary-card {
+  text-align: center;
+}
 
+.summary-value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #409EFF;
+}
+
+.unit {
+  font-size: 14px;
+  font-weight: normal;
+  color: #909399;
+}
 
-/* 自定义样式:父节点文字变灰,提示不可点击 */
 .el-tree .el-tree-node.is-leaf > .el-tree-node__content {
   color: #333;
   cursor: pointer;