learshaw 2 kuukautta sitten
vanhempi
commit
41c1c1d16e

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 185 - 399
ems-ui-cloud/src/views/analysis/power/consume.vue


+ 360 - 369
ems-ui-cloud/src/views/devmgr/warn/index.vue

@@ -1,7 +1,6 @@
 <template>
   <div class="app-container device-alarm-content">
-    <el-row :gutter="10">
-      <!-- 左侧区域树 -->
+    <el-row :gutter="20">
       <el-col :span="4" :xs="24">
         <div class="head-container">
           <el-input
@@ -11,25 +10,32 @@
             size="small"
             prefix-icon="el-icon-search"
             style="margin-bottom: 20px"
+            @input="filterTree"
           />
         </div>
-        <div class="head-container">
+        <div class="head-container tree-container">
           <el-tree
             ref="tree"
             :data="areaOptions"
-            :default-expand-all="true"
+            :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>
+            </span>
+          </el-tree>
         </div>
       </el-col>
 
-      <!-- 右侧内容区 -->
       <el-col :span="20" :xs="24">
-        <!-- KPI卡片区 -->
         <el-row :gutter="20" class="kpi-cards">
           <el-col :span="6" v-for="(item, index) in kpiData" :key="index">
             <div class="kpi-card" :class="'kpi-' + item.type">
@@ -44,68 +50,85 @@
           </el-col>
         </el-row>
 
-        <!-- 第一行图表 -->
         <el-row :gutter="20" style="margin-top: 20px">
           <el-col :span="12">
-            <PieChartBlock title="设备状态分布" :opt-cfg="deviceStatusChart" />
+            <div class="dashboard-card">
+              <PieChartBlock title="设备状态分布" :opt-cfg="deviceStatusChart" style="height: 100%"/>
+            </div>
           </el-col>
           <el-col :span="12">
-            <LineChartBlock title="告警趋势分析" :opt-cfg="alarmTrendChart">
-              <template v-slot:filters>
-                <SwitchTag
-                  :ds="dateTypeOptions"
-                  :defTag="dateType"
-                  :tagClick="onDateTypeSwitch"
-                />
-              </template>
-            </LineChartBlock>
+            <div class="dashboard-card">
+              <LineChartBlock title="告警趋势分析" :opt-cfg="alarmTrendChart" style="height: 100%">
+                <template v-slot:filters>
+                  <SwitchTag
+                    :ds="dateTypeOptions"
+                    :defTag="dateType"
+                    :tagClick="onDateTypeSwitch"
+                  />
+                </template>
+              </LineChartBlock>
+            </div>
           </el-col>
         </el-row>
 
-        <!-- 第二行 -->
         <el-row :gutter="20" style="margin-top: 20px">
           <el-col :span="14">
-            <BlockTable title="实时告警列表" :table-data="realTimeAlarmData">
-              <template v-slot:columns>
-                <el-table-column type="index" label="序号" align="center" width="60" />
-                <el-table-column prop="subSystemName" label="子系统" align="center" width="100" />
-                <el-table-column prop="objName" label="设备名称" align="center" show-overflow-tooltip />
-                <el-table-column prop="alarmMsg" label="告警描述" align="center" show-overflow-tooltip />
-                <el-table-column prop="alarmTime" label="告警时间" align="center" width="160" />
-                <el-table-column label="告警类型" align="center" width="100">
-                  <template slot-scope="scope">
-                    <el-tag :type="getAlarmTypeTag(scope.row.alarmType)" size="small">
-                      {{ getAlarmTypeName(scope.row.alarmType) }}
-                    </el-tag>
-                  </template>
-                </el-table-column>
-                <el-table-column label="状态" align="center" width="100">
-                  <template slot-scope="scope">
-                    <el-dropdown @command="(cmd) => handleAlarmStateChange(cmd, scope.row)" v-if="scope.row.alarmState !== 2 && scope.row.alarmState !== 3">
-                      <span class="el-dropdown-link">
-                        <el-tag :type="getAlarmStateTag(scope.row.alarmState)" size="small">
-                          {{ getAlarmStateName(scope.row.alarmState) }}
-                        </el-tag>
-                        <i class="el-icon-arrow-down el-icon--right"></i>
-                      </span>
-                      <el-dropdown-menu slot="dropdown">
-                        <el-dropdown-item :command="1" v-if="scope.row.alarmState === 0">
-                          开始处理
-                        </el-dropdown-item>
-                        <el-dropdown-item :command="2">已处置</el-dropdown-item>
-                        <el-dropdown-item :command="3">已消散</el-dropdown-item>
-                      </el-dropdown-menu>
-                    </el-dropdown>
-                    <el-tag v-else :type="getAlarmStateTag(scope.row.alarmState)" size="small">
-                      {{ getAlarmStateName(scope.row.alarmState) }}
-                    </el-tag>
-                  </template>
-                </el-table-column>
-              </template>
-            </BlockTable>
+            <div class="dashboard-card table-card">
+              <BlockTable title="实时告警列表" :table-data="realTimeAlarmData" style="height: 100%">
+                <template v-slot:columns>
+                  <el-table-column type="index" label="序号" align="center" width="60"/>
+                  <el-table-column prop="subSystemName" label="子系统" align="center" width="100"/>
+                  <el-table-column prop="objName" label="设备名称" align="center" show-overflow-tooltip/>
+                  <el-table-column prop="alarmMsg" label="告警描述" align="center" show-overflow-tooltip/>
+                  <el-table-column prop="alarmTime" label="告警时间" align="center" width="160"/>
+                  <el-table-column label="告警类型" align="center" width="100">
+                    <template slot-scope="scope">
+                      <el-tag :type="getAlarmTypeTag(scope.row.alarmType)" size="small">
+                        {{ getAlarmTypeName(scope.row.alarmType) }}
+                      </el-tag>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="状态" align="center" width="100">
+                    <template slot-scope="scope">
+                      <el-dropdown
+                        @command="(cmd) => handleAlarmStateChange(cmd, scope.row)"
+                        v-if="scope.row.alarmState !== 2 && scope.row.alarmState !== 3"
+                      >
+                        <span class="el-dropdown-link">
+                          <el-tag :type="getAlarmStateTag(scope.row.alarmState)" size="small">
+                            {{ getAlarmStateName(scope.row.alarmState) }}
+                          </el-tag>
+                          <i class="el-icon-arrow-down el-icon--right"></i>
+                        </span>
+                        <el-dropdown-menu slot="dropdown">
+                          <el-dropdown-item :command="1" v-if="scope.row.alarmState === 0">
+                            开始处理
+                          </el-dropdown-item>
+                          <el-dropdown-item :command="2">已处置</el-dropdown-item>
+                          <el-dropdown-item :command="3">已消散</el-dropdown-item>
+                        </el-dropdown-menu>
+                      </el-dropdown>
+                      <el-tag v-else :type="getAlarmStateTag(scope.row.alarmState)" size="small">
+                        {{ getAlarmStateName(scope.row.alarmState) }}
+                      </el-tag>
+                    </template>
+                  </el-table-column>
+                </template>
+              </BlockTable>
+            </div>
           </el-col>
+
           <el-col :span="10">
-            <PieChartBlock title="告警类型分布" :opt-cfg="alarmTypeChart" />
+            <div class="dashboard-card"
+                 v-if="alarmTypeChart.series[0].data && alarmTypeChart.series[0].data.length > 0"
+            >
+              <PieChartBlock title="告警类型分布" :opt-cfg="alarmTypeChart" style="height: 100%"/>
+            </div>
+
+            <div class="dashboard-card empty-state" v-else>
+              <div class="card-title">告警类型分布</div>
+              <el-empty description="暂无告警类型数据" :image-size="120"></el-empty>
+            </div>
           </el-col>
         </el-row>
       </el-col>
@@ -114,22 +137,23 @@
 </template>
 
 <script>
