6
0

2 Revīzijas 594cb97fa6 ... 79cb94a48b

Autors SHA1 Ziņojums Datums
  learshaw 79cb94a48b update 2 mēneši atpakaļ
  learshaw bfcdab4bcc 设备台账重构 2 mēneši atpakaļ

+ 1 - 1
ems-ui-cloud/src/api/basecfg/objAbility.js

@@ -52,7 +52,7 @@ export function delAbility(id) {
   })
 }
 /**
- * 查询能源对象能力列表
+ * 调用能源对象能力
  */
 export function callAbility(data) {
   return request({

+ 103 - 0
ems-ui-cloud/src/api/device/ledger.js

@@ -0,0 +1,103 @@
+import request from '@/utils/request'
+
+/**
+ * 查询设备台账列表
+ * @param {Object} query 查询参数
+ */
+export function listLedger(query) {
+  return request({
+    url: '/ems/device/ledger/list',
+    method: 'get',
+    params: query
+  })
+}
+
+/**
+ * 查询设备台账详细
+ * @param {Number|String} id 台账ID
+ */
+export function getLedger(id) {
+  return request({
+    url: '/ems/device/ledger/' + id,
+    method: 'get'
+  })
+}
+
+/**
+ * 新增设备台账
+ * @param {Object} data 台账数据
+ */
+export function addLedger(data) {
+  return request({
+    url: '/ems/device/ledger',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 修改设备台账
+ * @param {Object} data 台账数据
+ */
+export function updateLedger(data) {
+  return request({
+    url: '/ems/device/ledger',
+    method: 'put',
+    data: data
+  })
+}
+
+/**
+ * 删除设备台账
+ * @param {Number|String} id 台账ID或ID列表(逗号分隔)
+ */
+export function delLedger(id) {
+  return request({
+    url: '/ems/device/ledger/' + id,
+    method: 'delete'
+  })
+}
+
+/**
+ * 导出设备台账
+ * @param {Object} query 查询参数
+ */
+export function exportLedger(query) {
+  return request({
+    url: '/ems/device/ledger/export',
+    method: 'post',
+    params: query,
+    responseType: 'blob'
+  })
+}
+
+/**
+ * 生成台账编号(前端预生成)
+ * @returns {String} 台账编号 格式: TZ + 年月日 + 4位随机数
+ */
+export function generateRecordCode() {
+  const now = new Date()
+  const year = now.getFullYear()
+  const month = String(now.getMonth() + 1).padStart(2, '0')
+  const day = String(now.getDate()).padStart(2, '0')
+  const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0')
+  return `TZ${year}${month}${day}${random}`
+}
+
+/**
+ * 设备分类枚举
+ */
+export const DEVICE_CATEGORY = {
+  E: { value: 'E', label: '产能设备', icon: 'el-icon-sunny' },
+  C: { value: 'C', label: '储能设备', icon: 'el-icon-coin' },
+  W: { value: 'W', label: '输配设备', icon: 'el-icon-connection' },
+  Z: { value: 'Z', label: '用能设备', icon: 'el-icon-lightning' }
+}
+
+/**
+ * 获取设备分类选项列表
+ * @returns {Array} 设备分类选项数组
+ */
+export function getDeviceCategoryOptions() {
+  return Object.values(DEVICE_CATEGORY)
+}

+ 0 - 45
ems-ui-cloud/src/api/device/rbook.js

@@ -1,45 +0,0 @@
-import request from '@/utils/request'
-
-// 查询设备台账列表
-export function listRbook(query) {
-  return request({
-    url: '/ems/device/rbook/list',
-    method: 'get',
-    params: query
-  })
-}
-
-// 查询设备台账详细
-export function getRbook(id) {
-  return request({
-    url: '/ems/device/rbook/' + id,
-    method: 'get'
-
-  })
-}
-
-// 新增设备台账
-export function addRbook(data) {
-  return request({
-    url: '/ems/device/rbook',
-    method: 'post',
-    data: data
-  })
-}
-
-// 修改设备台账
-export function updateRbook(data) {
-  return request({
-    url: '/ems/device/rbook',
-    method: 'put',
-    data: data
-  })
-}
-
-// 删除设备台账
-export function delRbook(id) {
-  return request({
-    url: '/ems/device/rbook/' + id,
-    method: 'delete'
-  })
-}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 496 - 1173
ems-ui-cloud/src/views/adapter/gcc/index.vue


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 484 - 1165
ems-ui-cloud/src/views/adapter/hm/index.vue


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 492 - 1165
ems-ui-cloud/src/views/adapter/ljcy/index.vue


+ 50 - 36
ems-ui-cloud/src/views/analysis/power/save.vue

@@ -426,10 +426,11 @@ export default {
 
     calculateTableHeight() {
       this.$nextTick(() => {
-        const windowHeight = window.innerHeight
-        const tableOffset = 800
-        this.tableHeight = windowHeight - tableOffset
-      })
+        const windowHeight = window.innerHeight;
+        // 原来是 800,现在改为 500 左右,偏移量越小,表格越高
+        const tableOffset = 600;
+        this.tableHeight = windowHeight - tableOffset > 400 ? windowHeight - tableOffset : 400;
+      });
     },
 
     getTreeIcon(data) {
@@ -559,23 +560,32 @@ export default {
     // ==================== 节能分析相关方法 ====================
 
     initSavingDateRange() {
-      const today = new Date()
-      const sixMonthsAgo = new Date(today)
-      sixMonthsAgo.setMonth(today.getMonth() - 6)
-
-      this.savingDateRange = [
-        this.formatDate(sixMonthsAgo).substring(0, 7),
-        this.formatDate(today).substring(0, 7)
-      ]
-
-      this.savingQueryParams.startRecTime = this.savingDateRange[0] + '-01 00:00:00'
-      const lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0).getDate()
-      this.savingQueryParams.endRecTime = this.savingDateRange[1] + `-${lastDay} 23:59:59`
-
-      // 初始化表格日期范围
-      const lastMonth = new Date(today)
-      lastMonth.setMonth(today.getMonth() - 1)
-      this.savingTableDateRange = [this.formatDate(lastMonth), this.formatDate(today)]
+      const today = new Date();
+
+      // 1. 设置结束月份为“上个月”(不包含当月)
+      const endMonthDate = new Date(today.getFullYear(), today.getMonth() - 1, 1);
+      // 2. 设置起始月份为结束月份再往前推 5 个月(共 6 个月)
+      const startMonthDate = new Date(today.getFullYear(), today.getMonth() - 6, 1);
+
+      // 定义一个内部格式化函数,避免 "formatMonth is not a function" 报错
+      const formatM = (date) => {
+        const y = date.getFullYear();
+        const m = String(date.getMonth() + 1).padStart(2, '0');
+        return `${y}-${m}`;
+      };
+
+      const startStr = formatM(startMonthDate);
+      const endStr = formatM(endMonthDate);
+
+      this.savingDateRange = [startStr, endStr];
+
+      // 设置 API 查询需要的完整时间格式
+      this.savingQueryParams.startRecTime = startStr + '-01 00:00:00';
+      const lastDay = new Date(today.getFullYear(), today.getMonth(), 0).getDate();
+      this.savingQueryParams.endRecTime = endStr + `-${lastDay} 23:59:59`;
+
+      // 同时更新表格的默认范围(可选,保持一致性)
+      this.savingTableDateRange = [this.formatDate(startMonthDate), this.formatDate(endMonthDate)];
     },
 
     async initPvSummary() {
@@ -736,35 +746,39 @@ export default {
     },
 
     async loadTrendChartData() {
-      this.trendChartLoading = true
+      this.trendChartLoading = true;
       try {
         const response = await listPvSupply({
           ...this.savingQueryParams,
           pageNum: 1,
           pageSize: 1000,
           timeDimension: 'month'
-        })
+        });
 
         if (response.code === 200 && response.rows) {
-          const data = response.rows
-          const xData = []
-          const genData = []
-          const useData = []
-          const upData = []
+          // 核心修改:对 rows 进行排序,确保时间是从旧到新(正序)
+          const data = response.rows.sort((a, b) => {
+            return new Date(a.statisticMonth) - new Date(b.statisticMonth);
+          });
+
+          const xData = [];
+          const genData = [];
+          const useData = [];
+          const upData = [];
 
           data.forEach(item => {
-            xData.push(item.statisticMonth)
-            genData.push(parseFloat(item.genElecQuantity || 0).toFixed(2))
-            useData.push(parseFloat(item.useElecQuantity || 0).toFixed(2))
-            upData.push(parseFloat(item.upElecQuantity || 0).toFixed(2))
-          })
+            xData.push(item.statisticMonth);
+            genData.push(parseFloat(item.genElecQuantity || 0).toFixed(2));
+            useData.push(parseFloat(item.useElecQuantity || 0).toFixed(2));
+            upData.push(parseFloat(item.upElecQuantity || 0).toFixed(2));
+          });
 
-          this.updateTrendChart(xData, genData, useData, upData)
+          this.updateTrendChart(xData, genData, useData, upData);
         }
       } catch (error) {
-        console.error('加载趋势图数据失败', error)
+        console.error('加载趋势图数据失败', error);
       } finally {
-        this.trendChartLoading = false
+        this.trendChartLoading = false;
       }
     },
 

+ 0 - 776
ems-ui-cloud/src/views/devmgr/el/index.vue

@@ -1,776 +0,0 @@
-<template>
-  <div class="app-container">
-    <el-row :gutter="20">
-      <!-- 左侧树形区域 -->
-      <el-col :span="5" :xs="24">
-        <div class="head-container">
-          <el-input
-            v-model="areaName"
-            placeholder="请输入服务区名称"
-            clearable
-            size="small"
-            prefix-icon="el-icon-search"
-            style="margin-bottom: 20px"
-            @input="filterTree"
-          />
-        </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>
-            </span>
-          </el-tree>
-        </div>
-      </el-col>
-
-      <!-- 右侧内容区域 -->
-      <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>
-            </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">
-        <el-form-item label="记录编号" prop="recordCode">
-          <el-input v-model="form.recordCode" placeholder="请输入记录编号" :disabled="isViewOnly" />
-        </el-form-item>
-        <el-form-item label="对象类型" >
-          <el-radio-group v-model="form.objType" size="medium" @change="changeObjOptions" :disabled="isViewOnly">
-            <el-radio v-for="(item, index) in dict.type.device_type" :key="index" :label="item.value"
-                      :disabled="item.disabled">{{item.label}}</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <template v-if="form.objType" >
-          <el-form-item label="台账对象" prop="objCode" v-if="isObjCodeVisible">
-            <template v-if="form.objType === '1'">
-              <treeselect v-model="form.objCode" :options="objCodeOptions" :show-count="true" placeholder="请选择具体数据" @select="handleSelect" :disabled="isViewOnly"/>
-            </template>
-            <template v-else-if="form.objType === '2' || form.objType === '3'">
-              <el-row>
-                <el-col :span="12">
-                  <el-form-item label="" prop="areaCode" class="el-form-item--inline">
-                    <el-select v-model="form.areaCode" :disabled="isViewOnly">
-                      <el-option v-for="item in areaOptionsForSelect" :label="item.label" :value="item.id" :key="item.id" />
-                    </el-select>
-                  </el-form-item>
-                </el-col>
-                <el-col :span="12">
-                  <el-form-item label="" prop="objCode" class="el-form-item--inline">
-                    <treeselect v-model="form.objCode" :options="objCodeOptions" :show-count="true" placeholder="请选择具体数据" @select="handleSelect" :disabled="isViewOnly"/>
-                  </el-form-item>
-                </el-col>
-              </el-row>
-            </template>
-          </el-form-item>
-        </template>
-        <el-form-item label="创建时间" prop="recordTime">
-          <el-date-picker clearable v-model="form.recordTime" type="date" value-format="yyyy-MM-dd"
-                          placeholder="请选择日期 yyyy-MM-dd HH:mm:ss" :disabled="isViewOnly">
-          </el-date-picker>
-        </el-form-item>
-        <el-form-item label="目标对象" prop="objName">
-          <el-input v-model="form.objName" placeholder="请输入对象名称" :disabled="isViewOnly" />
-        </el-form-item>
-        <el-form-item label="安装位置" prop="insLocation">
-          <el-input v-model="form.insLocation" placeholder="请输入安装位置" :disabled="isViewOnly" />
-        </el-form-item>
-        <el-form-item label="维护标题" prop="maintainTitle">
-          <el-input v-model="form.maintainTitle" placeholder="请输入维护标题" :disabled="isViewOnly" />
-        </el-form-item>
-        <el-form-item label="维护内容">
-          <!-- 只读模式下显示内容 -->
-          <div v-if="isViewOnly" v-html="form.maintainContent"></div>
-          <!-- 编辑模式下显示编辑器 -->
-          <editor v-else v-model="form.maintainContent" :min-height="192"></editor>
-        </el-form-item>
-        <el-form-item label="维护人" prop="maintainPerson">
-          <el-input v-model="form.maintainPerson" placeholder="请输入维护人" :disabled="isViewOnly" />
-        </el-form-item>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <!-- 只在非查看模式显示提交按钮 -->
-        <el-button type="primary" v-if="!isViewOnly" @click="submitForm">确 定</el-button>
-        <!-- 编辑按钮,点击切换到编辑模式 -->
-        <el-button size="mini" type="text" icon="el-icon-edit" @click="handleEdit" v-hasPermi="['ems:rbook:edit']">
-          编辑
-        </el-button>
-        <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(selectedRow)"
-                   v-hasPermi="['ems:rbook:remove']">删除
-        </el-button>
-        <el-button v-if="isViewOnly" @click="cancel">取 消</el-button>
-      </div>
-    </el-dialog>
-
-    <!-- 添加或修改设备台账对话框 -->
-    <el-dialog :title="title" :visible.sync="addOrUpdateOpen" width="650px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="记录编号" prop="recordCode">
-          <el-input v-model="form.recordCode" placeholder="请输入记录编号" />
-        </el-form-item>
-        <el-form-item label="对象类型" >
-          <el-radio-group v-model="form.objType" size="medium" @change="changeObjOptions">
-            <el-radio v-for="(item, index) in dict.type.device_type" :key="index" :label="item.value"
-                      :disabled="item.disabled">{{item.label}}</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <template v-if="form.objType">
-          <el-form-item label="台账对象" prop="objCode">
-            <template v-if="form.objType === '1'">
-              <el-form-item label="" prop="objCode" class="el-form-item--inline">
-                <treeselect v-model="form.objCode" :options="objCodeOptions" :show-count="true" placeholder="请选择具体数据" @select="handleSelect"/>
-              </el-form-item>
-            </template>
-            <template v-else-if="form.objType === '2' || form.objType === '3'">
-              <el-row>
-                <el-col :span="12">
-                  <el-form-item label="" prop="areaCode" class="el-form-item--inline">
-                    <el-select v-model="form.areaCode">
-                      <el-option v-for="item in areaOptionsForSelect" :label="item.label" :value="item.id" :key="item.id" />
-                    </el-select>
-                  </el-form-item>
-                </el-col>
-                <el-col :span="12">
-                  <el-form-item label="" prop="objCode" class="el-form-item--inline">
-                    <treeselect v-model="form.objCode" :options="objCodeOptions" :show-count="true" placeholder="请选择具体数据" @select="handleSelect"/>
-                  </el-form-item>
-                </el-col>
-              </el-row>
-            </template>
-
-          </el-form-item>
-        </template>
-
-        <el-form-item label="创建时间" prop="recordTime">
-          <el-date-picker clearable v-model="form.recordTime" type="date" value-format="yyyy-MM-dd"
-                          placeholder="请选择日期 ">
-          </el-date-picker>
-        </el-form-item>
-        <el-form-item label="目标对象" prop="objName">
-          <el-input v-model="form.objName" placeholder="请输入对象名称" />
-        </el-form-item>
-        <el-form-item label="安装位置" prop="insLocation">
-          <el-input v-model="form.insLocation" placeholder="请输入安装位置" />
-        </el-form-item>
-        <el-form-item label="维护标题" prop="maintainTitle">
-          <el-input v-model="form.maintainTitle" placeholder="请输入维护标题" />
-        </el-form-item>
-        <el-form-item label="维护内容">
-          <editor v-model="form.maintainContent" :min-height="192" />
-        </el-form-item>
-        <el-form-item label="维护人" prop="maintainPerson">
-          <el-input v-model="form.maintainPerson" 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 {listRbook, getRbook, delRbook, addRbook, updateRbook} from '@/api/device/rbook'
-import {devTreeByFacs} from '@/api/device/device'
-import {areaTreeSelect} from '@/api/basecfg/area'
-import Treeselect from '@riophae/vue-treeselect'
-
-export default {
-  name: 'Rbook',
-  components: {Treeselect},
-  dicts: ['device_type'], // 声明需要加载的字典
-  data () {
-    return {
-      isViewOnly: false, // 查看/编辑模式标志
-      loading: true, // 遮罩层
-      ids: [], // 选中数组
-      single: true, // 非单个禁用
-      multiple: true, // 非多个禁用
-      showSearch: true, // 显示搜索条件
-      total: 0, // 总条数
-      rbookList: [], // 设备台账表格数据
-      areaOptionsForSelect:[], // 区域选择器选项
-      title: '', // 弹出层标题
-      open: false, // 查看对话框显示状态
-      addOrUpdateOpen: false, // 新增/编辑对话框显示状态
-      isObjCodeVisible: false, // 台账对象显示控制
-      AllCode: [], // 能源设施全部数据
-      objCodeOptions: [], // 对象代码选项
-      areaCode: '', // 区域代码
-      objCode: null, // 对象代码
-      showObjCode: false, // 对象代码显示控制
-      defaultExpandedKeys: [], // 默认展开的节点
-      defaultProps: {
-        children: 'children',
-        label: 'label'
-      },
-      // 查询参数
-      queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        selectedRow: null,
-        recordTime: null,
-        recordCode: null,
-        objType: null,
-        objCode: null,
-        objName: null,
-        insLocation: null,
-        maintainTitle: null,
-        maintainContent: null,
-        maintainPerson: null,
-        recordTimeRange: [],
-        areaCode: ''
-      },
-      queryFacsParams: {
-        areaCode: '',
-        objType: null,
-      },
-      objTypeOptiOns:[
-        {code: '1', name: '区域'},
-        {code: '2', name: '设备'},
-        {code: '3', name: '设施'}
-      ],
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        recordCode: [
-          {required: true, message: '记录编号不能为空', trigger: 'blur'}
-        ],
-        objType: [
-          {required: true, message: '对象类型不能为空', trigger: 'change'}
-        ],
-        maintainTitle: [
-          {required: true, message: '维护标题不能为空', trigger: 'blur'}
-        ],
-        recordTime: [
-          {required: true, message: '日期 yyyy-MM-dd 不能为空', trigger: 'blur'}
-        ]
-      },
-      areaName: undefined, // 区域名称搜索
-      areaOptions: [], // 区域树数据
-      selectedRow: null // 当前选中行
-    }
-  },
-  async created () {
-    await this.getAreaTreeByTag('0', 1)
-    // 等待字典加载完成后再初始化默认Tab(关键修复)
-    this.$nextTick(() => {
-      // 确保字典数据存在再赋值
-      if (this.dict.type.device_type && this.dict.type.device_type.length > 0) {
-        this.queryParams.objType = this.dict.type.device_type[0].value;
-      }
-      this.getList(true);
-    });
-  },
-  watch: {
-    'form.areaCode': function(newVal) {
-      if (newVal) {
-        this.updateObjCodeOptions();
-      }
-    },
-    'form.objType' (newVal) {
-      if (!this.isViewOnly) {
-        this.isObjCodeVisible = !!newVal;
-      }
-    },
-    'isViewOnly' (newVal) {
-      if (newVal) {
-        this.isObjCodeVisible = false;
-        this.form.objCode = null;
-      }
-    },
-    // 区域树筛选
-    areaName (val) {
-      this.$refs.tree.filter(val)
-    },
-  },
-  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) {
-        this.queryParams.pageNum = 1; // 强制刷新时重置页码
-      }
-      this.loading = true
-      listRbook(this.queryParams).then(response => {
-        this.rbookList = 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.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')
-          }
-        })
-      })
-    },
-
-    // 筛选节点
-    filterNode (value, data) {
-      if (!value) return true
-      return data.label.indexOf(value) !== -1
-    },
-
-    // 节点单击事件
-    handleNodeClick(data) {
-      this.queryParams.areaCode = data.id
-      this.form.areaCode = data.id;
-      this.handleQuery()
-      this.updateObjCodeOptions();
-    },
-
-    // 更新对象代码选项
-    updateObjCodeOptions() {
-      if (this.form.objType === '2') {
-        // 设备逻辑
-        areaTreeSelect(this.form.areaCode, 3).then(response => {
-          this.objCodeOptions = [{
-            id: this.form.areaCode,
-            label: this.getAreaLabelById(this.form.areaCode),
-            children: response.data
-          }];
-        });
-      } else if (this.form.objType === '3') {
-        // 设施逻辑
-        const devcCategory = 'Z';
-        devTreeByFacs(this.form.areaCode, devcCategory).then(response => {
-          this.objCodeOptions = response.data;
-        });
-      }
-    },
-
-    // 取消按钮
-    cancel () {
-      this.addOrUpdateOpen = false
-      this.open = false
-      this.reset()
-    },
-
-    // 表单重置
-    reset () {
-      this.form = {
-        id: null,
-        recordCode: null,
-        objType: null,
-        objCode: null,
-        objName: null,
-        recordTime: null,
-        insLocation: null,
-        maintainTitle: null,
-        maintainContent: null,
-        maintainPerson: null,
-        createTime: null,
-        updateTime: null
-      }
-      this.resetForm('form')
-      this.dateRange = []
-    },
-
-    // 搜索建议方法
-    querySearch (queryString, cb) {
-      const results = this.rbookList.filter(item => {
-        return item.objName.toLowerCase().indexOf(queryString.toLowerCase()) !== -1
-      })
-      cb(results.map(item => ({value: item.objName})))
-    },
-
-    /** 搜索按钮操作 */
-    handleQuery () {
-      this.queryParams.pageNum = 1
-      this.getList()
-    },
-
-    /** 重置按钮操作 */
-    resetQuery () {
-      this.resetForm('queryForm')
-      this.handleQuery()
-    },
-
-    // 多选框选中数据
-    handleSelectionChange (selection) {
-      this.ids = selection.map(item => item.id)
-      this.single = selection.length !== 1
-      this.multiple = !selection.length
-    },
-
-    // 根据ID获取区域名称
-    getAreaLabelById(id) {
-      const item = this.areaOptions.find(item => item.id === id);
-      return item ? item.label : 'default';
-    },
-
-    // 切换对象类型时更新选项
-    changeObjOptions (objType) {
-      this.objCodeOptions = [];
-      if (objType === '1') {
-        // 区域逻辑
-        areaTreeSelect('0', 1).then(response => {
-          if (response.data && response.data.length > 0) {
-            const firstItem = response.data[0];
-            if (firstItem.label === 'default' && firstItem.children) {
-              this.objCodeOptions = [{
-                id: firstItem.id,
-                label: firstItem.label,
-                children: firstItem.children.slice(0, 3)
-              }];
-            } else {
-              this.objCodeOptions = response.data;
-            }
-          }
-        })
-      } else if (objType === 2) {
-        // 设备逻辑
-        areaTreeSelect(this.form.areaCode, 3).then(response => {
-          this.objCodeOptions = [{
-            id: this.form.areaCode,
-            label: this.getAreaLabelById(this.form.areaCode),
-            children: response.data
-          }]
-        })
-      } else if (objType === 3) {
-        // 设施逻辑
-        const devcCategory = 'Z';
-        devTreeByFacs(this.form.areaCode, devcCategory).then(response => {
-          this.objCodeOptions = response.data
-        })
-      }
-    },
-
-    /** 数据自动填补 */
-    handleSelect(value) {
-      if (value && value.id && value.label) {
-        this.form.objName = value.label;
-        const pathLabels = this.findPathLabels(this.objCodeOptions, value.id);
-        if (pathLabels) {
-          this.form.insLocation = pathLabels.length > 1
-            ? pathLabels.slice(0, -1).join('/')
-            : pathLabels[0];
-        }
-      }
-    },
-
-    // 查找路径标签
-    findPathLabels(options, id, path = []) {
-      for (let i = 0; i < options.length; i++) {
-        const option = options[i];
-        if (option.id === id) {
-          return path.concat(option.label);
-        } else if (option.children && option.children.length > 0) {
-          const foundLabels = this.findPathLabels(option.children, id, path.concat(option.label));
-          if (foundLabels) {
-            return foundLabels;
-          }
-        }
-      }
-      return null;
-    },
-
-    /** 编辑按钮操作 */
-    handleEdit () {
-      this.isViewOnly = false // 切换到编辑模式
-      this.title = '编辑设备台账'
-    },
-
-    /** 新增按钮操作 */
-    handleAdd () {
-      this.reset()
-      this.addOrUpdateOpen = true
-      this.title = '添加设备台账'
-    },
-
-    /** 查看按钮操作 */
-    handleViewRec (row) {
-      this.selectedRow = row
-      const id = row.id || this.ids
-      getRbook(id).then(response => {
-        this.form = response.data
-        this.form.objCode = this.form.objCode ? this.form.objCode.split(',') : []
-        this.$nextTick(() => {
-          this.changeObjOptions(this.form.objType);
-        });
-        this.isObjCodeVisible = false;
-        this.open = true
-        this.title = '查看设备台账'
-        this.isViewOnly = true
-      })
-    },
-
-    /** 提交按钮操作 */
-    submitForm () {
-      // 确保objCode是数组并转为字符串
-      if (!Array.isArray(this.form.objCode)) {
-        this.form.objCode = [this.form.objCode]
-      }
-      this.form.objCode = this.form.objCode.join(',')
-      this.$refs['form'].validate(valid => {
-        if (valid) {
-          if (this.form.id != null) {
-            updateRbook(this.form).then(response => {
-              this.$modal.msgSuccess('修改成功')
-              this.open = false
-              this.addOrUpdateOpen = false
-              this.getList()
-            })
-          } else {
-            addRbook(this.form).then(response => {
-              this.$modal.msgSuccess('新增成功')
-              this.open = false
-              this.addOrUpdateOpen = false
-              this.getList()
-            })
-          }
-        }
-      })
-    },
-
-    /** 删除按钮操作 */
-    handleDelete (row) {
-      const ids = row.id || this.ids
-      this.$modal.confirm('是否确认删除设备台账编号为"' + ids + '"的数据项?').then(function () {
-        return delRbook(ids)
-      }).then(() => {
-        this.getList()
-        this.$modal.msgSuccess('删除成功')
-        this.open = false
-      }).catch(() => {})
-    },
-
-    /** 导出按钮操作 */
-    handleExport () {
-      this.download('ems/rbook/export', {
-        ...this.queryParams
-      }, `rbook_${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);
-  }
-}
-
-.el-form-item--inline {
-  display: inline-block;
-  margin-right: 10px;
-}
-
-// 响应式布局
-@media (max-width: 768px) {
-  .app-container {
-    padding: 10px;
-
-    .el-col {
-      margin-bottom: 20px;
-    }
-  }
-}
-</style>

+ 1233 - 0
ems-ui-cloud/src/views/devmgr/ledger/index.vue

@@ -0,0 +1,1233 @@
+<template>
+  <div class="ledger-container">
+    <!-- 左侧树形导航 -->
+    <div class="sidebar">
+      <div class="sidebar-header">
+        <h3 class="sidebar-title">
+          <i class="el-icon-location-outline"></i>
+          服务区域
+        </h3>
+      </div>
+      <div class="search-box">
+        <el-input
+          v-model="areaName"
+          placeholder="搜索服务区..."
+          clearable
+          prefix-icon="el-icon-search"
+          @input="filterTree"
+        />
+      </div>
+      <div class="tree-wrapper">
+        <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"
+        >
+          <template #default="{ node, data }">
+            <span class="tree-node">
+              <i :class="getTreeIcon(data)" class="node-icon"></i>
+              <span class="node-label">{{ node.label }}</span>
+            </span>
+          </template>
+        </el-tree>
+      </div>
+    </div>
+
+    <!-- 右侧内容区 -->
+    <div class="main-content">
+      <!-- 搜索筛选区 -->
+      <transition name="slide-fade">
+        <div class="filter-section" v-show="showSearch">
+          <el-form :model="queryParams" ref="queryForm" :inline="true" class="filter-form">
+            <el-form-item label="目标对象" prop="objName">
+              <el-input
+                v-model="queryParams.objName"
+                placeholder="输入对象名称"
+                clearable
+                @keyup.enter.native="handleQuery"
+              >
+                <template #prefix>
+                  <i class="el-icon-aim"></i>
+                </template>
+              </el-input>
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker
+                v-model="queryParams.recordTimeRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="yyyy-MM-dd"
+                :picker-options="pickerOptions"
+              />
+            </el-form-item>
+            <el-form-item label="维护标题" prop="maintainTitle">
+              <el-input
+                v-model="queryParams.maintainTitle"
+                placeholder="输入维护标题"
+                clearable
+                @keyup.enter.native="handleQuery"
+              >
+                <template #prefix>
+                  <i class="el-icon-document"></i>
+                </template>
+              </el-input>
+            </el-form-item>
+            <el-form-item label="维护人" prop="maintainPerson">
+              <el-input
+                v-model="queryParams.maintainPerson"
+                placeholder="输入维护人"
+                clearable
+                @keyup.enter.native="handleQuery"
+              >
+                <template #prefix>
+                  <i class="el-icon-user"></i>
+                </template>
+              </el-input>
+            </el-form-item>
+            <el-form-item class="filter-buttons">
+              <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+              <el-button icon="el-icon-refresh-left" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+      </transition>
+
+      <!-- 工具栏 -->
+      <div class="toolbar">
+        <div class="toolbar-left">
+          <el-button type="primary" icon="el-icon-plus" @click="handleAdd" v-hasPermi="['ems:ledger:add']">
+            新增台账
+          </el-button>
+          <el-button type="warning" icon="el-icon-download" plain @click="handleExport" v-hasPermi="['ems:ledger:export']">
+            导出
+          </el-button>
+        </div>
+        <div class="toolbar-right">
+          <el-tooltip content="刷新" placement="top">
+            <el-button circle icon="el-icon-refresh" @click="getList(true)" />
+          </el-tooltip>
+          <el-tooltip :content="showSearch ? '隐藏筛选' : '显示筛选'" placement="top">
+            <el-button circle :icon="showSearch ? 'el-icon-arrow-up' : 'el-icon-arrow-down'" @click="showSearch = !showSearch" />
+          </el-tooltip>
+        </div>
+      </div>
+
+      <!-- 数据表格 -->
+      <div class="table-wrapper">
+        <el-table
+          v-loading="loading"
+          :data="ledgerList"
+          @selection-change="handleSelectionChange"
+          :header-cell-style="{ background: '#f8fafc', color: '#475569', fontWeight: '600' }"
+          stripe
+        >
+          <el-table-column type="selection" width="50" align="center" />
+          <el-table-column label="园区名称" align="left" prop="areaName" min-width="140">
+            <template #default="{ row }">
+              <div class="cell-area">
+                <i class="el-icon-office-building"></i>
+                <span>{{ row.areaName || '-' }}</span>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="记录编号" align="center" prop="recordCode" min-width="150">
+            <template #default="{ row }">
+              <el-tag size="small" effect="plain">{{ row.recordCode }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="目标对象" align="left" prop="objName" min-width="160">
+            <template #default="{ row }">
+              <div class="cell-object">
+                <span class="obj-name">{{ row.objName || '-' }}</span>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="安装位置" align="left" prop="insLocation" min-width="180" show-overflow-tooltip />
+          <el-table-column label="维护标题" align="left" prop="maintainTitle" min-width="200" show-overflow-tooltip>
+            <template #default="{ row }">
+              <span class="maintain-title">{{ row.maintainTitle }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="创建时间" align="center" prop="recordTime" width="120">
+            <template #default="{ row }">
+              <span class="date-text">{{ parseTime(row.recordTime, '{y}-{m}-{d}') }}</span>
+            </template>
+          </el-table-column>
+
+          <el-table-column label="操作" align="center" width="200" fixed="right">
+            <template #default="{ row }">
+              <div class="action-buttons">
+                <el-button type="text" icon="el-icon-view" @click="handleView(row)">查看</el-button>
+                <el-button type="text" icon="el-icon-edit" @click="handleEdit(row)" v-hasPermi="['ems:ledger:edit']">编辑</el-button>
+                <el-button type="text" icon="el-icon-delete" class="danger-btn" @click="handleDelete(row)" v-hasPermi="['ems:ledger:remove']">删除</el-button>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <!-- 分页 -->
+      <div class="pagination-wrapper" v-show="total > 0">
+        <el-pagination
+          background
+          :current-page.sync="queryParams.pageNum"
+          :page-sizes="[10, 20, 50, 100]"
+          :page-size.sync="queryParams.pageSize"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="total"
+          @size-change="getList"
+          @current-change="getList"
+        />
+      </div>
+    </div>
+
+    <!-- 新增/编辑对话框 -->
+    <el-dialog
+      :title="dialogTitle"
+      :visible.sync="dialogVisible"
+      width="720px"
+      :close-on-click-modal="false"
+      custom-class="ledger-dialog"
+      append-to-body
+      @open="onDialogOpen"
+    >
+      <el-form ref="ledgerForm" :model="form" :rules="rules" label-width="100px" class="ledger-form">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="记录编号" prop="recordCode">
+              <el-input v-model="form.recordCode" placeholder="自动生成" :disabled="isView">
+                <template #append v-if="!isView && !form.id">
+                  <el-button icon="el-icon-refresh" @click="generateCode">生成</el-button>
+                </template>
+              </el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="创建时间" prop="recordTime">
+              <el-date-picker
+                v-model="form.recordTime"
+                type="date"
+                value-format="yyyy-MM-dd"
+                placeholder="选择日期"
+                style="width: 100%"
+                :disabled="isView"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 对象类型:只有设施和设备 -->
+        <el-form-item label="对象类型" prop="objType">
+          <el-radio-group v-model="form.objType" @change="handleObjTypeChange" :disabled="isView">
+            <el-radio-button label="3">
+              <i class="el-icon-office-building"></i>
+              设施
+            </el-radio-button>
+            <el-radio-button label="2">
+              <i class="el-icon-cpu"></i>
+              设备
+            </el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+
+        <!-- 台账对象选择 -->
+        <el-form-item label="台账对象" prop="objCode" v-if="form.objType && !isView">
+          <!-- 设施类型 -->
+          <template v-if="form.objType === '3'">
+            <el-select
+              v-model="form.objCode"
+              placeholder="请选择设施"
+              filterable
+              @change="handleFacsSelect"
+              clearable
+              style="width: 100%"
+              :loading="facsLoading"
+            >
+              <el-option-group
+                v-for="group in facsGroupList"
+                :key="group.category"
+                :label="group.categoryName"
+              >
+                <el-option
+                  v-for="item in group.children"
+                  :key="item.facsCode"
+                  :label="item.facsName"
+                  :value="item.facsCode"
+                >
+                  <div class="select-option">
+                    <span class="option-name">{{ item.facsName }}</span>
+                    <span class="option-extra">{{ item.refAreaName }}</span>
+                  </div>
+                </el-option>
+              </el-option-group>
+            </el-select>
+          </template>
+
+          <!-- 设备类型 - 分类和设备在一行 -->
+          <template v-else-if="form.objType === '2'">
+            <div class="device-select-row">
+              <el-select
+                v-model="form.deviceCategory"
+                placeholder="设备分类"
+                @change="handleDeviceCategoryChange"
+                clearable
+                class="category-select"
+              >
+                <el-option
+                  v-for="item in deviceCategoryOptions"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                >
+                  <span><i :class="item.icon" style="margin-right: 5px;"></i>{{ item.label }}</span>
+                </el-option>
+              </el-select>
+              <el-select
+                v-model="form.objCode"
+                placeholder="请选择设备"
+                filterable
+                :loading="deviceLoading"
+                @change="handleDeviceSelect"
+                clearable
+                class="device-select"
+              >
+                <el-option
+                  v-for="item in deviceList"
+                  :key="item.deviceCode"
+                  :label="item.deviceName"
+                  :value="item.deviceCode"
+                >
+                  <div class="select-option">
+                    <span class="option-name">{{ item.deviceName }}</span>
+                    <span class="option-extra">{{ item.location || item.locationRefName }}</span>
+                  </div>
+                </el-option>
+              </el-select>
+            </div>
+          </template>
+        </el-form-item>
+
+        <!-- 查看模式显示对象信息 -->
+        <el-form-item label="台账对象" v-if="isView && form.objName">
+          <el-input :value="form.objName" disabled />
+        </el-form-item>
+
+        <el-form-item label="安装位置" prop="insLocation">
+          <el-input v-model="form.insLocation" placeholder="选择台账对象后自动填充" :disabled="isView" />
+        </el-form-item>
+
+        <el-form-item label="维护标题" prop="maintainTitle">
+          <el-input v-model="form.maintainTitle" placeholder="请输入维护标题" :disabled="isView" />
+        </el-form-item>
+
+        <el-form-item label="维护内容">
+          <div v-if="isView" class="view-content" v-html="form.maintainContent || '暂无内容'"></div>
+          <editor v-else v-model="form.maintainContent" :min-height="200" />
+        </el-form-item>
+
+        <el-form-item label="维护人" prop="maintainPerson">
+          <el-input v-model="form.maintainPerson" placeholder="请输入维护人" :disabled="isView" style="width: 200px" />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false">取 消</el-button>
+          <el-button v-if="!isView" type="primary" :loading="submitLoading" @click="submitForm">
+            确 定
+          </el-button>
+          <el-button v-else type="primary" @click="switchToEdit" v-hasPermi="['ems:ledger:edit']">
+            编 辑
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listLedger, getLedger, addLedger, updateLedger, delLedger, generateRecordCode } from '@/api/device/ledger'
+import { listDevRecursionByArea } from '@/api/device/device'
+import { listAllFacs } from '@/api/basecfg/emsfacs'
+import { areaTreeSelect } from '@/api/basecfg/area'
+
+export default {
+  name: 'Ledger',
+  data() {
+    return {
+      // 加载状态
+      loading: false,
+      submitLoading: false,
+      deviceLoading: false,
+      facsLoading: false,
+
+      // 显示控制
+      showSearch: true,
+      dialogVisible: false,
+      dialogTitle: '',
+      isView: false,
+
+      // 列表数据
+      ledgerList: [],
+      total: 0,
+      ids: [],
+
+      // 左侧树形数据
+      areaOptions: [],
+      areaName: '',
+      defaultExpandedKeys: [],
+      defaultProps: {
+        children: 'children',
+        label: 'label'
+      },
+
+      // 设施数据
+      facsList: [],
+      facsGroupList: [],
+
+      // 设备数据
+      deviceList: [],
+
+      // 设备分类选项
+      deviceCategoryOptions: [
+        { value: 'E', label: '产能设备', icon: 'el-icon-sunny' },
+        { value: 'C', label: '储能设备', icon: 'el-icon-coin' },
+        { value: 'W', label: '输配设备', icon: 'el-icon-connection' },
+        { value: 'Z', label: '用能设备', icon: 'el-icon-lightning' }
+      ],
+
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        objType: '3', // 默认设施
+        objName: null,
+        maintainTitle: null,
+        maintainPerson: null,
+        recordTimeRange: [],
+        areaCode: ''
+      },
+
+      // 表单数据
+      form: {},
+
+      // 表单校验
+      rules: {
+        recordCode: [{ required: true, message: '记录编号不能为空', trigger: 'blur' }],
+        objType: [{ required: true, message: '请选择对象类型', trigger: 'change' }],
+        maintainTitle: [{ required: true, message: '维护标题不能为空', trigger: 'blur' }],
+        recordTime: [{ required: true, message: '请选择日期', trigger: 'change' }]
+      },
+
+      // 日期选择器配置
+      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])
+            }
+          }
+        ]
+      }
+    }
+  },
+
+  async created() {
+    // 加载左侧区域树(用于筛选)
+    await this.getAreaTree()
+    // 预加载设施列表
+    await this.loadFacsList()
+    // 加载台账列表
+    this.getList(true)
+  },
+
+  watch: {
+    areaName(val) {
+      this.$refs.tree.filter(val)
+    }
+  },
+
+  methods: {
+    // 获取树节点图标
+    getTreeIcon(data) {
+      if (data.id === '-1') return 'el-icon-s-home'
+      return 'el-icon-office-building'
+    },
+
+    // 过滤树节点
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
+    filterNode(value, data) {
+      if (!value) return true
+      return data.label.indexOf(value) !== -1
+    },
+
+    // 获取左侧区域树(用于数据筛选)
+    async getAreaTree() {
+      try {
+        const response = await areaTreeSelect('0', 1)
+        this.areaOptions = [{
+          id: '-1',
+          label: '全部',
+          children: []
+        }].concat(response.data || [])
+        this.defaultExpandedKeys = ['-1']
+        this.$nextTick(() => {
+          if (this.$refs.tree) {
+            this.$refs.tree.setCurrentKey('-1')
+          }
+        })
+      } catch (error) {
+        console.error('获取区域树失败:', error)
+      }
+    },
+
+    // 加载设施列表
+    async loadFacsList() {
+      this.facsLoading = true
+      try {
+        const response = await listAllFacs()
+        this.facsList = response.data || []
+        // 按分类分组
+        const grouped = {}
+        this.facsList.forEach(item => {
+          const key = item.facsCategory
+          if (!grouped[key]) {
+            grouped[key] = {
+              category: key,
+              categoryName: item.facsCategoryName,
+              children: []
+            }
+          }
+          grouped[key].children.push(item)
+        })
+        this.facsGroupList = Object.values(grouped)
+      } catch (error) {
+        console.error('加载设施列表失败:', error)
+        this.facsList = []
+        this.facsGroupList = []
+      } finally {
+        this.facsLoading = false
+      }
+    },
+
+    // 加载设备列表
+    async loadDeviceList(category) {
+      this.deviceLoading = true
+      this.deviceList = []
+      try {
+        const params = {
+          pageNum: 1,
+          pageSize: 9999
+        }
+        // 如果有分类则按分类筛选
+        if (category) {
+          params.deviceCategory = category
+        }
+        // 如果左侧树选中了区域,按区域筛选
+        if (this.queryParams.areaCode && this.queryParams.areaCode !== '-1' && this.queryParams.areaCode !== '') {
+          params.locationRef = this.queryParams.areaCode
+        }
+        const response = await listDevRecursionByArea(params)
+        this.deviceList = response.rows || []
+      } catch (error) {
+        console.error('加载设备列表失败:', error)
+        this.deviceList = []
+      } finally {
+        this.deviceLoading = false
+      }
+    },
+
+    // 左侧树节点点击
+    handleNodeClick(data) {
+      this.queryParams.areaCode = data.id === '-1' ? '' : data.id
+      this.handleQuery()
+    },
+
+    // 获取台账列表
+    getList(refresh = false) {
+      if (refresh) {
+        this.queryParams.pageNum = 1
+      }
+      this.loading = true
+      listLedger(this.queryParams).then(response => {
+        this.ledgerList = response.rows || []
+        this.total = response.total || 0
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+
+    // 搜索
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+
+    // 重置
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.queryParams.recordTimeRange = []
+      this.handleQuery()
+    },
+
+    // 切换顶部Tab
+    switchTab(type) {
+      this.queryParams.objType = type
+      this.resetQuery()
+    },
+
+    // 多选
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+    },
+
+    // 重置表单
+    resetForm(formName) {
+      if (this.$refs[formName]) {
+        this.$refs[formName].resetFields()
+      }
+    },
+
+    // 初始化表单
+    initForm() {
+      this.form = {
+        id: null,
+        recordCode: '',
+        objType: '3', // 默认设施
+        objCode: null,
+        objName: '',
+        recordTime: null,
+        insLocation: '',
+        maintainTitle: '',
+        maintainContent: '',
+        maintainPerson: '',
+        deviceCategory: null,
+        areaCode: '' // 区域编码
+      }
+      this.deviceList = []
+    },
+
+    // 生成编号
+    generateCode() {
+      this.form.recordCode = generateRecordCode()
+    },
+
+    // 对话框打开时
+    onDialogOpen() {
+      // 确保设施列表已加载(默认是设施类型)
+      if (!this.isView && this.form.objType === '3') {
+        if (this.facsGroupList.length === 0) {
+          this.loadFacsList()
+        }
+      }
+    },
+
+    // 对象类型变更
+    handleObjTypeChange(type) {
+      // 清空选择
+      this.form.objCode = null
+      this.form.objName = ''
+      this.form.insLocation = ''
+      this.form.deviceCategory = null
+      this.deviceList = []
+
+      if (type === '3') {
+        // 设施类型 - 确保设施列表已加载
+        if (this.facsGroupList.length === 0) {
+          this.loadFacsList()
+        }
+      } else if (type === '2') {
+        // 设备类型 - 加载全部设备
+        this.loadDeviceList('')
+      }
+    },
+
+    // 设备分类变更
+    handleDeviceCategoryChange(category) {
+      this.form.objCode = null
+      this.form.objName = ''
+      this.form.insLocation = ''
+      this.loadDeviceList(category)
+    },
+
+    // 设施选择
+    handleFacsSelect(facsCode) {
+      const facs = this.facsList.find(f => f.facsCode === facsCode)
+      if (facs) {
+        this.form.objName = facs.facsName
+        this.form.insLocation = facs.refAreaName || ''
+        this.form.areaCode = facs.refArea || '' // 设施的区域编码
+      }
+    },
+
+    // 设备选择
+    handleDeviceSelect(deviceCode) {
+      const device = this.deviceList.find(d => d.deviceCode === deviceCode)
+      if (device) {
+        this.form.objName = device.deviceName
+        this.form.insLocation = device.location || device.areaPath || device.locationRefName || ''
+        this.form.areaCode = device.areaCode || device.locationRef || '' // 设备的区域编码
+      }
+    },
+
+    // 新增
+    handleAdd() {
+      this.initForm()
+      this.generateCode()
+      this.form.recordTime = this.parseTime(new Date(), '{y}-{m}-{d}')
+      this.dialogTitle = '新增设备台账'
+      this.isView = false
+      this.dialogVisible = true
+    },
+
+    // 查看
+    handleView(row) {
+      this.initForm()
+      getLedger(row.id).then(response => {
+        this.form = { ...response.data }
+        this.dialogTitle = '查看设备台账'
+        this.isView = true
+        this.dialogVisible = true
+      })
+    },
+
+    // 编辑
+    handleEdit(row) {
+      this.initForm()
+      getLedger(row.id).then(response => {
+        this.form = { ...response.data }
+        this.dialogTitle = '编辑设备台账'
+        this.isView = false
+        this.dialogVisible = true
+        // 根据对象类型加载对应数据
+        this.$nextTick(() => {
+          if (this.form.objType === '3') {
+            if (this.facsGroupList.length === 0) {
+              this.loadFacsList()
+            }
+          } else if (this.form.objType === '2') {
+            this.loadDeviceList(this.form.deviceCategory || '')
+          }
+        })
+      })
+    },
+
+    // 切换到编辑模式
+    switchToEdit() {
+      this.isView = false
+      this.dialogTitle = '编辑设备台账'
+      // 根据对象类型加载对应数据
+      if (this.form.objType === '3') {
+        if (this.facsGroupList.length === 0) {
+          this.loadFacsList()
+        }
+      } else if (this.form.objType === '2') {
+        this.loadDeviceList(this.form.deviceCategory || '')
+      }
+    },
+
+    // 提交表单
+    submitForm() {
+      this.$refs.ledgerForm.validate(valid => {
+        if (!valid) return
+
+        this.submitLoading = true
+        const submitData = { ...this.form }
+
+        // 处理objCode
+        if (Array.isArray(submitData.objCode)) {
+          submitData.objCode = submitData.objCode.join(',')
+        }
+
+        const request = submitData.id ? updateLedger(submitData) : addLedger(submitData)
+
+        request.then(() => {
+          this.$modal.msgSuccess(submitData.id ? '修改成功' : '新增成功')
+          this.dialogVisible = false
+          this.getList()
+        }).finally(() => {
+          this.submitLoading = false
+        })
+      })
+    },
+
+    // 删除
+    handleDelete(row) {
+      const ids = row.id || this.ids.join(',')
+      this.$modal.confirm(`确认删除该台账记录吗?`).then(() => {
+        return delLedger(ids)
+      }).then(() => {
+        this.getList()
+        this.$modal.msgSuccess('删除成功')
+      }).catch(() => {})
+    },
+
+    // 导出
+    handleExport() {
+      this.download('ems/device/ledger/export', {
+        ...this.queryParams
+      }, `设备台账_${new Date().getTime()}.xlsx`)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+// 变量定义
+$primary-color: #4f46e5;
+$primary-light: #818cf8;
+$bg-light: #f8fafc;
+$border-color: #e2e8f0;
+$text-primary: #1e293b;
+$text-secondary: #64748b;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+$radius: 12px;
+$radius-sm: 8px;
+
+.ledger-container {
+  display: flex;
+  gap: 20px;
+  padding: 20px;
+  min-height: calc(100vh - 84px);
+  background: linear-gradient(135deg, #f0f4ff 0%, #faf5ff 50%, #fff1f2 100%);
+}
+
+// 左侧边栏
+.sidebar {
+  width: 280px;
+  flex-shrink: 0;
+  background: #fff;
+  border-radius: $radius;
+  box-shadow: $shadow-md;
+  overflow: hidden;
+
+  .sidebar-header {
+    padding: 20px;
+    background: linear-gradient(135deg, $primary-color 0%, $primary-light 100%);
+
+    .sidebar-title {
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #fff;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      i {
+        font-size: 20px;
+      }
+    }
+  }
+
+  .search-box {
+    padding: 16px;
+    border-bottom: 1px solid $border-color;
+
+    ::v-deep .el-input__inner {
+      border-radius: $radius-sm;
+      border-color: $border-color;
+
+      &:focus {
+        border-color: $primary-color;
+      }
+    }
+  }
+
+  .tree-wrapper {
+    padding: 12px;
+    max-height: calc(100vh - 300px);
+    overflow-y: auto;
+
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: #cbd5e1;
+      border-radius: 3px;
+    }
+
+    ::v-deep .el-tree {
+      background: transparent;
+
+      .el-tree-node__content {
+        height: 42px;
+        border-radius: $radius-sm;
+        margin-bottom: 4px;
+        transition: all 0.2s;
+
+        &:hover {
+          background: $bg-light;
+        }
+      }
+
+      .el-tree-node.is-current > .el-tree-node__content {
+        background: linear-gradient(135deg, rgba(79, 70, 229, 0.1) 0%, rgba(129, 140, 248, 0.1) 100%);
+        color: $primary-color;
+
+        .node-icon {
+          color: $primary-color;
+        }
+      }
+    }
+
+    .tree-node {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      .node-icon {
+        font-size: 16px;
+        color: $text-secondary;
+      }
+
+      .node-label {
+        font-size: 14px;
+      }
+    }
+  }
+}
+
+// 主内容区
+.main-content {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  min-width: 0;
+}
+
+// 标签页
+.content-header {
+  background: #fff;
+  border-radius: $radius;
+  padding: 4px;
+  box-shadow: $shadow-sm;
+
+  .tabs-wrapper {
+    display: flex;
+    gap: 4px;
+
+    .tab-item {
+      flex: 1;
+      padding: 12px 20px;
+      text-align: center;
+      font-size: 14px;
+      font-weight: 500;
+      color: $text-secondary;
+      border-radius: $radius-sm;
+      cursor: pointer;
+      transition: all 0.3s;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 6px;
+
+      i {
+        font-size: 16px;
+      }
+
+      &:hover {
+        color: $primary-color;
+        background: $bg-light;
+      }
+
+      &.active {
+        color: #fff;
+        background: linear-gradient(135deg, $primary-color 0%, $primary-light 100%);
+        box-shadow: 0 2px 8px rgba(79, 70, 229, 0.3);
+      }
+    }
+  }
+}
+
+// 筛选区
+.filter-section {
+  background: #fff;
+  border-radius: $radius;
+  padding: 20px;
+  box-shadow: $shadow-sm;
+
+  .filter-form {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12px;
+
+    ::v-deep .el-form-item {
+      margin-bottom: 0;
+      margin-right: 0;
+
+      .el-form-item__label {
+        color: $text-secondary;
+        font-weight: 500;
+      }
+
+      .el-input__inner,
+      .el-date-editor {
+        border-radius: $radius-sm;
+      }
+    }
+
+    .filter-buttons {
+      margin-left: auto;
+
+      .el-button {
+        border-radius: $radius-sm;
+      }
+    }
+  }
+}
+
+// 工具栏
+.toolbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  .toolbar-left {
+    display: flex;
+    gap: 12px;
+
+    .el-button {
+      border-radius: $radius-sm;
+    }
+  }
+
+  .toolbar-right {
+    display: flex;
+    gap: 8px;
+
+    .el-button.is-circle {
+      border: none;
+      background: #fff;
+      box-shadow: $shadow-sm;
+
+      &:hover {
+        color: $primary-color;
+        background: $bg-light;
+      }
+    }
+  }
+}
+
+// 表格
+.table-wrapper {
+  background: #fff;
+  border-radius: $radius;
+  overflow: hidden;
+  box-shadow: $shadow-sm;
+
+  ::v-deep .el-table {
+    th.el-table__cell {
+      border-bottom: 2px solid $border-color;
+    }
+
+    .cell-area {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+
+      i {
+        color: $primary-color;
+      }
+    }
+
+    .cell-object {
+      .obj-name {
+        font-weight: 500;
+        color: $text-primary;
+      }
+    }
+
+    .maintain-title {
+      color: $text-primary;
+    }
+
+    .date-text {
+      color: $text-secondary;
+      font-size: 13px;
+    }
+
+    .action-buttons {
+      display: flex;
+      gap: 8px;
+      justify-content: center;
+
+      .el-button {
+        padding: 4px 8px;
+      }
+
+      .danger-btn {
+        color: #ef4444;
+
+        &:hover {
+          color: #dc2626;
+        }
+      }
+    }
+  }
+}
+
+// 分页
+.pagination-wrapper {
+  display: flex;
+  justify-content: flex-end;
+  padding: 16px;
+  background: #fff;
+  border-radius: $radius;
+  box-shadow: $shadow-sm;
+
+  ::v-deep .el-pagination {
+    .btn-prev,
+    .btn-next,
+    .el-pager li {
+      border-radius: $radius-sm;
+    }
+
+    .el-pager li.active {
+      background: $primary-color;
+    }
+  }
+}
+
+// 对话框
+::v-deep .ledger-dialog {
+  border-radius: $radius;
+
+  .el-dialog__header {
+    padding: 20px 24px;
+    border-bottom: 1px solid $border-color;
+
+    .el-dialog__title {
+      font-size: 18px;
+      font-weight: 600;
+      color: $text-primary;
+    }
+  }
+
+  .el-dialog__body {
+    padding: 24px;
+  }
+
+  .el-dialog__footer {
+    padding: 16px 24px;
+    border-top: 1px solid $border-color;
+  }
+}
+
+.ledger-form {
+  ::v-deep .el-radio-button__inner {
+    display: flex;
+    align-items: center;
+    gap: 4px;
+  }
+
+  .view-content {
+    padding: 12px;
+    background: $bg-light;
+    border-radius: $radius-sm;
+    min-height: 100px;
+    line-height: 1.6;
+  }
+}
+
+// 下拉选项样式
+.select-option {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+
+  .option-name {
+    font-weight: 500;
+    color: $text-primary;
+  }
+
+  .option-extra {
+    font-size: 12px;
+    color: $text-secondary;
+    margin-left: 10px;
+  }
+}
+
+// 设备选择行样式
+.device-select-row {
+  display: flex;
+  gap: 10px;
+  width: 100%;
+
+  .category-select {
+    width: 140px;
+    flex-shrink: 0;
+  }
+
+  .device-select {
+    flex: 1;
+  }
+}
+
+// 动画
+.slide-fade-enter-active {
+  transition: all 0.3s ease;
+}
+
+.slide-fade-leave-active {
+  transition: all 0.2s ease;
+}
+
+.slide-fade-enter,
+.slide-fade-leave-to {
+  transform: translateY(-10px);
+  opacity: 0;
+}
+
+// 响应式
+@media (max-width: 1200px) {
+  .ledger-container {
+    flex-direction: column;
+  }
+
+  .sidebar {
+    width: 100%;
+
+    .tree-wrapper {
+      max-height: 300px;
+    }
+  }
+}
+</style>

+ 6 - 6
ems-ui-cloud/src/views/inspection/plan/components/PlanForm.vue

@@ -37,6 +37,12 @@
         </el-col>
       </el-row>
 
+      <el-form-item label="巡检目标" prop="targetCodeList">
+        <target-selector ref="targetSelector" v-model="form.targetCodeList"
+                         :target-type="form.targetType" :area-code="form.areaCode" @change="handleTargetChange"
+        />
+      </el-form-item>
+
       <el-row :gutter="20">
         <el-col :span="12" v-if="form.planType === 1">
           <el-form-item label="默认执行人" prop="executor">
@@ -101,12 +107,6 @@
         </el-row>
       </template>
 
-      <el-form-item label="巡检目标" prop="targetCodeList">
-        <target-selector ref="targetSelector" v-model="form.targetCodeList"
-                         :target-type="form.targetType" :area-code="form.areaCode" @change="handleTargetChange"
-        />
-      </el-form-item>
-
       <el-form-item label="计划描述" prop="description">
         <el-input v-model="form.description" type="textarea" :rows="2" maxlength="256" show-word-limit/>
       </el-form-item>

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels