From 0c116b0f5624786bd06990b86c467be25e2411fd Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期一, 26 一月 2026 17:51:38 +0800
Subject: [PATCH] feat: BOM导入模板下载,导入导出接口添加菜单权限

---
 src/views/procurementManagement/procurementLedger/index.vue | 1051 ++++++++++++++++++++++++++++-----------------------------
 1 files changed, 520 insertions(+), 531 deletions(-)

diff --git a/src/views/procurementManagement/procurementLedger/index.vue b/src/views/procurementManagement/procurementLedger/index.vue
index 9b86624..858b633 100644
--- a/src/views/procurementManagement/procurementLedger/index.vue
+++ b/src/views/procurementManagement/procurementLedger/index.vue
@@ -53,8 +53,7 @@
       <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
         <el-button type="primary"
                    @click="openForm('add')">鏂板鍙拌处</el-button>
-        <el-button type="success"
-                   @click="openScanAddDialog">鎵爜鏂板</el-button>
+        <el-button type="primary" plain @click="handleImport">瀵煎叆</el-button>
         <el-button @click="handleOut">瀵煎嚭</el-button>
         <el-button type="danger"
                    plain
@@ -69,8 +68,7 @@
                 show-summary
                 :summary-method="summarizeMainTable"
                 @expand-change="expandChange"
-                height="calc(100vh - 19em)"
-                :row-class-name="tableRowClassName">
+                height="calc(100vh - 21.5em)">
         <el-table-column align="center"
                          type="selection"
                          width="55" />
@@ -112,36 +110,28 @@
                          width="60" />
         <el-table-column label="閲囪喘鍚堝悓鍙�"
                          prop="purchaseContractNumber"
-                         width="200"
+                         width="160"
                          show-overflow-tooltip />
         <el-table-column label="閿�鍞悎鍚屽彿"
                          prop="salesContractNo"
+                          width="160"
                          show-overflow-tooltip />
         <el-table-column label="渚涘簲鍟嗗悕绉�"
                          prop="supplierName"
+                          width="160"
                          show-overflow-tooltip />
-        <el-table-column label="璁㈠崟鐘舵��"
-                         width="100"
-                         align="center">
-          <template #default="scope">
-            <el-tag v-if="scope.row.isInvalid"
-                    type="danger"
-                    size="small">澶辨晥</el-tag>
-            <el-tag v-else
-                    type="success"
-                    size="small">姝e父</el-tag>
-          </template>
-        </el-table-column>
         <el-table-column label="椤圭洰鍚嶇О"
                          prop="projectName"
-                         width="420"
+                         width="320"
                          show-overflow-tooltip />
         <el-table-column label="瀹℃壒鐘舵��"
                          prop="approvalStatus"
-                         width="200"
+                         width="100"
                          show-overflow-tooltip>
           <template #default="scope">
-            <el-tag size="small">
+            <el-tag 
+              :type="getApprovalStatusType(scope.row.approvalStatus)"
+              size="small">
               {{ approvalStatusText[scope.row.approvalStatus] || '鏈煡鐘舵��' }}
             </el-tag>
           </template>
@@ -169,17 +159,14 @@
                          show-overflow-tooltip />
         <el-table-column fixed="right"
                          label="鎿嶄綔"
-                         width="180"
+                         width="120"
                          align="center">
           <template #default="scope">
             <el-button link
                        type="primary"
                        size="small"
-                       @click="openForm('edit', scope.row)">缂栬緫</el-button>
-            <el-button link
-                       type="success"
-                       size="small"
-                       @click="showQRCode(scope.row)">鐢熸垚浜岀淮鐮�</el-button>
+                       @click="openForm('edit', scope.row)"
+                       :disabled="scope.row.approvalStatus !== 1 && scope.row.approvalStatus !== 4">缂栬緫</el-button>
             <el-button link
                        type="primary"
                        size="small"
@@ -194,10 +181,13 @@
                   :limit="page.size"
                   @pagination="paginationChange" />
     </div>
-    <el-dialog v-model="dialogFormVisible"
+    <FormDialog v-model="dialogFormVisible"
                :title="operationType === 'add' ? '鏂板閲囪喘鍙拌处椤甸潰' : '缂栬緫閲囪喘鍙拌处椤甸潰'"
-               width="70%"
-               @close="closeDia">
+               :width="'70%'"
+               :operation-type="operationType"
+               @close="closeDia"
+               @confirm="submitForm"
+               @cancel="closeDia">
       <el-form :model="form"
                label-width="140px"
                label-position="top"
@@ -275,24 +265,12 @@
         </el-row>
         <el-row :gutter="30">
           <el-col :span="12">
-            <el-form-item label="瀹℃壒浜猴細"
-                          prop="approverId">
-              <el-select v-model="form.approverId"
-                         placeholder="璇烽�夋嫨瀹℃壒浜�"
-                         clearable>
-                <el-option v-for="item in userList"
-                           :key="item.userId"
-                           :label="item.nickName"
-                           :value="item.userId" />
-              </el-select>
-            </el-form-item>
             <el-form-item label="褰曞叆浜猴細"
-                          prop="recorderId"
-                          v-show="false">
+                          prop="recorderId">
               <el-select v-model="form.recorderId"
                          placeholder="璇烽�夋嫨"
                          clearable
-                         disabled>
+                         filterable>
                 <el-option v-for="item in userList"
                            :key="item.userId"
                            :label="item.nickName"
@@ -313,6 +291,50 @@
             </el-form-item>
           </el-col>
         </el-row>
+        <el-row :gutter="30">
+          <el-col :span="24">
+            <el-form-item>
+              <template #label>
+                <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
+                  <span>瀹℃壒浜洪�夋嫨锛�</span>
+                  <el-button type="primary" size="small" @click="addApproverNode" icon="Plus">鏂板鑺傜偣</el-button>
+                </div>
+              </template>
+              <div class="approver-nodes-container">
+                <div
+                  v-for="(node, index) in approverNodes"
+                  :key="node.id"
+                  class="approver-node-item"
+                >
+                  <div class="approver-node-header">
+                    <span class="approver-node-label">瀹℃壒鑺傜偣 {{ index + 1 }}</span>
+                    <el-button
+                      v-if="approverNodes.length > 1"
+                      type="danger"
+                      size="small"
+                      text
+                      @click="removeApproverNode(index)"
+                      icon="Delete"
+                    >鍒犻櫎</el-button>
+                  </div>
+                  <el-select
+                    v-model="node.userId"
+                    placeholder="璇烽�夋嫨瀹℃壒浜�"
+                    filterable
+                    style="width: 100%;"
+                  >
+                    <el-option
+                      v-for="user in userList"
+                      :key="user.userId"
+                      :label="user.nickName"
+                      :value="user.userId"
+                    />
+                  </el-select>
+                </div>
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
         <el-row>
           <el-form-item label="浜у搧淇℃伅锛�"
                         prop="entryDate">