-import { areaTreeSelect } from '@/api/basecfg/area';
+import { areaTreeSelect } from '@/api/basecfg/area'
 import {
   listAlarmInfo,
   updateAlarmInfo,
   fetchAlarmIndexDay,
   fetchAlarmIndexMonth,
   fetchAlarmIndexYear,
-  fetchAlarmIndex,
-} from '@/api/alarm/alarm-info';
-import { listDeviceStatus } from '@/api/device/device';
-import BlockTable from '@/components/Block/BlockTable/index.vue';
-import LineChartBlock from '@/components/Block/charts/LineChartBlock.vue';
-import PieChartBlock from '@/components/Block/charts/PieChartBlock.vue';
-import SwitchTag from '@/components/SwitchTag/index.vue';
-import { DateTool } from '@/utils/DateTool';
-import dayjs from 'dayjs';
+  fetchAlarmIndex
+} from '@/api/alarm/alarm-info'
+import { listDeviceStatus } from '@/api/device/device'
+import BlockTable from '@/components/Block/BlockTable/index.vue'
+import LineChartBlock from '@/components/Block/charts/LineChartBlock.vue'
+import PieChartBlock from '@/components/Block/charts/PieChartBlock.vue'
+import SwitchTag from '@/components/SwitchTag/index.vue'
+import { DateTool } from '@/utils/DateTool'
+import dayjs from 'dayjs'
+import _ from 'lodash'
 
 export default {
   name: 'DeviceAlarm',
@@ -137,146 +161,105 @@ export default {
     BlockTable,
     LineChartBlock,
     PieChartBlock,
-    SwitchTag,
+    SwitchTag
   },
   data() {
     return {
       // 区域筛选
       areaName: '',
       areaOptions: [],
-      areaCode: '', // 默认为空字符串,表示全部
+      areaCode: '',
+      defaultExpandedKeys: ['-1'],
+      defaultProps: {
+        children: 'children',
+        label: 'label'
+      },
 
       // 时间维度
       dateType: { val: 'year', text: '按年' },
       dateTypeOptions: [
         { val: 'day', text: '按日' },
         { val: 'month', text: '按月' },
-        { val: 'year', text: '按年' },
+        { val: 'year', text: '按年' }
       ],
 
       // KPI数据
       kpiData: [
-        {
-          label: '设备总数',
-          value: 0,
-          icon: 'el-icon-monitor',
-          type: 'primary',
-        },
-        {
-          label: '在线设备',
-          value: 0,
-          icon: 'el-icon-circle-check',
-          type: 'success',
-        },
-        {
-          label: '离线设备',
-          value: 0,
-          icon: 'el-icon-warning-outline',
-          type: 'danger',
-        },
-        {
-          label: '待处理告警',
-          value: 0,
-          icon: 'el-icon-bell',
-          type: 'warning',
-        },
+        { label: '设备总数', value: 0, icon: 'el-icon-monitor', type: 'primary' },
+        { label: '在线设备', value: 0, icon: 'el-icon-circle-check', type: 'success' },
+        { label: '离线设备', value: 0, icon: 'el-icon-warning-outline', type: 'danger' },
+        { label: '待处理告警', value: 0, icon: 'el-icon-bell', type: 'warning' }
       ],
 
-      // 设备状态分布
-      deviceStatusChart: {
-        series: [
-          {
-            type: 'pie',
-            radius: ['50%', '70%'],
-            data: [],
-          },
-        ],
-      },
-
-      // 告警趋势
-      alarmTrendChart: {
-        unit: '',
-        xAxis: {
-          type: 'category',
-          data: [],
-        },
-        series: [],
-      },
-
-      // 告警类型分布
-      alarmTypeChart: {
-        series: [
-          {
-            type: 'pie',
-            radius: ['0%', '70%'],
-            data: [],
-          },
-        ],
-      },
-
-      // 实时告警数据
-      realTimeAlarmData: [],
-    };
+      // 图表数据初始化
+      deviceStatusChart: { series: [{ type: 'pie', radius: ['50%', '70%'], data: [] }] },
+      alarmTrendChart: { unit: '', xAxis: { type: 'category', data: [] }, series: [] },
+      alarmTypeChart: { series: [{ type: 'pie', radius: ['0%', '70%'], data: [] }] },
+      realTimeAlarmData: []
+    }
   },
   watch: {
     areaName(val) {
-      this.$refs.tree.filter(val);
-    },
+      this.$refs.tree.filter(val)
+    }
   },
   mounted() {
-    this.getAreaTree();
+    this.getAreaTree()
   },
   methods: {
-    // 获取区域树
+    getTreeIcon(data) {
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
+    filterNode(value, data) {
+      if (!value) return true
+      return data.label.indexOf(value) !== -1
+    },
+
     async getAreaTree() {
       try {
-        const response = await areaTreeSelect('0', 1);
-        this.areaOptions = [
-          {
-            id: '',
-            label: '全部',
-            children: [],
-          },
-        ].concat(response.data || []);
-
-        // 区域树加载完成后再加载数据
+        const response = await areaTreeSelect('0', 1)
+        this.areaOptions = [{
+          id: '-1',
+          label: '全部',
+          children: response.data || []
+        }]
         this.$nextTick(() => {
-          this.loadAllData();
-        });
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+          this.loadAllData()
+        })
       } catch (error) {
-        console.error('获取区域树失败:', error);
-        this.$message.error('获取区域树失败');
+        console.error('获取区域树失败:', error)
+        this.$message.error('获取区域树失败')
       }
     },
 
-    // 筛选节点
-    filterNode(value, data) {
-      if (!value) return true;
-      return data.label.indexOf(value) !== -1;
-    },
-
-    // 区域切换
     handleNodeClick(data) {
-      this.areaCode = data.id;
-      this.loadAllData();
+      this.areaCode = data.id === '-1' ? '' : data.id
+      this.loadAllData()
     },
 
-    // 时间维度切换
     async onDateTypeSwitch(item) {
-      this.dateType = item;
-      await this.loadAlarmTrend();
+      this.dateType = item
+      await this.loadAlarmTrend()
     },
 
-    // 构建查询参数
     buildQueryParams(extraParams = {}) {
-      const params = {
-        areaCode: this.areaCode || '', // 总是传递 areaCode,空字符串表示全部
+      return {
+        areaCode: this.areaCode || '',
         ...extraParams
-      };
-      return params;
+      }
     },
 
-    // 加载所有数据
     async loadAllData() {
       try {
         await Promise.all([
@@ -284,65 +267,35 @@ export default {
           this.loadDeviceStatus(),
           this.loadAlarmTrend(),
           this.loadRealTimeAlarm(),
-          this.loadAlarmTypeDistribution(),
-        ]);
+          this.loadAlarmTypeDistribution()
+        ])
       } catch (error) {
-        console.error('加载数据失败:', error);
+        console.error('加载数据失败:', error)
       }
     },
 
-    // 加载KPI数据
     async loadKpiData() {
       try {
-        // 获取设备统计
-        const deviceRes = await listDeviceStatus(this.buildQueryParams());
-        const deviceData = deviceRes.data || {};
-
-        // 获取待处理告警数
+        const deviceRes = await listDeviceStatus(this.buildQueryParams())
+        const deviceData = deviceRes.data || {}
         const alarmRes = await listAlarmInfo(
-          this.buildQueryParams({
-            pageNum: 1,
-            pageSize: 1,
-            alarmStateList: [0, 1],
-          })
-        );
+          this.buildQueryParams({ pageNum: 1, pageSize: 1, alarmStateList: [0, 1] })
+        )
 
         this.kpiData = [
-          {
-            label: '设备总数',
-            value: deviceData.total || 0,
-            icon: 'el-icon-monitor',
-            type: 'primary',
-          },
-          {
-            label: '在线设备',
-            value: deviceData.onlineCount || 0,
-            icon: 'el-icon-circle-check',
-            type: 'success',
-          },
-          {
-            label: '离线设备',
-            value: deviceData.offlineCount || 0,
-            icon: 'el-icon-warning-outline',
-            type: 'danger',
-          },
-          {
-            label: '待处理告警',
-            value: alarmRes.total || 0,
-            icon: 'el-icon-bell',
-            type: 'warning',
-          },
-        ];
+          { label: '设备总数', value: deviceData.total || 0, icon: 'el-icon-monitor', type: 'primary' },
+          { label: '在线设备', value: deviceData.onlineCount || 0, icon: 'el-icon-circle-check', type: 'success' },
+          { label: '离线设备', value: deviceData.offlineCount || 0, icon: 'el-icon-warning-outline', type: 'danger' },
+          { label: '待处理告警', value: alarmRes.total || 0, icon: 'el-icon-bell', type: 'warning' }
+        ]
       } catch (error) {
-        console.error('加载KPI数据失败:', error);
+        console.error('加载KPI数据失败:', error)
       }
     },
 
-    // 加载设备状态分布
     async loadDeviceStatus() {
       try {
-        const { data } = await listDeviceStatus(this.buildQueryParams());
-
+        const { data } = await listDeviceStatus(this.buildQueryParams())
         this.deviceStatusChart = {
           series: [
             {
@@ -350,226 +303,272 @@ export default {
               radius: ['50%', '70%'],
               data: [
                 { value: data.onlineCount || 0, name: '在线' },
-                { value: data.offlineCount || 0, name: '离线' },
-              ],
-            },
-          ],
-        };
+                { value: data.offlineCount || 0, name: '离线' }
+              ]
+            }
+          ]
+        }
       } catch (error) {
-        console.error('加载设备状态失败:', error);
+        console.error('加载设备状态失败:', error)
       }
     },
 
-    // 加载告警趋势
     async loadAlarmTrend() {
       try {
-        let xAxis = [];
-        let data = [];
-
-        const params = this.buildQueryParams();
+        let xAxis = []
+        let data = []
+        const params = this.buildQueryParams()
 
         if (this.dateType.val === 'day') {
-          const res = await fetchAlarmIndexDay(params);
-          data = res.data || [];
-          xAxis = DateTool.getTime(24);
+          const res = await fetchAlarmIndexDay(params)
+          data = res.data || []
+          xAxis = DateTool.getTime(24)
         } else if (this.dateType.val === 'month') {
-          const res = await fetchAlarmIndexMonth(params);
-          data = res.data || [];
-          xAxis = DateTool.getDayOfRange(
-            dayjs().subtract(1, 'month'),
-            dayjs(),
-            DateTool.DateFormat.YYYY_MM_DD
-          );
+          const res = await fetchAlarmIndexMonth(params)
+          data = res.data || []
+          xAxis = DateTool.getDayOfRange(dayjs().subtract(1, 'month'), dayjs(), DateTool.DateFormat.YYYY_MM_DD)
         } else {
-          const res = await fetchAlarmIndexYear(params);
-          data = res.data || [];
-          xAxis = DateTool.getMonthsOfYearAgo();
+          const res = await fetchAlarmIndexYear(params)
+          data = res.data || []
+          xAxis = DateTool.getMonthsOfYearAgo()
         }
 
-        const series = this.transformAlarmTrendData(data, xAxis);
-
+        const series = this.transformAlarmTrendData(data, xAxis)
         this.alarmTrendChart = {
           unit: '',
-          xAxis: {
-            type: 'category',
-            data: xAxis,
-          },
-          series,
-        };
+          xAxis: { type: 'category', data: xAxis },
+          series
+        }
       } catch (error) {
-        console.error('加载告警趋势失败:', error);
+        console.error('加载告警趋势失败:', error)
       }
     },
 
