From cbcce4d5e878cbdc68d7928634ab25c4d8efb125 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期三, 22 四月 2026 13:39:55 +0800
Subject: [PATCH] 工艺路线模块修改

---
 src/views/productionManagement/processRoute/processRouteItem/index.vue |  471 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 470 insertions(+), 1 deletions(-)

diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index c310b45..6fbaa2c 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -191,6 +191,163 @@
         </div>
       </div>
     </template>
+    <!-- bom妯″潡 -->
+    <div class="section-header"
+         style="margin-top: 20px;">
+      <div class="section-title">BOM 缁撴瀯</div>
+      <div class="section-actions"
+           v-if="pageType === 'order'">
+        <el-button v-if="!bomDataValue.isEdit"
+                   type="primary"
+                   @click="bomDataValue.isEdit = true">
+          缂栬緫
+        </el-button>
+        <el-button v-if="bomDataValue.isEdit"
+                   @click="cancelEditBom">
+          鍙栨秷
+        </el-button>
+        <el-button v-if="bomDataValue.isEdit"
+                   type="primary"
+                   @click="handleSaveBom"
+                   :loading="bomDataValue.loading">
+          淇濆瓨BOM
+        </el-button>
+      </div>
+    </div>
+    <el-table :data="bomTableData"
+              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="bomDataValue">
+            <el-table :data="bomDataValue.dataList"
+                      row-key="tempId"
+                      default-expand-all
+                      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+                      style="width: 100%">
+              <el-table-column prop="productName"
+                               label="浜у搧" />
+              <el-table-column prop="model"
+                               label="瑙勬牸">
+                <template #default="{ row }">
+                  <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
+                                :rules="[{ required: true, message: '璇烽�夋嫨瑙勬牸', trigger: ['blur','change'] }]"
+                                style="margin: 0">
+                    <el-select v-model="row.model"
+                               placeholder="璇烽�夋嫨瑙勬牸"
+                               clearable
+                               :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)"
+                               style="width: 100%"
+                               @visible-change="(v) => { if (v) openBomDialog(row.tempId) }">
+                      <el-option v-if="row.model"
+                                 :label="row.model"
+                                 :value="row.model" />
+                    </el-select>
+                  </el-form-item>
+                </template>
+              </el-table-column>
+              <el-table-column prop="processName"
+                               label="娑堣�楀伐搴�">
+                <template #default="{ row }">
+                  <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
+                                :rules="bomDataValue.dataList.some(item => (item).tempId === row.tempId) ? [] : [{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
+                                style="margin: 0">
+                    <el-select v-model="row.processId"
+                               placeholder="璇烽�夋嫨"
+                               filterable
+                               clearable
+                               style="width: 100%"
+                               @change="value => handleBomProcessChange(row, value)"
+                               :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)">
+                      <el-option v-for="item in bomDataValue.processOptions"
+                                 :key="item.id"
+                                 :label="item.name"
+                                 :value="item.id" />
+                    </el-select>
+                  </el-form-item>
+                </template>
+              </el-table-column>
+              <el-table-column prop="unitQuantity"
+                               label="鍗曚綅浜у嚭鎵�闇�鏁伴噺">
+                <template #default="{ row }">
+                  <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
+                                :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺', trigger: ['blur','change'] }]"
+                                style="margin: 0">
+                    <el-input-number v-model="row.unitQuantity"
+                                     :min="0"
+                                     :precision="2"
+                                     :step="1"
+                                     controls-position="right"
+                                     style="width: 100%"
+                                     :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
+                  </el-form-item>
+                </template>
+              </el-table-column>
+              <el-table-column v-if="pageType === 'order'"
+                               prop="demandedQuantity"
+                               label="闇�姹傛�婚噺">
+                <template #default="{ row }">
+                  <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
+                                :rules="[{ required: true, message: '璇疯緭鍏ラ渶姹傛�婚噺', trigger: ['blur','change'] }]"
+                                style="margin: 0">
+                    <el-input-number v-model="row.demandedQuantity"
+                                     :min="0"
+                                     :precision="2"
+                                     :step="1"
+                                     controls-position="right"
+                                     style="width: 100%"
+                                     :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
+                  </el-form-item>
+                </template>
+              </el-table-column>
+              <el-table-column prop="unit"
+                               label="鍗曚綅">
+                <template #default="{ row }">
+                  <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
+                                :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: ['blur','change'] }]"
+                                style="margin: 0">
+                    <el-input v-model="row.unit"
+                              placeholder="璇疯緭鍏ュ崟浣�"
+                              clearable
+                              :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
+                  </el-form-item>
+                </template>
+              </el-table-column>
+              <el-table-column label="鎿嶄綔"
+                               fixed="right"
+                               width="200"
+                               v-if="pageType === 'order' && bomDataValue.isEdit">
+                <template #default="{ row }">
+                  <el-button v-if="bomDataValue.isEdit && !bomDataValue.dataList.some(item => (item).tempId === row.tempId)"
+                             type="danger"
+                             text
+                             @click="removeBomItem(row.tempId)">鍒犻櫎
+                  </el-button>
+                  <el-button v-if="bomDataValue.isEdit"
+                             type="primary"
+                             text
+                             @click="addBomItem(row.tempId)">娣诲姞
+                  </el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </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>
+    <ProductSelectDialog v-if="bomDataValue.showProductDialog"
+                         v-model:model-value="bomDataValue.showProductDialog"
+                         :single="true"
+                         @confirm="handleBomProduct" />
     <!-- 鏂板/缂栬緫寮圭獥 -->
     <el-dialog v-model="dialogVisible"
                :title="operationType === 'add' ? '鏂板宸ヨ壓璺嚎椤圭洰' : '缂栬緫宸ヨ壓璺嚎椤圭洰'"
