From c9c7756d97b33d61876fa5b68582fcf8279020ce Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 21 四月 2026 14:05:39 +0800
Subject: [PATCH] 新疆马铃薯 1.设备巡检添加巡检状态和巡检结果展示

---
 src/views/equipmentManagement/ledger/index.vue |  707 ++++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 586 insertions(+), 121 deletions(-)

diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index 7fa970b..8e3fae5 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/src/views/equipmentManagement/ledger/index.vue
@@ -1,196 +1,499 @@
 <template>
-  <div class="app-container">
-    <el-form :model="filters" :inline="true">
-      <el-form-item label="璁惧鍚嶇О">
+  <div class="app-container ledger-view">
+    <div class="left-panel">
+      <div class="tree-toolbar">
         <el-input
-          v-model="filters.deviceName"
-          style="width: 240px"
-          placeholder="璇疯緭鍏ヨ澶囧悕绉�"
+          v-model="treeKeyword"
+          style="width: calc(100% - 102px)"
+          placeholder="璇疯緭鍏ュ尯鍩熷悕绉�"
           clearable
-          :prefix-icon="Search"
-          @change="getTableData"
+          prefix-icon="Search"
+          @input="filterTree"
+          @clear="filterTree"
         />
-      </el-form-item>
-      <el-form-item label="瑙勬牸鍨嬪彿">
-        <el-input
+        <el-button type="primary" @click="openAreaDialog('addRoot')">鏂板鍖哄煙</el-button>
+      </div>
+      <div class="tree-actions">
+        <el-button link type="primary" @click="resetTreeSelection">鍏ㄩ儴鍖哄煙</el-button>
+      </div>
+      <el-tree
+        ref="treeRef"
+        v-loading="treeLoading"
+        :data="treeData"
+        :props="treeProps"
+        node-key="id"
+        highlight-current
+        default-expand-all
+        :expand-on-click-node="false"
+        :filter-node-method="filterTreeNode"
+        class="ledger-tree"
+        @node-click="handleTreeNodeClick"
+      >
+        <template #default="{ node, data }">
+          <div class="tree-node">
+            <span class="tree-node-content">
+              <el-icon class="tree-node-icon">
+                <component
+                  :is="
+                    data.children && data.children.length > 0
+                      ? node.expanded
+                        ? 'FolderOpened'
+                        : 'Folder'
+                      : 'Tickets'
+                  "
+                />
+              </el-icon>
+              <span class="tree-node-label">{{ data.areaName }}</span>
+            </span>
+            <div class="tree-node-actions">
+              <el-button link type="primary" @click.stop="openAreaDialog('edit', data)">缂栬緫</el-button>
+              <el-button link type="primary" @click.stop="openAreaDialog('addChild', data)">鏂板</el-button>
+              <el-button
+                v-if="!hasChildren(data)"
+                link
+                type="danger"
+                @click.stop="handleDeleteArea(data)"
+              >
+                鍒犻櫎
+              </el-button>
+            </div>
+          </div>
+        </template>
+      </el-tree>
+    </div>
+
+    <div class="right-panel">
+      <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: 240px"
+            style="width: 200px"
             placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
             clearable
-            :prefix-icon="Search"
             @change="getTableData"
-        />
-      </el-form-item>
-      <el-form-item label="渚涘簲鍟�">
-        <el-input
+          />
+        </el-form-item>
+        <el-form-item label="渚涘簲鍟�">
+          <el-input
             v-model="filters.supplierName"
-            style="width: 240px"
+            style="width: 200px"
             placeholder="璇疯緭鍏ヤ緵搴斿晢"
             clearable
-            :prefix-icon="Search"
             @change="getTableData"
-        />
-      </el-form-item>
-      <el-form-item label="鍗曚綅">
-        <el-input
-            v-model="filters.unit"
-            style="width: 240px"
-            placeholder="璇疯緭鍏ュ崟浣�"
+          />
+        </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
-            :prefix-icon="Search"
-            @change="getTableData"
+            @change="changeDaterange"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+          <el-button @click="handleResetFilters">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+
+      <div class="table_list">
+        <div class="actions">
+          <div class="actions-tip">
+            <span v-if="selectedAreaName">褰撳墠鍖哄煙锛歿{ selectedAreaName }}</span>
+          </div>
+          <div>
+            <el-button type="primary" icon="Plus" @click="add">鏂板</el-button>
+            <el-button type="info" icon="Upload" @click="handleImport">瀵煎叆</el-button>
+            <el-button icon="download" @click="handleOut">瀵煎嚭</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"
         />
-      </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 @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>
+
+    <Modal ref="modalRef" @success="getTableData" />
+
+    <el-dialog
+      v-model="areaDialogVisible"
+      :title="areaDialogTitle"
+      width="480px"
+      @close="closeAreaDialog"
+    >
+      <el-form ref="areaFormRef" :model="areaForm" :rules="areaRules" label-width="88px">
+        <el-form-item label="鍖哄煙鍚嶇О" prop="areaName">
+          <el-input v-model="areaForm.areaName" placeholder="璇疯緭鍏ュ尯鍩熷悕绉�" />
+        </el-form-item>
+        <el-form-item label="鎺掑簭" prop="sort">
+          <el-input-number v-model="areaForm.sort" :min="0" :step="1" style="width: 100%" />
+        </el-form-item>
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input
+            v-model="areaForm.remark"
+            type="textarea"
+            :rows="4"
+            maxlength="200"
+            show-word-limit
+            placeholder="璇疯緭鍏ュ娉�"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitAreaForm">纭畾</el-button>
+          <el-button @click="closeAreaDialog">鍙栨秷</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="qrDialogVisible" title="浜岀淮鐮�" width="300px" draggable>
+      <div class="qr-dialog">
+        <img :src="qrCodeUrl" alt="浜岀淮鐮�" class="qr-image" />
+        <div class="qr-footer">
+          <el-button type="primary" @click="downloadQRCode">涓嬭浇浜岀淮鐮佸浘鐗�</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"
+    </el-dialog>
+
+    <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
+      <el-upload
+        ref="uploadRef"
+        :limit="1"
+        accept=".xlsx, .xls"
+        :headers="upload.headers"
+        :action="upload.url"
+        :disabled="upload.isUploading"
+        :on-progress="handleFileUploadProgress"
+        :on-success="handleFileSuccess"
+        :auto-upload="false"
+        drag
       >
-        <template #operation="{ row }">
-          <el-button type="primary" text @click="edit(row.id)" icon="editPen">
-            缂栬緫
-          </el-button>
-          <el-button
-            type="danger"
-            text
-            icon="delete"
-            @click="deleteRow(row.id)"
-          >
-            鍒犻櫎
-          </el-button>
+        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+        <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+        <template #tip>
+          <div class="el-upload__tip text-center">
+            <span>浠呭厑璁稿鍏� xls銆亁lsx 鏍煎紡鏂囦欢銆�</span>
+            <el-link
+              type="primary"
+              :underline="false"
+              style="font-size: 12px; vertical-align: baseline; margin-left: 5px"
+              @click="importTemplate"
+            >
+              涓嬭浇妯℃澘
+            </el-link>
+          </div>
         </template>
-      </PIMTable>
-    </div>
-    <Modal ref="modalRef" @success="getTableData"></Modal>
+      </el-upload>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitFileForm">纭畾</el-button>
+          <el-button @click="upload.open = false">鍙栨秷</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
 import { usePaginationApi } from "@/hooks/usePaginationApi";
-// import { Search } from "@element-plus/icons-vue";
 import { getLedgerPage, delLedger } from "@/api/equipmentManagement/ledger";
-import { onMounted, getCurrentInstance } from "vue";
+import {
+  getDeviceAreaTree,
+  getDeviceAreaDetail,
+  addDeviceArea,
+  updateDeviceArea,
+  deleteDeviceArea,
+} from "@/api/equipmentManagement/deviceArea";
+import { onMounted, getCurrentInstance, ref, reactive } from "vue";
 import Modal from "./Modal.vue";
 import { ElMessageBox, ElMessage } from "element-plus";
+import { UploadFilled } from "@element-plus/icons-vue";
+import { getToken } from "@/utils/auth";
+import dayjs from "dayjs";
+import QRCode from "qrcode";
 
 defineOptions({
   name: "璁惧鍙拌处",
 });
 
-// 琛ㄦ牸澶氶�夋閫変腑椤�
 const multipleList = ref([]);
 const { proxy } = getCurrentInstance();
 const modalRef = ref();
+const treeRef = ref();
+const areaFormRef = ref();
+const treeKeyword = ref("");
+const treeLoading = ref(false);
+const treeData = ref([]);
+const selectedAreaName = ref("");
+const areaDialogVisible = ref(false);
+const areaDialogTitle = ref("鏂板鍖哄煙");
+const areaDialogMode = ref("addRoot");
+const qrDialogVisible = ref(false);
+const qrCodeUrl = ref("");
+const qrRowData = ref(null);
+const uploadRef = ref(null);
+
+const treeProps = {
+  children: "children",
+  label: "areaName",
+};
+
+const upload = reactive({
+  open: false,
+  title: "",
+  isUploading: false,
+  headers: { Authorization: "Bearer " + getToken() },
+  url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import",
+});
+
+const areaForm = reactive({
+  id: undefined,
+  areaName: "",
+  parentId: undefined,
+  sort: 0,
+  remark: "",
+});
+
+const areaRules = {
+  areaName: [{ required: true, message: "璇疯緭鍏ュ尯鍩熷悕绉�", trigger: "blur" }],
+};
+
 const {
   filters,
   columns,
   dataList,
   pagination,
   getTableData,
-  resetFilters,
   onCurrentChange,
 } = usePaginationApi(
   getLedgerPage,
   {
-    searchText: undefined,
+    deviceName: undefined,
+    deviceModel: undefined,
+    supplierName: undefined,
+    entryDate: undefined,
+    entryDateStart: undefined,
+    entryDateEnd: undefined,
+    areaId: undefined,
+    areaName: undefined,
   },
   [
     {
+      label: "鎵�鍦ㄥ尯鍩�",
+      prop: "areaName",
+    },
+    {
       label: "璁惧鍚嶇О",
-      align: "center",
       prop: "deviceName",
     },
     {
       label: "瑙勬牸鍨嬪彿",
-      align: "center",
       prop: "deviceModel",
     },
     {
+      label: "璁惧鍝佺墝",
+      prop: "deviceBrand",
+    },
+    {
+      label: "璁惧绫诲瀷",
+      prop: "type",
+    },
+    {
       label: "渚涘簲鍟�",
-      align: "center",
       prop: "supplierName",
     },
     {
-      label: "鍗曚綅",
-      align: "center",
-      prop: "unit",
+      label: "瀛樻斁浣嶇疆",
+      prop: "storageLocation",
     },
     {
       label: "鏁伴噺",
-      align: "center",
       prop: "number",
     },
     {
-      label: "鍚◣鍗曚环",
-      align: "center",
-      prop: "taxIncludingPriceUnit",
-    },
-    {
-      label: "鍚◣鎬讳环",
-      align: "center",
-      prop: "taxIncludingPriceTotal",
-    },
-    {
-      label: "绋庣巼",
-      align: "center",
-      prop: "taxRate",
-    },
-    {
-      label: "涓嶅惈绋庢�讳环",
-      align: "center",
-      prop: "unTaxIncludingPriceTotal",
-    },
-    {
       label: "褰曞叆浜�",
-      align: "center",
       prop: "createUser",
     },
     {
       label: "褰曞叆鏃ユ湡",
-      align: "center",
       prop: "createTime",
+      formatData: (v) => {
+        if (!v) return "";
+        return v.includes(" ") ? v.split(" ")[0] : v;
+      },
     },
     {
-      fixed: "right",
+      dataType: "action",
       label: "鎿嶄綔",
-      dataType: "slot",
-      slot: "operation",
       align: "center",
-      width: "200px",
+      fixed: "right",
+      width: 150,
+      operation: [
+        {
+          name: "缂栬緫",
+          clickFun: (row) => {
+            edit(row.id);
+          },
+        },
+        {
+          name: "鐢熸垚浜岀淮鐮�",
+          clickFun: (row) => {
+            showQRCode(row);
+          },
+        },
+      ],
     },
   ]
 );
 
-// 澶氶�夊悗鍋氫粈涔�
+const loadTreeData = async () => {
+  treeLoading.value = true;
+  try {
+    const res = await getDeviceAreaTree();
+    treeData.value = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : [];
+  } finally {
+    treeLoading.value = false;
+  }
+};
+
+const resetAreaForm = () => {
+  areaForm.id = undefined;
+  areaForm.areaName = "";
+  areaForm.parentId = undefined;
+  areaForm.sort = 0;
+  areaForm.remark = "";
+};
+
+const filterTree = () => {
+  treeRef.value?.filter(treeKeyword.value);
+};
+
+const filterTreeNode = (value, data) => {
+  if (!value) {
+    return true;
+  }
+  return String(data.areaName || "").includes(value);
+};
+
+const handleTreeNodeClick = (data) => {
+  filters.areaId = data.id;
+  filters.areaName = data.areaName;
+  selectedAreaName.value = data.areaName || "";
+  getTableData();
+};
+
+const openAreaDialog = async (mode, row) => {
+  areaDialogMode.value = mode;
+  areaDialogTitle.value =
+    mode === "edit" ? "缂栬緫鍖哄煙" : mode === "addChild" ? "鏂板瀛愬尯鍩�" : "鏂板鍖哄煙";
+  resetAreaForm();
+  areaDialogVisible.value = true;
+  if (mode === "addChild") {
+    areaForm.parentId = row.id;
+    areaForm.sort = 0;
+    return;
+  }
+  if (mode === "edit" && row?.id) {
+    const res = await getDeviceAreaDetail(row.id);
+    const detail = res?.data || {};
+    areaForm.id = detail.id;
+    areaForm.areaName = detail.areaName || "";
+    areaForm.parentId = detail.parentId;
+    areaForm.sort = detail.sort ?? 0;
+    areaForm.remark = detail.remark || "";
+  }
+};
+
+const closeAreaDialog = () => {
+  areaDialogVisible.value = false;
+  areaFormRef.value?.resetFields();
+  resetAreaForm();
+};
+
+const submitAreaForm = () => {
+  areaFormRef.value?.validate(async (valid) => {
+    if (!valid) {
+      return;
+    }
+    const submitData = {
+      id: areaForm.id,
+      areaName: areaForm.areaName,
+      parentId: areaForm.parentId,
+      sort: areaForm.sort,
+      remark: areaForm.remark,
+    };
+    const request = areaDialogMode.value === "edit" ? updateDeviceArea : addDeviceArea;
+    const { code } = await request(submitData);
+    if (code === 200) {
+      ElMessage.success(areaDialogMode.value === "edit" ? "淇敼鎴愬姛" : "鏂板鎴愬姛");
+      closeAreaDialog();
+      await loadTreeData();
+    }
+  });
+};
+
+const handleDeleteArea = (row) => {
+  if (hasChildren(row)) {
+    ElMessage.warning("褰撳墠鍖哄煙瀛樺湪涓嬬骇鍖哄煙锛屼笉鑳藉垹闄�");
+    return;
+  }
+  ElMessageBox.confirm("姝ゆ搷浣滃皢鍒犻櫎璇ヨ澶囧尯鍩燂紝鏄惁缁х画锛�", "鎻愮ず", {
+    confirmButtonText: "纭畾",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  }).then(async () => {
+    const { code } = await deleteDeviceArea([row.id]);
+    if (code === 200) {
+      ElMessage.success("鍒犻櫎鎴愬姛");
+      if (filters.areaId === row.id) {
+        resetTreeSelection();
+      }
+      await loadTreeData();
+    }
+  });
+};
+
+const hasChildren = (row) => Array.isArray(row?.children) && row.children.length > 0;
+
+const resetTreeSelection = () => {
+  treeRef.value?.setCurrentKey(null);
+  selectedAreaName.value = "";
+  filters.areaId = undefined;
+  filters.areaName = undefined;
+  getTableData();
+};
+
 const handleSelectionChange = (selectionList) => {
   multipleList.value = selectionList;
 };
@@ -198,15 +501,19 @@
 const add = () => {
   modalRef.value.openModal();
 };
+
 const edit = (id) => {
   modalRef.value.loadForm(id);
 };
-const changePage = ({ page }) => {
+
+const changePage = ({ page, limit }) => {
   pagination.currentPage = page;
+  pagination.pageSize = limit;
   onCurrentChange(page);
 };
+
 const deleteRow = (id) => {
-  ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ枃浠�, 鏄惁缁х画?", "鎻愮ず", {
+  ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ暟鎹紝鏄惁缁х画锛�", "鎻愮ず", {
     confirmButtonText: "纭畾",
     cancelButtonText: "鍙栨秷",
     type: "warning",
@@ -222,32 +529,190 @@
   });
 };
 
+const changeDaterange = (value) => {
+  if (value) {
+    filters.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
+    filters.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
+  } else {
+    filters.entryDateStart = undefined;
+    filters.entryDateEnd = undefined;
+  }
+  getTableData();
+};
+
+const handleResetFilters = () => {
+  filters.deviceName = undefined;
+  filters.deviceModel = undefined;
+  filters.supplierName = undefined;
+  filters.entryDate = undefined;
+  filters.entryDateStart = undefined;
+  filters.entryDateEnd = undefined;
+  getTableData();
+};
+
 const handleOut = () => {
-  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+  ElMessageBox.confirm("褰撳墠鏌ヨ缁撴灉灏嗚瀵煎嚭锛屾槸鍚︾‘璁ゅ鍑猴紵", "瀵煎嚭", {
     confirmButtonText: "纭",
     cancelButtonText: "鍙栨秷",
     type: "warning",
   })
     .then(() => {
-      proxy.download(`/device/ledger/export`, {}, "璁惧鍙拌处妗f.xlsx");
+      proxy.download("/device/ledger/export", {}, "璁惧鍙拌处妗f.xlsx");
     })
     .catch(() => {
       proxy.$modal.msg("宸插彇娑�");
     });
 };
 
-onMounted(() => {
+const showQRCode = async (row) => {
+  const qrContent = proxy.javaApi + "/device-info?deviceId=" + row.id;
+  qrCodeUrl.value = await QRCode.toDataURL(qrContent);
+  qrRowData.value = row;
+  qrDialogVisible.value = true;
+};
+
+const downloadQRCode = () => {
+  const a = document.createElement("a");
+  a.href = qrCodeUrl.value;
+  a.download = `${qrRowData.value.deviceName || "浜岀淮鐮�"}.png`;
+  a.click();
+};
+
+const handleImport = () => {
+  upload.title = "璁惧鍙拌处瀵煎叆";
+  upload.open = true;
+};
+
+const importTemplate = () => {
+  proxy.download("/device/ledger/downloadTemplate", {}, `璁惧鍙拌处瀵煎叆妯℃澘_${new Date().getTime()}.xlsx`);
+};
+
+const handleFileUploadProgress = () => {
+  upload.isUploading = true;
+};
+
+const handleFileSuccess = (response, file) => {
+  upload.open = false;
+  upload.isUploading = false;
+  uploadRef.value?.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 = () => {
+  uploadRef.value?.submit();
+};
+
+onMounted(async () => {
+  await loadTreeData();
   getTableData();
 });
 </script>
 
 <style lang="scss" scoped>
-.table_list {
-  margin-top: unset;
+.ledger-view {
+  display: flex;
+  gap: 20px;
 }
+
+.left-panel {
+  width: 320px;
+  min-width: 320px;
+  padding: 16px;
+  background: #fff;
+  border-radius: 4px;
+}
+
+.right-panel {
+  flex: 1;
+  min-width: 0;
+  padding: 16px;
+  background: #fff;
+  border-radius: 4px;
+}
+
+.tree-toolbar {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.tree-actions {
+  display: flex;
+  justify-content: flex-end;
+  margin-bottom: 8px;
+}
+
+.ledger-tree {
+  height: calc(100vh - 230px);
+  overflow-y: auto;
+}
+
+.tree-node {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+}
+
+.tree-node-content {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+}
+
+.tree-node-icon {
+  color: #e6a23c;
+  margin-right: 8px;
+  font-size: 18px;
+}
+
+.tree-node-label {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.tree-node-actions {
+  flex-shrink: 0;
+}
+
+.table_list {
+  margin-top: 0;
+}
+
 .actions {
   display: flex;
   justify-content: space-between;
+  align-items: center;
   margin-bottom: 10px;
+  gap: 12px;
+}
+
+.actions-tip {
+  color: #606266;
+  font-size: 14px;
+}
+
+.qr-dialog {
+  text-align: center;
+}
+
+.qr-image {
+  width: 200px;
+  height: 200px;
+}
+
+.qr-footer {
+  margin: 10px 0;
 }
 </style>

--
Gitblit v1.9.3