-    // 转换告警趋势数据
     transformAlarmTrendData(data, xAxis) {
-      const dayGroup = _.groupBy(data, 'alarmType');
-      const series = [];
-
+      const dayGroup = _.groupBy(data, 'alarmType')
+      const series = []
       const alarmTypeMap = {
-        1: '一般告警',
-        2: '重要告警',
-        3: '紧急告警',
-        4: '恢复告警',
-        5: '诊断告警',
-        6: '其他告警',
-      };
+        1: '一般告警', 2: '重要告警', 3: '紧急告警', 4: '恢复告警', 5: '诊断告警', 6: '其他告警'
+      }
 
       Object.keys(dayGroup).forEach((alarmType) => {
-        let ds = {};
+        let ds = {}
         dayGroup[alarmType].forEach((item) => {
-          ds[item.dateIndex] = item.cnt;
-        });
-
-        let seriesData = [];
-        xAxis.forEach((item) => seriesData.push(ds[item] || 0));
-
+          ds[item.dateIndex] = item.cnt
+        })
+        let seriesData = []
+        xAxis.forEach((item) => seriesData.push(ds[item] || 0))
         series.push({
           name: alarmTypeMap[alarmType] || '未知',
           type: 'line',
           smooth: true,
-          data: seriesData,
-        });
-      });
-
-      return series;
+          data: seriesData
+        })
+      })
+      return series
     },
 
-    // 加载实时告警
     async loadRealTimeAlarm() {
       try {
+        // 【关键修改】将 pageSize 改为 6,以适配 430px 的高度
         const { rows } = await listAlarmInfo(
-          this.buildQueryParams({
-            pageNum: 1,
-            pageSize: 10,
-            alarmStateList: [0, 1],
-          })
-        );
-
-        this.realTimeAlarmData = rows || [];
+          this.buildQueryParams({ pageNum: 1, pageSize: 6, alarmStateList: [0, 1] })
+        )
+        this.realTimeAlarmData = rows || []
       } catch (error) {
-        console.error('加载实时告警失败:', error);
+        console.error('加载实时告警失败:', error)
       }
     },
 
-    // 加载告警类型分布
     async loadAlarmTypeDistribution() {
       try {
-        const endTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
-        const startTime = dayjs().startOf('year').format('YYYY-MM-DD HH:mm:ss'); // 当年1月1日 00:00:00
-
-        const params = this.buildQueryParams({
-          startRecTime: startTime,
-          endRecTime: endTime,
-        });
-
-        const { data } = await fetchAlarmIndex(params);
+        const endTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
+        const startTime = dayjs().startOf('year').format('YYYY-MM-DD HH:mm:ss')
+        const params = this.buildQueryParams({ startRecTime: startTime, endRecTime: endTime })
+        const { data } = await fetchAlarmIndex(params)
 
         const alarmTypeMap = {
-          1: '一般告警',
-          2: '重要告警',
-          3: '紧急告警',
-          4: '恢复告警',
-          5: '诊断告警',
-          6: '其他告警',
-        };
-
+          1: '一般告警', 2: '重要告警', 3: '紧急告警', 4: '恢复告警', 5: '诊断告警', 6: '其他告警'
+        }
         const chartData = (data || []).map((item) => ({
           value: item.cnt,
-          name: alarmTypeMap[item.alarmType] || '未知',
-        }));
+          name: alarmTypeMap[item.alarmType] || '未知'
+        }))
 
         this.alarmTypeChart = {
-          series: [
-            {
-              type: 'pie',
-              radius: ['0%', '70%'],
-              data: chartData,
-            },
-          ],
-        };
+          series: [{ type: 'pie', radius: ['0%', '70%'], data: chartData }]
+        }
       } catch (error) {
-        console.error('加载告警类型分布失败:', error);
+        console.error('加载告警类型分布失败:', error)
       }
     },
 
-    // 告警状态变更
     async handleAlarmStateChange(command, row) {
       try {
-        await updateAlarmInfo({
-          id: row.id,
-          alarmState: command,
-        });
-
-        this.$message.success('告警状态更新成功');
-        await this.loadRealTimeAlarm();
-        await this.loadKpiData();
+        await updateAlarmInfo({ id: row.id, alarmState: command })
+        this.$message.success('告警状态更新成功')
+        await this.loadRealTimeAlarm()
+        await this.loadKpiData()
       } catch (error) {
-        console.error('更新告警状态失败:', error);
-        this.$message.error('告警状态更新失败');
+        this.$message.error('告警状态更新失败')
       }
     },
 
-    // 获取告警类型名称
     getAlarmTypeName(type) {
-      const typeMap = {
-        1: '一般',
-        2: '重要',
-        3: '紧急',
-        4: '恢复',
-        5: '诊断',
-        6: '其他',
-      };
-      return typeMap[type] || '未知';
+      const map = { 1: '一般', 2: '重要', 3: '紧急', 4: '恢复', 5: '诊断', 6: '其他' }
+      return map[type] || '未知'
     },
-
-    // 获取告警类型标签
     getAlarmTypeTag(type) {
-      const tagMap = {
-        1: 'info',
-        2: 'warning',
-        3: 'danger',
-        4: 'success',
-        5: '',
-        6: 'info',
-      };
-      return tagMap[type] || 'info';
+      const map = { 1: 'info', 2: 'warning', 3: 'danger', 4: 'success', 5: '', 6: 'info' }
+      return map[type] || 'info'
     },
-
-    // 获取告警状态名称
     getAlarmStateName(state) {
-      const stateMap = {
-        0: '新增',
-        1: '处理中',
-        2: '已处置',
-        3: '已消散',
-      };
-      return stateMap[state] || '未知';
+      const map = { 0: '新增', 1: '处理中', 2: '已处置', 3: '已消散' }
+      return map[state] || '未知'
     },
-
-    // 获取告警状态标签
     getAlarmStateTag(state) {
-      const tagMap = {
-        0: 'danger',
-        1: 'warning',
-        2: 'success',
-        3: 'info',
-      };
-      return tagMap[state] || 'info';
-    },
-  },
-};
+      const map = { 0: 'danger', 1: 'warning', 2: 'success', 3: 'info' }
+      return map[state] || 'info'
+    }
+  }
+}
 </script>
 
 <style scoped lang="scss">