@@ -289,6 +446,11 @@
     sortRouteItem,
   } from "@/api/productionManagement/productProcessRoute.js";
   import { processList } from "@/api/productionManagement/productionProcess.js";
+  import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
+  import {
+    queryList,
+    addBomDetail,
+  } from "@/api/productionManagement/productStructure.js";
   import { useRoute } from "vue-router";
   import { ElMessageBox, ElMessage } from "element-plus";
   import Sortable from "sortablejs";
@@ -414,6 +576,7 @@
     processList({ size: -1, current: -1 })
       .then(res => {
         processOptions.value = res.data.records || [];
+        bomDataValue.value.processOptions = processOptions.value;
       })
       .catch(err => {
         console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
@@ -427,9 +590,13 @@
       productName: route.query.productName || "",
       model: route.query.model || "",
       bomNo: route.query.bomNo || "",
+      bomId: route.query.bomId || "",
       description: route.query.description || "",
       status: !(route.query.status == 1 || route.query.status === "false"),
     };
+    bomTableData.value[0].productName = routeInfo.value.productName;
+    bomTableData.value[0].model = routeInfo.value.model;
+    bomTableData.value[0].bomNo = routeInfo.value.bomNo;
   };
 
   // 鏂板
@@ -596,7 +763,7 @@
   const handleViewParams = row => {
     currentProcess.value = row;
     const query = {
-      routeItemId: row.id,
+      technologyRoutingOperationId: row.id,
       orderId: orderId.value,
     };
 
@@ -758,10 +925,312 @@
     }
   };
 