@@ -323,29 +345,42 @@
                        @click="deleteProduct">鍒犻櫎</el-button>
           </el-form-item>
           <div class="select-button-group"
-               style="width: 220px; margin: 20px 0;"
+               style="width: 500px; margin: 20px 0;"
                v-if="operationType === 'add'">
             <el-select filterable
                        allow-create
                        :reserve-keyword="true"
                        :default-first-option="false"
+                       clearable
                        v-model="templateName"
                        :input-value="filterInputValue"
                        @filter-change="onTemplateFilterChange"
                        @change="onTemplateChange"
-                       style="width: 180px; border-right: none; border-radius: 4px 0 0 4px;"
-                       placeholder="璇烽�夋嫨"
+                       @focus="getTemplateList"
+                       style="width: 500px;"
+                       placeholder="璇烽�夋嫨妯$増鎴栬�呰緭鍏ユ柊鐨勬ā鐗堝悕绉板悗閫夋嫨"
                        class="no-arrow-select">
               <el-option v-for="item in templateList"
-                         :key="item.value"
+                         :key="item.id || item.value"
                          :label="item.templateName"
-                         :value="item.templateName"></el-option>
+                         :value="item.templateName">
+                <div style="display: flex; justify-content: space-between; align-items: center;">
+                  <span>{{ item.templateName }}</span>
+                  <el-icon 
+                    v-if="item.id"
+                    class="delete-icon"
+                    @click.stop="handleDeleteTemplate(item)"
+                    style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;">
+                    <Delete />
+                  </el-icon>
+                </div>
+              </el-option>
             </el-select>
             <!-- 鎸夐挳锛氫笌 Select 楂樺害鍖归厤锛屽幓鎺夊乏渚ц竟妗嗭紝鏃犵紳琛旀帴 -->
             <el-button size="small"
-                       style="height: 32px; border-radius: 0 4px 4px 0; margin-left: -1px;"
+                       style="height: 32px;margin-left: 8px;"
                        @click="handleButtonClick"
-                       :disabled="!templateName || templateName.trim() === '' || isTemplateNameDuplicate">
+                       :disabled="!templateName || templateName.trim() === '' || (!currentTemplateId && isTemplateNameDuplicate)">
               淇濆瓨
             </el-button>
           </div>
@@ -450,18 +485,49 @@
           </el-col>
         </el-row>
       </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary"
-                     @click="submitForm">纭</el-button>
-          <el-button @click="closeDia">鍙栨秷</el-button>
+    </FormDialog>
+    <!-- 瀵煎叆寮圭獥 -->
+    <FormDialog
+      v-model="importUpload.open"
+      :title="importUpload.title"
+      :width="'600px'"
+      @close="importUpload.open = false"
+      @confirm="submitImportFile"
+      @cancel="importUpload.open = false"
+    >
+      <el-upload
+        ref="importUploadRef"
+        :limit="1"
+        accept=".xlsx,.xls"
+        :action="importUpload.url"
+        :headers="importUpload.headers"
+        :before-upload="importUpload.beforeUpload"
+        :on-success="importUpload.onSuccess"
+        :on-error="importUpload.onError"
+        :on-progress="importUpload.onProgress"
+        :on-change="importUpload.onChange"
+        :auto-upload="false"
+        drag
+      >
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">
+          灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em>
         </div>
-      </template>
-    </el-dialog>
-    <el-dialog v-model="productFormVisible"
+        <template #tip>
+          <div class="el-upload__tip">
+            浠呮敮鎸� xls/xlsx锛屽ぇ灏忎笉瓒呰繃 10MB銆�
+            <el-button link type="primary" @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
+          </div>
+        </template>
+      </el-upload>
+    </FormDialog>
+    <FormDialog v-model="productFormVisible"
                :title="productOperationType === 'add' ? '鏂板浜у搧' : '缂栬緫浜у搧'"
-               width="40%"
-               @close="closeProductDia">
+               :width="'40%'"
+               :operation-type="productOperationType"
+               @close="closeProductDia"
+               @confirm="submitProduct"
+               @cancel="closeProductDia">
       <el-form :model="productForm"
                label-width="140px"
                label-position="top"
@@ -617,228 +683,12 @@
           </el-col>
         </el-row>
       </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary"
