From 0f075e80d7c9fbe731419891ee55948be6f8fb65 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 13 三月 2026 16:47:20 +0800
Subject: [PATCH] 军泰伟业 1.产品数据添加导入功能

---
 src/views/basicData/product/index.vue |  208 ++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 186 insertions(+), 22 deletions(-)

diff --git a/src/views/basicData/product/index.vue b/src/views/basicData/product/index.vue
index b88d678..cc1e1b5 100644
--- a/src/views/basicData/product/index.vue
+++ b/src/views/basicData/product/index.vue
@@ -1,7 +1,7 @@
 <template>
   <div class="app-container product-view">
     <div class="left">
-      <div>
+      <div class="left-header">
         <el-input
           v-model="search"
           style="width: 210px"
@@ -11,12 +11,17 @@
           clearable
           prefix-icon="Search"
         />
-        <el-button
-          type="primary"
-          @click="openProDia('addOne')"
-          style="margin-left: 10px"
-          >鏂板浜у搧澶х被</el-button
-        >
+        <div class="button-group">
+          <el-button
+            type="primary"
+            @click="openProDia('addOne')"
+          >鏂板浜у搧澶х被</el-button>
+          <el-button
+            type="success"
+            @click="handleImport"
+            icon="Upload"
+          >瀵煎叆</el-button>
+        </div>
       </div>
       <div ref="containerRef">
         <el-tree
@@ -25,18 +30,13 @@
           :data="list"
           @node-click="handleNodeClick"
           :expand-on-click-node="false"
-          default-expand-all
           :default-expanded-keys="expandedKeys"
-          :draggable="true"
           :filter-node-method="filterNode"
           :props="{ children: 'children', label: 'label' }"
           highlight-current
           node-key="id"
-          style="
-            height: calc(100vh - 190px);
-            overflow-y: scroll;
-            scrollbar-width: none;
-          "
+          class="product-tree-scroll"
+          style="height: calc(100vh - 190px); overflow-y: auto"
         >
           <template #default="{ node, data }">
             <div class="custom-tree-node">
@@ -45,7 +45,7 @@
                   <component :is="data.children && data.children.length > 0
                   ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
                 </el-icon>
-                {{ data.label }}
+                <span class="tree-node-label">{{ data.label }}</span>
               </span>
               <div>
                 <el-button
@@ -113,6 +113,8 @@
               <el-input
                 v-model="form.productName"
                 placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+                maxlength="20"
+                show-word-limit
                 clearable
                 @keydown.enter.prevent
               />
@@ -127,8 +129,7 @@
         </div>
       </template>
     </el-dialog>
-    <el-dialog
-      v-model="modelDia"
+    <el-dialog v-model="modelDia"
       title="瑙勬牸鍨嬪彿"
       width="400px"
       @close="closeModelDia"
@@ -155,6 +156,18 @@
         </el-row>
         <el-row>
           <el-col :span="24">
+            <el-form-item label="鍥剧焊缂栧彿锛�" prop="unit">
+              <el-input
+                  v-model="modelForm.drawingNumber"
+                  placeholder="璇疯緭鍏ュ浘绾哥紪鍙�"
+                  clearable
+                  @keydown.enter.prevent
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
             <el-form-item label="鍗曚綅锛�" prop="unit">
               <el-input
                 v-model="modelForm.unit"
@@ -173,12 +186,47 @@
         </div>
       </template>
     </el-dialog>
+    <el-dialog v-model="importDia" title="浜у搧瀵煎叆" width="600px">
+      <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 #tip>
+          <div class="el-upload__tip">
+            浠呮敮鎸� xls/xlsx锛屽ぇ灏忎笉瓒呰繃 10MB銆�
+            <el-button link type="primary" @click="importTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
+          </div>
+        </template>
+      </el-upload>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitImport">纭瀵煎叆</el-button>
+          <el-button @click="importDia = false">鍙栨秷</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
 import { ref } from "vue";
 import { ElMessageBox } from "element-plus";
+import { getToken } from "@/utils/auth.js";
+import { FileUpload } from "@/components/Upload";
 import {
   addOrEditProduct,
   addOrEditProductModel,
@@ -186,15 +234,18 @@
   delProductModel,
   modelListPage,
   productTreeList,
+  downloadTemplate,
 } from "@/api/basicData/product.js";
 import ImportExcel from "./ImportExcel/index.vue";
 
 const { proxy } = getCurrentInstance();
 const tree = ref(null);
 const containerRef = ref(null);
+const importUploadRef = ref(null);
 
 const productDia = ref(false);
 const modelDia = ref(false);
+const importDia = ref(false);
 const modelOperationType = ref("");
 const search = ref("");
 const currentId = ref("");
@@ -207,6 +258,10 @@
   {
     label: "瑙勬牸鍨嬪彿",
     prop: "model",
+  },
+  {
+    label: "鍥剧焊缂栧彿",
+    prop: "drawingNumber",
   },
   {
     label: "鍗曚綅",
@@ -241,18 +296,70 @@
     productName: "",
   },
   rules: {
-    productName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+    productName: [
+      { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+      { max: 20, message: "浜у搧鍚嶇О涓嶈兘瓒呰繃20涓瓧绗�", trigger: "blur" },
+    ],
   },
   modelForm: {
     model: "",
     unit: "",
+    drawingNumber: "",
   },
   modelRules: {
     model: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
     unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+    drawingNumber: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
   },
 });
 const { form, rules, modelForm, modelRules } = toRefs(data);
+
+const importUpload = reactive({
+  title: "浜у搧瀵煎叆",
+  open: false,
+  url: import.meta.env.VITE_APP_BASE_API + "/basic/product/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) => {
+    console.log('鏂囦欢鐘舵�佹敼鍙�', file, fileList);
+  },
+  onProgress: (event, file, fileList) => {
+    console.log('涓婁紶涓�...', event.percent);
+  },
+  onSuccess: (response, file, fileList) => {
+    console.log('涓婁紶鎴愬姛', response, file, fileList);
+    importUpload.isUploading = false;
+    if (response.code === 200) {
+      proxy.$modal.msgSuccess("瀵煎叆鎴愬姛");
+      importDia.value = false;
+      if (importUploadRef.value) {
+        importUploadRef.value.clearFiles();
+      }
+      getProductTreeList();
+    } else {
+      proxy.$modal.msgError(response.msg || "瀵煎叆澶辫触");
+    }
+  },
+  onError: (error, file, fileList) => {
+    console.log('涓婁紶澶辫触', error, file, fileList);
+    importUpload.isUploading = false;
+    proxy.$modal.msgError("瀵煎叆澶辫触");
+  }
+});
+
 // 鏌ヨ浜у搧鏍�
 const getProductTreeList = () => {
   treeLoad.value = true;
@@ -285,7 +392,6 @@
 const openModelDia = (type, data) => {
   modelOperationType.value = type;
   modelDia.value = true;
-  modelForm.value.model = "";
   modelForm.value.model = "";
   modelForm.value.id = "";
   if (type === "edit") {
@@ -461,6 +567,22 @@
   // 娌″尮閰嶅埌杩斿洖false
   return false;
 };
+
+const handleImport = () => {
+  importDia.value = true;
+  if (importUploadRef.value) {
+    importUploadRef.value.clearFiles();
+  }
+};
+
+const submitImport = () => {
+  importUploadRef.value.submit();
+};
+
+const importTemplate = () => {
+  proxy.download("/basic/product/downloadTemplate", {}, "浜у搧瀵煎叆妯℃澘.xlsx");
+};
+
 getProductTreeList();
 </script>
 
@@ -469,18 +591,31 @@
   display: flex;
 }
 .left {
-  width: 380px;
+  width: 450px;
+  min-width: 450px;
   padding: 16px;
   background: #ffffff;
 }
+.left-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16px;
+}
+.button-group {
+  display: flex;
+  gap: 10px;
+}
 .right {
-  width: calc(100% - 380px);
+  flex: 1;
+  min-width: 0;
   padding: 16px;
   margin-left: 20px;
   background: #ffffff;
 }
 .custom-tree-node {
   flex: 1;
+  min-width: 0;
   display: flex;
   align-items: center;
   justify-content: space-between;
@@ -488,13 +623,42 @@
   padding-right: 8px;
 }
 .tree-node-content {
+  flex: 1;
+  min-width: 0;
   display: flex;
-  align-items: center; /* 鍨傜洿灞呬腑 */
+  align-items: center;
   height: 100%;
+  overflow: hidden;
+}
+.tree-node-content .orange-icon {
+  flex-shrink: 0;
+}
+.tree-node-label {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 .orange-icon {
   color: orange;
   font-size: 18px;
   margin-right: 8px; /* 鍥炬爣涓庢枃瀛椾箣闂村姞鐐归棿璺� */
 }
+.product-tree-scroll {
+  scrollbar-width: thin;
+  scrollbar-color: #c0c4cc #f5f7fa;
+}
+.product-tree-scroll::-webkit-scrollbar {
+  width: 8px;
+}
+.product-tree-scroll::-webkit-scrollbar-track {
+  background: #f5f7fa;
+  border-radius: 4px;
+}
+.product-tree-scroll::-webkit-scrollbar-thumb {
+  background: #c0c4cc;
+  border-radius: 4px;
+}
+.product-tree-scroll::-webkit-scrollbar-thumb:hover {
+  background: #909399;
+}
 </style>

--
Gitblit v1.9.3