From 9646ba4b026e4dbee0c0d5ac3d52b428bfef7b29 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 28 八月 2026 10:49:44 +0800
Subject: [PATCH] 新疆-新聚 1.销售台账中的销售合同按框架合同处理 2.设备台账需按车间/区域汇总设备,车间/位置可自行新增,且支持子父级递归 3.计量器具台账支持通过 Excel 文件批量新增数据 4.设备保养定时任务新增“年度保养”任务频率

---
 src/views/equipmentManagement/ledger/index.vue |  646 ++++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 490 insertions(+), 156 deletions(-)

diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index af10532..95baaca 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/src/views/equipmentManagement/ledger/index.vue
@@ -1,84 +1,160 @@
 <template>
   <div class="app-container">
-    <el-form :model="filters" :inline="true">
-      <el-form-item label="璁惧鍚嶇О">
-        <el-input
-          v-model="filters.deviceName"
-          style="width: 200px"
-          placeholder="璇疯緭鍏ヨ澶囧悕绉�"
-          clearable
-          @change="getTableData"
-        />
-      </el-form-item>
-      <el-form-item label="瑙勬牸鍨嬪彿">
-        <el-input
-            v-model="filters.deviceModel"
-            style="width: 200px"
-            placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
-            clearable
-            @change="getTableData"
-        />
-      </el-form-item>
-      <el-form-item label="渚涘簲鍟�">
-        <el-input
-            v-model="filters.supplierName"
-            style="width: 200px"
-            placeholder="璇疯緭鍏ヤ緵搴斿晢"
-            clearable
-            @change="getTableData"
-        />
-      </el-form-item>
-      <el-form-item label="褰曞叆鏃ユ湡:">
-        <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
-                        placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" @click="getTableData">鎼滅储</el-button>
-        <el-button @click="resetFilters">閲嶇疆</el-button>
-      </el-form-item>
-    </el-form>
-    <div class="table_list">
-      <div class="actions">
-        <div></div>
-        <div>
-          <el-button type="primary" @click="add" icon="Plus"> 鏂板 </el-button>
-          <el-button type="info" @click="handleImport" icon="Upload">瀵煎叆</el-button>
-          <el-button @click="handleOut" icon="download">瀵煎嚭</el-button>
-          <el-button
-            type="danger"
-            icon="Delete"
-            :disabled="multipleList.length <= 0"
-            @click="deleteRow(multipleList.map((item) => item.id))"
-          >
-            鎵归噺鍒犻櫎
+    <div class="area-view">
+      <!-- 宸︿晶鍖哄煙鏍� -->
+      <div class="area-left">
+        <div class="area-left-header">
+          <el-button type="primary" @click="openAreaDialog('add')" icon="Plus">
+            鏂板鍖哄煙
           </el-button>
         </div>
+        <div
+          class="all-device-node"
+          :class="{ active: currentAreaId === null }"
+          @click="handleAllDeviceClick"
+        >
+          鍏ㄩ儴璁惧
+        </div>
+        <el-tree
+          ref="areaTreeRef"
+          v-loading="areaTreeLoading"
+          :data="areaTree"
+          node-key="id"
+          :props="{ label: 'label', children: 'children' }"
+          :expand-on-click-node="false"
+          default-expand-all
+          draggable
+          highlight-current
+          :current-node-key="currentAreaId"
+          @node-click="handleAreaNodeClick"
+          @node-drop="handleAreaNodeDrop"
+        >
+          <template #default="{ node, data }">
+            <div class="area-tree-node">
+              <span class="area-node-label">{{ data.label }}</span>
+              <span class="area-node-count">{{ data.deviceCount ?? 0 }}</span>
+              <span class="area-node-actions">
+                <el-button
+                  link
+                  type="primary"
+                  size="small"
+                  @click.stop="openAreaDialog('addChild', data)"
+                >
+                  瀛愮骇
+                </el-button>
+                <el-button
+                  link
+                  type="primary"
+                  size="small"
+                  @click.stop="openAreaDialog('edit', data)"
+                >
+                  缂栬緫
+                </el-button>
+                <el-button
+                  link
+                  type="danger"
+                  size="small"
+                  @click.stop="handleAreaDelete(node, data)"
+                >
+                  鍒犻櫎
+                </el-button>
+              </span>
+            </div>
+          </template>
+        </el-tree>
       </div>
-      <PIMTable
-        rowKey="id"
-        isSelection
-        :column="columns"
-        :tableData="dataList"
-        :page="{
-          current: pagination.currentPage,
-          size: pagination.pageSize,
-          total: pagination.total,
-        }"
-        @selection-change="handleSelectionChange"
-        @pagination="changePage"
-      >
-      </PIMTable>
+
+      <!-- 鍙充晶璁惧鍒楄〃 -->
+      <div class="area-right">
+        <el-form :model="filters" :inline="true">
+          <el-form-item label="璁惧鍚嶇О">
+            <el-input
+              v-model="filters.deviceName"
+              style="width: 200px"
+              placeholder="璇疯緭鍏ヨ澶囧悕绉�"
+              clearable
+              @change="getTableData"
+            />
+          </el-form-item>
+          <el-form-item label="瑙勬牸鍨嬪彿">
+            <el-input
+              v-model="filters.deviceModel"
+              style="width: 200px"
+              placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
+              clearable
+              @change="getTableData"
+            />
+          </el-form-item>
+          <el-form-item label="渚涘簲鍟�">
+            <el-input
+              v-model="filters.supplierName"
+              style="width: 200px"
+              placeholder="璇疯緭鍏ヤ緵搴斿晢"
+              clearable
+              @change="getTableData"
+            />
+          </el-form-item>
+          <el-form-item label="褰曞叆鏃ユ湡:">
+            <el-date-picker
+              v-model="filters.entryDate"
+              value-format="YYYY-MM-DD"
+              format="YYYY-MM-DD"
+              type="daterange"
+              placeholder="璇烽�夋嫨"
+              clearable
+              @change="changeDaterange"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+            <el-button @click="handleReset">閲嶇疆</el-button>
+          </el-form-item>
+        </el-form>
+        <div class="table_list">
+          <div class="actions">
+            <div></div>
+            <div>
+              <el-button type="primary" @click="add" icon="Plus"> 鏂板 </el-button>
+              <el-button type="info" @click="handleImport" icon="Upload">瀵煎叆</el-button>
+              <el-button @click="handleOut" icon="download">瀵煎嚭</el-button>
+              <el-button
+                type="danger"
+                icon="Delete"
+                :disabled="multipleList.length <= 0"
+                @click="deleteRow(multipleList.map((item) => item.id))"
+              >
+                鎵归噺鍒犻櫎
+              </el-button>
+            </div>
+          </div>
+          <PIMTable
+            rowKey="id"
+            isSelection
+            :column="columns"
+            :tableData="dataList"
+            :page="{
+              current: pagination.currentPage,
+              size: pagination.pageSize,
+              total: pagination.total,
+            }"
+            @selection-change="handleSelectionChange"
+            @pagination="changePage"
+          >
+          </PIMTable>
+        </div>
+      </div>
     </div>
+
     <Modal ref="modalRef" @success="getTableData"></Modal>
     <el-dialog v-model="qrDialogVisible" title="浜岀淮鐮�" width="300px" draggable>
-      <div style="text-align:center;">
-        <img :src="qrCodeUrl" alt="浜岀淮鐮�" style="width:200px;height:200px;" />
-        <div style="margin:10px 0;">
+      <div style="text-align: center">
+        <img :src="qrCodeUrl" alt="浜岀淮鐮�" style="width: 200px; height: 200px" />
+        <div style="margin: 10px 0">
           <el-button type="primary" @click="downloadQRCode">涓嬭浇浜岀淮鐮佸浘鐗�</el-button>
         </div>
       </div>
     </el-dialog>
-    
+
     <!-- 瀵煎叆瀵硅瘽妗� -->
     <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
       <el-upload
@@ -98,7 +174,14 @@
         <template #tip>
           <div class="el-upload__tip text-center">
             <span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
-            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline; margin-left: 5px;" @click="importTemplate">涓嬭浇妯℃澘</el-link>
+            <el-link
+              type="primary"
+              :underline="false"
+              style="font-size: 12px; vertical-align: baseline; margin-left: 5px"
+              @click="importTemplate"
+            >
+              涓嬭浇妯℃澘
+            </el-link>
           </div>
         </template>
       </el-upload>
@@ -118,6 +201,7 @@
         <el-descriptions-item label="璁惧鍝佺墝">{{ detailData.deviceBrand }}</el-descriptions-item>
         <el-descriptions-item label="璁惧绫诲瀷">{{ detailData.type }}</el-descriptions-item>
         <el-descriptions-item label="渚涘簲鍟�">{{ detailData.supplierName }}</el-descriptions-item>
+        <el-descriptions-item label="鎵�灞炲尯鍩�">{{ detailData.areaName }}</el-descriptions-item>
         <el-descriptions-item label="瀛樻斁浣嶇疆">{{ detailData.storageLocation }}</el-descriptions-item>
         <el-descriptions-item label="鍗曚綅">{{ detailData.unit }}</el-descriptions-item>
         <el-descriptions-item label="鏁伴噺">{{ detailData.number }}</el-descriptions-item>
@@ -128,17 +212,22 @@
         <el-descriptions-item label="绋庣巼(%)">{{ detailData.taxRate }}</el-descriptions-item>
         <el-descriptions-item label="涓嶅惈绋庢�讳环">{{ detailData.unTaxIncludingPriceTotal }}</el-descriptions-item>
         <el-descriptions-item label="褰曞叆鏃ユ湡">{{ detailData.createTime }}</el-descriptions-item>
-        <el-descriptions-item label="棰勮杩愯鏃堕棿">{{ detailData.planRuntimeTime ? dayjs(detailData.planRuntimeTime).format('YYYY-MM-DD') : '' }}</el-descriptions-item>
+        <el-descriptions-item label="棰勮杩愯鏃堕棿">{{
+          detailData.planRuntimeTime ? dayjs(detailData.planRuntimeTime).format('YYYY-MM-DD') : ''
+        }}</el-descriptions-item>
         <el-descriptions-item label="璁惧鍥剧墖" :span="2">
-          <div v-if="detailData.storageBlobVOs && detailData.storageBlobVOs.length > 0" style="display: flex; gap: 10px; flex-wrap: wrap;">
-            <el-image 
-              v-for="(file, index) in detailData.storageBlobVOs" 
-              :key="index" 
-              :src="file.previewURL || file.url" 
-              :preview-src-list="detailData.storageBlobVOs.map(u => u.previewURL || u.url)"
+          <div
+            v-if="detailData.storageBlobVOs && detailData.storageBlobVOs.length > 0"
+            style="display: flex; gap: 10px; flex-wrap: wrap"
+          >
+            <el-image
+              v-for="(file, index) in detailData.storageBlobVOs"
+              :key="index"
+              :src="file.previewURL || file.url"
+              :preview-src-list="detailData.storageBlobVOs.map((u) => u.previewURL || u.url)"
               :initial-index="index"
-              style="width: 100px; height: 100px" 
-              fit="cover" 
+              style="width: 100px; height: 100px"
+              fit="cover"
             />
           </div>
           <span v-else>鏃犲浘鐗�</span>
@@ -148,14 +237,57 @@
         <el-button @click="detailDialogVisible = false">鍏抽棴</el-button>
       </template>
     </el-dialog>