-                     @click="submitProduct">纭</el-button>
-          <el-button @click="closeProductDia">鍙栨秷</el-button>
-        </div>
-      </template>
-    </el-dialog>
-    <!-- 浜岀淮鐮佹樉绀哄璇濇 -->
-    <el-dialog v-model="qrCodeDialogVisible"
-               title="閲囪喘鍚堝悓鍙蜂簩缁寸爜"
-               width="400px"
-               center>
-      <div style="text-align: center;">
-        <img :src="qrCodeUrl"
-             alt="浜岀淮鐮�"
-             style="width:200px;height:200px;" />
-        <div style="margin: 20px;">
-          <el-button type="primary"
-                     @click="downloadQRCode">涓嬭浇浜岀淮鐮佸浘鐗�</el-button>
-        </div>
-      </div>
-    </el-dialog>
-    <!-- 鎵爜鏂板瀵硅瘽妗� -->
-    <el-dialog v-model="scanAddDialogVisible"
-               title="鎵爜鏂板閲囪喘鍙拌处"
-               width="70%"
-               @close="closeScanAddDialog">
-      <el-form :model="scanAddForm"
-               label-width="140px"
-               label-position="top"
-               :rules="scanAddRules"
-               ref="scanAddFormRef">
-        <el-row :gutter="20">
-          <el-col :span="24">
-            <el-form-item label="鎵爜鍐呭锛�">
-              <el-input v-model="scanAddForm.scanContent"
-                        type="textarea"
-                        :rows="3"
-                        placeholder="璇锋壂鎻忎簩缁寸爜鎴栨墜鍔ㄨ緭鍏ラ噰璐悎鍚屼俊鎭�"
-                        @input="parseScanContent" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="閲囪喘鍚堝悓鍙凤細"
-                          prop="purchaseContractNumber">
-              <el-input v-model="scanAddForm.purchaseContractNumber"
-                        placeholder="璇疯緭鍏�"
-                        clearable />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="渚涘簲鍟嗗悕绉帮細"
-                          prop="supplierName">
-              <el-input v-model="scanAddForm.supplierName"
-                        placeholder="璇疯緭鍏�"
-                        clearable />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="椤圭洰鍚嶇О锛�"
-                          prop="projectName">
-              <el-input v-model="scanAddForm.projectName"
-                        placeholder="璇疯緭鍏�"
-                        clearable />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="鍚堝悓閲戦(鍏�)锛�"
-                          prop="contractAmount">
-              <el-input-number v-model="scanAddForm.contractAmount"
-                               :precision="2"
-                               :step="0.1"
-                               clearable
-                               style="width: 100%"
-                               placeholder="璇疯緭鍏�" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="浠樻鏂瑰紡锛�">
-              <el-input v-model="scanAddForm.paymentMethod"
-                        placeholder="璇疯緭鍏�"
-                        clearable />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="褰曞叆浜猴細">
-              <el-input v-model="scanAddForm.recorderName"
-                        disabled />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="24">
-            <el-form-item label="澶囨敞锛�">
-              <el-input v-model="scanAddForm.remark"
-                        type="textarea"
-                        :rows="2"
-                        placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�"
-                        clearable />
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary"
-                     @click="submitScanAdd">纭鏂板</el-button>
-          <el-button @click="closeScanAddDialog">鍙栨秷</el-button>
-        </div>
-      </template>
-    </el-dialog>
-    <!-- 鎵爜鐧昏瀵硅瘽妗� -->
-    <el-dialog v-model="scanDialogVisible"
-               title="鎵爜鐧昏"
-               width="60%"
-               @close="closeScanDialog">
-      <el-form :model="scanForm"
-               label-width="120px"
-               label-position="left"
-               :rules="scanRules"
-               ref="scanFormRef">
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="閲囪喘鍚堝悓鍙凤細">
-              <el-input v-model="scanForm.purchaseContractNumber"
-                        disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="渚涘簲鍟嗗悕绉帮細">
-              <el-input v-model="scanForm.supplierName"
-                        disabled />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="椤圭洰鍚嶇О锛�">
-              <el-input v-model="scanForm.projectName"
-                        disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="鎵爜鏃堕棿锛�">
-              <el-input v-model="scanForm.scanTime"
-                        disabled />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="鎵爜浜猴細">
-              <el-input v-model="scanForm.scannerName"
-                        disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="鎵爜鐘舵�侊細">
-              <el-tag :type="scanForm.scanStatus === '宸叉壂鐮�' ? 'success' : 'warning'">
-                {{ scanForm.scanStatus }}
-              </el-tag>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="24">
-            <el-form-item label="鎵爜澶囨敞锛�">
-              <el-input v-model="scanForm.scanRemark"
-                        type="textarea"
-                        :rows="3"
-                        placeholder="璇疯緭鍏ユ壂鐮佸娉ㄤ俊鎭�" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="24">
-            <el-form-item label="鎵爜璁板綍锛�">
-              <el-table :data="scanRecords"
-                        border
-                        style="width: 100%">
-                <el-table-column label="搴忓彿"
-                                 type="index"
-                                 width="60"
-                                 align="center" />
-                <el-table-column label="鎵爜鏃堕棿"
-                                 prop="scanTime"
-                                 width="180" />
-                <el-table-column label="鎵爜浜�"
-                                 prop="scannerName"
-                                 width="120" />
-                <el-table-column label="鎵爜鐘舵��"
-                                 prop="scanStatus"
-                                 width="100">
-                  <template #default="scope">
-                    <el-tag :type="scope.row.scanStatus === '宸叉壂鐮�' ? 'success' : 'warning'">
-                      {{ scope.row.scanStatus }}
-                    </el-tag>
-                  </template>
-                </el-table-column>
-                <el-table-column label="澶囨敞"
-                                 prop="scanRemark" />
-              </el-table>
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary"
-                     @click="submitScan">纭鎵爜</el-button>
-          <el-button @click="closeScanDialog">鍙栨秷</el-button>
-        </div>
-      </template>
-    </el-dialog>
-    <FileList ref="fileListRef" />
+    </FormDialog>
+    <FileListDialog 
+      ref="fileListRef" 
+      v-model="fileListDialogVisible"
+      title="闄勪欢鍒楄〃"
+    />
   </div>
 </template>
 
@@ -853,10 +703,11 @@
     getCurrentInstance,
     nextTick,
   } from "vue";
-  import { Search } from "@element-plus/icons-vue";
+  import { Search, Delete } from "@element-plus/icons-vue";
   import { ElMessageBox, ElMessage } from "element-plus";
   import { userListNoPage } from "@/api/system/user.js";
-  import FileList from "./fileList.vue";
+  import FormDialog from '@/components/Dialog/FormDialog.vue';
+  import FileListDialog from '@/components/Dialog/FileListDialog.vue';
   import {
     getSalesLedgerWithProducts,
     addOrUpdateSalesLedgerProduct,
@@ -867,6 +718,7 @@
   import {
     addOrEditPurchase,
     addPurchaseTemplate,
+    updatePurchaseTemplate,
     createPurchaseNo,
     delPurchase,
     getSalesNo,
@@ -875,9 +727,9 @@
     getPurchaseById,
     getOptions,
     getPurchaseTemplateList,
+    delPurchaseTemplate,
   } from "@/api/procurementManagement/procurementLedger.js";
   import useFormData from "@/hooks/useFormData.js";
-  import QRCode from "qrcode";
 
   const { proxy } = getCurrentInstance();
   const tableData = ref([]);
@@ -902,21 +754,41 @@
 
   const userStore = useUserStore();
 
-  // 浜岀淮鐮佺浉鍏冲彉閲�
-  const qrCodeDialogVisible = ref(false);
-  const qrCodeUrl = ref("");
+  // 瀹℃壒浜鸿妭鐐癸紙浠块攢鍞彴璐﹀彂璐у鎵逛汉锛�
+  const approverNodes = ref([{ id: 1, userId: null }]);
+  let nextApproverId = 2;
+  const addApproverNode = () => {
+    approverNodes.value.push({ id: nextApproverId++, userId: null });
+  };
+  const removeApproverNode = (index) => {
+    approverNodes.value.splice(index, 1);
+  };
 
   // 璁㈠崟瀹℃壒鐘舵�佹樉绀烘枃鏈�
   const approvalStatusText = {
-    0: "瀹℃壒涓�",
-    1: "瀹℃壒閫氳繃",
-    2: "瀹℃壒澶辫触",
+    1: "寰呭鏍�",
+    2: "瀹℃壒涓�",
+    3: "瀹℃壒閫氳繃",
+    4: "瀹℃壒澶辫触",
+  };
+
+  // 鑾峰彇瀹℃壒鐘舵�佹爣绛剧被鍨�
+  const getApprovalStatusType = (status) => {
+    const typeMap = {
+      1: "info",      // 寰呭鏍� - 鐏拌壊
+      2: "warning",   // 瀹℃壒涓� - 姗欒壊
+      3: "success",   // 瀹℃壒閫氳繃 - 缁胯壊
+      4: "danger",    // 瀹℃壒澶辫触 - 绾㈣壊
+    };
+    return typeMap[status] || "";
   };
 
   const templateName = ref("");
   const filterInputValue = ref("");
   const templateList = ref([]);
   const isTemplateNameDuplicate = ref(false); // 鏍囪妯℃澘鍚嶇О鏄惁閲嶅
+  // 褰撳墠閫変腑鐨勬ā鏉縄D锛堢敤浜庡尯鍒嗘柊澧炴ā鏉胯繕鏄洿鏂版ā鏉匡級
+  const currentTemplateId = ref(null);
 
   // 妫�鏌ユā鏉垮悕绉版槸鍚﹂噸澶�
   const checkTemplateNameDuplicate = name => {
@@ -970,22 +842,28 @@
     );
 
     if (matchedTemplate?.id) {
-      // 濡傛灉鎵惧埌妯℃澘锛屽姞杞芥ā鏉挎暟鎹�
-      form.value = {
-        ...form.value,
-        ...matchedTemplate,
-      };
-      productData.value = matchedTemplate.productData || [];
-      // 鐢熸垚鏂扮殑閲囪喘鍚堝悓鍙�
-      try {
-        const res = await createPurchaseNo();
-        if (res?.data) {
-          form.value.purchaseContractNumber = res.data;
-        }
-      } catch (error) {
-        console.error("鐢熸垚閲囪喘鍚堝悓鍙峰け璐�:", error);
+      // 璁板綍褰撳墠閫変腑鐨勬ā鏉縄D锛屽悗缁繚瀛樻椂杩涜鏇存柊鎿嶄綔
+      currentTemplateId.value = matchedTemplate.id;
+      // 閫変腑宸叉湁妯℃澘鏃讹紝涓嶅簲瑙嗕负鈥滄ā鏉垮悕绉伴噸澶嶅鑷翠笉鍙繚瀛樷��
+      isTemplateNameDuplicate.value = false;
+      // 濡傛灉鎵惧埌妯℃澘锛屽彧璧嬪�间緵搴斿晢銆侀」鐩悕绉般�佷粯娆炬柟寮忓拰浜у搧淇℃伅
+      if (matchedTemplate.supplierId) {
+        form.value.supplierId = matchedTemplate.supplierId;
       }
+      if (matchedTemplate.supplierName) {
+        form.value.supplierName = matchedTemplate.supplierName;
+      }
+      if (matchedTemplate.projectName) {
+        form.value.projectName = matchedTemplate.projectName;
+      }
+      if (matchedTemplate.paymentMethod) {
+        form.value.paymentMethod = matchedTemplate.paymentMethod;
+      }
+      // 妯℃澘鏁版嵁涓殑浜у搧瀛楁鏄� productList锛岄渶瑕佽浆鎹负 productData
+      productData.value = matchedTemplate.productList || matchedTemplate.productData || [];
     } else {
+      // 鏈尮閰嶅埌宸叉湁妯℃澘锛岃涓烘柊妯℃澘
+      currentTemplateId.value = null;
       // 濡傛灉娌℃湁鎵惧埌妯℃澘锛岄噸缃〃鍗曪紙淇濇寔褰撳墠琛ㄥ崟鐘舵�侊級
       const currentFormData = { ...form.value };
       const currentProductData = [...productData.value];
@@ -1108,6 +986,71 @@
     headers: { Authorization: "Bearer " + getToken() },
   });
 
+  // 瀵煎叆鐩稿叧
+  const importUploadRef = ref(null);
+  const importUpload = reactive({
+    title: "瀵煎叆閲囪喘鍙拌处",
+    open: false,
+    url: import.meta.env.VITE_APP_BASE_API + "/purchase/ledger/import",
+    headers: { Authorization: "Bearer " + getToken() },
+    isUploading: false,
+    beforeUpload: (file) => {
+      const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls");
+      const isLt10M = file.size / 1024 / 1024 < 10;
+      if (!isExcel) {
+        proxy.$modal.msgError("涓婁紶鏂囦欢鍙兘鏄� xlsx/xls 鏍煎紡!");
+        return false;
+      }
+      if (!isLt10M) {
+        proxy.$modal.msgError("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃 10MB!");
+        return false;
+      }
+      return true;
+    },
+    onChange: (file, fileList) => {
+      // noop
+    },
+    onProgress: (event, file, fileList) => {
+      // noop
+    },
+    onSuccess: (response, file, fileList) => {
+      importUpload.isUploading = false;
+      if (response?.code === 200) {
+        proxy.$modal.msgSuccess("瀵煎叆鎴愬姛");
+        importUpload.open = false;
+        if (importUploadRef.value) {
+          importUploadRef.value.clearFiles?.();
+        }
+        getList();
+      } else {
+        proxy.$modal.msgError(response?.msg || "瀵煎叆澶辫触");
+      }
+    },
+    onError: () => {
+      importUpload.isUploading = false;
+      proxy.$modal.msgError("瀵煎叆澶辫触锛岃閲嶈瘯");
+    },
+  });
+
+  const handleImport = () => {
+    importUpload.title = "瀵煎叆閲囪喘鍙拌处";
+    importUpload.open = true;
+    importUpload.isUploading = false;
+    if (importUploadRef.value) {
+      importUploadRef.value.clearFiles?.();
+    }
+  };
+
+  // 涓嬭浇瀵煎叆妯℃澘锛堝鍚庣璺緞涓嶅悓锛屽彲鍦ㄦ澶勮皟鏁达級
+  const downloadTemplate = () => {
+    proxy.download("/purchase/ledger/exportTemplate", {}, "閲囪喘鍙拌处瀵煎叆妯℃澘.xlsx");
+  };
+
+  const submitImportFile = () => {
+    importUpload.isUploading = true;
+    proxy.$refs["importUploadRef"]?.submit?.();
+  };
+
   const changeDaterange = value => {
     if (value) {
       searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
@@ -1140,14 +1083,17 @@
       return;
     }
 
-    // 妫�鏌ユā鏉垮悕绉版槸鍚﹂噸澶�
-    const isDuplicate = checkTemplateNameDuplicate(templateName.value);
-    if (isDuplicate) {
-      ElMessage({
-        message: "妯℃澘鍚嶇О宸插瓨鍦紝璇锋洿鎹㈡ā鏉垮悕绉�",
-        type: "warning",
-      });
-      return;
+    // 濡傛灉鏄�滄柊澧炴ā鏉库�濓紙娌℃湁閫変腑宸叉湁妯℃澘锛夛紝鎵嶉渶瑕佸仛閲嶅悕鏍¢獙锛�
+    // 鑻ユ槸閫変腑宸叉湁妯℃澘鍚庝慨鏀癸紝鍒欏厑璁镐娇鐢ㄥ師鍚嶇О锛堣涓烘洿鏂帮級
+    if (!currentTemplateId.value) {
+      const isDuplicate = checkTemplateNameDuplicate(templateName.value);
+      if (isDuplicate) {
+        ElMessage({
+          message: "妯℃澘鍚嶇О宸插瓨鍦紝璇锋洿鎹㈡ā鏉垮悕绉�",
+          type: "warning",
+        });
+        return;
+      }
     }
 
     // 妫�鏌ヤ緵搴斿晢鏄惁閫夋嫨
@@ -1160,29 +1106,47 @@
     }
 
     // 妫�鏌ユ槸鍚︽湁浜у搧鏁版嵁
-    // if (!productData.value || productData.value.length === 0) {
-    //   ElMessage({
-    //     message: '璇峰厛娣诲姞浜у搧淇℃伅',
-    //     type: 'warning',
-    //   });
-    //   return;
-    // }
+    if (!productData.value || productData.value.length === 0) {
+      ElMessage({
+        message: '璇峰厛娣诲姞浜у搧淇℃伅',
+        type: 'warning',
+      });
+      return;
+    }
 
     try {
+      // 鑾峰彇瀹℃壒浜篒D瀛楃涓�
+      const approveUserIds = approverNodes.value
+        .filter(node => node.userId)
+        .map(node => node.userId)
+        .join(",");
+      
       let params = {
         productData: proxy.HaveJson(productData.value),
         supplierId: form.value.supplierId,
         paymentMethod: form.value.paymentMethod,
         recorderId: form.value.recorderId,
-        approverId: form.value.approverId,
+        projectName: form.value.projectName,
+        approveUserIds: approveUserIds,
         templateName: templateName.value.trim(),
       };
-      console.log(params);
-      let res = await addPurchaseTemplate(params);
+      console.log("template params ===>", params, "currentTemplateId:", currentTemplateId.value);
+
+      // 濡傛灉 currentTemplateId 鏈夊�硷紝璇存槑褰撳墠鏄�滅紪杈戝凡鏈夋ā鏉库�� 鈫� 璋冪敤鏇存柊鎺ュ彛
+      // 鍚﹀垯涓衡�滄柊寤烘ā鏉库�� 鈫� 璋冪敤鏂板鎺ュ彛
+      let res;
+      if (currentTemplateId.value) {
+        res = await updatePurchaseTemplate({
+          id: currentTemplateId.value,
+          ...params,
+        });
+      } else {
+        res = await addPurchaseTemplate(params);
+      }
 
       if (res && res.code === 200) {
         ElMessage({
-          message: "妯℃澘淇濆瓨鎴愬姛",
+          message: currentTemplateId.value ? "妯℃澘鏇存柊鎴愬姛" : "妯℃澘淇濆瓨鎴愬姛",
           type: "success",
         });
         // 淇濆瓨鎴愬姛鍚庨噸鏂拌幏鍙栨ā鏉垮垪琛�
@@ -1191,6 +1155,8 @@
         templateName.value = "";
         filterInputValue.value = "";
         isTemplateNameDuplicate.value = false;
+        // 淇濆瓨/鏇存柊瀹屾垚鍚庯紝閲嶇疆褰撳墠妯℃澘ID
+        currentTemplateId.value = null;
       } else {
         ElMessage({
           message: res?.msg || "妯℃澘淇濆瓨澶辫触",
@@ -1238,7 +1204,6 @@
         // tableData.value = res.data.records;
         tableData.value = res.data.records.map(record => ({
           ...record,
-          isInvalid: record.isWhite === 1,
         }));
         // 鍒濆鍖栧瓙鏁版嵁鏁扮粍
         tableData.value.forEach(item => {
@@ -1297,6 +1262,14 @@
   };
   // 鎵撳紑寮规
   const openForm = async (type, row) => {
+    // 缂栬緫鏃舵鏌ュ鏍哥姸鎬侊紝鍙湁寰呭鏍�(1)鍜屽鎵瑰け璐�(4)鎵嶈兘缂栬緫
+    if (type === "edit" && row) {
+      if (row.approvalStatus !== 1 && row.approvalStatus !== 4) {
+        proxy.$modal.msgWarning("鍙湁寰呭鏍稿拰瀹℃壒澶辫触鐘舵�佺殑璁板綍鎵嶈兘缂栬緫");
+        return;
+      }
+    }
+    
     await getTemplateList();
     operationType.value = type;
     form.value = {};
@@ -1305,6 +1278,9 @@
     templateName.value = "";
     filterInputValue.value = "";
     isTemplateNameDuplicate.value = false;
+    // 閲嶇疆瀹℃壒浜鸿妭鐐癸紙榛樿涓�涓┖鑺傜偣锛�
+    approverNodes.value = [{ id: 1, userId: null }];
+    nextApproverId = 2;
     try {
       // 骞惰鍔犺浇鍩虹鏁版嵁
       const [userRes, salesRes, supplierRes] = await Promise.all([
@@ -1343,6 +1319,15 @@
           form.value = { ...purchaseRes };
           productData.value = purchaseRes.productData || [];
           fileList.value = purchaseRes.salesLedgerFiles || [];
+          // 濡傛灉缂栬緫鏃舵湁瀹℃壒浜猴紝瑙f瀽瀹℃壒浜篒D瀛楃涓插苟璁剧疆鍒拌妭鐐逛腑
+          if (purchaseRes.approveUserIds) {
+            const approverIds = purchaseRes.approveUserIds.split(",");
+            approverNodes.value = approverIds.map((id, index) => ({
+              id: index + 1,
+              userId: Number(id)
+            }));
+            nextApproverId = approverIds.length + 1;
+          }
         } catch (error) {
           console.error("鍔犺浇閲囪喘鍙拌处鏁版嵁澶辫触:", error);
           proxy.$modal.msgError("鍔犺浇鏁版嵁澶辫触");
@@ -1411,8 +1396,24 @@
   const submitForm = () => {
     proxy.$refs["formRef"].validate(valid => {
       if (valid) {
+        // 瀹℃壒浜哄繀濉牎楠岋紙鎵�鏈夎妭鐐归兘瑕侀�変汉锛�
+        const hasEmptyApprover = approverNodes.value.some(node => !node.userId);
+        if (hasEmptyApprover) {
+          proxy.$modal.msgError("璇蜂负鎵�鏈夊鎵硅妭鐐归�夋嫨瀹℃壒浜猴紒");
+          return;
+        }
+        const approveUserIds = approverNodes.value.map(node => node.userId).join(",");
+        
         if (productData.value.length > 0) {
-          form.value.productData = proxy.HaveJson(productData.value);
+          // 鏂板鏃讹紝闇�瑕佷粠姣忎釜浜у搧瀵硅薄涓垹闄� id 瀛楁
+          let processedProductData = productData.value;
+          if (operationType.value === "add") {
+            processedProductData = productData.value.map(product => {
+              const { id, ...rest } = product;
+              return rest;
+            });
+          }
+          form.value.productData = proxy.HaveJson(processedProductData);
         } else {
           proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
           return;
@@ -1423,13 +1424,20 @@
         }
         form.value.tempFileIds = tempFileIds;
         form.value.type = 2;
+        form.value.approveUserIds = approveUserIds;
 
         // 濡傛灉salesLedgerId涓虹┖锛屽垯涓嶄紶閫抯alesContractNo
         if (!form.value.salesLedgerId) {
           form.value.salesContractNo = "";
         }
 
-        addOrEditPurchase(form.value).then(res => {
+        // 鏂板鏃朵笉浼犻�抜d
+        const submitData = { ...form.value };
+        if (operationType.value === "add") {
+          delete submitData.id;
+        }
+
+        addOrEditPurchase(submitData).then(res => {
           proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
           closeDia();
           getList();
@@ -1440,35 +1448,102 @@
   // 鍏抽棴寮规
   const closeDia = () => {
     proxy.resetForm("formRef");
+    // 閲嶇疆瀹℃壒浜鸿妭鐐癸紙榛樿涓�涓┖鑺傜偣锛�
+    approverNodes.value = [{ id: 1, userId: null }];
+    nextApproverId = 2;
     dialogFormVisible.value = false;
   };
   // 鎵撳紑浜у搧寮规
-  const openProductForm = (type, row, index) => {
+  const openProductForm = async (type, row, index) => {
     productOperationType.value = type;
     productOperationIndex.value = index;
     productForm.value = {};
     proxy.resetForm("productFormRef");
-    if (type === "edit") {
-      productForm.value = { ...row };
-    }
     productFormVisible.value = true;
-    getProductOptions();
+    
+    // 鍏堣幏鍙栦骇鍝侀�夐」锛岀‘淇濇暟鎹姞杞藉畬鎴�
+    await getProductOptions();
+    
+    // 绛夊緟 DOM 鏇存柊
+    await nextTick();
+    
+    if (type === "edit") {
+      // 澶嶅埗琛屾暟鎹�
+      productForm.value = { ...row };
+      
+      // 濡傛灉鏄粠妯℃澘鍔犺浇鐨勬暟鎹紝鍙兘娌℃湁 productId 鍜� productModelId
+      // 闇�瑕佹牴鎹� productCategory 鍜� specificationModel 鏉ユ煡鎵惧搴旂殑 ID
+      if (!productForm.value.productId && productForm.value.productCategory) {
+        // 鏍规嵁 productCategory 鏌ユ壘 productId
+        const findProductIdByCategory = (nodes, categoryName) => {
+          for (let i = 0; i < nodes.length; i++) {
+            if (nodes[i].label === categoryName) {
+              return nodes[i].value;
+            }
+            if (nodes[i].children && nodes[i].children.length > 0) {
+              const found = findProductIdByCategory(nodes[i].children, categoryName);
+              if (found) return found;
+            }
+          }
+          return null;
+        };
+        
+        const productId = findProductIdByCategory(productOptions.value, productForm.value.productCategory);
+        if (productId) {
+          productForm.value.productId = productId;
+          // 鑾峰彇鍨嬪彿鍒楄〃骞剁瓑寰呭畬鎴�
+          const modelRes = await modelList({ id: productId });
+          modelOptions.value = modelRes;
+          
+          // 绛夊緟 DOM 鏇存柊
+          await nextTick();
+          
+          // 鏍规嵁 specificationModel 鏌ユ壘 productModelId
+          if (productForm.value.specificationModel && modelOptions.value.length > 0) {
+            const modelItem = modelOptions.value.find(
+              item => item.model === productForm.value.specificationModel
+            );
+            if (modelItem) {
+              productForm.value.productModelId = modelItem.id;
+              // 璁剧疆瑙勬牸鍨嬪彿鍜屽崟浣�
+              getProductModel(modelItem.id);
+            }
+          }
+        }
+      } else if (productForm.value.productId) {
+        // 濡傛灉鏈� productId锛屾甯稿姞杞藉瀷鍙峰垪琛�
+        await getModels(productForm.value.productId);
+        
+        // 绛夊緟 DOM 鏇存柊
+        await nextTick();
+        
+        if (productForm.value.productModelId) {
+          getProductModel(productForm.value.productModelId);
+        }
+      }
+      
+      // 鏈�鍚庡啀绛夊緟涓�娆� DOM 鏇存柊锛岀‘淇濇墍鏈夋暟鎹兘宸茶缃�
+      await nextTick();
+    }
   };
   const getProductOptions = () => {
-    productTreeList().then(res => {
+    return productTreeList().then(res => {
       productOptions.value = convertIdToValue(res);
+      return res;
     });
   };
   const getModels = value => {
     if (value) {
       productForm.value.productCategory =
         findNodeById(productOptions.value, value) || "";
-      modelList({ id: value }).then(res => {
+      return modelList({ id: value }).then(res => {
         modelOptions.value = res;
+        return res;
       });
     } else {
       productForm.value.productCategory = "";
       modelOptions.value = [];
+      return Promise.resolve([]);
     }
   };
   const getProductModel = value => {
@@ -1773,205 +1848,11 @@
   };
 
   const fileListRef = ref(null);
+  const fileListDialogVisible = ref(false);
   const downLoadFile = row => {
-    fileListRef.value.open(row.salesLedgerFiles);
-  };
-
-  // 鏄剧ず浜岀淮鐮�
-  const showQRCode = async row => {
-    try {
-      // 鏋勫缓浜岀淮鐮佸唴瀹癸紝鍙寘鍚噰璐悎鍚屽彿锛堢函鏂囨湰锛�
-      const qrContent = row.purchaseContractNumber || "";
-      // 妫�鏌ュ唴瀹规槸鍚︿负绌�
-      if (!qrContent || qrContent.trim() === "") {
-        proxy.$modal.msgWarning("璇ヨ娌℃湁閲囪喘鍚堝悓鍙凤紝鏃犳硶鐢熸垚浜岀淮鐮�");
-        return;
-      }
-      qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
-        width: 200,
-        margin: 2,
-        color: {
-          dark: "#000000",
-          light: "#FFFFFF",
-        },
-      });
-      qrCodeDialogVisible.value = true;
-    } catch (error) {
-      console.error("鐢熸垚浜岀淮鐮佸け璐�:", error);
-      proxy.$modal.msgError("鐢熸垚浜岀淮鐮佸け璐ワ細" + error.message);
+    if (fileListRef.value) {
+      fileListRef.value.open(row.salesLedgerFiles);
     }
-  };
-
-  // 涓嬭浇浜岀淮鐮�
-  const downloadQRCode = () => {
-    if (!qrCodeUrl.value) {
-      proxy.$modal.msgWarning("浜岀淮鐮佹湭鐢熸垚");
-      return;
-    }
-
-    const a = document.createElement("a");
-    a.href = qrCodeUrl.value;
-    a.download = `閲囪喘鍚堝悓鍙蜂簩缁寸爜_${new Date().getTime()}.png`;
-    document.body.appendChild(a);
-    a.click();
-    document.body.removeChild(a);
-    proxy.$modal.msgSuccess("涓嬭浇鎴愬姛");
-  };
-
-  // 鎵爜鏂板瀵硅瘽妗嗙浉鍏冲彉閲�
-  const scanAddDialogVisible = ref(false);
-  const scanAddForm = reactive({
-    scanContent: "",
-    purchaseContractNumber: "",
-    supplierName: "",
-    projectName: "",
-    contractAmount: "",
-    paymentMethod: "",
-    recorderName: "",
-    scanRemark: "",
-  });
-  const scanAddRules = {
-    purchaseContractNumber: [
-      { required: true, message: "璇疯緭鍏ラ噰璐悎鍚屽彿", trigger: "blur" },
-    ],
-    supplierName: [
-      { required: true, message: "璇疯緭鍏ヤ緵搴斿晢鍚嶇О", trigger: "blur" },
-    ],
-    projectName: [{ required: true, message: "璇疯緭鍏ラ」鐩悕绉�", trigger: "blur" }],
-  };
-
-  // 鎵爜鐧昏瀵硅瘽妗嗙浉鍏冲彉閲�
-  const scanDialogVisible = ref(false);
-  const scanForm = reactive({
-    purchaseContractNumber: "",
-    supplierName: "",
-    projectName: "",
-    scanTime: "",
-    scannerName: "",
-    scanStatus: "鏈壂鐮�",
-    scanRemark: "",
-  });
-  const scanRules = {
-    scanRemark: [{ required: true, message: "璇疯緭鍏ユ壂鐮佸娉�", trigger: "blur" }],
-  };
-  const scanRecords = ref([]);
-
-  // 鎵撳紑鎵爜鏂板瀵硅瘽妗�
-  const openScanAddDialog = () => {
-    scanAddForm.scanContent = "";
-    scanAddForm.purchaseContractNumber = "";
-    scanAddForm.supplierName = "";
-    scanAddForm.projectName = "";
-    scanAddForm.contractAmount = "";
-    scanAddForm.paymentMethod = "";
-    scanAddForm.recorderName = userStore.nickName;
-    scanAddForm.scanRemark = "";
-    scanAddDialogVisible.value = true;
-  };
-
-  // 瑙f瀽鎵爜鍐呭锛堟ā鎷熻В鏋愪簩缁寸爜鏁版嵁锛�
-  const parseScanContent = content => {
-    if (!content) return;
-
-    // 妯℃嫙瑙f瀽浜岀淮鐮佸唴瀹癸紝杩欓噷鍙互鏍规嵁瀹為檯闇�姹傝皟鏁磋В鏋愰�昏緫
-    // 鍋囪鎵爜鍐呭鏍煎紡涓猴細鍚堝悓鍙穦渚涘簲鍟唡閲戦|浠樻鏂瑰紡
-    const parts = content.split("|");
-    if (parts.length >= 2) {
-      scanAddForm.purchaseContractNumber = parts[0] || "";
-      scanAddForm.supplierName = parts[1] || "";
-      scanAddForm.contractAmount = parts[2] || "";
-      scanAddForm.paymentMethod = parts[3] || "";
-      scanAddForm.projectName = parts[4] || "";
-      // scanAddForm.contractAmount = parts[3] || "";
-      // scanAddForm.paymentMethod = parts[4] || "";
-    }
-  };
-
-  // 鍏抽棴鎵爜鏂板瀵硅瘽妗�
-  const closeScanAddDialog = () => {
-    scanAddDialogVisible.value = false;
-    proxy.resetForm("scanAddFormRef");
-  };
-
-  // 鎻愪氦鎵爜鏂板
-  const submitScanAdd = () => {
-    proxy.$refs["scanAddFormRef"].validate(valid => {
-      if (valid) {
-        // 鏋勫缓鏂板鏁版嵁
-        const newData = {
-          purchaseContractNumber: scanAddForm.purchaseContractNumber,
-          supplierName: scanAddForm.supplierName,
-          projectName: scanAddForm.projectName,
-          contractAmount: scanAddForm.contractAmount,
-          paymentMethod: scanAddForm.paymentMethod,
-          recorderName: scanAddForm.recorderName,
-          entryDate: getCurrentDate(),
-          remark: scanAddForm.scanRemark,
-          type: 2,
-        };
-
-        // 妯℃嫙鏂板鎴愬姛
-        proxy.$modal.msgSuccess("鎵爜鏂板鎴愬姛锛�");
-        closeScanAddDialog();
-
-        // 鍙互閫夋嫨鏄惁鍒锋柊鍒楄〃
-        // getList();
-      }
-    });
-  };
-
-  // 鎵撳紑鎵爜鐧昏瀵硅瘽妗�
-  const openScanDialog = row => {
-    scanForm.purchaseContractNumber = row.purchaseContractNumber;
-    scanForm.supplierName = row.supplierName;
-    scanForm.projectName = row.projectName;
-    scanForm.scanTime = getCurrentDateTime();
-    scanForm.scannerName = userStore.nickName;
-    scanForm.scanStatus = "鏈壂鐮�";
-    scanForm.scanRemark = "";
-    scanRecords.value = [];
-    scanDialogVisible.value = true;
-  };
-
-  // 鍏抽棴鎵爜鐧昏瀵硅瘽妗�
-  const closeScanDialog = () => {
-    scanDialogVisible.value = false;
-    proxy.resetForm("scanFormRef");
-  };
-
-  // 鎻愪氦鎵爜鐧昏
-  const submitScan = () => {
-    proxy.$refs["scanFormRef"].validate(valid => {
-      if (valid) {
-        // 娣诲姞鎵爜璁板綍
-        scanRecords.value.push({
-          ...scanForm,
-          id: Date.now(), // 妯℃嫙ID
-          scanTime: getCurrentDateTime(),
-        });
-        scanForm.scanStatus = "宸叉壂鐮�";
-        scanForm.scanRemark = scanForm.scanRemark || "鏃�";
-        proxy.$modal.msgSuccess("鎵爜鐧昏鎴愬姛锛�");
-        closeScanDialog();
-      }
-    });
-  };
-
-  // 鑾峰彇褰撳墠鏃ユ湡鏃堕棿
-  function getCurrentDateTime() {
-    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 hours = String(now.getHours()).padStart(2, "0");
-    const minutes = String(now.getMinutes()).padStart(2, "0");
-    const seconds = String(now.getSeconds()).padStart(2, "0");
-    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
-  }
-
-  // 娣诲姞琛岀被鍚嶆柟娉�
-  const tableRowClassName = ({ row }) => {
-    return row.isInvalid ? "invalid-row" : "";
   };
 
   // 鑾峰彇妯℃澘淇℃伅
@@ -1979,6 +1860,54 @@
     let res = await getPurchaseTemplateList();
     if (res && res.code === 200 && Array.isArray(res.data)) {
       templateList.value = res.data;
+    }
+  };
+
+  // 鍒犻櫎妯℃澘
+  const handleDeleteTemplate = async (item) => {
+    if (!item.id) {
+      proxy.$modal.msgWarning("鏃犳硶鍒犻櫎璇ユā鏉�");
+      return;
+    }
+    
+    try {
+      await ElMessageBox.confirm(
+        `纭畾瑕佸垹闄ゆā鏉�"${item.templateName}"鍚楋紵`,
+        "鍒犻櫎纭",
+        {
+          confirmButtonText: "纭畾",
+          cancelButtonText: "鍙栨秷",
+          type: "warning",
+        }
+      );
+      
+      const res = await delPurchaseTemplate([item.id]);
+      if (res && res.code === 200) {
+        ElMessage({
+          message: "鍒犻櫎鎴愬姛",
+          type: "success",
+        });
+        // 濡傛灉鍒犻櫎鐨勬槸褰撳墠閫変腑鐨勬ā鏉匡紝娓呯┖閫夋嫨
+        if (templateName.value === item.templateName) {
+          templateName.value = "";
+          filterInputValue.value = "";
+        }
+        // 閲嶆柊鑾峰彇妯℃澘鍒楄〃
+        await getTemplateList();
+      } else {
+        ElMessage({
+          message: res?.msg || "鍒犻櫎澶辫触",
+          type: "error",
+        });
+      }
+    } catch (error) {
+      if (error !== "cancel") {
+        console.error("鍒犻櫎妯℃澘澶辫触:", error);
+        ElMessage({
+          message: "鍒犻櫎澶辫触锛岃绋嶅悗閲嶈瘯",
+          type: "error",
+        });
+      }
     }
   };
 
@@ -2004,4 +1933,64 @@
     display: flex;
     align-items: center;
   }
+  
+  // 瀹℃壒浜鸿妭鐐瑰鍣ㄦ牱寮�
+  .approver-nodes-container {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 16px;
+    padding: 16px;
+    background-color: #f8f9fa;
+    border-radius: 4px;
+    border: 1px solid #e4e7ed;
+  }
+  
+  .approver-node-item {
+    flex: 0 0 calc(33.333% - 12px);
+    min-width: 200px;
+    padding: 12px;
+    background-color: #fff;
+    border-radius: 4px;
+    border: 1px solid #dcdfe6;
+    transition: all 0.3s;
+    
+    &:hover {
+      border-color: #409eff;
+      box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
+    }
+  }
+  
+  .approver-node-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 8px;
+  }
+  
+  .approver-node-label {
+    font-size: 13px;
+    font-weight: 500;
+    color: #606266;
+  }
+  
+  @media (max-width: 1200px) {
+    .approver-node-item {
+      flex: 0 0 calc(50% - 8px);
+    }
+  }
+  
+  @media (max-width: 768px) {
+    .approver-node-item {
+      flex: 0 0 100%;
+    }
+  }
+  
+  // 鍒犻櫎鍥炬爣鏍峰紡
+  .delete-icon {
+    transition: all 0.3s;
+    &:hover {
+      color: #f56c6c !important;
+      transform: scale(1.2);
+    }
+  }
 </style>

--
Gitblit v1.9.3