+  // BOM鐩稿叧鐘舵�佸拰鏂规硶
+  const bomTableData = ref([
+    {
+      productName: "",
+      model: "",
+      bomNo: "",
+    },
+  ]);
+
+  const bomDataValue = ref({
+    dataList: [],
+    processOptions: [],
+    showProductDialog: false,
+    currentRowName: null,
+    loading: false,
+    isEdit: false,
+  });
+
+  const syncProcessOperationFields = item => {
+    const processId = item.processId ?? item.operationId ?? "";
+    if (!processId) {
+      item.processId = "";
+      return;
+    }
+    const option = bomDataValue.value.processOptions.find(
+      p => p.id === processId
+    );
+    const processName =
+      option?.name || item.processName || item.operationName || "";
+
+    item.processId = processId;
+    item.operationId = processId;
+    item.processName = processName;
+    item.operationName = processName;
+  };
+
+  const normalizeTreeData = items => {
+    items.forEach(item => {
+      item.tempId = item.tempId || item.id || `${Date.now()}_${Math.random()}`;
+      syncProcessOperationFields(item);
+      if (Array.isArray(item.children) && item.children.length > 0) {
+        normalizeTreeData(item.children);
+      }
+    });
+  };
+
+  const handleBomProcessChange = (row, value) => {
+    row.processId = value || "";
+    syncProcessOperationFields(row);
+  };
+
+  const openBomDialog = tempId => {
+    bomDataValue.value.currentRowName = tempId;
+    bomDataValue.value.showProductDialog = true;
+  };
+
+  const fetchBomData = async () => {
+    try {
+      const { data } = await queryList(routeInfo.value.bomId);
+      bomDataValue.value.dataList = data || [];
+      normalizeTreeData(bomDataValue.value.dataList);
+    } catch (err) {
+      console.error("鑾峰彇BOM鏁版嵁澶辫触锛�", err);
+    }
+  };
+
+  const childItem = (item, tempId, productData) => {
+    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 handleBomProduct = row => {
+    if (!Array.isArray(row) || row.length === 0) {
+      ElMessage.warning("璇烽�夋嫨涓�涓骇鍝�");
+      return;
+    }
+    const productData = row[row.length - 1];
+
+    const isTopLevel = bomDataValue.value.dataList.some(
+      item => item.tempId === bomDataValue.value.currentRowName
+    );
+    if (isTopLevel) {
+      if (
+        productData.productName === bomTableData.value[0].productName &&
+        productData.model === bomTableData.value[0].model
+      ) {
+        const hasOther = bomDataValue.value.dataList.some(
+          item =>
+            item.tempId !== bomDataValue.value.currentRowName &&
+            item.productName === bomTableData.value[0].productName &&
+            item.model === bomTableData.value[0].model
+        );
+        if (hasOther) {
+          ElMessage.warning("鏈�澶栧眰鍜屽綋鍓嶄骇鍝佷竴鏍风殑涓�绾у彧鑳芥湁涓�涓�");
+          return;
+        }
+      }
+    }
+    bomDataValue.value.dataList.forEach(item => {
+      if (item.tempId === bomDataValue.value.currentRowName) {
+        item.productName = productData.productName;
+        item.model = productData.model;
+        item.productModelId = productData.id;
+        item.unit = productData.unit || "";
+        return;
+      }
+      childItem(item, bomDataValue.value.currentRowName, productData);
+    });
+    bomDataValue.value.showProductDialog = false;
+  };
+
+  const removeBomItem = tempId => {
+    const topIndex = bomDataValue.value.dataList.findIndex(
+      item => item.tempId === tempId
+    );
+    if (topIndex !== -1) {
+      bomDataValue.value.dataList.splice(topIndex, 1);
+      return;
+    }
+
+    const delchildItem = (items, tempId) => {
+      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;
+    };
+
+    bomDataValue.value.dataList.forEach(item => {
+      if (item.children && item.children.length > 0) {
+        delchildItem(item.children, tempId);
+      }
+    });
+  };
+
+  const addchildItem = (item, tempId) => {
+    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: "",
+        operationId: "",
+        operationName: "",
+        unitQuantity: 1,
+        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 addBomItem = tempId => {
+    bomDataValue.value.dataList.forEach(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: "",
+          operationId: "",
+          operationName: "",
+          unitQuantity: 1,
+          demandedQuantity: 0,
+          unit: "",
+          children: [],
+          tempId: new Date().getTime(),
+        });
+        return;
+      }
+      addchildItem(item, tempId);
+    });
+  };
+
+  const validateAllBom = () => {
+    let isValid = true;
+    const isOrderPage = pageType.value === "order";
+
+    const validateItem = (item, 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 && !item.demandedQuantity) {
+        ElMessage.error("璇疯緭鍏ラ渶姹傛�婚噺");
+        isValid = false;
+        return;
+      }
+
+      if (item.children && item.children.length > 0) {
+        item.children.forEach(child => {
+          validateItem(child, false);
+        });
+      }
+    };
+
+    bomDataValue.value.dataList.forEach(item => {
+      validateItem(item, true);
+    });
+
+    return isValid;
+  };
+
+  const buildSubmitTree = items => {
+    return items.map(item => {
+      const current = { ...item };
+      syncProcessOperationFields(current);
+      current.children = Array.isArray(current.children)
+        ? buildSubmitTree(current.children)
+        : [];
+      return current;
+    });
+  };
+
+  const cancelEditBom = () => {
+    bomDataValue.value.isEdit = false;
+    fetchBomData();
+  };
+
+  const handleSaveBom = () => {
+    bomDataValue.value.loading = true;
+    normalizeTreeData(bomDataValue.value.dataList);
+
+    const valid = validateAllBom();
+    if (valid) {
+      addBomDetail({
+        bomId: routeInfo.value.bomId,
+        children: buildSubmitTree(bomDataValue.value.dataList || []),
+      })
+        .then(() => {
+          ElMessage.success("BOM淇濆瓨鎴愬姛");
+          bomDataValue.value.isEdit = false;
+          fetchBomData();
+        })
+        .catch(() => {
+          ElMessage.error("BOM淇濆瓨澶辫触");
+        })
+        .finally(() => {
+          bomDataValue.value.loading = false;
+        });
+    } else {
+      bomDataValue.value.loading = false;
+    }
+  };
+
   onMounted(() => {
     getRouteInfo();
     getList();
     getProcessList();
+    fetchBomData();
   });
 
   onUnmounted(() => {

--
Gitblit v1.9.3