+
+    <!-- 鍖哄煙鏂板/缂栬緫寮圭獥 -->
+    <el-dialog
+      v-model="areaDialogVisible"
+      :title="areaDialogTitle"
+      width="420px"
+      draggable
+      @closed="areaFormRef && areaFormRef.resetFields()"
+    >
+      <el-form :model="areaForm" :rules="areaRules" ref="areaFormRef" label-width="90px">
+        <el-form-item label="涓婄骇鍖哄煙" prop="parentId">
+          <el-tree-select
+            v-model="areaForm.parentId"
+            :data="areaTree"
+            node-key="id"
+            :props="{ label: 'label', children: 'children' }"
+            check-strictly
+            clearable
+            placeholder="涓嶉�夊垯涓烘牴鍖哄煙"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="鍖哄煙鍚嶇О" prop="name">
+          <el-input v-model="areaForm.name" placeholder="璇疯緭鍏ュ尯鍩�/杞﹂棿/浣嶇疆鍚嶇О" clearable />
+        </el-form-item>
+        <el-form-item label="鎺掑簭" prop="sort">
+          <el-input-number v-model="areaForm.sort" :min="0" style="width: 100%" placeholder="鍙��" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitAreaForm">纭畾</el-button>
+        <el-button @click="areaDialogVisible = false">鍙栨秷</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
 import { usePaginationApi } from "@/hooks/usePaginationApi";
-// import { Search } from "@element-plus/icons-vue";
-import { getLedgerPage, delLedger, getLedgerById } from "@/api/equipmentManagement/ledger";
-import { onMounted, getCurrentInstance, ref, reactive } from "vue";
+import {
+  getLedgerPage,
+  delLedger,
+  getLedgerById,
+} from "@/api/equipmentManagement/ledger";
+import {
+  getDeviceAreaTree,
+  addDeviceArea,
+  editDeviceArea,
+  delDeviceArea,
+} from "@/api/equipmentManagement/area";
+import { onMounted, getCurrentInstance, ref, reactive, computed } from "vue";
 import Modal from "./Modal.vue";
 import { ElMessageBox, ElMessage } from "element-plus";
 import { UploadFilled } from "@element-plus/icons-vue";
@@ -178,20 +310,38 @@
 const detailDialogVisible = ref(false);
 const detailData = ref({});
 
+// 鍖哄煙鐩稿叧
+const areaTree = ref([]);
+const areaTreeLoading = ref(false);
+const areaTreeRef = ref();
+const currentAreaId = ref(null);
+const areaDialogVisible = ref(false);
+const areaDialogType = ref("add");
+const areaFormRef = ref();
+const areaForm = reactive({
+  id: undefined,
+  name: "",
+  parentId: null,
+  sort: 0,
+});
+const areaRules = {
+  name: [{ required: true, message: "璇疯緭鍏ュ尯鍩熷悕绉�", trigger: "blur" }],
+};
+const areaDialogTitle = computed(() => {
+  if (areaDialogType.value === "edit") return "缂栬緫鍖哄煙";
+  if (areaDialogType.value === "addChild") return "鏂板瀛愬尯鍩�";
+  return "鏂板鍖哄煙";
+});
+
 // 瀵煎叆鐩稿叧