-@import '@/assets/styles/variables.scss';
-
 .device-alarm-content {
-  background: #fff;
+  background: #f5f7fa;
   padding: 20px;
+  min-height: calc(100vh - 84px);
+
+  // ---------------------------------------------------------
+  // 核心样式:统一卡片容器
+  // ---------------------------------------------------------
+  .dashboard-card {
+    height: 430px; // 【关键】统一四个板块的高度
+    background: #fff;
+    border-radius: 8px;
+    padding: 15px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+
+    // 空状态样式
+    &.empty-state {
+      .card-title {
+        font-size: 16px;
+        font-weight: bold;
+        color: #333;
+        border-left: 4px solid #409EFF;
+        padding-left: 10px;
+        line-height: 1;
+        margin-bottom: 20px;
+      }
+
+      ::v-deep .el-empty {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+      }
+    }
+
+    // 针对表格卡片的特殊处理
+    &.table-card {
+      // 深度选择器,强行让内部表格适应高度
+      ::v-deep .block-table-container {
+        height: 100%;
+        display: flex;
+        flex-direction: column;
+      }
+
+      ::v-deep .el-table {
+        flex: 1; // 撑满剩余高度
+        height: 0; // 关键:Flex布局下允许自适应缩放
+      }
+    }
+  }
+
+  // 左侧树容器
+  .head-container {
+    background: #fff;
+    padding: 15px;
+    border-radius: 8px;
+    margin-bottom: 15px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    &.tree-container {
+      max-height: calc(100vh - 280px);
+      overflow-y: auto;
+
+      &::-webkit-scrollbar {
+        width: 6px;
+      }
+
+      &::-webkit-scrollbar-track {
+        background: #f1f1f1;
+        border-radius: 3px;
+      }
+
+      &::-webkit-scrollbar-thumb {
+        background: #c1c1c1;
+        border-radius: 3px;
+      }
+
+      &::-webkit-scrollbar-thumb:hover {
+        background: #a8a8a8;
+      }
+
+      ::v-deep .el-tree {
+        background: transparent;
+
+        .el-tree-node__content {
+          height: 40px;
+          padding: 0 8px;
+          transition: all 0.3s;
+
+          &:hover {
+            background-color: #f5f7fa;
+          }
+        }
+
+        .el-tree-node.is-current > .el-tree-node__content {
+          background-color: #ecf5ff;
+          color: #409eff;
+        }
+
+        .custom-tree-node {
+          flex: 1;
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          font-size: 14px;
+
+          .tree-label {
+            display: flex;
+            align-items: center;
+
+            .tree-icon {
+              margin-right: 8px;
+              font-size: 16px;
+              color: #909399;
+              transition: color 0.3s;
+            }
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
 
   // KPI卡片样式
   .kpi-cards {
+    margin-bottom: 20px;
+
     .kpi-card {
       background: #fff;
       border-radius: 8px;
@@ -660,14 +659,6 @@ export default {
     }
   }
 
-  // 区域树样式
-  .head-container {
-    background: #f5f7fa;
-    padding: 10px;
-    border-radius: 4px;
-  }
-
-  // 下拉链接样式
   .el-dropdown-link {
     cursor: pointer;
     display: flex;

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 213 - 399
ems-ui-cloud/src/views/mgr/powergrid.vue


+ 82 - 146
ems-ui-cloud/src/views/mgr/powerstore.vue

@@ -18,7 +18,6 @@
     <el-tabs v-model="activeName" @tab-click="tabClick" class="main-tabs">
       <el-tab-pane label="总览" name="summary">
         <div class="chartGroup">
-          <!-- 当日充电量卡片 -->
           <div class="chart-card">
             <div class="chart-header">
               <i class="el-icon-s-data"></i>
@@ -33,7 +32,6 @@
             </div>
           </div>
 
-          <!-- 当日放电量卡片 -->
           <div class="chart-card">
             <div class="chart-header">
               <i class="el-icon-s-marketing"></i>
@@ -52,7 +50,6 @@
 
       <el-tab-pane label="区块储能" name="area">
         <el-row :gutter="20">
-          <!-- 左侧树形区域 -->
           <el-col :span="5" :xs="24">
             <div class="head-container">
               <el-input
@@ -67,13 +64,13 @@
             </div>
             <div class="head-container tree-container">
               <el-tree
-                ref="tree"
+                ref="areaTree"
                 :data="areaOptions"
                 :props="defaultProps"
                 :expand-on-click-node="false"
                 :filter-node-method="filterNode"
                 node-key="id"
-                default-expand-all
+                :default-expanded-keys="defaultExpandedKeys"
                 highlight-current
                 @node-click="handleNodeClick"
               >
@@ -83,7 +80,7 @@
                     {{ node.label }}
                   </span>
                   <el-tag
-                    v-if="data.type === 'storage'"
+                    v-if="data.facsCategory === 'C'"
                     size="mini"
                     effect="plain"
                     class="tree-tag"
@@ -95,7 +92,6 @@
             </div>
           </el-col>
 
-          <!-- 右侧内容区域 -->
           <el-col :span="19" :xs="24">
             <div class="container-block">
               <SubTitle :title="`时段储能数据【${selectedLabel}】`"/>
@@ -113,7 +109,8 @@
                   range-separator="至"
                   start-placeholder="开始日期"
                   end-placeholder="结束日期"
-                  align="right">
+                  align="right"
+                >
                 </el-date-picker>
               </div>
               <el-table
@@ -121,9 +118,10 @@
                 :data="elecStoreHList"
                 max-height="400px"
                 stripe
-                border>
-                <el-table-column label="日期" align="center" prop="date" width="180" />
-                <el-table-column label="时间" align="center" prop="time" />
+                border
+              >
+                <el-table-column label="日期" align="center" prop="date" width="180"/>
+                <el-table-column label="时间" align="center" prop="time"/>
                 <el-table-column label="充电电量(kW·h)" align="center" prop="chargeElecQuantity">
                   <template slot-scope="scope">
                     <span class="text-primary">{{ scope.row.chargeElecQuantity || 0 }}</span>
@@ -150,36 +148,39 @@ import { dateFormat, numToStr } from '@/utils/index.js'
 import dayjs from 'dayjs'
 import { DateTool } from '@/utils/DateTool'
 import BaseChart from '@/components/BaseChart'
-import { listConfig } from "@/api/system/config"
+import { listConfig } from '@/api/system/config'
+import SubTitle from '@/components/SubTitle' // 确保引入SubTitle
 
 export default {
   name: 'ElecStoreH',
   components: {
-    BaseChart
+    BaseChart,
+    SubTitle
   },
   data() {
     return {
       activeName: 'summary',
       // 遮罩层
       loading: true,
-      facsCategory: '',
+      facsCategory: 'C', // 储能分类
       facsSubCategory: '',
       // 储能计量-小时表格数据
       elecStoreHList: [],
       areaName: undefined,
       defaultProps: {
-        children: "children",
-        label: "label"
+        children: 'children',
+        label: 'label'
       },
+      defaultExpandedKeys: ['-1'], // 默认展开全部
       pvCfg: {
         total: 0,
         areas: []
       },
-      selectedLabel: '',
+      selectedLabel: '全部',
       areaType: '',
       // 查询参数
       queryParams: {
-        areaCode: ''
+        areaCode: '-1'
       },
       pickerOptions: {
         shortcuts: [
@@ -213,24 +214,6 @@ export default {
               start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
               picker.$emit('pick', [start, end])
             }
-          },
-          {
-            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) {
-              const end = new Date()
-              const start = new Date()
-              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
-              picker.$emit('pick', [start, end])
-            }
           }
         ]
       },
@@ -260,23 +243,13 @@ export default {
           top: 0,
           show: true,
           feature: {
-            magicType: {
-              show: true,
-              type: ['bar', 'line']
-            },
-            saveAsImage: {
-              show: true
-            }
+            magicType: { show: true, type: ['bar', 'line'] },
+            saveAsImage: { show: true }
           }
         },
         tooltip: {
           trigger: 'axis',
-          axisPointer: {
-            type: 'cross',
-            crossStyle: {
-              color: '#999'
-            }
-          },
+          axisPointer: { type: 'cross', crossStyle: { color: '#999' } },
           formatter: (params) => {
             let relVal = params[0].name
             for (let i = 0, l = params.length; i < l; i++) {
@@ -286,55 +259,24 @@ export default {
             return relVal
           }
         },
-        legend: {
-          data: ['充电电量', '放电电量']
-        },
-        grid: {
-          left: '3%',
-          right: '4%',
-          bottom: '3%',
-          containLabel: true
-        },
-        xAxis: {
-          type: 'category',
-          data: xData,
-          axisPointer: {
-            type: 'shadow'
-          }
-        },
-        yAxis: [
-          {
-            name: 'kW·h(千瓦时)',
-            type: 'value',
-            axisLabel: {
-              formatter: '{value}'
-            }
-          }
-        ],
+        legend: { data: ['充电电量', '放电电量'] },
+        grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
+        xAxis: { type: 'category', data: xData, axisPointer: { type: 'shadow' } },
+        yAxis: [{ name: 'kW·h(千瓦时)', type: 'value', axisLabel: { formatter: '{value}' } }],
         series: [
           {
             name: '充电电量',
             type: 'bar',
             data: chargeQuantity,
             barWidth: 20,
-            itemStyle: {
-              normal: {
-                color: '#5470c6',
-                barBorderRadius: [4, 4, 0, 0]
-              }
-            }
+            itemStyle: { normal: { color: '#5470c6', barBorderRadius: [4, 4, 0, 0] } }
           },
           {
             name: '放电电量',
             type: 'bar',
             data: dischargeQuantity,
             barWidth: 20,
-            itemStyle: {
-              normal: {
-                color: '#91cc75',
-                barBorderRadius: [4, 4, 0, 0]
-              }
-            }
+            itemStyle: { normal: { color: '#91cc75', barBorderRadius: [4, 4, 0, 0] } }
           }
         ]
       }
@@ -356,20 +298,9 @@ export default {
             radius: ['40%', '70%'],
             center: ['50%', '50%'],
             roseType: 'area',
-            itemStyle: {
-              borderRadius: 8
-            },
-            label: {
-              show: true,
-              formatter: '{b}: {c}'
-            },
-            emphasis: {
-              label: {
-                show: true,
-                fontSize: '16',
-                fontWeight: 'bold'
-              }
-            },
+            itemStyle: { borderRadius: 8 },
+            label: { show: true, formatter: '{b}: {c}' },
+            emphasis: { label: { show: true, fontSize: '16', fontWeight: 'bold' } },
             data: this.hourSum.map(item => ({
               name: item.time,
               value: item.chargeElecQuantity || 0
@@ -395,20 +326,9 @@ export default {
             radius: ['40%', '70%'],
             center: ['50%', '50%'],
             roseType: 'area',
-            itemStyle: {
-              borderRadius: 8
-            },
-            label: {
-              show: true,
-              formatter: '{b}: {c}'
-            },
-            emphasis: {
-              label: {
-                show: true,
-                fontSize: '16',
-                fontWeight: 'bold'
-              }
-            },
+            itemStyle: { borderRadius: 8 },
+            label: { show: true, formatter: '{b}: {c}' },
+            emphasis: { label: { show: true, fontSize: '16', fontWeight: 'bold' } },
             data: this.hourSum.map(item => ({
               name: item.time,
               value: item.dischargeElecQuantity || 0
@@ -419,9 +339,12 @@ export default {
     }
   },
   created() {
-    this.facsCategory = 'C'
-    this.getAreaList()
-    this.tabClick()
+    // 初始化时如果是summary,加载summary,否则加载树和列表
+    if (this.activeName === 'summary') {
+      this.getSummary()
+    } else {
+      this.tabClick()
+    }
     this.getPvConfig()
   },
   methods: {
@@ -429,21 +352,24 @@ export default {
 
     // 获取树节点图标
     getTreeIcon(data) {
-      if (data.type === 'storage') {
-        return 'el-icon-coin'
+      if (data.facsCategory === 'C') {
+        return 'el-icon-coin' // 储能图标
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home' // 全部
       }
-      return 'el-icon-office-building'
+      return 'el-icon-office-building' // 区域
     },
 
     // 过滤树
     filterTree() {
-      this.$refs.tree.filter(this.areaName)
+      this.$refs.areaTree.filter(this.areaName)
     },
 
     async getPvConfig() {
       try {
         const { rows } = await listConfig({
-          configKey: "storage-equipped-capacity",
+          configKey: 'storage-equipped-capacity'
         })
         if (!rows || !rows.length) {
           return
@@ -466,15 +392,12 @@ export default {
       }
     },
 
-    // 查询区域列表
+    // 查询区域列表 - 返回Promise以供tabClick使用
     async getAreaList() {
       try {
         const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
-        this.areaOptions = [{
-          id: '-1',
-          label: '全部',
-          children: []
-        }].concat(response.data || [])
+        this.areaOptions = [{ id: '-1', label: '全部', children: response.data || [] }]
+
       } catch (error) {
         this.$message.error('获取区域列表失败')
       }
@@ -549,14 +472,23 @@ export default {
       this.getTodayChart()
     },
 
-    /** 搜索按钮操作 */
+    /** Tab 切换逻辑 - 修复树高亮问题 */
     tabClick() {
       if (this.activeName === 'summary') {
         this.getSummary()
       } else {
         this.selectedLabel = '全部'
         this.queryParams.areaCode = '-1'
-        this.getAreaList()
+
+        // 加载树数据,并在回调中设置高亮
+        this.getAreaList().then(() => {
+          this.$nextTick(() => {
+            if (this.$refs.areaTree) {
+              this.$refs.areaTree.setCurrentKey('-1')
+            }
+          })
+        })
+
         this.getTodayChart()
         this.getList()
       }
@@ -618,17 +550,16 @@ export default {
   }
 }
 
-// 图表组样式 - 修复核心样式
+// 图表组样式
 .chartGroup {
   display: flex;
   gap: 20px;
 
-  // 自定义图表卡片样式 - 替代原有的Block组件
   .chart-card {
     flex: 1;
     background: #fff;
     border-radius: 8px;
-    overflow: hidden;  // 关键:确保圆角生效
+    overflow: hidden;
     box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
     transition: all 0.3s;
 
@@ -637,7 +568,6 @@ export default {
       transform: translateY(-2px);
     }
 
-    // 标题栏样式
     .chart-header {
       background: linear-gradient(135deg, #409eff 0%, #53a8ff 100%);
       padding: 16px 20px;
@@ -653,14 +583,12 @@ export default {
       }
     }
 
-    // 图表主体样式
     .chart-body {
       padding: 20px;
       position: relative;
       min-height: 450px;
       background: #fff;
 
-      // 中心标签样式
       .center-label {
         position: absolute;
         top: 50%;
@@ -670,7 +598,7 @@ export default {
         flex-direction: column;
         align-items: center;
         z-index: 10;
-        pointer-events: none;  // 防止遮挡图表交互
+        pointer-events: none;
 
         .label-title {
           font-size: 14px;
@@ -689,16 +617,16 @@ export default {
   }
 }
 
-// 左侧树形区域样式
 .head-container {
   background: #fff;
   padding: 15px;
   border-radius: 8px;
   margin-bottom: 15px;
   box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  border: 1px solid #ebeef5;
 
   &.tree-container {
-    max-height: calc(100vh - 280px);
+    max-height: calc(100vh - 350px);
     overflow-y: auto;
 
     &::-webkit-scrollbar {
@@ -711,12 +639,12 @@ export default {
     }
 
     &::-webkit-scrollbar-thumb {
-      background: #888;
+      background: #c1c1c1;
       border-radius: 3px;
+    }
 
-      &:hover {
-        background: #555;
-      }
+    &::-webkit-scrollbar-thumb:hover {
+      background: #a8a8a8;
     }
 
     ::v-deep .el-tree {
@@ -725,16 +653,20 @@ export default {
       .el-tree-node__content {
         height: 40px;
         padding: 0 8px;
-        transition: all 0.3s;
 
         &:hover {
           background-color: #f5f7fa;
         }
       }
 
+      /* 核心 CSS 修复:仅选中节点高亮 */
       .el-tree-node.is-current > .el-tree-node__content {
         background-color: #ecf5ff;
         color: #409eff;
+
+        .tree-icon {
+          color: #409eff !important;
+        }
       }
 
       .custom-tree-node {
@@ -757,6 +689,12 @@ export default {
 
         .tree-tag {
           margin-right: 8px;
+
+          &.warning {
+            color: #e6a23c;
+            background: #fdf6ec;
+            border-color: #f5dab1;
+          }
         }
       }
     }
@@ -829,7 +767,6 @@ export default {
       margin-bottom: 20px;
     }
   }
-
   .tips {
     flex-direction: column;
 
@@ -837,7 +774,6 @@ export default {
       margin-bottom: 15px;
     }
   }
-
   .ctl-container {
     ::v-deep .el-date-editor {
       width: 100%;

+ 141 - 199
ems-ui-cloud/src/views/mgr/poweruse.vue

@@ -1,10 +1,8 @@
 <template>
   <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
@@ -23,7 +21,6 @@
             </div>
           </div>
 
-          <!-- 统计卡片 -->
           <div class="stat-cards">
             <el-row :gutter="16">
               <el-col :span="6">
@@ -112,7 +109,6 @@
             </el-row>
           </div>
 
-          <!-- 图表和表格区域 -->
           <el-row :gutter="20" class="content-row">
             <el-col :span="14">
               <div class="chart-panel">
@@ -172,7 +168,6 @@
         </div>
       </el-tab-pane>
 
-      <!-- 设施分类 Tabs (动态生成) - 修复:使用独立的 detail-pane 组件或唯一ref -->
       <el-tab-pane
         v-for="item in facsCategoryOptions"
         :key="item.code"
@@ -181,46 +176,50 @@
         :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 class="head-container">
+              <el-input
+                v-model="areaKeyword"
+                placeholder="搜索区域"
+                clearable
+                size="small"
+                prefix-icon="el-icon-search"
+                style="margin-bottom: 20px"
+                @input="filterAreaTree"
+              />
+            </div>
+            <div class="head-container tree-container">
+              <el-tree
+                :ref="'areaTree_' + item.code"
+                :data="areaTreeData"
+                :props="treeProps"
+                :expand-on-click-node="false"
+                :filter-node-method="filterNode"
+                node-key="id"
+                :default-expanded-keys="defaultExpandedKeys"
+                highlight-current
+                @node-click="handleAreaNodeClick"
+              >
+                <span class="custom-tree-node" slot-scope="{ node, data }">
+                  <span class="tree-label">
+                    <i :class="getTreeNodeIcon(data)" class="tree-icon"></i>
+                    {{ node.label }}
+                  </span>
+                  <el-tag
+                    v-if="data.id !== '-1'"
+                    size="mini"
+                    effect="plain"
+                    class="tree-tag"
+                  >
+                    区域
+                  </el-tag>
+                </span>
+              </el-tree>
             </div>
           </el-col>
 
-          <!-- 右侧内容区 -->
           <el-col :span="19">
             <div class="detail-content">
-              <!-- 筛选条件 -->
               <div class="filter-section">
                 <div class="filter-left">
                   <span class="current-area">
@@ -259,7 +258,6 @@
                 </div>
               </div>
 
-              <!-- 设施汇总表格 -->
               <div class="detail-table-section">
                 <div class="section-header">
                   <h3 class="section-title"><i class="el-icon-s-grid"></i> 设施用电汇总</h3>
@@ -313,7 +311,6 @@
                 </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>
@@ -337,7 +334,6 @@
                   </div>
                 </div>
                 <div class="chart-container" v-loading="detailChartLoading">
-                  <!-- 关键修复:每个tab使用独立的ref名称 -->
                   <div
                     :ref="'detailChart_' + item.code"
                     class="chart-canvas"
@@ -394,10 +390,11 @@ export default {
       // ========== 设施分类 ==========
       facsCategoryOptions: [],
 
-      // ========== 区域树 ==========
+      // ========== 区域树 (更新配置) ==========
       areaKeyword: '',
       areaTreeData: [],
       treeProps: { children: 'children', label: 'label' },
+      defaultExpandedKeys: ['-1'], // 默认展开全部
       selectedAreaCode: '-1',
       selectedAreaLabel: '全部',
 
@@ -506,7 +503,7 @@ export default {
   },
 
   methods: {
-    // ==================== 工具方法 ====================
+    // ... (保留原有的 formatNumber, formatSmartNumber 等工具方法) ...
     formatSmartNumber(value) {
       if (value === null || value === undefined) {
         return { display: '0', suffix: '', full: '0', isShortened: false }
@@ -572,26 +569,19 @@ export default {
       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)
       }
@@ -610,17 +600,15 @@ export default {
         const response = await listFacsMeterHourSta(params)
         const data = response.data || []
 
-        // 修改:字段从 quantity 改为 totalElecQuantity
         this.overviewTableData = data
           .map(item => ({
             objCode: item.objCode,
             objName: item.objName,
-            quantity: item.totalElecQuantity || 0  // 修改此处
+            quantity: item.totalElecQuantity || 0
           }))
           .filter(item => item.quantity > 0)
           .sort((a, b) => b.quantity - a.quantity)
 
-        // 修改:利用新字段获取分时电量统计
         const totalQuantity = data.reduce((sum, item) => sum + (item.totalElecQuantity || 0), 0)
         const peakQuantity = data.reduce((sum, item) => sum + (item.peakQuantity || 0) + (item.sharpPeakQuantity || 0), 0)
         const valleyQuantity = data.reduce((sum, item) => sum + (item.valleyQuantity || 0) + (item.deepValleyQuantity || 0), 0)
@@ -629,10 +617,10 @@ export default {
 
         this.overviewSummary = {
           totalQuantity: totalQuantity,
-          peakQuantity: peakQuantity,      // 现在可以正确显示峰时用电
-          valleyQuantity: valleyQuantity,  // 现在可以正确显示谷时用电
+          peakQuantity: peakQuantity,
+          valleyQuantity: valleyQuantity,
           normalQuantity: normalQuantity,
-          totalCost: totalCost             // 现在可以正确显示总电费
+          totalCost: totalCost
         }
 
         this.$nextTick(() => this.renderOverviewChart())
@@ -644,26 +632,22 @@ export default {
       }
     },
 
+    // 树数据加载:只负责加载数据,不负责业务重置
     async loadAreaTree() {
       try {
         const response = await areaTreeByFacsCategory('Z', this.queryParams.facsSubCategory, false)
+        // 使用 ID -1 确保 key 唯一
         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
 
@@ -677,21 +661,14 @@ export default {
           facsSubCategory: this.queryParams.facsSubCategory
         }
 
-        console.log('[loadDetailData] 请求参数:', baseParams)
-
-        // 1. 加载设施汇总数据
         const summaryRes = await listFacsMeterHourSta(baseParams)
-        console.log('[loadDetailData] 设施汇总响应:', summaryRes)
 
-        // 修改:字段从 quantity 改为 totalElecQuantity
         this.facsSummaryList = (summaryRes.data || []).map(item => ({
           objCode: item.objCode,
           objName: item.objName,
-          quantity: item.totalElecQuantity || 0  // 修改此处
+          quantity: item.totalElecQuantity || 0
         }))
-        console.log('[loadDetailData] 设施汇总数据:', this.facsSummaryList)
 
-        // 更新设施下拉选项
         this.facsOptions = this.facsSummaryList.map(item => ({
           objCode: item.objCode,
           objName: item.objName
@@ -699,13 +676,10 @@ export default {
 
         this.detailLoading = false
 
-        // 2. 加载时段电耗数据 - 分别获取每个设施的小时数据
         await this.loadHourlyDataByFacs(baseParams)
 
-        // 3. 渲染图表 - 使用更长的延迟确保DOM已渲染
         this.$nextTick(() => {
           setTimeout(() => {
-            console.log('[loadDetailData] 准备渲染图表')
             this.renderDetailChart()
           }, 300)
         })
@@ -718,20 +692,11 @@ export default {
       }
     },
 
-    /**
-     * 分别加载每个设施的小时数据 - 关键修复
-     */
     async loadHourlyDataByFacs(baseParams) {
-      console.log('[loadHourlyDataByFacs] 开始加载小时数据')
-
-      // 如果选择了具体设施,只查该设施
       if (this.selectedObjCode) {
         const params = { ...baseParams, objCode: this.selectedObjCode }
-        console.log('[loadHourlyDataByFacs] 查询单个设施:', this.selectedObjCode)
-
         const hourlyRes = await listFacsElecHourMeter(params)
         const rawData = hourlyRes.rows || []
-
         const facs = this.facsSummaryList.find(f => f.objCode === this.selectedObjCode)
         const facsName = facs ? facs.objName : this.selectedObjCode
 
@@ -740,16 +705,12 @@ export default {
           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 listFacsElecHourMeter(baseParams)
         const rawData = hourlyRes.rows || []
 
@@ -758,16 +719,12 @@ export default {
         } 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 listFacsElecHourMeter(params)
           const rawData = hourlyRes.rows || []
 
@@ -777,7 +734,6 @@ export default {
             hourlyData: this.processHourlyData(rawData)
           }
         } catch (error) {
-          console.error(`加载设施 ${facs.objCode} 小时数据失败:`, error)
           return {
             objCode: facs.objCode,
             objName: facs.objName,
@@ -787,21 +743,15 @@ export default {
       })
 
       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"
+          time: item.time ? item.time.substring(0, 5) : '',
           value: parseFloat(item.elecQuantity) || 0,
           recordTime: item.recordTime
         }))
@@ -809,16 +759,11 @@ export default {
         .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,
@@ -826,24 +771,18 @@ export default {
             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({
@@ -859,7 +798,6 @@ export default {
       })
     },
 
-    // ==================== 图表渲染 ====================
     renderOverviewChart() {
       const chartDom = this.$refs.overviewChartRef
       if (!chartDom || this.overviewTableData.length === 0) return
@@ -882,6 +820,7 @@ export default {
       this.overviewChartInstance.setOption(option)
     },
 
+    // ... (保留图表Option配置方法) ...
     getPieChartOption(data) {
       const total = data.reduce((sum, item) => sum + item.value, 0)
       return {
@@ -910,7 +849,6 @@ export default {
         }]
       }
     },
-
     getBarChartOption(data) {
       return {
         tooltip: {
@@ -946,26 +884,11 @@ export default {
       }
     },
 
-    /**
-     * 渲染详情图表 - 多设施对比(核心修复版)
-     */
     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 (!chartDom) return
 
       if (this.hourlyDataByFacs.length === 0) {
-        console.warn('[renderDetailChart] 无时段数据')
         if (this.detailChartInstance) {
           this.detailChartInstance.dispose()
           this.detailChartInstance = null
@@ -973,46 +896,31 @@ export default {
         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 {
@@ -1056,7 +964,6 @@ export default {
               <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
@@ -1065,14 +972,12 @@ export default {
                 <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>`
             }
-
             html += '</div>'
             return html
           }
@@ -1114,38 +1019,30 @@ export default {
       }
 
       this.detailChartInstance.setOption(option)
-      console.log('[renderDetailChart] 图表渲染完成,系列数:', series.length, '时间点:', xAxisData.length)
     },
 
-    // ==================== 颜色相关 ====================
     getChartColor(index) {
       const colors = ['#4facfe', '#43e97b', '#fa709a', '#fee140', '#30cfd0', '#667eea', '#f093fb', '#f5576c', '#00f2fe', '#38f9d7', '#f77062', '#fe5196']
       return colors[index % colors.length]
     },
-
     getSeriesColor(index) {
       const colors = ['#4facfe', '#43e97b', '#fa709a', '#fee140', '#667eea', '#30cfd0', '#f093fb', '#f5576c']
       return colors[index % colors.length]
     },
-
     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})`
     },
-
     getProgressColor(percentage) {
       if (percentage >= 30) return '#f56c6c'
       if (percentage >= 15) return '#e6a23c'
       return '#409eff'
     },
 
-    // ==================== 事件处理 ====================
+    // 交互逻辑修改:在切换Tab时,手动设置当前树的高亮,防止状态丢失
     handleTabClick(tab) {
-      console.log('[handleTabClick] 切换Tab:', tab.name)
-
-      // 销毁旧图表实例
       if (this.detailChartInstance) {
         this.detailChartInstance.dispose()
         this.detailChartInstance = null
@@ -1155,15 +1052,24 @@ export default {
         this.$nextTick(() => this.loadOverviewData())
       } else {
         this.queryParams.facsSubCategory = tab.name
-        console.log('[handleTabClick] 切换到设施分类:', tab.name)
-
         this.resetDetailState()
-        this.loadAreaTree()
 
-        // 增加延迟确保 tab-pane 完全渲染
+        // 1. 加载树数据
+        this.loadAreaTree().then(() => {
+          // 2. 确保 "全部" 节点被高亮
+          this.$nextTick(() => {
+            const refName = 'areaTree_' + tab.name
+            const treeRef = this.$refs[refName]
+            // v-for 中的 ref 是数组
+            const tree = Array.isArray(treeRef) ? treeRef[0] : treeRef
+            if (tree) {
+              tree.setCurrentKey('-1')
+            }
+          })
+        })
+
         this.$nextTick(() => {
           setTimeout(() => {
-            console.log('[handleTabClick] 开始加载详情数据')
             this.loadDetailData()
           }, 400)
         })
@@ -1204,26 +1110,22 @@ export default {
       return data.label && data.label.indexOf(value) !== -1
     },
 
-    // ==================== 表格相关 ====================
     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
     },
-
     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) => {
@@ -1243,7 +1145,6 @@ export default {
       })
       return sums
     },
-
     getFacsSummaries({ columns }) {
       const sums = []
       columns.forEach((column, index) => {
@@ -1264,13 +1165,13 @@ export default {
       return sums
     },
 
+    // 树图标逻辑:保持与源网侧一致
     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'
+      if (data.children && data.children.length > 0) return 'el-icon-office-building'
+      return 'el-icon-location-information'
     },
 
-    // ==================== 生命周期 ====================
     handleResize() {
       if (this.overviewChartInstance) this.overviewChartInstance.resize()
       if (this.detailChartInstance) this.detailChartInstance.resize()
@@ -1296,6 +1197,7 @@ export default {
   background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
   min-height: calc(100vh - 84px);
 
+  // ... (保留 main-tabs, summary-section 等其他原有样式) ...
   .main-tabs {
     ::v-deep .el-tabs__header {
       background: #fff;
@@ -1474,48 +1376,88 @@ export default {
     }
   }
 
-  .tree-panel {
+  .head-container {
     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);
+    padding: 15px;
+    border-radius: 8px;
+    margin-bottom: 15px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    border: 1px solid #ebeef5; // 增加边框以匹配整体风格
+
+    &.tree-container {
+      max-height: calc(100vh - 350px);
       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-track {
+        background: #f1f1f1;
+        border-radius: 3px;
+      }
+
       &::-webkit-scrollbar-thumb {
         background: #c1c1c1;
         border-radius: 3px;
       }
+
+      &::-webkit-scrollbar-thumb:hover {
+        background: #a8a8a8;
+      }
+
+      ::v-deep .el-tree {
+        background: transparent;
+
+        .el-tree-node__content {
+          height: 40px;
+          padding: 0 8px;
+
+          &:hover {
+            background-color: #f5f7fa;
+          }
+        }
+
+        /* 核心 CSS 修复:仅选中节点高亮 */
+        .el-tree-node.is-current > .el-tree-node__content {
+          background-color: #ecf5ff;
+          color: #409eff;
+
+          .tree-icon {
+            color: #409eff !important;
+          }
+        }
+
+        .custom-tree-node {
+          flex: 1;
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          font-size: 14px;
+
+          .tree-label {
+            display: flex;
+            align-items: center;
+
+            .tree-icon {
+              margin-right: 8px;
+              font-size: 16px;
+              color: #409eff;
+            }
+          }
+
+          .tree-tag {
+            margin-right: 8px;
+
+            &.warning {
+              color: #e6a23c;
+              background: #fdf6ec;
+              border-color: #f5dab1;
+            }
+          }
+        }
+      }
     }
   }
 

+ 97 - 212
ems-ui-cloud/src/views/prediction/consume.vue

@@ -1,7 +1,6 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
-      <!-- 左侧树形区域 -->
       <el-col :span="5" :xs="24">
         <div class="head-container">
           <el-input
@@ -28,7 +27,7 @@
           >
             <span class="custom-tree-node" slot-scope="{ node, data }">
               <span class="tree-label">
-                <i :class="getTreeIcon(data, node)" class="tree-icon"></i>
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
                 {{ node.label }}
               </span>
               <el-tag
@@ -44,10 +43,8 @@
         </div>
       </el-col>
 
-      <!-- 右侧内容区域 -->
       <el-col :span="19" :xs="24">
         <div class="content-wrapper">
-          <!-- 标题区域 -->
           <div class="content-header">
             <div class="header-left">
               <h3 class="page-title">
@@ -69,7 +66,6 @@
             </div>
           </div>
 
-          <!-- 搜索区域 -->
           <el-form
             :model="queryParams"
             ref="queryForm"
@@ -96,10 +92,8 @@
             </el-form-item>
           </el-form>
 
-          <!-- 汇总统计卡片 -->
           <div class="summary-section" v-loading="summaryLoading">
             <el-row :gutter="16">
-              <!-- 预测周期卡片 -->
               <el-col :span="6">
                 <div class="stat-card">
                   <div class="card-icon period-icon">
@@ -116,7 +110,6 @@
                 </div>
               </el-col>
 
-              <!-- 总用电量卡片 -->
               <el-col :span="6">
                 <div class="stat-card">
                   <div class="card-icon total-icon">
@@ -135,7 +128,6 @@
                 </div>
               </el-col>
 
-              <!-- 日均用电量卡片 -->
               <el-col :span="6">
                 <div class="stat-card">
                   <div class="card-icon avg-icon">
@@ -152,7 +144,6 @@
                 </div>
               </el-col>
 
-              <!-- 子项数量卡片 -->
               <el-col :span="6">
                 <div class="stat-card">
                   <div class="card-icon count-icon">
@@ -171,7 +162,6 @@
             </el-row>
           </div>
 
-          <!-- 趋势图表区域 -->
           <div class="chart-section" v-if="showChart">
             <div class="section-header">
               <h4 class="section-title">
@@ -194,7 +184,6 @@
             </div>
           </div>
 
-          <!-- 数据表格区域 -->
           <div class="table-section">
             <div class="section-header">
               <h4 class="section-title">
@@ -226,7 +215,9 @@
             >
               <el-table-column label="序号" type="index" width="60" align="center">
                 <template slot-scope="scope">
-                  <span class="row-index">{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
+                  <span class="row-index">{{
+                      (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1
+                    }}</span>
                 </template>
               </el-table-column>
 
@@ -311,7 +302,6 @@ export default {
       },
       total: 0,
       forecastConsumeList: [],
-      // 汇总数据
       summaryData: {
         dateRange: '',
         dayCount: 0,
@@ -320,14 +310,11 @@ export default {
         itemCount: 0,
         trend: null
       },
-      // 每日趋势数据
       dailyTrendData: [],
       chartInstance: null,
       chartType: 'bar',
       showChart: true,
-      // 日期范围
       dateRange: [],
-      // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
@@ -338,7 +325,6 @@ export default {
         startRecTime: null,
         endRecTime: null
       },
-      // 日期选择器配置 - 只允许选择明天及之后的日期
       datePickerOptions: {
         disabledDate: (time) => {
           const today = new Date()
@@ -378,7 +364,6 @@ export default {
           }
         ]
       },
-      // 最大用电量(用于计算进度条)
       maxElec: 0
     }
   },
@@ -402,18 +387,25 @@ export default {
     }
   },
   methods: {
-    /**
-     * 获取明天的日期
-     */
+    getTreeIcon(data) {
+      // 1. 设施节点 (facsCategory 为 Z) -> CPU图标
+      if (data.facsCategory === 'Z') {
+        return 'el-icon-cpu'
+      }
+      // 2. 根节点 (ID为-1) -> 首页图标
+      if (data.id === -1 || data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      // 3. 区域节点 -> 写字楼图标 (与其他界面保持一致)
+      return 'el-icon-office-building'
+    },
+
     getTomorrow() {
       const tomorrow = new Date()
       tomorrow.setDate(tomorrow.getDate() + 1)
       return tomorrow
     },
 
-    /**
-     * 初始化日期范围 - 从明天开始
-     */
     initDateRange() {
       const tomorrow = this.getTomorrow()
       const endDate = new Date(tomorrow)
@@ -424,9 +416,6 @@ export default {
       this.queryParams.endRecTime = this.dateRange[1]
     },
 
-    /**
-     * 日期变化处理
-     */
     handleDateChange(val) {
       if (val && val.length === 2) {
         const tomorrow = this.getTomorrow()
@@ -450,9 +439,6 @@ export default {
       }
     },
 
-    /**
-     * 加载所有数据
-     */
     async loadAllData() {
       await Promise.all([
         this.getConsumeList(),
@@ -461,9 +447,6 @@ export default {
       this.calculateSummary()
     },
 
-    /**
-     * 获取列表数据
-     */
     async getConsumeList() {
       this.loading = true
       try {
@@ -482,9 +465,6 @@ export default {
       }
     },
 
-    /**
-     * 获取每日趋势数据
-     */
     async getDailyTrendData() {
       this.chartLoading = true
       try {
@@ -507,9 +487,6 @@ export default {
       }
     },
 
-    /**
-     * 计算汇总数据
-     */
     calculateSummary() {
       this.summaryLoading = true
 
@@ -546,9 +523,6 @@ export default {
       }
     },
 
-    /**
-     * 渲染图表
-     */
     renderChart() {
       if (!this.$refs.chartRef || !this.dailyTrendData.length) return
 
@@ -569,150 +543,78 @@ export default {
           trigger: 'axis',
           backgroundColor: 'rgba(50, 50, 50, 0.9)',
           borderColor: 'transparent',
-          textStyle: {
-            color: '#fff'
-          },
+          textStyle: { color: '#fff' },
           formatter: (params) => {
             const data = params[0]
-            return `
-              <div style="padding: 8px;">
-                <div style="margin-bottom: 8px; color: #aaa;">${data.name}</div>
-                <div style="font-size: 18px; font-weight: bold; color: #4facfe;">
-                  ${this.formatNumber(data.value)} kW·h
-                </div>
-              </div>
-            `
+            return `<div style="padding: 8px;"><div style="margin-bottom: 8px; color: #aaa;">${data.name}</div><div style="font-size: 18px; font-weight: bold; color: #4facfe;">${this.formatNumber(data.value)} kW·h</div></div>`
           }
         },
-        grid: {
-          left: '3%',
-          right: '4%',
-          bottom: '10%',
-          top: '12%',
-          containLabel: true
-        },
+        grid: { left: '3%', right: '4%', bottom: '10%', top: '12%', containLabel: true },
         xAxis: {
           type: 'category',
           data: dates,
-          axisLine: {
-            lineStyle: {
-              color: '#e0e0e0'
-            }
-          },
-          axisTick: {
-            show: false
-          },
-          axisLabel: {
-            color: '#666',
-            fontSize: 11,
-            rotate: dates.length > 10 ? 45 : 0
-          }
+          axisLine: { lineStyle: { color: '#e0e0e0' } },
+          axisTick: { show: false },
+          axisLabel: { color: '#666', fontSize: 11, rotate: dates.length > 10 ? 45 : 0 }
         },
         yAxis: {
           type: 'value',
           name: 'kW·h',
-          nameTextStyle: {
-            color: '#999',
-            fontSize: 11
-          },
-          axisLine: {
-            show: false
-          },
-          axisTick: {
-            show: false
-          },
-          splitLine: {
-            lineStyle: {
-              color: '#f0f0f0',
-              type: 'dashed'
-            }
-          },
-          axisLabel: {
-            color: '#666',
-            fontSize: 11
-          }
+          nameTextStyle: { color: '#999', fontSize: 11 },
+          axisLine: { show: false },
+          axisTick: { show: false },
+          splitLine: { lineStyle: { color: '#f0f0f0', type: 'dashed' } },
+          axisLabel: { color: '#666', fontSize: 11 }
         },
-        series: [
-          {
-            name: '预测用电量',
-            type: this.chartType,
-            data: values,
-            smooth: true,
-            symbol: 'circle',
-            symbolSize: 6,
-            itemStyle: {
-              color: '#4facfe',
-              borderColor: '#fff',
-              borderWidth: 2
-            },
-            lineStyle: {
-              width: 3,
-              color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
-                { offset: 0, color: '#4facfe' },
-                { offset: 1, color: '#00f2fe' }
-              ])
-            },
-            areaStyle: this.chartType === 'line' ? {
-              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-                { offset: 0, color: 'rgba(79, 172, 254, 0.4)' },
-                { offset: 1, color: 'rgba(79, 172, 254, 0.02)' }
-              ])
-            } : undefined,
-            barWidth: '40%',
-            barMaxWidth: 40,
-            emphasis: {
-              itemStyle: {
-                shadowBlur: 20,
-                shadowColor: 'rgba(79, 172, 254, 0.5)'
-              }
-            }
-          }
-        ]
+        series: [{
+          name: '预测用电量',
+          type: this.chartType,
+          data: values,
+          smooth: true,
+          symbol: 'circle',
+          symbolSize: 6,
+          itemStyle: { color: '#4facfe', borderColor: '#fff', borderWidth: 2 },
+          lineStyle: {
+            width: 3,
+            color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{ offset: 0, color: '#4facfe' }, {
+              offset: 1,
+              color: '#00f2fe'
+            }])
+          },
+          areaStyle: this.chartType === 'line' ? {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+              offset: 0,
+              color: 'rgba(79, 172, 254, 0.4)'
+            }, { offset: 1, color: 'rgba(79, 172, 254, 0.02)' }])
+          } : undefined,
+          barWidth: '40%',
+          barMaxWidth: 40,
+          emphasis: { itemStyle: { shadowBlur: 20, shadowColor: 'rgba(79, 172, 254, 0.5)' } }
+        }]
       }
 
       if (this.chartType === 'bar') {
         option.series[0].itemStyle = {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: '#4facfe' },
-            { offset: 1, color: '#00f2fe' }
-          ]),
-          borderRadius: [4, 4, 0, 0]
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+            offset: 0,
+            color: '#4facfe'
+          }, { offset: 1, color: '#00f2fe' }]), borderRadius: [4, 4, 0, 0]
         }
       }
 
       this.chartInstance.setOption(option)
     },
 
-    /**
-     * 处理窗口大小变化
-     */
     handleResize() {
       if (this.chartInstance) {
         this.chartInstance.resize()
       }
     },
 
-    /**
-     * 获取树节点图标
-     */
-    getTreeIcon(data, node) {
-      if (data.facsCategory === 'Z') return 'el-icon-cpu'
-      if (data.id === -1) return 'el-icon-s-home'
-      if (!node.parent || node.level === 1) return 'el-icon-office-building'
-      if (node.isLeaf) return 'el-icon-place'
-      return 'el-icon-folder'
-    },
-
-    /**
-     * 获取对象图标
-     */
     getObjIcon(row) {
       return this.activeName === 'facsConsume' ? 'el-icon-cpu' : 'el-icon-location-outline'
     },
 
-    /**
-     * 切换tab
-     */
     handleTabChange(tabName) {
       this.clear()
       if (tabName === 'areaConsume') {
@@ -725,9 +627,6 @@ export default {
       this.loadAllData()
     },
 
-    /**
-     * 重置
-     */
     clear() {
       this.queryParams.areaCode = null
       this.queryParams.objCode = null
@@ -746,9 +645,6 @@ export default {
       }
     },
 
-    /**
-     * 获取区域树
-     */
     async getAreaList() {
       try {
         const response = await areaTreeSelect(0, 3)
@@ -768,9 +664,6 @@ export default {
       }
     },
 
-    /**
-     * 获取设施树
-     */
     async getFacsList() {
       try {
         const response = await getFacsCategoryTree()
@@ -786,9 +679,6 @@ export default {
       }
     },
 
-    /**
-     * 扁平化树数据
-     */
     flattenTreeData(regions) {
       if (!Array.isArray(regions)) return []
       return regions.map(region => {
@@ -807,17 +697,11 @@ export default {
       }).filter(Boolean)
     },
 
-    /**
-     * 搜索
-     */
     handleQuery() {
       this.queryParams.pageNum = 1
       this.loadAllData()
     },
 
-    /**
-     * 重置查询
-     */
     resetQuery() {
       this.initDateRange()
       this.queryParams.objCode = null
@@ -831,24 +715,15 @@ export default {
       this.handleQuery()
     },
 
-    /**
-     * 树筛选
-     */
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
 
-    /**
-     * 过滤树
-     */
     filterTree() {
       this.$refs.tree.filter(this.areaName)
     },
 
-    /**
-     * 节点点击
-     */
     handleNodeClick(data, node) {
       this.selectedNode = data
 
@@ -866,9 +741,6 @@ export default {
       this.loadAllData()
     },
 
-    /**
-     * 获取顶级节点ID
-     */
     getTopLevelId(node) {
       let current = node
       while (current.parent && current.parent.data.id !== -1) {
@@ -877,9 +749,6 @@ export default {
       return current.data.id
     },
 
-    /**
-     * 格式化数字
-     */
     formatNumber(value) {
       if (value === null || value === undefined) return '0.00'
       const num = parseFloat(value)
@@ -890,9 +759,46 @@ export default {
       })
     },
 
-    /**
-     * 格式化日期
-     */
+    parseTime(time, pattern) {
+      if (arguments.length === 0 || !time) {
+        return null
+      }
+      const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
+      let date
+      if (typeof time === 'object') {
+        date = time
+      } else {
+        if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+          time = parseInt(time)
+        }
+        if ((typeof time === 'number') && (time.toString().length === 10)) {
+          time = time * 1000
+        }
+        date = new Date(time)
+      }
+      const formatObj = {
+        y: date.getFullYear(),
+        m: date.getMonth() + 1,
+        d: date.getDate(),
+        h: date.getHours(),
+        i: date.getMinutes(),
+        s: date.getSeconds(),
+        a: date.getDay()
+      }
+      const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+        let value = formatObj[key]
+        // Note: getDay() returns 0 on Sunday
+        if (key === 'a') {
+          return ['日', '一', '二', '三', '四', '五', '六'][value]
+        }
+        if (result.length > 0 && value < 10) {
+          value = '0' + value
+        }
+        return value || 0
+      })
+      return time_str
+    },
+
     formatDate(date) {
       if (!date) return ''
       const d = new Date(date)
@@ -902,9 +808,6 @@ export default {
       return `${year}-${month}-${day}`
     },
 
-    /**
-     * 获取趋势文本
-     */
     getTrendText(trend) {
       if (trend === null || trend === undefined) return '— 较上期'
       const abs = Math.abs(trend).toFixed(1)
@@ -913,9 +816,6 @@ export default {
       return '— 持平'
     },
 
-    /**
-     * 获取数值级别
-     */
     getValueLevel(value) {
       const num = parseFloat(value) || 0
       if (num >= 500) return 'high'
@@ -923,34 +823,22 @@ export default {
       return 'low'
     },
 
-    /**
-     * 获取进度条宽度
-     */
     getBarWidth(value) {
       if (!this.maxElec) return 0
       const num = parseFloat(value) || 0
       return Math.min((num / this.maxElec) * 100, 100)
     },
 
-    /**
-     * 获取百分比
-     */
     getPercentage(value) {
       if (!this.summaryData.totalElec) return '0.0'
       const num = parseFloat(value) || 0
       return ((num / this.summaryData.totalElec) * 100).toFixed(1)
     },
 
-    /**
-     * 表格行样式
-     */
     tableRowClassName({ rowIndex }) {
       return rowIndex % 2 === 0 ? 'even-row' : 'odd-row'
     },
 
-    /**
-     * 合计行
-     */
     getSummaries({ columns, data }) {
       const sums = []
       columns.forEach((column, index) => {
@@ -968,9 +856,6 @@ export default {
       return sums
     },
 
-    /**
-     * 导出
-     */
     async handleExport() {
       this.exportLoading = true
       try {

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä