From 915aa93d7a0cc8e5cc3eacfe05a81025780fccf5 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期五, 23 一月 2026 16:37:13 +0800
Subject: [PATCH] fix: 完成工单上传附件功能

---
 src/views/productionManagement/productStructure/Detail/index.vue |  568 +++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 392 insertions(+), 176 deletions(-)

diff --git a/src/views/productionManagement/productStructure/Detail/index.vue b/src/views/productionManagement/productStructure/Detail/index.vue
index 20a472b..3a76e48 100644
--- a/src/views/productionManagement/productStructure/Detail/index.vue
+++ b/src/views/productionManagement/productStructure/Detail/index.vue
@@ -2,10 +2,6 @@
   <div class="app-container">
     <PageHeader content="浜у搧缁撴瀯璇︽儏">
       <template #right-button>
-        <el-button v-if="dataValue.isEdit && !isOrderPage"
-                   type="primary"
-                   @click="addItem">娣诲姞
-        </el-button>
         <el-button v-if="!dataValue.isEdit && !isOrderPage"
                    type="primary"
                    @click="dataValue.isEdit = true">缂栬緫
@@ -22,34 +18,34 @@
         </el-button>
       </template>
     </PageHeader>
-    <el-table
-        :data="tableData"
-        border
-        :preserve-expanded-content="false"
-        :default-expand-all="true"
-        style="width: 100%"
-    >
+    <el-table :data="tableData"
+              border
+              :preserve-expanded-content="false"
+              :default-expand-all="true"
+              style="width: 100%">
       <el-table-column type="expand">
         <template #default="props">
           <el-form ref="form"
                    :model="dataValue">
             <el-table :data="dataValue.dataList"
+                      row-key="tempId"
+                      default-expand-all
+                      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
                       style="width: 100%">
               <el-table-column prop="productName"
-                               label="浜у搧"/>
+                               label="浜у搧" />
               <el-table-column prop="model"
                                label="瑙勬牸">
                 <template #default="{ row, $index }">
                   <el-form-item v-if="dataValue.isEdit"
-                                :prop="`dataList.${$index}.model`"
                                 :rules="[{ required: true, message: '璇烽�夋嫨瑙勬牸', trigger: ['blur','change'] }]"
                                 style="margin: 0">
                     <el-select v-model="row.model"
                                placeholder="璇烽�夋嫨瑙勬牸"
                                clearable
-                               :disabled="!dataValue.isEdit"
+                               :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                                style="width: 100%"
-                               @visible-change="(v) => { if (v) openDialog($index) }">
+                               @visible-change="(v) => { if (v) openDialog(row.tempId) }">
                       <el-option v-if="row.model"
                                  :label="row.model"
                                  :value="row.model" />
@@ -57,18 +53,18 @@
                   </el-form-item>
                 </template>
               </el-table-column>
-              <el-table-column prop="processId"
+              <el-table-column prop="processName"
                                label="娑堣�楀伐搴�">
                 <template #default="{ row, $index }">
-                  <el-form-item :prop="`dataList.${$index}.processId`"
-                                :rules="[{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
+                  <el-form-item v-if="dataValue.isEdit"
+                                :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
                                 style="margin: 0">
                     <el-select v-model="row.processId"
                                placeholder="璇烽�夋嫨"
                                filterable
                                clearable
                                style="width: 100%"
-                               :disabled="!dataValue.isEdit">
+                               :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)">
                       <el-option v-for="item in dataValue.processOptions"
                                  :key="item.id"
                                  :label="item.name"
@@ -80,7 +76,7 @@
               <el-table-column prop="unitQuantity"
                                label="鍗曚綅浜у嚭鎵�闇�鏁伴噺">
                 <template #default="{ row, $index }">
-                  <el-form-item :prop="`dataList.${$index}.unitQuantity`"
+                  <el-form-item v-if="dataValue.isEdit"
                                 :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺', trigger: ['blur','change'] }]"
                                 style="margin: 0">
                     <el-input-number v-model="row.unitQuantity"
@@ -89,7 +85,7 @@
                                      :step="1"
                                      controls-position="right"
                                      style="width: 100%"
-                                     :disabled="!dataValue.isEdit" />
+                                     :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                   </el-form-item>
                 </template>
               </el-table-column>
@@ -97,7 +93,7 @@
                                prop="demandedQuantity"
                                label="闇�姹傛�婚噺">
                 <template #default="{ row, $index }">
-                  <el-form-item :prop="`dataList.${$index}.demandedQuantity`"
+                  <el-form-item v-if="dataValue.isEdit"
                                 :rules="[{ required: true, message: '璇疯緭鍏ラ渶姹傛�婚噺', trigger: ['blur','change'] }]"
                                 style="margin: 0">
                     <el-input-number v-model="row.demandedQuantity"
@@ -106,29 +102,36 @@
                                      :step="1"
                                      controls-position="right"
                                      style="width: 100%"
-                                     :disabled="!dataValue.isEdit" />
+                                     :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                   </el-form-item>
                 </template>
               </el-table-column>
               <el-table-column prop="unit"
                                label="鍗曚綅">
                 <template #default="{ row, $index }">
-                  <el-form-item :prop="`dataList.${$index}.unit`"
+                  <el-form-item v-if="dataValue.isEdit"
                                 :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: ['blur','change'] }]"
                                 style="margin: 0">
                     <el-input v-model="row.unit"
                               placeholder="璇疯緭鍏ュ崟浣�"
                               clearable
-                              :disabled="!dataValue.isEdit" />
+                               :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                   </el-form-item>
                 </template>
               </el-table-column>
-              <el-table-column label="鎿嶄綔" fixed="right" width="100">
+              <el-table-column label="鎿嶄綔"
+                               fixed="right"
+                               width="200">
                 <template #default="{ row, $index }">
-                  <el-button v-if="dataValue.isEdit"
+                  <el-button v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                              type="danger"
                              text
-                             @click="dataValue.dataList.splice($index, 1)">鍒犻櫎
+                             @click="removeItem(row.tempId)">鍒犻櫎
+                  </el-button>
+                  <el-button v-if="dataValue.isEdit"
+                             type="primary"
+                             text
+                             @click="addItem2(row.tempId)">娣诲姞
                   </el-button>
                 </template>
               </el-table-column>
@@ -136,11 +139,13 @@
           </el-form>
         </template>
       </el-table-column>
-      <el-table-column label="BOM缂栧彿" prop="bomNo" />
-      <el-table-column label="浜у搧鍚嶇О" prop="productName" />
-      <el-table-column label="瑙勬牸鍨嬪彿" prop="model" />
+      <el-table-column label="BOM缂栧彿"
+                       prop="bomNo" />
+      <el-table-column label="浜у搧鍚嶇О"
+                       prop="productName" />
+      <el-table-column label="瑙勬牸鍨嬪彿"
+                       prop="model" />
     </el-table>
-
     <product-select-dialog v-if="dataValue.showProductDialog"
                            v-model:model-value="dataValue.showProductDialog"
                            @confirm="handleProduct" />
@@ -148,153 +153,364 @@
 </template>
 
 <script setup lang="ts">
-import {
-  computed,
-  defineAsyncComponent,
-  defineComponent,
-  onMounted,
-  reactive,
-  ref,
-} from "vue";
-import { queryList, add } from "@/api/productionManagement/productStructure.js";
-import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
-import { list } from "@/api/productionManagement/productionProcess";
-import { ElMessage } from "element-plus";
-import {useRoute, useRouter} from "vue-router";
+  import {
+    computed,
+    defineAsyncComponent,
+    defineComponent,
+    onMounted,
+    reactive,
+    ref,
+  } from "vue";
+  import { queryList, add } from "@/api/productionManagement/productStructure.js";
+  import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
+  import { list } from "@/api/productionManagement/productionProcess";
+  import { ElMessage } from "element-plus";
+  import { useRoute, useRouter } from "vue-router";
 
-defineComponent({
-  name: "StructureEdit",
-});
-
-const ProductSelectDialog = defineAsyncComponent(
-    () => import("@/views/basicData/product/ProductSelectDialog.vue")
-);
-const form = ref();
-
-const route = useRoute()
-const router = useRouter()
-const routeId = computed({
-  get() {
-    return route.query.id;
-  },
-
-  set(val) {
-    emit('update:router', val)
-  }
-});
-
-// 浠庤矾鐢卞弬鏁拌幏鍙栦骇鍝佷俊鎭�
-const routeBomNo = computed(() => route.query.bomNo || '');
-const routeProductName = computed(() => route.query.productName || '');
-const routeProductModelName = computed(() => route.query.productModelName || '');
-const routeOrderId = computed(() => route.query.orderId);
-const pageType = computed(() => route.query.type);
-const isOrderPage = computed(() => pageType.value === 'order' && routeOrderId.value);
-
-const dataValue = reactive({
-  dataList: [],
-  productOptions: [],
-  processOptions: [],
-  showProductDialog: false,
-  currentRowIndex: null,
-  loading: false,
-  isEdit: false,
-});
-
-const tableData = reactive([
-  {
-    productName: "",
-    model: "",
-    bomNo: "",
-  }
-])
-
-const openDialog = index => {
-  dataValue.currentRowIndex = index;
-  dataValue.showProductDialog = true;
-};
-
-const fetchData = async () => {
-  if (isOrderPage.value) {
-    // 璁㈠崟鎯呭喌锛氫娇鐢ㄨ鍗曠殑浜у搧缁撴瀯鎺ュ彛
-    const { data } = await listProcessBom({ orderId: routeOrderId.value });
-    dataValue.dataList = data || [];
-  } else {
-    // 闈炶鍗曟儏鍐碉細浣跨敤鍘熸潵鐨勬帴鍙�
-    const { data } = await queryList(routeId.value);
-    dataValue.dataList = data || [];
-  }
-};
-
-const fetchProcessOptions = async () => {
-  const { data } = await list(routeId.value);
-  dataValue.processOptions = data;
-};
-
-const handleProduct = row => {
-  if (row?.length > 1) {
-    ElMessage.error("鍙兘閫夋嫨涓�涓骇鍝�");
-  }
-  dataValue.dataList[dataValue.currentRowIndex].productName =
-      row[0].productName;
-  dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
-  dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
-  dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || "";
-  dataValue.showProductDialog = false;
-};
-
-const submit = () => {
-  form.value
-      .validate(valid => {
-        dataValue.loading = true;
-        if (valid) {
-          add({
-            bomId: routeId.value,
-            productStructureList: dataValue.dataList || [],
-          }).then(res => {
-            router.push({
-              path: '/productionManagement/productionManagement/productStructure/index',
-            })
-            ElMessage.success("淇濆瓨鎴愬姛");
-            dataValue.loading = false;
-          });
-        }
-      })
-      .finally(() => {
-        dataValue.loading = false;
-      });
-};
-
-const addItem = () => {
-  dataValue.dataList.push({
-    productName: "",
-    productId: "",
-    model: undefined,
-    productModelId: undefined,
-    processId: "",
-    unitQuantity: 0,
-    demandedQuantity: 0,
-    unit: "",
+  defineComponent({
+    name: "StructureEdit",
   });
-};
 
-const cancelEdit = () => {
-  dataValue.isEdit = false;
-  dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
-};
+  const ProductSelectDialog = defineAsyncComponent(
+    () => import("@/views/basicData/product/ProductSelectDialog.vue")
+  );
+  const emit = defineEmits(["update:router"]);
+  const form = ref();
 
-onMounted(() => {
-  // 浠庤矾鐢卞弬鏁板洖鏄炬暟鎹�
-  tableData[0].productName = routeProductName.value;
-  tableData[0].model = routeProductModelName.value;
-  tableData[0].bomNo = routeBomNo.value;
-  
-  // 璁㈠崟鎯呭喌涓嬬鐢ㄧ紪杈�
-  if (isOrderPage.value) {
+  const route = useRoute();
+  const router = useRouter();
+  const routeId = computed({
+    get() {
+      return route.query.id;
+    },
+
+    set(val) {
+      emit("update:router", val);
+    },
+  });
+
+  // 浠庤矾鐢卞弬鏁拌幏鍙栦骇鍝佷俊鎭�
+  const routeBomNo = computed(() => route.query.bomNo || "");
+  const routeProductName = computed(() => route.query.productName || "");
+  const routeProductModelName = computed(
+    () => route.query.productModelName || ""
+  );
+  const routeOrderId = computed(() => route.query.orderId);
+  const pageType = computed(() => route.query.type);
+  const isOrderPage = computed(
+    () => pageType.value === "order" && routeOrderId.value
+  );
+
+  const dataValue = reactive({
+    dataList: [],
+    productOptions: [],
+    processOptions: [],
+    showProductDialog: false,
+    currentRowIndex: null,
+    currentRowName: null,
+    loading: false,
+    isEdit: false,
+  });
+
+  const tableData = reactive([
+    {
+      productName: "",
+      model: "",
+      bomNo: "",
+    },
+  ]);
+
+  const openDialog = (tempId: any) => {
+    console.log(tempId, "tempId");
+    dataValue.currentRowName = tempId;
+    dataValue.showProductDialog = true;
+  };
+
+  const fetchData = async () => {
+    if (isOrderPage.value) {
+      // 璁㈠崟鎯呭喌锛氫娇鐢ㄨ鍗曠殑浜у搧缁撴瀯鎺ュ彛
+      const { data } = await listProcessBom({ orderId: routeOrderId.value });
+      dataValue.dataList = (data as any) || [];
+    } else {
+      // 闈炶鍗曟儏鍐碉細浣跨敤鍘熸潵鐨勬帴鍙�
+      const { data } = await queryList(routeId.value);
+      dataValue.dataList = (data as any) || [];
+      // 涓烘墍鏈夐」鍙婂叾瀛愰」璁剧疆name灞炴��
+      const setNameRecursively = (items: any[]) => {
+        items.forEach((item: any) => {
+          item.tempId = item.id;
+          item.processName =
+            dataValue.processOptions.find(option => option.id === item.processId)
+              ?.name || "";
+          if (item.children && item.children.length > 0) {
+            setNameRecursively(item.children);
+          }
+        });
+      };
+      setNameRecursively(dataValue.dataList);
+      console.log(dataValue.dataList, "dataValue.dataList");
+    }
+  };
+
+  const fetchProcessOptions = async () => {
+    const { data } = await list();
+    dataValue.processOptions = data as any;
+  };
+
+  const handleProduct = (row: any) => {
+    if (row?.length > 1) {
+      ElMessage.error("鍙兘閫夋嫨涓�涓骇鍝�");
+    }
+    const productData = row[0];
+
+    //  鏈�澶栧眰缁勪欢涓紝涓庡綋鍓嶄骇鍝佺浉鍚岀殑浜у搧鍙兘鏈変竴涓�
+    const isTopLevel = dataValue.dataList.some(item => (item as any).tempId === dataValue.currentRowName);
+    if (isTopLevel) {
+      if (productData.productName === tableData[0].productName &&
+        productData.model === tableData[0].model) {
+        //  鏌ユ壘鏄惁宸茬粡鏈夊叾浠栭《灞傝宸茬粡鏄繖涓骇鍝�
+        const hasOther = dataValue.dataList.some(item =>
+          (item as any).tempId !== dataValue.currentRowName &&
+          (item as any).productName === tableData[0].productName &&
+          (item as any).model === tableData[0].model
+        );
+        if (hasOther) {
+          ElMessage.warning("鏈�澶栧眰鍜屽綋鍓嶄骇鍝佷竴鏍风殑涓�绾у彧鑳芥湁涓�涓�");
+          return;
+        }
+      }
+    }
+    // dataValue.dataList[dataValue.currentRowIndex].productName =
+    //   row[0].productName;
+    // dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
+    // dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
+    // dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || "";
+    dataValue.dataList.map(item => {
+      if (item.tempId === dataValue.currentRowName) {
+        item.productName = productData.productName;
+        item.model = productData.model;
+        item.productModelId = productData.id;
+        item.unit = productData.unit || "";
+        return;
+      }
+      childItem(item, dataValue.currentRowName, productData);
+    });
+    dataValue.showProductDialog = false;
+  };
+  const childItem = (item: any, tempId: any, productData: any) => {
+    if (item.tempId === tempId) {
+      item.productName = productData.productName;
+      item.model = productData.model;
+      item.productModelId = productData.id;
+      item.unit = productData.unit || "";
+      return true;
+    }
+    if (item.children && item.children.length > 0) {
+      for (let child of item.children) {
+        if (childItem(child, tempId, productData)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  };
+
+  // 閫掑綊鏍¢獙鎵�鏈夊眰绾х殑琛ㄥ崟鏁版嵁
+  const validateAll = () => {
+    let isValid = true;
+
+    // 鏍¢獙鍑芥暟
+    const validateItem = (item: any, isTopLevel = false) => {
+      // 鏍¢獙褰撳墠椤圭殑蹇呭~瀛楁
+      if (!item.model) {
+        ElMessage.error("璇烽�夋嫨瑙勬牸");
+        isValid = false;
+        return;
+      }
+      if (!isTopLevel && !item.processId) {
+        ElMessage.error("璇烽�夋嫨娑堣�楀伐搴�");
+        isValid = false;
+        return;
+      }
+      if (!item.unitQuantity) {
+        ElMessage.error("璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺");
+        isValid = false;
+        return;
+      }
+      if (isOrderPage.value && !item.demandedQuantity) {
+        ElMessage.error("璇疯緭鍏ラ渶姹傛�婚噺");
+        isValid = false;
+        return;
+      }
+      // if (!item.unit) {
+      //   ElMessage.error("璇疯緭鍏ュ崟浣�");
+      //   isValid = false;
+      //   return;
+      // }
+
+      // 閫掑綊鏍¢獙瀛愰」
+      if (item.children && item.children.length > 0) {
+        item.children.forEach(child => {
+          validateItem(child, false);
+        });
+      }
+    };
+
+    // 閬嶅巻鎵�鏈夐《灞傞」
+    dataValue.dataList.forEach(item => {
+      validateItem(item, true);
+    });
+
+    return isValid;
+  };
+
+  const submit = () => {
+    dataValue.loading = true;
+
+    // 鍏堣繘琛岃〃鍗曟牎楠�
+    const valid = validateAll();
+    console.log(dataValue.dataList, "dataValue.dataList");
+    if (valid) {
+      add({
+        bomId: routeId.value,
+        children: dataValue.dataList || [],
+      })
+        .then(res => {
+          router.push({
+            path: "/productionManagement/productionManagement/productStructure/index",
+          });
+          ElMessage.success("淇濆瓨鎴愬姛");
+          dataValue.loading = false;
+        })
+        .catch(() => {
+          dataValue.loading = false;
+        });
+    } else {
+      dataValue.loading = false;
+    }
+  };
+
+  const removeItem = (tempId:string) => {
+    // 鍏堝皾璇曚粠椤跺眰鍒犻櫎
+    const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId);
+    if (topIndex !== -1) {
+      dataValue.dataList.splice(topIndex, 1);
+      return;
+    }
+
+    // 閫掑綊鍒犻櫎瀛愰」
+    const delchildItem = (items: any[], tempId: any) => {
+      for (let i = 0; i < items.length; i++) {
+        const item = items[i];
+        if (item.tempId === tempId) {
+          items.splice(i, 1);
+          return true;
+        }
+        if (item.children && item.children.length > 0) {
+          if (delchildItem(item.children, tempId)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    };
+
+    dataValue.dataList.forEach(item => {
+      if (item.children && item.children.length > 0) {
+        delchildItem(item.children, tempId);
+      }
+    });
+  };
+  const addItem2 = tempId => {
+    dataValue.dataList.map(item => {
+      if (item.tempId === tempId) {
+        if (!item.children) {
+          item.children = [];
+        }
+        item.children.push({
+          parentId: item.id || "",
+          parentTempId: item.tempId || "",
+          productName: "",
+          productId: "",
+          model: undefined,
+          productModelId: undefined,
+          processId: "",
+          processName: "",
+          unitQuantity: 0,
+          demandedQuantity: 0,
+          unit: "",
+          children: [],
+
+          tempId: new Date().getTime(),
+        });
+        return;
+      }
+      addchildItem(item, tempId);
+    });
+  };
+  const addchildItem = (item: any, tempId: any) => {
+    if (item.tempId === tempId) {
+      console.log(item, "item");
+      if (!item.children) {
+        item.children = [];
+      }
+      item.children.push({
+        parentId: item.id || "",
+        parentTempId: item.tempId || "",
+        productName: "",
+        productId: "",
+        model: undefined,
+        productModelId: undefined,
+        processId: "",
+        unitQuantity: 0,
+        demandedQuantity: 0,
+        children: [],
+        unit: "",
+        tempId: new Date().getTime(),
+      });
+      return true;
+    }
+    if (item.children && item.children.length > 0) {
+      for (let child of item.children) {
+        if (addchildItem(child, tempId)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  };
+
+  const getPropPath = (row, field) => {
+    // 涓烘瘡涓猺ow鐢熸垚鍞竴鐨勮矾寰�
+    // 浣跨敤row.id鎴栫储寮曚綔涓哄敮涓�鏍囪瘑
+    let path = "dataList";
+
+    // 绠�鍗曞疄鐜帮細浣跨敤row鐨刬d鎴栦竴涓敮涓�鏍囪瘑
+    const uniqueId = row.id || Math.floor(Math.random() * 10000);
+    path += `.${uniqueId}`;
+
+    return path + `.${field}`;
+  };
+
+  const cancelEdit = () => {
     dataValue.isEdit = false;
-  }
-  
-  fetchData();
-  fetchProcessOptions();
-});
+    // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
+    fetchData();
+  };
+
+  onMounted(async () => {
+    // 浠庤矾鐢卞弬鏁板洖鏄炬暟鎹�
+    tableData[0].productName = routeProductName.value as string;
+    tableData[0].model = routeProductModelName.value as string;
+    tableData[0].bomNo = routeBomNo.value as string;
+
+    // 璁㈠崟鎯呭喌涓嬬鐢ㄧ紪杈�
+    if (isOrderPage.value) {
+      dataValue.isEdit = false;
+    }
+
+    // 鍏堝姞杞藉伐搴忛�夐」锛屽啀鍔犺浇鏁版嵁锛岀‘淇漞l-select鑳藉姝g‘鍥炴樉
+    await fetchProcessOptions();
+    await fetchData();
+  });
 </script>
\ No newline at end of file

--
Gitblit v1.9.3