-const uploadRef = ref(null)
+const uploadRef = ref(null);
 const upload = reactive({
-  // 鏄惁鏄剧ず寮瑰嚭灞�
   open: false,
-  // 寮瑰嚭灞傛爣棰�
   title: "",
-  // 鏄惁绂佺敤涓婁紶
   isUploading: false,
-  // 璁剧疆涓婁紶鐨勮姹傚ご閮�
   headers: { Authorization: "Bearer " + getToken() },
-  // 涓婁紶鐨勫湴鍧�
-  url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import"
-})
+  url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import",
+});
 
 const {
   filters,
@@ -209,6 +359,7 @@
     supplierName: undefined,
     entryDateStart: undefined,
     entryDateEnd: undefined,
+    areaId: undefined,
   },
   [
     {
@@ -226,6 +377,10 @@
     {
       label: "璁惧绫诲瀷",
       prop: "type",
+    },
+    {
+      label: "鎵�灞炲尯鍩�",
+      prop: "areaName",
     },
     {
       label: "渚涘簲鍟�",
@@ -247,43 +402,167 @@
       label: "褰曞叆鏃ユ湡",
       prop: "createTime",
       formatData: (v) => {
-        if (!v) return '';
-        // 濡傛灉鍖呭惈鏃跺垎绉掞紝鍙彇鏃ユ湡閮ㄥ垎
-        if (v.includes(' ')) {
-          return v.split(' ')[0];
+        if (!v) return "";
+        if (v.includes(" ")) {
+          return v.split(" ")[0];
         }
         return v;
       },
     },
-		{
-			dataType: "action",
-			label: "鎿嶄綔",
-			align: "center",
-			fixed: 'right',
-			width: 180,
-			operation: [
+    {
+      dataType: "action",
+      label: "鎿嶄綔",
+      align: "center",
+      fixed: "right",
+      width: 180,
+      operation: [
         {
           name: "璇︽儏",
           clickFun: (row) => {
             handleDetail(row);
           },
         },
-				{
-					name: "缂栬緫",
-					clickFun: (row) => {
-						edit(row.id)
-					},
-				},
-				{
-					name: "浜岀淮鐮�",
-					clickFun: (row) => {
-						showQRCode(row)
-					},
-				},
-			],
-		},
+        {
+          name: "缂栬緫",
+          clickFun: (row) => {
+            edit(row.id);
+          },
+        },
+        {
+          name: "浜岀淮鐮�",
+          clickFun: (row) => {
+            showQRCode(row);
+          },
+        },
+      ],
+    },
   ]
 );
+
+// 鍔犺浇鍖哄煙鏍�
+const getAreaTree = () => {
+  areaTreeLoading.value = true;
+  getDeviceAreaTree()
+    .then((res) => {
+      areaTree.value = res.data || [];
+    })
+    .finally(() => {
+      areaTreeLoading.value = false;
+    });
+};
+
+// 閲嶇疆鎼滅储涓庡尯鍩熺瓫閫�
+const handleReset = () => {
+  currentAreaId.value = null;
+  resetFilters();
+};
+
+// 鐐瑰嚮"鍏ㄩ儴璁惧"
+const handleAllDeviceClick = () => {
+  currentAreaId.value = null;
+  filters.areaId = undefined;
+  getTableData();
+};
+
+// 鐐瑰嚮鍖哄煙鑺傜偣
+const handleAreaNodeClick = (data) => {
+  currentAreaId.value = data.id;
+  filters.areaId = data.id;
+  getTableData();
+};
+
+// 鎷栨嫿璋冩暣鐖跺瓙鍏崇郴
+const handleAreaNodeDrop = (draggingNode, dropNode, dropType) => {
+  const node = draggingNode.data;
+  let parentId = null;
+  if (dropNode && dropType === "inner") {
+    parentId = dropNode.data.id;
+  } else if (dropNode) {
+    parentId = dropNode.data.parentId ?? null;
+  } else {
+    parentId = null;
+  }
+  if (parentId === node.id) {
+    proxy.$modal.msgWarning("涓嶈兘灏嗗尯鍩熸寕杞藉埌鑷韩");
+    getAreaTree();
+    return;
+  }
+  editDeviceArea({
+    id: node.id,
+    name: node.label,
+    parentId,
+    sort: node.sort,
+  })
+    .then(() => {
+      proxy.$modal.msgSuccess("璋冩暣鎴愬姛");
+      getAreaTree();
+    })
+    .catch(() => {
+      getAreaTree();
+    });
+};
+
+// 鎵撳紑鍖哄煙寮圭獥
+const openAreaDialog = (type, data) => {
+  areaDialogType.value = type;
+  areaForm.id = undefined;
+  areaForm.name = "";
+  areaForm.parentId = null;
+  areaForm.sort = 0;
+  if (type === "addChild" && data) {
+    areaForm.parentId = data.id;
+  } else if (type === "edit" && data) {
+    areaForm.id = data.id;
+    areaForm.name = data.label;
+    areaForm.parentId = data.parentId ?? null;
+    areaForm.sort = data.sort ?? 0;
+  }
+  areaDialogVisible.value = true;
+};
+
+// 鎻愪氦鍖哄煙琛ㄥ崟
+const submitAreaForm = () => {
+  if (!areaFormRef.value) return;
+  areaFormRef.value.validate((valid) => {
+    if (!valid) return;
+    const payload = {
+      name: areaForm.name,
+      parentId: areaForm.parentId ?? null,
+      sort: areaForm.sort ?? 0,
+    };
+    const api =
+      areaDialogType.value === "edit"
+        ? editDeviceArea({ ...payload, id: areaForm.id })
+        : addDeviceArea(payload);
+    api
+      .then(() => {
+        proxy.$modal.msgSuccess("鎿嶄綔鎴愬姛");
+        areaDialogVisible.value = false;
+        getAreaTree();
+      })
+      .catch(() => {});
+  });
+};
+
+// 鍒犻櫎鍖哄煙
+const handleAreaDelete = (node, data) => {
+  ElMessageBox.confirm("纭鍒犻櫎璇ュ尯鍩熷悧锛�", "鎻愮ず", {
+    confirmButtonText: "纭畾",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => delDeviceArea(data.id))
+    .then(() => {
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+      if (currentAreaId.value === data.id) {
+        currentAreaId.value = null;
+        filters.areaId = undefined;
+      }
+      getAreaTree();
+      getTableData();
+    })
+    .catch(() => {});
+};
 
 // 澶氶�夊悗鍋氫粈涔�
 const handleSelectionChange = (selectionList) => {
@@ -305,7 +584,7 @@
 };
 const changePage = ({ page, limit }) => {
   pagination.currentPage = page;
-	pagination.pageSize = limit;
+  pagination.pageSize = limit;
   onCurrentChange(page);
 };
 const deleteRow = (id) => {
@@ -351,36 +630,33 @@
 };
 
 const showQRCode = async (row) => {
-  // 鐩存帴浣跨敤URL锛屼笉瑕佺敤JSON.stringify鍖呰
-  const qrContent = proxy.javaApi + '/device-info?deviceId=' + row.id;
+  const qrContent = proxy.javaApi + "/device-info?deviceId=" + row.id;
   const qrDataUrl = await QRCode.toDataURL(qrContent, { width: 200, margin: 2 });
-  
-  // 鍒涘缓canvas鍚堟垚甯﹀悕绉扮殑浜岀淮鐮佸浘鐗�
-  const canvas = document.createElement('canvas');
-  const ctx = canvas.getContext('2d');
+
+  const canvas = document.createElement("canvas");
+  const ctx = canvas.getContext("2d");
   const qrSize = 200;
   const textHeight = 30;
   const padding = 10;
   canvas.width = qrSize + padding * 2;
   canvas.height = qrSize + textHeight + padding * 2;
-  
-  // 濉厖鐧借壊鑳屾櫙
-  ctx.fillStyle = '#ffffff';
+
+  ctx.fillStyle = "#ffffff";
   ctx.fillRect(0, 0, canvas.width, canvas.height);
-  
-  // 缁樺埗浜岀淮鐮�
+
   const qrImg = new Image();
   qrImg.src = qrDataUrl;
-  await new Promise((resolve) => { qrImg.onload = resolve; });
+  await new Promise((resolve) => {
+    qrImg.onload = resolve;
+  });
   ctx.drawImage(qrImg, padding, padding, qrSize, qrSize);
-  
-  // 缁樺埗璁惧鍚嶇О
-  ctx.fillStyle = '#333333';
-  ctx.font = 'bold 14px Arial';
-  ctx.textAlign = 'center';
-  ctx.fillText(row.deviceName || '', canvas.width / 2, qrSize + padding + 20);
-  
-  qrCodeUrl.value = canvas.toDataURL('image/png');
+
+  ctx.fillStyle = "#333333";
+  ctx.font = "bold 14px Arial";
+  ctx.textAlign = "center";
+  ctx.fillText(row.deviceName || "", canvas.width / 2, qrSize + padding + 20);
+
+  qrCodeUrl.value = canvas.toDataURL("image/png");
   qrRowData.value = row;
   qrDialogVisible.value = true;
 };
@@ -392,42 +668,100 @@
   a.click();
 };
 
-// 瀵煎叆鎸夐挳鎿嶄綔
 const handleImport = () => {
-  upload.title = "璁惧鍙拌处瀵煎叆"
-  upload.open = true
-}
+  upload.title = "璁惧鍙拌处瀵煎叆";
+  upload.open = true;
+};
 
