6
0

2 کامیت‌ها 794f8a8015 ... 42c9d5f591

نویسنده SHA1 پیام تاریخ
  learshaw 42c9d5f591 样式统一调整 2 هفته پیش
  learshaw bdc0843554 样式统一调整 2 هفته پیش
30فایلهای تغییر یافته به همراه11140 افزوده شده و 3619 حذف شده
  1. 318 174
      ems-ui-cloud/src/views/alarm/alarm-info/index.vue
  2. 309 154
      ems-ui-cloud/src/views/alarm/index.vue
  3. 465 242
      ems-ui-cloud/src/views/analysis/power/consume.vue
  4. 410 100
      ems-ui-cloud/src/views/analysis/power/prod.vue
  5. 767 308
      ems-ui-cloud/src/views/analysis/power/save.vue
  6. 570 117
      ems-ui-cloud/src/views/analysis/power/store.vue
  7. 139 57
      ems-ui-cloud/src/views/analysis/report/statement-consume.vue
  8. 621 168
      ems-ui-cloud/src/views/analysis/report/statement-prod.vue
  9. 536 112
      ems-ui-cloud/src/views/analysis/report/statement-self.vue
  10. 404 148
      ems-ui-cloud/src/views/analysis/report/statement-warn.vue
  11. 763 152
      ems-ui-cloud/src/views/ca/emission.vue
  12. 714 138
      ems-ui-cloud/src/views/ca/emissionCalc.vue
  13. 272 139
      ems-ui-cloud/src/views/devmgr/device/index.vue
  14. 243 83
      ems-ui-cloud/src/views/devmgr/el/index.vue
  15. 0 0
      ems-ui-cloud/src/views/devmgr/warn/DevcWarning/img/icon_waring.svg
  16. 0 0
      ems-ui-cloud/src/views/devmgr/warn/DevcWarning/index.scss
  17. 0 0
      ems-ui-cloud/src/views/devmgr/warn/DevcWarning/index.vue
  18. 0 0
      ems-ui-cloud/src/views/devmgr/warn/index.scss
  19. 0 0
      ems-ui-cloud/src/views/devmgr/warn/index.vue
  20. 1 1
      ems-ui-cloud/src/views/devmgr/warn/warn.vue
  21. 0 293
      ems-ui-cloud/src/views/ems/EmsEcoD/index.vue
  22. 85 95
      ems-ui-cloud/src/views/mgr/charging.vue
  23. 946 149
      ems-ui-cloud/src/views/mgr/chargingpile.vue
  24. 700 206
      ems-ui-cloud/src/views/mgr/powergrid.vue
  25. 531 139
      ems-ui-cloud/src/views/mgr/powerstore.vue
  26. 167 35
      ems-ui-cloud/src/views/mgr/poweruse.vue
  27. 495 288
      ems-ui-cloud/src/views/mgr/strategy.vue
  28. 696 87
      ems-ui-cloud/src/views/prediction/ca.vue
  29. 514 112
      ems-ui-cloud/src/views/prediction/consume.vue
  30. 474 122
      ems-ui-cloud/src/views/prediction/prod.vue

+ 318 - 174
ems-ui-cloud/src/views/alarm/alarm-info/index.vue

@@ -1,114 +1,120 @@
 <template>
   <div class="app-container">
-    <el-row :gutter="10">
-      <el-col :span="4" :xs="24">
+    <el-row :gutter="20">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入服务区名称" clearable size="small" prefix-icon="el-icon-search"
-            style="margin-bottom: 20px" />
+          <el-input
+            v-model="areaName"
+            placeholder="请输入服务区名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container">
-          <el-tree ref="tree" :data="areaOptions" default-expand-all :expand-on-click-node="false" :filter-node-method="filterNode"
-            node-key="id" highlight-current @node-click="handleNodeClick" />
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <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">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
-          label-width="68px">
-          <el-form-item label="告警类型" prop="alarmType">
-            <el-select v-model="queryParams.alarmType" placeholder="请选择告警类型" clearable>
-              <el-option v-for="dict in dict.type.alarm_type" :key="dict.value" :label="dict.label"
-                :value="dict.value" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="子系统" prop="systemCode">
-            <el-select v-model="queryParams.systemCode" placeholder="请选择子系统">
-              <el-option v-for="subsystem in subsystemList" :key="subsystem.systemCode" :label="subsystem.systemName"
-                :value="subsystem.systemCode"></el-option>
-            </el-select>
-          </el-form-item>
-          <el-form-item label="告警状态" prop="alarmState">
-            <el-select v-model="queryParams.alarmState" placeholder="请选择告警状态">
-              <el-option v-for="dict in dict.type.alarm_state" :key="dict.value" :label="dict.label"
-                :value="dict.value" />
-            </el-select>
-          </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-form-item>
-        </el-form>
-
-        <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
-              v-hasPermi="['ems:alarm-info:add']">新增
-            </el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"
-              v-hasPermi="['ems:alarm-info:edit']">修改
-            </el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
-              v-hasPermi="['ems:alarm-info:remove']">删除
-            </el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
-              v-hasPermi="['ems:alarm-info:export']">导出
-            </el-button>
-          </el-col>
-          <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-        </el-row>
-
-        <el-table v-loading="loading" :data="alarmInfoList" @selection-change="handleSelectionChange">
-          <el-table-column label="告警代码" align="center" prop="alarmCode" />
-          <el-table-column label="园区名称" align="left" prop="areaShortName" />
-          <el-table-column label="子系统" align="center" prop="subSystemName" />
-          <el-table-column label="对象类型" align="center" prop="objType">
-            <template slot-scope="scope">
-              <dict-tag :options="dict.type.obj_type" :value="scope.row.objType" />
-            </template>
-          </el-table-column>
-          <el-table-column label="对象代码" align="center" prop="objCode" />
-          <el-table-column label="告警时间" align="center" prop="alarmDate" width="180">
-            <template slot-scope="scope">
-              <span>{{ parseTime(scope.row.alarmDate, '{y}-{m}-{d}') + " " + parseTime(scope.row.alarmTime,
-                '{hh}:{mm}:{s}')}}</span>
-            </template>
-          </el-table-column>
-
-          <el-table-column label="告警描述" align="center" prop="alarmMsg" />
-          <el-table-column label="告警类型" align="center" prop="alarmType">
-            <template slot-scope="scope">
-              <dict-tag :options="dict.type.alarm_type" :value="scope.row.alarmType" />
-            </template>
-          </el-table-column>
-          <el-table-column label="告警状态" align="center" prop="alarmState">
-            <template slot-scope="scope">
-              <dict-tag :options="dict.type.alarm_state" :value="scope.row.alarmState" />
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template slot-scope="scope">
-              <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
-                v-hasPermi="['ems:alarm-info:edit']">修改
-              </el-button>
-              <el-button size="mini" type="text" icon="el-icon-delete" class="deleteBtn"
-                @click="handleDelete(scope.row)" v-hasPermi="['basecfg:alarm-info:remove']">
-                删除</el-button>
-
-            </template>
-          </el-table-column>
-        </el-table>
-
-        <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
-          @pagination="getList" />
-
-      </el-col></el-row>
 
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+            <el-form-item label="告警类型" prop="alarmType">
+              <el-select v-model="queryParams.alarmType" placeholder="请选择告警类型" clearable>
+                <el-option v-for="dict in dict.type.alarm_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="子系统" prop="systemCode">
+              <el-select v-model="queryParams.systemCode" placeholder="请选择子系统">
+                <el-option v-for="subsystem in subsystemList" :key="subsystem.systemCode" :label="subsystem.systemName" :value="subsystem.systemCode"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="告警状态" prop="alarmState">
+              <el-select v-model="queryParams.alarmState" placeholder="请选择告警状态">
+                <el-option v-for="dict in dict.type.alarm_state" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </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-form-item>
+          </el-form>
+
+          <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+              <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['ems:alarm-info:add']">新增</el-button>
+            </el-col>
+            <el-col :span="1.5">
+              <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate" v-hasPermi="['ems:alarm-info:edit']">修改</el-button>
+            </el-col>
+            <el-col :span="1.5">
+              <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['ems:alarm-info:remove']">删除</el-button>
+            </el-col>
+            <el-col :span="1.5">
+              <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['ems:alarm-info:export']">导出</el-button>
+            </el-col>
+            <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+          </el-row>
 
+          <el-table v-loading="loading" :data="alarmInfoList" @selection-change="handleSelectionChange">
+            <el-table-column label="告警代码" align="center" prop="alarmCode" />
+            <el-table-column label="园区名称" align="left" prop="areaShortName" />
+            <el-table-column label="子系统" align="center" prop="subSystemName" />
+            <el-table-column label="对象类型" align="center" prop="objType">
+              <template slot-scope="scope">
+                <dict-tag :options="dict.type.obj_type" :value="scope.row.objType" />
+              </template>
+            </el-table-column>
+            <el-table-column label="对象代码" align="center" prop="objCode" />
+            <el-table-column label="告警时间" align="center" prop="alarmDate" width="180">
+              <template slot-scope="scope">
+                <span>{{ parseTime(scope.row.alarmDate, '{y}-{m}-{d}') + " " + parseTime(scope.row.alarmTime, '{hh}:{mm}:{s}')}}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="告警描述" align="center" prop="alarmMsg" />
+            <el-table-column label="告警类型" align="center" prop="alarmType">
+              <template slot-scope="scope">
+                <dict-tag :options="dict.type.alarm_type" :value="scope.row.alarmType" />
+              </template>
+            </el-table-column>
+            <el-table-column label="告警状态" align="center" prop="alarmState">
+              <template slot-scope="scope">
+                <dict-tag :options="dict.type.alarm_state" :value="scope.row.alarmState" />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+              <template slot-scope="scope">
+                <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems:alarm-info:edit']">修改</el-button>
+                <el-button size="mini" type="text" icon="el-icon-delete" class="deleteBtn" @click="handleDelete(scope.row)" v-hasPermi="['basecfg:alarm-info:remove']">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+        </div>
+      </el-col>
+    </el-row>
 
     <!-- 添加或修改能源设施告警对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
@@ -118,23 +124,19 @@
         </el-form-item>
         <el-form-item label="对象类型" prop="objType">
           <el-select v-model="form.objType" placeholder="请选择对象类型">
-            <el-option v-for="dict in dict.type.obj_type" :key="dict.value" :label="dict.label"
-              :value="parseInt(dict.value)"></el-option>
+            <el-option v-for="dict in dict.type.obj_type" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
           </el-select>
         </el-form-item>
         <el-form-item label="对象代码" prop="objCode">
           <el-input v-model="form.objCode" placeholder="请输入对象代码" />
         </el-form-item>
         <el-form-item label="告警时间" prop="alarmTime">
-          <el-date-picker v-model="form.alarmTime" type="datetime" format="yyyy-MM-dd HH:mm"
-            value-format="yyyy-MM-dd HH:mm:00" :style="{ width: '100%' }" placeholder="请选择告警时间" @change="dateChange"
-            clearable>
+          <el-date-picker v-model="form.alarmTime" type="datetime" format="yyyy-MM-dd HH:mm" value-format="yyyy-MM-dd HH:mm:00" :style="{ width: '100%' }" placeholder="请选择告警时间" @change="dateChange" clearable>
           </el-date-picker>
         </el-form-item>
         <el-form-item label="子系统" prop="systemCode">
           <el-select v-model="form.systemCode" placeholder="请选择子系统">
-            <el-option v-for="subsystem in subsystemList" :key="subsystem.systemCode" :label="subsystem.systemName"
-              :value="subsystem.systemCode"></el-option>
+            <el-option v-for="subsystem in subsystemList" :key="subsystem.systemCode" :label="subsystem.systemName" :value="subsystem.systemCode"></el-option>
           </el-select>
         </el-form-item>
         <el-form-item label="告警代码" prop="alarmCode">
@@ -145,14 +147,12 @@
         </el-form-item>
         <el-form-item label="告警类型" prop="alarmType">
           <el-select v-model="form.alarmType" placeholder="请选择告警类型">
-            <el-option v-for="dict in dict.type.alarm_type" :key="dict.value" :label="dict.label"
-              :value="parseInt(dict.value)"></el-option>
+            <el-option v-for="dict in dict.type.alarm_type" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
           </el-select>
         </el-form-item>
         <el-form-item label="告警状态" prop="alarmState">
           <el-select v-model="form.alarmState" placeholder="请选择告警状态">
-            <el-option v-for="dict in dict.type.alarm_state" :key="dict.value" :label="dict.label"
-              :value="parseInt(dict.value)"></el-option>
+            <el-option v-for="dict in dict.type.alarm_state" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
           </el-select>
         </el-form-item>
       </el-form>
@@ -165,9 +165,9 @@
 </template>
 
 <script>
-import {listSubsystem} from '@/api/adapter/subsystem';
-import {addAlarmInfo, delAlarmInfo, getAlarmInfo, listAlarmInfo, updateAlarmInfo} from '@/api/alarm/alarm-info';
-import dayjs from 'dayjs';
+import {listSubsystem} from '@/api/adapter/subsystem'
+import {addAlarmInfo, delAlarmInfo, getAlarmInfo, listAlarmInfo, updateAlarmInfo} from '@/api/alarm/alarm-info'
+import dayjs from 'dayjs'
 import {areaTreeSelect} from '@/api/basecfg/area'
 
 export default {
@@ -194,7 +194,12 @@ export default {
       title: '',
       // 是否显示弹出层
       open: false,
-      areaOptions: undefined,
+      // 默认展开的节点
+      defaultExpandedKeys: [],
+      defaultProps: {
+        children: 'children',
+        label: 'label'
+      },
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -263,34 +268,49 @@ export default {
       },
       areaName: undefined,
       areaOptions: [],
-    };
+    }
   },
   watch: {
-     // 根据名称筛选区域树
-     areaName (val) {
+    // 根据名称筛选区域树
+    areaName (val) {
       this.$refs.tree.filter(val)
     }
   },
   created () {
-    this.getAreaTree( '0', 1);
-    this.getList();
-    this.getSubList();
+    this.getAreaTree('0', 1)
+    this.getList()
+    this.getSubList()
   },
   methods: {
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     /** 查询能源设施告警列表 */
     getList () {
-      this.loading = true;
+      this.loading = true
       listAlarmInfo(this.queryParams).then(response => {
-        this.alarmInfoList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
+        this.alarmInfoList = response.rows
+        this.total = response.total
+        this.loading = false
+      })
     },
+
     // 取消按钮
     cancel () {
-      this.open = false;
-      this.reset();
+      this.open = false
+      this.reset()
     },
+
     // 表单重置
     reset () {
       this.form = {
@@ -303,108 +323,232 @@ export default {
         alarmMsg: null,
         alarmType: null,
         alarmState: null,
-      };
-      this.resetForm('form');
+      }
+      this.resetForm('form')
     },
+
     async getSubList () {
       const {rows} = await listSubsystem({
         pageNum: 1,
         pageSize: 999,
-      });
-      this.subsystemList = rows;
+      })
+      this.subsystemList = rows
     },
+
     dateChange (val) {
-      this.$refs.form.alarmTime = val;
-      this.$refs.form.alarmDate = dayjs(val).format('YYYY-MM-DD');
-      this.form.alarmDate = dayjs(val).format('YYYY-MM-DD');
+      this.$refs.form.alarmTime = val
+      this.$refs.form.alarmDate = dayjs(val).format('YYYY-MM-DD')
+      this.form.alarmDate = dayjs(val).format('YYYY-MM-DD')
     },
+
     /** 搜索按钮操作 */
     handleQuery () {
-      this.queryParams.pageNum = 1;
-      this.getList();
+      this.queryParams.pageNum = 1
+      this.getList()
     },
+
     /** 重置按钮操作 */
     resetQuery () {
-      this.resetForm('queryForm');
-      this.handleQuery();
+      this.resetForm('queryForm')
+      this.handleQuery()
     },
+
     // 多选框选中数据
     handleSelectionChange (selection) {
-      this.ids = selection.map(item => item.id);
-      this.single = selection.length !== 1;
-      this.multiple = !selection.length;
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
     },
+
     /** 新增按钮操作 */
     handleAdd () {
-      this.reset();
-      this.open = true;
-      this.title = '添加能源设施告警';
+      this.reset()
+      this.open = true
+      this.title = '添加能源设施告警'
     },
+
     /** 修改按钮操作 */
     handleUpdate (row) {
-      this.reset();
-      const id = row.id || this.ids;
+      this.reset()
+      const id = row.id || this.ids
       getAlarmInfo(id).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = '修改能源设施告警';
-      });
+        this.form = response.data
+        this.open = true
+        this.title = '修改能源设施告警'
+      })
     },
+
     /** 提交按钮 */
     submitForm () {
       this.$refs['form'].validate(valid => {
         if (valid) {
           if (this.form.id != null) {
             updateAlarmInfo(this.form).then(response => {
-              this.$modal.msgSuccess('修改成功');
-              this.open = false;
-              this.getList();
-            });
+              this.$modal.msgSuccess('修改成功')
+              this.open = false
+              this.getList()
+            })
           } else {
             addAlarmInfo(this.form).then(response => {
-              this.$modal.msgSuccess('新增成功');
-              this.open = false;
-              this.getList();
-            });
+              this.$modal.msgSuccess('新增成功')
+              this.open = false
+              this.getList()
+            })
           }
         }
-      });
+      })
     },
+
     /** 删除按钮操作 */
     handleDelete (row) {
-      const ids = row.id || this.ids;
+      const ids = row.id || this.ids
       this.$modal.confirm('是否确认删除能源设施告警编号为"' + ids + '"的数据项?').then(function () {
-        return delAlarmInfo(ids);
+        return delAlarmInfo(ids)
       }).then(() => {
-        this.getList();
-        this.$modal.msgSuccess('删除成功');
-      }).catch(() => { });
+        this.getList()
+        this.$modal.msgSuccess('删除成功')
+      }).catch(() => { })
     },
+
     /** 导出按钮操作 */
     handleExport () {
       this.download('ems/alarm-info/export', {
         ...this.queryParams,
-      }, `alarm-info_${new Date().getTime()}.xlsx`);
+      }, `alarm-info_${new Date().getTime()}.xlsx`)
     },
+
     getAreaTree(areaCode, layer) {
       areaTreeSelect(areaCode, layer).then(response => {
-        this.areaOptions =[{
+        this.areaOptions = [{
           id: '-1',
           label: '全部',
           children: []
         }].concat(response.data)
         this.queryParams.areaCode = '-1'
-      });
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      })
     },
-     // 筛选节点
-     filterNode (value, data) {
+
+    // 筛选节点
+    filterNode (value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
     handleNodeClick (data, node) {
       this.queryParams.areaCode = data.id
       this.getList()
     },
   },
-};
+}
 </script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+  }
+}
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+  }
+}
+</style>

+ 309 - 154
ems-ui-cloud/src/views/alarm/index.vue

@@ -1,102 +1,116 @@
 <template>
   <div class="app-container">
-    <el-row :gutter="10">
-      <el-col :span="4" :xs="24">
+    <el-row :gutter="20">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入服务区名称" clearable size="small" prefix-icon="el-icon-search"
-            style="margin-bottom: 20px" />
+          <el-input
+            v-model="areaName"
+            placeholder="请输入服务区名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container">
-          <el-tree ref="tree" :data="areaOptions" default-expand-all :expand-on-click-node="false" :filter-node-method="filterNode"
-            node-key="id" highlight-current @node-click="handleNodeClick" />
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <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">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
-          label-width="68px">
-          <el-form-item label="策略名称" prop="policyName">
-            <el-input v-model="queryParams.policyName" placeholder="请输入策略名称" clearable
-              @keyup.enter.native="handleQuery" />
-          </el-form-item>
-          <el-form-item label="对象类型" prop="alarmObjType">
-            <el-select v-model="queryParams.alarmObjType" placeholder="请选择告警对象类型" clearable>
-              <el-option v-for="dict in dict.type.obj_type" :key="dict.value" :label="dict.label" :value="dict.value" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="告警代码" prop="alarmCode">
-            <el-input v-model="queryParams.alarmCode" placeholder="请输入告警代码" clearable
-              @keyup.enter.native="handleQuery" />
-          </el-form-item>
-          <el-form-item label="告警类型" prop="alarmType">
-            <el-select v-model="queryParams.alarmType" placeholder="请选择告警类型" clearable>
-              <el-option v-for="dict in dict.type.alarm_type" :key="dict.value" :label="dict.label"
-                :value="dict.value" />
-            </el-select>
-          </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-form-item>
-        </el-form>
-
-        <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
-              v-hasPermi="['ems:alarm-strategy:add']">新增</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"
-              v-hasPermi="['ems:alarm-strategy:edit']">修改</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
-              v-hasPermi="['ems:alarm-strategy:remove']">删除</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
-              v-hasPermi="['ems:alarm-strategy:export']">导出</el-button>
-          </el-col>
-          <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-        </el-row>
-
-        <el-table v-loading="loading" :data="alarmList" @selection-change="handleSelectionChange">
-          <el-table-column type="selection" width="55" align="center" />
-          <el-table-column label="策略代码" align="left" prop="policyCode" />
-          <el-table-column label="策略名称" align="center" prop="policyName" />
-          <el-table-column label="告警对象类型" align="center" prop="alarmObjType">
-            <template slot-scope="scope">
-              <dict-tag :options="dict.type.obj_type" :value="scope.row.alarmObjType" />
-            </template>
-          </el-table-column>
-          <el-table-column label="告警对象指标" align="center" prop="alarmObjIndex" />
-          <el-table-column label="告警规则" align="center" prop="alarmRuleType">
-            <template slot-scope="scope">
-              <dict-tag :options="dict.type.alarm_thre_type" :value="scope.row.alarmRuleType" />
-            </template>
-          </el-table-column>
-          <el-table-column label="告警阈值" align="center" prop="alarmThresholdValue" />
-          <el-table-column label="告警代码" align="center" prop="alarmCode" />
-          <el-table-column label="告警描述" align="center" prop="alarmMsg" />
-          <el-table-column label="告警类型" align="center" prop="alarmType">
-            <template slot-scope="scope">
-              <dict-tag :options="dict.type.alarm_type" :value="scope.row.alarmType" />
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template slot-scope="scope">
-
-              <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
-                v-hasPermi="['ems:alarm-strategy:edit']">修改</el-button>
-              <el-button size="mini" type="text" icon="el-icon-delete" class="deleteBtn"
-                @click="handleDelete(scope.row)" v-hasPermi="['ems:alarm-strategy:remove']">
-                删除</el-button>
-
-            </template>
-          </el-table-column>
-        </el-table>
-
-        <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
-          @pagination="getList" />
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+            <el-form-item label="策略名称" prop="policyName">
+              <el-input v-model="queryParams.policyName" placeholder="请输入策略名称" clearable @keyup.enter.native="handleQuery" />
+            </el-form-item>
+            <el-form-item label="对象类型" prop="alarmObjType">
+              <el-select v-model="queryParams.alarmObjType" placeholder="请选择告警对象类型" clearable>
+                <el-option v-for="dict in dict.type.obj_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="告警代码" prop="alarmCode">
+              <el-input v-model="queryParams.alarmCode" placeholder="请输入告警代码" clearable @keyup.enter.native="handleQuery" />
+            </el-form-item>
+            <el-form-item label="告警类型" prop="alarmType">
+              <el-select v-model="queryParams.alarmType" placeholder="请选择告警类型" clearable>
+                <el-option v-for="dict in dict.type.alarm_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </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-form-item>
+          </el-form>
+
+          <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+              <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['ems:alarm-strategy:add']">新增</el-button>
+            </el-col>
+            <el-col :span="1.5">
+              <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate" v-hasPermi="['ems:alarm-strategy:edit']">修改</el-button>
+            </el-col>
+            <el-col :span="1.5">
+              <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['ems:alarm-strategy:remove']">删除</el-button>
+            </el-col>
+            <el-col :span="1.5">
+              <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['ems:alarm-strategy:export']">导出</el-button>
+            </el-col>
+            <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+          </el-row>
+
+          <el-table v-loading="loading" :data="alarmList" @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="策略代码" align="left" prop="policyCode" />
+            <el-table-column label="策略名称" align="center" prop="policyName" />
+            <el-table-column label="告警对象类型" align="center" prop="alarmObjType">
+              <template slot-scope="scope">
+                <dict-tag :options="dict.type.obj_type" :value="scope.row.alarmObjType" />
+              </template>
+            </el-table-column>
+            <el-table-column label="告警对象指标" align="center" prop="alarmObjIndex" />
+            <el-table-column label="告警规则" align="center" prop="alarmRuleType">
+              <template slot-scope="scope">
+                <dict-tag :options="dict.type.alarm_thre_type" :value="scope.row.alarmRuleType" />
+              </template>
+            </el-table-column>
+            <el-table-column label="告警阈值" align="center" prop="alarmThresholdValue" />
+            <el-table-column label="告警代码" align="center" prop="alarmCode" />
+            <el-table-column label="告警描述" align="center" prop="alarmMsg" />
+            <el-table-column label="告警类型" align="center" prop="alarmType">
+              <template slot-scope="scope">
+                <dict-tag :options="dict.type.alarm_type" :value="scope.row.alarmType" />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+              <template slot-scope="scope">
+                <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems:alarm-strategy:edit']">修改</el-button>
+                <el-button size="mini" type="text" icon="el-icon-delete" class="deleteBtn" @click="handleDelete(scope.row)" v-hasPermi="['ems:alarm-strategy:remove']">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+        </div>
       </el-col>
     </el-row>
 
@@ -116,8 +130,7 @@
         </el-form-item>
         <el-form-item label="告警对象类型" prop="alarmObjType">
           <el-select v-model="form.alarmObjType" placeholder="请选择告警对象类型">
-            <el-option v-for="dict in dict.type.obj_type" :key="dict.value" :label="dict.label"
-              :value="parseInt(dict.value)"></el-option>
+            <el-option v-for="dict in dict.type.obj_type" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
           </el-select>
         </el-form-item>
         <el-form-item label="告警对象指标" prop="alarmObjIndex">
@@ -125,8 +138,7 @@
         </el-form-item>
         <el-form-item label="告警规则" prop="alarmRuleType">
           <el-select v-model="form.alarmRuleType" placeholder="请选择告警规则">
-            <el-option v-for="dict in dict.type.alarm_thre_type" :key="dict.value" :label="dict.label"
-              :value="parseInt(dict.value)"></el-option>
+            <el-option v-for="dict in dict.type.alarm_thre_type" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
           </el-select>
         </el-form-item>
         <el-form-item label="告警阈值" prop="alarmThresholdValue">
@@ -140,8 +152,7 @@
         </el-form-item>
         <el-form-item label="告警类型" prop="alarmType">
           <el-select v-model="form.alarmType" placeholder="请选择告警类型">
-            <el-option v-for="dict in dict.type.alarm_type" :key="dict.value" :label="dict.label"
-              :value="parseInt(dict.value)"></el-option>
+            <el-option v-for="dict in dict.type.alarm_type" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
           </el-select>
         </el-form-item>
       </el-form>
@@ -154,8 +165,9 @@
 </template>
 
 <script>
-import {listAlarm, getAlarm, delAlarm, addAlarm, updateAlarm} from "@/api/alarm/alarm";
+import {listAlarm, getAlarm, delAlarm, addAlarm, updateAlarm} from "@/api/alarm/alarm"
 import {areaTreeSelect} from '@/api/basecfg/area'
+
 export default {
   name: "Alarm",
   dicts: ['obj_type', 'alarm_thre_type', 'alarm_type'],
@@ -179,6 +191,12 @@ export default {
       title: "",
       // 是否显示弹出层
       open: false,
+      // 默认展开的节点
+      defaultExpandedKeys: [],
+      defaultProps: {
+        children: 'children',
+        label: 'label'
+      },
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -187,7 +205,7 @@ export default {
         alarmObjType: null,
         alarmCode: null,
         alarmType: null,
-        areaCode:null
+        areaCode: null
       },
       // 表单参数
       form: {},
@@ -212,54 +230,82 @@ export default {
       areaName: undefined,
       areaOptions: [],
       sourceAreaOptions: [],
-    };
+    }
   },
   watch: {
-     // 根据名称筛选区域树
-     areaName (val) {
+    // 根据名称筛选区域树
+    areaName (val) {
       this.$refs.tree.filter(val)
     }
   },
- async created () {
+  async created () {
     await this.getAreaTreeByTag('0', 1)
-    this.getList();
+    this.getList()
   },
   methods: {
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     /** 查询能源设施告警策略列表 */
     getList () {
-      this.loading = true;
+      this.loading = true
       listAlarm(this.queryParams).then(response => {
-        this.alarmList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
+        this.alarmList = response.rows
+        this.total = response.total
+        this.loading = false
+      })
+    },
+
+    /** 查询区域树结构 */
+    async getAreaTreeByTag(areaCode, layer) {
+      await areaTreeSelect(areaCode, layer).then(response => {
+        this.areaOptions = [{
+          id: '-1',
+          label: '全部',
+          children: []
+        }].concat(response.data)
+        this.sourceAreaOptions = response.data
+        this.queryParams.areaCode = '-1'
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      })
     },
-     /** 查询区域树结构 */
-     async getAreaTreeByTag(areaCode, layer) {
-       await areaTreeSelect(areaCode, layer).then(response => {
-         this.areaOptions = [{
-           id: '-1',
-           label: '全部',
-           children: []
-         }].concat(response.data)
-         this.sourceAreaOptions = response.data
-         this.queryParams.areaCode = '-1'
-       })
-     },
+
     // 筛选节点
     filterNode (value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
     handleNodeClick (data, node) {
       this.queryParams.areaCode = data.id
       this.getList()
     },
+
     // 取消按钮
     cancel () {
-      this.open = false;
-      this.reset();
+      this.open = false
+      this.reset()
     },
+
     // 表单重置
     reset () {
       this.form = {
@@ -273,71 +319,79 @@ export default {
         alarmCode: null,
         alarmMsg: null,
         alarmType: null
-      };
-      this.resetForm("form");
+      }
+      this.resetForm("form")
     },
+
     /** 搜索按钮操作 */
     handleQuery () {
-      this.queryParams.pageNum = 1;
-      this.getList();
+      this.queryParams.pageNum = 1
+      this.getList()
     },
+
     /** 重置按钮操作 */
     resetQuery () {
-      this.resetForm("queryForm");
-      this.handleQuery();
+      this.resetForm("queryForm")
+      this.handleQuery()
     },
+
     // 多选框选中数据
     handleSelectionChange (selection) {
       this.ids = selection.map(item => item.id)
       this.single = selection.length !== 1
       this.multiple = !selection.length
     },
+
     /** 新增按钮操作 */
     handleAdd () {
-      this.reset();
-      this.open = true;
-      this.title = "添加能源设施告警策略";
+      this.reset()
+      this.open = true
+      this.title = "添加能源设施告警策略"
     },
+
     /** 修改按钮操作 */
     handleUpdate (row) {
-      this.reset();
+      this.reset()
       const id = row.id || this.ids
       getAlarm(id).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = "修改能源设施告警策略";
-      });
+        this.form = response.data
+        this.open = true
+        this.title = "修改能源设施告警策略"
+      })
     },
+
     /** 提交按钮 */
     submitForm () {
       this.$refs["form"].validate(valid => {
         if (valid) {
           if (this.form.id != null) {
             updateAlarm(this.form).then(response => {
-              this.$modal.msgSuccess("修改成功");
-              this.open = false;
-              this.getList();
-            });
+              this.$modal.msgSuccess("修改成功")
+              this.open = false
+              this.getList()
+            })
           } else {
             addAlarm(this.form).then(response => {
-              this.$modal.msgSuccess("新增成功");
-              this.open = false;
-              this.getList();
-            });
+              this.$modal.msgSuccess("新增成功")
+              this.open = false
+              this.getList()
+            })
           }
         }
-      });
+      })
     },
+
     /** 删除按钮操作 */
     handleDelete (row) {
-      const ids = row.id || this.ids;
+      const ids = row.id || this.ids
       this.$modal.confirm('是否确认删除能源设施告警策略编号为"' + ids + '"的数据项?').then(function () {
-        return delAlarm(ids);
+        return delAlarm(ids)
       }).then(() => {
-        this.getList();
-        this.$modal.msgSuccess("删除成功");
-      }).catch(() => { });
+        this.getList()
+        this.$modal.msgSuccess("删除成功")
+      }).catch(() => { })
     },
+
     /** 导出按钮操作 */
     handleExport () {
       this.download('ems/alarm/export', {
@@ -345,5 +399,106 @@ export default {
       }, `alarm_${new Date().getTime()}.xlsx`)
     }
   }
-};
+}
 </script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+  }
+}
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+  }
+}
+</style>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 465 - 242
ems-ui-cloud/src/views/analysis/power/consume.vue


+ 410 - 100
ems-ui-cloud/src/views/analysis/power/prod.vue

@@ -1,105 +1,194 @@
 <template>
   <div class="app-container">
-    <el-row :gutter="10">
-      <el-col :span="4" :xs="24">
+    <el-row :gutter="20">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-            style="margin-bottom: 20px" />
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-            :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-            @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;" />
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-label">
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.facsCategory === 'E'"
+                size="mini"
+                effect="plain"
+                class="tree-tag"
+              >
+                光伏
+              </el-tag>
+            </span>
+          </el-tree>
         </div>
       </el-col>
-      <el-col :span="20" :xs="24">
-       <el-card>
-        <div class="container-block">
-          <div style="display: flex;justify-content: space-between;">
-            <SubTitle :title="`发电量-日【${selectedLabel}】`" />
-            <el-button-group>
-              <el-button v-for="item in btnGroup" :key="item.name" size="mini"
-                :type="item.name === activeBtn ? 'primary' : ''" :icon="item.icon" @click="btnChange(item)" />
-            </el-button-group>
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <!-- 标题区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <SubTitle :title="`发电量-日【${selectedLabel}】`" />
+            </div>
+            <div class="header-right">
+              <el-button-group class="view-toggle">
+                <el-button
+                  v-for="item in btnGroup"
+                  :key="item.name"
+                  size="small"
+                  :type="item.name === activeBtn ? 'primary' : ''"
+                  :icon="item.icon"
+                  @click="btnChange(item)"
+                >
+                  {{ item.label }}
+                </el-button>
+              </el-button-group>
+            </div>
+          </div>
+
+          <!-- 控制区域 -->
+          <div class="control-container">
+            <el-date-picker
+              v-model="dateRange"
+              type="daterange"
+              @change="getList"
+              :picker-options="pickerOptions"
+              value-format="yyyy-MM-dd"
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              align="right"
+              :clearable="false"
+              size="small"
+            />
           </div>
-          <div class="ctl-container">
-            <el-date-picker v-model="dateRange" type="daterange" @change="getList" :picker-options="pickerOptions"
-              value-format="yyyy-MM-dd" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
-              align="right" :clearable="false">
-            </el-date-picker>
+
+          <!-- 数据展示区域 -->
+          <div class="data-container">
+            <!-- 表格视图 -->
+            <el-table
+              v-if="activeBtn === 'table'"
+              v-loading="loading"
+              :data="elecStoreHList"
+              max-height="600px"
+              class="data-table"
+            >
+              <el-table-column label="日期" align="center" prop="date" width="180" />
+              <el-table-column label="发电量(kW·h)" align="center" prop="genElecQuantity">
+                <template slot-scope="scope">
+                  <span class="data-value">{{ scope.row.genElecQuantity || 0 }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="上网电量(kW·h)" align="center" prop="upElecQuantity">
+                <template slot-scope="scope">
+                  <span class="data-value success">{{ scope.row.upElecQuantity || 0 }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="自用电量(kW·h)" align="center" prop="useElecQuantity">
+                <template slot-scope="scope">
+                  <span class="data-value info">{{ scope.row.useElecQuantity || 0 }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="上网收益(元)" align="center" prop="upElecEarn">
+                <template slot-scope="scope">
+                  <span class="data-value warning">¥ {{ scope.row.upElecEarn || 0 }}</span>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <!-- 图表视图 -->
+            <BaseChart
+              v-else
+              width="100%"
+              height="600px"
+              :option="elecOptions"
+            />
           </div>
-          <el-table v-if="activeBtn == 'table'" v-loading="loading" :data="elecStoreHList" max-height="400px">
-            <el-table-column label="日期" align="center" prop="date" width="180">
-            </el-table-column>
-            <el-table-column label="发电量(kW·h)" align="center" prop="genElecQuantity" />
-            <el-table-column label="上网电量(kW·h)" align="center" prop="upElecQuantity" />
-            <el-table-column label="自用电量(kW·h)" align="center" prop="useElecQuantity" />
-            <el-table-column label="上网收益(元)" align="center" prop="upElecEarn" />
-          </el-table>
-          <BaseChart v-else width="100%" height="400px" :option="elecOptions" />
         </div>
-       </el-card>
       </el-col>
     </el-row>
   </div>
 </template>
 
-
 <script>
-import {listPvSupplyD} from '@/api/mgr/pgSupplyH.js'
+import { listPvSupplyD } from '@/api/mgr/pgSupplyH.js'
 import { areaTreeByFacsCategory } from '@/api/basecfg/area'
 import BaseChart from '@/components/BaseChart'
 import SubTitle from '@/components/SubTitle'
-import Treeselect from "@riophae/vue-treeselect";
-import "@riophae/vue-treeselect/dist/vue-treeselect.css";
-import {dateFormat, getDayAgoDate} from '@/utils'
+import { dateFormat, getDayAgoDate } from '@/utils'
+
 export default {
   name: 'ElecStoreD',
   components: {
-    Treeselect,
     BaseChart,
     SubTitle
   },
-  data () {
+  data() {
     const lastWeek = getDayAgoDate(7)
     const nowDay = new Date()
+
     return {
       // 遮罩层
       loading: true,
       facsCategory: 'E',
       facsSubCategory: '',
       elecStoreHList: [],
-      areaName: undefined,
+      areaName: '',
       defaultProps: {
-        children: "children",
-        label: "label"
+        children: 'children',
+        label: 'label'
       },
-      selectedLabel: '',
+      defaultExpandedKeys: [],
+      selectedLabel: '全部',
       // 查询参数
       queryParams: {
-        areaCode: ''
+        areaCode: '-1'
       },
       pickerOptions: {
         shortcuts: [
           {
             text: '最近一周',
-            onClick (picker) {
+            onClick(picker) {
               const end = new Date()
               const start = new Date()
               start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
               picker.$emit('pick', [start, end])
             }
-          }, {
+          },
+          {
             text: '最近一个月',
-            onClick (picker) {
+            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) {
+            onClick(picker) {
               const end = new Date()
               const start = new Date()
               start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
@@ -111,27 +200,30 @@ export default {
       // 表单参数
       areaOptions: [],
       dateRange: [dateFormat(lastWeek, 'yyyy-MM-dd'), dateFormat(nowDay, 'yyyy-MM-dd')],
-      activeBtn: 'chart', //--chart图表 --table表格
+      activeBtn: 'chart',
       btnGroup: [
         {
           name: 'chart',
-          icon: 'el-icon-s-data'
+          icon: 'el-icon-s-data',
+          label: '图表'
         },
         {
           name: 'table',
-          icon: 'el-icon-menu'
-        }]
+          icon: 'el-icon-menu',
+          label: '表格'
+        }
+      ]
     }
   },
   computed: {
-    elecOptions () {
+    elecOptions() {
       return {
         tooltip: {
           trigger: 'axis',
           formatter: (params) => {
             let relVal = params[0].name
             for (let i = 0, l = params.length; i < l; i++) {
-              const unit = params[i].seriesName==='上网收益'?'元':'kW·h'
+              const unit = params[i].seriesName === '上网收益' ? '元' : 'kW·h'
               relVal =
                 relVal +
                 '<br/>' +
@@ -144,6 +236,13 @@ export default {
           }
         },
         legend: {
+          data: ['自用电量', '上网电量', '上网收益']
+        },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '15%',
+          containLabel: true
         },
         xAxis: {
           type: 'category',
@@ -164,20 +263,20 @@ export default {
         ],
         dataZoom: [
           {
-            type: "slider",
+            type: 'slider',
             start: 0,
             end: 40,
             height: 10,
             bottom: '5%',
             showDetail: false,
             showDataShadow: false,
-            borderColor: "transparent"
+            borderColor: 'transparent'
           },
           {
-            type: "inside",
+            type: 'inside',
             start: 0,
-            end: 40,
-          },
+            end: 40
+          }
         ],
         series: [
           {
@@ -194,10 +293,9 @@ export default {
               normal: {
                 color: '#6395FA',
                 label: {
-                  show: true, // 开启显示
-                  position: 'top', // 在上方显示
+                  show: true,
+                  position: 'top',
                   textStyle: {
-                    // 数值样式
                     color: '#000',
                     fontSize: 14,
                     fontWeight: 600
@@ -220,10 +318,9 @@ export default {
               normal: {
                 color: '#8CDF6C',
                 label: {
-                  show: true, // 开启显示
-                  position: 'top', // 在上方显示
+                  show: true,
+                  position: 'top',
                   textStyle: {
-                    // 数值样式
                     color: '#000',
                     fontSize: 14,
                     fontWeight: 600
@@ -237,89 +334,302 @@ export default {
             type: 'line',
             yAxisIndex: 1,
             data: this.elecStoreHList.map(item => item.upElecEarn),
-            smooth: false
+            smooth: false,
+            itemStyle: {
+              color: '#FF9F7F'
+            }
           }
         ]
       }
-    },
-
-  },
-  watch: {
-    // 根据名称筛选区域树
-    areaName (val) {
-      this.$refs.tree.filter(val)
     }
   },
-  async created () {
+  async created() {
     await this.getAreaList()
     this.getList()
   },
   methods: {
-    btnChange (item) {
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.facsCategory === 'E') {
+        return 'el-icon-sunny'
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 按钮切换
+    btnChange(item) {
       this.activeBtn = item.name
     },
+
     // 查询区域列表
-    async getAreaList () {
-      await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false).then(response => {
+    async getAreaList() {
+      try {
+        const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
         this.areaOptions = [{
           id: '-1',
           label: '全部',
-          children: []
-        }].concat(response.data)
-        this.selectedLabel = '全部'
-        this.queryParams.areaCode = '-1'
-      })
+          children: response.data || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      } catch (error) {
+        console.error('加载区域树失败', error)
+        this.$message.error('加载区域树失败')
+      }
     },
-    /** 查询储能计量-小时列表 */
-    getList () {
+
+    // 查询储能计量列表
+    getList() {
       this.loading = true
-      const {areaCode} = this.queryParams
+      const { areaCode } = this.queryParams
       let startRecTime = ''
       let endRecTime = ''
+
       if (this.dateRange && this.dateRange.length) {
         const [start, end] = this.dateRange
         startRecTime = start
         endRecTime = end
       }
+
       listPvSupplyD({
         areaCode,
         startRecTime,
         endRecTime
-      }).then(({code, data}) => {
+      }).then(({ code, data }) => {
         this.loading = false
         if (code === 200) {
-          this.elecStoreHList = data
+          this.elecStoreHList = data || []
         }
-
+      }).catch(() => {
+        this.loading = false
+        this.$message.error('数据加载失败')
       })
     },
+
     // 筛选节点
-    filterNode (value, data) {
+    filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     // 节点单击事件
-    handleNodeClick (data) {
+    handleNodeClick(data) {
       this.queryParams.areaCode = data.id
       this.selectedLabel = data.label
       this.getList()
-    },
+    }
   }
 }
 </script>
 
 <style lang="scss" scoped>
-@import './index.scss';
-
 .app-container {
-  ::v-deep .el-tabs__content {
-    overflow: initial;
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+            }
+          }
+
+          .tree-tag {
+            margin-right: 8px;
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 1px solid #ebeef5;
+
+      .header-left {
+        ::v-deep .sub-title {
+          font-size: 18px;
+          font-weight: 600;
+          color: #303133;
+        }
+      }
+
+      .view-toggle {
+        ::v-deep .el-button {
+          padding: 8px 15px;
+
+          &.el-button--primary {
+            background: linear-gradient(90deg, #409eff 0%, #53a8ff 100%);
+            border-color: #409eff;
+          }
+        }
+      }
+    }
+
+    .control-container {
+      display: flex;
+      justify-content: flex-end;
+      margin-bottom: 20px;
+
+      ::v-deep .el-date-editor {
+        width: 350px;
+
+        .el-range-separator {
+          color: #606266;
+        }
+      }
+    }
+
+    .data-container {
+      .data-table {
+        ::v-deep .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+          }
+        }
+
+        ::v-deep .el-table__body {
+          .data-value {
+            font-weight: 500;
+
+            &.success {
+              color: #67c23a;
+            }
+
+            &.info {
+              color: #409eff;
+            }
+
+            &.warning {
+              color: #e6a23c;
+            }
+          }
+        }
+      }
+    }
   }
 }
 
-.ctl-container {
-  display: flex;
-  justify-content: flex-end;
-  margin: 10px 0;
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+
+        .header-right {
+          margin-top: 10px;
+        }
+      }
+
+      .control-container {
+        justify-content: center;
+
+        ::v-deep .el-date-editor {
+          width: 100%;
+        }
+      }
+    }
+  }
 }
 </style>

+ 767 - 308
ems-ui-cloud/src/views/analysis/power/save.vue

@@ -1,350 +1,809 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="园区代码" prop="areaCode">
-        <el-input
-            v-model="queryParams.areaCode"
-            placeholder="请输入园区代码"
+    <el-row :gutter="20">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
+        <div class="head-container">
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
             clearable
-            @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="设施代码" prop="facsCode">
-        <el-input
-            v-model="queryParams.facsCode"
-            placeholder="请输入设施代码"
-            clearable
-            @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="日期" prop="date">
-        <el-date-picker clearable
-                        v-model="queryParams.date"
-                        type="date"
-                        value-format="yyyy-MM-dd"
-                        placeholder="请选择日期 yyyy-MM-dd">
-        </el-date-picker>
-      </el-form-item>
-      <el-form-item label="充电电量 " prop="chargeElecQuantity">
-        <el-input
-            v-model="queryParams.chargeElecQuantity"
-            placeholder="请输入充电电量 "
-            clearable
-            @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="放电电量" prop="dischargeElecQuantity">
-        <el-input
-            v-model="queryParams.dischargeElecQuantity"
-            placeholder="请输入放电电量"
-            clearable
-            @keyup.enter.native="handleQuery"
-        />
-      </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-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
-            type="primary"
-            plain
-            icon="el-icon-plus"
-            size="mini"
-            @click="handleAdd"
-            v-hasPermi="['ems:elecStoreH:add']"
-        >新增
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-            type="success"
-            plain
-            icon="el-icon-edit"
-            size="mini"
-            :disabled="single"
-            @click="handleUpdate"
-            v-hasPermi="['ems:elecStoreH:edit']"
-        >修改
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-            type="danger"
-            plain
-            icon="el-icon-delete"
-            size="mini"
-            :disabled="multiple"
-            @click="handleDelete"
-            v-hasPermi="['ems:elecStoreH:remove']"
-        >删除
-        </el-button>
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
+        </div>
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-label">
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.facsCategory === 'Z'"
+                size="mini"
+                effect="plain"
+                class="tree-tag eco"
+              >
+                节能
+              </el-tag>
+            </span>
+          </el-tree>
+        </div>
       </el-col>
-      <el-col :span="1.5">
-        <el-button
-            type="warning"
-            plain
-            icon="el-icon-download"
-            size="mini"
-            @click="handleExport"
-            v-hasPermi="['ems:elecStoreH:export']"
-        >导出
-        </el-button>
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <!-- 统计卡片区域 -->
+        <div class="statistic-cards">
+          <el-row :gutter="15">
+            <el-col :span="12" :xs="24">
+              <div class="stat-card electricity">
+                <div class="card-header">
+                  <i class="el-icon-s-data card-icon"></i>
+                  <span class="card-title">上月平均日用电</span>
+                </div>
+                <div class="card-body">
+                  <div class="stat-item">
+                    <div class="stat-value">
+                      <span class="number">{{ lastMonthAvgElec || 0 }}</span>
+                      <span class="unit">kW·h</span>
+                    </div>
+                    <div class="stat-label">用电量</div>
+                  </div>
+                  <div class="stat-divider"></div>
+                  <div class="stat-item">
+                    <div class="stat-value cost">
+                      <span class="currency">¥</span>
+                      <span class="number">{{ lastMonthAvgElecCost || 0 }}</span>
+                    </div>
+                    <div class="stat-label">用电花费</div>
+                  </div>
+                </div>
+              </div>
+            </el-col>
+            <el-col :span="12" :xs="24">
+              <div class="stat-card water">
+                <div class="card-header">
+                  <i class="el-icon-s-opportunity card-icon"></i>
+                  <span class="card-title">上月平均日用水</span>
+                </div>
+                <div class="card-body">
+                  <div class="stat-item">
+                    <div class="stat-value">
+                      <span class="number">{{ lastMonthAvgWater || 0 }}</span>
+                      <span class="unit">t</span>
+                    </div>
+                    <div class="stat-label">用水量</div>
+                  </div>
+                  <div class="stat-divider"></div>
+                  <div class="stat-item">
+                    <div class="stat-value cost">
+                      <span class="currency">¥</span>
+                      <span class="number">{{ lastMonthAvgWaterCost || 0 }}</span>
+                    </div>
+                    <div class="stat-label">用水花费</div>
+                  </div>
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 数据表格区域 -->
+        <div class="content-wrapper">
+          <!-- 标题和搜索区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <h3 class="section-title">节能分析明细</h3>
+            </div>
+            <div class="header-right">
+              <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
+                <el-form-item label="日期范围">
+                  <el-date-picker
+                    v-model="dateRange"
+                    type="daterange"
+                    value-format="yyyy-MM-dd"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    :picker-options="pickerOptions"
+                    @change="handleDateChange"
+                  />
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
+                  <el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
+                </el-form-item>
+              </el-form>
+            </div>
+          </div>
+
+          <!-- 数据表格 -->
+          <div class="table-container">
+            <el-table
+              v-loading="loading"
+              :data="EmsEcoDList"
+              class="data-table"
+              :height="tableHeight"
+            >
+              <el-table-column label="位置" align="center" prop="areaName" min-width="180">
+                <template slot-scope="scope">
+                  <span class="area-name">{{ scope.row.areaName }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="日期" align="center" prop="date" width="110">
+                <template slot-scope="scope">
+                  <span class="date-text">{{ parseTime(scope.row.date, '{y}-{m}-{d}') }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="电能分析" align="center">
+                <el-table-column label="节电量(kW·h)" align="center" min-width="150">
+                  <template slot-scope="scope">
+                    <div :class="getValueClass(scope.row.elecEcoQuantity)">
+                      <i :class="getValueIcon(scope.row.elecEcoQuantity)"></i>
+                      <span class="value">{{ Math.abs(scope.row.elecEcoQuantity || 0).toFixed(2) }}</span>
+                    </div>
+                  </template>
+                </el-table-column>
+                <el-table-column label="节电金额(元)" align="center" min-width="150">
+                  <template slot-scope="scope">
+                    <div :class="getValueClass(scope.row.elecEcoCost)">
+                      <span class="currency">¥</span>
+                      <span class="value">{{ Math.abs(scope.row.elecEcoCost || 0).toFixed(2) }}</span>
+                    </div>
+                  </template>
+                </el-table-column>
+              </el-table-column>
+              <el-table-column label="水能分析" align="center">
+                <el-table-column label="节水量(t)" align="center" min-width="140">
+                  <template slot-scope="scope">
+                    <div :class="getValueClass(scope.row.waterEcoQuantity)">
+                      <i :class="getValueIcon(scope.row.waterEcoQuantity)"></i>
+                      <span class="value">{{ Math.abs(scope.row.waterEcoQuantity || 0).toFixed(2) }}</span>
+                    </div>
+                  </template>
+                </el-table-column>
+                <el-table-column label="节水金额(元)" align="center" min-width="150">
+                  <template slot-scope="scope">
+                    <div :class="getValueClass(scope.row.waterEcoCost)">
+                      <span class="currency">¥</span>
+                      <span class="value">{{ Math.abs(scope.row.waterEcoCost || 0).toFixed(2) }}</span>
+                    </div>
+                  </template>
+                </el-table-column>
+              </el-table-column>
+              <el-table-column label="综合评价" align="center" min-width="100">
+                <template slot-scope="scope">
+                  <el-tag :type="getOverallType(scope.row)" effect="plain" size="small">
+                    {{ getOverallText(scope.row) }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <!-- 分页 -->
+            <pagination
+              v-show="total > 0"
+              :total="total"
+              :page.sync="queryParams.pageNum"
+              :limit.sync="queryParams.pageSize"
+              @pagination="getList"
+              class="pagination-container"
+            />
+          </div>
+        </div>
       </el-col>
-      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
-
-    <el-table v-loading="loading" :data="elecStoreHList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="序号" align="center" prop="id" />
-      <el-table-column label="园区代码" align="center" prop="areaCode" />
-      <el-table-column label="园区名称" align="center" prop="areaName" />
-      <el-table-column label="设施代码" align="center" prop="facsCode" />
-      <el-table-column label="设施名称" align="center" prop="facsName" />
-      <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="充电电量(kW·h)" align="center" prop="chargeElecQuantity" />
-      <el-table-column label="放电电量(kW·h)" align="center" prop="dischargeElecQuantity" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-        <template slot-scope="scope">
-          <el-button
-              size="mini"
-              type="text"
-              icon="el-icon-edit"
-              @click="handleUpdate(scope.row)"
-              v-hasPermi="['ems:elecStoreH:edit']"
-          >修改
-          </el-button>
-          <el-button size="mini" type="text" icon="el-icon-delete" class="deleteBtn" @click="handleDelete(scope.row)" v-hasPermi="['ems:elecStoreH:remove']">
-            删除</el-button>
-
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <pagination
-        v-show="total>0"
-        :total="total"
-        :page.sync="queryParams.pageNum"
-        :limit.sync="queryParams.pageSize"
-        @pagination="getList"
-    />
-
-    <!-- 添加或修改储能计量-小时对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="园区代码" prop="areaCode">
-          <el-input v-model="form.areaCode" placeholder="请输入园区代码" />
-        </el-form-item>
-        <el-form-item label="设施代码" prop="facsCode">
-          <el-input v-model="form.facsCode" placeholder="请输入设施代码" />
-        </el-form-item>
-        <el-form-item label="日期" prop="date">
-          <el-date-picker clearable
-                          v-model="form.date"
-                          type="date"
-                          value-format="yyyy-MM-dd"
-                          placeholder="请选择日期">
-          </el-date-picker>
-        </el-form-item>
-        <el-form-item label="时间" prop="time">
-          <el-date-picker clearable
-                          v-model="form.time"
-                          type="date"
-                          value-format="yyyy-MM-dd"
-                          placeholder="请选择时间">
-          </el-date-picker>
-        </el-form-item>
-        <el-form-item label="充电电量 " prop="chargeElecQuantity">
-          <el-input v-model="form.chargeElecQuantity" placeholder="请输入充电电量 " />
-        </el-form-item>
-        <el-form-item label="放电电量" prop="dischargeElecQuantity">
-          <el-input v-model="form.dischargeElecQuantity" placeholder="请输入放电电量" />
-        </el-form-item>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="cancel">取 消</el-button>
-      </div>
-    </el-dialog>
   </div>
 </template>
 
 <script>
-import { addElecStoreH, delElecStoreH, getElecStoreH, listElecStoreH, updateElecStoreH } from '@/api/mgr/elecStoreH';
+import { listEmsEcoD } from "@/api/ems/EmsEcoD"
+import { getElecDayAvg } from '@/api/device/elecMeterH'
+import { getWaterDayAvg } from '@/api/device/waterMeterH'
+import { areaTreeByFacsCategory } from '@/api/basecfg/area'
 
 export default {
-  name: 'ElecStoreH',
+  name: "EmsEcoD",
   data() {
     return {
       // 遮罩层
       loading: true,
-      // 选中数组
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
       // 显示搜索条件
       showSearch: true,
       // 总条数
       total: 0,
-      // 储能计量-小时表格数据
-      elecStoreHList: [],
-      // 弹出层标题
-      title: '',
-      // 是否显示弹出层
-      open: false,
+      // 节能计量日表格数据
+      EmsEcoDList: [],
+      // 表格高度
+      tableHeight: 500,
+      // 树形结构相关
+      areaOptions: [],
+      areaName: '',
+      facsCategory: 'Z',
+      facsSubCategory: '',
+      defaultProps: {
+        children: "children",
+        label: "label"
+      },
+      defaultExpandedKeys: [],
+      selectedLabel: '全部',
+      // 统计数据
+      lastMonthAvgElec: 0,
+      lastMonthAvgElecCost: 0,
+      lastMonthAvgWater: 0,
+      lastMonthAvgWaterCost: 0,
+      // 日期范围
+      dateRange: [],
+      pickerOptions: {
+        shortcuts: [{
+          text: '最近一周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        }, {
+          text: '最近一个月',
+          onClick(picker) {
+            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])
+          }
+        }]
+      },
       // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        areaCode: null,
-        facsCode: null,
-        date: null,
-        chargeElecQuantity: null,
-        dischargeElecQuantity: null,
+        areaCode: '-1',
+        startRecTime: null,
+        endRecTime: null
       },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        areaCode: [
-          {
-            required: true,
-            message: '园区代码不能为空',
-            trigger: 'blur',
-          },
-        ],
-        facsCode: [
-          {
-            required: true,
-            message: '设施代码不能为空',
-            trigger: 'blur',
-          },
-        ],
-        date: [
-          {
-            required: true,
-            message: '日期不能为空',
-            trigger: 'blur',
-          },
-        ],
-        time: [
-          {
-            required: true,
-            message: '时间不能为空',
-            trigger: 'blur',
-          },
-        ],
-      },
-    };
+      queryDayAvgParams: {
+        areaCode: '-1',
+        deviceCode: null,
+        startRecTime: null,
+        endRecTime: null
+      }
+    }
+  },
+  async created() {
+    await this.getAreaList()
+    this.initDateRange()
+    this.initDayAvg()
+    this.getList()
+    this.calculateTableHeight()
+    window.addEventListener('resize', this.calculateTableHeight)
   },
-  created() {
-    this.getList();
+  destroyed() {
+    window.removeEventListener('resize', this.calculateTableHeight)
   },
   methods: {
-    /** 查询储能计量-小时列表 */
-    getList() {
-      this.loading = true;
-      listElecStoreH(this.queryParams).then(response => {
-        this.elecStoreHList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
+    // 计算表格高度
+    calculateTableHeight() {
+      this.$nextTick(() => {
+        const windowHeight = window.innerHeight
+        const tableOffset = 420
+        this.tableHeight = windowHeight - tableOffset
+      })
+    },
+
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.facsCategory === 'Z') {
+        return 'el-icon-s-help'
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 初始化日期范围
+    initDateRange() {
+      const today = new Date()
+      const yesterday = new Date(today)
+      yesterday.setDate(today.getDate() - 1)
+      const sevenDaysAgo = new Date(today)
+      sevenDaysAgo.setDate(today.getDate() - 7)
+
+      this.dateRange = [this.formatDate(sevenDaysAgo), this.formatDate(yesterday)]
+      this.queryParams.startRecTime = this.dateRange[0]
+      this.queryParams.endRecTime = this.dateRange[1]
     },
-    // 取消按钮
-    cancel() {
-      this.open = false;
-      this.reset();
+
+    // 初始化月平均数据
+    async initDayAvg() {
+      const today = new Date()
+      const lastMonth = new Date(today)
+      lastMonth.setMonth(today.getMonth() - 1)
+
+      const firstDayOfLastMonth = new Date(lastMonth.getFullYear(), lastMonth.getMonth(), 1)
+      const lastDayOfLastMonth = new Date(lastMonth.getFullYear(), lastMonth.getMonth() + 1, 0)
+
+      this.queryDayAvgParams.startRecTime = this.formatDate(firstDayOfLastMonth)
+      this.queryDayAvgParams.endRecTime = this.formatDate(lastDayOfLastMonth)
+
+      try {
+        const [elecRes, waterRes] = await Promise.all([
+          getElecDayAvg(this.queryDayAvgParams),
+          getWaterDayAvg(this.queryDayAvgParams)
+        ])
+
+        if (elecRes.data) {
+          this.lastMonthAvgElec = parseFloat((elecRes.data.quantity || 0).toFixed(2))
+          this.lastMonthAvgElecCost = parseFloat((elecRes.data.useCost || 0).toFixed(2))
+        }
+
+        if (waterRes.data) {
+          this.lastMonthAvgWater = parseFloat((waterRes.data.quantity || 0).toFixed(2))
+          this.lastMonthAvgWaterCost = parseFloat((waterRes.data.useCost || 0).toFixed(2))
+        }
+      } catch (error) {
+        console.error('加载平均数据失败', error)
+      }
     },
-    // 表单重置
-    reset() {
-      this.form = {
-        id: null,
-        areaCode: null,
-        facsCode: null,
-        date: null,
-        time: null,
-        chargeElecQuantity: null,
-        dischargeElecQuantity: null,
-      };
-      this.resetForm('form');
+
+    // 格式化日期
+    formatDate(date) {
+      const year = date.getFullYear()
+      const month = String(date.getMonth() + 1).padStart(2, '0')
+      const day = String(date.getDate()).padStart(2, '0')
+      return `${year}-${month}-${day}`
     },
-    /** 搜索按钮操作 */
+
+    // 查询区域列表
+    async getAreaList() {
+      try {
+        const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
+        this.areaOptions = [{
+          id: '-1',
+          label: '全部',
+          children: response.data || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      } catch (error) {
+        console.error('加载区域树失败', error)
+        this.$message.error('加载区域树失败')
+      }
+    },
+
+    // 查询节能计量日列表
+    getList() {
+      this.loading = true
+      listEmsEcoD(this.queryParams).then(response => {
+        this.EmsEcoDList = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      }).catch(() => {
+        this.loading = false
+        this.$message.error('数据加载失败')
+      })
+    },
+
+    // 处理日期范围变化
+    handleDateChange(val) {
+      if (val && val.length === 2) {
+        this.queryParams.startRecTime = val[0]
+        this.queryParams.endRecTime = val[1]
+      }
+    },
+
+    // 搜索按钮操作
     handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
+      this.queryParams.pageNum = 1
+      this.getList()
     },
-    /** 重置按钮操作 */
+
+    // 重置按钮操作
     resetQuery() {
-      this.resetForm('queryForm');
-      this.handleQuery();
+      this.initDateRange()
+      this.handleQuery()
     },
-    // 多选框选中数据
-    handleSelectionChange(selection) {
-      this.ids = selection.map(item => item.id);
-      this.single = selection.length !== 1;
-      this.multiple = !selection.length;
+
+    // 筛选节点
+    filterNode(value, data) {
+      if (!value) return true
+      return data.label.indexOf(value) !== -1
     },
-    /** 新增按钮操作 */
-    handleAdd() {
-      this.reset();
-      this.open = true;
-      this.title = '添加储能计量-小时';
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
     },
-    /** 修改按钮操作 */
-    handleUpdate(row) {
-      this.reset();
-      const id = row.id || this.ids;
-      getElecStoreH(id).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = '修改储能计量-小时';
-      });
+
+    // 节点单击事件
+    handleNodeClick(data) {
+      this.queryParams.areaCode = data.id
+      this.queryDayAvgParams.areaCode = data.id
+      this.selectedLabel = data.label
+      this.initDayAvg()
+      this.getList()
     },
-    /** 提交按钮 */
-    submitForm() {
-      this.$refs['form'].validate(valid => {
-        if (valid) {
-          if (this.form.id != null) {
-            updateElecStoreH(this.form).then(response => {
-              this.$modal.msgSuccess('修改成功');
-              this.open = false;
-              this.getList();
-            });
-          } else {
-            addElecStoreH(this.form).then(response => {
-              this.$modal.msgSuccess('新增成功');
-              this.open = false;
-              this.getList();
-            });
-          }
-        }
-      });
+
+    // 获取值的样式类
+    getValueClass(value) {
+      const numValue = Number(value)
+      if (numValue > 0) {
+        return 'eco-value saving'
+      } else if (numValue < 0) {
+        return 'eco-value increase'
+      }
+      return 'eco-value neutral'
     },
-    /** 删除按钮操作 */
-    handleDelete(row) {
-      const ids = row.id || this.ids;
-      this.$modal.confirm('是否确认删除储能计量-小时编号为"' + ids + '"的数据项?').then(function () {
-        return delElecStoreH(ids);
-      }).then(() => {
-        this.getList();
-        this.$modal.msgSuccess('删除成功');
-      }).catch(() => {});
+
+    // 获取值的图标
+    getValueIcon(value) {
+      const numValue = Number(value)
+      if (numValue > 0) {
+        return 'el-icon-bottom'
+      } else if (numValue < 0) {
+        return 'el-icon-top'
+      }
+      return ''
     },
-    /** 导出按钮操作 */
-    handleExport() {
-      this.download('ems/elecStoreH/export', {
-        ...this.queryParams,
-      }, `elecStoreH_${new Date().getTime()}.xlsx`);
+
+    // 获取综合评价类型
+    getOverallType(row) {
+      const totalSaving = (row.elecEcoCost || 0) + (row.waterEcoCost || 0)
+      if (totalSaving > 0) return 'success'
+      if (totalSaving < 0) return 'danger'
+      return 'info'
     },
-  },
-};
+
+    // 获取综合评价文本
+    getOverallText(row) {
+      const totalSaving = (row.elecEcoCost || 0) + (row.waterEcoCost || 0)
+      if (totalSaving > 0) return '节能'
+      if (totalSaving < 0) return '超支'
+      return '持平'
+    }
+  }
+}
 </script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  // 左侧树形结构
+  .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;
+      }
+
+      ::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;
+            }
+          }
+
+          .tree-tag.eco {
+            background: linear-gradient(135deg, #52c41a 0%, #95de64 100%);
+            color: #fff;
+            border: none;
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
+
+  // 统计卡片
+  .statistic-cards {
+    margin-bottom: 20px;
+
+    .stat-card {
+      background: #fff;
+      border-radius: 8px;
+      padding: 20px;
+      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+      transition: all 0.3s;
+
+      &:hover {
+        transform: translateY(-3px);
+        box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.12);
+      }
+
+      &.electricity {
+        .card-icon {
+          color: #409eff;
+          background: linear-gradient(135deg, #e6f3ff 0%, #d4e8ff 100%);
+        }
+      }
+
+      &.water {
+        .card-icon {
+          color: #00c0ef;
+          background: linear-gradient(135deg, #e0f7ff 0%, #c3ecfc 100%);
+        }
+      }
+
+      .card-header {
+        display: flex;
+        align-items: center;
+        margin-bottom: 20px;
+
+        .card-icon {
+          width: 40px;
+          height: 40px;
+          border-radius: 8px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          font-size: 20px;
+          margin-right: 12px;
+        }
+
+        .card-title {
+          font-size: 16px;
+          color: #303133;
+          font-weight: 500;
+        }
+      }
+
+      .card-body {
+        display: flex;
+        justify-content: space-around;
+        align-items: center;
+
+        .stat-item {
+          text-align: center;
+          flex: 1;
+
+          .stat-value {
+            margin-bottom: 8px;
+            display: flex;
+            align-items: baseline;
+            justify-content: center;
+            gap: 4px;
+
+            .number {
+              font-size: 24px;
+              font-weight: 600;
+              color: #303133;
+            }
+
+            .unit {
+              font-size: 14px;
+              color: #909399;
+            }
+
+            .currency {
+              font-size: 16px;
+              color: #f56c6c;
+              font-weight: 500;
+            }
+
+            &.cost .number {
+              color: #f56c6c;
+            }
+          }
+
+          .stat-label {
+            font-size: 14px;
+            color: #909399;
+          }
+        }
+
+        .stat-divider {
+          width: 1px;
+          height: 40px;
+          background: #ebeef5;
+          margin: 0 20px;
+        }
+      }
+    }
+  }
+
+  // 内容区域
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 1px solid #ebeef5;
+
+      .section-title {
+        font-size: 18px;
+        font-weight: 600;
+        color: #303133;
+        margin: 0;
+      }
+
+      ::v-deep .el-form-item {
+        margin-bottom: 0;
+      }
+    }
+
+    .table-container {
+      .data-table {
+        ::v-deep .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+          }
+
+          .el-table-column--group > th {
+            background-color: #f0f2f5;
+          }
+        }
+
+        ::v-deep .el-table__body {
+          .area-name {
+            font-weight: 500;
+            color: #303133;
+          }
+
+          .date-text {
+            color: #606266;
+          }
+
+          .eco-value {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 4px;
+            font-weight: 500;
+
+            &.saving {
+              color: #52c41a;
+
+              i {
+                font-size: 14px;
+              }
+            }
+
+            &.increase {
+              color: #f56c6c;
+
+              i {
+                font-size: 14px;
+              }
+            }
+
+            &.neutral {
+              color: #909399;
+            }
+
+            .currency {
+              font-size: 12px;
+            }
+
+            .value {
+              font-size: 14px;
+            }
+          }
+        }
+      }
+
+      .pagination-container {
+        margin-top: 20px;
+        display: flex;
+        justify-content: flex-end;
+      }
+    }
+  }
+}
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .statistic-cards {
+      .el-col {
+        margin-bottom: 15px;
+      }
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+
+        .section-title {
+          margin-bottom: 15px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 570 - 117
ems-ui-cloud/src/views/analysis/power/store.vue

@@ -1,97 +1,199 @@
 <template>
   <div class="app-container">
-    <el-row :gutter="10">
-      <el-col :span="4" :xs="24">
+    <el-row :gutter="20">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px"/>
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                   :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                   @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;"/>
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-label">
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.facsCategory === 'C'"
+                size="mini"
+                effect="plain"
+                class="tree-tag"
+              >
+                储能
+              </el-tag>
+            </span>
+          </el-tree>
         </div>
       </el-col>
-      <el-col :span="20" :xs="24">
-        <el-card>
-          <el-row :gutter="10">
-            <el-col v-for="item in deviceModelStatus" :key="item.deviceCode" :span="12" :xs="24">
-              <el-card shadow="hover">
-                <el-descriptions :title="item.deviceName">
-                  <el-descriptions-item label="位置信息">{{ item.location }}</el-descriptions-item>
-                  <el-descriptions-item label="设备编码">{{ item.deviceCode }}</el-descriptions-item>
-                  <el-descriptions-item v-for="attr in item.attrJson" :key="attr.attr_key" :label="attr.attr_name">{{attr.attr_value}} {{attr.attr_unit}}</el-descriptions-item>
-                </el-descriptions>
-              </el-card>
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <!-- 设备状态卡片区域 -->
+        <div class="device-status-section" v-if="deviceModelStatus.length > 0">
+          <el-row :gutter="15">
+            <el-col
+              v-for="item in deviceModelStatus"
+              :key="item.deviceCode"
+              :span="12"
+              :xs="24"
+              class="device-card-col"
+            >
+              <div class="device-card">
+                <div class="device-header">
+                  <i class="el-icon-s-platform device-icon"></i>
+                  <span class="device-name">{{ item.deviceName }}</span>
+                </div>
+                <div class="device-info">
+                  <div class="info-item">
+                    <span class="info-label">位置信息:</span>
+                    <span class="info-value">{{ item.location }}</span>
+                  </div>
+                  <div class="info-item">
+                    <span class="info-label">设备编码:</span>
+                    <span class="info-value code">{{ item.deviceCode }}</span>
+                  </div>
+                  <div v-for="attr in item.attrJson" :key="attr.attr_key" class="info-item">
+                    <span class="info-label">{{ attr.attr_name }}:</span>
+                    <span class="info-value">
+                      {{ attr.attr_value }}
+                      <span class="info-unit" v-if="attr.attr_unit">{{ attr.attr_unit }}</span>
+                    </span>
+                  </div>
+                </div>
+              </div>
             </el-col>
           </el-row>
-        </el-card>
-        <el-card style="margin-top: 10px;">
-          <div class="container-block">
-            <div style="display: flex;justify-content: space-between;">
-              <SubTitle :title="`储能-日【${selectedLabel}】`"/>
-              <el-button-group>
-                <el-button v-for="item in btnGroup" :key="item.name" size="mini"
-                           :type="item.name === activeBtn ? 'primary' : ''" :icon="item.icon" @click="btnChange(item)"/>
-              </el-button-group>
+        </div>
+
+        <!-- 数据展示区域 -->
+        <div class="content-wrapper">
+          <!-- 标题区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <SubTitle :title="`储能-日【${selectedLabel}】`" />
             </div>
-            <div class="ctl-container">
-              <el-date-picker v-model="dateRange" type="daterange" @change="getList" :picker-options="pickerOptions"
-                              value-format="yyyy-MM-dd" range-separator="至" start-placeholder="开始日期"
-                              end-placeholder="结束日期"
-                              align="right" :clearable="false">
-              </el-date-picker>
+            <div class="header-right">
+              <el-button-group class="view-toggle">
+                <el-button
+                  v-for="item in btnGroup"
+                  :key="item.name"
+                  size="small"
+                  :type="item.name === activeBtn ? 'primary' : ''"
+                  :icon="item.icon"
+                  @click="btnChange(item)"
+                >
+                  {{ item.label }}
+                </el-button>
+              </el-button-group>
             </div>
-            <el-table v-if="activeBtn == 'table'" v-loading="loading" :data="storeList" max-height="400px">
-              <el-table-column label="日期" align="center" prop="date" width="180">
+          </div>
+
+          <!-- 控制区域 -->
+          <div class="control-container">
+            <el-date-picker
+              v-model="dateRange"
+              type="daterange"
+              @change="getList"
+              :picker-options="pickerOptions"
+              value-format="yyyy-MM-dd"
+              range-separator="至"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              align="right"
+              :clearable="false"
+              size="small"
+            />
+          </div>
+
+          <!-- 数据展示区域 -->
+          <div class="data-container">
+            <!-- 表格视图 -->
+            <el-table
+              v-if="activeBtn === 'table'"
+              v-loading="loading"
+              :data="storeList"
+              max-height="500px"
+              class="data-table"
+            >
+              <el-table-column label="日期" align="center" prop="date" width="180" />
+              <el-table-column label="充电电量(kW·h)" align="center" prop="chargeElecQuantity">
+                <template slot-scope="scope">
+                  <span class="data-value charge">{{ scope.row.chargeElecQuantity || 0 }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="放电电量(kW·h)" align="center" prop="dischargeElecQuantity">
+                <template slot-scope="scope">
+                  <span class="data-value discharge">{{ scope.row.dischargeElecQuantity || 0 }}</span>
+                </template>
               </el-table-column>
-              <el-table-column label="充电电量(kW·h)" align="center" prop="chargeElecQuantity"/>
-              <el-table-column label="放电电量(kW·h)" align="center" prop="dischargeElecQuantity"/>
             </el-table>
-            <BaseChart v-else width="100%" height="400px" :option="elecOptions"/>
+
+            <!-- 图表视图 -->
+            <BaseChart
+              v-else
+              width="100%"
+              height="500px"
+              :option="elecOptions"
+            />
           </div>
-        </el-card>
+        </div>
       </el-col>
     </el-row>
   </div>
 </template>
 
-
 <script>
-import {getStoreDayList} from '@/api/mgr/elecStoreH.js'
-import {areaTreeByFacsCategory} from '@/api/basecfg/area'
+import { getStoreDayList } from '@/api/mgr/elecStoreH.js'
+import { areaTreeByFacsCategory } from '@/api/basecfg/area'
 import BaseChart from '@/components/BaseChart'
 import SubTitle from '@/components/SubTitle'
-import Treeselect from "@riophae/vue-treeselect";
-import "@riophae/vue-treeselect/dist/vue-treeselect.css";
-import {dateFormat, getDayAgoDate} from '@/utils'
-import {listDeviceModel} from "@/api/device/device";
+import { dateFormat, getDayAgoDate } from '@/utils'
+import { listDeviceModel } from "@/api/device/device"
 
 export default {
   name: 'ElecStoreD',
   components: {
-    Treeselect,
     BaseChart,
     SubTitle
   },
   data() {
     const lastWeek = getDayAgoDate(7)
     const nowDay = new Date()
+
     return {
       // 遮罩层
       loading: true,
-      facsCategory: 'E',
+      facsCategory: 'C',
       facsSubCategory: '',
       storeList: [],
-      areaName: undefined,
+      areaName: '',
       defaultProps: {
-        children: "children",
-        label: "label"
+        children: 'children',
+        label: 'label'
       },
-      selectedLabel: '',
+      defaultExpandedKeys: [],
+      selectedLabel: '全部',
       // 查询参数
       queryParams: {
-        areaCode: ''
+        areaCode: '-1'
       },
       pickerOptions: {
         shortcuts: [
@@ -103,7 +205,8 @@ export default {
               start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
               picker.$emit('pick', [start, end])
             }
-          }, {
+          },
+          {
             text: '最近一个月',
             onClick(picker) {
               const end = new Date()
@@ -111,7 +214,8 @@ export default {
               start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
               picker.$emit('pick', [start, end])
             }
-          }, {
+          },
+          {
             text: '最近三个月',
             onClick(picker) {
               const end = new Date()
@@ -126,24 +230,24 @@ export default {
       areaOptions: [],
       deviceModelStatus: [],
       dateRange: [dateFormat(lastWeek, 'yyyy-MM-dd'), dateFormat(nowDay, 'yyyy-MM-dd')],
-      activeBtn: 'chart', //--chart图表 --table表格
+      activeBtn: 'chart',
       btnGroup: [
         {
           name: 'chart',
-          icon: 'el-icon-s-data'
+          icon: 'el-icon-s-data',
+          label: '图表'
         },
         {
           name: 'table',
-          icon: 'el-icon-menu'
-        }]
+          icon: 'el-icon-menu',
+          label: '表格'
+        }
+      ]
     }
   },
   computed: {
     elecOptions() {
-      const xData = this.storeList.map(item => item.date)
-      const chargeQuantity = this.storeList.map(item => item.chargeElecQuantity)
-      const dischargeQuantity = this.storeList.map(item => item.dischargeElecQuantity)
-      const option = {
+      return {
         toolbox: {
           itemGap: 10,
           itemSize: 16,
@@ -163,24 +267,31 @@ export default {
         tooltip: {
           trigger: 'axis',
           formatter: (params) => {
-            var relVal = params[0].name
-            for (var i = 0, l = params.length; i < l; i++) {
+            let relVal = params[0].name
+            for (let i = 0, l = params.length; i < l; i++) {
               relVal =
-                  relVal +
-                  '<br/>' +
-                  params[i].marker +
-                  params[i].seriesName +
-                  '&nbsp;&nbsp;&nbsp;' +
-                  params[i].value +
-                  'kW·h'
+                relVal +
+                '<br/>' +
+                params[i].marker +
+                params[i].seriesName +
+                '&nbsp;&nbsp;&nbsp;' +
+                params[i].value + ' kW·h'
             }
             return relVal
           }
         },
-        legend: {},
+        legend: {
+          data: ['充电电量', '放电电量']
+        },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '15%',
+          containLabel: true
+        },
         xAxis: {
           type: 'category',
-          data: xData,
+          data: this.storeList.map(item => item.date),
           axisPointer: {
             type: 'shadow'
           }
@@ -191,39 +302,66 @@ export default {
             type: 'value'
           }
         ],
+        dataZoom: [
+          {
+            type: 'slider',
+            start: 0,
+            end: 40,
+            height: 10,
+            bottom: '5%',
+            showDetail: false,
+            showDataShadow: false,
+            borderColor: 'transparent'
+          },
+          {
+            type: 'inside',
+            start: 0,
+            end: 40
+          }
+        ],
         series: [
           {
             name: '充电电量',
             type: 'bar',
-            data: chargeQuantity,
-            barWidth: 20,
+            data: this.storeList.map(item => item.chargeElecQuantity),
+            barWidth: 30,
             itemStyle: {
               normal: {
-                color: '#6395FA'
+                color: '#6395FA',
+                label: {
+                  show: true,
+                  position: 'top',
+                  textStyle: {
+                    color: '#6395FA',
+                    fontSize: 12,
+                    fontWeight: 500
+                  }
+                }
               }
             }
           },
           {
             name: '放电电量',
             type: 'bar',
-            data: dischargeQuantity,
-            barWidth: 20,
+            data: this.storeList.map(item => item.dischargeElecQuantity),
+            barWidth: 30,
             itemStyle: {
               normal: {
-                color: '#8CDF6C'
+                color: '#8CDF6C',
+                label: {
+                  show: true,
+                  position: 'top',
+                  textStyle: {
+                    color: '#8CDF6C',
+                    fontSize: 12,
+                    fontWeight: 500
+                  }
+                }
               }
             }
           }
         ]
       }
-      return option
-    },
-
-  },
-  watch: {
-    // 根据名称筛选区域树
-    areaName(val) {
-      this.$refs.tree.filter(val)
     }
   },
   async created() {
@@ -232,74 +370,389 @@ export default {
     this.getListDeviceModel()
   },
   methods: {
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.facsCategory === 'C') {
+        return 'el-icon-s-grid'
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 按钮切换
     btnChange(item) {
       this.activeBtn = item.name
     },
+
     // 查询区域列表
     async getAreaList() {
-      await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false).then(response => {
+      try {
+        const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
         this.areaOptions = [{
           id: '-1',
           label: '全部',
-          children: []
-        }].concat(response.data)
-        this.selectedLabel = '全部'
-        this.queryParams.areaCode = '-1'
-      })
+          children: response.data || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      } catch (error) {
+        console.error('加载区域树失败', error)
+        this.$message.error('加载区域树失败')
+      }
     },
-    /** 查询储能计量-小时列表 */
+
+    // 查询储能计量列表
     getList() {
       this.loading = true
-      const {areaCode} = this.queryParams
-      const [startRecTime, endRecTime] = this.dateRange
+      const { areaCode } = this.queryParams
+      let startRecTime = ''
+      let endRecTime = ''
+
+      if (this.dateRange && this.dateRange.length) {
+        const [start, end] = this.dateRange
+        startRecTime = start
+        endRecTime = end
+      }
+
       getStoreDayList({
         areaCode,
         startRecTime,
         endRecTime
-      }).then(({code, data}) => {
+      }).then(({ code, data }) => {
         this.loading = false
         if (code === 200) {
-          this.storeList = data
+          this.storeList = data || []
         }
-
+      }).catch(() => {
+        this.loading = false
+        this.$message.error('数据加载失败')
       })
     },
+
+    // 查询设备模型状态
+    async getListDeviceModel() {
+      try {
+        const { data } = await listDeviceModel({
+          areaCode: this.queryParams.areaCode,
+          subsystemCode: 'SYS_CN',
+          attrGroup: 'State'
+        })
+        this.deviceModelStatus = data || []
+      } catch (error) {
+        console.error('加载设备状态失败', error)
+        this.deviceModelStatus = []
+      }
+    },
+
     // 筛选节点
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     // 节点单击事件
     handleNodeClick(data) {
       this.queryParams.areaCode = data.id
       this.selectedLabel = data.label
       this.getList()
       this.getListDeviceModel()
-    },
-    async getListDeviceModel() {
-      const {data} = await listDeviceModel({
-        areaCode: this.queryParams.areaCode,
-        subsystemCode: 'SYS_CN',
-        attrGroup: 'State'
-      })
-      this.deviceModelStatus = data
     }
   }
 }
 </script>
 
 <style lang="scss" scoped>
-@import './index.scss';
-
 .app-container {
-  ::v-deep .el-tabs__content {
-    overflow: initial;
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+            }
+          }
+
+          .tree-tag {
+            margin-right: 8px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: #fff;
+            border: none;
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
+
+  // 设备状态卡片样式
+  .device-status-section {
+    margin-bottom: 20px;
+
+    .device-card-col {
+      margin-bottom: 15px;
+    }
+
+    .device-card {
+      background: #fff;
+      border-radius: 8px;
+      padding: 20px;
+      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+      transition: all 0.3s;
+
+      &:hover {
+        transform: translateY(-3px);
+        box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.12);
+      }
+
+      .device-header {
+        display: flex;
+        align-items: center;
+        margin-bottom: 15px;
+        padding-bottom: 10px;
+        border-bottom: 2px solid #f0f0f0;
+
+        .device-icon {
+          font-size: 24px;
+          color: #409eff;
+          margin-right: 10px;
+        }
+
+        .device-name {
+          font-size: 16px;
+          font-weight: 600;
+          color: #303133;
+        }
+      }
+
+      .device-info {
+        .info-item {
+          display: flex;
+          align-items: center;
+          margin-bottom: 8px;
+          font-size: 14px;
+
+          &:last-child {
+            margin-bottom: 0;
+          }
+
+          .info-label {
+            color: #909399;
+            min-width: 80px;
+            flex-shrink: 0;
+          }
+
+          .info-value {
+            color: #606266;
+            flex: 1;
+
+            &.code {
+              font-family: 'Courier New', monospace;
+              background: #f4f4f5;
+              padding: 2px 8px;
+              border-radius: 4px;
+              font-size: 13px;
+            }
+
+            .info-unit {
+              color: #909399;
+              font-size: 12px;
+              margin-left: 4px;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 1px solid #ebeef5;
+
+      .header-left {
+        ::v-deep .sub-title {
+          font-size: 18px;
+          font-weight: 600;
+          color: #303133;
+        }
+      }
+
+      .view-toggle {
+        ::v-deep .el-button {
+          padding: 8px 15px;
+
+          &.el-button--primary {
+            background: linear-gradient(90deg, #409eff 0%, #53a8ff 100%);
+            border-color: #409eff;
+          }
+        }
+      }
+    }
+
+    .control-container {
+      display: flex;
+      justify-content: flex-end;
+      margin-bottom: 20px;
+
+      ::v-deep .el-date-editor {
+        width: 350px;
+
+        .el-range-separator {
+          color: #606266;
+        }
+      }
+    }
+
+    .data-container {
+      .data-table {
+        ::v-deep .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+          }
+        }
+
+        ::v-deep .el-table__body {
+          .data-value {
+            font-weight: 500;
+            font-size: 14px;
+
+            &.charge {
+              color: #409eff;
+            }
+
+            &.discharge {
+              color: #67c23a;
+            }
+          }
+        }
+      }
+    }
   }
 }
 
-.ctl-container {
-  display: flex;
-  justify-content: flex-end;
-  margin: 10px 0;
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+
+    .device-status-section {
+      .device-card-col {
+        margin-bottom: 10px;
+      }
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+
+        .header-right {
+          margin-top: 10px;
+        }
+      }
+
+      .control-container {
+        justify-content: center;
+
+        ::v-deep .el-date-editor {
+          width: 100%;
+        }
+      }
+    }
+  }
 }
 </style>

+ 139 - 57
ems-ui-cloud/src/views/analysis/report/statement-consume.vue

@@ -10,7 +10,7 @@
 
     <el-row :gutter="20">
       <!-- 左侧树形选择器 -->
-      <el-col :span="4" :xs="24">
+      <el-col :span="5" :xs="24">
         <div class="head-container">
           <el-input
             v-model="filterText"
@@ -19,40 +19,41 @@
             size="small"
             prefix-icon="el-icon-search"
             style="margin-bottom: 20px"
+            @input="filterTree"
           />
         </div>
         <div class="head-container tree-container">
           <el-tree
+            ref="tree"
             :data="treeOptions"
             :props="defaultProps"
             :expand-on-click-node="false"
             :filter-node-method="filterNode"
-            ref="tree"
             node-key="id"
-            default-expand-all
+            :default-expanded-keys="defaultExpandedKeys"
             highlight-current
             @node-click="handleNodeClick"
           >
-            <template #default="{ node, data }">
-              <el-tooltip
-                class="tree-node-tooltip"
-                effect="dark"
-                :content="data.label"
-                placement="right"
-                :disabled="!isTextOverflow(data.label)"
+            <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-label">
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.energyType"
+                size="mini"
+                effect="plain"
+                class="tree-tag"
               >
-                <div class="tree-node">
-                  <i class="el-icon-folder-opened node-icon"></i>
-                  <span class="node-label">{{ data.label }}</span>
-                </div>
-              </el-tooltip>
-            </template>
+                {{ data.energyType }}
+              </el-tag>
+            </span>
           </el-tree>
         </div>
       </el-col>
 
       <!-- 右侧内容区域 -->
-      <el-col :span="20" :xs="24">
+      <el-col :span="19" :xs="24">
         <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px">
           <!-- 能源类型选择 -->
           <el-form-item label="能源类型">
@@ -285,6 +286,7 @@ export default {
         children: 'children',
         label: 'label'
       },
+      defaultExpandedKeys: [],
 
       // 数据
       consumptionList: [],
@@ -446,6 +448,25 @@ export default {
       return fieldName ? (row[fieldName] || 0) : 0
     },
 
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (this.activeName === 'facsConsume') {
+        if (data.id === 'Z') {
+          return 'el-icon-s-platform'
+        }
+        return 'el-icon-s-tools'
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.filterText)
+    },
+
     // 能源类型切换处理
     async handleEnergyTypeChange() {
       this.clearData()
@@ -489,6 +510,16 @@ export default {
           label: '全部区域',
           children: response.data || []
         }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
       } catch (error) {
         this.$modal.msgError('加载区域数据失败')
       }
@@ -499,6 +530,11 @@ export default {
       try {
         const response = await getFacsCategoryTree()
         this.treeOptions = this.flattenFacsTreeData(response.data || [])
+
+        // 设置默认展开第一级
+        if (this.treeOptions.length > 0) {
+          this.defaultExpandedKeys = this.treeOptions.map(item => item.id)
+        }
       } catch (error) {
         this.$modal.msgError('加载设施数据失败')
       }
@@ -758,11 +794,7 @@ export default {
         throw new Error('文件下载失败')
       }
     },
-    isTextOverflow(text) {
-      // 简单判断:超过一定字符数就显示tooltip
-      // 可以根据实际情况调整这个数值
-      return text && text.length > 8
-    },
+
     // 获取当前时间字符串
     getCurrentTimeStr() {
       const now = new Date()
@@ -882,59 +914,101 @@ export default {
 </script>
 
 <style scoped>
-/* 树容器样式 - 参考附件2的美化设计 */
-.tree-container {
-  height: calc(100vh - 200px);
+/* 应用容器样式 */
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+}
+
+/* 内容包装器样式 */
+.content-wrapper {
+  background: #fff;
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  min-height: calc(100vh - 200px);
+}
+
+/* 树容器样式 - 采用产能分析界面的美化设计 */
+.head-container {
+  background: #fff;
+  padding: 15px;
+  border-radius: 8px;
+  margin-bottom: 15px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.head-container.tree-container {
+  max-height: calc(100vh - 280px);
   overflow-y: auto;
-  border: 1px solid #e8e8e8;
-  border-radius: 4px;
-  padding: 10px;
-  background-color: #fafafa;
 }
 
-/* 树节点样式 */
-.tree-node {
-  display: flex;
-  align-items: center;
-  width: 100%;
-  padding: 2px 0;
-  transition: all 0.3s;
-  cursor: pointer;
+/* 自定义滚动条样式 */
+.tree-container::-webkit-scrollbar {
+  width: 6px;
 }
 
-.node-icon {
-  margin-right: 8px;
-  font-size: 16px;
+.tree-container::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+.tree-container::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+.tree-container::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+
+/* 树节点样式 - 优化版 */
+/deep/ .el-tree {
+  background: transparent;
+}
+
+/deep/ .el-tree-node__content {
+  height: 40px;
+  padding: 0 8px;
   transition: all 0.3s;
-  color: #409EFF;
 }
 
-.node-label {
+/deep/ .el-tree-node__content:hover {
+  background-color: #f5f7fa;
+}
+
+/deep/ .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;
 }
 
-/* 节点hover效果 */
-.tree-node:hover {
-  background-color: #f0f7ff;
-  border-radius: 4px;
-  padding-left: 4px;
+.tree-label {
+  display: flex;
+  align-items: center;
 }
 
-.tree-node:hover .node-icon {
-  color: #2b7bff;
-  transform: scale(1.1);
+.tree-icon {
+  margin-right: 8px;
+  font-size: 16px;
+  color: #909399;
+  transition: color 0.3s;
 }
 
-/* 高亮当前选中的节点 */
-/deep/ .el-tree-node.is-current > .el-tree-node__content .tree-node {
-  background-color: #e6f7ff;
-  border-radius: 4px;
-  padding-left: 4px;
+/deep/ .el-tree-node.is-current .tree-icon {
+  color: #409eff;
 }
 
-/deep/ .el-tree-node.is-current > .el-tree-node__content .node-icon {
-  color: #1890ff;
+.tree-tag {
+  margin-right: 8px;
 }
 
 /* 超紧凑统计卡片样式 */
@@ -1115,6 +1189,14 @@ export default {
 }
 
 @media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+  }
+
+  .el-col {
+    margin-bottom: 20px;
+  }
+
   .summary-row-compact {
     flex-direction: column;
     gap: 8px;

+ 621 - 168
ems-ui-cloud/src/views/analysis/report/statement-prod.vue

@@ -1,150 +1,281 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
-      <el-col :span="4" :xs="24">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px"/>
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                   :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                   @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;"/>
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-label">
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.facsCategory === 'E'"
+                size="mini"
+                effect="plain"
+                class="tree-tag"
+              >
+                光伏
+              </el-tag>
+            </span>
+          </el-tree>
         </div>
       </el-col>
-      <el-col :span="20" :xs="24">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
-                 label-width="68px">
-          <!-- 开始时间选择器 -->
-          <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>
-          </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>
-          </el-form-item>
-          <el-form-item>
-            <el-radio-group v-model="tabPosition">
-              <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 icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-            <el-button type="warning" plain icon="el-icon-download" size="mini" v-hasPermi="['ems:EmsEcoD:export']"
-                       @click="handleExport">导出
-            </el-button>
-          </el-form-item>
-        </el-form>
-
-        <el-table v-loading="loading" :data="pvSupplyHList">
-          <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="genElecQuantity"/>
-          <el-table-column label="自用电量(kW·h)" align="center" prop="useElecQuantity"/>
-          <el-table-column label="上网电量(kW·h)" align="center" prop="upElecQuantity"/>
-          <el-table-column label="上网收益(¥)" align="center" prop="upElecEarn"/>
-        </el-table>
-        <pagination
-            v-show="total>0"
-            :total="total"
-            :page.sync="queryParams.pageNum"
-            :limit.sync="queryParams.pageSize"
-            @pagination="getList"
-        />
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <!-- 标题区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <h3 class="page-title">
+                <i class="el-icon-sunny"></i>
+                光伏发电统计【{{ selectedLabel }}】
+              </h3>
+            </div>
+            <div class="header-right">
+              <el-button-group class="period-toggle">
+                <el-button
+                  v-for="item in periodOptions"
+                  :key="item.value"
+                  size="small"
+                  :type="tabPosition === item.value ? 'primary' : ''"
+                  @click="tabPosition = item.value"
+                >
+                  {{ item.label }}
+                </el-button>
+              </el-button-group>
+            </div>
+          </div>
+
+          <!-- 查询条件区域 -->
+          <div class="search-container">
+            <el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
+              <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-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-form-item>
+
+              <el-form-item class="search-buttons">
+                <el-button icon="el-icon-search" type="primary" 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"
+                  v-hasPermi="['ems:EmsEcoD:export']"
+                  @click="handleExport"
+                >
+                  导出
+                </el-button>
+              </el-form-item>
+            </el-form>
+          </div>
+
+          <!-- 数据统计卡片 -->
+          <div class="stats-cards">
+            <el-row :gutter="20">
+              <el-col :span="6" :xs="24">
+                <div class="stat-card">
+                  <div class="stat-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
+                    <i class="el-icon-s-data"></i>
+                  </div>
+                  <div class="stat-content">
+                    <div class="stat-value">{{ totalStats.genElecQuantity || 0 }}</div>
+                    <div class="stat-label">总发电量(kW·h)</div>
+                  </div>
+                </div>
+              </el-col>
+              <el-col :span="6" :xs="24">
+                <div class="stat-card">
+                  <div class="stat-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
+                    <i class="el-icon-house"></i>
+                  </div>
+                  <div class="stat-content">
+                    <div class="stat-value">{{ totalStats.useElecQuantity || 0 }}</div>
+                    <div class="stat-label">自用电量(kW·h)</div>
+                  </div>
+                </div>
+              </el-col>
+              <el-col :span="6" :xs="24">
+                <div class="stat-card">
+                  <div class="stat-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
+                    <i class="el-icon-upload"></i>
+                  </div>
+                  <div class="stat-content">
+                    <div class="stat-value">{{ totalStats.upElecQuantity || 0 }}</div>
+                    <div class="stat-label">上网电量(kW·h)</div>
+                  </div>
+                </div>
+              </el-col>
+              <el-col :span="6" :xs="24">
+                <div class="stat-card">
+                  <div class="stat-icon" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
+                    <i class="el-icon-wallet"></i>
+                  </div>
+                  <div class="stat-content">
+                    <div class="stat-value">¥ {{ totalStats.upElecEarn || 0 }}</div>
+                    <div class="stat-label">上网收益</div>
+                  </div>
+                </div>
+              </el-col>
+            </el-row>
+          </div>
+
+          <!-- 数据表格 -->
+          <div class="table-container">
+            <el-table
+              v-loading="loading"
+              :data="pvSupplyHList"
+              class="data-table"
+              stripe
+            >
+              <el-table-column label="日期" align="center" prop="date" width="180">
+                <template slot-scope="scope">
+                  <span class="date-text">{{ parseTime(scope.row.date, '{y}-{m}-{d}') }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="时间" align="center" prop="time">
+                <template slot-scope="scope">
+                  <el-tag size="mini" type="info">{{ scope.row.time }}</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="总发电量" align="center" prop="genElecQuantity">
+                <template slot-scope="scope">
+                  <span class="data-value primary">{{ scope.row.genElecQuantity || 0 }}</span>
+                  <span class="data-unit">kW·h</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="自用电量" align="center" prop="useElecQuantity">
+                <template slot-scope="scope">
+                  <span class="data-value info">{{ scope.row.useElecQuantity || 0 }}</span>
+                  <span class="data-unit">kW·h</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="上网电量" align="center" prop="upElecQuantity">
+                <template slot-scope="scope">
+                  <span class="data-value success">{{ scope.row.upElecQuantity || 0 }}</span>
+                  <span class="data-unit">kW·h</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="上网收益" align="center" prop="upElecEarn">
+                <template slot-scope="scope">
+                  <span class="data-value warning">¥ {{ scope.row.upElecEarn || 0 }}</span>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <pagination
+              v-show="total > 0"
+              :total="total"
+              :page.sync="queryParams.pageNum"
+              :limit.sync="queryParams.pageSize"
+              @pagination="getList"
+            />
+          </div>
+        </div>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-import {areaTreeByFacsCategory} from '@/api/basecfg/area'
-import {listPvSupplyH} from "@/api/mgr/pgSupplyH";
-import {DateTool} from "@/utils/DateTool";
+import { areaTreeByFacsCategory } from '@/api/basecfg/area'
+import { listPvSupplyH } from "@/api/mgr/pgSupplyH"
+import { DateTool } from "@/utils/DateTool"
 
 export default {
-  name: "EmsEcoD",
+  name: "PvSupplyStatistics",
   data() {
     return {
       // 遮罩层
       loading: true,
       // 选中数组
       ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
-      // 显示搜索条件
-      showSearch: true,
       // 总条数
       total: 0,
-      // 节能计量日表格数据
-      EmsEcoDList: [],
+      // 光伏供电数据
       pvSupplyHList: [],
-      // 弹出层标题
-      title: "",
-      // 是否显示弹出层
-      open: false,
-      // 表单参数
+      // 区域树配置
       areaOptions: [],
       areaName: undefined,
       facsCategory: 'E',
       facsSubCategory: 'E5',
-      // 设施选项
-      facsOptions: undefined,
+      selectedLabel: '全部',
+      defaultExpandedKeys: [],
       defaultProps: {
         children: "children",
         label: "label"
       },
+      // 时间周期选项
+      periodOptions: [
+        { label: '日', value: 'day' },
+        { label: '月', value: 'month' },
+        { label: '年', value: 'year' }
+      ],
       tabPosition: 'month',
       // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        areaCode: null,
-        startRecTime: this.getFirstDayOfMonth(), // 本月1号
-        endRecTime: this.getTodayEndTime(),    // 当天结束时间
+        areaCode: '-1',
+        startRecTime: this.getFirstDayOfMonth(),
+        endRecTime: this.getTodayEndTime(),
       },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        areaCode: [
-          {required: true, message: "园区代码不能为空", trigger: "blur"}
-        ],
-        date: [
-          {required: true, message: "日期不能为空", trigger: "blur"}
-        ],
+      // 统计数据
+      totalStats: {
+        genElecQuantity: 0,
+        useElecQuantity: 0,
+        upElecQuantity: 0,
+        upElecEarn: 0
       },
       // 时间选择器配置
       startPickerOptions: {
         disabledDate: (time) => {
-          // 禁用未来时间和超过90天前的时间
           const ninetyDaysAgo = new Date();
           ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
           return time.getTime() > Date.now() - 8.64e7 || time.getTime() < ninetyDaysAgo.getTime();
@@ -154,17 +285,15 @@ export default {
       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);
 
             return time.getTime() < startDate.getTime() ||
-                time.getTime() > endDateLimit.getTime() ||
-                time.getTime() > Date.now() - 8.64e7;
+              time.getTime() > endDateLimit.getTime() ||
+              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();
@@ -174,27 +303,24 @@ export default {
     };
   },
   watch: {
-    // 根据名称筛选区域树
     tabPosition(val) {
-      if (!val) {
-        return;
-      }
+      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') {
+      } else 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') {
+      } else 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);
       }
+
+      this.getList();
     }
   },
   created() {
-    // 初始化时间格式
     if (this.queryParams.startRecTime) {
       this.queryParams.startRecTime = this.formatDateTime(this.queryParams.startRecTime);
     }
@@ -205,88 +331,122 @@ export default {
     this.getList();
   },
   methods: {
-    /** 查询节能计量日列表 */
+    // 获取数据列表
     getList() {
       this.loading = true;
       listPvSupplyH(this.queryParams).then(response => {
-        this.pvSupplyHList = response.rows
-        this.total = response.total
-        this.loading = false
-      })
+        this.pvSupplyHList = response.rows;
+        this.total = response.total;
+        this.calculateStats(response.rows);
+        this.loading = false;
+      });
+    },
+
+    // 计算统计数据
+    calculateStats(data) {
+      this.totalStats = {
+        genElecQuantity: 0,
+        useElecQuantity: 0,
+        upElecQuantity: 0,
+        upElecEarn: 0
+      };
+
+      if (data && data.length > 0) {
+        data.forEach(item => {
+          this.totalStats.genElecQuantity += parseFloat(item.genElecQuantity || 0);
+          this.totalStats.useElecQuantity += parseFloat(item.useElecQuantity || 0);
+          this.totalStats.upElecQuantity += parseFloat(item.upElecQuantity || 0);
+          this.totalStats.upElecEarn += parseFloat(item.upElecEarn || 0);
+        });
+
+        // 保留两位小数
+        this.totalStats.genElecQuantity = this.totalStats.genElecQuantity.toFixed(2);
+        this.totalStats.useElecQuantity = this.totalStats.useElecQuantity.toFixed(2);
+        this.totalStats.upElecQuantity = this.totalStats.upElecQuantity.toFixed(2);
+        this.totalStats.upElecEarn = this.totalStats.upElecEarn.toFixed(2);
+      }
     },
+
     // 查询区域列表
     async getAreaList() {
       await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false).then(response => {
         this.areaOptions = [{
           id: '-1',
           label: '全部',
-          children: []
-        }].concat(response.data)
-        this.selectedLabel = '全部'
-        this.queryParams.areaCode = '-1'
-      })
+          children: response.data || []
+        }];
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1'];
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1');
+          }
+        });
+      });
+    },
+
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.facsCategory === 'E') {
+        return 'el-icon-sunny';
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home';
+      }
+      return 'el-icon-office-building';
     },
+
     // 生成整点时间范围
     generateHourRanges() {
-      const ranges = []
+      const ranges = [];
       for (let i = 0; i < 24; i++) {
-        const start = `${i.toString().padStart(2, '0')}:00:00`
-        const end = `${i.toString().padStart(2, '0')}:59:59`
-        ranges.push(`${start} - ${end}`)
+        const start = `${i.toString().padStart(2, '0')}:00:00`;
+        const end = `${i.toString().padStart(2, '0')}:59:59`;
+        ranges.push(`${start} - ${end}`);
       }
-      return ranges
+      return ranges;
     },
 
     // 时间选择处理
     handleTimeChange(field) {
-      this.tabPosition = ""
+      this.tabPosition = "";
       if (this.queryParams[field]) {
-        const date = new Date(this.queryParams[field])
-        // 手动格式化日期(替代moment.js)
+        const date = new Date(this.queryParams[field]);
         const formatDate = (d) => {
-          const pad = (n) => String(n).padStart(2, '0')
-          return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:00:00`
-        }
-        this.queryParams[field] = formatDate(date)
+          const pad = (n) => String(n).padStart(2, '0');
+          return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:00:00`;
+        };
+        this.queryParams[field] = formatDate(date);
       }
 
-      // 自动同步结束时间范围
       if (field === 'startRecTime' && this.queryParams.endRecTime) {
         this.$nextTick(() => {
-          this.$refs.queryForm.validateField('endRecTime')
-        })
+          this.$refs.queryForm.validateField('endRecTime');
+        });
       }
     },
-    // 取消按钮
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    // 表单重置
-    reset() {
-      this.form = {
-        id: null,
-        areaCode: null,
-        date: null
-      };
-      this.resetForm("form");
-    },
-    /** 搜索按钮操作 */
+
+    // 搜索按钮操作
     handleQuery() {
       this.queryParams.pageNum = 1;
       this.getList();
     },
-    /** 重置按钮操作 */
+
+    // 重置按钮操作
     resetQuery() {
       this.resetForm("queryForm");
+      this.tabPosition = 'month';
       this.handleQuery();
     },
 
-    /** 导出按钮操作 */
+    // 导出按钮操作
     handleExport() {
       this.download('ems/prod/pv/hour/export', {
         ...this.queryParams
-      }, `产能报表_${new Date().getTime()}.xlsx`)
+      }, `产能报表_${new Date().getTime()}.xlsx`);
     },
 
     // 获取本月1号 00:00:00
@@ -304,7 +464,7 @@ export default {
       return this.formatDateTime(date);
     },
 
-    // 格式化日期时间为 yyyy-MM-dd HH:mm:ss
+    // 格式化日期时间
     formatDateTime(date) {
       if (!date) return '';
       if (typeof date === 'string') {
@@ -320,18 +480,311 @@ export default {
 
       return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
     },
+
     // 筛选节点
     filterNode(value, data) {
-      if (!value) return true
-      return data.label.indexOf(value) !== -1
+      if (!value) return true;
+      return data.label.indexOf(value) !== -1;
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName);
     },
+
     // 节点单击事件
     handleNodeClick(data) {
-      this.queryParams.areaCode = data.id
-      this.selectedLabel = data.label
-      this.getList()
+      this.queryParams.areaCode = data.id;
+      this.selectedLabel = data.label;
+      this.getList();
     }
-
   }
 };
 </script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+            }
+          }
+
+          .tree-tag {
+            margin-right: 8px;
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 2px solid #f0f2f5;
+
+      .page-title {
+        margin: 0;
+        font-size: 20px;
+        font-weight: 600;
+        color: #303133;
+
+        i {
+          margin-right: 8px;
+          color: #409eff;
+        }
+      }
+
+      .period-toggle {
+        ::v-deep .el-button {
+          padding: 8px 20px;
+
+          &.el-button--primary {
+            background: linear-gradient(90deg, #409eff 0%, #53a8ff 100%);
+            border-color: #409eff;
+          }
+        }
+      }
+    }
+
+    .search-container {
+      background: #f9fbfd;
+      border-radius: 8px;
+      padding: 15px;
+      margin-bottom: 20px;
+
+      ::v-deep .el-form {
+        .el-form-item {
+          margin-bottom: 0;
+          margin-right: 15px;
+
+          .el-form-item__label {
+            font-weight: 500;
+            color: #606266;
+          }
+        }
+
+        .search-buttons {
+          margin-right: 0;
+
+          .el-button {
+            padding: 7px 15px;
+          }
+        }
+      }
+    }
+
+    .stats-cards {
+      margin-bottom: 20px;
+
+      .stat-card {
+        background: #fff;
+        border-radius: 8px;
+        padding: 20px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+        display: flex;
+        align-items: center;
+        transition: transform 0.3s, box-shadow 0.3s;
+
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
+        }
+
+        .stat-icon {
+          width: 60px;
+          height: 60px;
+          border-radius: 12px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          margin-right: 15px;
+
+          i {
+            font-size: 28px;
+            color: #fff;
+          }
+        }
+
+        .stat-content {
+          flex: 1;
+
+          .stat-value {
+            font-size: 24px;
+            font-weight: 600;
+            color: #303133;
+            margin-bottom: 5px;
+          }
+
+          .stat-label {
+            font-size: 14px;
+            color: #909399;
+          }
+        }
+      }
+    }
+
+    .table-container {
+      .data-table {
+        ::v-deep .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+            font-size: 14px;
+          }
+        }
+
+        ::v-deep .el-table__body {
+          .date-text {
+            font-weight: 500;
+            color: #606266;
+          }
+
+          .data-value {
+            font-weight: 600;
+            font-size: 15px;
+
+            &.primary {
+              color: #409eff;
+            }
+
+            &.success {
+              color: #67c23a;
+            }
+
+            &.info {
+              color: #909399;
+            }
+
+            &.warning {
+              color: #e6a23c;
+            }
+          }
+
+          .data-unit {
+            font-size: 12px;
+            color: #909399;
+            margin-left: 2px;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+
+        .period-toggle {
+          margin-top: 10px;
+        }
+      }
+
+      .search-container {
+        ::v-deep .el-form {
+          .el-form-item {
+            display: block;
+            margin-bottom: 10px;
+          }
+        }
+      }
+
+      .stats-cards {
+        .el-col {
+          margin-bottom: 10px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 536 - 112
ems-ui-cloud/src/views/analysis/report/statement-self.vue

@@ -1,64 +1,187 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
-      <!-- 区域树结构 -->
-      <el-col :span="4" :xs="24">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search" style="margin-bottom: 20px" />
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;" />
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <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">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px">
-          <el-form-item label="报表类型">
-            <el-select v-model="queryParams.reportType" placeholder="请选择类型" @change="handleReportTypeChange">
-              <el-option label="产能" value="prod" />
-              <el-option label="用能" value="consume" />
-            </el-select>
-          </el-form-item>
-
-          <el-form-item label="指标项">
-            <el-select v-model="queryParams.metricField" placeholder="请选择指标">
-              <el-option v-for="item in metricOptions" :key="item.value" :label="item.label" :value="item.value" />
-            </el-select>
-          </el-form-item>
-
-          <el-form-item label="条件">
-            <el-select v-model="queryParams.conditionType" placeholder="请选择条件">
-              <el-option label="大于" value="gt" />
-              <el-option label="小于" value="lt" />
-              <el-option label="等于" value="eq" />
-              <el-option label="不等于" value="ne" />
-            </el-select>
-          </el-form-item>
-
-          <el-form-item label="指标值">
-            <el-input v-model.number="queryParams.metricValue" type="number" placeholder="请输入值" />
-          </el-form-item>
-
-          <el-form-item label="开始时间">
-            <el-date-picker v-model="queryParams.startRecTime" type="datetime" value-format="yyyy-MM-dd HH:00:00" />
-          </el-form-item>
-
-          <el-form-item label="结束时间">
-            <el-date-picker v-model="queryParams.endRecTime" type="datetime" value-format="yyyy-MM-dd HH:00:00" />
-          </el-form-item>
-
-          <el-form-item>
-            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
-            <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
-          </el-form-item>
-        </el-form>
-
-        <el-table v-loading="loading" :data="resultList">
-          <el-table-column v-for="col in resultTableCols" :key="col.prop" :label="col.label" :prop="col.prop" align="center" />
-        </el-table>
-
-        <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="handleQuery" />
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <!-- 标题区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <h3 class="page-title">
+                <i class="el-icon-document"></i>
+                自定义报表【{{ selectedLabel }}】
+              </h3>
+            </div>
+          </div>
+
+          <!-- 查询条件区域 -->
+          <div class="search-container">
+            <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px">
+              <el-form-item label="报表类型">
+                <el-select v-model="queryParams.reportType" placeholder="请选择类型" @change="handleReportTypeChange">
+                  <el-option label="产能" value="prod" />
+                  <el-option label="用能" value="consume" />
+                </el-select>
+              </el-form-item>
+
+              <el-form-item label="指标项">
+                <el-select v-model="queryParams.metricField" placeholder="请选择指标">
+                  <el-option v-for="item in metricOptions" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+              </el-form-item>
+
+              <el-form-item label="条件">
+                <el-select v-model="queryParams.conditionType" placeholder="请选择条件">
+                  <el-option label="大于" value="gt" />
+                  <el-option label="小于" value="lt" />
+                  <el-option label="等于" value="eq" />
+                  <el-option label="不等于" value="ne" />
+                </el-select>
+              </el-form-item>
+
+              <el-form-item label="指标值">
+                <el-input v-model.number="queryParams.metricValue" type="number" placeholder="请输入值" />
+              </el-form-item>
+
+              <el-form-item label="开始时间">
+                <el-date-picker
+                  v-model="queryParams.startRecTime"
+                  type="datetime"
+                  value-format="yyyy-MM-dd HH:00:00"
+                  placeholder="选择开始时间"
+                />
+              </el-form-item>
+
+              <el-form-item label="结束时间">
+                <el-date-picker
+                  v-model="queryParams.endRecTime"
+                  type="datetime"
+                  value-format="yyyy-MM-dd HH:00:00"
+                  placeholder="选择结束时间"
+                />
+              </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-form>
+          </div>
+
+          <!-- 统计卡片 -->
+          <div class="stats-cards" v-if="resultList.length > 0">
+            <el-row :gutter="20">
+              <el-col :span="6" :xs="24">
+                <div class="stat-card">
+                  <div class="stat-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
+                    <i class="el-icon-data-analysis"></i>
+                  </div>
+                  <div class="stat-content">
+                    <div class="stat-value">{{ total }}</div>
+                    <div class="stat-label">符合条件记录</div>
+                  </div>
+                </div>
+              </el-col>
+              <el-col :span="6" :xs="24">
+                <div class="stat-card">
+                  <div class="stat-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
+                    <i class="el-icon-document-checked"></i>
+                  </div>
+                  <div class="stat-content">
+                    <div class="stat-value">{{ queryParams.reportType === 'prod' ? '产能' : '用能' }}</div>
+                    <div class="stat-label">报表类型</div>
+                  </div>
+                </div>
+              </el-col>
+              <el-col :span="6" :xs="24">
+                <div class="stat-card">
+                  <div class="stat-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
+                    <i class="el-icon-s-data"></i>
+                  </div>
+                  <div class="stat-content">
+                    <div class="stat-value">{{ getMetricLabel() }}</div>
+                    <div class="stat-label">当前指标</div>
+                  </div>
+                </div>
+              </el-col>
+              <el-col :span="6" :xs="24">
+                <div class="stat-card">
+                  <div class="stat-icon" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
+                    <i class="el-icon-s-operation"></i>
+                  </div>
+                  <div class="stat-content">
+                    <div class="stat-value">{{ getConditionLabel() }} {{ queryParams.metricValue || 0 }}</div>
+                    <div class="stat-label">筛选条件</div>
+                  </div>
+                </div>
+              </el-col>
+            </el-row>
+          </div>
+
+          <!-- 数据表格 -->
+          <div class="table-container">
+            <el-table v-loading="loading" :data="resultList" stripe>
+              <el-table-column
+                v-for="col in resultTableCols"
+                :key="col.prop"
+                :label="col.label"
+                :prop="col.prop"
+                align="center"
+              >
+                <template slot-scope="scope">
+                  <span v-if="col.prop.includes('Quantity') || col.prop.includes('Cost') || col.prop.includes('Earn')" class="data-value">
+                    {{ formatNumber(scope.row[col.prop]) }}
+                  </span>
+                  <span v-else>{{ scope.row[col.prop] }}</span>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <pagination
+              v-show="total > 0"
+              :total="total"
+              :page.sync="queryParams.pageNum"
+              :limit.sync="queryParams.pageSize"
+              @pagination="handleQuery"
+            />
+          </div>
+        </div>
       </el-col>
     </el-row>
   </div>
@@ -76,6 +199,8 @@ export default {
       loading: false,
       areaName: '',
       areaOptions: [],
+      defaultExpandedKeys: [],
+      selectedLabel: '全部',
       total: 0,
       resultList: [],
       resultTableCols: [],
@@ -110,108 +235,407 @@ export default {
     }
   },
   created() {
-    this.metricOptions = this.metricOptionsMap[this.queryParams.reportType];
-    this.getAreaList();
+    this.metricOptions = this.metricOptionsMap[this.queryParams.reportType]
+    this.getAreaList()
   },
   methods: {
     getAreaList() {
       areaTreeSelect(0, 1).then(response => {
-        this.areaOptions = [{ id: '-1', label: '全部', children: [] }].concat(response.data || []);
-      });
+        this.areaOptions = [{
+          id: '-1',
+          label: '全部',
+          children: response.data || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      })
     },
+
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     handleNodeClick(data) {
-      this.queryParams.areaCode = data.id;
+      this.queryParams.areaCode = data.id
+      this.selectedLabel = data.label
     },
+
     handleReportTypeChange(type) {
-      this.metricOptions = this.metricOptionsMap[type];
-      this.queryParams.metricField = '';
+      this.metricOptions = this.metricOptionsMap[type]
+      this.queryParams.metricField = ''
     },
+
     handleQuery() {
-      const { reportType, ...params } = this.queryParams;
-      this.loading = true;
-      const api = reportType === 'prod' ? listPvSupplyH : listAreaMeter;
+      const { reportType, ...params } = this.queryParams
+      this.loading = true
+      const api = reportType === 'prod' ? listPvSupplyH : listAreaMeter
+
       api(params).then(res => {
-        const list = res.rows || [];
-        const metric = this.queryParams.metricField;
-        const value = this.queryParams.metricValue;
-        const op = this.queryParams.conditionType;
+        const list = res.rows || []
+        const metric = this.queryParams.metricField
+        const value = this.queryParams.metricValue
+        const op = this.queryParams.conditionType
+
         this.resultList = list.filter(item => {
-          const v = item[metric];
-          if (v === undefined || v === null) return false;
+          const v = item[metric]
+          if (v === undefined || v === null) return false
           switch (op) {
-            case 'gt': return v > value;
-            case 'lt': return v < value;
-            case 'eq': return v == value;
-            case 'ne': return v != value;
-            default: return true;
+            case 'gt': return v > value
+            case 'lt': return v < value
+            case 'eq': return v == value
+            case 'ne': return v != value
+            default: return true
           }
-        });
-        this.resultTableCols = this.generateColumns(reportType);
-        this.total = this.resultList.length;
-        this.loading = false;
-      });
+        })
+
+        this.resultTableCols = this.generateColumns(reportType)
+        this.total = this.resultList.length
+        this.loading = false
+      })
     },
+
+    resetQuery() {
+      this.queryParams = {
+        ...this.queryParams,
+        metricField: '',
+        conditionType: 'gt',
+        metricValue: null,
+        startRecTime: this.getFirstDayOfMonth(),
+        endRecTime: this.getTodayEndTime(),
+      }
+      this.resultList = []
+      this.total = 0
+    },
+
     generateColumns(type) {
       return type === 'prod'
         ? [
           { label: '日期', prop: 'date' },
           { label: '时间', prop: 'time' },
-          { label: '总发电量', prop: 'genElecQuantity' },
-          { label: '自用电量', prop: 'useElecQuantity' },
-          { label: '上网电量', prop: 'upElecQuantity' },
-          { label: '上网收益', prop: 'upElecEarn' },
+          { label: '总发电量(kW·h)', prop: 'genElecQuantity' },
+          { label: '自用电量(kW·h)', prop: 'useElecQuantity' },
+          { label: '上网电量(kW·h)', prop: 'upElecQuantity' },
+          { label: '上网收益(元)', prop: 'upElecEarn' },
         ]
         : [
           { label: '对象名称', prop: 'deviceName' },
           { label: '日期', prop: 'date' },
           { label: '时间', prop: 'time' },
-          { label: '用电量', prop: 'elecQuantity' },
-          { label: '用电花费', prop: 'useElecCost' },
-        ];
+          { label: '用电量(kW·h)', prop: 'elecQuantity' },
+          { label: '用电花费(元)', prop: 'useElecCost' },
+        ]
     },
+
     handleExport() {
-      const { reportType } = this.queryParams;
+      const { reportType } = this.queryParams
       const url = reportType === 'prod'
         ? 'ems/prod/pv/hour/export'
-        : 'ems/elecMeterH/exportAreaMeter';
-      this.download(url, this.queryParams, `自定义报表_${new Date().getTime()}.xlsx`);
+        : 'ems/elecMeterH/exportAreaMeter'
+      this.download(url, this.queryParams, `自定义报表_${new Date().getTime()}.xlsx`)
+    },
+
+    getMetricLabel() {
+      const metric = this.metricOptions.find(m => m.value === this.queryParams.metricField)
+      return metric ? metric.label : '未选择'
+    },
+
+    getConditionLabel() {
+      const conditions = {
+        'gt': '>',
+        'lt': '<',
+        'eq': '=',
+        'ne': '≠'
+      }
+      return conditions[this.queryParams.conditionType] || ''
+    },
+
+    formatNumber(value) {
+      if (value === null || value === undefined) return '0.00'
+      return parseFloat(value).toFixed(2)
     },
+
     getFirstDayOfMonth() {
-      const date = new Date();
-      date.setDate(1);
-      date.setHours(0, 0, 0, 0);
-      return this.formatDateTime(date);
+      const date = new Date()
+      date.setDate(1)
+      date.setHours(0, 0, 0, 0)
+      return this.formatDateTime(date)
     },
+
     getTodayEndTime() {
-      const date = new Date();
-      date.setHours(23, 59, 59, 999);
-      return this.formatDateTime(date);
+      const date = new Date()
+      date.setHours(23, 59, 59, 999)
+      return this.formatDateTime(date)
     },
+
     formatDateTime(date) {
-      if (!date) return '';
-      if (typeof date === 'string') date = new Date(date);
-      const y = date.getFullYear();
-      const m = String(date.getMonth() + 1).padStart(2, '0');
-      const d = String(date.getDate()).padStart(2, '0');
-      const h = String(date.getHours()).padStart(2, '0');
-      return `${y}-${m}-${d} ${h}:00:00`;
+      if (!date) return ''
+      if (typeof date === 'string') date = new Date(date)
+      const y = date.getFullYear()
+      const m = String(date.getMonth() + 1).padStart(2, '0')
+      const d = String(date.getDate()).padStart(2, '0')
+      const h = String(date.getHours()).padStart(2, '0')
+      return `${y}-${m}-${d} ${h}:00:00`
     },
+
     filterNode(value, data) {
-      if (!value) return true;
-      return data.label.indexOf(value) !== -1;
+      if (!value) return true
+      return data.label.indexOf(value) !== -1
     }
   }
 }
 </script>
 
-<style scoped>
-.el-tree .el-tree-node.is-leaf > .el-tree-node__content {
-  color: #333;
-  cursor: pointer;
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 2px solid #f0f2f5;
+
+      .page-title {
+        margin: 0;
+        font-size: 20px;
+        font-weight: 600;
+        color: #303133;
+
+        i {
+          margin-right: 8px;
+          color: #409eff;
+        }
+      }
+    }
+
+    .search-container {
+      background: #f9fbfd;
+      border-radius: 8px;
+      padding: 15px;
+      margin-bottom: 20px;
+
+      ::v-deep .el-form {
+        .el-form-item {
+          margin-bottom: 10px;
+          margin-right: 15px;
+
+          .el-form-item__label {
+            font-weight: 500;
+            color: #606266;
+          }
+        }
+
+        .el-button {
+          padding: 7px 15px;
+        }
+      }
+    }
+
+    .stats-cards {
+      margin-bottom: 20px;
+
+      .stat-card {
+        background: #fff;
+        border-radius: 8px;
+        padding: 20px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+        display: flex;
+        align-items: center;
+        transition: transform 0.3s, box-shadow 0.3s;
+
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
+        }
+
+        .stat-icon {
+          width: 60px;
+          height: 60px;
+          border-radius: 12px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          margin-right: 15px;
+
+          i {
+            font-size: 28px;
+            color: #fff;
+          }
+        }
+
+        .stat-content {
+          flex: 1;
+
+          .stat-value {
+            font-size: 24px;
+            font-weight: 600;
+            color: #303133;
+            margin-bottom: 5px;
+          }
+
+          .stat-label {
+            font-size: 14px;
+            color: #909399;
+          }
+        }
+      }
+    }
+
+    .table-container {
+      ::v-deep .el-table {
+        .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+            font-size: 14px;
+          }
+        }
+
+        .el-table__body {
+          .data-value {
+            font-weight: 600;
+            color: #409eff;
+            font-size: 14px;
+          }
+        }
+      }
+    }
+  }
 }
 
-.el-tree .el-tree-node:not(.is-leaf) > .el-tree-node__content {
-  color: #909399;
-  cursor: not-allowed;
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+      }
+
+      .search-container {
+        ::v-deep .el-form {
+          .el-form-item {
+            display: block;
+            margin-bottom: 10px;
+          }
+        }
+      }
+
+      .stats-cards {
+        .el-col {
+          margin-bottom: 10px;
+        }
+      }
+    }
+  }
 }
 </style>

+ 404 - 148
ems-ui-cloud/src/views/analysis/report/statement-warn.vue

@@ -1,106 +1,124 @@
 <template>
   <div class="app-container">
-    <el-row :gutter="10">
-      <el-col :span="4" :xs="24">
+    <el-row :gutter="20">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px"/>
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                   :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                   @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;"/>
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-label">
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.facsCategory === 'E'"
+                size="mini"
+                effect="plain"
+                class="tree-tag"
+              >
+                光伏
+              </el-tag>
+            </span>
+          </el-tree>
         </div>
       </el-col>
-      <el-col :span="20" :xs="24">
-        <el-card>
-          <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
-                   label-width="68px">
-            <!--            <el-form-item label="区域名称" prop="areaCode">-->
-            <!--              <el-input-->
-            <!--                v-model="queryParams.areaName"-->
-            <!--                placeholder="请输入区域名称"-->
-            <!--                clearable-->
-            <!--                @keyup.enter.native="handleQuery"-->
-            <!--              />-->
-            <!--            </el-form-item>-->
-            <el-form-item label="日期" prop="date">
-              <el-date-picker clearable
-                              v-model="queryParams.date"
-                              type="date"
-                              value-format="yyyy-MM-dd"
-                              placeholder="请选择日期">
-              </el-date-picker>
-            </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-form>
-
-<!--          <el-row :gutter="10" class="mb8">-->
-<!--            &lt;!&ndash;      <el-col :span="1.5">&ndash;&gt;-->
-<!--            &lt;!&ndash;        <el-button&ndash;&gt;-->
-<!--            &lt;!&ndash;          type="primary"&ndash;&gt;-->
-<!--            &lt;!&ndash;          plain&ndash;&gt;-->
-<!--            &lt;!&ndash;          icon="el-icon-plus"&ndash;&gt;-->
-<!--            &lt;!&ndash;          size="mini"&ndash;&gt;-->
-<!--            &lt;!&ndash;          @click="handleAdd"&ndash;&gt;-->
-<!--            &lt;!&ndash;          v-hasPermi="['ems:EmsEcoD:add']"&ndash;&gt;-->
-<!--            &lt;!&ndash;        >新增</el-button>&ndash;&gt;-->
-<!--            &lt;!&ndash;      </el-col>&ndash;&gt;-->
-<!--            &lt;!&ndash;      <el-col :span="1.5">&ndash;&gt;-->
-<!--            &lt;!&ndash;        <el-button&ndash;&gt;-->
-<!--            &lt;!&ndash;          type="success"&ndash;&gt;-->
-<!--            &lt;!&ndash;          plain&ndash;&gt;-->
-<!--            &lt;!&ndash;          icon="el-icon-edit"&ndash;&gt;-->
-<!--            &lt;!&ndash;          size="mini"&ndash;&gt;-->
-<!--            &lt;!&ndash;          :disabled="single"&ndash;&gt;-->
-<!--            &lt;!&ndash;          @click="handleUpdate"&ndash;&gt;-->
-<!--            &lt;!&ndash;          v-hasPermi="['ems:EmsEcoD:edit']"&ndash;&gt;-->
-<!--            &lt;!&ndash;        >修改</el-button>&ndash;&gt;-->
-<!--            &lt;!&ndash;      </el-col>&ndash;&gt;-->
-<!--            &lt;!&ndash;      <el-col :span="1.5">&ndash;&gt;-->
-<!--            &lt;!&ndash;        <el-button&ndash;&gt;-->
-<!--            &lt;!&ndash;          type="danger"&ndash;&gt;-->
-<!--            &lt;!&ndash;          plain&ndash;&gt;-->
-<!--            &lt;!&ndash;          icon="el-icon-delete"&ndash;&gt;-->
-<!--            &lt;!&ndash;          size="mini"&ndash;&gt;-->
-<!--            &lt;!&ndash;          :disabled="multiple"&ndash;&gt;-->
-<!--            &lt;!&ndash;          @click="handleDelete"&ndash;&gt;-->
-<!--            &lt;!&ndash;          v-hasPermi="['ems:EmsEcoD:remove']"&ndash;&gt;-->
-<!--            &lt;!&ndash;        >删除</el-button>&ndash;&gt;-->
-<!--            &lt;!&ndash;      </el-col>&ndash;&gt;-->
-
-<!--            <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>-->
-<!--          </el-row>-->
-
-          <el-table v-loading="loading" :data="pvAlarmHList">
-
-            <el-table-column label="时间" align="center" prop="time">
-              <template slot-scope="scope">
-                <span>{{ scope.row.alarmTime }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column label="区域名称" align="center" prop="areaName"/>
-            <el-table-column label="子系统名称" align="center" prop="subSystemName"/>
-            <el-table-column label="告警内容" align="center" prop="alarmMsg"/>
-          </el-table>
-          <pagination
-            v-show="total>0"
-            :total="total"
-            :page.sync="queryParams.pageNum"
-            :limit.sync="queryParams.pageSize"
-            @pagination="getList"
-          />
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <!-- 标题区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <h3 class="page-title">
+                <i class="el-icon-warning-outline"></i>
+                告警信息【{{ selectedLabel }}】
+              </h3>
+            </div>
+          </div>
+
+          <!-- 查询条件区域 -->
+          <div class="search-container">
+            <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+              <el-form-item label="日期" prop="date">
+                <el-date-picker
+                  clearable
+                  v-model="queryParams.date"
+                  type="date"
+                  value-format="yyyy-MM-dd"
+                  placeholder="请选择日期"
+                >
+                </el-date-picker>
+              </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-form>
+          </div>
+
+          <!-- 数据表格 -->
+          <div class="table-container">
+            <el-table v-loading="loading" :data="pvAlarmHList" stripe>
+              <el-table-column label="时间" align="center" prop="time">
+                <template slot-scope="scope">
+                  <span class="time-text">{{ scope.row.alarmTime }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="区域名称" align="center" prop="areaName">
+                <template slot-scope="scope">
+                  <el-tag type="info" size="mini">{{ scope.row.areaName }}</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="子系统名称" align="center" prop="subSystemName">
+                <template slot-scope="scope">
+                  <span class="system-text">{{ scope.row.subSystemName }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="告警内容" align="center" prop="alarmMsg">
+                <template slot-scope="scope">
+                  <span class="alarm-msg">{{ scope.row.alarmMsg }}</span>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <pagination
+              v-show="total > 0"
+              :total="total"
+              :page.sync="queryParams.pageNum"
+              :limit.sync="queryParams.pageSize"
+              @pagination="getList"
+            />
+          </div>
 
           <!-- 添加或修改节能计量日对话框 -->
           <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
@@ -109,11 +127,13 @@
                 <el-input v-model="form.areaCode" placeholder="请输入园区代码"/>
               </el-form-item>
               <el-form-item label="日期" prop="date">
-                <el-date-picker clearable
-                                v-model="form.date"
-                                type="date"
-                                value-format="yyyy-MM-dd"
-                                placeholder="请选择日期">
+                <el-date-picker
+                  clearable
+                  v-model="form.date"
+                  type="date"
+                  value-format="yyyy-MM-dd"
+                  placeholder="请选择日期"
+                >
                 </el-date-picker>
               </el-form-item>
               <el-form-item label="节电(千瓦时)" prop="elecEcoQuantity">
@@ -123,7 +143,7 @@
                 <el-input v-model="form.elecEcoCost" placeholder="请输入节电金额"/>
               </el-form-item>
               <el-form-item label="节水量 (吨)" prop="waterEcoQuantity">
-                <el-input v-model="form.waterEcoQuantity" placeholder="请输入节水量 "/>
+                <el-input v-model="form.waterEcoQuantity" placeholder="请输入节水量"/>
               </el-form-item>
               <el-form-item label="节水金额(元)" prop="waterEcoCost">
                 <el-input v-model="form.waterEcoCost" placeholder="请输入节水金额"/>
@@ -134,19 +154,19 @@
               <el-button @click="cancel">取 消</el-button>
             </div>
           </el-dialog>
-        </el-card>
+        </div>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-import {addEmsEcoD, delEmsEcoD, getEmsEcoD, updateEmsEcoD} from "@/api/ems/EmsEcoD";
-import {listAlarmInfo} from "@/api/alarm/alarm-info";
-import {areaTreeByFacsCategory} from "@/api/basecfg/area";
+import { addEmsEcoD, delEmsEcoD, getEmsEcoD, updateEmsEcoD } from "@/api/ems/EmsEcoD"
+import { listAlarmInfo } from "@/api/alarm/alarm-info"
+import { areaTreeByFacsCategory } from "@/api/basecfg/area"
 
 export default {
-  name: "EmsEcoD",
+  name: "AlarmList",
   data() {
     return {
       areaName: undefined,
@@ -154,6 +174,8 @@ export default {
         children: "children",
         label: "label"
       },
+      defaultExpandedKeys: [],
+      selectedLabel: '全部',
       // 表单参数
       areaOptions: [],
       facsCategory: 'E',
@@ -189,17 +211,17 @@ export default {
       // 表单校验
       rules: {
         areaCode: [
-          {required: true, message: "园区代码不能为空", trigger: "blur"}
+          { required: true, message: "园区代码不能为空", trigger: "blur" }
         ],
         alarmDate: [
-          {required: true, message: "日期不能为空", trigger: "blur"}
+          { required: true, message: "日期不能为空", trigger: "blur" }
         ],
       }
-    };
+    }
   },
   async created() {
     await this.getAreaList()
-    this.getList();
+    this.getList()
   },
   watch: {
     // 根据名称筛选区域树
@@ -214,115 +236,349 @@ export default {
         this.areaOptions = [{
           id: '-1',
           label: '全部',
-          children: []
-        }].concat(response.data)
+          children: response.data || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+
         this.selectedLabel = '全部'
         this.queryParams.areaCode = '-1'
       })
     },
+
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.facsCategory === 'E') {
+        return 'el-icon-sunny'
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     /** 查询节能计量日列表 */
     getList() {
-      this.loading = true;
+      this.loading = true
       listAlarmInfo(this.queryParams).then(response => {
         this.pvAlarmHList = response.rows
         this.total = response.total
         this.loading = false
       })
     },
+
     // 取消按钮
     cancel() {
-      this.open = false;
-      this.reset();
+      this.open = false
+      this.reset()
     },
+
     // 表单重置
     reset() {
       this.form = {
         id: null,
         areaCode: null,
         date: null
-      };
-      this.resetForm("form");
+      }
+      this.resetForm("form")
     },
+
     /** 搜索按钮操作 */
     handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
+      this.queryParams.pageNum = 1
+      this.getList()
     },
+
     /** 重置按钮操作 */
     resetQuery() {
-      this.resetForm("queryForm");
-      this.handleQuery();
+      this.resetForm("queryForm")
+      this.handleQuery()
     },
+
     // 多选框选中数据
     handleSelectionChange(selection) {
       this.ids = selection.map(item => item.id)
       this.single = selection.length !== 1
       this.multiple = !selection.length
     },
+
     /** 新增按钮操作 */
     handleAdd() {
-      this.reset();
-      this.open = true;
-      this.title = "添加节能计量日";
+      this.reset()
+      this.open = true
+      this.title = "添加节能计量日"
     },
+
     /** 修改按钮操作 */
     handleUpdate(row) {
-      this.reset();
+      this.reset()
       const id = row.id || this.ids
       getEmsEcoD(id).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = "修改节能计量日";
-      });
+        this.form = response.data
+        this.open = true
+        this.title = "修改节能计量日"
+      })
     },
+
     /** 提交按钮 */
     submitForm() {
       this.$refs["form"].validate(valid => {
         if (valid) {
           if (this.form.id != null) {
             updateEmsEcoD(this.form).then(response => {
-              this.$modal.msgSuccess("修改成功");
-              this.open = false;
-              this.getList();
-            });
+              this.$modal.msgSuccess("修改成功")
+              this.open = false
+              this.getList()
+            })
           } else {
             addEmsEcoD(this.form).then(response => {
-              this.$modal.msgSuccess("新增成功");
-              this.open = false;
-              this.getList();
-            });
+              this.$modal.msgSuccess("新增成功")
+              this.open = false
+              this.getList()
+            })
           }
         }
-      });
+      })
     },
+
     /** 删除按钮操作 */
     handleDelete(row) {
-      const ids = row.id || this.ids;
+      const ids = row.id || this.ids
       this.$modal.confirm('是否确认删除节能计量日编号为"' + ids + '"的数据项?').then(function () {
-        return delEmsEcoD(ids);
+        return delEmsEcoD(ids)
       }).then(() => {
-        this.getList();
-        this.$modal.msgSuccess("删除成功");
-      }).catch(() => {
-      });
+        this.getList()
+        this.$modal.msgSuccess("删除成功")
+      }).catch(() => {})
     },
+
     /** 导出按钮操作 */
     handleExport() {
       this.download('ems/alarm-info/export', {
         ...this.queryParams
       }, `alarm_${new Date().getTime()}.xlsx`)
     },
+
     // 筛选节点
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
     // 节点单击事件
     handleNodeClick(data) {
       this.queryParams.areaCode = data.id
       this.selectedLabel = data.label
       this.getList()
-    },
+    }
   }
-};
+}
 </script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+            }
+          }
+
+          .tree-tag {
+            margin-right: 8px;
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 2px solid #f0f2f5;
+
+      .page-title {
+        margin: 0;
+        font-size: 20px;
+        font-weight: 600;
+        color: #303133;
+
+        i {
+          margin-right: 8px;
+          color: #e6a23c;
+        }
+      }
+    }
+
+    .search-container {
+      background: #f9fbfd;
+      border-radius: 8px;
+      padding: 15px;
+      margin-bottom: 20px;
+
+      ::v-deep .el-form {
+        .el-form-item {
+          margin-bottom: 0;
+          margin-right: 15px;
+
+          .el-form-item__label {
+            font-weight: 500;
+            color: #606266;
+          }
+        }
+
+        .el-button {
+          padding: 7px 15px;
+        }
+      }
+    }
+
+    .table-container {
+      ::v-deep .el-table {
+        .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+            font-size: 14px;
+          }
+        }
+
+        .el-table__body {
+          .time-text {
+            font-weight: 500;
+            color: #606266;
+          }
+
+          .system-text {
+            color: #409eff;
+            font-weight: 500;
+          }
+
+          .alarm-msg {
+            color: #e6a23c;
+            font-weight: 500;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+      }
+
+      .search-container {
+        ::v-deep .el-form {
+          .el-form-item {
+            display: block;
+            margin-bottom: 10px;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 763 - 152
ems-ui-cloud/src/views/ca/emission.vue

@@ -1,72 +1,213 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
-      <el-col :span="4" :xs="24">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px"/>
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                   :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                   @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;"/>
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-label">
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.facsCategory === 'Z'"
+                size="mini"
+                effect="plain"
+                class="tree-tag carbon"
+              >
+                碳排
+              </el-tag>
+            </span>
+          </el-tree>
         </div>
       </el-col>
-      <el-col :span="20" :xs="24">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
-                 label-width="68px">
-          <el-form-item label="开始日期" prop="date">
-            <el-date-picker clearable
-                            v-model="queryParams.startRecTime"
-                            type="date"
-                            value-format="yyyy-MM-dd"
-                            placeholder="请选择日期">
-            </el-date-picker>
-          </el-form-item>
-          <el-form-item label="结束日期" prop="date">
-            <el-date-picker clearable
-                            v-model="queryParams.endRecTime"
-                            type="date"
-                            value-format="yyyy-MM-dd"
-                            placeholder="请选择日期">
-            </el-date-picker>
-          </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-form-item>
-        </el-form>
-
-        <el-table v-loading="loading" :data="caMeterDList">
-          <el-table-column label="位置" align="center" prop="areaName"/>
-          <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="碳排放量(kg)" align="center" prop="caEmissionQuantity">
-            <template slot-scope="scope">
-              <span>{{ scope.row.caEmissionQuantity }} {{ calcTrend(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="getList"
-        />
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <!-- 统计卡片区域 -->
+        <div class="statistic-cards">
+          <el-row :gutter="15">
+            <el-col :span="8" :xs="24">
+              <div class="stat-card total">
+                <div class="card-icon">
+                  <i class="el-icon-data-analysis"></i>
+                </div>
+                <div class="card-content">
+                  <div class="stat-label">当月累计排放</div>
+                  <div class="stat-value">
+                    <span class="number">{{ monthTotal || 0 }}</span>
+                    <span class="unit">kg</span>
+                  </div>
+                </div>
+              </div>
+            </el-col>
+            <el-col :span="8" :xs="24">
+              <div class="stat-card average">
+                <div class="card-icon">
+                  <i class="el-icon-s-marketing"></i>
+                </div>
+                <div class="card-content">
+                  <div class="stat-label">日均排放量</div>
+                  <div class="stat-value">
+                    <span class="number">{{ dailyAverage || 0 }}</span>
+                    <span class="unit">kg</span>
+                  </div>
+                </div>
+              </div>
+            </el-col>
+            <el-col :span="8" :xs="24">
+              <div class="stat-card trend">
+                <div class="card-icon">
+                  <i class="el-icon-s-data"></i>
+                </div>
+                <div class="card-content">
+                  <div class="stat-label">排放趋势</div>
+                  <div class="stat-value">
+                    <span :class="getTrendClass()">
+                      <i :class="getTrendIcon()"></i>
+                      <span class="number">{{ Math.abs(trendPercent) || 0 }}</span>
+                      <span class="unit">%</span>
+                    </span>
+                  </div>
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 数据表格区域 -->
+        <div class="content-wrapper">
+          <!-- 标题和搜索区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <h3 class="section-title">碳排放明细</h3>
+            </div>
+            <div class="header-right">
+              <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
+                <el-form-item>
+                  <el-date-picker
+                    v-model="dateRange"
+                    type="daterange"
+                    value-format="yyyy-MM-dd"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    :picker-options="pickerOptions"
+                    @change="handleDateChange"
+                  />
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
+                  <el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
+                </el-form-item>
+              </el-form>
+            </div>
+          </div>
+
+          <!-- 数据表格 -->
+          <div class="table-container">
+            <el-table
+              v-loading="loading"
+              :data="caMeterDList"
+              class="data-table"
+              :height="tableHeight"
+            >
+              <el-table-column label="序号" type="index" width="60" align="center" />
+              <el-table-column label="位置" align="center" prop="areaName" min-width="200">
+                <template slot-scope="scope">
+                  <div class="area-info">
+                    <i class="el-icon-location-outline"></i>
+                    <span class="area-name">{{ scope.row.areaName }}</span>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="日期" align="center" prop="date" width="120">
+                <template slot-scope="scope">
+                  <span class="date-text">{{ parseTime(scope.row.date, '{y}-{m}-{d}') }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="碳排放量" align="center" min-width="180">
+                <template slot-scope="scope">
+                  <div class="emission-value">
+                    <span class="value">{{ (scope.row.caEmissionQuantity || 0).toFixed(2) }}</span>
+                    <span class="unit">kg</span>
+                    <el-tag
+                      v-if="calcTrend(scope.row)"
+                      :type="getTrendType(scope.row)"
+                      size="mini"
+                      class="trend-tag"
+                    >
+                      {{ calcTrend(scope.row) }}
+                    </el-tag>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="对比平均值" align="center" min-width="150">
+                <template slot-scope="scope">
+                  <div class="compare-value">
+                    <el-progress
+                      :percentage="getComparePercent(scope.row)"
+                      :color="getProgressColor(scope.row)"
+                      :stroke-width="6"
+                      :show-text="false"
+                    />
+                    <span class="percent-text" :style="{color: getProgressColor(scope.row)}">
+                      {{ getCompareText(scope.row) }}
+                    </span>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="状态评价" align="center" width="100">
+                <template slot-scope="scope">
+                  <el-tag :type="getStatusType(scope.row)" effect="plain" size="small">
+                    {{ getStatusText(scope.row) }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <!-- 分页 -->
+            <pagination
+              v-show="total > 0"
+              :total="total"
+              :page.sync="queryParams.pageNum"
+              :limit.sync="queryParams.pageSize"
+              @pagination="getList"
+              class="pagination-container"
+            />
+          </div>
+        </div>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-import {listCaMeterD, qryAvgCaMeterD} from "@/api/ca/caMeterD";
-import {areaTreeByFacsCategory} from '@/api/basecfg/area'
-import {array2Map} from "@/utils";
+import { listCaMeterD, qryAvgCaMeterD } from "@/api/ca/caMeterD"
+import { areaTreeByFacsCategory } from '@/api/basecfg/area'
+import { array2Map } from "@/utils"
 
 export default {
   name: "CaMeterD",
@@ -74,149 +215,619 @@ export default {
     return {
       // 遮罩层
       loading: true,
-      // 选中数组
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
       // 显示搜索条件
       showSearch: true,
       // 总条数
       total: 0,
       // 碳计量日表格数据
       caMeterDList: [],
-      // 弹出层标题
-      title: "",
-      // 是否显示弹出层
-      open: false,
+      // 表格高度
+      tableHeight: 500,
+      // 树形结构相关
       areaOptions: [],
-      areaName: undefined,
+      areaName: '',
       facsCategory: 'Z',
       facsSubCategory: '',
       defaultProps: {
         children: "children",
         label: "label"
       },
+      defaultExpandedKeys: [],
+      selectedLabel: '全部',
+      // 统计数据
+      monthTotal: 0,
+      dailyAverage: 0,
+      trendPercent: 0,
       areaAvgMap: {},
+      // 日期范围
+      dateRange: [],
+      pickerOptions: {
+        shortcuts: [{
+          text: '本月',
+          onClick(picker) {
+            const start = new Date()
+            start.setDate(1)
+            const end = new Date()
+            picker.$emit('pick', [start, end])
+          }
+        }, {
+          text: '上月',
+          onClick(picker) {
+            const start = new Date()
+            start.setMonth(start.getMonth() - 1)
+            start.setDate(1)
+            const end = new Date(start.getFullYear(), start.getMonth() + 1, 0)
+            picker.$emit('pick', [start, end])
+          }
+        }, {
+          text: '最近30天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        }]
+      },
       // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        areaCode: null,
+        areaCode: '-1',
         startRecTime: null,
-        endRecTime: null,
-      },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        areaCode: [
-          {required: true, message: "园区代码不能为空", trigger: "blur"}
-        ],
-        date: [
-          {required: true, message: "日期不能为空", trigger: "blur"}
-        ],
-      }
-    };
+        endRecTime: null
+      }
+    }
+  },
+  async created() {
+    this.initDateRange()
+    await this.getAreaList()
+    this.getList()
+    this.calculateTableHeight()
+    window.addEventListener('resize', this.calculateTableHeight)
   },
-  created() {
-    this.initDateRange();
-    this.getAreaList();
-    this.getList();
+  destroyed() {
+    window.removeEventListener('resize', this.calculateTableHeight)
   },
   methods: {
-    /** 查询碳计量日列表 */
+    // 计算表格高度
+    calculateTableHeight() {
+      this.$nextTick(() => {
+        const windowHeight = window.innerHeight
+        const tableOffset = 420
+        this.tableHeight = windowHeight - tableOffset
+      })
+    },
+
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.facsCategory === 'Z') {
+        return 'el-icon-s-opportunity'
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 初始化日期范围
+    initDateRange() {
+      const today = new Date()
+      const yesterday = new Date(today)
+      yesterday.setDate(yesterday.getDate() - 1)
+
+      const firstDayOfMonth = new Date(yesterday.getFullYear(), yesterday.getMonth(), 1)
+
+      this.dateRange = [this.formatDate(firstDayOfMonth), this.formatDate(yesterday)]
+      this.queryParams.startRecTime = this.dateRange[0]
+      this.queryParams.endRecTime = this.dateRange[1]
+    },
+
+    // 格式化日期
+    formatDate(date) {
+      const year = date.getFullYear()
+      const month = String(date.getMonth() + 1).padStart(2, '0')
+      const day = String(date.getDate()).padStart(2, '0')
+      return `${year}-${month}-${day}`
+    },
+
+    // 查询区域列表
+    async getAreaList() {
+      try {
+        const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
+        this.areaOptions = [{
+          id: '-1',
+          label: '全部',
+          children: response.data || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      } catch (error) {
+        console.error('加载区域树失败', error)
+        this.$message.error('加载区域树失败')
+      }
+    },
+
+    // 查询碳计量日列表
     async getList() {
-      this.loading = true;
-      const {data} = await qryAvgCaMeterD()
-      this.areaAvgMap = array2Map(data, 'areaCode')
-      const {rows, total} = await listCaMeterD(this.queryParams)
-      this.caMeterDList = rows;
-      this.total = total;
-      this.loading = false;
+      this.loading = true
+      try {
+        const { data } = await qryAvgCaMeterD()
+        this.areaAvgMap = array2Map(data, 'areaCode')
+
+        const { rows, total } = await listCaMeterD(this.queryParams)
+        this.caMeterDList = rows || []
+        this.total = total || 0
+
+        // 计算统计数据
+        this.calculateStatistics()
+      } catch (error) {
+        console.error('加载数据失败', error)
+        this.$message.error('数据加载失败')
+      } finally {
+        this.loading = false
+      }
+    },
+
+    // 计算统计数据
+    calculateStatistics() {
+      if (this.caMeterDList.length > 0) {
+        // 计算当月累计
+        this.monthTotal = this.caMeterDList.reduce((sum, item) => {
+          return sum + (item.caEmissionQuantity || 0)
+        }, 0).toFixed(2)
+
+        // 计算日均
+        this.dailyAverage = (this.monthTotal / this.caMeterDList.length).toFixed(2)
+
+        // 计算趋势(对比上月同期)
+        // 这里简化处理,实际应该对比上月同期数据
+        const avgValue = Object.values(this.areaAvgMap)[0]?.caEmissionQuantity || 0
+        if (avgValue > 0) {
+          this.trendPercent = (((this.dailyAverage - avgValue) / avgValue) * 100).toFixed(1)
+        }
+      }
     },
+
+    // 计算趋势
     calcTrend(row) {
-      const {areaCode, caEmissionQuantity} = row
+      const { areaCode, caEmissionQuantity } = row
       if (!this.areaAvgMap[areaCode] || !this.areaAvgMap[areaCode].caEmissionQuantity || !caEmissionQuantity) {
         return ''
       }
-      if (caEmissionQuantity > this.areaAvgMap[areaCode].caEmissionQuantity) {
-        return "↑"
+      const avg = this.areaAvgMap[areaCode].caEmissionQuantity
+      if (caEmissionQuantity > avg * 1.1) {
+        return '↑高'
+      }
+      if (caEmissionQuantity > avg) {
+        return '↑'
+      }
+      if (caEmissionQuantity < avg * 0.9) {
+        return '↓低'
       }
-      if (caEmissionQuantity < this.areaAvgMap[areaCode].caEmissionQuantity) {
-        return "↓"
+      if (caEmissionQuantity < avg) {
+        return '↓'
       }
-      return "-"
+      return '→'
     },
-    // 取消按钮
-    cancel() {
-      this.open = false;
-      this.reset();
+
+    // 获取趋势类型
+    getTrendType(row) {
+      const trend = this.calcTrend(row)
+      if (trend.includes('↑')) return 'danger'
+      if (trend.includes('↓')) return 'success'
+      return 'info'
     },
-    // 表单重置
-    reset() {
-      this.form = {
-        id: null,
-        areaCode: null,
-        date: null,
-        caEmissionQuantity: null,
-        caSinkQuantity: null
-      };
-      this.resetForm("form");
+
+    // 获取对比百分比
+    getComparePercent(row) {
+      const { areaCode, caEmissionQuantity } = row
+      if (!this.areaAvgMap[areaCode] || !this.areaAvgMap[areaCode].caEmissionQuantity) {
+        return 0
+      }
+      const avg = this.areaAvgMap[areaCode].caEmissionQuantity
+      return Math.min(100, (caEmissionQuantity / avg * 100).toFixed(0))
     },
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
+
+    // 获取对比文本
+    getCompareText(row) {
+      const percent = this.getComparePercent(row)
+      if (percent > 100) return `+${(percent - 100).toFixed(0)}%`
+      if (percent < 100) return `-${(100 - percent).toFixed(0)}%`
+      return '持平'
     },
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm("queryForm");
-      this.handleQuery();
+
+    // 获取进度条颜色
+    getProgressColor(row) {
+      const percent = this.getComparePercent(row)
+      if (percent > 110) return '#f56c6c'
+      if (percent > 100) return '#e6a23c'
+      if (percent < 90) return '#67c23a'
+      return '#409eff'
     },
-    // 查询区域列表
-    async getAreaList() {
-      await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false).then(response => {
-        this.areaOptions = [{
-          id: '-1',
-          label: '全部',
-          children: []
-        }].concat(response.data)
-        this.selectedLabel = '全部'
-        this.queryParams.areaCode = '-1'
-      })
+
+    // 获取状态类型
+    getStatusType(row) {
+      const percent = this.getComparePercent(row)
+      if (percent > 110) return 'danger'
+      if (percent < 90) return 'success'
+      return 'info'
     },
-    initDateRange() {
-      // 获取当前日期
-      const today = new Date();
-      const yesterday = new Date(today);
-      yesterday.setDate(yesterday.getDate() - 1);
 
-      // 设置开始日期为当月第一天
-      const firstDayOfMonth = new Date(yesterday.getFullYear(), yesterday.getMonth(), 1);
-      this.queryParams.startRecTime = this.formatDate(firstDayOfMonth);
+    // 获取状态文本
+    getStatusText(row) {
+      const percent = this.getComparePercent(row)
+      if (percent > 110) return '偏高'
+      if (percent < 90) return '良好'
+      return '正常'
+    },
 
-      // 设置结束日期为昨天
-      this.queryParams.endRecTime = this.formatDate(yesterday);
+    // 获取趋势样式
+    getTrendClass() {
+      if (this.trendPercent > 0) return 'trend-up'
+      if (this.trendPercent < 0) return 'trend-down'
+      return 'trend-stable'
     },
-    formatDate(date) {
-      // 格式化日期为 yyyy-MM-dd 格式
-      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}`
+
+    // 获取趋势图标
+    getTrendIcon() {
+      if (this.trendPercent > 0) return 'el-icon-top'
+      if (this.trendPercent < 0) return 'el-icon-bottom'
+      return 'el-icon-minus'
+    },
+
+    // 处理日期范围变化
+    handleDateChange(val) {
+      if (val && val.length === 2) {
+        this.queryParams.startRecTime = val[0]
+        this.queryParams.endRecTime = val[1]
+      }
     },
+
+    // 搜索按钮操作
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+
+    // 重置按钮操作
+    resetQuery() {
+      this.initDateRange()
+      this.handleQuery()
+    },
+
     // 筛选节点
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     // 节点单击事件
     handleNodeClick(data) {
       this.queryParams.areaCode = data.id
-      this.getList()
-    },
+      this.selectedLabel = data.label
+      this.getList()  // 这会同时获取分页数据和全部统计数据
+    }
   }
-};
+}
 </script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  // 左侧树形结构
+  .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;
+      }
+
+      ::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;
+            }
+          }
+
+          .tree-tag.carbon {
+            background: linear-gradient(135deg, #909399 0%, #606266 100%);
+            color: #fff;
+            border: none;
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
+
+  // 统计卡片
+  .statistic-cards {
+    margin-bottom: 20px;
+
+    .stat-card {
+      background: #fff;
+      border-radius: 8px;
+      padding: 20px;
+      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+      display: flex;
+      align-items: center;
+      transition: all 0.3s;
+      height: 100px;
+
+      &:hover {
+        transform: translateY(-3px);
+        box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.12);
+      }
+
+      .card-icon {
+        width: 60px;
+        height: 60px;
+        border-radius: 12px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 28px;
+        margin-right: 20px;
+      }
+
+      &.total {
+        .card-icon {
+          background: linear-gradient(135deg, #fef3e7 0%, #ffd4a3 100%);
+          color: #e6a23c;
+        }
+      }
+
+      &.average {
+        .card-icon {
+          background: linear-gradient(135deg, #e7f3ff 0%, #b3d8ff 100%);
+          color: #409eff;
+        }
+      }
+
+      &.trend {
+        .card-icon {
+          background: linear-gradient(135deg, #f0f9ff 0%, #c6f0ff 100%);
+          color: #00a0e9;
+        }
+      }
+
+      .card-content {
+        flex: 1;
+
+        .stat-label {
+          font-size: 14px;
+          color: #909399;
+          margin-bottom: 8px;
+        }
+
+        .stat-value {
+          display: flex;
+          align-items: baseline;
+          gap: 4px;
+
+          .number {
+            font-size: 28px;
+            font-weight: 600;
+            color: #303133;
+          }
+
+          .unit {
+            font-size: 14px;
+            color: #909399;
+          }
+
+          &.trend-up {
+            color: #f56c6c;
+          }
+
+          &.trend-down {
+            color: #67c23a;
+          }
+
+          &.trend-stable {
+            color: #409eff;
+          }
+        }
+      }
+    }
+  }
+
+  // 内容区域
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 1px solid #ebeef5;
+
+      .section-title {
+        font-size: 18px;
+        font-weight: 600;
+        color: #303133;
+        margin: 0;
+      }
+
+      ::v-deep .el-form-item {
+        margin-bottom: 0;
+      }
+    }
+
+    .table-container {
+      .data-table {
+        ::v-deep .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+          }
+        }
+
+        ::v-deep .el-table__body {
+          .area-info {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 6px;
+
+            i {
+              color: #909399;
+            }
+
+            .area-name {
+              font-weight: 500;
+              color: #303133;
+            }
+          }
+
+          .date-text {
+            color: #606266;
+          }
+
+          .emission-value {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 6px;
+
+            .value {
+              font-size: 16px;
+              font-weight: 600;
+              color: #303133;
+            }
+
+            .unit {
+              font-size: 12px;
+              color: #909399;
+            }
+
+            .trend-tag {
+              margin-left: 8px;
+            }
+          }
+
+          .compare-value {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            gap: 8px;
+
+            ::v-deep .el-progress {
+              width: 100px;
+            }
+
+            .percent-text {
+              font-size: 12px;
+              font-weight: 500;
+            }
+          }
+        }
+      }
+
+      .pagination-container {
+        margin-top: 20px;
+        display: flex;
+        justify-content: flex-end;
+      }
+    }
+  }
+}
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .statistic-cards {
+      .el-col {
+        margin-bottom: 15px;
+      }
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+
+        .section-title {
+          margin-bottom: 15px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 714 - 138
ems-ui-cloud/src/views/ca/emissionCalc.vue

@@ -1,65 +1,199 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
-      <el-col :span="4" :xs="24">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px" />
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                   :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                   @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;" />
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-label">
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.facsCategory === 'Z'"
+                size="mini"
+                effect="plain"
+                class="tree-tag sink"
+              >
+                碳汇
+              </el-tag>
+            </span>
+          </el-tree>
         </div>
       </el-col>
-      <el-col :span="20" :xs="24">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
-          <el-form-item label="开始日期" prop="date">
-            <el-date-picker clearable
-                            v-model="queryParams.startRecTime"
-                            type="date"
-                            value-format="yyyy-MM-dd"
-                            placeholder="请选择日期">
-            </el-date-picker>
-          </el-form-item>
-          <el-form-item label="结束日期" prop="date">
-            <el-date-picker clearable
-                            v-model="queryParams.endRecTime"
-                            type="date"
-                            value-format="yyyy-MM-dd"
-                            placeholder="请选择日期">
-            </el-date-picker>
-          </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-form-item>
-        </el-form>
-
-        <el-table v-loading="loading" :data="caMeterDList">
-          <el-table-column label="位置" align="center" prop="areaName"/>
-          <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="碳汇量(kg)" align="center" prop="caSinkQuantity"/>
-        </el-table>
-
-        <pagination
-          v-show="total>0"
-          :total="total"
-          :page.sync="queryParams.pageNum"
-          :limit.sync="queryParams.pageSize"
-          @pagination="getList"
-        />
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <!-- 统计卡片区域 -->
+        <div class="statistic-cards">
+          <el-row :gutter="15">
+            <el-col :span="8" :xs="24">
+              <div class="stat-card total">
+                <div class="card-icon">
+                  <i class="el-icon-s-data"></i>
+                </div>
+                <div class="card-content">
+                  <div class="stat-label">当月累计碳汇</div>
+                  <div class="stat-value">
+                    <span class="number">{{ monthTotalSink || 0 }}</span>
+                    <span class="unit">kg</span>
+                  </div>
+                </div>
+              </div>
+            </el-col>
+            <el-col :span="8" :xs="24">
+              <div class="stat-card average">
+                <div class="card-icon">
+                  <i class="el-icon-s-marketing"></i>
+                </div>
+                <div class="card-content">
+                  <div class="stat-label">日均碳汇量</div>
+                  <div class="stat-value">
+                    <span class="number">{{ dailyAverageSink || 0 }}</span>
+                    <span class="unit">kg</span>
+                  </div>
+                </div>
+              </div>
+            </el-col>
+            <el-col :span="8" :xs="24">
+              <div class="stat-card efficiency">
+                <div class="card-icon">
+                  <i class="el-icon-s-flag"></i>
+                </div>
+                <div class="card-content">
+                  <div class="stat-label">碳汇效率</div>
+                  <div class="stat-value">
+                    <span class="number">{{ sinkEfficiency || 0 }}</span>
+                    <span class="unit">%</span>
+                  </div>
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 数据表格区域 -->
+        <div class="content-wrapper">
+          <!-- 标题和搜索区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <h3 class="section-title">碳汇明细</h3>
+            </div>
+            <div class="header-right">
+              <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
+                <el-form-item>
+                  <el-date-picker
+                    v-model="dateRange"
+                    type="daterange"
+                    value-format="yyyy-MM-dd"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    :picker-options="pickerOptions"
+                    @change="handleDateChange"
+                  />
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
+                  <el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
+                </el-form-item>
+              </el-form>
+            </div>
+          </div>
+
+          <!-- 数据表格 -->
+          <div class="table-container">
+            <el-table
+              v-loading="loading"
+              :data="caMeterDList"
+              class="data-table"
+              :height="tableHeight"
+            >
+              <el-table-column label="序号" type="index" width="60" align="center" />
+              <el-table-column label="位置" align="center" prop="areaName" min-width="200">
+                <template slot-scope="scope">
+                  <div class="area-info">
+                    <i class="el-icon-location-outline"></i>
+                    <span class="area-name">{{ scope.row.areaName }}</span>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="日期" align="center" prop="date" width="120">
+                <template slot-scope="scope">
+                  <span class="date-text">{{ parseTime(scope.row.date, '{y}-{m}-{d}') }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="碳汇量" align="center" min-width="180">
+                <template slot-scope="scope">
+                  <div class="sink-value">
+                    <i class="el-icon-s-opportunity value-icon"></i>
+                    <span class="value">{{ (scope.row.caSinkQuantity || 0).toFixed(2) }}</span>
+                    <span class="unit">kg</span>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="碳汇等级" align="center" width="120">
+                <template slot-scope="scope">
+                  <el-tag :type="getSinkLevelType(scope.row.caSinkQuantity)" effect="plain" size="small">
+                    {{ getSinkLevelText(scope.row.caSinkQuantity) }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="贡献度" align="center" min-width="150">
+                <template slot-scope="scope">
+                  <div class="contribution-value">
+                    <el-progress
+                      :percentage="getContributionPercent(scope.row)"
+                      :color="getProgressColor(scope.row)"
+                      :stroke-width="6"
+                      :show-text="false"
+                    />
+                    <span class="percent-text">{{ getContributionPercent(scope.row) }}%</span>
+                  </div>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <!-- 分页 -->
+            <pagination
+              v-show="total > 0"
+              :total="total"
+              :page.sync="queryParams.pageNum"
+              :limit.sync="queryParams.pageSize"
+              @pagination="getList"
+              class="pagination-container"
+            />
+          </div>
+        </div>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-import {  listCaMeterD } from "@/api/ca/caMeterD";
+import { listCaMeterD, listSumCaMeterD } from "@/api/ca/caMeterD"
 import { areaTreeByFacsCategory } from '@/api/basecfg/area'
 
 export default {
@@ -68,134 +202,576 @@ export default {
     return {
       // 遮罩层
       loading: true,
-      // 选中数组
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
       // 显示搜索条件
       showSearch: true,
       // 总条数
       total: 0,
       // 碳计量日表格数据
       caMeterDList: [],
-      // 弹出层标题
-      title: "",
-      // 是否显示弹出层
-      open: false,
+      // 表格高度
+      tableHeight: 500,
+      // 树形结构相关
       areaOptions: [],
-      areaName: undefined,
+      areaName: '',
       facsCategory: 'Z',
       facsSubCategory: '',
       defaultProps: {
         children: "children",
         label: "label"
       },
+      defaultExpandedKeys: [],
+      selectedLabel: '全部',
+      // 统计数据
+      monthTotalSink: 0,
+      dailyAverageSink: 0,
+      sinkEfficiency: 0,
+      maxSinkValue: 0,
+      // 日期范围
+      dateRange: [],
+      pickerOptions: {
+        shortcuts: [{
+          text: '本月',
+          onClick(picker) {
+            const start = new Date()
+            start.setDate(1)
+            const end = new Date()
+            picker.$emit('pick', [start, end])
+          }
+        }, {
+          text: '上月',
+          onClick(picker) {
+            const start = new Date()
+            start.setMonth(start.getMonth() - 1)
+            start.setDate(1)
+            const end = new Date(start.getFullYear(), start.getMonth() + 1, 0)
+            picker.$emit('pick', [start, end])
+          }
+        }, {
+          text: '最近30天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        }]
+      },
       // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        areaCode: null,
+        areaCode: '-1',
         startRecTime: null,
-        endRecTime: null,
-      },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        areaCode: [
-          {required: true, message: "园区代码不能为空", trigger: "blur"}
-        ],
-        date: [
-          {required: true, message: "日期不能为空", trigger: "blur"}
-        ],
-      }
-    };
+        endRecTime: null
+      }
+    }
   },
-  created() {
-    this.initDateRange();
-    this.getAreaList();
-    this.getList();
+  async created() {
+    this.initDateRange()
+    await this.getAreaList()
+    this.getList()
+    this.calculateTableHeight()
+    window.addEventListener('resize', this.calculateTableHeight)
+  },
+  destroyed() {
+    window.removeEventListener('resize', this.calculateTableHeight)
   },
   methods: {
-    /** 查询碳计量日列表 */
-    getList() {
-      this.loading = true;
-      listCaMeterD(this.queryParams).then(response => {
-        this.caMeterDList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
-    },
-    // 取消按钮
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    // 表单重置
-    reset() {
-      this.form = {
-        id: null,
-        areaCode: null,
-        date: null,
-        caEmissionQuantity: null,
-        caSinkQuantity: null
-      };
-      this.resetForm("form");
-    },
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
-    },
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm("queryForm");
-      this.handleQuery();
-    },
-    // 查询区域列表
-    async getAreaList () {
-      await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false).then(response => {
-        this.areaOptions = [{
-          id: '-1',
-          label: '全部',
-          children: []
-        }].concat(response.data)
-        this.selectedLabel = '全部'
-        this.queryParams.areaCode = '-1'
+    // 计算表格高度
+    calculateTableHeight() {
+      this.$nextTick(() => {
+        const windowHeight = window.innerHeight
+        const tableOffset = 420
+        this.tableHeight = windowHeight - tableOffset
       })
     },
+
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.facsCategory === 'Z') {
+        return 'el-icon-s-promotion'
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 初始化日期范围
     initDateRange() {
-      // 获取当前日期
-      const today = new Date();
-      const yesterday = new Date(today);
-      yesterday.setDate(yesterday.getDate() - 1);
+      const today = new Date()
+      const yesterday = new Date(today)
+      yesterday.setDate(yesterday.getDate() - 1)
 
-      // 设置开始日期为当月第一天
-      const firstDayOfMonth = new Date(yesterday.getFullYear(), yesterday.getMonth(), 1);
-      this.queryParams.startRecTime = this.formatDate(firstDayOfMonth);
+      const firstDayOfMonth = new Date(yesterday.getFullYear(), yesterday.getMonth(), 1)
 
-      // 设置结束日期为昨天
-      this.queryParams.endRecTime = this.formatDate(yesterday);
+      this.dateRange = [this.formatDate(firstDayOfMonth), this.formatDate(yesterday)]
+      this.queryParams.startRecTime = this.dateRange[0]
+      this.queryParams.endRecTime = this.dateRange[1]
     },
+
+    // 格式化日期
     formatDate(date) {
-      // 格式化日期为 yyyy-MM-dd 格式
       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}`
     },
+
+    // 查询区域列表
+    async getAreaList() {
+      try {
+        const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
+        this.areaOptions = [{
+          id: '-1',
+          label: '全部',
+          children: response.data || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      } catch (error) {
+        console.error('加载区域树失败', error)
+        this.$message.error('加载区域树失败')
+      }
+    },
+
+    // 查询碳汇列表
+    async getList() {
+      this.loading = true
+      try {
+        // 获取分页数据用于表格展示
+        const { rows, total } = await listCaMeterD(this.queryParams)
+        this.caMeterDList = rows || []
+        this.total = total || 0
+
+        // 获取全量数据用于统计计算
+        if (listSumCaMeterD) {
+          const allDataParams = {
+            areaCode: this.queryParams.areaCode,
+            startRecTime: this.queryParams.startRecTime,
+            endRecTime: this.queryParams.endRecTime
+          }
+          const allDataRes = await listSumCaMeterD(allDataParams)
+          const allRows = allDataRes.rows || allDataRes.data || []
+          this.calculateStatistics(allRows)
+        } else {
+          // 降级方案:使用当前分页数据
+          this.calculateStatistics(this.caMeterDList)
+        }
+      } catch (error) {
+        console.error('加载数据失败', error)
+        this.$message.error('数据加载失败')
+        this.caMeterDList = []
+        this.total = 0
+        this.resetStatistics()
+      } finally {
+        this.loading = false
+      }
+    },
+
+    // 计算统计数据
+    calculateStatistics(dataList) {
+      if (dataList.length > 0) {
+        // 计算当月累计碳汇
+        this.monthTotalSink = dataList.reduce((sum, item) => {
+          return sum + (item.caSinkQuantity || 0)
+        }, 0).toFixed(2)
+
+        // 计算日均碳汇
+        this.dailyAverageSink = (this.monthTotalSink / dataList.length).toFixed(2)
+
+        // 计算最大值用于贡献度计算
+        this.maxSinkValue = Math.max(...dataList.map(item => item.caSinkQuantity || 0))
+
+        // 计算碳汇效率(示例:假设有目标值)
+        const targetSink = 1000 // 示例目标值
+        this.sinkEfficiency = Math.min(100, ((this.monthTotalSink / targetSink) * 100).toFixed(1))
+      } else {
+        this.resetStatistics()
+      }
+    },
+
+    // 重置统计数据
+    resetStatistics() {
+      this.monthTotalSink = 0
+      this.dailyAverageSink = 0
+      this.sinkEfficiency = 0
+      this.maxSinkValue = 0
+    },
+
+    // 获取碳汇等级类型
+    getSinkLevelType(value) {
+      if (value >= 50) return 'success'
+      if (value >= 30) return 'warning'
+      if (value >= 10) return 'info'
+      return 'danger'
+    },
+
+    // 获取碳汇等级文本
+    getSinkLevelText(value) {
+      if (value >= 50) return '优秀'
+      if (value >= 30) return '良好'
+      if (value >= 10) return '一般'
+      return '较低'
+    },
+
+    // 获取贡献度百分比
+    getContributionPercent(row) {
+      if (this.maxSinkValue === 0) return 0
+      return Math.min(100, ((row.caSinkQuantity || 0) / this.maxSinkValue * 100).toFixed(0))
+    },
+
+    // 获取进度条颜色
+    getProgressColor(row) {
+      const percent = this.getContributionPercent(row)
+      if (percent >= 80) return '#67c23a'
+      if (percent >= 60) return '#409eff'
+      if (percent >= 40) return '#e6a23c'
+      return '#f56c6c'
+    },
+
+    // 处理日期范围变化
+    handleDateChange(val) {
+      if (val && val.length === 2) {
+        this.queryParams.startRecTime = val[0]
+        this.queryParams.endRecTime = val[1]
+      }
+    },
+
+    // 搜索按钮操作
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+
+    // 重置按钮操作
+    resetQuery() {
+      this.initDateRange()
+      this.handleQuery()
+    },
+
     // 筛选节点
-    filterNode (value, data) {
+    filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     // 节点单击事件
-    handleNodeClick (data) {
+    handleNodeClick(data) {
       this.queryParams.areaCode = data.id
+      this.selectedLabel = data.label
       this.getList()
-    },
+    }
   }
-};
+}
 </script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  // 左侧树形结构
+  .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;
+      }
+
+      ::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;
+            }
+          }
+
+          .tree-tag.sink {
+            background: linear-gradient(135deg, #13c2c2 0%, #006d75 100%);
+            color: #fff;
+            border: none;
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
+
+  // 统计卡片
+  .statistic-cards {
+    margin-bottom: 20px;
+
+    .stat-card {
+      background: #fff;
+      border-radius: 8px;
+      padding: 20px;
+      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+      display: flex;
+      align-items: center;
+      transition: all 0.3s;
+      height: 100px;
+
+      &:hover {
+        transform: translateY(-3px);
+        box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.12);
+      }
+
+      .card-icon {
+        width: 60px;
+        height: 60px;
+        border-radius: 12px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 28px;
+        margin-right: 20px;
+      }
+
+      &.total {
+        .card-icon {
+          background: linear-gradient(135deg, #e6f7ff 0%, #91d5ff 100%);
+          color: #1890ff;
+        }
+      }
+
+      &.average {
+        .card-icon {
+          background: linear-gradient(135deg, #f6ffed 0%, #b7eb8f 100%);
+          color: #52c41a;
+        }
+      }
+
+      &.efficiency {
+        .card-icon {
+          background: linear-gradient(135deg, #fff7e6 0%, #ffd591 100%);
+          color: #fa8c16;
+        }
+      }
+
+      .card-content {
+        flex: 1;
+
+        .stat-label {
+          font-size: 14px;
+          color: #909399;
+          margin-bottom: 8px;
+        }
+
+        .stat-value {
+          display: flex;
+          align-items: baseline;
+          gap: 4px;
+
+          .number {
+            font-size: 28px;
+            font-weight: 600;
+            color: #303133;
+          }
+
+          .unit {
+            font-size: 14px;
+            color: #909399;
+          }
+        }
+      }
+    }
+  }
+
+  // 内容区域
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 1px solid #ebeef5;
+
+      .section-title {
+        font-size: 18px;
+        font-weight: 600;
+        color: #303133;
+        margin: 0;
+      }
+
+      ::v-deep .el-form-item {
+        margin-bottom: 0;
+      }
+    }
+
+    .table-container {
+      .data-table {
+        ::v-deep .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+          }
+        }
+
+        ::v-deep .el-table__body {
+          .area-info {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 6px;
+
+            i {
+              color: #909399;
+            }
+
+            .area-name {
+              font-weight: 500;
+              color: #303133;
+            }
+          }
+
+          .date-text {
+            color: #606266;
+          }
+
+          .sink-value {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 6px;
+
+            .value-icon {
+              color: #13c2c2;
+              font-size: 16px;
+            }
+
+            .value {
+              font-size: 16px;
+              font-weight: 600;
+              color: #13c2c2;
+            }
+
+            .unit {
+              font-size: 12px;
+              color: #909399;
+            }
+          }
+
+          .contribution-value {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            gap: 8px;
+
+            ::v-deep .el-progress {
+              width: 100px;
+            }
+
+            .percent-text {
+              font-size: 12px;
+              font-weight: 500;
+              color: #606266;
+            }
+          }
+        }
+      }
+
+      .pagination-container {
+        margin-top: 20px;
+        display: flex;
+        justify-content: flex-end;
+      }
+    }
+  }
+}
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .statistic-cards {
+      .el-col {
+        margin-bottom: 15px;
+      }
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+
+        .section-title {
+          margin-bottom: 15px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 272 - 139
ems-ui-cloud/src/views/devmgr/attr/index.vue → ems-ui-cloud/src/views/devmgr/device/index.vue

@@ -1,110 +1,126 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
-      <el-col :span="4" :xs="24">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px"
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
           />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="treeAreaOptions" :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
+            ref="tree"
+            :data="treeAreaOptions"
+            :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">
-        <el-tabs v-model="queryParams.deviceCategory" @tab-click="deviceCategoryChange">
-          <el-tab-pane label="产能设备" name="E"></el-tab-pane>
-          <el-tab-pane label="储能设备" name="C"></el-tab-pane>
-          <el-tab-pane label="输配设备" name="W"></el-tab-pane>
-          <el-tab-pane label="用能设备" name="Z"></el-tab-pane>
-        </el-tabs>
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
-                 label-width="68px"
-        >
-          <el-form-item label="设备分类" prop="deviceSubCategory">
-            <el-select v-model="queryParams.deviceSubCategory">
-              <el-option v-for="item in subCategoryOptions" placeholder="设备分类" :label="item.name" :value="item.code"
-                         :key="item.code"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="归属设施" prop="refFacs">
-            <el-select v-model="queryParams.refFacs">
-              <el-option v-for="item in facsOptions" :label="item.facsName" :value="item.facsCode"
-                         :key="item.facsCode"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="归属系统" prop="subsystemCode">
-            <el-select v-model="queryParams.subsystemCode">
-              <el-option v-for="item in subsystemOptions" :label="item.systemName" :value="item.systemCode"
-                         :key="item.systemCode"
-              />
-            </el-select>
-          </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-form-item>
-        </el-form>
-
-        <el-table v-loading="loading" :data="deviceList">
-          <el-table-column label="设备名称" align="left" prop="deviceName"/>
-          <el-table-column label="归属区域" align="left" prop="areaPath" width="280px"/>
-          <el-table-column label="设备分类" align="center" prop="deviceCategoryName"/>
-          <el-table-column label="归属设施" align="center" prop="refFacsName"/>
-          <el-table-column label="归属系统" align="center" prop="subsystemName"/>
-          <el-table-column label="设备状态" align="center" prop="deviceStatus">
-            <template slot-scope="scope">
-              <span :style="{
-                padding: '6px 12px',
-                borderRadius: '4px',
-                display: 'inline-block',
-                textAlign: 'center',
-                cursor: 'pointer',
-                color: getDeviceStatusTextColor(scope.row.deviceStatus),
-                backgroundColor: getDeviceStatusBgColor(scope.row.deviceStatus),
-                minWidth: '70px',
-                height: '35px',
-                textAlign: 'center'
-              }"
-              >
-                {{ getDeviceStatusText(scope.row.deviceStatus) }}
-              </span>
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template slot-scope="scope">
-              <el-button size="mini" type="text" icon="el-icon-info" @click="handleDetail(scope.row)">详情</el-button>
-              <!-- 下拉菜单 -->
-              <el-dropdown trigger="click">
-                <el-button type="text" size="mini" @click="loadAbilities(scope.row)">
-                  操作<i class="el-icon-arrow-down el-icon--right"></i>
-                </el-button>
-                <el-dropdown-menu slot="dropdown">
-                  <template v-if="abilityDevice && abilityDevice.length > 0">
-                    <el-dropdown-item
-                      v-for="ability in abilityDevice"
-                      :key="ability.abilityKey"
-                      @click.native="handleDeviceOperate(ability, scope.row)"
-                    >
-                      {{ ability.abilityName }}
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <el-tabs v-model="queryParams.deviceCategory" @tab-click="deviceCategoryChange">
+            <el-tab-pane label="产能设备" name="E"></el-tab-pane>
+            <el-tab-pane label="储能设备" name="C"></el-tab-pane>
+            <el-tab-pane label="输配设备" name="W"></el-tab-pane>
+            <el-tab-pane label="用能设备" name="Z"></el-tab-pane>
+          </el-tabs>
+
+          <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+            <el-form-item label="设备分类" prop="deviceSubCategory">
+              <el-select v-model="queryParams.deviceSubCategory">
+                <el-option v-for="item in subCategoryOptions" placeholder="设备分类" :label="item.name" :value="item.code" :key="item.code" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="归属设施" prop="refFacs">
+              <el-select v-model="queryParams.refFacs">
+                <el-option v-for="item in facsOptions" :label="item.facsName" :value="item.facsCode" :key="item.facsCode" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="归属系统" prop="subsystemCode">
+              <el-select v-model="queryParams.subsystemCode">
+                <el-option v-for="item in subsystemOptions" :label="item.systemName" :value="item.systemCode" :key="item.systemCode" />
+              </el-select>
+            </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-form-item>
+          </el-form>
+
+          <el-table v-loading="loading" :data="deviceList">
+            <el-table-column label="设备名称" align="left" prop="deviceName"/>
+            <el-table-column label="归属区域" align="left" prop="areaPath" width="280px"/>
+            <el-table-column label="设备分类" align="center" prop="deviceCategoryName"/>
+            <el-table-column label="归属设施" align="center" prop="refFacsName"/>
+            <el-table-column label="归属系统" align="center" prop="subsystemName"/>
+            <el-table-column label="设备状态" align="center" prop="deviceStatus">
+              <template slot-scope="scope">
+                <span :style="{
+                  padding: '6px 12px',
+                  borderRadius: '4px',
+                  display: 'inline-block',
+                  textAlign: 'center',
+                  cursor: 'pointer',
+                  color: getDeviceStatusTextColor(scope.row.deviceStatus),
+                  backgroundColor: getDeviceStatusBgColor(scope.row.deviceStatus),
+                  minWidth: '70px',
+                  height: '35px',
+                  textAlign: 'center'
+                }">
+                  {{ getDeviceStatusText(scope.row.deviceStatus) }}
+                </span>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+              <template slot-scope="scope">
+                <el-button size="mini" type="text" icon="el-icon-info" @click="handleDetail(scope.row)">详情</el-button>
+                <!-- 下拉菜单 -->
+                <el-dropdown trigger="click">
+                  <el-button type="text" size="mini" @click="loadAbilities(scope.row)">
+                    操作<i class="el-icon-arrow-down el-icon--right"></i>
+                  </el-button>
+                  <el-dropdown-menu slot="dropdown">
+                    <template v-if="abilityDevice && abilityDevice.length > 0">
+                      <el-dropdown-item
+                        v-for="ability in abilityDevice"
+                        :key="ability.abilityKey"
+                        @click.native="handleDeviceOperate(ability, scope.row)"
+                      >
+                        {{ ability.abilityName }}
+                      </el-dropdown-item>
+                    </template>
+                    <el-dropdown-item v-else>
+                      无
                     </el-dropdown-item>
-                  </template>
-                  <el-dropdown-item v-else>
-                    无
-                  </el-dropdown-item>
-                </el-dropdown-menu>
-              </el-dropdown>
-            </template>
-          </el-table-column>
-        </el-table>
-        <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
-                    :limit.sync="queryParams.pageSize" @pagination="getList"
-        />
+                  </el-dropdown-menu>
+                </el-dropdown>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+        </div>
 
         <!-- 调用日志详情弹窗 -->
         <el-dialog :visible.sync="callLogDetailDialog" title="调用日志详情" width="50%">
@@ -116,11 +132,9 @@
             <p><strong>响应时间:</strong>{{ callLogDetailData.resTime }}</p>
             <p><strong>调用结果:</strong>{{ formatCallStatus(callLogDetailData.callStatus) }}</p>
             <p><strong>调用载体:</strong></p>
-            <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px;"
-            >{{ callLogDetailData.callPayload }}</pre>
+            <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px;">{{ callLogDetailData.callPayload }}</pre>
             <p><strong>响应载体:</strong></p>
-            <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px;"
-            >{{ callLogDetailData.resPayload }}</pre>
+            <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px;">{{ callLogDetailData.resPayload }}</pre>
           </div>
         </el-dialog>
 
@@ -131,8 +145,7 @@
             <p><strong>消息描述:</strong>{{ reportDetailData.msgDesc }}</p>
             <p><strong>上报时间:</strong>{{ reportDetailData.reportTime }}</p>
             <p><strong>上报载体:</strong></p>
-            <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px;"
-            >{{ reportDetailData.reportPayload }}</pre>
+            <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px;">{{ reportDetailData.reportPayload }}</pre>
           </div>
         </el-dialog>
 
@@ -147,7 +160,7 @@
           </div>
         </el-dialog>
 
-       <!--详情 -->
+        <!--详情 -->
         <el-dialog :visible.sync="open" title="设备详情" custom-class="detail-dialog" width="65%">
           <div v-if="curRow">
             <el-tabs v-model="activeTab">
@@ -186,11 +199,11 @@
             <div v-if="activeTab === 'attr'">
               <el-card class="box-card">
                 <div v-for="(tableData, tableName) in attrTables" :key="tableName">
-                    <p class="section-title" @click="toggleCollapse(tableName)" style="cursor: pointer;">
-                      <i :class="collapsed[tableName] ? 'el-icon-arrow-right' : 'el-icon-arrow-down'" style="margin-right: 8px;"></i>
+                  <p class="section-title" @click="toggleCollapse(tableName)" style="cursor: pointer;">
+                    <i :class="collapsed[tableName] ? 'el-icon-arrow-right' : 'el-icon-arrow-down'" style="margin-right: 8px;"></i>
                     {{ tableData.title }} ({{ tableData.data.length }})
-                    </p>
-                    <el-table v-if="!collapsed[tableName]" :data="tableData.data" style="width: 100%" :show-header="true" :empty-text="'暂无数据'">
+                  </p>
+                  <el-table v-if="!collapsed[tableName]" :data="tableData.data" style="width: 100%" :show-header="true" :empty-text="'暂无数据'">
                     <el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
                     <el-table-column label="属性名称" prop="attrName"></el-table-column>
                     <el-table-column label="属性标识" prop="attrKey"></el-table-column>
@@ -382,8 +395,6 @@
                 @pagination="handleLogQuery"
               />
             </div>
-
-
           </div>
         </el-dialog>
 
@@ -397,7 +408,6 @@
             <el-table-column label="更新时间" prop="updateTime"></el-table-column>
           </el-table>
         </el-dialog>
-
       </el-col>
     </el-row>
   </div>
@@ -444,6 +454,8 @@ export default {
       areaName: undefined,
       // 区域树选项
       treeAreaOptions: undefined,
+      // 默认展开的节点
+      defaultExpandedKeys: [],
       // 设施选项
       facsOptions: undefined,
       // 设备分类
@@ -460,26 +472,26 @@ export default {
       abilityDevice: [],
       eventData: [],
       BaseData: [],
-        ProtocolData: [],
-        StateData: [],
-        MeasureData: [],
-        // 折叠状态,默认全部折叠
-        collapsed: {
-          Base: true,
-          Protocol: true,
-          State: true,
-          Measure: true
-        },
-        attrTables: {
-          Base: { title: '基础属性', data: [] },
-          Protocol: { title: '协议属性', data: [] },
-          State: { title: '状态属性', data: [] },
-          Measure: { title: '计量属性', data: [] }
-        },
-        // 控制表格显示的方法
-        toggleCollapse(tableName) {
-          this.collapsed[tableName] = !this.collapsed[tableName];
-        },
+      ProtocolData: [],
+      StateData: [],
+      MeasureData: [],
+      // 折叠状态,默认全部折叠
+      collapsed: {
+        Base: true,
+        Protocol: true,
+        State: true,
+        Measure: true
+      },
+      attrTables: {
+        Base: { title: '基础属性', data: [] },
+        Protocol: { title: '协议属性', data: [] },
+        State: { title: '状态属性', data: [] },
+        Measure: { title: '计量属性', data: [] }
+      },
+      // 控制表格显示的方法
+      toggleCollapse(tableName) {
+        this.collapsed[tableName] = !this.collapsed[tableName];
+      },
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -545,7 +557,7 @@ export default {
     // 监听 activeTab 的变化
     activeTab(newVal) {
       if (newVal === 'callLog') {
-        // 当切换到“调用日志”时,调用查询接口
+        // 当切换到"调用日志"时,调用查询接口
         this.handleCallLogQuery()
       } else if(newVal ==='reportLog'){
         this.handleLogQuery()
@@ -573,6 +585,19 @@ export default {
 
   },
   methods: {
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.id === null) {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     formatDate(date) {
       if (!date) return ''
       const year = date.getFullYear()
@@ -592,7 +617,7 @@ export default {
       return statusMap[status] || '未知状态'
     },
 
-   /** 查询事件日志*/
+    /** 查询事件日志*/
     handleEventLogQuery() {
       if (this.curRow) {
         const startTime = this.logDaterangeTime[0];
@@ -601,7 +626,7 @@ export default {
         this.getEventLog(this.curRow.deviceCode, eventKey,startTime, endTime)
       }
     },
-/** 获取事件日志数据*/
+    /** 获取事件日志数据*/
     getEventLog(deviceCode, eventKey,startTime, endTime) {
       const query = {
         objCode: deviceCode,
@@ -616,7 +641,7 @@ export default {
         this.eventLogQueryTotal = response.total;
       });
     },
-   /** 重置事件日志查询*/
+    /** 重置事件日志查询*/
     resetEventLogQuery() {
       this.logDaterangeTime = [];
       this.eventLogQueryParams.eventName=''
@@ -624,7 +649,7 @@ export default {
       this.eventLogQueryParams.pageSize = 10;
       this.handleEventLogQuery();
     },
-   /** 事件日志详情*/
+    /** 事件日志详情*/
     handleEventLogDetail(row) {
       getEventLog(row.id).then(response => {
         this.eventLogDetailData = response.data || {};
@@ -814,6 +839,16 @@ export default {
           label: '全部',
           children: []
         }].concat(response.data)
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = [null]
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey(null)
+          }
+        })
       })
     },
     // 筛选节点
@@ -913,6 +948,94 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+  }
+}
+
 .detail-dialog .el-dialog {
   width: 80%;
 }
@@ -933,14 +1056,14 @@ export default {
 }
 
 .status-online {
-  color: #00FF00; /* 在线状态颜色 */
+  color: #00FF00;
   background-color: #DDFFDD;
   padding: 2px 6px;
   border-radius: 4px;
 }
 
 .status-offline {
-  color: #FF0000; /* 离线状态颜色 */
+  color: #FF0000;
   background-color: #FFDDDD;
   padding: 2px 6px;
   border-radius: 4px;
@@ -950,4 +1073,14 @@ export default {
   width: 70%;
 }
 
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+  }
+}
 </style>

+ 243 - 83
ems-ui-cloud/src/views/devmgr/el/index.vue

@@ -1,94 +1,126 @@
 <template>
   <div class="app-container">
-    <el-row :gutter="10">
-      <el-col :span="4" :xs="24">
+    <el-row :gutter="20">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入服务区名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px" />
+          <el-input
+            v-model="areaName"
+            placeholder="请输入服务区名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container">
-          <el-tree ref="tree" :data="areaOptions" default-expand-all :expand-on-click-node="false" :filter-node-method="filterNode"
-                   node-key="id" highlight-current @node-click="handleNodeClick" />
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <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="18" :xs="24" >
-        <!-- 优化标签页渲染逻辑,添加字典存在性判断 -->
-        <el-tabs v-model="queryParams.objType" @tab-click="resetQuery" v-if="dict.type.device_type && dict.type.device_type.length">
-          <el-tab-pane v-for="dictItem in dict.type.device_type" :key="dictItem.value" :label="dictItem.label" :name="dictItem.value">
-          </el-tab-pane>
-        </el-tabs>
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
-                 label-width="68px">
-          <el-form-item label="目标对象" prop="objName">
-            <el-autocomplete v-model="queryParams.objName" placeholder="请输入对象名称" clearable
-                             :fetch-suggestions="querySearch" @select="handleSelect"></el-autocomplete>
-          </el-form-item>
-          <el-form-item label="创建时间">
-            <el-date-picker
-              v-model="queryParams.recordTimeRange"
-              style="width: 240px"
-              value-format="yyyy-MM-dd"
-              type="daterange"
-              range-separator="-"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-            ></el-date-picker>
-          </el-form-item>
-          <el-form-item label="维护标题" prop="maintainTitle">
-            <el-input v-model="queryParams.maintainTitle" placeholder="请输入维护标题" clearable
-                      @keyup.enter.native="handleQuery" />
-          </el-form-item>
-          <el-form-item label="维护人" prop="maintainPerson">
-            <el-input v-model="queryParams.maintainPerson" placeholder="请输入维护人" clearable
-                      @keyup.enter.native="handleQuery" />
-          </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-form-item>
-        </el-form>
-        <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
-                       v-hasPermi="['ems:rbook:add']">新增
-            </el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
-                       v-hasPermi="['ems:rbook:export']">导出
-            </el-button>
-          </el-col>
-          <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-        </el-row>
-        <el-table v-loading="loading" :data="rbookList" @selection-change="handleSelectionChange">
-          <el-table-column type="selection" width="55" align="center" />
-          <el-table-column label="园区名称" align="center" prop="areaName" />
-          <el-table-column label="记录编号" align="center" prop="recordCode" />
-          <el-table-column label="目标对象" align="center" prop="objName">
-            <template slot-scope="scope">
-              {{ scope.row.objName }}
-            </template>
-          </el-table-column>
-          <el-table-column label="安装位置" align="center" prop="insLocation" />
-          <el-table-column label="维护标题" align="center" prop="maintainTitle" />
-          <el-table-column label="创建时间" align="center" prop="recordTime" width="180">
-            <template slot-scope="scope">
-              <span>{{ parseTime(scope.row.recordTime, '{y}-{m}-{d}') }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="维护人" align="center" prop="maintainPerson" />
-          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template slot-scope="scope">
-              <el-button size="mini" type="text" icon="el-icon-document" @click="handleViewRec(scope.row)">
-                查看
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <!-- 优化标签页渲染逻辑,添加字典存在性判断 -->
+          <el-tabs v-model="queryParams.objType" @tab-click="resetQuery" v-if="dict.type.device_type && dict.type.device_type.length">
+            <el-tab-pane v-for="dictItem in dict.type.device_type" :key="dictItem.value" :label="dictItem.label" :name="dictItem.value">
+            </el-tab-pane>
+          </el-tabs>
+
+          <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+            <el-form-item label="目标对象" prop="objName">
+              <el-autocomplete v-model="queryParams.objName" placeholder="请输入对象名称" clearable
+                               :fetch-suggestions="querySearch" @select="handleSelect"></el-autocomplete>
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker
+                v-model="queryParams.recordTimeRange"
+                style="width: 240px"
+                value-format="yyyy-MM-dd"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              ></el-date-picker>
+            </el-form-item>
+            <el-form-item label="维护标题" prop="maintainTitle">
+              <el-input v-model="queryParams.maintainTitle" placeholder="请输入维护标题" clearable
+                        @keyup.enter.native="handleQuery" />
+            </el-form-item>
+            <el-form-item label="维护人" prop="maintainPerson">
+              <el-input v-model="queryParams.maintainPerson" placeholder="请输入维护人" clearable
+                        @keyup.enter.native="handleQuery" />
+            </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-form-item>
+          </el-form>
+
+          <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+              <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+                         v-hasPermi="['ems:rbook:add']">新增
               </el-button>
-            </template>
-          </el-table-column>
-        </el-table>
-        <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
-                    :limit.sync="queryParams.pageSize" @pagination="getList" />
+            </el-col>
+            <el-col :span="1.5">
+              <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
+                         v-hasPermi="['ems:rbook:export']">导出
+              </el-button>
+            </el-col>
+            <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+          </el-row>
+
+          <el-table v-loading="loading" :data="rbookList" @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="园区名称" align="center" prop="areaName" />
+            <el-table-column label="记录编号" align="center" prop="recordCode" />
+            <el-table-column label="目标对象" align="center" prop="objName">
+              <template slot-scope="scope">
+                {{ scope.row.objName }}
+              </template>
+            </el-table-column>
+            <el-table-column label="安装位置" align="center" prop="insLocation" />
+            <el-table-column label="维护标题" align="center" prop="maintainTitle" />
+            <el-table-column label="创建时间" align="center" prop="recordTime" width="180">
+              <template slot-scope="scope">
+                <span>{{ parseTime(scope.row.recordTime, '{y}-{m}-{d}') }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="维护人" align="center" prop="maintainPerson" />
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+              <template slot-scope="scope">
+                <el-button size="mini" type="text" icon="el-icon-document" @click="handleViewRec(scope.row)">
+                  查看
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
+                      :limit.sync="queryParams.pageSize" @pagination="getList" />
+        </div>
       </el-col>
     </el-row>
+
     <!-- 查看设备台账对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="650px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
@@ -260,6 +292,11 @@ export default {
       areaCode: '', // 区域代码
       objCode: null, // 对象代码
       showObjCode: false, // 对象代码显示控制
+      defaultExpandedKeys: [], // 默认展开的节点
+      defaultProps: {
+        children: 'children',
+        label: 'label'
+      },
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -342,6 +379,19 @@ export default {
     },
   },
   methods: {
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     /** 查询设备台账列表 */
     getList(refresh = false) {
       if (refresh) {
@@ -366,6 +416,16 @@ export default {
         this.form.areaCode = '-1'
         // 取前3个子项作为区域选择器选项
         this.areaOptionsForSelect = response.data.slice(0, 3);
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
       })
     },
 
@@ -608,9 +668,109 @@ export default {
   }
 }
 </script>
-<style lang="css">
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+  }
+}
+
 .el-form-item--inline {
   display: inline-block;
   margin-right: 10px;
 }
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+  }
+}
 </style>

+ 0 - 0
ems-ui-cloud/src/views/analysis/device/DevcWarning/img/icon_waring.svg → ems-ui-cloud/src/views/devmgr/warn/DevcWarning/img/icon_waring.svg


+ 0 - 0
ems-ui-cloud/src/views/analysis/device/DevcWarning/index.scss → ems-ui-cloud/src/views/devmgr/warn/DevcWarning/index.scss


+ 0 - 0
ems-ui-cloud/src/views/analysis/device/DevcWarning/index.vue → ems-ui-cloud/src/views/devmgr/warn/DevcWarning/index.vue


+ 0 - 0
ems-ui-cloud/src/views/analysis/device/index.scss → ems-ui-cloud/src/views/devmgr/warn/index.scss


+ 0 - 0
ems-ui-cloud/src/views/analysis/device/index.vue → ems-ui-cloud/src/views/devmgr/warn/index.vue


+ 1 - 1
ems-ui-cloud/src/views/analysis/device/warn.vue → ems-ui-cloud/src/views/devmgr/warn/warn.vue

@@ -92,7 +92,7 @@ import BarChartBlock from '@/components/Block/charts/BarChartBlock.vue';
 import PieChartBlock from '@/components/Block/charts/PieChartBlock.vue';
 import {ALARM_STATE} from '@/enums/alarm';
 import {DateTool} from '@/utils/DateTool';
-import DeviceWaring from '@/views/analysis/device/DevcWarning/index.vue';
+import DeviceWaring from '@/views/devmgr/warn/DevcWarning/index.vue';
 import dayjs from 'dayjs';
 import {areaTreeSelect} from '@/api/basecfg/area'
 import LineChartBlock from '../../../components/Block/charts/LineChartBlock.vue';

+ 0 - 293
ems-ui-cloud/src/views/ems/EmsEcoD/index.vue

@@ -1,293 +0,0 @@
-<template>
-  <div class="app-container">
-    <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" />
-        </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                   :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                   @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;" />
-        </div>
-      </el-col>
-      <el-col :span="20" :xs="24">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
-          <el-form-item label="开始日期" prop="date">
-            <el-date-picker clearable
-                            v-model="queryParams.startRecTime"
-                            type="date"
-                            value-format="yyyy-MM-dd"
-                            placeholder="请选择日期">
-            </el-date-picker>
-          </el-form-item>
-          <el-form-item label="结束日期" prop="date">
-            <el-date-picker clearable
-                            v-model="queryParams.endRecTime"
-                            type="date"
-                            value-format="yyyy-MM-dd"
-                            placeholder="请选择日期">
-            </el-date-picker>
-          </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-form-item>
-        </el-form>
-
-        <div class="statistic-container" style="display: flex; align-items: center; margin-bottom: 16px;">
-          <span style="margin-right: 20px;">上月平均日用电量: {{ lastMonthAvgElec }} (kW·h)</span>
-          <span style="margin-right: 20px;">上月平均日用电花费: {{ lastMonthAvgElecCost }} (元)</span>
-          <span>上月平均日用水量: {{ lastMonthAvgWater }}(t)</span>
-          <span>上月平均日用水花费: {{ lastMonthAvgWaterCost }}(元)</span>
-        </div>
-
-        <el-table v-loading="loading" :data="EmsEcoDList" >
-          <el-table-column label="位置" align="center" prop="areaName" />
-          <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">
-            <template slot-scope="scope">
-              <span>{{ formatValue(scope.row.elecEcoQuantity) }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="节电金额(元)" align="center">
-            <template slot-scope="scope">
-              <span>{{ formatValue(scope.row.elecEcoCost) }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="节水量 (吨)" align="center">
-            <template slot-scope="scope">
-              <span>{{ formatValue(scope.row.waterEcoQuantity) }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="节水金额(元)" align="center">
-            <template slot-scope="scope">
-              <span>{{ formatValue(scope.row.waterEcoCost) }}</span>
-            </template>
-          </el-table-column>
-        </el-table>
-
-        <pagination
-          v-show="total>0"
-          :total="total"
-          :page.sync="queryParams.pageNum"
-          :limit.sync="queryParams.pageSize"
-          @pagination="getList"
-        />
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script>
-import { listEmsEcoD } from "@/api/ems/EmsEcoD";
-import { getElecDayAvg } from '@/api/device/elecMeterH'
-import { getWaterDayAvg } from '@/api/device/waterMeterH'
-import { areaTreeByFacsCategory } from '@/api/basecfg/area'
-
-export default {
-  name: "EmsEcoD",
-  data() {
-    return {
-      // 遮罩层
-      loading: true,
-      // 选中数组
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
-      // 显示搜索条件
-      showSearch: true,
-      // 总条数
-      total: 0,
-      // 节能计量日表格数据
-      EmsEcoDList: [],
-      // 弹出层标题
-      title: "",
-      // 是否显示弹出层
-      open: false,
-      // 表单参数
-      areaOptions: [],
-      areaName: undefined,
-      facsCategory: 'Z',
-      facsSubCategory: '',
-      lastMonthAvgElec: 0,
-      lastMonthAvgElecCost:0,
-      lastMonthAvgWater: 0,
-      lastMonthAvgWaterCost: 0,
-      defaultProps: {
-        children: "children",
-        label: "label"
-      },
-      // 查询参数
-      queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        areaCode: null,
-        startRecTime: null,
-        endRecTime: null,
-      },
-      queryDayAvgParams:{
-        areaCode: null,
-        deviceCode: null,
-        startRecTime: null,
-        endRecTime: null,
-      },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        areaCode: [
-          { required: true, message: "园区代码不能为空", trigger: "blur" }
-        ],
-        date: [
-          { required: true, message: "日期不能为空", trigger: "blur" }
-        ],
-      }
-    };
-  },
-  created() {
-    this.getAreaList();
-    this.initDateRange();
-    this.initDayAvg();
-    this.getList();
-  },
-  methods: {
-    initDateRange() {
-      const today = new Date();
-
-      // 计算前一天
-      const yesterday = new Date(today);
-      yesterday.setDate(today.getDate() - 1);
-
-      // 计算前7天
-      const sevenDaysAgo = new Date(today);
-      sevenDaysAgo.setDate(today.getDate() - 7);
-
-      // 设置日期格式并赋值
-      this.queryParams.startRecTime = this.formatDate(sevenDaysAgo);
-      this.queryParams.endRecTime = this.formatDate(yesterday);
-    },
-    async initDayAvg() {
-      const today = new Date();
-
-      // 获取上个月的年份和月份
-      const lastMonth = new Date(today);
-      lastMonth.setMonth(today.getMonth() - 1);
-
-      // 获取上个月的第一天
-      const firstDayOfLastMonth = new Date(lastMonth.getFullYear(), lastMonth.getMonth(), 1);
-
-      // 获取上个月的最后一天
-      const lastDayOfLastMonth = new Date(lastMonth.getFullYear(), lastMonth.getMonth() + 1, 0);
-
-      // 设置日期格式并赋值
-      this.queryDayAvgParams.startRecTime = this.formatDate(firstDayOfLastMonth);
-      this.queryDayAvgParams.endRecTime = this.formatDate(lastDayOfLastMonth);
-
-      getElecDayAvg(this.queryDayAvgParams).then(response => {
-        this.lastMonthAvgElec = parseFloat(response.data.quantity.toFixed(2));
-        this.lastMonthAvgElecCost = parseFloat(response.data.useCost.toFixed(2));
-      });
-      getWaterDayAvg(this.queryDayAvgParams).then(response => {
-        this.lastMonthAvgWater = parseFloat(response.data.quantity.toFixed(2));
-        this.lastMonthAvgWaterCost = parseFloat(response.data.useCost.toFixed(2));
-      });
-    },
-    formatDate(date) {
-      const year = date.getFullYear();
-      const month = String(date.getMonth() + 1).padStart(2, '0');
-      const day = String(date.getDate()).padStart(2, '0');
-      return `${year}-${month}-${day}`;
-    },
-    // 查询区域列表
-    async getAreaList () {
-      await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false).then(response => {
-        this.areaOptions = [{
-          id: '-1',
-          label: '全部',
-          children: []
-        }].concat(response.data)
-        this.selectedLabel = '全部'
-        this.queryParams.areaCode = '-1'
-      })
-    },
-    /** 查询节能计量日列表 */
-    getList() {
-      this.loading = true;
-      listEmsEcoD(this.queryParams).then(response => {
-        this.EmsEcoDList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
-    },
-    // 取消按钮
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    // 表单重置
-    reset() {
-      this.form = {
-        id: null,
-        areaCode: null,
-        date: null,
-        elecEcoQuantity: null,
-        elecEcoCost: null,
-        waterEcoQuantity: null,
-        waterEcoCost: null
-      };
-      this.resetForm("form");
-    },
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
-    },
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm("queryForm");
-      this.handleQuery();
-    },
-    // 筛选节点
-    filterNode (value, data) {
-      if (!value) return true
-      return data.label.indexOf(value) !== -1
-    },
-    // 节点单击事件
-    handleNodeClick (data) {
-      this.queryParams.areaCode = data.id
-      this.queryDayAvgParams.areaCode = data.id
-      this.selectedLabel = data.label
-      this.initDayAvg()
-      this.getList()
-    },
-    formatValue(value) {
-      // 如果值为空或不是数字,直接返回原值
-      if (value === null || value === undefined || isNaN(value)) {
-        return value;
-      }
-
-      // 转换为数字并取绝对值
-      const numValue = Number(value);
-      const absValue = Math.abs(numValue);
-
-      // 根据正负值返回不同的格式化结果
-      return numValue >= 0 ? `降低 ↓ ${absValue}` : `增长 ↑ ${absValue}`;
-
-      // 根据正负值返回不同的格式化结果
-      if (numValue >= 0) {
-        return `<span style="color: #67c23a;">降低 ↓ </span>${absValue}`;
-      } else {
-        return `<span style="color: #f56c6c;">增长 ↑ </span>${absValue}`;
-      }
-    }
-  }
-};
-</script>

+ 85 - 95
ems-ui-cloud/src/views/mgr/charging.vue

@@ -46,49 +46,34 @@
 
       <!-- 右侧内容区域 -->
       <el-col :span="19" :xs="24">
-        <div class="tab-container">
-          <div class="custom-tabs">
-            <div class="tabs-header">
-              <div
-                class="tab-item"
-                :class="{ active: activeTab === 'overview' }"
-                @click="handleTabChange('overview')"
-              >
-                <i class="el-icon-data-board"></i>
-                <span>总览</span>
-              </div>
-              <div
-                class="tab-item"
-                :class="{ active: activeTab === 'seller' }"
-                @click="handleTabChange('seller')"
-              >
-                <i class="el-icon-user"></i>
-                <span>个户</span>
-              </div>
-            </div>
-            <div class="tabs-content">
-              <!-- 总览Tab -->
-              <OverviewPanel
-                v-if="activeTab === 'overview'"
-                ref="overviewPanel"
-                :query-params="queryParams"
-                :selected-area="selectedArea"
-                :area-tree-data="areaOptions"
-                @refresh="refreshData"
-              />
-
-              <!-- 个户Tab -->
-              <SellerPanel
-                v-if="activeTab === 'seller'"
-                ref="sellerPanel"
-                :query-params="queryParams"
-                :selected-area="selectedArea"
-                :area-tree-data="areaOptions"
-                @refresh="refreshData"
-              />
-            </div>
-          </div>
-        </div>
+        <!-- 使用 el-tabs 替代自定义tabs -->
+        <el-tabs v-model="activeTab" @tab-click="handleTabClick" class="charging-tabs">
+          <el-tab-pane name="overview">
+            <span slot="label">
+              <i class="el-icon-data-board"></i> 总览
+            </span>
+            <OverviewPanel
+              ref="overviewPanel"
+              :query-params="queryParams"
+              :selected-area="selectedArea"
+              :area-tree-data="areaOptions"
+              @refresh="refreshData"
+            />
+          </el-tab-pane>
+
+          <el-tab-pane name="seller">
+            <span slot="label">
+              <i class="el-icon-user"></i> 个户
+            </span>
+            <SellerPanel
+              ref="sellerPanel"
+              :query-params="queryParams"
+              :selected-area="selectedArea"
+              :area-tree-data="areaOptions"
+              @refresh="refreshData"
+            />
+          </el-tab-pane>
+        </el-tabs>
       </el-col>
     </el-row>
   </div>
@@ -227,10 +212,10 @@ export default {
       this.refreshData()
     },
 
-    // Tab切换
-    handleTabChange(tab) {
-      console.log('切换到tab:', tab)
-      this.activeTab = tab
+    // Tab切换处理
+    handleTabClick(tab) {
+      console.log('切换到tab:', tab.name)
+      this.activeTab = tab.name
       this.selectedArea = null
       this.queryParams.areaCode = null
       this.queryParams.objCode = null
@@ -339,7 +324,7 @@ export default {
             .tree-icon {
               margin-right: 8px;
               font-size: 16px;
-              color: #909399;
+              color: #409eff;
             }
           }
 
@@ -351,63 +336,68 @@ export default {
     }
   }
 
-  .tab-container {
-    .custom-tabs {
+  // el-tabs 样式定制
+  .charging-tabs {
+    ::v-deep .el-tabs__header {
+      margin: 0 0 20px;
       background: #fff;
-      border-radius: 12px;
+      padding: 0 20px;
+      border-radius: 8px 8px 0 0;
       box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
-      overflow: hidden;
+    }
 
-      .tabs-header {
-        display: flex;
-        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-        padding: 0;
+    ::v-deep .el-tabs__nav-wrap {
+      &::after {
+        height: 1px;
+        background-color: #e4e7ed;
+      }
+    }
 
-        .tab-item {
-          flex: 1;
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          height: 60px;
-          color: rgba(255, 255, 255, 0.7);
-          font-size: 16px;
-          font-weight: 500;
-          cursor: pointer;
-          transition: all 0.3s;
-          position: relative;
-
-          i {
-            margin-right: 8px;
-            font-size: 20px;
-          }
+    ::v-deep .el-tabs__item {
+      height: 50px;
+      line-height: 50px;
+      font-size: 15px;
+      font-weight: 500;
+      color: #606266;
 
-          &:hover {
-            color: rgba(255, 255, 255, 0.9);
-            background: rgba(255, 255, 255, 0.1);
-          }
+      i {
+        margin-right: 5px;
+        font-size: 18px;
+      }
 
-          &.active {
-            color: #fff;
-            background: rgba(255, 255, 255, 0.2);
-
-            &::after {
-              content: '';
-              position: absolute;
-              bottom: 0;
-              left: 20%;
-              right: 20%;
-              height: 3px;
-              background: #fff;
-              border-radius: 2px;
-            }
-          }
-        }
+      &:hover {
+        color: #409eff;
       }
 
-      .tabs-content {
-        padding: 0;
+      &.is-active {
+        color: #409eff;
+        font-weight: 600;
       }
     }
+
+    ::v-deep .el-tabs__active-bar {
+      height: 3px;
+      background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+    }
+
+    ::v-deep .el-tabs__content {
+      background: #fff;
+      padding: 20px;
+      border-radius: 0 0 8px 8px;
+      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+      min-height: calc(100vh - 300px);
+    }
+  }
+}
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
   }
 }
 </style>

+ 946 - 149
ems-ui-cloud/src/views/mgr/chargingpile.vue

@@ -1,71 +1,325 @@
 <template>
   <div class="app-container">
     <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="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree"
-                   node-key="id" default-expand-all highlight-current @node-click="handleNodeClick" />
+        <div class="head-container tree-container">
+          <el-tree
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            ref="tree"
+            node-key="id"
+            default-expand-all
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <template #default="{ node, data }">
+              <el-tooltip
+                class="tree-node-tooltip"
+                effect="dark"
+                :content="data.label"
+                placement="right"
+                :disabled="!isTextOverflow(data.label)"
+              >
+                <div class="tree-node">
+                  <i class="el-icon-office-building node-icon"></i>
+                  <span class="node-label">{{ data.label }}</span>
+                  <span class="tree-node-count" v-if="data.count">
+                    {{ data.count }}
+                  </span>
+                </div>
+              </el-tooltip>
+            </template>
+          </el-tree>
         </div>
       </el-col>
+
+      <!-- 右侧内容区 -->
       <el-col :span="20" :xs="24">
-        <el-row :gutter="20">
-          <el-col :span="6" v-for="(item, index) in chargingStation" :key="index">
-            <el-card class="box-card" :style="getCardStyle(item)">
-                <h2>{{ item.deviceName }}</h2>
-              <div class="status" :style="getStatusStyle(item.deviceStatus)">
-                {{ item.deviceStatus === 1 ? '在线' : '离线' }}
+        <!-- 统计概览 -->
+        <div class="stats-overview">
+          <el-row :gutter="20">
+            <el-col :span="6" v-for="stat in statistics" :key="stat.key">
+              <div class="stat-card" :style="{ background: stat.color }">
+                <div class="stat-icon">
+                  <i :class="stat.icon"></i>
+                </div>
+                <div class="stat-content">
+                  <div class="stat-value">{{ stat.value }}</div>
+                  <div class="stat-label">{{ stat.label }}</div>
+                </div>
               </div>
-              <div class="state" :style="getStateStyle(item.state)">
-                {{ item.stateName }}
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 筛选和操作栏 -->
+        <div class="filter-bar">
+          <el-row type="flex" justify="space-between" align="middle">
+            <el-col :span="18">
+              <el-radio-group v-model="filterStatus" @change="handleFilterChange" size="small">
+                <el-radio-button label="">全部 ({{ chargingStation.length }})</el-radio-button>
+                <el-radio-button label="idle">
+                  <i class="el-icon-circle-check" style="color: #67c23a;"></i> 空闲
+                </el-radio-button>
+                <el-radio-button label="charging">
+                  <i class="el-icon-loading" style="color: #409eff;"></i> 充电中
+                </el-radio-button>
+                <el-radio-button label="fault">
+                  <i class="el-icon-warning" style="color: #f56c6c;"></i> 故障
+                </el-radio-button>
+                <el-radio-button label="offline">
+                  <i class="el-icon-circle-close" style="color: #909399;"></i> 离线
+                </el-radio-button>
+              </el-radio-group>
+            </el-col>
+            <el-col :span="6" style="text-align: right;">
+              <el-button-group>
+                <el-button size="small" :type="viewMode === 'card' ? 'primary' : ''" @click="viewMode = 'card'">
+                  <i class="el-icon-s-grid"></i>
+                </el-button>
+                <el-button size="small" :type="viewMode === 'list' ? 'primary' : ''" @click="viewMode = 'list'">
+                  <i class="el-icon-s-unfold"></i>
+                </el-button>
+              </el-button-group>
+              <el-button type="primary" size="small" icon="el-icon-refresh" @click="refreshData" style="margin-left: 10px;">
+                刷新
+              </el-button>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 卡片视图 -->
+        <el-row :gutter="20" v-if="viewMode === 'card'" class="charging-cards">
+          <el-col :span="6" :xs="24" :sm="12" :md="8" :lg="6" v-for="(item, index) in filteredStations" :key="index">
+            <div class="charging-card" :class="getCardClass(item)" @click="showDetail(item)">
+              <!-- 状态标签 -->
+              <div class="card-status">
+                <span class="status-badge" :class="'status-' + getStatusKey(item)">
+                  {{ getStatusText(item) }}
+                </span>
+                <span class="device-code">{{ item.deviceCode }}</span>
               </div>
-              <div class="card-content">
-                <p>电流:{{ item.current }} {{ item.currentUnit }}</p>
-                <p>电压:{{ item.voltage }} {{ item.voltageUnit }}</p>
-                <p>功率:{{ item.power }} {{ item.powerUnit }}</p>
+
+              <!-- 设备名称 -->
+              <div class="card-header">
+                <h3>{{ item.deviceName }}</h3>
+                <p class="location">
+                  <i class="el-icon-location-outline"></i> {{ item.location }}
+                </p>
+              </div>
+
+              <!-- 充电信息 -->
+              <div class="charging-info" v-if="item.state === '1'">
+                <div class="charging-progress">
+                  <el-progress
+                    :percentage="getChargingProgress(item)"
+                    :color="customColorMethod"
+                    :stroke-width="8"
+                  ></el-progress>
+                </div>
+                <div class="charging-time">
+                  <i class="el-icon-time"></i> 已充电 {{ getChargingTime(item) }}
+                </div>
+              </div>
+
+              <!-- 实时数据 -->
+              <div class="card-metrics">
+                <div class="metric-row">
+                  <div class="metric-item">
+                    <span class="metric-label">电压</span>
+                    <span class="metric-value">{{ item.voltage || 0 }}</span>
+                    <span class="metric-unit">{{ item.voltageUnit || 'V' }}</span>
+                  </div>
+                  <div class="metric-item">
+                    <span class="metric-label">电流</span>
+                    <span class="metric-value">{{ item.current || 0 }}</span>
+                    <span class="metric-unit">{{ item.currentUnit || 'A' }}</span>
+                  </div>
+                </div>
+                <div class="power-display">
+                  <span class="power-label">实时功率</span>
+                  <span class="power-value">{{ item.power || 0 }}</span>
+                  <span class="power-unit">{{ item.powerUnit || 'kW' }}</span>
+                </div>
+              </div>
+
+              <!-- 操作按钮 -->
+              <div class="card-actions">
+                <el-button
+                  v-if="item.state === '0' && item.deviceStatus === 1"
+                  type="primary"
+                  size="mini"
+                  plain
+                  @click.stop="startCharging(item)"
+                >
+                  启动充电
+                </el-button>
+                <el-button
+                  v-else-if="item.state === '1'"
+                  type="danger"
+                  size="mini"
+                  plain
+                  @click.stop="stopCharging(item)"
+                >
+                  停止充电
+                </el-button>
+                <el-button
+                  size="mini"
+                  @click.stop="showDetail(item)"
+                >
+                  详情
+                </el-button>
               </div>
-            </el-card>
+            </div>
           </el-col>
         </el-row>
+
+        <!-- 列表视图 -->
+        <el-table
+          v-if="viewMode === 'list'"
+          :data="filteredStations"
+          style="width: 100%"
+          @row-click="showDetail"
+        >
+          <el-table-column label="设备编号" prop="deviceCode" width="180"></el-table-column>
+          <el-table-column label="设备名称" prop="deviceName" width="120"></el-table-column>
+          <el-table-column label="位置" prop="location"></el-table-column>
+          <el-table-column label="状态" width="100">
+            <template slot-scope="scope">
+              <span class="status-badge" :class="'status-' + getStatusKey(scope.row)">
+                {{ getStatusText(scope.row) }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column label="电压(V)" prop="voltage" width="80"></el-table-column>
+          <el-table-column label="电流(A)" prop="current" width="80"></el-table-column>
+          <el-table-column label="功率(kW)" prop="power" width="80"></el-table-column>
+          <el-table-column label="型号" prop="deviceSpec"></el-table-column>
+          <el-table-column label="品牌" prop="deviceBrand"></el-table-column>
+          <el-table-column label="操作" width="200">
+            <template slot-scope="scope">
+              <el-button
+                v-if="scope.row.state === '0' && scope.row.deviceStatus === 1"
+                type="primary"
+                size="mini"
+                @click.stop="startCharging(scope.row)"
+              >
+                启动
+              </el-button>
+              <el-button
+                v-else-if="scope.row.state === '1'"
+                type="danger"
+                size="mini"
+                @click.stop="stopCharging(scope.row)"
+              >
+                停止
+              </el-button>
+              <el-button size="mini" @click.stop="showDetail(scope.row)">详情</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 详情弹窗 -->
+        <el-dialog
+          :title="currentDevice ? currentDevice.deviceName + ' - 详细信息' : ''"
+          :visible.sync="detailVisible"
+          width="800px"
+        >
+          <el-tabs v-model="activeTab" v-if="currentDevice">
+            <el-tab-pane label="基本信息" name="basic">
+              <el-descriptions :column="2" border>
+                <el-descriptions-item label="设备编号">{{ currentDevice.deviceCode }}</el-descriptions-item>
+                <el-descriptions-item label="设备名称">{{ currentDevice.deviceName }}</el-descriptions-item>
+                <el-descriptions-item label="设备型号">{{ currentDevice.deviceSpec }}</el-descriptions-item>
+                <el-descriptions-item label="设备品牌">{{ currentDevice.deviceBrand }}</el-descriptions-item>
+                <el-descriptions-item label="安装位置">{{ currentDevice.location }}</el-descriptions-item>
+                <el-descriptions-item label="所属区域">{{ currentDevice.areaCode }}</el-descriptions-item>
+                <el-descriptions-item label="归属设施">{{ currentDevice.refFacs }}</el-descriptions-item>
+                <el-descriptions-item label="子系统">{{ currentDevice.subsystemCode }}</el-descriptions-item>
+              </el-descriptions>
+            </el-tab-pane>
+
+            <el-tab-pane label="实时数据" name="realtime">
+              <div class="realtime-data">
+                <el-row :gutter="20">
+                  <el-col :span="8">
+                    <div class="data-card">
+                      <div class="data-icon voltage">
+                        <i class="el-icon-lightning"></i>
+                      </div>
+                      <div class="data-info">
+                        <div class="data-value">{{ currentDevice.voltage || 0 }} {{ currentDevice.voltageUnit || 'V' }}</div>
+                        <div class="data-label">电压</div>
+                      </div>
+                    </div>
+                  </el-col>
+                  <el-col :span="8">
+                    <div class="data-card">
+                      <div class="data-icon current">
+                        <i class="el-icon-s-data"></i>
+                      </div>
+                      <div class="data-info">
+                        <div class="data-value">{{ currentDevice.current || 0 }} {{ currentDevice.currentUnit || 'A' }}</div>
+                        <div class="data-label">电流</div>
+                      </div>
+                    </div>
+                  </el-col>
+                  <el-col :span="8">
+                    <div class="data-card">
+                      <div class="data-icon power">
+                        <i class="el-icon-s-opportunity"></i>
+                      </div>
+                      <div class="data-info">
+                        <div class="data-value">{{ currentDevice.power || 0 }} {{ currentDevice.powerUnit || 'kW' }}</div>
+                        <div class="data-label">功率</div>
+                      </div>
+                    </div>
+                  </el-col>
+                </el-row>
+              </div>
+            </el-tab-pane>
+
+            <el-tab-pane label="充电记录" name="history">
+              <el-table :data="chargingHistory" style="width: 100%">
+                <el-table-column label="开始时间" prop="startTime"></el-table-column>
+                <el-table-column label="结束时间" prop="endTime"></el-table-column>
+                <el-table-column label="充电量(kWh)" prop="energy"></el-table-column>
+                <el-table-column label="费用(元)" prop="cost"></el-table-column>
+                <el-table-column label="用户" prop="user"></el-table-column>
+              </el-table>
+            </el-tab-pane>
+          </el-tabs>
+        </el-dialog>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-
-import { areaTreeByFacsCategory, areaTreeSelect } from '@/api/basecfg/area'
-import {getByCondition} from '@/api/device/device.js'
+import { areaTreeByFacsCategory } from '@/api/basecfg/area'
+import { getByCondition } from '@/api/device/device.js'
 import { getObjAttr } from '@/api/basecfg/objAttribute'
 
-
 export default {
+  name: 'ChargingStation',
   data() {
     return {
-      // 遮罩层
-      loading: true,
-      // 选中数组
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
-      // 显示搜索条件
-      showSearch: true,
-      // 总条数
-      total: 0,
-      // 充电桩表格数据
-      chargingStation: [],
-      // 弹出层标题
-      title: '',
-      // 是否显示弹出层
-      open: false,
-      areaCode: undefined,
-      // 区域名称
-      areaName: undefined,
+      // 区域相关
+      areaName: '',
       areaOptions: [],
       defaultProps: {
         children: 'children',
@@ -78,71 +332,146 @@ export default {
         pageSize: 10,
         areaCode: null,
       },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {},
+
+      // 充电桩数据
+      chargingStation: [],
+      filteredStations: [],
+
+      // 视图控制
+      viewMode: 'card', // card | list
+      filterStatus: '', // 筛选状态
+      detailVisible: false,
+      currentDevice: null,
+      activeTab: 'basic',
+
+      // 统计数据
+      statistics: [
+        { key: 'total', label: '充电桩总数', value: 0, icon: 'el-icon-s-grid', color: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
+        { key: 'online', label: '在线设备', value: 0, icon: 'el-icon-circle-check', color: 'linear-gradient(135deg, #5acd76 0%, #48b461 100%)' },
+        { key: 'charging', label: '充电中', value: 0, icon: 'el-icon-loading', color: 'linear-gradient(135deg, #36d1dc 0%, #5b86e5 100%)' },
+        { key: 'fault', label: '故障设备', value: 0, icon: 'el-icon-warning', color: 'linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%)' }
+      ],
+
+      // 充电记录模拟数据
+      chargingHistory: [
+        { startTime: '2025-01-09 08:30', endTime: '2025-01-09 09:45', energy: 35.2, cost: 42.24, user: '张三' },
+        { startTime: '2025-01-09 10:15', endTime: '2025-01-09 11:30', energy: 28.5, cost: 34.20, user: '李四' },
+        { startTime: '2025-01-09 14:00', endTime: '2025-01-09 15:20', energy: 31.8, cost: 38.16, user: '王五' }
+      ],
+
+      // 定时器
+      refreshTimer: null
     };
   },
+
   created() {
-    this.getAreaTree('Z', '',false)
+    this.getAreaTree('Z', '', false)
     this.getList()
+    // 每30秒自动刷新数据
+    this.refreshTimer = setInterval(() => {
+      this.getList()
+    }, 30000)
   },
+
+  beforeDestroy() {
+    if (this.refreshTimer) {
+      clearInterval(this.refreshTimer)
+    }
+  },
+
   methods: {
-    /** 查询充电桩列表 */
-    getList() {
-      this.loading = true;
-      getByCondition({
-        areaCode: this.queryParams.areaCode,
-        psCode: 'DC-EVSE'
-      }).then(({ code, data }) => {
+    // 新增方法:判断文本是否溢出
+    isTextOverflow(text) {
+      // 简单判断:超过一定字符数就显示tooltip
+      // 可以根据实际情况调整这个数值
+      return text && text.length > 8
+    },
+
+    // 获取充电桩列表
+    async getList() {
+      try {
+        const { code, data } = await getByCondition({
+          areaCode: this.queryParams.areaCode,
+          psCode: 'DC-EVSE'
+        })
+
         if (code === 200) {
           const promises = data.map(item => {
             return getObjAttr(2, item.deviceCode).then(response => {
               if (response.code === 200) {
-                const measureData = response.data.Measure || [];
-                const power = measureData.find(attr => attr.attrKey === 'power')?.attrValue || '0';
-                const powerUnit = measureData.find(attr => attr.attrKey === 'power')?.attrUnit || 'W';
-                const voltage = measureData.find(attr => attr.attrKey === 'voltage')?.attrValue || '0';
-                const voltageUnit = measureData.find(attr => attr.attrKey === 'voltage')?.attrUnit || 'V';
-                const current = measureData.find(attr => attr.attrKey === 'current')?.attrValue || '0';
-                const currentUnit = measureData.find(attr => attr.attrKey === 'current')?.attrUnit || 'A';
-                const stateData = response.data.State || [];
-                const state = stateData.find(attr => attr.attrGroup === 'State')?.attrValue || -1;
-                const stateName = stateData.find(attr => attr.attrGroup === 'State')?.attrValueName || '';
-                item.current = current;
-                item.currentUnit = currentUnit;
-                item.voltage = voltage;
-                item.voltageUnit = voltageUnit;
-                item.power = power;
-                item.powerUnit = powerUnit;
-                item.state = state;
-                item.stateName = stateName;
+                // 计量数据
+                const measureData = response.data.Measure || []
+                const power = measureData.find(attr => attr.attrKey === 'power')?.attrValue || '0'
+                const powerUnit = measureData.find(attr => attr.attrKey === 'power')?.attrUnit || 'kW'
+                const voltage = measureData.find(attr => attr.attrKey === 'voltage')?.attrValue || '0'
+                const voltageUnit = measureData.find(attr => attr.attrKey === 'voltage')?.attrUnit || 'V'
+                const current = measureData.find(attr => attr.attrKey === 'current')?.attrValue || '0'
+                const currentUnit = measureData.find(attr => attr.attrKey === 'current')?.attrUnit || 'A'
+
+                // 状态数据
+                const stateData = response.data.State || []
+                const state = stateData.find(attr => attr.attrGroup === 'State')?.attrValue || '0'
+                const stateName = stateData.find(attr => attr.attrGroup === 'State')?.attrValueName || '空闲'
+
+                // 模拟充电进度和时间
+                if (state === '1') {
+                  item.chargingProgress = Math.floor(Math.random() * 80) + 20
+                  item.chargingTime = Math.floor(Math.random() * 120) + 10
+                }
+
+                Object.assign(item, {
+                  current,
+                  currentUnit,
+                  voltage,
+                  voltageUnit,
+                  power: (parseFloat(power) / 1000).toFixed(2), // 转换为kW
+                  powerUnit: 'kW',
+                  state,
+                  stateName
+                })
               }
-            });
-          });
-          Promise.all(promises).then(() => {
-            this.chargingStation = data;
-          });
+            })
+          })
+
+          await Promise.all(promises)
+          this.chargingStation = data
+          this.filteredStations = data
+          this.updateStatistics()
+          this.applyFilter()
         }
-      })
+      } catch (error) {
+        this.$message.error('获取充电桩数据失败')
+      }
     },
 
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
+    // 更新统计数据
+    updateStatistics() {
+      const stats = this.statistics
+      stats[0].value = this.chargingStation.length
+      stats[1].value = this.chargingStation.filter(s => s.deviceStatus === 1).length
+      stats[2].value = this.chargingStation.filter(s => s.state === '1').length
+      stats[3].value = this.chargingStation.filter(s => s.state === '2').length
     },
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm('queryForm');
-      this.handleQuery();
-    },
-    // 多选框选中数据
-    handleSelectionChange(selection) {
-      this.ids = selection.map(item => item.id);
-      this.single = selection.length !== 1;
-      this.multiple = !selection.length;
+
+    // 获取区域树
+    async getAreaTree(category, subCategory, recursion) {
+      try {
+        const response = await areaTreeByFacsCategory(category, subCategory, recursion)
+        this.areaOptions = [{
+          id: null,
+          label: '全部',
+          children: []
+        }].concat(response.data)
+
+        // 添加设备数量统计
+        this.areaOptions.forEach(area => {
+          if (area.id) {
+            area.count = this.chargingStation.filter(s => s.areaCode === area.id).length
+          }
+        })
+      } catch (error) {
+        this.$message.error('获取区域数据失败')
+      }
     },
 
     // 筛选节点
@@ -151,77 +480,545 @@ export default {
       return data.label.indexOf(value) !== -1
     },
 
-    // 节点单击事件
+    // 筛选树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
+    // 节点点击事件
     handleNodeClick(data) {
-      if (data.id === '-1') {
-        this.queryParams.areaCode = null;
-      } else {
-        this.queryParams.areaCode = data.id;
-      }
-      this.handleQuery();
-    },
-    /** 查询区域树结构 */
-    getAreaTree(category, subCategory, recursion) {
-       areaTreeByFacsCategory(category, subCategory, recursion).then(response => {
-         this.areaOptions = [{
-           id: null,
-           label: '全部',
-           children: []
-         }].concat(response.data)
-      })
+      this.queryParams.areaCode = data.id
+      this.getList()
+    },
+
+    // 状态筛选变化
+    handleFilterChange() {
+      this.applyFilter()
     },
-    /** 获取卡片背景颜色*/
-    getCardStyle(item) {
-      if (item.deviceStatus === 0 || item.state === "2") {
-        return 'background: linear-gradient(135deg, #dcdcdc, #f0f0f0);';
-      } else {
-        return 'background: linear-gradient(135deg, #9AF576, #FFFFFF);';
+
+    // 应用筛选
+    applyFilter() {
+      let filtered = [...this.chargingStation]
+
+      if (this.filterStatus) {
+        switch (this.filterStatus) {
+          case 'idle':
+            filtered = filtered.filter(s => s.state === '0' && s.deviceStatus === 1)
+            break
+          case 'charging':
+            filtered = filtered.filter(s => s.state === '1')
+            break
+          case 'fault':
+            filtered = filtered.filter(s => s.state === '2')
+            break
+          case 'offline':
+            filtered = filtered.filter(s => s.deviceStatus === 0)
+            break
+        }
       }
+
+      this.filteredStations = filtered
     },
-    /**获取设备在线状态文字样式*/
-    getStatusStyle(status) {
-      return status === 1 ? 'color: #fff; background: #67c23a;' : 'color: #fff; background: linear-gradient(135deg, #f44336, #e57373);';
+
+    // 刷新数据
+    refreshData() {
+      this.$message.success('正在刷新数据...')
+      this.getList()
     },
-    /**获取设备使用状态文字样式 0=空闲,1=使用中,2=故障*/
-    getStateStyle(state) {
-      switch (state) {
-        case '0':
-          return 'color: #fff; background: #f44336;';
-        case '1':
-          return 'color: #fff; background: #67c23a; ';
-        case '2':
-          return 'color: #fff; background: #909399;';
-      }
+
+    // 获取卡片类
+    getCardClass(item) {
+      if (item.deviceStatus === 0) return 'card-offline'
+      if (item.state === '2') return 'card-fault'
+      if (item.state === '1') return 'card-charging'
+      return 'card-idle'
     },
-  },
-};
+
+    // 获取状态键
+    getStatusKey(item) {
+      if (item.deviceStatus === 0) return 'offline'
+      if (item.state === '2') return 'fault'
+      if (item.state === '1') return 'charging'
+      return 'idle'
+    },
+
+    // 获取状态文本
+    getStatusText(item) {
+      if (item.deviceStatus === 0) return '离线'
+      if (item.state === '2') return '故障'
+      if (item.state === '1') return '充电中'
+      return '空闲'
+    },
+
+    // 获取充电进度
+    getChargingProgress(item) {
+      return item.chargingProgress || 0
+    },
+
+    // 获取充电时间
+    getChargingTime(item) {
+      const minutes = item.chargingTime || 0
+      const hours = Math.floor(minutes / 60)
+      const mins = minutes % 60
+      return hours > 0 ? `${hours}小时${mins}分钟` : `${mins}分钟`
+    },
+
+    // 自定义进度条颜色
+    customColorMethod(percentage) {
+      if (percentage < 30) return '#f56c6c'
+      if (percentage < 70) return '#e6a23c'
+      return '#5cb87a'
+    },
+
+    // 启动充电
+    startCharging(item) {
+      this.$confirm('确认启动充电桩?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.$message.success('充电桩启动成功')
+        item.state = '1'
+        item.stateName = '充电中'
+        this.updateStatistics()
+      })
+    },
+
+    // 停止充电
+    stopCharging(item) {
+      this.$confirm('确认停止充电?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.$message.success('充电已停止')
+        item.state = '0'
+        item.stateName = '空闲'
+        this.updateStatistics()
+      })
+    },
+
+    // 显示详情
+    showDetail(item) {
+      this.currentDevice = item
+      this.detailVisible = true
+      this.activeTab = 'basic'
+    }
+  }
+}
 </script>
 
-<style lang="css" scoped>
-.box-card {
-  margin: 10px;
-  width: calc(100% - 40px);
-  position: relative;
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
 }
 
-.status{
-  position: absolute;
-  top: 10px;
-  right: 10px;
-  padding: 2px 8px;
+.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;
+  border: 1px solid #e8e8e8;
   border-radius: 4px;
-  font-size: 12px;
-  color: #fff;
+  padding: 10px;
+  background-color: #fafafa;
+}
+
+/* 树节点样式 */
+.tree-node {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  padding: 2px 0;
+  transition: all 0.3s;
+  cursor: pointer;
+}
+
+.node-icon {
+  margin-right: 8px;
+  font-size: 16px;
+  transition: all 0.3s;
+  color: #409EFF;
+}
+
+.node-label {
+  flex: 1;
+  font-size: 14px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
-.state{
-  position: absolute;
-  top: 35px;
-  right: 10px;
+
+.tree-node-count {
+  background: #409eff;
+  color: #fff;
   padding: 2px 8px;
-  border-radius: 4px;
+  border-radius: 10px;
   font-size: 12px;
-  color: #fff;
+  margin-left: auto;
+}
+
+/* 节点hover效果 */
+.tree-node:hover {
+  background-color: #f0f7ff;
+  border-radius: 4px;
+  padding-left: 4px;
+}
+
+.tree-node:hover .node-icon {
+  color: #2b7bff;
+  transform: scale(1.1);
+}
+
+/* 高亮当前选中的节点 */
+.el-tree-node.is-current > .el-tree-node__content .tree-node {
+  background-color: #e6f7ff;
+  border-radius: 4px;
+  padding-left: 4px;
+}
+
+.el-tree-node.is-current > .el-tree-node__content .node-icon {
+  color: #1890ff;
 }
 
+/* 覆盖默认的el-tree样式,确保自定义样式生效 */
+.el-tree-node__content {
+  height: auto;
+  padding: 0 !important;
+}
 
+.el-tree-node__content:hover {
+  background-color: transparent;
+}
+
+.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
+  background-color: transparent;
+}
+
+/* 树节点展开图标调整 */
+.el-tree-node__expand-icon {
+  color: #909399;
+  font-size: 14px;
+}
+
+.el-tree-node__expand-icon:hover {
+  color: #409EFF;
+}
+
+/* 滚动条美化 */
+.tree-container::-webkit-scrollbar {
+  width: 6px;
+}
+
+.tree-container::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+.tree-container::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+.tree-container::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+
+// 统计卡片
+.stats-overview {
+  margin-bottom: 20px;
+
+  .stat-card {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border-radius: 12px;
+    padding: 20px;
+    color: #fff;
+    display: flex;
+    align-items: center;
+    box-shadow: 0 4px 15px 0 rgba(31, 38, 135, 0.2);
+
+    .stat-icon {
+      font-size: 36px;
+      margin-right: 15px;
+      opacity: 0.9;
+    }
+
+    .stat-content {
+      flex: 1;
+
+      .stat-value {
+        font-size: 28px;
+        font-weight: bold;
+        margin-bottom: 5px;
+      }
+
+      .stat-label {
+        font-size: 14px;
+        opacity: 0.95;
+      }
+    }
+  }
+}
+
+// 筛选栏
+.filter-bar {
+  background: #fff;
+  padding: 15px;
+  border-radius: 8px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+// 充电桩卡片
+.charging-cards {
+  .charging-card {
+    background: #fff;
+    border-radius: 12px;
+    padding: 15px;
+    margin-bottom: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    transition: all 0.3s;
+    cursor: pointer;
+    position: relative;
+    overflow: hidden;
+
+    &:hover {
+      transform: translateY(-5px);
+      box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);
+    }
+
+    &.card-idle {
+      border-left: 4px solid #67c23a;
+    }
+
+    &.card-charging {
+      border-left: 4px solid #409eff;
+      background: linear-gradient(135deg, rgba(64, 158, 255, 0.05) 0%, #fff 100%);
+    }
+
+    &.card-fault {
+      border-left: 4px solid #f56c6c;
+      background: linear-gradient(135deg, rgba(245, 108, 108, 0.05) 0%, #fff 100%);
+    }
+
+    &.card-offline {
+      border-left: 4px solid #909399;
+      opacity: 0.7;
+    }
+
+    .card-status {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 10px;
+
+      .status-badge {
+        padding: 3px 10px;
+        border-radius: 12px;
+        font-size: 12px;
+        font-weight: 500;
+
+        &.status-idle {
+          background: #f0f9ff;
+          color: #67c23a;
+        }
+
+        &.status-charging {
+          background: #f0f9ff;
+          color: #409eff;
+        }
+
+        &.status-fault {
+          background: #fef0f0;
+          color: #f56c6c;
+        }
+
+        &.status-offline {
+          background: #f4f4f5;
+          color: #909399;
+        }
+      }
+
+      .device-code {
+        font-size: 11px;
+        color: #909399;
+      }
+    }
+
+    .card-header {
+      margin-bottom: 15px;
+
+      h3 {
+        margin: 0 0 5px 0;
+        font-size: 16px;
+        color: #303133;
+      }
+
+      .location {
+        margin: 0;
+        font-size: 12px;
+        color: #909399;
+
+        i {
+          margin-right: 3px;
+        }
+      }
+    }
+
+    .charging-info {
+      margin-bottom: 15px;
+      padding: 10px;
+      background: #f5f7fa;
+      border-radius: 8px;
+
+      .charging-progress {
+        margin-bottom: 8px;
+      }
+
+      .charging-time {
+        font-size: 12px;
+        color: #606266;
+
+        i {
+          margin-right: 5px;
+        }
+      }
+    }
+
+    .card-metrics {
+      .metric-row {
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 10px;
+
+        .metric-item {
+          flex: 1;
+          text-align: center;
+
+          .metric-label {
+            display: block;
+            font-size: 12px;
+            color: #909399;
+            margin-bottom: 3px;
+          }
+
+          .metric-value {
+            font-size: 18px;
+            font-weight: bold;
+            color: #303133;
+          }
+
+          .metric-unit {
+            font-size: 12px;
+            color: #606266;
+            margin-left: 2px;
+          }
+        }
+      }
+
+      .power-display {
+        text-align: center;
+        padding: 10px;
+        background: linear-gradient(135deg, #ffa726 0%, #ff7043 100%);
+        border-radius: 8px;
+        color: #fff;
+
+        .power-label {
+          display: block;
+          font-size: 12px;
+          margin-bottom: 5px;
+        }
+
+        .power-value {
+          font-size: 24px;
+          font-weight: bold;
+        }
+
+        .power-unit {
+          font-size: 14px;
+          margin-left: 5px;
+        }
+      }
+    }
+
+    .card-actions {
+      margin-top: 15px;
+      padding-top: 15px;
+      border-top: 1px solid #ebeef5;
+      display: flex;
+      justify-content: space-between;
+    }
+  }
+}
+
+// 详情弹窗中的实时数据卡片
+.realtime-data {
+  padding: 20px 0;
+
+  .data-card {
+    display: flex;
+    align-items: center;
+    padding: 20px;
+    background: #f5f7fa;
+    border-radius: 12px;
+
+    .data-icon {
+      width: 60px;
+      height: 60px;
+      border-radius: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-right: 15px;
+      font-size: 24px;
+      color: #fff;
+
+      &.voltage {
+        background: linear-gradient(135deg, #ffa726 0%, #ff7043 100%);
+      }
+
+      &.current {
+        background: linear-gradient(135deg, #42a5f5 0%, #1e88e5 100%);
+      }
+
+      &.power {
+        background: linear-gradient(135deg, #66bb6a 0%, #43a047 100%);
+      }
+    }
+
+    .data-info {
+      flex: 1;
+
+      .data-value {
+        font-size: 24px;
+        font-weight: bold;
+        color: #303133;
+        margin-bottom: 5px;
+      }
+
+      .data-label {
+        font-size: 14px;
+        color: #909399;
+      }
+    }
+  }
+}
+
+// 响应式
+@media (max-width: 768px) {
+  .charging-cards {
+    .el-col {
+      margin-bottom: 10px;
+    }
+  }
+}
 </style>

+ 700 - 206
ems-ui-cloud/src/views/mgr/powergrid.vue

@@ -1,156 +1,307 @@
 <template>
   <div class="app-container">
-    <el-card>
-      <div class="tips">
-        <el-statistic title="国网接入">
+    <!-- 顶部统计卡片 -->
+    <div class="stats-card">
+      <div class="stats-container">
+        <el-statistic title="国网接入" class="stat-item">
           <template slot="formatter">
-            10kV
+            <span class="stat-value primary">10kV</span>
           </template>
         </el-statistic>
-        <el-statistic title="光伏总装机">
+        <el-statistic title="光伏总装机" class="stat-item">
           <template slot="formatter">
-            {{ pvCfg.total }}kWp
+            <span class="stat-value success">{{ pvCfg.total }}kWp</span>
           </template>
         </el-statistic>
-        <el-statistic :title="`光伏${item.name}装机`" v-for="(item,index) in pvCfg.areas" :key="`pv_${index}`">
+        <el-statistic
+          :title="`光伏${item.name}装机`"
+          v-for="(item,index) in pvCfg.areas"
+          :key="`pv_${index}`"
+          class="stat-item"
+        >
           <template slot="formatter">
-            {{ numToStr(item.pv) }}kWp
+            <span class="stat-value info">{{ numToStr(item.pv) }}kWp</span>
           </template>
         </el-statistic>
       </div>
-    </el-card>
-    <el-tabs v-model="activeName" @tab-click="tabClick">
-      <el-tab-pane label="总览" name="summery">
-        <el-row type="flex" :gutter="20" style="margin-top: 20px">
+    </div>
+
+    <!-- 主内容区域 -->
+    <el-tabs v-model="activeName" @tab-click="tabClick" class="main-tabs">
+      <!-- 总览标签页 -->
+      <el-tab-pane name="summery">
+        <span slot="label">
+          <i class="el-icon-data-analysis"></i> 总览
+        </span>
+        <el-row :gutter="20" style="margin-top: 20px">
           <el-col :span="12">
-            <PieChartBlock title="当日供电量【单位:kW·h】" :opt-cfg="elecQuantity">
-            </PieChartBlock>
+            <div class="chart-card">
+              <PieChartBlock title="当日供电量【单位:kW·h】" :opt-cfg="elecQuantity" />
+            </div>
           </el-col>
           <el-col :span="12">
-            <PieChartBlock title="当日电费【单位:元】" :opt-cfg="elecCost">
-            </PieChartBlock>
+            <div class="chart-card">
+              <PieChartBlock title="当日电费【单位:元】" :opt-cfg="elecCost" />
+            </div>
           </el-col>
         </el-row>
-        <el-row type="flex" :gutter="20" style="margin-top: 20px">
+        <el-row :gutter="20" style="margin-top: 20px">
           <el-col :span="24">
-            <BarChartBlock title="当日供电量柱状图" :opt-cfg="pvSupplyIndex"/>
+            <div class="chart-card">
+              <BarChartBlock title="当日供电量柱状图" :opt-cfg="pvSupplyIndex" />
+            </div>
           </el-col>
         </el-row>
       </el-tab-pane>
-      <el-tab-pane label="市电" name="first">
-        <el-col :span="4" :xs="24">
-          <div class="head-container">
-            <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small"
-                      prefix-icon="el-icon-search"
-                      style="margin-bottom: 20px"/>
-          </div>
-          <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-            <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                     :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                     @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;"/>
-          </div>
-        </el-col>
-        <el-col :span="20" :xs="24">
-          <div class="container-block">
-            <SubTitle :title="`时段供电数据【${selectedLabel}】`"/>
-            <BaseChart width="100%" height="300px" :option="elecOptions"/>
-          </div>
-          <div class="container-block">
-            <div class="ctl-container">
-              <el-date-picker v-model="dateRange" type="datetimerange" @change="getList"
-                              value-format="yyyy-MM-dd hh:mm:ss" :picker-options="pickerOptions" range-separator="至"
-                              start-placeholder="开始日期" end-placeholder="结束日期" align="right">
-              </el-date-picker>
+
+      <!-- 市电标签页 -->
+      <el-tab-pane name="first">
+        <span slot="label">
+          <i class="el-icon-lightning"></i> 市电
+        </span>
+        <el-row :gutter="20">
+          <!-- 左侧树形区域 -->
+          <el-col :span="5" :xs="24">
+            <div class="head-container">
+              <el-input
+                v-model="areaName"
+                placeholder="请输入区域名称"
+                clearable
+                size="small"
+                prefix-icon="el-icon-search"
+                style="margin-bottom: 20px"
+                @input="filterTree"
+              />
+            </div>
+            <div class="head-container tree-container">
+              <el-tree
+                ref="tree"
+                :data="areaOptions"
+                :props="defaultProps"
+                :expand-on-click-node="false"
+                :filter-node-method="filterNode"
+                node-key="id"
+                :default-expanded-keys="defaultExpandedKeys"
+                highlight-current
+                @node-click="handleNodeClick"
+              >
+                <span class="custom-tree-node" slot-scope="{ node, data }">
+                  <span class="tree-label">
+                    <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                    {{ node.label }}
+                  </span>
+                  <el-tag
+                    v-if="data.facsCategory === 'W'"
+                    size="mini"
+                    effect="plain"
+                    class="tree-tag"
+                  >
+                    市电
+                  </el-tag>
+                </span>
+              </el-tree>
             </div>
-            <el-table v-loading="loading" :data="pgSupplyHList">
-              <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="计量类型" align="center" prop="meterType">
-                <template slot-scope="scope">
-                  <span>{{ getMeterTypeName(scope.row.meterType) }}</span>
-                </template>
-              </el-table-column>
-              <el-table-column label="单位电价(¥)" align="center" prop="meterUnitPrice"/>
-              <el-table-column label="供电量(kW·h)" align="center" prop="useElecQuantity"/>
-              <el-table-column label="供电电费(¥)" align="center" prop="useElecCost"/>
-            </el-table>
-            <pagination :total="total" :page-size.sync="queryParams.pageSize" :page-sizes="[10, 20, 50]"
-                        :page.sync="queryParams.pageNum" @pagination="getList"/>
-          </div>
-        </el-col>
+          </el-col>
+
+          <!-- 右侧内容区域 -->
+          <el-col :span="19" :xs="24">
+            <div class="content-wrapper">
+              <!-- 图表区域 -->
+              <div class="chart-section">
+                <SubTitle :title="`时段供电数据【${selectedLabel}】`" />
+                <BaseChart width="100%" height="300px" :option="elecOptions" />
+              </div>
+
+              <!-- 数据表格区域 -->
+              <div class="table-section">
+                <div class="control-container">
+                  <el-date-picker
+                    v-model="dateRange"
+                    type="datetimerange"
+                    @change="getList"
+                    value-format="yyyy-MM-dd HH:mm:ss"
+                    :picker-options="pickerOptions"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    align="right"
+                    size="small"
+                  />
+                </div>
+                <el-table v-loading="loading" :data="pgSupplyHList" class="data-table">
+                  <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" />
+                  <el-table-column label="计量类型" align="center" prop="meterType">
+                    <template slot-scope="scope">
+                      <el-tag :type="getMeterTypeTag(scope.row.meterType)" size="small">
+                        {{ getMeterTypeName(scope.row.meterType) }}
+                      </el-tag>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="单位电价(¥)" align="center" prop="meterUnitPrice">
+                    <template slot-scope="scope">
+                      <span class="data-value">{{ scope.row.meterUnitPrice }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="供电量(kW·h)" align="center" prop="useElecQuantity">
+                    <template slot-scope="scope">
+                      <span class="data-value primary">{{ scope.row.useElecQuantity }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="供电电费(¥)" align="center" prop="useElecCost">
+                    <template slot-scope="scope">
+                      <span class="data-value warning">{{ scope.row.useElecCost }}</span>
+                    </template>
+                  </el-table-column>
+                </el-table>
+                <pagination
+                  :total="total"
+                  :page-size.sync="queryParams.pageSize"
+                  :page-sizes="[10, 20, 50]"
+                  :page.sync="queryParams.pageNum"
+                  @pagination="getList"
+                />
+              </div>
+            </div>
+          </el-col>
+        </el-row>
       </el-tab-pane>
-      <el-tab-pane label="光伏" name="second">
-        <el-col :span="4" :xs="24">
-          <div class="head-container">
-            <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small"
-                      prefix-icon="el-icon-search"
-                      style="margin-bottom: 20px"/>
-          </div>
-          <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-            <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                     :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                     @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;"/>
-          </div>
-        </el-col>
-        <el-col :span="20" :xs="24">
-          <div class="container-block">
-            <SubTitle :title="`时段发电数据【${selectedLabel}】`"/>
-            <BaseChart width="100%" height="300px" :option="pvOptions"/>
-          </div>
-          <div class="container-block">
-            <div class="ctl-container">
-              <el-date-picker v-model="dateRange" type="datetimerange" @change="getList" :picker-options="pickerOptions"
-                              value-format="yyyy-MM-dd hh:mm:ss" range-separator="至" start-placeholder="开始日期"
-                              end-placeholder="结束日期"
-                              align="right">
-              </el-date-picker>
+
+      <!-- 光伏标签页 -->
+      <el-tab-pane name="second">
+        <span slot="label">
+          <i class="el-icon-sunny"></i> 光伏
+        </span>
+        <el-row :gutter="20">
+          <!-- 左侧树形区域 -->
+          <el-col :span="5" :xs="24">
+            <div class="head-container">
+              <el-input
+                v-model="areaName"
+                placeholder="请输入区域名称"
+                clearable
+                size="small"
+                prefix-icon="el-icon-search"
+                style="margin-bottom: 20px"
+                @input="filterTree"
+              />
+            </div>
+            <div class="head-container tree-container">
+              <el-tree
+                ref="tree"
+                :data="areaOptions"
+                :props="defaultProps"
+                :expand-on-click-node="false"
+                :filter-node-method="filterNode"
+                node-key="id"
+                :default-expanded-keys="defaultExpandedKeys"
+                highlight-current
+                @node-click="handleNodeClick"
+              >
+                <span class="custom-tree-node" slot-scope="{ node, data }">
+                  <span class="tree-label">
+                    <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                    {{ node.label }}
+                  </span>
+                  <el-tag
+                    v-if="data.facsCategory === 'E'"
+                    size="mini"
+                    effect="plain"
+                    class="tree-tag warning"
+                  >
+                    光伏
+                  </el-tag>
+                </span>
+              </el-tree>
+            </div>
+          </el-col>
+
+          <!-- 右侧内容区域 -->
+          <el-col :span="19" :xs="24">
+            <div class="content-wrapper">
+              <!-- 图表区域 -->
+              <div class="chart-section">
+                <SubTitle :title="`时段发电数据【${selectedLabel}】`" />
+                <BaseChart width="100%" height="300px" :option="pvOptions" />
+              </div>
+
+              <!-- 数据表格区域 -->
+              <div class="table-section">
+                <div class="control-container">
+                  <el-date-picker
+                    v-model="dateRange"
+                    type="datetimerange"
+                    @change="getList"
+                    :picker-options="pickerOptions"
+                    value-format="yyyy-MM-dd HH:mm:ss"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    align="right"
+                    size="small"
+                  />
+                </div>
+                <el-table v-loading="loading" :data="pvSupplyHList" class="data-table">
+                  <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" />
+                  <el-table-column label="总发电量(kW·h)" align="center" prop="genElecQuantity">
+                    <template slot-scope="scope">
+                      <span class="data-value success">{{ scope.row.genElecQuantity }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="自用电量(kW·h)" align="center" prop="useElecQuantity">
+                    <template slot-scope="scope">
+                      <span class="data-value info">{{ scope.row.useElecQuantity }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="上网电量(kW·h)" align="center" prop="upElecQuantity">
+                    <template slot-scope="scope">
+                      <span class="data-value primary">{{ scope.row.upElecQuantity }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="上网收益(¥)" align="center" prop="upElecEarn">
+                    <template slot-scope="scope">
+                      <span class="data-value warning">{{ scope.row.upElecEarn }}</span>
+                    </template>
+                  </el-table-column>
+                </el-table>
+                <pagination
+                  :total="total"
+                  :page-size.sync="queryParams.pageSize"
+                  :page-sizes="[10, 20, 50]"
+                  :page.sync="queryParams.pageNum"
+                  @pagination="getList"
+                />
+              </div>
             </div>
-            <el-table v-loading="loading" :data="pvSupplyHList">
-              <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="genElecQuantity"/>
-              <el-table-column label="自用电量(kW·h)" align="center" prop="useElecQuantity"/>
-              <el-table-column label="上网电量(kW·h)" align="center" prop="upElecQuantity"/>
-              <el-table-column label="上网收益(¥)" align="center" prop="upElecEarn"/>
-            </el-table>
-            <pagination :total="total" :page-size.sync="queryParams.pageSize" :page-sizes="[10, 20, 50]"
-                        :page.sync="queryParams.pageNum" @pagination="getList"/>
-          </div>
-        </el-col>
+          </el-col>
+        </el-row>
       </el-tab-pane>
     </el-tabs>
   </div>
 </template>
 
 <script>
-import {ApiCode} from '@/api/apiEmums'
-import {areaTreeByFacsCategory} from '@/api/basecfg/area'
-import {get} from '@/api/commonApi'
-import {listPgSupplyH, listPvSupplyH} from '@/api/mgr/pgSupplyH'
+import { ApiCode } from '@/api/apiEmums'
+import { areaTreeByFacsCategory } from '@/api/basecfg/area'
+import { get } from '@/api/commonApi'
+import { listPgSupplyH, listPvSupplyH } from '@/api/mgr/pgSupplyH'
 import BaseChart from '@/components/BaseChart'
 import BarChartBlock from '@/components/Block/charts/BarChartBlock.vue'
 import PieChartBlock from '@/components/Block/charts/PieChartBlock.vue'
 import SubTitle from '@/components/SubTitle'
-import {DateTool} from '@/utils/DateTool'
+import { DateTool } from '@/utils/DateTool'
 import dayjs from 'dayjs'
-import {listConfig} from "@/api/system/config";
-import {numToStr} from "@/utils";
+import { listConfig } from "@/api/system/config"
+import { numToStr } from "@/utils"
 
 export default {
   name: 'PgSupplyH',
@@ -164,13 +315,11 @@ export default {
   data() {
     return {
       activeName: 'summery',
-      // 遮罩层
       loading: true,
-      // 总条数
       total: 0,
       facsCategory: '',
       facsSubCategory: '',
-      areaName: undefined,
+      areaName: '',
       selectedLabel: '全部',
       pgSupplyHList: [],
       pgSupplyTodayList: [],
@@ -180,19 +329,24 @@ export default {
         children: 'children',
         label: 'label'
       },
+      defaultExpandedKeys: [],
       pvCfg: {
         total: 0,
         areas: []
       },
-      // 查询参数
       queryParams: {
         areaCode: '-1',
         startRecTime: dayjs().subtract(1, 'month').format(DateTool.DateFormat.YYYY_MM_DD_HH_mm),
-        endRecTime: dayjs().format(DateTool.DateFormat.YYYY_MM_DD_HH_mm)
+        endRecTime: dayjs().format(DateTool.DateFormat.YYYY_MM_DD_HH_mm),
+        pageNum: 1,
+        pageSize: 10
       },
       areaOptions: [],
       elecData: [],
-      dateRange: [dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00), dayjs().format(DateTool.DateFormat.YYYY_MM_DD_23_59_59)],
+      dateRange: [
+        dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00),
+        dayjs().format(DateTool.DateFormat.YYYY_MM_DD_23_59_59)
+      ],
       pickerOptions: {
         shortcuts: [
           {
@@ -203,7 +357,8 @@ export default {
               start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
               picker.$emit('pick', [start, end])
             }
-          }, {
+          },
+          {
             text: '最近一个月',
             onClick(picker) {
               const end = new Date()
@@ -211,7 +366,8 @@ export default {
               start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
               picker.$emit('pick', [start, end])
             }
-          }, {
+          },
+          {
             text: '最近三个月',
             onClick(picker) {
               const end = new Date()
@@ -244,7 +400,7 @@ export default {
                   align: 'center'
                 }
               },
-              formatter: this.getLabelContentForElecQuantity // 使用方法生成标签内容
+              formatter: this.getLabelContentForElecQuantity
             }
           }
         ]
@@ -271,7 +427,7 @@ export default {
                   align: 'center'
                 }
               },
-              formatter: this.getLabelContentForElecCost // 使用方法生成标签内容
+              formatter: this.getLabelContentForElecCost
             }
           }
         ]
@@ -301,7 +457,6 @@ export default {
             return relVal
           }
         },
-
         series: []
       },
       totalSupply: 0,
@@ -313,7 +468,8 @@ export default {
       const xData = this.pgSupplyTodayList.map(item => item.time)
       const quantity = this.pgSupplyTodayList.map(item => item.useElecQuantity)
       const cost = this.pgSupplyTodayList.map(item => item.useElecCost)
-      const option = {
+
+      return {
         tooltip: {
           trigger: 'axis',
           axisPointer: {
@@ -334,6 +490,12 @@ export default {
         legend: {
           data: ['供电量', '供电电费']
         },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          containLabel: true
+        },
         xAxis: {
           type: 'category',
           data: xData.reverse(),
@@ -364,8 +526,7 @@ export default {
             data: quantity.reverse(),
             itemStyle: {
               normal: {
-                color: '#6395FA',
-
+                color: '#6395FA'
               }
             }
           },
@@ -377,17 +538,15 @@ export default {
             smooth: false,
             itemStyle: {
               normal: {
-                color: '#5BD9A5',
-
+                color: '#5BD9A5'
               }
             }
           }
         ]
       }
-      return option
     },
     pvOptions() {
-      const option = {
+      return {
         tooltip: {
           trigger: 'axis',
           axisPointer: {
@@ -408,6 +567,12 @@ export default {
         legend: {
           data: ['自用电量', '上网电量', '上网收益']
         },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          containLabel: true
+        },
         xAxis: {
           type: 'category',
           data: this.pvSupplyTodayList.map(item => item.time).reverse(),
@@ -440,10 +605,9 @@ export default {
               normal: {
                 color: '#6395FA',
                 label: {
-                  show: true, // 开启显示
-                  position: 'top', // 在上方显示
+                  show: true,
+                  position: 'top',
                   textStyle: {
-                    // 数值样式
                     color: '#000',
                     fontSize: 14,
                     fontWeight: 600
@@ -466,10 +630,9 @@ export default {
               normal: {
                 color: '#8CDF6C',
                 label: {
-                  show: true, // 开启显示
-                  position: 'top', // 在上方显示
+                  show: true,
+                  position: 'top',
                   textStyle: {
-                    // 数值样式
                     color: '#000',
                     fontSize: 14,
                     fontWeight: 600
@@ -483,17 +646,13 @@ export default {
             type: 'line',
             yAxisIndex: 1,
             data: this.pvSupplyTodayList.map(item => item.upElecEarn).reverse(),
-            smooth: false
+            smooth: false,
+            itemStyle: {
+              color: '#FF9F7F'
+            }
           }
         ]
       }
-      return option
-    }
-  },
-  watch: {
-    // 根据名称筛选区域树
-    areaName(val) {
-      this.$refs.tree.filter(val)
     }
   },
   async created() {
@@ -502,18 +661,51 @@ export default {
   },
   methods: {
     numToStr,
+
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.facsCategory === 'E') {
+        return 'el-icon-sunny'
+      }
+      if (data.facsCategory === 'W') {
+        return 'el-icon-lightning'
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 获取计量类型标签样式
+    getMeterTypeTag(meterType) {
+      const typeMap = {
+        '-1': 'success',
+        '0': '',
+        '1': 'warning',
+        '2': 'danger'
+      }
+      return typeMap[meterType] || ''
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     getLabelContentForElecQuantity() {
-      return `总供电{totalSupply|${this.totalSupply} kW·h}`;
+      return `总供电{totalSupply|${this.totalSupply} kW·h}`
     },
+
     getLabelContentForElecCost() {
-      return `供电成本{totalCost|${this.totalCost} 元}`;
+      return `供电成本{totalCost|${this.totalCost} 元}`
     },
+
     async getPvConfig() {
-      const {rows} = await listConfig({
+      const { rows } = await listConfig({
         configKey: "storage-equipped-capacity",
       })
       if (!rows || !rows.length) {
-        return;
+        return
       }
       const cfg = JSON.parse(rows[0].remark)
       const areas = []
@@ -529,9 +721,13 @@ export default {
         areas: areas
       }
     },
+
     tabClick() {
       if (this.activeName !== 'summery') {
-        this.dateRange = [dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00), dayjs().format(DateTool.DateFormat.YYYY_MM_DD_23_59_59)]
+        this.dateRange = [
+          dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00),
+          dayjs().format(DateTool.DateFormat.YYYY_MM_DD_23_59_59)
+        ]
         this.queryParams.areaCode = '-1'
         this.queryParams.pageNum = 1
         this.selectedLabel = '全部'
@@ -541,29 +737,23 @@ export default {
         this.getSummery()
       }
     },
+
     async getSummery() {
       await this.getHSummery()
       await this.getThisDaySummery()
     },
+
     async getThisDaySummery() {
-      const {
-        data,
-        code
-      } = await get('pg/supply/hour/summery/this/day')
+      const { data, code } = await get('pg/supply/hour/summery/this/day')
       if (ApiCode.SUCCESS !== code || !data) {
         this.elecQuantity.series[0].data = []
         this.elecCost.series[0].data = []
         return
       }
-      const {
-        pv,
-        supply
-      } = data
+      const { pv, supply } = data
 
-      // 计算总供电量
-      const totalSupply = (supply?.quantity || 0) + (pv?.useQuantity || 0);
-      // 计算总电费
-      const totalCost = (supply?.cost || 0) + (pv?.upEarn || 0);
+      const totalSupply = (supply?.quantity || 0) + (pv?.useQuantity || 0)
+      const totalCost = (supply?.cost || 0) + (pv?.upEarn || 0)
 
       this.elecQuantity.series[0].data = [
         {
@@ -572,7 +762,6 @@ export default {
           itemStyle: {
             color: '#6395FA'
           }
-
         },
         {
           value: pv?.useQuantity,
@@ -598,29 +787,22 @@ export default {
           }
         }
       ]
-      // 更新饼图中间显示的总和
-      this.totalSupply = totalSupply;
-      this.totalCost = totalCost;
+      this.totalSupply = totalSupply
+      this.totalCost = totalCost
     },
 
     async getHSummery() {
-      const {
-        data,
-        code
-      } = await get('pg/supply/hour/summery/h')
+      const { data, code } = await get('pg/supply/hour/summery/h')
       if (ApiCode.SUCCESS !== code || !data || data.length < 1) {
         return
       }
       const xaxis = DateTool.getTime(60)
-      const {
-        supply,
-        pv
-      } = data
+      const { supply, pv } = data
       const series = [
         {
           name: '市电',
           type: 'bar',
-          barWidth: '30%', // 调整柱状图宽度
+          barWidth: '30%',
           data: [],
           itemStyle: {
             color: '#6395FA'
@@ -629,13 +811,12 @@ export default {
         {
           name: '光伏',
           type: 'bar',
-          barWidth: '30%', // 调整柱状图宽度
+          barWidth: '30%',
           data: [],
           itemStyle: {
             color: '#8CDF6C'
           }
         }
-
       ]
       xaxis.forEach((item, index) => {
         let timeIndex = index + 1
@@ -653,14 +834,21 @@ export default {
       this.pvSupplyIndex.series = series
       this.pvSupplyIndex.xAxis.data = xaxis
     },
+
     getTodayList() {
       if (this.activeName === 'first') {
         areaTreeByFacsCategory('W', this.facsSubCategory, false).then(response => {
           this.areaOptions = [{
             id: '-1',
             label: '全部',
-            children: []
-          }].concat(response.data)
+            children: response.data || []
+          }]
+          this.defaultExpandedKeys = ['-1']
+          this.$nextTick(() => {
+            if (this.$refs.tree) {
+              this.$refs.tree.setCurrentKey('-1')  // 设置默认选中全部
+            }
+          })
         })
         listPgSupplyH({
           startRecTime: dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00),
@@ -676,8 +864,14 @@ export default {
           this.areaOptions = [{
             id: '-1',
             label: '全部',
-            children: []
-          }].concat(response.data)
+            children: response.data || []
+          }]
+          this.defaultExpandedKeys = ['-1']
+          this.$nextTick(() => {
+            if (this.$refs.tree) {
+              this.$refs.tree.setCurrentKey('-1')  // 设置默认选中全部
+            }
+          })
         })
         listPvSupplyH({
           startRecTime: dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00),
@@ -690,6 +884,7 @@ export default {
         })
       }
     },
+
     getList() {
       this.loading = true
       this.queryParams.startRecTime = ''
@@ -700,15 +895,13 @@ export default {
         this.queryParams.endRecTime = endRecTime
       }
       if (this.activeName === 'first') {
-        listPgSupplyH(
-          {
-            startRecTime: this.queryParams.startRecTime,
-            endRecTime: this.queryParams.endRecTime,
-            areaCode: this.queryParams.areaCode,
-            pageNum: 1,
-            pageSize: 999
-          }
-        ).then(response => {
+        listPgSupplyH({
+          startRecTime: this.queryParams.startRecTime,
+          endRecTime: this.queryParams.endRecTime,
+          areaCode: this.queryParams.areaCode,
+          pageNum: this.queryParams.pageNum,
+          pageSize: this.queryParams.pageSize
+        }).then(response => {
           this.pgSupplyHList = response.rows
           this.total = response.total
           this.loading = false
@@ -718,8 +911,8 @@ export default {
           startRecTime: this.queryParams.startRecTime,
           endRecTime: this.queryParams.endRecTime,
           areaCode: this.queryParams.areaCode,
-          pageNum: 1,
-          pageSize: 999
+          pageNum: this.queryParams.pageNum,
+          pageSize: this.queryParams.pageSize
         }).then(response => {
           this.pvSupplyHList = response.rows
           this.total = response.total
@@ -727,28 +920,29 @@ export default {
         })
       }
     },
-    // 筛选节点
+
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
-    // 节点单击事件
+
     handleNodeClick(data) {
       this.queryParams.areaCode = data.id
       this.selectedLabel = data.label
       this.handleQuery()
     },
-    /** 搜索按钮操作 */
+
     handleQuery() {
       this.queryParams.pageNum = 1
       this.getTodayList()
       this.getList()
     },
+
     getMeterTypeName(meterType) {
       const meterTypeMap = {
         '-1': '低谷电',
         '0': '平峰电',
-        '1': '高峰',
+        '1': '高峰',
         '2': '尖峰电'
       }
       return meterTypeMap[meterType] || '平峰电'
@@ -756,17 +950,317 @@ export default {
   }
 }
 </script>
+
 <style lang="scss" scoped>
 .app-container {
-  padding-top: 10px;
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  // 总览页面的图表卡片样式
+  .chart-card {
+    background: #fff;
+    border-radius: 12px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
+    height: 100%;
+
+    ::v-deep .el-card {
+      border-radius: 12px;
+    }
+  }
+
+  // 顶部统计卡片
+  .stats-card {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    margin-bottom: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    .stats-container {
+      display: flex;
+      justify-content: space-around;
+      flex-wrap: wrap;
+
+      .stat-item {
+        flex: 1;
+        min-width: 150px;
+        text-align: center;
+        padding: 10px;
+
+        ::v-deep .el-statistic__head {
+          color: #409eff;
+          font-size: 14px;
+          margin-bottom: 10px;
+        }
+
+        .stat-value {
+          font-size: 24px;
+          font-weight: 600;
+
+          &.primary {
+            color: #409eff;
+          }
+
+          &.success {
+            color: #67c23a;
+          }
+
+          &.info {
+            color: #409eff;
+          }
+        }
+      }
+    }
+  }
+
+  // 主标签页样式
+  .main-tabs {
+    ::v-deep .el-tabs__header {
+      margin: 0 0 20px;
+      background: #fff;
+      padding: 0 20px;
+      border-radius: 8px 8px 0 0;
+      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    }
+
+    ::v-deep .el-tabs__nav-wrap {
+      &::after {
+        height: 1px;
+        background-color: #e4e7ed;
+      }
+    }
+
+    ::v-deep .el-tabs__item {
+      height: 50px;
+      line-height: 50px;
+      font-size: 15px;
+      font-weight: 500;
+      color: #606266;
+
+      i {
+        margin-right: 5px;
+        font-size: 18px;
+      }
+
+      &:hover {
+        color: #409eff;
+      }
+
+      &.is-active {
+        color: #409eff;
+        font-weight: 600;
+      }
+    }
+
+    ::v-deep .el-tabs__active-bar {
+      height: 3px;
+      background: linear-gradient(90deg, #409eff 0%, #53a8ff 100%);
+    }
+
+    ::v-deep .el-tabs__content {
+      overflow: initial;
+    }
+  }
+
+  // 树容器样式
+  .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 - 350px);
+      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;
+
+          .tree-icon {
+            color: #409eff !important;
+          }
+        }
 
-  ::v-deep .el-tabs__content {
-    overflow: initial;
+        .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;
+              transition: color 0.3s;
+            }
+          }
+
+          .tree-tag {
+            margin-right: 8px;
+
+            &.warning {
+              color: #e6a23c;
+              background: #fdf6ec;
+              border-color: #f5dab1;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 光伏tab下的树节点图标特殊样式
+  .pv-tab-pane {
+    .tree-container {
+      ::v-deep .el-tree {
+        .custom-tree-node {
+          .tree-label {
+            .tree-icon {
+              color: #409eff !important; // 光伏tab下图标都显示为蓝色
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 右侧内容区域
+  .content-wrapper {
+    background: #fff;
+    border-radius: 12px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
+
+    .chart-section {
+      padding: 20px;
+      border-bottom: 1px solid #ebeef5;
+
+      ::v-deep .sub-title {
+        font-size: 16px;
+        font-weight: 600;
+        color: #303133;
+        margin-bottom: 15px;
+      }
+    }
+
+    .table-section {
+      padding: 20px;
+
+      .control-container {
+        display: flex;
+        justify-content: flex-end;
+        margin-bottom: 15px;
+
+        ::v-deep .el-date-editor {
+          width: 400px;
+        }
+      }
+
+      .data-table {
+        ::v-deep .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+          }
+        }
+
+        ::v-deep .el-table__body {
+          .data-value {
+            font-weight: 500;
+
+            &.primary {
+              color: #409eff;
+            }
+
+            &.success {
+              color: #67c23a;
+            }
+
+            &.info {
+              color: #409eff;
+            }
+
+            &.warning {
+              color: #e6a23c;
+            }
+          }
+        }
+      }
+    }
   }
 }
 
-.tips {
-  display: flex;
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .stats-card {
+      .stats-container {
+        flex-direction: column;
+
+        .stat-item {
+          margin-bottom: 10px;
+        }
+      }
+    }
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+
+    .content-wrapper {
+      .table-section {
+        .control-container {
+          justify-content: center;
+
+          ::v-deep .el-date-editor {
+            width: 100%;
+          }
+        }
+      }
+    }
+  }
 }
 </style>
 <style lang="scss" scoped src="./index.scss"></style>

+ 531 - 139
ems-ui-cloud/src/views/mgr/powerstore.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-card>
+    <el-card class="header-card">
       <div class="tips">
         <el-statistic title="总装机">
           <template slot="formatter">
@@ -14,90 +14,148 @@
         </el-statistic>
       </div>
     </el-card>
-    <el-tabs v-model="activeName" @tab-click="tabClick">
+
+    <el-tabs v-model="activeName" @tab-click="tabClick" class="main-tabs">
       <el-tab-pane label="总览" name="summary">
         <div class="chartGroup">
-          <Block title="当日充电量【单位:kW·h】">
-            <div class="block-area" slot="main">
+          <!-- 当日充电量卡片 -->
+          <div class="chart-card">
+            <div class="chart-header">
+              <i class="el-icon-s-data"></i>
+              <span>当日充电量【单位:kW·h】</span>
+            </div>
+            <div class="chart-body">
               <div class="center-label">
-                <div>总充电量</div>
-                <div>{{ daySum.chargeElecQuantity }}kW·h</div>
+                <div class="label-title">总充电量</div>
+                <div class="label-value">{{ daySum.chargeElecQuantity || 0 }}kW·h</div>
               </div>
-              <BaseChart width="100%" height="500px" :option="chargePieOptions"/>
+              <BaseChart width="100%" height="450px" :option="chargePieOptions"/>
+            </div>
+          </div>
+
+          <!-- 当日放电量卡片 -->
+          <div class="chart-card">
+            <div class="chart-header">
+              <i class="el-icon-s-marketing"></i>
+              <span>当日放电量【单位:kW·h】</span>
             </div>
-          </Block>
-          <Block title="当日放电量【单位:kW·h】">
-            <div class="block-area" slot="main">
+            <div class="chart-body">
               <div class="center-label">
-                <div>总放电量</div>
-                <div>{{ daySum.dischargeElecQuantity }}kW·h</div>
+                <div class="label-title">总放电量</div>
+                <div class="label-value">{{ daySum.dischargeElecQuantity || 0 }}kW·h</div>
               </div>
-              <BaseChart width="100%" height="500px" :option="disChargePieOptions"/>
+              <BaseChart width="100%" height="450px" :option="disChargePieOptions"/>
             </div>
-          </Block>
+          </div>
         </div>
       </el-tab-pane>
+
       <el-tab-pane label="区块储能" name="area">
-        <el-col :span="4" :xs="24">
-          <div class="head-container">
-            <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small"
-                      prefix-icon="el-icon-search"
-                      style="margin-bottom: 20px"/>
-          </div>
-          <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-            <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                     :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                     @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;"/>
-          </div>
-        </el-col>
-        <el-col :span="20" :xs="24">
-          <div class="container-block">
-            <SubTitle :title="`时段储能数据【${selectedLabel}】`"/>
-            <BaseChart width="100%" height="300px" :option="elecOptions"/>
-          </div>
-          <div class="container-block">
-            <div class="ctl-container">
-              <el-date-picker v-model="dateRange" type="datetimerange" @change="getList" :picker-options="pickerOptions"
-                              value-format="yyyy-MM-dd hh:mm:ss" range-separator="至" start-placeholder="开始日期"
-                              end-placeholder="结束日期"
-                              align="right">
-              </el-date-picker>
+        <el-row :gutter="20">
+          <!-- 左侧树形区域 -->
+          <el-col :span="5" :xs="24">
+            <div class="head-container">
+              <el-input
+                v-model="areaName"
+                placeholder="请输入区域名称"
+                clearable
+                size="small"
+                prefix-icon="el-icon-search"
+                style="margin-bottom: 20px"
+                @input="filterTree"
+              />
             </div>
-            <el-table v-loading="loading" :data="elecStoreHList" max-height="400px">
-              <el-table-column label="日期" align="center" prop="date" width="180">
-              </el-table-column>
-              <el-table-column label="时间" align="center" prop="time">
-              </el-table-column>
-              <el-table-column label="充电电量(kW·h)" align="center" prop="chargeElecQuantity"/>
-              <el-table-column label="放电电量(kW·h)" align="center" prop="dischargeElecQuantity"/>
-            </el-table>
-          </div>
-        </el-col>
+            <div class="head-container tree-container">
+              <el-tree
+                ref="tree"
+                :data="areaOptions"
+                :props="defaultProps"
+                :expand-on-click-node="false"
+                :filter-node-method="filterNode"
+                node-key="id"
+                default-expand-all
+                highlight-current
+                @node-click="handleNodeClick"
+              >
+                <span class="custom-tree-node" slot-scope="{ node, data }">
+                  <span class="tree-label">
+                    <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                    {{ node.label }}
+                  </span>
+                  <el-tag
+                    v-if="data.type === 'storage'"
+                    size="mini"
+                    effect="plain"
+                    class="tree-tag"
+                  >
+                    储能
+                  </el-tag>
+                </span>
+              </el-tree>
+            </div>
+          </el-col>
+
+          <!-- 右侧内容区域 -->
+          <el-col :span="19" :xs="24">
+            <div class="container-block">
+              <SubTitle :title="`时段储能数据【${selectedLabel}】`"/>
+              <BaseChart width="100%" height="300px" :option="elecOptions"/>
+            </div>
+
+            <div class="container-block">
+              <div class="ctl-container">
+                <el-date-picker
+                  v-model="dateRange"
+                  type="datetimerange"
+                  @change="getList"
+                  :picker-options="pickerOptions"
+                  value-format="yyyy-MM-dd HH:mm:ss"
+                  range-separator="至"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  align="right">
+                </el-date-picker>
+              </div>
+              <el-table
+                v-loading="loading"
+                :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" />
+                <el-table-column label="充电电量(kW·h)" align="center" prop="chargeElecQuantity">
+                  <template slot-scope="scope">
+                    <span class="text-primary">{{ scope.row.chargeElecQuantity || 0 }}</span>
+                  </template>
+                </el-table-column>
+                <el-table-column label="放电电量(kW·h)" align="center" prop="dischargeElecQuantity">
+                  <template slot-scope="scope">
+                    <span class="text-success">{{ scope.row.dischargeElecQuantity || 0 }}</span>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </div>
+          </el-col>
+        </el-row>
       </el-tab-pane>
     </el-tabs>
   </div>
 </template>
 
 <script>
-import {dayStatistics, listElecStoreH} from '@/api/mgr/elecStoreH'
-import {areaTreeByFacsCategory} from '@/api/basecfg/area'
-import {dateFormat, numToStr} from '@/utils/index.js'
+import { dayStatistics, listElecStoreH } from '@/api/mgr/elecStoreH'
+import { areaTreeByFacsCategory } from '@/api/basecfg/area'
+import { dateFormat, numToStr } from '@/utils/index.js'
 import dayjs from 'dayjs'
-import {DateTool} from '@/utils/DateTool'
+import { DateTool } from '@/utils/DateTool'
 import BaseChart from '@/components/BaseChart'
-import SubTitle from '@/components/SubTitle'
-import Block from '@/components/Block/block.vue'
-import Treeselect from "@riophae/vue-treeselect";
-import "@riophae/vue-treeselect/dist/vue-treeselect.css";
-import {listConfig} from "@/api/system/config";
+import { listConfig } from "@/api/system/config"
 
 export default {
   name: 'ElecStoreH',
   components: {
-    Treeselect,
-    BaseChart,
-    Block,
-    SubTitle
+    BaseChart
   },
   data() {
     return {
@@ -118,6 +176,7 @@ export default {
         areas: []
       },
       selectedLabel: '',
+      areaType: '',
       // 查询参数
       queryParams: {
         areaCode: ''
@@ -125,6 +184,28 @@ export default {
       pickerOptions: {
         shortcuts: [
           {
+            text: '今天',
+            onClick(picker) {
+              const start = new Date()
+              start.setHours(0, 0, 0, 0)
+              const end = new Date()
+              end.setHours(23, 59, 59, 999)
+              picker.$emit('pick', [start, end])
+            }
+          },
+          {
+            text: '昨天',
+            onClick(picker) {
+              const start = new Date()
+              start.setTime(start.getTime() - 3600 * 1000 * 24)
+              start.setHours(0, 0, 0, 0)
+              const end = new Date()
+              end.setTime(end.getTime() - 3600 * 1000 * 24)
+              end.setHours(23, 59, 59, 999)
+              picker.$emit('pick', [start, end])
+            }
+          },
+          {
             text: '最近一周',
             onClick(picker) {
               const end = new Date()
@@ -132,7 +213,8 @@ export default {
               start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
               picker.$emit('pick', [start, end])
             }
-          }, {
+          },
+          {
             text: '最近一个月',
             onClick(picker) {
               const end = new Date()
@@ -140,7 +222,8 @@ export default {
               start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
               picker.$emit('pick', [start, end])
             }
-          }, {
+          },
+          {
             text: '最近三个月',
             onClick(picker) {
               const end = new Date()
@@ -156,15 +239,20 @@ export default {
       hourSum: [],
       daySum: {},
       todayList: [],
-      dateRange: [dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00), dayjs().format(DateTool.DateFormat.YYYY_MM_DD_23_59_59)]
+      dateRange: [
+        dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00),
+        dayjs().format(DateTool.DateFormat.YYYY_MM_DD_23_59_59)
+      ]
     }
   },
   computed: {
     elecOptions() {
       const xData = this.todayList.map(item => item.time)
-      const chargeQuantity = this.todayList.map(item => item.chargeElecQuantity)
-      const dischargeQuantity = this.todayList.map(item => item.dischargeElecQuantity)
-      const option = {
+      const chargeQuantity = this.todayList.map(item => item.chargeElecQuantity || 0)
+      const dischargeQuantity = this.todayList.map(item => item.dischargeElecQuantity || 0)
+
+      return {
+        color: ['#5470c6', '#91cc75'],
         toolbox: {
           itemGap: 10,
           itemSize: 16,
@@ -190,8 +278,8 @@ export default {
             }
           },
           formatter: (params) => {
-            var relVal = params[0].name
-            for (var i = 0, l = params.length; i < l; i++) {
+            let relVal = params[0].name
+            for (let i = 0, l = params.length; i < l; i++) {
               const unit = 'kW·h'
               relVal = `${relVal}<br/>${params[i].marker}${params[i].seriesName}&nbsp;&nbsp;&nbsp;${params[i].value}${unit}`
             }
@@ -201,6 +289,12 @@ export default {
         legend: {
           data: ['充电电量', '放电电量']
         },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          containLabel: true
+        },
         xAxis: {
           type: 'category',
           data: xData,
@@ -211,7 +305,10 @@ export default {
         yAxis: [
           {
             name: 'kW·h(千瓦时)',
-            type: 'value'
+            type: 'value',
+            axisLabel: {
+              formatter: '{value}'
+            }
           }
         ],
         series: [
@@ -222,7 +319,8 @@ export default {
             barWidth: 20,
             itemStyle: {
               normal: {
-                color: '#6395FA'
+                color: '#5470c6',
+                barBorderRadius: [4, 4, 0, 0]
               }
             }
           },
@@ -233,119 +331,159 @@ export default {
             barWidth: 20,
             itemStyle: {
               normal: {
-                color: '#8CDF6C'
+                color: '#91cc75',
+                barBorderRadius: [4, 4, 0, 0]
               }
             }
           }
         ]
       }
-      return option
     },
+
     chargePieOptions() {
       return {
+        color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'],
         tooltip: {
           trigger: 'item',
           formatter: (params) => {
-            return `${params.marker}${params.name}&nbsp;&nbsp;&nbsp;${params.value}kW·h`
+            return `${params.marker}${params.name}&nbsp;&nbsp;&nbsp;${params.value}kW·h<br/>占比:${params.percent}%`
           }
         },
         series: [
-
           {
             name: '充电量',
             type: 'pie',
-            radius: [50, 150],
+            radius: ['40%', '70%'],
             center: ['50%', '50%'],
             roseType: 'area',
             itemStyle: {
               borderRadius: 8
             },
-            data:
-                this.hourSum.map(item => ({
-                  name: item.time,
-                  value: item.chargeElecQuantity
-                }))
+            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
+            })).filter(item => item.value > 0)
           }
         ]
       }
     },
+
     disChargePieOptions() {
       return {
+        color: ['#ea7ccc', '#9a60b4', '#fc8452', '#3ba272', '#73c0de', '#ee6666', '#fac858', '#91cc75', '#5470c6'],
         tooltip: {
           trigger: 'item',
           formatter: (params) => {
-            return `${params.marker}${params.name}&nbsp;&nbsp;&nbsp;${params.value}kW·h`
+            return `${params.marker}${params.name}&nbsp;&nbsp;&nbsp;${params.value}kW·h<br/>占比:${params.percent}%`
           }
         },
         series: [
           {
             name: '放电量',
             type: 'pie',
-            radius: [50, 150],
+            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'
+              }
+            },
             data: this.hourSum.map(item => ({
               name: item.time,
-              value: item.dischargeElecQuantity
-            }))
+              value: item.dischargeElecQuantity || 0
+            })).filter(item => item.value > 0)
           }
         ]
       }
     }
-
   },
-  watch: {
-    // 根据名称筛选区域树
-    areaName(val) {
-      this.$refs.tree.filter(val)
-    }
-  },
-  async created() {
+  created() {
     this.facsCategory = 'C'
-    await this.getAreaList()
+    this.getAreaList()
     this.tabClick()
-    await this.getPvConfig()
+    this.getPvConfig()
   },
   methods: {
     numToStr,
-    async getPvConfig() {
-      const {rows} = await listConfig({
-        configKey: "storage-equipped-capacity",
-      })
-      if (!rows || !rows.length) {
-        return;
+
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.type === 'storage') {
+        return 'el-icon-coin'
       }
-      const cfg = JSON.parse(rows[0].remark)
-      const areas = []
-      let equipedStorage = cfg[this.areaType] ? cfg[this.areaType].store : 0
-      if (!this.areaType || this.areaType === '-1') {
-        for (let item in cfg) {
-          equipedStorage += cfg[item].store
-          areas.push(cfg[item])
+      return 'el-icon-office-building'
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
+    async getPvConfig() {
+      try {
+        const { rows } = await listConfig({
+          configKey: "storage-equipped-capacity",
+        })
+        if (!rows || !rows.length) {
+          return
         }
-      }
-      this.pvCfg = {
-        total: numToStr(equipedStorage),
-        areas: areas
+        const cfg = JSON.parse(rows[0].remark)
+        const areas = []
+        let equipedStorage = cfg[this.areaType] ? cfg[this.areaType].store : 0
+        if (!this.areaType || this.areaType === '-1') {
+          for (let item in cfg) {
+            equipedStorage += cfg[item].store
+            areas.push(cfg[item])
+          }
+        }
+        this.pvCfg = {
+          total: numToStr(equipedStorage),
+          areas: areas
+        }
+      } catch (error) {
+        console.error('获取配置失败', error)
       }
     },
+
     // 查询区域列表
     async getAreaList() {
-      await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false).then(response => {
+      try {
+        const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
         this.areaOptions = [{
           id: '-1',
           label: '全部',
           children: []
-        }].concat(response.data)
-      })
+        }].concat(response.data || [])
+      } catch (error) {
+        this.$message.error('获取区域列表失败')
+      }
     },
+
     /** 查询储能计量-小时列表 */
     getList() {
       this.loading = true
-      const {areaCode} = this.queryParams
+      const { areaCode } = this.queryParams
       let startRecTime = ''
       let endRecTime = ''
       if (this.dateRange && this.dateRange.length) {
@@ -360,13 +498,17 @@ export default {
         startRecTime,
         endRecTime
       }).then(response => {
-        this.elecStoreHList = response.rows
-        this.total = response.total
+        this.elecStoreHList = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      }).catch(() => {
         this.loading = false
+        this.$message.error('获取数据失败')
       })
     },
+
     getTodayChart() {
-      const {areaCode} = this.queryParams
+      const { areaCode } = this.queryParams
       const nowDay = dateFormat(new Date(), 'yyyy-MM-dd')
       listElecStoreH({
         areaCode,
@@ -375,29 +517,38 @@ export default {
         startRecTime: `${nowDay} 00:00:00`,
         endRecTime: `${nowDay} 23:59:59`
       }).then(response => {
-        this.todayList = response.rows
+        this.todayList = response.rows || []
+      }).catch(() => {
+        this.$message.error('获取今日数据失败')
       })
     },
+
     getSummary() {
-      dayStatistics({date: dateFormat(new Date(), 'yyyy-MM-dd')}).then(({code, data}) => {
+      const date = dateFormat(new Date(), 'yyyy-MM-dd')
+      dayStatistics({ date }).then(({ code, data }) => {
         if (code === 200) {
           this.hourSum = data.hourSum || []
           this.daySum = data.daySum || {}
         }
+      }).catch(() => {
+        this.$message.error('获取统计数据失败')
       })
     },
+
     // 筛选节点
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
     // 节点单击事件
     handleNodeClick(data) {
       this.queryParams.areaCode = data.id
       this.selectedLabel = data.label
       this.getList()
-      this.getTodayChart();
+      this.getTodayChart()
     },
+
     /** 搜索按钮操作 */
     tabClick() {
       if (this.activeName === 'summary') {
@@ -415,41 +566,282 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-@import './index.scss';
-
 .app-container {
-  padding-top: 10px;
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+}
+
+// 头部卡片样式
+.header-card {
+  margin-bottom: 20px;
+  border-radius: 8px;
+
+  ::v-deep .el-card__body {
+    padding: 20px;
+  }
+
+  .tips {
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
 
+    ::v-deep .el-statistic {
+      text-align: center;
+
+      .el-statistic__head {
+        color: #909399;
+        font-size: 14px;
+        margin-bottom: 8px;
+      }
+
+      .el-statistic__content {
+        color: #303133;
+        font-size: 24px;
+        font-weight: bold;
+      }
+    }
+  }
+}
+
+// 主标签页样式
+.main-tabs {
   ::v-deep .el-tabs__content {
     overflow: initial;
   }
-}
 
-.tips {
-  display: flex;
+  ::v-deep .el-tabs__header {
+    background: #fff;
+    padding: 0 20px;
+    border-radius: 8px 8px 0 0;
+    margin-bottom: 20px;
+  }
 }
 
+// 图表组样式 - 修复核心样式
 .chartGroup {
   display: flex;
+  gap: 20px;
 
-  > div {
+  // 自定义图表卡片样式 - 替代原有的Block组件
+  .chart-card {
     flex: 1;
+    background: #fff;
+    border-radius: 8px;
+    overflow: hidden;  // 关键:确保圆角生效
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    transition: all 0.3s;
+
+    &:hover {
+      box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);
+      transform: translateY(-2px);
+    }
+
+    // 标题栏样式
+    .chart-header {
+      background: linear-gradient(135deg, #409eff 0%, #53a8ff 100%);
+      padding: 16px 20px;
+      color: #fff;
+      font-size: 16px;
+      font-weight: 500;
+      display: flex;
+      align-items: center;
+
+      i {
+        margin-right: 8px;
+        font-size: 18px;
+      }
+    }
+
+    // 图表主体样式
+    .chart-body {
+      padding: 20px;
+      position: relative;
+      min-height: 450px;
+      background: #fff;
+
+      // 中心标签样式
+      .center-label {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        z-index: 10;
+        pointer-events: none;  // 防止遮挡图表交互
+
+        .label-title {
+          font-size: 14px;
+          color: #909399;
+          margin-bottom: 8px;
+        }
+
+        .label-value {
+          font-size: 22px;
+          font-weight: 600;
+          color: #303133;
+          text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+        }
+      }
+    }
+  }
+}
+
+// 左侧树形区域样式
+.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: #888;
+      border-radius: 3px;
+
+      &:hover {
+        background: #555;
+      }
+    }
+
+    ::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: #409eff;
+          }
+        }
+
+        .tree-tag {
+          margin-right: 8px;
+        }
+      }
+    }
   }
+}
 
-  .block-area {
-    position: relative;
-    width: 100%;
+// 右侧内容区域样式
+.container-block {
+  background: #fff;
+  padding: 20px;
+  border-radius: 8px;
+  margin-bottom: 15px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+  ::v-deep .sub-title {
+    margin-bottom: 20px;
+    padding-bottom: 10px;
+    border-bottom: 2px solid #409eff;
+    font-size: 16px;
+    font-weight: 500;
+    color: #303133;
   }
+}
 
-  .center-label {
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    display: flex;
+.ctl-container {
+  margin-bottom: 15px;
+
+  ::v-deep .el-date-editor {
+    width: 380px;
+  }
+}
+
+// 表格样式优化
+::v-deep .el-table {
+  border-radius: 4px;
+
+  .text-primary {
+    color: #409eff;
+    font-weight: 500;
+  }
+
+  .text-success {
+    color: #67c23a;
+    font-weight: 500;
+  }
+
+  th {
+    background: #f5f7fa;
+    color: #303133;
+    font-weight: 500;
+  }
+}
+
+// 响应式布局
+@media (max-width: 1200px) {
+  .chartGroup {
     flex-direction: column;
-    align-items: center;
+
+    .container-block {
+      margin-bottom: 20px;
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+  }
+
+  .tips {
+    flex-direction: column;
+
+    ::v-deep .el-statistic {
+      margin-bottom: 15px;
+    }
+  }
+
+  .ctl-container {
+    ::v-deep .el-date-editor {
+      width: 100%;
+    }
   }
 }
 </style>

+ 167 - 35
ems-ui-cloud/src/views/mgr/poweruse.vue

@@ -5,11 +5,11 @@
         <div class="First">
           <div class="time-range-buttons">
             <el-button :class="{ 'is-active': selectedTimeRange === 'day' }"
-              @click="changeTimeRange('day')">今日</el-button>
+                       @click="changeTimeRange('day')">今日</el-button>
             <el-button :class="{ 'is-active': selectedTimeRange === 'month' }"
-              @click="changeTimeRange('month')">本月</el-button>
+                       @click="changeTimeRange('month')">本月</el-button>
             <el-button :class="{ 'is-active': selectedTimeRange === 'year' }"
-              @click="changeTimeRange('year')">本年</el-button>
+                       @click="changeTimeRange('year')">本年</el-button>
           </div>
           <div class="first-content">
             <div>
@@ -33,13 +33,42 @@
       <el-tab-pane v-for="item in facsCategoryOptions" :key="item.code" :label="item.name" :name="item.code">
         <el-col :span="4" :xs="24">
           <div class="head-container">
-            <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-              style="margin-bottom: 20px" />
+            <el-input
+              v-model="areaName"
+              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="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-              :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-              @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;" />
+          <div class="head-container tree-container">
+            <el-tree
+              :data="areaOptions"
+              :props="defaultProps"
+              :expand-on-click-node="false"
+              :filter-node-method="filterNode"
+              ref="tree"
+              node-key="id"
+              default-expand-all
+              highlight-current
+              @node-click="handleNodeClick"
+            >
+              <template #default="{ node, data }">
+                <el-tooltip
+                  class="tree-node-tooltip"
+                  effect="dark"
+                  :content="data.label"
+                  placement="right"
+                  :disabled="!isTextOverflow(data.label)"
+                >
+                  <div class="tree-node">
+                    <i class="el-icon-office-building node-icon"></i>
+                    <span class="node-label">{{ data.label }}</span>
+                  </div>
+                </el-tooltip>
+              </template>
+            </el-tree>
           </div>
         </el-col>
         <el-col :span="20" :xs="24">
@@ -48,14 +77,14 @@
               <SubTitle :title="`设施汇总电耗【${selectedLabel}】`" />
               <div>
                 <el-select v-model="objCode" placeholder="选择设施" clearable @visible-change="handleObjSelectClick"
-                  @change="getList">
+                           @change="getList">
                   <el-option v-for="item in objOptions" :label="item.objName" :value="item.objCode"
-                    :key="item.objCode" />
+                             :key="item.objCode" />
 
                 </el-select>
                 <el-date-picker v-model="dateRange" type="datetimerange" @change="getList"
-                  :picker-options="pickerOptions" value-format="yyyy-MM-dd hh:mm:ss" range-separator="至"
-                  start-placeholder="开始日期" end-placeholder="结束日期" :clearable="false" align="right">
+                                :picker-options="pickerOptions" value-format="yyyy-MM-dd hh:mm:ss" range-separator="至"
+                                start-placeholder="开始日期" end-placeholder="结束日期" :clearable="false" align="right">
                 </el-date-picker>
               </div>
             </div>
@@ -404,16 +433,16 @@ export default {
             ]
           },
         },
-        {
-          name: '有功功率',
-          type: 'line',
-          data: yData2,
-        },
-        {
-          name: '无功功率',
-          type: 'line',
-          data: yData3,
-        },
+          {
+            name: '有功功率',
+            type: 'line',
+            data: yData2,
+          },
+          {
+            name: '无功功率',
+            type: 'line',
+            data: yData3,
+          },
         ]
       };
     },
@@ -525,21 +554,27 @@ export default {
             ]
           },
         },
-        {
-          name: '有功功率',
-          type: 'line',
-          data: yData2,
-        },
-        {
-          name: '无功功率',
-          type: 'line',
-          data: yData3,
-        },
+          {
+            name: '有功功率',
+            type: 'line',
+            data: yData2,
+          },
+          {
+            name: '无功功率',
+            type: 'line',
+            data: yData3,
+          },
         ]
       };
     }
   },
   methods: {
+    // 新增方法:判断文本是否溢出
+    isTextOverflow(text) {
+      // 简单判断:超过一定字符数就显示tooltip
+      // 可以根据实际情况调整这个数值
+      return text && text.length > 8
+    },
     /**平均功率-15分钟*/
     getPowerChart () {
       this.powerMaxLoad = ''
@@ -827,7 +862,6 @@ export default {
   }
 }
 
-
 .first-content {
   display: flex;
   margin-top: 20px;
@@ -849,7 +883,105 @@ export default {
   display: flex;
   justify-content: space-between;
 }
-</style>
 
+/* 树容器样式 - 统一美化设计 */
+.tree-container {
+  height: calc(100vh - 200px);
+  overflow-y: auto;
+  border: 1px solid #e8e8e8;
+  border-radius: 4px;
+  padding: 10px;
+  background-color: #fafafa;
+}
+
+/* 树节点样式 */
+.tree-node {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  padding: 2px 0;
+  transition: all 0.3s;
+  cursor: pointer;
+}
+
+.node-icon {
+  margin-right: 8px;
+  font-size: 16px;
+  transition: all 0.3s;
+  color: #409EFF;
+}
+
+.node-label {
+  flex: 1;
+  font-size: 14px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+/* 节点hover效果 */
+.tree-node:hover {
+  background-color: #f0f7ff;
+  border-radius: 4px;
+  padding-left: 4px;
+}
+
+.tree-node:hover .node-icon {
+  color: #2b7bff;
+  transform: scale(1.1);
+}
+
+/* 高亮当前选中的节点 */
+.el-tree-node.is-current > .el-tree-node__content .tree-node {
+  background-color: #e6f7ff;
+  border-radius: 4px;
+  padding-left: 4px;
+}
+
+.el-tree-node.is-current > .el-tree-node__content .node-icon {
+  color: #1890ff;
+}
+
+/* 覆盖默认的el-tree样式,确保自定义样式生效 */
+.el-tree-node__content {
+  height: auto;
+  padding: 0 !important;
+}
+
+.el-tree-node__content:hover {
+  background-color: transparent;
+}
 
+.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
+  background-color: transparent;
 }
+
+/* 树节点展开图标调整 */
+.el-tree-node__expand-icon {
+  color: #909399;
+  font-size: 14px;
+}
+
+.el-tree-node__expand-icon:hover {
+  color: #409EFF;
+}
+
+/* 滚动条美化 */
+.tree-container::-webkit-scrollbar {
+  width: 6px;
+}
+
+.tree-container::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+.tree-container::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+.tree-container::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+</style>

+ 495 - 288
ems-ui-cloud/src/views/mgr/strategy.vue

@@ -1,290 +1,314 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
-      <el-col :span="4" :xs="24">
+      <!-- 左侧树形区域 - 优化版 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区块名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px" />
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区块名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree"
-                   node-key="id" default-expand-all highlight-current @node-click="handleNodeClick" />
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <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">
-        <el-tabs v-model="strategyType" @tab-click="strategyTypeChange">
-          <el-tab-pane label="源网策略" name="1"></el-tab-pane>
-          <el-tab-pane label="源荷策略" name="2"></el-tab-pane>
-          <el-tab-pane label="网储策略" name="3"></el-tab-pane>
-          <el-tab-pane label="其他策略" name="4"></el-tab-pane>
-        </el-tabs>
-
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
-          <el-form-item label="策略名称" prop="strategyName">
-            <el-input
-              v-model="queryParams.strategyName"
-              placeholder="请输入策略名称"
-              clearable
-              @keyup.enter.native="handleQuery"
-            />
-          </el-form-item>
-          <el-form-item label="执行模式" prop="execMode">
-            <el-select v-model="queryParams.execMode" placeholder="请选择执行模式" clearable>
-              <el-option
-                v-for="dict in dict.type.exec_mode"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </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-form-item>
-        </el-form>
-
-        <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button
-              type="primary"
-              plain
-              icon="el-icon-plus"
-              size="mini"
-              @click="handleAdd"
-              v-hasPermi="['ems:energyStrategy:add']"
-            >新增
-            </el-button>
-          </el-col>
-          <el-col :span="1.5">
-          </el-col>
-          <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-        </el-row>
-
-        <el-table v-loading="loading" :data="energyStrategyList" @selection-change="handleSelectionChange">
-          <el-table-column label="策略代码" align="center" prop="strategyCode" />
-          <el-table-column label="策略名称" align="center" prop="strategyName" />
-          <el-table-column label="策略类型" align="center" prop="strategyType">
-            <template slot-scope="scope">
-              <dict-tag :options="dict.type.strategy_type" :value="scope.row.strategyType" />
-            </template>
-          </el-table-column>
-          <el-table-column label="执行模式" align="center" prop="execMode">
-            <template slot-scope="scope">
-              <dict-tag :options="dict.type.exec_mode" :value="scope.row.execMode" />
-            </template>
-          </el-table-column>
-          <el-table-column label="状态" align="center" prop="strategyState" >
-            <template slot-scope="scope">
-              {{ getStateName(scope.row.strategyState)}}
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template slot-scope="scope">
-              <el-button
-                size="mini"
-                type="text"
-                icon="el-icon-message"
-                @click="handleParameter(scope.row)"
-                v-hasPermi="['basecfg:energyStrategy:edit']"
-              >参数
-              </el-button>
-              <el-button
-                size="mini"
-                type="text"
-                icon="el-icon-info"
-                @click="handleStep(scope.row)"
-                v-hasPermi="['basecfg:energyStrategy:edit']"
-              >步骤
-              </el-button>
-              <el-button
-                size="mini"
-                type="text"
-                icon="el-icon-edit"
-                @click="handleUpdate(scope.row)"
-                v-hasPermi="['ems:energyStrategy:edit']"
-              >修改
-              </el-button>
-              <el-button
-                size="mini"
-                type="text"
-                icon="el-icon-delete"
-                class="deleteBtn"
-                @click="handleDelete(scope.row)"
-                v-hasPermi="['ems:energyStrategy:remove']">
-                删除</el-button>
-            </template>
-          </el-table-column>
-
-          <!-- 参数弹框 -->
-          <el-dialog title="策略参数维护" :visible.sync="openParameterDialog" width="50%" append-to-body>
-            <div>
-              <div class="container-block">
-                <SubTitle title="默认参数" />
-              </div>
-              <div v-for="param in defaultParams" :key="param.paramName">
-                <label class="param-label">{{ param.paramName }}</label>
-                <el-input v-if="param.paramValueFormat === 'text'" v-model="param.paramValue" placeholder="请输入" @change="handleParamChange"></el-input>
-                <el-select v-if="param.paramValueFormat === 'enum'" v-model="param.paramValue" placeholder="请选择" @change="handleParamChange">
-                  <el-option
-                    v-for="item in paramOptions"
-                    :key="item.key"
-                    :label="item.name"
-                    :value="item.key">
-                    <el-tooltip :content="item.desc" placement="top">
-                      <div>{{ item.name }}</div>
-                    </el-tooltip>
-                  </el-option>
-                </el-select>
-              </div>
-            </div>
-      <!--  模型参数输入框/下拉框 -->
-            <div class="param-spacer"></div>
-            <div class="container-block">
-              <SubTitle title="模式参数" />
-            </div>
-            <div v-if="selectedParamValue === 'maxPowerTrack'">
-              <!-- 当 selectedParamValue 为 'maxPowerTrack' 时,不显示任何模式参数 -->
-            </div>
-            <div v-if="selectedParamValue === 'inverterControl'">
-              <el-select v-model="selectedParamKey" placeholder="请选择">
-                <el-option
-                  v-for="item in modelParamOptions"
-                  :key="item.key"
-                  :label="item.name"
-                  :value="item.key">
-                  <el-tooltip :content="item.desc" placement="top">
-                    <div>{{ item.name }}</div>
-                  </el-tooltip>
-                </el-option>
-              </el-select>
-            </div>
-            <div v-if="selectedParamValue === 'powerAndVoltage'">
-              <!-- 当 selectedParamValue 为 'powerAndVoltage' 时,显示文本框 -->
-              <div v-for="item in defaultParamsOption" :key="item.paramName" class="param-container">
-                <label>{{ item.paramName }}</label>
-                <el-input v-if="item.paramValueFormat === 'text'" v-model="item.paramValue" class="param-containerInput"></el-input>
-              </div>
-            </div>
-            <div slot="footer" class="dialog-footer">
-              <el-button type="primary" @click="updateOpEnergyStrategy">确认</el-button>
-              <el-button @click="openParameterDialog = false">关闭</el-button>
-            </div>
-          </el-dialog>
-
-
-        </el-table>
-        <!-- 步骤流程图弹框 -->
-        <el-dialog title="步骤流程图" :visible.sync="stepDialogVisible" width="80%" :style="{ height: '100vh' }">
-          <div style="display: flex;">
-            <div style="width: 50%;" >
-              <el-steps direction="vertical" :active="currentStepDetail.stepIndex">
-                <el-step
-                  v-for="(step, index) in strategyStepOption"
-                  :key="step.id"
-                  :title="step.stepName"
-                  :index="index + 1"
-                  @click.native="handleStepClick(step)">
-                </el-step>
-              </el-steps>
-                <el-button type="primary" icon="el-icon-plus" @click="addStep" style="position: absolute; left: 16px;  width: 30px; height: 30px; padding: 0; border-radius: 50%;"></el-button>
-            </div>
-            <div style="width: 50%; padding-left: 20px;">
-              <div v-if="currentStepDetail">
-                <el-form v-if="editMode" label-width="120px">
-                  <el-form-item label="步骤名称">
-                    <el-input v-model="currentStepDetail.stepName"></el-input>
-                  </el-form-item>
-<!--                  <el-form-item label="策略代码">-->
-<!--                    <el-input v-model="currentStepDetail.strategyCode"></el-input>-->
-<!--                  </el-form-item>-->
-                  <el-form-item label="步骤代码">
-                    <el-input v-model="currentStepDetail.stepCode"></el-input>
-                  </el-form-item>
-<!--                  <el-form-item label="步骤顺序">-->
-<!--                    <el-input v-model="currentStepDetail.stepIndex"></el-input>-->
-<!--                  </el-form-item>-->
-                  <el-form-item label="步骤处理">
-                    <el-input v-model="currentStepDetail.stepHandler"></el-input>
-                  </el-form-item>
-                  <el-form-item label="步骤参数">
-                    <el-input v-model="currentStepDetail.stepParam"></el-input>
-                  </el-form-item>
-                  <el-form-item label="目标设施">
-                    <el-input v-model="currentStepDetail.targetFacs"></el-input>
-                  </el-form-item>
-                  <el-form-item label="目标设备">
-                    <el-input v-model="currentStepDetail.targetDevice"></el-input>
-                  </el-form-item>
-                  <el-form-item>
-                    <el-button type="primary" @click="saveStep">保存</el-button>
-                    <el-button type="primary" @click="deleteStep">删除步骤</el-button>
-                    <el-button @click="cancelEdit">退出编辑</el-button>
-                  </el-form-item>
-                </el-form>
-                <div v-else>
-                  <h2>步骤详情:</h2>
-                  <ul>
-                    <li><p><strong>步骤名称:</strong> {{ currentStepDetail.stepName }}</p></li>
-                    <li><p><strong>策略代码:</strong> {{ currentStepDetail.strategyCode }}</p></li>
-                    <li><p><strong>步骤代码:</strong> {{ currentStepDetail.stepCode }}</p></li>
-                    <li><p><strong>步骤顺序:</strong> {{ currentStepDetail.stepIndex }}</p></li>
-                    <li><p><strong>步骤处理:</strong> {{ currentStepDetail.stepHandler }}</p></li>
-                    <li><p><strong>步骤参数:</strong> {{ currentStepDetail.stepParam }}</p></li>
-                    <li><p><strong>目标设施:</strong> {{ currentStepDetail.targetFacs }}</p></li>
-                    <li><p><strong>目标设备:</strong> {{ currentStepDetail.targetDevice }}</p></li>
-                  </ul>
-
-                  <el-button type="primary" @click="editMode = true">编辑</el-button>
-                  <el-button  @click="stepDialogVisible = false">关闭</el-button>
-                </div>
-              </div>
-            </div>
-          </div>
-        </el-dialog>
-
-
-        <pagination
-          v-show="total>0"
-          :total="total"
-          :page.sync="queryParams.pageNum"
-          :limit.sync="queryParams.pageSize"
-          @pagination="getList"
-        />
-
-        <!-- 添加或修改能源策略对话框 -->
-        <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
-          <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-            <el-form-item label="地块" prop="areaCode">
-              <treeselect v-model="form.areaCode" :options="areaOptions" :show-count="true" placeholder="请选择策略区域" />
-            </el-form-item>
-            <el-form-item label="策略代码" prop="strategyCode">
-              <el-input v-model="form.strategyCode" placeholder="请输入策略代码" />
-            </el-form-item>
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <!-- 策略类型标签页 -->
+          <el-tabs v-model="strategyType" @tab-click="strategyTypeChange" class="strategy-tabs">
+            <el-tab-pane label="源网策略" name="1"></el-tab-pane>
+            <el-tab-pane label="源荷策略" name="2"></el-tab-pane>
+            <el-tab-pane label="网储策略" name="3"></el-tab-pane>
+            <el-tab-pane label="其他策略" name="4"></el-tab-pane>
+          </el-tabs>
+
+          <!-- 查询表单 -->
+          <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px" class="search-form">
             <el-form-item label="策略名称" prop="strategyName">
-              <el-input v-model="form.strategyName" placeholder="请输入策略名称" />
+              <el-input
+                v-model="queryParams.strategyName"
+                placeholder="请输入策略名称"
+                clearable
+                @keyup.enter.native="handleQuery"
+              />
             </el-form-item>
             <el-form-item label="执行模式" prop="execMode">
-              <el-select v-model="form.execMode" placeholder="请选择执行模式" @change="handleExecModeChange">
+              <el-select v-model="queryParams.execMode" placeholder="请选择执行模式" clearable>
                 <el-option
                   v-for="dict in dict.type.exec_mode"
                   :key="dict.value"
                   :label="dict.label"
-                  :value="parseInt(dict.value)"
-                ></el-option>
+                  :value="dict.value"
+                />
               </el-select>
             </el-form-item>
-            <el-form-item v-if="showExecRule" label="执行规则" prop="execRule">
-              <el-input v-model="form.execRule" placeholder="请输入执行规则" />
+            <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-form-item>
           </el-form>
-          <div slot="footer" class="dialog-footer">
-            <el-button type="primary" @click="submitForm">确 定</el-button>
-            <el-button @click="cancel">取 消</el-button>
-          </div>
-        </el-dialog>
+
+          <!-- 操作按钮行 -->
+          <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+              <el-button
+                type="primary"
+                plain
+                icon="el-icon-plus"
+                size="mini"
+                @click="handleAdd"
+                v-hasPermi="['ems:energyStrategy:add']"
+              >新增
+              </el-button>
+            </el-col>
+            <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+          </el-row>
+
+          <!-- 数据表格 -->
+          <el-table v-loading="loading" :data="energyStrategyList" @selection-change="handleSelectionChange" class="data-table">
+            <el-table-column label="策略代码" align="center" prop="strategyCode" />
+            <el-table-column label="策略名称" align="center" prop="strategyName" />
+            <el-table-column label="策略类型" align="center" prop="strategyType">
+              <template slot-scope="scope">
+                <dict-tag :options="dict.type.strategy_type" :value="scope.row.strategyType" />
+              </template>
+            </el-table-column>
+            <el-table-column label="执行模式" align="center" prop="execMode">
+              <template slot-scope="scope">
+                <dict-tag :options="dict.type.exec_mode" :value="scope.row.execMode" />
+              </template>
+            </el-table-column>
+            <el-table-column label="状态" align="center" prop="strategyState">
+              <template slot-scope="scope">
+                {{ getStateName(scope.row.strategyState) }}
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+              <template slot-scope="scope">
+                <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-message"
+                  @click="handleParameter(scope.row)"
+                  v-hasPermi="['basecfg:energyStrategy:edit']"
+                >参数
+                </el-button>
+                <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-info"
+                  @click="handleStep(scope.row)"
+                  v-hasPermi="['basecfg:energyStrategy:edit']"
+                >步骤
+                </el-button>
+                <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-edit"
+                  @click="handleUpdate(scope.row)"
+                  v-hasPermi="['ems:energyStrategy:edit']"
+                >修改
+                </el-button>
+                <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-delete"
+                  class="deleteBtn"
+                  @click="handleDelete(scope.row)"
+                  v-hasPermi="['ems:energyStrategy:remove']"
+                >删除
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <!-- 分页 -->
+          <pagination
+            v-show="total > 0"
+            :total="total"
+            :page-size.sync="queryParams.pageSize"
+            :page-sizes="[10, 20, 50]"
+            :page.sync="queryParams.pageNum"
+            @pagination="getList"
+          />
+        </div>
       </el-col>
     </el-row>
+
+    <!-- 参数弹框 -->
+    <el-dialog title="策略参数维护" :visible.sync="openParameterDialog" width="50%" append-to-body>
+      <div>
+        <div class="container-block">
+          <SubTitle title="默认参数" />
+        </div>
+        <div v-for="param in defaultParams" :key="param.paramName">
+          <label class="param-label">{{ param.paramName }}</label>
+          <el-input v-if="param.paramValueFormat === 'text'" v-model="param.paramValue" placeholder="请输入" @change="handleParamChange"></el-input>
+          <el-select v-if="param.paramValueFormat === 'enum'" v-model="param.paramValue" placeholder="请选择" @change="handleParamChange">
+            <el-option
+              v-for="item in paramOptions"
+              :key="item.key"
+              :label="item.name"
+              :value="item.key">
+              <el-tooltip :content="item.desc" placement="top">
+                <div>{{ item.name }}</div>
+              </el-tooltip>
+            </el-option>
+          </el-select>
+        </div>
+      </div>
+      <!-- 模型参数输入框/下拉框 -->
+      <div class="param-spacer"></div>
+      <div class="container-block">
+        <SubTitle title="模式参数" />
+      </div>
+      <div v-if="selectedParamValue === 'maxPowerTrack'">
+        <!-- 当 selectedParamValue 为 'maxPowerTrack' 时,不显示任何模式参数 -->
+      </div>
+      <div v-if="selectedParamValue === 'inverterControl'">
+        <el-select v-model="selectedParamKey" placeholder="请选择">
+          <el-option
+            v-for="item in modelParamOptions"
+            :key="item.key"
+            :label="item.name"
+            :value="item.key">
+            <el-tooltip :content="item.desc" placement="top">
+              <div>{{ item.name }}</div>
+            </el-tooltip>
+          </el-option>
+        </el-select>
+      </div>
+      <div v-if="selectedParamValue === 'powerAndVoltage'">
+        <!-- 当 selectedParamValue 为 'powerAndVoltage' 时,显示文本框 -->
+        <div v-for="item in defaultParamsOption" :key="item.paramName" class="param-container">
+          <label>{{ item.paramName }}</label>
+          <el-input v-if="item.paramValueFormat === 'text'" v-model="item.paramValue" class="param-containerInput"></el-input>
+        </div>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="updateOpEnergyStrategy">确认</el-button>
+        <el-button @click="openParameterDialog = false">关闭</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 步骤流程图弹框 -->
+    <el-dialog title="步骤流程图" :visible.sync="stepDialogVisible" width="80%" :style="{ height: '100vh' }">
+      <div style="display: flex;">
+        <div style="width: 50%;">
+          <el-steps direction="vertical" :active="currentStepDetail.stepIndex">
+            <el-step
+              v-for="(step, index) in strategyStepOption"
+              :key="step.id"
+              :title="step.stepName"
+              :index="index + 1"
+              @click.native="handleStepClick(step)">
+            </el-step>
+          </el-steps>
+          <el-button type="primary" icon="el-icon-plus" @click="addStep" style="position: absolute; left: 16px;  width: 30px; height: 30px; padding: 0; border-radius: 50%;"></el-button>
+        </div>
+        <div style="width: 50%; padding-left: 20px;">
+          <div v-if="currentStepDetail">
+            <el-form v-if="editMode" label-width="120px">
+              <el-form-item label="步骤名称">
+                <el-input v-model="currentStepDetail.stepName"></el-input>
+              </el-form-item>
+              <el-form-item label="步骤代码">
+                <el-input v-model="currentStepDetail.stepCode"></el-input>
+              </el-form-item>
+              <el-form-item label="步骤处理">
+                <el-input v-model="currentStepDetail.stepHandler"></el-input>
+              </el-form-item>
+              <el-form-item label="步骤参数">
+                <el-input v-model="currentStepDetail.stepParam"></el-input>
+              </el-form-item>
+              <el-form-item label="目标设施">
+                <el-input v-model="currentStepDetail.targetFacs"></el-input>
+              </el-form-item>
+              <el-form-item label="目标设备">
+                <el-input v-model="currentStepDetail.targetDevice"></el-input>
+              </el-form-item>
+              <el-form-item>
+                <el-button type="primary" @click="saveStep">保存</el-button>
+                <el-button type="primary" @click="deleteStep">删除步骤</el-button>
+                <el-button @click="cancelEdit">退出编辑</el-button>
+              </el-form-item>
+            </el-form>
+            <div v-else>
+              <h2>步骤详情:</h2>
+              <ul>
+                <li><p><strong>步骤名称:</strong> {{ currentStepDetail.stepName }}</p></li>
+                <li><p><strong>策略代码:</strong> {{ currentStepDetail.strategyCode }}</p></li>
+                <li><p><strong>步骤代码:</strong> {{ currentStepDetail.stepCode }}</p></li>
+                <li><p><strong>步骤顺序:</strong> {{ currentStepDetail.stepIndex }}</p></li>
+                <li><p><strong>步骤处理:</strong> {{ currentStepDetail.stepHandler }}</p></li>
+                <li><p><strong>步骤参数:</strong> {{ currentStepDetail.stepParam }}</p></li>
+                <li><p><strong>目标设施:</strong> {{ currentStepDetail.targetFacs }}</p></li>
+                <li><p><strong>目标设备:</strong> {{ currentStepDetail.targetDevice }}</p></li>
+              </ul>
+              <el-button type="primary" @click="editMode = true">编辑</el-button>
+              <el-button @click="stepDialogVisible = false">关闭</el-button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 添加或修改能源策略对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="地块" prop="areaCode">
+          <treeselect v-model="form.areaCode" :options="areaOptions" :show-count="true" placeholder="请选择策略区域" />
+        </el-form-item>
+        <el-form-item label="策略代码" prop="strategyCode">
+          <el-input v-model="form.strategyCode" placeholder="请输入策略代码" />
+        </el-form-item>
+        <el-form-item label="策略名称" prop="strategyName">
+          <el-input v-model="form.strategyName" placeholder="请输入策略名称" />
+        </el-form-item>
+        <el-form-item label="执行模式" prop="execMode">
+          <el-select v-model="form.execMode" placeholder="请选择执行模式" @change="handleExecModeChange">
+            <el-option
+              v-for="dict in dict.type.exec_mode"
+              :key="dict.value"
+              :label="dict.label"
+              :value="parseInt(dict.value)"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item v-if="showExecRule" label="执行规则" prop="execRule">
+          <el-input v-model="form.execRule" placeholder="请输入执行规则" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -339,6 +363,7 @@ export default {
         children: 'children',
         label: 'label'
       },
+      defaultExpandedKeys: ['-1'],
 
       // 控制参数弹框的显示
       openParameterDialog: false,
@@ -429,6 +454,19 @@ export default {
     this.getList();
   },
   methods: {
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      return 'el-icon-office-building'
+    },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     /** 查询能源策略列表 */
     getList() {
       this.loading = true;
@@ -493,9 +531,8 @@ export default {
       this.getStrategyParam(row.strategyCode);
       this.getStrategyParamOption(row.strategyType, 'default.controlMode');
       console.log("打开参数弹框时的 selectedParamValue:", this.selectedParamValue);
-
-
     },
+
     /** 获取默认参数 */
     getStrategyParam(strategyCode) {
       getStrategyParam(strategyCode).then(response => {
@@ -503,11 +540,10 @@ export default {
         this.defaultParams = [
           { paramName: params.paramName, paramValue: params.paramValue, paramValueFormat: params.paramValueFormat },
         ];
-      console.log(" 获取默认参数", this.defaultParams)
+        console.log(" 获取默认参数", this.defaultParams)
       })
     },
 
-
     /** 获取参数选项 */
     getStrategyParamOption(strategyType, paramKey) {
       getStrategyParamOption({strategyType, paramKey}).then(response => {
@@ -515,12 +551,12 @@ export default {
         this.paramOptions = response.data;
       })
     },
+
     getStrategyParamOptions(strategyType, paramKey) {
       getStrategyParamOption({strategyType, paramKey}).then(response => {
         // 更新模式参数下拉框的选项
         this.modelParamOptions = response.data;
         console.log('更新下拉框的选项',this.modelParamOptions);
-
       })
     },
 
@@ -570,7 +606,7 @@ export default {
           updateOpEnergyStrategy(requestBody).then(response => {
             if (response || response.data || response.data.success) {
               this.$message.success('参数修改成功');
-               this.openParameterDialog = false; // 关闭弹框
+              this.openParameterDialog = false; // 关闭弹框
             } else {
               this.$message.error('参数修改失败');
             }
@@ -584,18 +620,19 @@ export default {
             paramKey: key,
             paramValue: this.selectedParamKey,
           };
-        console.log("requestBody",requestBody)
-        updateOpEnergyStrategy(requestBody).then(response => {
-          if (response || response.data || response.data.success) {
-            this.$message.success('参数修改成功');
-            this.openParameterDialog = false; // 关闭弹框
-          } else {
-            this.$message.error('参数修改失败');
-          }
-        });
+          console.log("requestBody",requestBody)
+          updateOpEnergyStrategy(requestBody).then(response => {
+            if (response || response.data || response.data.success) {
+              this.$message.success('参数修改成功');
+              this.openParameterDialog = false; // 关闭弹框
+            } else {
+              this.$message.error('参数修改失败');
+            }
+          });
         });
       }
     },
+
     /**步骤流程图弹框*/
     handleStep(row){
       this.stepDialogVisible=true;
@@ -609,10 +646,12 @@ export default {
         this.editMode = false;
       })
     },
+
     handleStepClick(step) {
       this.currentStepDetail = step;
       this.editMode = false;
     },
+
     addStep() {
       const newStep = {
         id:'',
@@ -658,6 +697,7 @@ export default {
     cancelEdit() {
       this.editMode = false;
     },
+
     deleteStep() {
       deleteStrategyStep(this.currentStepDetail.strategyCode).then(response => {
         this.$message.success('删除成功');
@@ -667,6 +707,7 @@ export default {
         console.error('删除失败', error);
       });
     },
+
     /** 修改按钮操作 */
     handleUpdate(row) {
       this.reset();
@@ -677,6 +718,7 @@ export default {
         this.title = '修改能源策略';
       });
     },
+
     /** 提交按钮 */
     submitForm() {
       this.$refs['form'].validate(valid => {
@@ -697,6 +739,7 @@ export default {
         }
       });
     },
+
     /** 删除按钮操作 */
     handleDelete(row) {
       const ids = row.id || this.ids;
@@ -707,16 +750,19 @@ export default {
         this.$modal.msgSuccess('删除成功');
       }).catch(() => {});
     },
+
     // 筛选节点
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
     // 节点单击事件
     handleNodeClick(data) {
       this.queryParams.areaCode = data.id
       this.handleQuery()
     },
+
     /** 查询区域树结构 */
     getAreaTree(areaCode, layer) {
       areaTreeSelect(areaCode, layer).then(response => {
@@ -727,13 +773,16 @@ export default {
         }]
       })
     },
+
     strategyTypeChange() {
       this.queryParams.strategyType = this.strategyType
       this.getList()
     },
+
     handleExecModeChange(value) {
       this.showExecRule = value === 1;
     },
+
     getStateName(key) {
       if (key === null || key === 0) {
         return "停用"
@@ -745,31 +794,189 @@ export default {
 };
 </script>
 
-<style lang="css" scoped>
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  // 左侧树容器优化样式
+  .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;
+
+          .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: #606266;
+              transition: color 0.3s;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 右侧内容区域
+  .content-wrapper {
+    background: #fff;
+    border-radius: 12px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
+
+    // 策略标签页样式
+    .strategy-tabs {
+      ::v-deep .el-tabs__header {
+        margin: 0 0 20px;
+        border-bottom: 2px solid #e4e7ed;
+      }
+
+      ::v-deep .el-tabs__item {
+        height: 45px;
+        line-height: 45px;
+        font-size: 14px;
+        font-weight: 500;
+
+        &:hover {
+          color: #409eff;
+        }
+
+        &.is-active {
+          color: #409eff;
+          font-weight: 600;
+        }
+      }
+
+      ::v-deep .el-tabs__active-bar {
+        height: 3px;
+        background: linear-gradient(90deg, #409eff 0%, #53a8ff 100%);
+      }
+    }
+
+    // 搜索表单样式
+    .search-form {
+      padding: 15px;
+      background: #f5f7fa;
+      border-radius: 8px;
+      margin-bottom: 15px;
+    }
+
+    // 数据表格样式
+    .data-table {
+      ::v-deep .el-table__header {
+        th {
+          background-color: #f5f7fa;
+          color: #606266;
+          font-weight: 600;
+        }
+      }
+
+      .deleteBtn {
+        color: #f56c6c;
+
+        &:hover {
+          color: #f23030;
+        }
+      }
+    }
+  }
+}
+
+// 参数弹框样式
 .param-spacer {
-  margin: 50px 0; /* 这将增加上下各20px的间距 */
+  margin: 50px 0;
 }
 
 .container-block {
-  margin-bottom: 15px; /* 这将为每个参数块增加底部间距 */
+  margin-bottom: 15px;
 }
+
 .param-label {
   display: inline-block;
   margin-right: 10px;
 }
+
 .param-container {
   display: flex;
   align-items: center;
-  margin-bottom: 10px; /* 根据需要调整间距 */
+  margin-bottom: 10px;
 }
+
 .param-containerInput {
   width: 40%;
 }
 
 .param-container label {
-  margin-right: 10px; /* 根据需要调整标签和输入框之间的间距 */
-  white-space: nowrap; /* 防止标签换行 */
+  margin-right: 10px;
+  white-space: nowrap;
 }
 
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+  }
+}
 </style>

+ 696 - 87
ems-ui-cloud/src/views/prediction/ca.vue

@@ -1,57 +1,196 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
-      <el-col :span="4" :xs="24">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px" />
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                   :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                   @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;" />
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-label">
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.facsCategory === 'Z'"
+                size="mini"
+                effect="plain"
+                class="tree-tag eco-tag"
+              >
+                低碳
+              </el-tag>
+            </span>
+          </el-tree>
         </div>
       </el-col>
-      <el-col :span="20" :xs="24">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
-          <el-form-item label="开始月份" prop="date">
-            <el-date-picker clearable
-                            v-model="queryParams.startRecTime"
-                            type="month"
-                            value-format="yyyy-MM"
-                            placeholder="请选择月份">
-            </el-date-picker>
-          </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-form-item>
-        </el-form>
-
-        <el-table v-loading="loading" :data="forecastCaList" >
-          <el-table-column label="园区名称" align="center" prop="areaName" />
-          <el-table-column label="月份" align="center" prop="month" width="180"/>
-          <el-table-column label="碳排量(kg)" align="center" prop="caEmission" />
-        </el-table>
-
-        <pagination
-          v-show="total>0"
-          :total="total"
-          :page.sync="queryParams.pageNum"
-          :limit.sync="queryParams.pageSize"
-          @pagination="getList"
-        />
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <!-- 标题区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <SubTitle :title="`碳排预测【${selectedLabel}】`" />
+            </div>
+            <div class="header-right">
+              <el-button
+                v-show="showSearch"
+                type="text"
+                icon="el-icon-search"
+                size="mini"
+                @click="showSearch = !showSearch"
+              >
+                搜索条件
+              </el-button>
+            </div>
+          </div>
+
+          <!-- 搜索区域 -->
+          <div class="search-form" v-show="showSearch">
+            <el-form
+              :model="queryParams"
+              ref="queryForm"
+              size="small"
+              :inline="true"
+              label-width="80px"
+            >
+              <el-form-item label="开始月份" prop="startRecTime">
+                <el-date-picker
+                  clearable
+                  v-model="queryParams.startRecTime"
+                  type="month"
+                  value-format="yyyy-MM"
+                  placeholder="请选择月份"
+                />
+              </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-form-item>
+            </el-form>
+          </div>
+
+          <!-- 数据展示区域 -->
+          <div class="data-container">
+            <el-table
+              v-loading="loading"
+              :data="forecastCaList"
+              class="data-table"
+              max-height="600px"
+              stripe
+            >
+              <el-table-column
+                type="index"
+                label="序号"
+                width="60"
+                align="center"
+              />
+              <el-table-column
+                label="园区名称"
+                align="center"
+                prop="areaName"
+                min-width="150"
+                :show-overflow-tooltip="true"
+              />
+              <el-table-column
+                label="月份"
+                align="center"
+                prop="month"
+                width="120"
+              >
+                <template slot-scope="scope">
+                  <span class="month-tag">
+                    <i class="el-icon-date"></i>
+                    {{ scope.row.month }}
+                  </span>
+                </template>
+              </el-table-column>
+              <el-table-column
+                label="碳排量(kg)"
+                align="center"
+                prop="caEmission"
+                width="150"
+              >
+                <template slot-scope="scope">
+                  <span :class="getEmissionClass(scope.row.caEmission)">
+                    {{ formatNumber(scope.row.caEmission) }}
+                  </span>
+                </template>
+              </el-table-column>
+              <el-table-column
+                label="环比变化"
+                align="center"
+                width="120"
+              >
+                <template slot-scope="scope">
+                  <span :class="getTrendClass(scope.row.trend)">
+                    <i :class="getTrendIcon(scope.row.trend)"></i>
+                    {{ getTrendText(scope.row.trend) }}
+                  </span>
+                </template>
+              </el-table-column>
+              <el-table-column
+                label="等级"
+                align="center"
+                width="100"
+                fixed="right"
+              >
+                <template slot-scope="scope">
+                  <el-tag
+                    size="small"
+                    :type="getEmissionLevel(scope.row.caEmission).type"
+                  >
+                    {{ getEmissionLevel(scope.row.caEmission).text }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <pagination
+              v-show="total > 0"
+              :total="total"
+              :page.sync="queryParams.pageNum"
+              :limit.sync="queryParams.pageSize"
+              @pagination="getList"
+            />
+          </div>
+        </div>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-import { listForecastCa } from '@/api/prediction/forecastCa';
+import { listForecastCa } from '@/api/prediction/forecastCa'
 import { areaTreeByFacsCategory } from '@/api/basecfg/area'
+import SubTitle from '@/components/SubTitle'
 
 export default {
   name: 'ForecastCa',
+  components: {
+    SubTitle
+  },
   data() {
     return {
       // 遮罩层
@@ -72,21 +211,32 @@ export default {
       title: '',
       // 是否显示弹出层
       open: false,
-      // 表单参数
-      areaOptions: [],
-      areaName: undefined,
+      // 区域名称搜索
+      areaName: '',
+      // 设施类别
       facsCategory: 'Z',
       facsSubCategory: '',
+      // 区域树选项
+      areaOptions: [],
+      // 默认展开的节点
+      defaultExpandedKeys: [],
+      // 选中的标签
+      selectedLabel: '全部',
+      // 树属性配置
       defaultProps: {
-        children: "children",
-        label: "label"
+        children: 'children',
+        label: 'label'
       },
+      // 统计数据
+      totalEmission: '0.00',
+      avgEmission: '0.00',
+      forecastMonths: 0,
       // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        areaCode: null,
-        startRecTime: null,
+        areaCode: '-1',
+        startRecTime: null
       },
       // 表单参数
       form: {},
@@ -96,90 +246,549 @@ export default {
           {
             required: true,
             message: '园区代码不能为空',
-            trigger: 'blur',
-          },
+            trigger: 'blur'
+          }
         ],
         date: [
           {
             required: true,
             message: '日期不能为空',
-            trigger: 'blur',
-          },
-        ],
-      },
-    };
+            trigger: 'blur'
+          }
+        ]
+      }
+    }
   },
-  created() {
-    this.setTodayDate();
-    this.getAreaList();
-    this.getList();
+  async created() {
+    this.setTodayDate()
+    await this.getAreaList()
+    this.getList()
   },
   methods: {
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.facsCategory === 'Z') {
+        return 'el-icon-s-flag'
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      if (data.type === 'eco') {
+        return 'el-icon-eleme'
+      }
+      return 'el-icon-office-building'
+    },
+
     /** 查询碳排放预测列表 */
     getList() {
-      this.loading = true;
+      this.loading = true
       listForecastCa(this.queryParams).then(response => {
-        this.forecastCaList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
+        this.forecastCaList = response.rows
+        this.total = response.total
+
+        // 计算统计数据
+        this.calculateStats(response.rows)
+
+        this.loading = false
+      }).catch(() => {
+        this.loading = false
+        this.$message.error('数据加载失败')
+      })
+    },
+
+    // 计算统计数据
+    calculateStats(data) {
+      if (data && data.length > 0) {
+        // 计算总碳排量
+        const total = data.reduce((sum, item) => {
+          return sum + (parseFloat(item.caEmission) || 0)
+        }, 0)
+        this.totalEmission = total.toFixed(2)
+
+        // 计算月均碳排
+        this.avgEmission = (total / data.length).toFixed(2)
+
+        // 计算预测月数(去重月份)
+        const uniqueMonths = [...new Set(data.map(item => item.month))]
+        this.forecastMonths = uniqueMonths.length
+      } else {
+        this.totalEmission = '0.00'
+        this.avgEmission = '0.00'
+        this.forecastMonths = 0
+      }
     },
+
     // 查询区域列表
-    async getAreaList () {
-      await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false).then(response => {
+    async getAreaList() {
+      try {
+        const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
         this.areaOptions = [{
           id: '-1',
           label: '全部',
-          children: []
-        }].concat(response.data)
-        this.selectedLabel = '全部'
-        this.queryParams.areaCode = '-1'
-      })
+          children: response.data || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      } catch (error) {
+        console.error('加载区域树失败', error)
+        this.$message.error('加载区域树失败')
+      }
     },
+
+    // 设置今天日期
     setTodayDate() {
-      // 获取当前日期
-      const today = new Date();
-      // 格式化日期为 yyyy-MM-dd 格式
-      const year = today.getFullYear();
-      const month = String(today.getMonth() + 1).padStart(2, '0');
-      // 设置日期选择器的值
-      this.queryParams.startRecTime = `${year}-${month}`;
+      const today = new Date()
+      const year = today.getFullYear()
+      const month = String(today.getMonth() + 1).padStart(2, '0')
+      this.queryParams.startRecTime = `${year}-${month}`
     },
+
     // 取消按钮
     cancel() {
-      this.open = false;
-      this.reset();
+      this.open = false
+      this.reset()
     },
+
     // 表单重置
     reset() {
       this.form = {
         id: null,
         areaCode: null,
         date: null,
-        caEmission: null,
-      };
-      this.resetForm('form');
+        caEmission: null
+      }
+      this.resetForm('form')
     },
+
     /** 搜索按钮操作 */
     handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
+      this.queryParams.pageNum = 1
+      this.getList()
     },
+
     /** 重置按钮操作 */
     resetQuery() {
-      this.resetForm('queryForm');
-      this.handleQuery();
+      this.resetForm('queryForm')
+      this.queryParams.areaCode = '-1'
+      this.selectedLabel = '全部'
+      this.setTodayDate()
+      this.handleQuery()
     },
+
     // 筛选节点
-    filterNode (value, data) {
+    filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     // 节点单击事件
-    handleNodeClick (data) {
+    handleNodeClick(data) {
       this.queryParams.areaCode = data.id
+      this.selectedLabel = data.label
       this.getList()
     },
-  },
-};
+
+    // 格式化数字
+    formatNumber(value) {
+      const num = parseFloat(value) || 0
+      return num.toFixed(2)
+    },
+
+    // 获取排放量样式类
+    getEmissionClass(value) {
+      const num = parseFloat(value) || 0
+      if (num < 1000) return 'data-value success'
+      if (num < 5000) return 'data-value primary'
+      if (num < 10000) return 'data-value warning'
+      return 'data-value danger'
+    },
+
+    // 获取排放等级
+    getEmissionLevel(value) {
+      const num = parseFloat(value) || 0
+      if (num < 1000) return { type: 'success', text: '优秀' }
+      if (num < 5000) return { type: '', text: '良好' }
+      if (num < 10000) return { type: 'warning', text: '一般' }
+      return { type: 'danger', text: '较高' }
+    },
+
+    // 获取趋势样式
+    getTrendClass(index) {
+      // 模拟趋势数据
+      const trend = index % 3 === 0 ? 1 : index % 3 === 1 ? -1 : 0
+      if (trend === 0) return 'trend-flat'
+      return trend > 0 ? 'trend-up' : 'trend-down'
+    },
+
+    // 获取趋势图标
+    getTrendIcon(index) {
+      const trend = index % 3 === 0 ? 1 : index % 3 === 1 ? -1 : 0
+      if (trend === 0) return 'el-icon-minus'
+      return trend > 0 ? 'el-icon-top' : 'el-icon-bottom'
+    },
+
+    // 获取趋势文本
+    getTrendText(index) {
+      const trend = index % 3 === 0 ? 1 : index % 3 === 1 ? -1 : 0
+      if (trend === 0) return '持平'
+      const percent = Math.abs(trend * 12.5).toFixed(1)
+      return trend > 0 ? `+${percent}%` : `-${percent}%`
+    }
+  }
+}
 </script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+            }
+          }
+
+          .tree-tag {
+            margin-right: 8px;
+
+            &.eco-tag {
+              background-color: #f0f9ff;
+              border-color: #b3e5b3;
+              color: #67c23a;
+            }
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 1px solid #ebeef5;
+
+      .header-left {
+        ::v-deep .sub-title {
+          font-size: 18px;
+          font-weight: 600;
+          color: #303133;
+        }
+      }
+
+      .header-right {
+        display: flex;
+        align-items: center;
+      }
+    }
+
+    .search-form {
+      background: #f5f7fa;
+      padding: 15px;
+      border-radius: 4px;
+      margin-bottom: 20px;
+
+      ::v-deep .el-form {
+        margin-bottom: 0;
+      }
+
+      ::v-deep .el-form-item {
+        margin-bottom: 0;
+      }
+
+      ::v-deep .el-date-editor {
+        width: 200px;
+      }
+    }
+
+    // 统计卡片样式
+    .stats-cards {
+      margin-bottom: 20px;
+
+      .stat-card {
+        padding: 20px;
+        border-radius: 8px;
+        color: #fff;
+        position: relative;
+        overflow: hidden;
+        display: flex;
+        align-items: center;
+        min-height: 100px;
+        transition: transform 0.3s, box-shadow 0.3s;
+
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+        }
+
+        &.gradient-green {
+          background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
+        }
+
+        &.gradient-blue {
+          background: linear-gradient(135deg, #667eea 0%, #4c9aff 100%);
+        }
+
+        &.gradient-orange {
+          background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
+        }
+
+        .stat-icon {
+          font-size: 48px;
+          opacity: 0.3;
+          margin-right: 20px;
+        }
+
+        .stat-content {
+          flex: 1;
+          z-index: 1;
+
+          .stat-title {
+            font-size: 14px;
+            opacity: 0.9;
+            margin-bottom: 8px;
+          }
+
+          .stat-value {
+            font-size: 28px;
+            font-weight: 600;
+            line-height: 1;
+
+            .stat-unit {
+              font-size: 14px;
+              font-weight: normal;
+              margin-left: 4px;
+              opacity: 0.8;
+            }
+          }
+        }
+
+        &::after {
+          content: '';
+          position: absolute;
+          width: 100px;
+          height: 100px;
+          background: rgba(255, 255, 255, 0.1);
+          border-radius: 50%;
+          top: -50px;
+          right: -50px;
+        }
+      }
+    }
+
+    .data-container {
+      .data-table {
+        ::v-deep .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+            padding: 12px 0;
+          }
+        }
+
+        ::v-deep .el-table__body {
+          td {
+            padding: 12px 0;
+          }
+
+          .el-table__cell {
+            padding: 8px 10px;
+          }
+
+          .month-tag {
+            color: #606266;
+
+            i {
+              margin-right: 4px;
+              color: #909399;
+            }
+          }
+
+          .data-value {
+            font-weight: 600;
+            font-size: 15px;
+
+            &.primary {
+              color: #409eff;
+            }
+
+            &.success {
+              color: #67c23a;
+            }
+
+            &.warning {
+              color: #e6a23c;
+            }
+
+            &.danger {
+              color: #f56c6c;
+            }
+          }
+
+          .trend-up {
+            color: #f56c6c;
+            font-weight: 500;
+          }
+
+          .trend-down {
+            color: #67c23a;
+            font-weight: 500;
+          }
+
+          .trend-flat {
+            color: #909399;
+            font-weight: 500;
+          }
+        }
+
+        ::v-deep .el-table__empty-text {
+          color: #909399;
+        }
+
+        // 优化表格布局
+        ::v-deep .el-table {
+          font-size: 14px;
+        }
+
+        ::v-deep .el-table th,
+        ::v-deep .el-table td {
+          border-right: none;
+        }
+
+        ::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td {
+          background: #fafafa;
+        }
+      }
+
+      ::v-deep .pagination-container {
+        margin-top: 20px;
+        background: transparent;
+        padding: 0;
+        display: flex;
+        justify-content: flex-end;
+      }
+    }
+  }
+}
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+
+        .header-right {
+          margin-top: 10px;
+        }
+      }
+
+      .search-form {
+        ::v-deep .el-form-item {
+          margin-bottom: 10px;
+        }
+
+        ::v-deep .el-date-editor {
+          width: 100%;
+        }
+      }
+    }
+  }
+}
+</style>

+ 514 - 112
ems-ui-cloud/src/views/prediction/consume.vue

@@ -1,95 +1,187 @@
 <template>
   <div class="app-container">
-    <el-tabs v-model="activeName" @tab-click="tabClick">
-      <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">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px"
+          <el-input
+            v-model="areaName"
+            placeholder="请输入名称搜索"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
           />
         </div>
-        <div class="head-container" 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
+            ref="tree"
+            :data="objOptions"
+            :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, node)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.facsCategory === 'Z'"
+                size="mini"
+                effect="plain"
+                class="tree-tag"
+              >
+                用能
+              </el-tag>
+            </span>
+          </el-tree>
         </div>
       </el-col>
-      <el-col :span="20" :xs="24">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
-          <el-form-item label="开始日期" prop="date">
-            <el-date-picker clearable
-                            v-model="queryParams.startRecTime"
-                            type="date"
-                            value-format="yyyy-MM-dd"
-                            placeholder="请选择日期"
+
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <!-- 标题区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <SubTitle :title="`用能预测【${selectedLabel}】`" />
+            </div>
+            <div class="header-right">
+              <el-button-group class="view-toggle">
+                <el-button
+                  size="small"
+                  :type="activeName === 'areaConsume' ? 'primary' : ''"
+                  @click="switchTab('areaConsume')"
+                >
+                  区域预测
+                </el-button>
+                <el-button
+                  size="small"
+                  :type="activeName === 'facsConsume' ? 'primary' : ''"
+                  @click="switchTab('facsConsume')"
+                >
+                  设施预测
+                </el-button>
+              </el-button-group>
+            </div>
+          </div>
+
+          <!-- 搜索区域 -->
+          <div class="search-form">
+            <el-form
+              :model="queryParams"
+              ref="queryForm"
+              size="small"
+              :inline="true"
+              label-width="80px"
             >
-            </el-date-picker>
-          </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-form-item>
-
-          <el-table v-loading="loading" :data="forecastConsumeList" >
-            <el-table-column label="对象名称" align="center" prop="objName"/>
-
-            <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="用电量(kW·h)" align="center" prop="elecUseQuantity"/>
-          </el-table>
-
-          <pagination
-            v-show="total>0"
-            :total="total"
-            :page.sync="queryParams.pageNum"
-            :limit.sync="queryParams.pageSize"
-            @pagination="getConsumeList"
-          />
+              <el-form-item label="开始日期" prop="startRecTime">
+                <el-date-picker
+                  clearable
+                  v-model="queryParams.startRecTime"
+                  type="date"
+                  value-format="yyyy-MM-dd"
+                  placeholder="请选择日期"
+                  :picker-options="startPickerOptions"
+                />
+              </el-form-item>
 
-        </el-form>
+              <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-form-item>
+            </el-form>
+          </div>
 
+          <!-- 数据展示区域 -->
+          <div class="data-container">
+            <el-table
+              v-loading="loading"
+              :data="forecastConsumeList"
+              class="data-table"
+              max-height="600px"
+            >
+              <el-table-column
+                label="对象名称"
+                align="center"
+                prop="objName"
+                min-width="150"
+              />
+              <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="用电量(kW·h)"
+                align="center"
+                prop="elecUseQuantity"
+                min-width="120"
+              >
+                <template slot-scope="scope">
+                  <span class="data-value primary">{{ scope.row.elecUseQuantity || 0 }}</span>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <pagination
+              v-show="total > 0"
+              :total="total"
+              :page.sync="queryParams.pageNum"
+              :limit.sync="queryParams.pageSize"
+              @pagination="getConsumeList"
+            />
+          </div>
+        </div>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-
 import { getFacsCategoryTree } from '@/api/basecfg/emsfacs'
 import { areaTreeSelect } from '@/api/basecfg/area'
 import { listForecastConsume } from '@/api/prediction/forecastConsume'
-import Treeselect from '@riophae/vue-treeselect'
-import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+import SubTitle from '@/components/SubTitle'
 
 export default {
-  name: 'consume',
-  components: { Treeselect },
+  name: 'ForecastConsume',
+  components: {
+    SubTitle
+  },
   data() {
     return {
       // 遮罩层
       loading: true,
+      // 当前激活的tab
       activeName: 'areaConsume',
-      // 表单参数
-      areaOptions: [],
+      // 区域名称搜索
+      areaName: '',
+      // 树选项
       objOptions: [],
-      areaName: undefined,
+      // 默认展开的节点
+      defaultExpandedKeys: [],
+      // 选中的标签
+      selectedLabel: '全部',
+      // 树属性配置
       defaultProps: {
         children: 'children',
         label: 'label'
       },
       // 总条数
       total: 0,
+      // 预测数据列表
       forecastConsumeList: [],
       // 查询参数
       queryParams: {
@@ -99,23 +191,20 @@ export default {
         objType: '1',
         objCode: null,
         facsCategory: 'Z',
-        startRecTime: this.getFirstDayOfMonth(), // 本月1号
+        startRecTime: this.getFirstDayOfMonth(),
         endRecTime: null
       },
       // 时间选择器配置
       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()
+        }
       },
       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)
@@ -125,12 +214,10 @@ export default {
               time.getTime() > endDateLimit.getTime() ||
               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()
+        }
       }
     }
   },
@@ -143,19 +230,42 @@ export default {
     this.getAreaList()
     this.getConsumeList()
   },
-  watch: {},
   methods: {
+    // 获取树节点图标
+    getTreeIcon(data, node) {
+      // 设施类型图标
+      if (data.facsCategory === 'Z') {
+        return 'el-icon-s-data'
+      }
+      // 顶级节点
+      if (!node.parent || node.level === 1) {
+        return 'el-icon-s-home'
+      }
+      // 叶子节点(具体设施)
+      if (node.isLeaf) {
+        return 'el-icon-monitor'
+      }
+      // 中间节点(区域)
+      return 'el-icon-office-building'
+    },
+
+    // 切换tab
+    switchTab(tabName) {
+      this.activeName = tabName
+      this.tabClick()
+    },
+
+    // tab切换处理
     tabClick() {
       this.clear()
       if (this.activeName === 'areaConsume') {
-        this.queryParams.objType = 1
+        this.queryParams.objType = '1'
         this.getAreaList()
-        this.getConsumeList() // 初始化区域用能数据
       } else if (this.activeName === 'facsConsume') {
-        this.queryParams.objType = 2
+        this.queryParams.objType = '2'
         this.getFacsList()
-        this.getConsumeList() // 初始化设施用能数据
       }
+      this.getConsumeList()
     },
 
     // 表单重置
@@ -164,32 +274,73 @@ export default {
         pageNum: 1,
         pageSize: 10,
         areaCode: -1,
+        objType: this.queryParams.objType, // 保留当前类型
         objCode: null,
         facsCategory: 'Z',
-        startRecTime: this.getFirstDayOfMonth(), // 本月1号
+        startRecTime: this.getFirstDayOfMonth(),
         endRecTime: null
       }
       this.total = 0
-      this.consumeList = []
+      this.forecastConsumeList = []
+      this.selectedLabel = '全部'
     },
+
     /** 查询能源区域树列表 */
-    getAreaList() {
-      areaTreeSelect(0, 3).then(response => {
-        this.objOptions = response.data
-      })
+    async getAreaList() {
+      try {
+        const response = await areaTreeSelect(0, 3)
+        this.objOptions = [{
+          id: -1,
+          label: '全部',
+          children: response.data || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = [-1]
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey(-1)
+          }
+        })
+      } catch (error) {
+        console.error('加载区域树失败', error)
+        this.$message.error('加载区域树失败')
+      }
     },
+
     /** 查询能源设施树列表 */
-    getFacsList() {
-      getFacsCategoryTree().then(response => {
-        this.objOptions = this.flattenTreeData(response.data)
-      })
+    async getFacsList() {
+      try {
+        const response = await getFacsCategoryTree()
+        const flattened = this.flattenTreeData(response.data)
+        this.objOptions = [{
+          id: -1,
+          label: '全部',
+          children: flattened || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = [-1]
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey(-1)
+          }
+        })
+      } catch (error) {
+        console.error('加载设施树失败', error)
+        this.$message.error('加载设施树失败')
+      }
     },
+
     // 核心处理函数:压缩层级并扁平化树形结构
     flattenTreeData(regions) {
       if (!Array.isArray(regions)) return []
 
       return regions.map(region => {
-        // 防御性处理children
         const children = region.children || []
         if (!Array.isArray(children)) return null
 
@@ -200,19 +351,26 @@ export default {
         if (zFacility) {
           return {
             ...region,
-            // 直接使用Z节点的子节点作为区域的子节点
-            children: (zFacility.children || []).map(child => ({ ...child }))
+            children: (zFacility.children || []).map(child => ({
+              ...child,
+              facsCategory: 'Z' // 添加标识
+            }))
           }
         }
         return null
       }).filter(Boolean)
     },
+
+    // 获取用能预测列表
     getConsumeList() {
       this.loading = true
       listForecastConsume(this.queryParams).then(response => {
         this.forecastConsumeList = response.rows
         this.total = response.total
         this.loading = false
+      }).catch(() => {
+        this.loading = false
+        this.$message.error('数据加载失败')
       })
     },
 
@@ -221,9 +379,14 @@ export default {
       this.queryParams.pageNum = 1
       this.getConsumeList()
     },
+
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm('queryForm')
+      this.queryParams.startRecTime = this.getFirstDayOfMonth()
+      this.queryParams.objCode = null
+      this.queryParams.areaCode = -1
+      this.selectedLabel = '全部'
       this.handleQuery()
     },
 
@@ -232,33 +395,47 @@ export default {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     // 节点点击事件处理
     handleNodeClick(data, node) {
-      this.queryParams.areaCode = this.getTopLevelId(node)
-      this.queryParams.objCode = data.id
+      // 处理全部节点
+      if (data.id === -1) {
+        this.queryParams.areaCode = -1
+        this.queryParams.objCode = null
+        this.selectedLabel = '全部'
+      } else {
+        this.queryParams.areaCode = this.getTopLevelId(node)
+        this.queryParams.objCode = data.id
+        this.selectedLabel = data.label
+      }
+
       this.queryParams.pageNum = 1
-      console.log(this.queryParams.areaCode)
       this.getConsumeList()
     },
 
     // 追溯顶级节点ID的辅助函数
     getTopLevelId(node) {
-      let currentNode = node;
+      let currentNode = node
 
-      while (currentNode.parent && currentNode.parent.id !== 0) {
-        currentNode = currentNode.parent;
+      while (currentNode.parent && currentNode.parent.data.id !== -1) {
+        currentNode = currentNode.parent
       }
-      return currentNode.data.id;
+      return currentNode.data.id
     },
 
-    // 获取本月1号 00:00:00
+    // 获取本月1号
     getFirstDayOfMonth() {
       const date = new Date()
       date.setDate(1)
       return this.formatDateTime(date)
     },
 
-    // 格式化日期时间为 yyyy-MM-dd HH:mm:ss
+    // 格式化日期时间为 yyyy-MM-dd
     formatDateTime(date) {
       if (!date) return ''
       if (typeof date === 'string') {
@@ -270,32 +447,257 @@ export default {
       const day = String(date.getDate()).padStart(2, '0')
 
       return `${year}-${month}-${day}`
-    },
-
-    // 生成整点时间范围(优化版)
-    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}`)
-      }
-      return ranges
     }
   }
 }
 </script>
-<style scoped>
 
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+        }
+
+        // 叶子节点样式
+        .el-tree-node.is-leaf > .el-tree-node__content {
+          color: #303133;
+          cursor: pointer;
+        }
+
+        // 非叶子节点样式(提示不可点击)
+        .el-tree-node:not(.is-leaf) > .el-tree-node__content {
+          color: #909399;
+
+          &:hover {
+            background-color: #f9f9f9;
+          }
+        }
 
-/* 自定义样式:父节点文字变灰,提示不可点击 */
-.el-tree .el-tree-node.is-leaf > .el-tree-node__content {
-  color: #333;
-  cursor: pointer;
+        .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;
+            }
+          }
+
+          .tree-tag {
+            margin-right: 8px;
+            background-color: #f0f9ff;
+            border-color: #b3d8ff;
+            color: #409eff;
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 1px solid #ebeef5;
+
+      .header-left {
+        ::v-deep .sub-title {
+          font-size: 18px;
+          font-weight: 600;
+          color: #303133;
+        }
+      }
+
+      .view-toggle {
+        ::v-deep .el-button {
+          padding: 8px 15px;
+
+          &.el-button--primary {
+            background: linear-gradient(90deg, #409eff 0%, #53a8ff 100%);
+            border-color: #409eff;
+          }
+        }
+      }
+    }
+
+    .search-form {
+      background: #f5f7fa;
+      padding: 15px;
+      border-radius: 4px;
+      margin-bottom: 20px;
+
+      ::v-deep .el-form {
+        margin-bottom: 0;
+      }
+
+      ::v-deep .el-form-item {
+        margin-bottom: 0;
+      }
+
+      ::v-deep .el-date-editor {
+        width: 200px;
+      }
+    }
+
+    .data-container {
+      .data-table {
+        ::v-deep .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+          }
+        }
+
+        ::v-deep .el-table__body {
+          td {
+            padding: 12px 0;
+          }
+
+          .data-value {
+            font-weight: 500;
+            font-size: 14px;
+
+            &.primary {
+              color: #409eff;
+            }
+
+            &.success {
+              color: #67c23a;
+            }
+
+            &.warning {
+              color: #e6a23c;
+            }
+
+            &.danger {
+              color: #f56c6c;
+            }
+          }
+        }
+
+        ::v-deep .el-table__empty-text {
+          color: #909399;
+        }
+      }
+
+      ::v-deep .pagination-container {
+        margin-top: 20px;
+        background: transparent;
+        padding: 0;
+      }
+    }
+  }
 }
 
-.el-tree .el-tree-node:not(.is-leaf) > .el-tree-node__content {
-  color: #909399;
-  cursor: not-allowed;
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+
+        .header-right {
+          margin-top: 10px;
+          width: 100%;
+
+          .view-toggle {
+            width: 100%;
+            display: flex;
+
+            ::v-deep .el-button {
+              flex: 1;
+            }
+          }
+        }
+      }
+
+      .search-form {
+        ::v-deep .el-form-item {
+          margin-bottom: 10px;
+        }
+
+        ::v-deep .el-date-editor {
+          width: 100%;
+        }
+      }
+    }
+  }
 }
 </style>

+ 474 - 122
ems-ui-cloud/src/views/prediction/prod.vue

@@ -1,72 +1,163 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
-      <el-col :span="4" :xs="24">
+      <!-- 左侧树形区域 -->
+      <el-col :span="5" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px" />
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false"
-                   :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
-                   @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;" />
+        <div class="head-container tree-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-label">
+                <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                {{ node.label }}
+              </span>
+              <el-tag
+                v-if="data.facsCategory === 'E'"
+                size="mini"
+                effect="plain"
+                class="tree-tag"
+              >
+                光伏
+              </el-tag>
+            </span>
+          </el-tree>
         </div>
       </el-col>
-      <el-col :span="20" :xs="24">
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
 
-          <el-form-item label="选择设施" prop="facsCode">
-            <el-select v-model="queryParams.facsCode" clearable>
-              <el-option v-for="item in facsOptions" :label="item.facsName" :value="item.facsCode"
-                         :key="item.facsCode"
+      <!-- 右侧内容区域 -->
+      <el-col :span="19" :xs="24">
+        <div class="content-wrapper">
+          <!-- 标题区域 -->
+          <div class="content-header">
+            <div class="header-left">
+              <SubTitle :title="`电力产能预测【${selectedLabel}】`" />
+            </div>
+            <div class="header-right">
+              <el-button
+                v-show="showSearch"
+                type="text"
+                icon="el-icon-search"
+                size="mini"
+                @click="showSearch = !showSearch"
+              >
+                搜索条件
+              </el-button>
+            </div>
+          </div>
+
+          <!-- 搜索区域 -->
+          <el-form
+            :model="queryParams"
+            ref="queryForm"
+            size="small"
+            :inline="true"
+            v-show="showSearch"
+            label-width="80px"
+            class="search-form"
+          >
+            <el-form-item label="选择设施" prop="facsCode">
+              <el-select
+                v-model="queryParams.facsCode"
+                clearable
+                placeholder="请选择设施"
+              >
+                <el-option
+                  v-for="item in facsOptions"
+                  :label="item.facsName"
+                  :value="item.facsCode"
+                  :key="item.facsCode"
+                />
+              </el-select>
+            </el-form-item>
+
+            <el-form-item label="开始日期" prop="startRecTime">
+              <el-date-picker
+                clearable
+                v-model="queryParams.startRecTime"
+                type="date"
+                value-format="yyyy-MM-dd"
+                placeholder="请选择日期"
               />
-            </el-select>
-          </el-form-item>
-
-          <el-form-item label="开始日期" prop="date">
-            <el-date-picker clearable
-                            v-model="queryParams.startRecTime"
-                            type="date"
-                            value-format="yyyy-MM-dd"
-                            placeholder="请选择日期">
-            </el-date-picker>
-          </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-form-item>
-        </el-form>
-
-        <el-table v-loading="loading" :data="predictionProdList" >
-          <el-table-column label="位置名称" align="center" prop="areaName"/>
-          <el-table-column label="设施名称" align="center" prop="facsName"/>
-          <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="发电量(kW·h)" align="center" prop="elecProdQuantity"/>
-          <el-table-column label="平均功率(kW)" align="center" prop="avgPower"/>
-        </el-table>
-        <pagination
-          v-show="total>0"
-          :total="total"
-          :page.sync="queryParams.pageNum"
-          :limit.sync="queryParams.pageSize"
-          @pagination="getList"
-        />
+            </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-form-item>
+          </el-form>
+
+          <!-- 数据表格 -->
+          <div class="data-container">
+            <el-table
+              v-loading="loading"
+              :data="predictionProdList"
+              class="data-table"
+              max-height="600px"
+            >
+              <el-table-column label="位置名称" align="center" prop="areaName" />
+              <el-table-column label="设施名称" align="center" prop="facsName" />
+              <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="发电量(kW·h)" align="center" prop="elecProdQuantity">
+                <template slot-scope="scope">
+                  <span class="data-value primary">{{ scope.row.elecProdQuantity || 0 }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="平均功率(kW)" align="center" prop="avgPower">
+                <template slot-scope="scope">
+                  <span class="data-value success">{{ scope.row.avgPower || 0 }}</span>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <pagination
+              v-show="total > 0"
+              :total="total"
+              :page.sync="queryParams.pageNum"
+              :limit.sync="queryParams.pageSize"
+              @pagination="getList"
+            />
+          </div>
+        </div>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-import { listPredictionProd } from '@/api/prediction/predictionProd';
+import { listPredictionProd } from '@/api/prediction/predictionProd'
 import { areaTreeByFacsCategory } from '@/api/basecfg/area'
 import { listAllFacs } from '@/api/basecfg/emsfacs'
+import SubTitle from '@/components/SubTitle'
 
 export default {
   name: 'PredictionProd',
+  components: {
+    SubTitle
+  },
   data() {
     return {
       // 遮罩层
@@ -87,26 +178,33 @@ export default {
       title: '',
       // 是否显示弹出层
       open: false,
-      // 表单参数
-      areaOptions: [],
-      areaName: undefined,
+      // 区域名称搜索
+      areaName: '',
+      // 设施类别
       facsCategory: 'E',
       facsSubCategory: 'E5',
       // 设施选项
-      facsOptions: undefined,
+      facsOptions: [],
+      // 区域树选项
+      areaOptions: [],
+      // 默认展开的节点
+      defaultExpandedKeys: [],
+      // 选中的标签
+      selectedLabel: '全部',
+      // 树属性配置
       defaultProps: {
-        children: "children",
-        label: "label"
+        children: 'children',
+        label: 'label'
       },
       // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        areaCode: null,
+        areaCode: '-1',
         facsCode: null,
         startRecTime: null,
         elecProdQuantity: null,
-        avgPower: null,
+        avgPower: null
       },
       // 表单参数
       form: {},
@@ -116,89 +214,119 @@ export default {
           {
             required: true,
             message: '园区代码不能为空',
-            trigger: 'blur',
-          },
+            trigger: 'blur'
+          }
         ],
         facsCode: [
           {
             required: true,
             message: '设施代码不能为空',
-            trigger: 'blur',
-          },
+            trigger: 'blur'
+          }
         ],
         date: [
           {
             required: true,
             message: '日期不能为空',
-            trigger: 'blur',
-          },
+            trigger: 'blur'
+          }
         ],
         elecProdQuantity: [
           {
             required: true,
             message: '发电量不能为空',
-            trigger: 'blur',
-          },
+            trigger: 'blur'
+          }
         ],
         avgPower: [
           {
             required: true,
             message: '功率不能为空',
-            trigger: 'blur',
-          },
-        ],
-      },
-    };
-  },
-  watch: {
-    // 根据名称筛选区域树
-    areaName (val) {
-      this.$refs.tree.filter(val)
+            trigger: 'blur'
+          }
+        ]
+      }
     }
   },
-  created() {
-    this.getAreaList();
-    this.getFacsOptions();
-    this.setTodayDate();
-    this.getList();
+  async created() {
+    await this.getAreaList()
+    this.getFacsOptions()
+    this.setTodayDate()
+    this.getList()
   },
   methods: {
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.facsCategory === 'E') {
+        return 'el-icon-sunny'
+      }
+      if (data.id === '-1') {
+        return 'el-icon-s-home'
+      }
+      if (data.type === 'service') {
+        return 'el-icon-service'
+      }
+      return 'el-icon-office-building'
+    },
+
     /** 查询电力产能预测列表 */
     getList() {
-      this.loading = true;
+      this.loading = true
       listPredictionProd(this.queryParams).then(response => {
-        this.predictionProdList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
+        this.predictionProdList = response.rows
+        this.total = response.total
+        this.loading = false
+      }).catch(() => {
+        this.loading = false
+        this.$message.error('数据加载失败')
+      })
     },
+
     // 查询区域列表
-    async getAreaList () {
-      await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false).then(response => {
+    async getAreaList() {
+      try {
+        const response = await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false)
         this.areaOptions = [{
           id: '-1',
           label: '全部',
-          children: []
-        }].concat(response.data)
-        this.selectedLabel = '全部'
-        this.queryParams.areaCode = '-1'
-      })
+          children: response.data || []
+        }]
+
+        // 设置默认展开第一级
+        this.defaultExpandedKeys = ['-1']
+
+        // 默认选中全部
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      } catch (error) {
+        console.error('加载区域树失败', error)
+        this.$message.error('加载区域树失败')
+      }
     },
+
+    // 获取设施选项
     getFacsOptions() {
       const getFacsParams = {
         facsCategory: this.facsCategory,
-        subCategory: this.queryParams.deviceSubCategory,
-        refArea: this.queryParams.areaCode,
+        subCategory: this.facsSubCategory,
+        refArea: this.queryParams.areaCode === '-1' ? null : this.queryParams.areaCode
       }
       listAllFacs(getFacsParams).then(response => {
-        this.facsOptions = response.data
+        this.facsOptions = response.data || []
+      }).catch(() => {
+        this.facsOptions = []
       })
     },
+
     // 取消按钮
     cancel() {
-      this.open = false;
-      this.reset();
+      this.open = false
+      this.reset()
     },
+
     // 表单重置
     reset() {
       this.form = {
@@ -207,48 +335,272 @@ export default {
         facsCode: null,
         date: null,
         elecProdQuantity: null,
-        avgPower: null,
-      };
-      this.resetForm('form');
+        avgPower: null
+      }
+      this.resetForm('form')
     },
+
     /** 搜索按钮操作 */
     handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
+      this.queryParams.pageNum = 1
+      this.getList()
     },
+
     /** 重置按钮操作 */
     resetQuery() {
-      this.resetForm('queryForm');
-      this.handleQuery();
+      this.resetForm('queryForm')
+      this.queryParams.areaCode = '-1'
+      this.selectedLabel = '全部'
+      this.setTodayDate()
+      this.handleQuery()
     },
-    handle(selection) {
-      this.ids = selection.map(item => item.id);
-      this.single = selection.length !== 1;
-      this.multiple = !selection.length;
+
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
     },
+
+    // 设置今天日期
     setTodayDate() {
-      // 获取当前日期
-      const today = new Date();
-      // 格式化日期为 yyyy-MM-dd 格式
-      const year = today.getFullYear();
-      const month = String(today.getMonth() + 1).padStart(2, '0');
-      const day = String(today.getDate()).padStart(2, '0');
-      // 设置日期选择器的值
-      this.queryParams.startRecTime = `${year}-${month}-${day}`;
+      const today = new Date()
+      const year = today.getFullYear()
+      const month = String(today.getMonth() + 1).padStart(2, '0')
+      const day = String(today.getDate()).padStart(2, '0')
+      this.queryParams.startRecTime = `${year}-${month}-${day}`
     },
+
     // 筛选节点
-    filterNode (value, data) {
+    filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
     // 节点单击事件
-    handleNodeClick (data) {
+    handleNodeClick(data) {
       this.queryParams.areaCode = data.id
       this.selectedLabel = data.label
-      this.queryParams.facsCode = undefined
+      this.queryParams.facsCode = null
       this.getFacsOptions()
       this.getList()
-    },
-  },
-};
+    }
+  }
+}
 </script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .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;
+            }
+          }
+
+          .tree-tag {
+            margin-right: 8px;
+          }
+        }
+
+        .el-tree-node.is-current .tree-icon {
+          color: #409eff;
+        }
+      }
+    }
+  }
+
+  .content-wrapper {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    min-height: calc(100vh - 160px);
+
+    .content-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 1px solid #ebeef5;
+
+      .header-left {
+        ::v-deep .sub-title {
+          font-size: 18px;
+          font-weight: 600;
+          color: #303133;
+        }
+      }
+
+      .header-right {
+        display: flex;
+        align-items: center;
+      }
+    }
+
+    .search-form {
+      background: #f5f7fa;
+      padding: 15px;
+      border-radius: 4px;
+      margin-bottom: 20px;
+
+      ::v-deep .el-form-item {
+        margin-bottom: 0;
+      }
+
+      ::v-deep .el-select {
+        width: 200px;
+      }
+
+      ::v-deep .el-date-editor {
+        width: 200px;
+      }
+    }
+
+    .data-container {
+      .data-table {
+        ::v-deep .el-table__header {
+          th {
+            background-color: #f5f7fa;
+            color: #606266;
+            font-weight: 600;
+          }
+        }
+
+        ::v-deep .el-table__body {
+          .data-value {
+            font-weight: 500;
+
+            &.primary {
+              color: #409eff;
+            }
+
+            &.success {
+              color: #67c23a;
+            }
+
+            &.info {
+              color: #909399;
+            }
+
+            &.warning {
+              color: #e6a23c;
+            }
+
+            &.danger {
+              color: #f56c6c;
+            }
+          }
+        }
+      }
+
+      ::v-deep .pagination-container {
+        margin-top: 20px;
+        background: transparent;
+      }
+    }
+  }
+}
+
+// 响应式布局
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px;
+
+    .el-col {
+      margin-bottom: 20px;
+    }
+
+    .content-wrapper {
+      .content-header {
+        flex-direction: column;
+        align-items: flex-start;
+
+        .header-right {
+          margin-top: 10px;
+        }
+      }
+
+      .search-form {
+        ::v-deep .el-form-item {
+          margin-bottom: 10px;
+        }
+
+        ::v-deep .el-select,
+        ::v-deep .el-date-editor {
+          width: 100%;
+        }
+      }
+    }
+  }
+}
+</style>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است