-// 涓嬭浇妯℃澘鎿嶄綔
 const importTemplate = () => {
-  proxy.download("/device/ledger/downloadTemplate", {}, `璁惧鍙拌处瀵煎叆妯℃澘_${new Date().getTime()}.xlsx`)
-}
+  proxy.download(
+    "/device/ledger/downloadTemplate",
+    {},
+    `璁惧鍙拌处瀵煎叆妯℃澘_${new Date().getTime()}.xlsx`
+  );
+};
 
-// 鏂囦欢涓婁紶涓鐞�
 const handleFileUploadProgress = (event, file, fileList) => {
-  upload.isUploading = true
-}
+  upload.isUploading = true;
+};
 
-// 鏂囦欢涓婁紶鎴愬姛澶勭悊
 const handleFileSuccess = (response, file, fileList) => {
-  upload.open = false
-  upload.isUploading = false
-  proxy.$refs["uploadRef"].handleRemove(file)
-  proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "瀵煎叆缁撴灉", { dangerouslyUseHTMLString: true })
-  getTableData()
-}
+  upload.open = false;
+  upload.isUploading = false;
+  proxy.$refs["uploadRef"].handleRemove(file);
+  proxy.$alert(
+    "<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" +
+      response.msg +
+      "</div>",
+    "瀵煎叆缁撴灉",
+    { dangerouslyUseHTMLString: true }
+  );
+  getTableData();
+};
 
-// 鎻愪氦涓婁紶鏂囦欢
 const submitFileForm = () => {
-  proxy.$refs["uploadRef"].submit()
-}
+  proxy.$refs["uploadRef"].submit();
+};
 
 onMounted(() => {
+  getAreaTree();
   getTableData();
 });
 </script>
 
 <style lang="scss" scoped>
+.area-view {
+  display: flex;
+}
+.area-left {
+  width: 320px;
+  flex-shrink: 0;
+  padding: 16px;
+  background: #ffffff;
+  margin-right: 16px;
+}
+.area-left-header {
+  margin-bottom: 10px;
+}
+.all-device-node {
+  padding: 8px 12px;
+  cursor: pointer;
+  font-size: 14px;
+  border-radius: 4px;
+  margin-bottom: 8px;
+}
+.all-device-node:hover {
+  background: #f5f7fa;
+}
+.all-device-node.active {
+  background: #ecf5ff;
+  color: #409eff;
+}
+.area-tree-node {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 14px;
+  padding-right: 4px;
+}
+.area-node-count {
+  color: #909399;
+  font-size: 12px;
+  margin-left: 6px;
+}
+.area-node-actions {
+  display: none;
+}
+.area-tree-node:hover .area-node-actions {
+  display: inline-flex;
+}
+.area-right {
+  flex: 1;
+  min-width: 0;
+  padding: 16px;
+  background: #ffffff;
+}
 .table_list {
   margin-top: unset;
 }

--
Gitblit v1.9.3