From add86852b44af3dfb55c70c4d7b3ea9bb4fca227 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 15 四月 2026 17:41:20 +0800
Subject: [PATCH] 中兴实强 1.新增工艺路线的时候产品可以多选 2.绑定工艺路线时,可以删减工序并且每道工序可以多选报工人 3.报工列表展示报工人,非本订单报工人不可报工 其他

---
 src/views/basicData/product/ProductSelectDialog.vue                    |   80 +
 src/views/productionManagement/productionOrder/New.vue                 |  450 +++++++++++---
 src/views/productionManagement/processRoute/processRouteItem/index.vue |  277 +++++---
 src/views/productionManagement/productionOrder/index.vue               |  977 +++++++++++++++++++-------------
 4 files changed, 1,144 insertions(+), 640 deletions(-)

diff --git a/src/views/basicData/product/ProductSelectDialog.vue b/src/views/basicData/product/ProductSelectDialog.vue
index ded23cc..358e6e4 100644
--- a/src/views/basicData/product/ProductSelectDialog.vue
+++ b/src/views/basicData/product/ProductSelectDialog.vue
@@ -18,7 +18,7 @@
     <!-- 鍒楄〃 -->
     <el-table ref="tableRef" v-loading="loading" :data="tableData" height="420" highlight-current-row row-key="id"
       @selection-change="handleSelectionChange" @select="handleSelect">
-      <el-table-column type="selection" width="55" />
+      <el-table-column type="selection" width="55" :reserve-selection="true" />
       <el-table-column type="index" label="搴忓彿" width="60" />
       <el-table-column prop="productName" label="浜у搧澶х被" min-width="160" />
       <el-table-column prop="model" label="鍨嬪彿鍚嶇О" min-width="200" />
@@ -54,7 +54,7 @@
 
 const props = defineProps<{
   modelValue: boolean;
-  single?: boolean; // 鏄惁鍙兘閫夋嫨涓�涓紝榛樿false锛堝彲閫夋嫨澶氫釜锛�
+  single?: boolean;
 }>();
 
 const emit = defineEmits(['update:modelValue', 'confirm']);
@@ -78,46 +78,68 @@
 const tableData = ref<ProductRow[]>([]);
 const total = ref(0);
 const multipleSelection = ref<ProductRow[]>([]);
+const selectedMap = ref<Map<number, ProductRow>>(new Map());
 const tableRef = ref();
 
 function close() {
   visible.value = false;
 }
 
+function syncCurrentPageSelection() {
+  nextTick(() => {
+    if (!tableRef.value) {
+      return;
+    }
+
+    tableData.value.forEach((item) => {
+      tableRef.value.toggleRowSelection(item, selectedMap.value.has(item.id));
+    });
+  });
+}
+
 const handleSelectionChange = (val: ProductRow[]) => {
   if (props.single && val.length > 1) {
-    // 濡傛灉闄愬埗涓哄崟涓�夋嫨锛屽彧淇濈暀鏈�鍚庝竴涓�変腑鐨�
     const lastSelected = val[val.length - 1];
     multipleSelection.value = [lastSelected];
-    // 娓呯┖琛ㄦ牸閫変腑鐘舵�侊紝鐒跺悗閲嶆柊閫変腑鏈�鍚庝竴涓�
+    selectedMap.value = new Map(lastSelected ? [[lastSelected.id, lastSelected]] : []);
     nextTick(() => {
       if (tableRef.value) {
         tableRef.value.clearSelection();
-        tableRef.value.toggleRowSelection(lastSelected, true);
+        if (lastSelected) {
+          tableRef.value.toggleRowSelection(lastSelected, true);
+        }
       }
     });
-  } else {
-    multipleSelection.value = val;
+    return;
   }
+
+  const currentPageIds = new Set(tableData.value.map((item) => item.id));
+  currentPageIds.forEach((id) => {
+    selectedMap.value.delete(id);
+  });
+  val.forEach((item) => {
+    selectedMap.value.set(item.id, item);
+  });
+  multipleSelection.value = Array.from(selectedMap.value.values());
 }
 
-// 澶勭悊鍗曚釜閫夋嫨
 const handleSelect = (selection: ProductRow[], row: ProductRow) => {
-  if (props.single) {
-    // 濡傛灉闄愬埗涓哄崟涓紝娓呯┖鍏朵粬閫夋嫨锛屽彧淇濈暀褰撳墠琛�
-    if (selection.includes(row)) {
-      // 閫変腑褰撳墠琛屾椂锛屾竻绌哄叾浠栭�変腑
-      multipleSelection.value = [row];
-      nextTick(() => {
-        if (tableRef.value) {
-          tableData.value.forEach((item) => {
-            if (item.id !== row.id) {
-              tableRef.value.toggleRowSelection(item, false);
-            }
-          });
-        }
-      });
-    }
+  if (!props.single) {
+    return;
+  }
+
+  if (selection.includes(row)) {
+    multipleSelection.value = [row];
+    selectedMap.value = new Map([[row.id, row]]);
+    nextTick(() => {
+      if (tableRef.value) {
+        tableData.value.forEach((item) => {
+          if (item.id !== row.id) {
+            tableRef.value.toggleRowSelection(item, false);
+          }
+        });
+      }
+    });
   }
 }
 
@@ -139,7 +161,7 @@
 
 function onConfirm() {
   if (multipleSelection.value.length === 0) {
-    ElMessage.warning("璇烽�夋嫨涓�鏉′骇鍝�");
+    ElMessage.warning("璇烽�夋嫨浜у搧");
     return;
   }
   if (props.single && multipleSelection.value.length > 1) {
@@ -153,24 +175,26 @@
 async function loadData() {
   loading.value = true;
   try {
-    multipleSelection.value = []; // 缈婚〉/鎼滅储鍚庢竻绌洪�夋嫨鏇寸鍚堥鏈�
     const res: any = await productModelList({
       productName: query.productName.trim(),
       model: query.model.trim(),
       current: page.pageNum,
       size: page.pageSize,
     });
-    tableData.value = res.records;
-    total.value = res.total;
+    tableData.value = res.records || [];
+    total.value = res.total || 0;
+    syncCurrentPageSelection();
   } finally {
     loading.value = false;
   }
 }
 
-// 鐩戝惉寮圭獥鎵撳紑锛岄噸缃�夋嫨
 watch(() => props.modelValue, (visible) => {
   if (visible) {
     multipleSelection.value = [];
+    selectedMap.value = new Map();
+    page.pageNum = 1;
+    loadData();
   }
 });
 
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index c1c490c..213c6aa 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -179,18 +179,27 @@
           </el-select>
         </el-form-item>
 
-        <el-form-item label="浜у搧鍚嶇О" prop="productModelId">
+        <el-form-item label="浜у搧鍚嶇О" prop="selectedProducts">
           <el-button type="primary" @click="showProductSelectDialog = true">
-            {{ form.productName && form.model 
-              ? `${form.productName} - ${form.model}` 
-              : '閫夋嫨浜у搧' }}
+            {{ form.selectedProducts.length ? '閲嶆柊閫夋嫨浜у搧' : '閫夋嫨浜у搧' }}
           </el-button>
+          <div v-if="form.selectedProducts.length" class="selected-product-tags">
+            <el-tag
+              v-for="product in form.selectedProducts"
+              :key="product.id"
+              class="selected-product-tag"
+              type="info"
+              effect="plain"
+            >
+              {{ product.productName }} - {{ product.model }}
+            </el-tag>
+          </div>
         </el-form-item>
 
         <el-form-item label="鍗曚綅" prop="unit">
           <el-input 
               v-model="form.unit" 
-              :placeholder="form.productModelId ? '鏍规嵁閫夋嫨鐨勪骇鍝佽嚜鍔ㄥ甫鍑�' : '璇峰厛閫夋嫨浜у搧'" 
+              :placeholder="form.selectedProducts.length === 1 ? '鏍规嵁閫夋嫨鐨勪骇鍝佽嚜鍔ㄥ甫鍑�' : '澶氫釜浜у搧鏃朵笉灞曠ず鍗曚釜鍗曚綅'" 
               clearable 
               :disabled="true" 
           />
@@ -211,7 +220,6 @@
     <ProductSelectDialog
         v-model="showProductSelectDialog"
         @confirm="handleProductSelect"
-        single
     />
   </div>
 </template>
@@ -269,6 +277,8 @@
   routeId: routeId.value,
   processId: undefined,
   productModelId: undefined,
+  productModelIds: "",
+  selectedProducts: [],
   productName: "",
   model: "",
   unit: "",
@@ -277,94 +287,118 @@
 
 const rules = {
   processId: [{ required: true, message: '璇烽�夋嫨宸ュ簭', trigger: 'change' }],
-  productModelId: [{ required: true, message: '璇烽�夋嫨浜у搧', trigger: 'change' }],
+  selectedProducts: [{
+    required: true,
+    validator: (_, value, callback) => {
+      if (Array.isArray(value) && value.length > 0) {
+        callback();
+        return;
+      }
+      callback(new Error('璇烽�夋嫨浜у搧'));
+    },
+    trigger: 'change',
+  }],
 };
 
-// 鏍规嵁宸ュ簭ID鑾峰彇宸ュ簭鍚嶇О
 const getProcessName = (processId) => {
   if (!processId) return '';
-  const process = processOptions.value.find(p => p.id === processId);
+  const process = processOptions.value.find((p) => p.id === processId);
   return process ? process.name : '';
 };
 
-// 鑾峰彇鍒楄〃
 const getList = () => {
   tableLoading.value = true;
   const listPromise =
-    pageType.value === "order"
+    pageType.value === 'order'
       ? findProductProcessRouteItemList({ orderId: orderId.value })
       : findProcessRouteItemList({ routeId: routeId.value });
 
   listPromise
-    .then(res => {
+    .then((res) => {
       tableData.value = res.data || [];
       tableLoading.value = false;
-      // 鍒楄〃鍔犺浇瀹屾垚鍚庡垵濮嬪寲鎷栨嫿鎺掑簭
       nextTick(() => {
         initSortable();
       });
     })
-    .catch(err => {
+    .catch((err) => {
       tableLoading.value = false;
-      console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
-      proxy?.$modal?.msgError("鑾峰彇鍒楄〃澶辫触");
+      console.error('鑾峰彇鍒楄〃澶辫触:', err);
+      proxy?.$modal?.msgError('鑾峰彇鍒楄〃澶辫触');
     });
 };
 
-// 鑾峰彇宸ュ簭鍒楄〃
 const getProcessList = () => {
   processList({})
-    .then(res => {
+    .then((res) => {
       processOptions.value = res.data || [];
     })
-    .catch(err => {
-      console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
+    .catch((err) => {
+      console.error('鑾峰彇宸ュ簭澶辫触:', err);
     });
 };
 
-// 鑾峰彇宸ヨ壓璺嚎璇︽儏锛堜粠璺敱鍙傛暟鑾峰彇锛�
 const getRouteInfo = () => {
   routeInfo.value = {
     processRouteCode: route.query.processRouteCode || '',
     productName: route.query.productName || '',
     model: route.query.model || '',
     bomNo: route.query.bomNo || '',
-    description: route.query.description || ''
+    description: route.query.description || '',
   };
 };
 
-// 鏂板
+const getEditSelectedProducts = (row) => {
+  const idList = String(row.productModelIds || row.productModelId || '')
+    .split(',')
+    .map((item) => item.trim())
+    .filter(Boolean);
+  const nameList = String(row.productName || '')
+    .split(',')
+    .map((item) => item.trim());
+  const modelList = String(row.model || '')
+    .split(',')
+    .map((item) => item.trim());
+
+  return idList.map((id, index) => ({
+    id: Number(id) || id,
+    productName: nameList[index] || row.productName || '',
+    model: modelList[index] || row.model || '',
+    unit: row.unit || '',
+  }));
+};
+
 const handleAdd = () => {
   operationType.value = 'add';
   resetForm();
   dialogVisible.value = true;
 };
 
-// 缂栬緫
 const handleEdit = (row) => {
   operationType.value = 'edit';
+  const selectedProducts = getEditSelectedProducts(row);
   form.value = {
     id: row.id,
     routeId: routeId.value,
     processId: row.processId,
     productModelId: row.productModelId,
-    productName: row.productName || "",
-    model: row.model || "",
-    unit: row.unit || "",
+    productModelIds: row.productModelIds || (row.productModelId ? String(row.productModelId) : ''),
+    selectedProducts,
+    productName: row.productName || '',
+    model: row.model || '',
+    unit: row.unit || '',
     isQuality: row.isQuality,
   };
   dialogVisible.value = true;
 };
 
-// 鍒犻櫎
 const handleDelete = (row) => {
   ElMessageBox.confirm('纭鍒犻櫎璇ュ伐鑹鸿矾绾块」鐩紵', '鎻愮ず', {
     confirmButtonText: '纭',
     cancelButtonText: '鍙栨秷',
-    type: 'warning'
+    type: 'warning',
   })
     .then(() => {
-      // 鐢熶骇璁㈠崟涓嬩娇鐢� productProcessRoute 鐨勫垹闄ゆ帴鍙o紙璺敱鍚庢嫾鎺� id锛夛紝鍏跺畠鎯呭喌浣跨敤宸ヨ壓璺嚎椤圭洰鎵归噺鍒犻櫎鎺ュ彛
       const deletePromise =
         pageType.value === 'order'
           ? deleteRouteItem(row.id)
@@ -382,112 +416,115 @@
     .catch(() => {});
 };
 
-// 浜у搧閫夋嫨
 const handleProductSelect = (products) => {
   if (products && products.length > 0) {
-    const product = products[0];
-    form.value.productModelId = product.id;
-    form.value.productName = product.productName;
-    form.value.model = product.model;
-    form.value.unit = product.unit || "";
+    const firstProduct = products[0];
+    form.value.selectedProducts = products;
+    form.value.productModelIds = products.map((item) => item.id).join(',');
+    form.value.productModelId = products.length === 1 ? firstProduct.id : undefined;
+    form.value.productName = products.length === 1 ? firstProduct.productName : '';
+    form.value.model = products.length === 1 ? firstProduct.model : '';
+    form.value.unit = products.length === 1 ? (firstProduct.unit || '') : '';
     showProductSelectDialog.value = false;
-    // 瑙﹀彂琛ㄥ崟楠岃瘉
-    formRef.value?.validateField('productModelId');
+    formRef.value?.validateField('selectedProducts');
   }
 };
 
-// 鎻愪氦
 const handleSubmit = () => {
   formRef.value.validate((valid) => {
-    if (valid) {
-      submitLoading.value = true;
-      
-      if (operationType.value === 'add') {
-        // 鏂板锛氫紶鍗曚釜瀵硅薄锛屽寘鍚玠ragSort瀛楁
-        // dragSort = 褰撳墠鍒楄〃闀垮害 + 1锛岃〃绀烘柊澧炶褰曟帓鍦ㄦ渶鍚�
-        const dragSort = tableData.value.length + 1;
-        const isOrderPage = pageType.value === 'order';
-
-        const addPromise = isOrderPage
-          ? addRouteItem({
-              productOrderId: orderId.value,
-              productRouteId: routeId.value,
-              processId: form.value.processId,
-              productModelId: form.value.productModelId,
-              isQuality: form.value.isQuality,
-              dragSort,
-            })
-          : addOrUpdateProcessRouteItem({
-              routeId: routeId.value,
-              processId: form.value.processId,
-              productModelId: form.value.productModelId,
-              isQuality: form.value.isQuality,
-              dragSort,
-            });
-
-        addPromise
-          .then(() => {
-            proxy?.$modal?.msgSuccess('鏂板鎴愬姛');
-            closeDialog();
-            getList();
-          })
-          .catch(() => {
-            proxy?.$modal?.msgError('鏂板澶辫触');
-          })
-          .finally(() => {
-            submitLoading.value = false;
-          });
-      } else {
-        // 缂栬緫锛氱敓浜ц鍗曚笅浣跨敤 productProcessRoute/updateRouteItem锛屽叾瀹冩儏鍐典娇鐢ㄥ伐鑹鸿矾绾块」鐩洿鏂版帴鍙�
-        const isOrderPage = pageType.value === 'order';
-        
-        const updatePromise = isOrderPage
-          ? addOrUpdateProductProcessRouteItem({
-              id: form.value.id,
-              processId: form.value.processId,
-              productModelId: form.value.productModelId,
-              isQuality: form.value.isQuality,
-            })
-          : addOrUpdateProcessRouteItem({
-              routeId: routeId.value,
-              processId: form.value.processId,
-              productModelId: form.value.productModelId,
-              id: form.value.id,
-              isQuality: form.value.isQuality,
-            });
-
-        updatePromise
-          .then(() => {
-            proxy?.$modal?.msgSuccess('淇敼鎴愬姛');
-            closeDialog();
-            getList();
-          })
-          .catch(() => {
-            proxy?.$modal?.msgError('淇敼澶辫触');
-          })
-          .finally(() => {
-            submitLoading.value = false;
-          });
-      }
+    if (!valid) {
+      return;
     }
+
+    submitLoading.value = true;
+
+    if (operationType.value === 'add') {
+      const dragSort = tableData.value.length + 1;
+      const isOrderPage = pageType.value === 'order';
+
+      const addPromise = isOrderPage
+        ? addRouteItem({
+            productOrderId: orderId.value,
+            productRouteId: routeId.value,
+            processId: form.value.processId,
+            productModelId: form.value.productModelId,
+            productModelIds: form.value.productModelIds,
+            isQuality: form.value.isQuality,
+            dragSort,
+          })
+        : addOrUpdateProcessRouteItem({
+            routeId: routeId.value,
+            processId: form.value.processId,
+            productModelId: form.value.productModelId,
+            productModelIds: form.value.productModelIds,
+            isQuality: form.value.isQuality,
+            dragSort,
+          });
+
+      addPromise
+        .then(() => {
+          proxy?.$modal?.msgSuccess('鏂板鎴愬姛');
+          closeDialog();
+          getList();
+        })
+        .catch(() => {
+          proxy?.$modal?.msgError('鏂板澶辫触');
+        })
+        .finally(() => {
+          submitLoading.value = false;
+        });
+      return;
+    }
+
+    const isOrderPage = pageType.value === 'order';
+    const updatePromise = isOrderPage
+      ? addOrUpdateProductProcessRouteItem({
+          id: form.value.id,
+          processId: form.value.processId,
+          productModelId: form.value.productModelId,
+          productModelIds: form.value.productModelIds,
+          isQuality: form.value.isQuality,
+        })
+      : addOrUpdateProcessRouteItem({
+          routeId: routeId.value,
+          processId: form.value.processId,
+          productModelId: form.value.productModelId,
+          productModelIds: form.value.productModelIds,
+          id: form.value.id,
+          isQuality: form.value.isQuality,
+        });
+
+    updatePromise
+      .then(() => {
+        proxy?.$modal?.msgSuccess('淇敼鎴愬姛');
+        closeDialog();
+        getList();
+      })
+      .catch(() => {
+        proxy?.$modal?.msgError('淇敼澶辫触');
+      })
+      .finally(() => {
+        submitLoading.value = false;
+      });
   });
 };
 
-// 閲嶇疆琛ㄥ崟
 const resetForm = () => {
   form.value = {
     id: undefined,
     routeId: routeId.value,
     processId: undefined,
     productModelId: undefined,
-    productName: "",
-    model: "",
-    unit: "",
+    productModelIds: '',
+    selectedProducts: [],
+    productName: '',
+    model: '',
+    unit: '',
+    isQuality: false,
   };
   formRef.value?.resetFields();
 };
 
-// 鍏抽棴寮圭獥
 const closeDialog = () => {
   dialogVisible.value = false;
   resetForm();
@@ -753,6 +790,18 @@
   margin: 10px 0;
 }
 
+.selected-product-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-top: 10px;
+}
+
+.selected-product-tag {
+  max-width: 100%;
+}
+
+
 .card-footer {
   display: flex;
   justify-content: space-around;
diff --git a/src/views/productionManagement/productionOrder/New.vue b/src/views/productionManagement/productionOrder/New.vue
index c9c478b..edd8bb8 100644
--- a/src/views/productionManagement/productionOrder/New.vue
+++ b/src/views/productionManagement/productionOrder/New.vue
@@ -1,68 +1,137 @@
 <template>
   <div>
     <el-dialog
-        v-model="isShow"
-        title="鏂板鐢熶骇璁㈠崟"
-        width="800"
-        @close="closeModal"
+      v-model="isShow"
+      title="鏂板鐢熶骇璁㈠崟"
+      width="800"
+      @close="closeModal"
     >
-      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+      <el-form
+        ref="formRef"
+        :model="formState"
+        label-width="140px"
+        label-position="top"
+      >
         <el-form-item
-            label="浜у搧鍚嶇О"
-            prop="productModelId"
-            :rules="[
-                {
-                required: true,
-                message: '璇烽�夋嫨浜у搧',
-                trigger: 'change',
-              }
-            ]"
+          label="浜у搧鍚嶇О"
+          prop="productModelId"
+          :rules="[
+            {
+              required: true,
+              message: '璇烽�夋嫨浜у搧',
+              trigger: 'change',
+            },
+          ]"
         >
           <el-button type="primary" @click="showProductSelectDialog = true">
-            {{ formState.productName ? formState.productName : '閫夋嫨浜у搧' }}
+            {{ formState.productName || "閫夋嫨浜у搧" }}
           </el-button>
         </el-form-item>
 
-        <el-form-item
-            label="瑙勬牸"
-            prop="productModelName"
-        >
-          <el-input v-model="formState.productModelName"  disabled />
+        <el-form-item label="瑙勬牸" prop="productModelName">
+          <el-input v-model="formState.productModelName" disabled />
+        </el-form-item>
+
+        <el-form-item label="鍗曚綅" prop="unit">
+          <el-input v-model="formState.unit" disabled />
         </el-form-item>
 
         <el-form-item
-            label="鍗曚綅"
-            prop="unit"
+          label="宸ヨ壓璺嚎"
+          prop="routeId"
+          :rules="[
+            {
+              required: true,
+              message: '璇烽�夋嫨宸ヨ壓璺嚎',
+              trigger: 'change',
+            },
+          ]"
         >
-          <el-input v-model="formState.unit"  disabled />
-        </el-form-item>
-
-        <el-form-item label="宸ヨ壓璺嚎">
-          <el-select v-model="formState.routeId"
-                     placeholder="璇烽�夋嫨宸ヨ壓璺嚎"
-                     style="width: 100%;"
-                     :loading="bindRouteLoading">
-            <el-option v-for="item in routeOptions"
-                       :key="item.id"
-                       :label="`${item.processRouteCode || ''}`"
-                       :value="item.id" />
+          <el-select
+            v-model="formState.routeId"
+            placeholder="璇烽�夋嫨宸ヨ壓璺嚎"
+            style="width: 100%"
+            :loading="bindRouteLoading"
+            @change="handleRouteChange"
+          >
+            <el-option
+              v-for="item in routeOptions"
+              :key="item.id"
+              :label="item.processRouteCode || ''"
+              :value="item.id"
+            />
           </el-select>
         </el-form-item>
 
         <el-form-item
-            label="闇�姹傛暟閲�"
-            prop="quantity"
+          v-if="processListData.length"
+          label="宸ュ簭鎶ュ伐浜�"
+          prop="processUserList"
+          :rules="[
+            {
+              validator: validateProcessUsers,
+              trigger: 'change',
+            },
+          ]"
         >
-          <el-input-number v-model="formState.quantity" :step="1" :min="1" style="width: 100%" />
+          <div class="process-user-list">
+            <div
+              v-for="(item, index) in processListData"
+              :key="item.id || `${item.processId}-${index}`"
+              class="process-user-item"
+            >
+              <div class="process-user-header">
+                <div class="process-user-name">
+                  {{ item.name || item.processName || item.no || `宸ュ簭${index + 1}` }}
+                </div>
+                <el-button
+                  type="danger"
+                  link
+                  class="process-user-remove"
+                  @click="removeProcessItem(index)"
+                >
+                  鍒犻櫎
+                </el-button>
+              </div>
+              <el-select
+                v-model="formState.processUserList[index].userIds"
+                placeholder="璇烽�夋嫨鎶ュ伐浜�"
+                class="process-user-select"
+                filterable
+                clearable
+                multiple
+                collapse-tags
+                collapse-tags-tooltip
+                :max-collapse-tags="3"
+                @change="handleProcessUserChange(index, $event)"
+              >
+                <el-option
+                  v-for="user in userOptions"
+                  :key="user.userId"
+                  :label="user.nickName"
+                  :value="user.userId"
+                />
+              </el-select>
+            </div>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="闇�姹傛暟閲�" prop="quantity">
+          <el-input-number
+            v-model="formState.quantity"
+            :step="1"
+            :min="1"
+            style="width: 100%"
+          />
         </el-form-item>
       </el-form>
 
-      <!-- 浜у搧閫夋嫨寮圭獥 -->
       <ProductSelectDialog
-          v-model="showProductSelectDialog"
-          @confirm="handleProductSelect"
-          single
+        v-model="showProductSelectDialog"
+        single
+        @confirm="handleProductSelect"
       />
+
       <template #footer>
         <div class="dialog-footer">
           <el-button type="primary" @click="handleSubmit">纭</el-button>
@@ -74,115 +143,234 @@
 </template>
 
 <script setup>
-import {ref, computed, getCurrentInstance} from "vue";
+import { computed, getCurrentInstance, nextTick, ref } from "vue";
 import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
-import {addProductOrder, listProcessRoute} from "@/api/productionManagement/productionOrder.js";
+import { addProductOrder, listProcessRoute } from "@/api/productionManagement/productionOrder.js";
+import { processList } from "@/api/productionManagement/productionProcess.js";
+import { userListNoPageByTenantId } from "@/api/system/user.js";
 
 const props = defineProps({
   visible: {
     type: Boolean,
     required: true,
   },
-
   type: {
     type: String,
-    required: true,
-    default: 'qualified',
+    default: "qualified",
   },
 });
 
-const emit = defineEmits(['update:visible', 'completed']);
+const emit = defineEmits(["update:visible", "completed"]);
 
-// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
-const formState = ref({
+const createDefaultFormState = () => ({
   productId: undefined,
   productModelId: undefined,
   routeId: undefined,
   productName: "",
   productModelName: "",
   unit: "",
-  quantity: 0,
+  quantity: 1,
+  processUserList: [],
 });
+
+const formState = ref(createDefaultFormState());
 
 const isShow = computed({
   get() {
     return props.visible;
   },
   set(val) {
-    emit('update:visible', val);
+    emit("update:visible", val);
   },
 });
 
 const showProductSelectDialog = ref(false);
+const routeOptions = ref([]);
+const bindRouteLoading = ref(false);
+const processListData = ref([]);
+const userOptions = ref([]);
+const userLoading = ref(false);
 
-let { proxy } = getCurrentInstance()
+const { proxy } = getCurrentInstance();
+const formRef = ref();
+
+const validateProcessUserField = async () => {
+  await nextTick();
+  if (!formRef.value) return;
+
+  if (processListData.value.length) {
+    formRef.value.validateField("processUserList");
+    return;
+  }
+
+  formRef.value.clearValidate("processUserList");
+};
+
+const resetProcessUsers = () => {
+  processListData.value = [];
+  formState.value.processUserList = [];
+};
 
 const closeModal = () => {
-  // 閲嶇疆琛ㄥ崟鏁版嵁
-  formState.value = {
-    productId: undefined,
-    productModelId: undefined,
-    routeId: undefined,
-    productName: "",
-    productModelName: "",
-    quantity: '',
-  };
+  formState.value = createDefaultFormState();
+  routeOptions.value = [];
+  resetProcessUsers();
   isShow.value = false;
 };
 
-// 浜у搧閫夋嫨澶勭悊
-const handleProductSelect = async (products) => {
-  if (products && products.length > 0) {
-    const product = products[0];
-    formState.value.productId = product.productId;
-    formState.value.productName = product.productName;
-    formState.value.productModelName = product.model;
-    formState.value.productModelId = product.id;
-    formState.value.unit = product.unit;
-    showProductSelectDialog.value = false;
-    fetchRouteOptions( product.id);
-    // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
-    proxy.$refs["formRef"]?.validateField('productModelId');
-  }
+const ensureUserOptions = () => {
+  if (userOptions.value.length || userLoading.value) return;
+
+  userLoading.value = true;
+  userListNoPageByTenantId()
+    .then(res => {
+      userOptions.value = res.data || [];
+    })
+    .finally(() => {
+      userLoading.value = false;
+    });
 };
 
-const routeOptions = ref([]);
-const bindRouteLoading = ref(false);
-const fetchRouteOptions = (productModelId) => {
+const createProcessUserList = list =>
+  list.map(item => ({
+    processId: item.id,
+    processName: item.name || item.processName || item.no || "",
+    userIds: [],
+    userNames: "",
+  }));
+
+const fetchProcessList = routeId => {
+  if (!routeId) {
+    resetProcessUsers();
+    return;
+  }
+
+  processList({ routeId })
+    .then(res => {
+      processListData.value = res.data || [];
+      formState.value.processUserList = createProcessUserList(processListData.value);
+      ensureUserOptions();
+      validateProcessUserField();
+    })
+    .catch(() => {
+      resetProcessUsers();
+    });
+};
+
+const handleRouteChange = routeId => {
+  fetchProcessList(routeId);
+};
+
+const handleProcessUserChange = (index, userIds) => {
+  const selectedUsers = userOptions.value.filter(user => Array.isArray(userIds) && userIds.includes(user.userId));
+  formState.value.processUserList[index].userNames = selectedUsers.map(user => user.nickName).join(",");
+  validateProcessUserField();
+};
+
+const removeProcessItem = index => {
+  processListData.value.splice(index, 1);
+  formState.value.processUserList.splice(index, 1);
+  validateProcessUserField();
+};
+
+const validateProcessUsers = (_, value, callback) => {
+  if (!formState.value.routeId) {
+    callback();
+    return;
+  }
+  if (!processListData.value.length) {
+    callback(new Error("褰撳墠宸ヨ壓璺嚎涓嬫病鏈夊伐搴�"));
+    return;
+  }
+  if (!Array.isArray(value) || value.length !== processListData.value.length) {
+    callback(new Error("璇蜂负姣忛亾宸ュ簭閫夋嫨鎶ュ伐浜�"));
+    return;
+  }
+
+  const hasEmptyUser = value.some(item => !Array.isArray(item.userIds) || item.userIds.length === 0);
+  if (hasEmptyUser) {
+    callback(new Error("璇蜂负姣忛亾宸ュ簭閫夋嫨鎶ュ伐浜�"));
+    return;
+  }
+  callback();
+};
+
+const handleProductSelect = products => {
+  if (!products?.length) return;
+
+  const product = products[0];
+  formState.value.productId = product.productId;
+  formState.value.productName = product.productName;
+  formState.value.productModelName = product.model;
+  formState.value.productModelId = product.id;
+  formState.value.unit = product.unit;
   formState.value.routeId = undefined;
-  routeOptions.value = []
+  routeOptions.value = [];
+  resetProcessUsers();
+  showProductSelectDialog.value = false;
+  fetchRouteOptions(product.id);
+  formRef.value?.validateField("productModelId");
+};
+
+const fetchRouteOptions = productModelId => {
+  formState.value.routeId = undefined;
+  routeOptions.value = [];
+  resetProcessUsers();
   bindRouteLoading.value = true;
-  listProcessRoute({ productModelId: productModelId }).then(res => {
-    routeOptions.value = res.data || [];
-  }).finally(() => {
-    bindRouteLoading.value = false;
-  })
-}
+  listProcessRoute({ productModelId })
+    .then(res => {
+      routeOptions.value = res.data || [];
+    })
+    .finally(() => {
+      bindRouteLoading.value = false;
+    });
+};
+
+const buildProcessRouteItems = () =>
+  processListData.value.map((item, index) => {
+    const processUser = formState.value.processUserList[index] || {};
+    return {
+      productOrderId: undefined,
+      productRouteId: formState.value.routeId,
+      processId: item.id,
+      productModelId: formState.value.productModelId,
+      dragSort: item.dragSort ?? index + 1,
+      isQuality: item.isQuality ?? false,
+      reportUserIds: Array.isArray(processUser.userIds) ? processUser.userIds.join(",") : "",
+    };
+  });
 
 const handleSubmit = () => {
-  proxy.$refs["formRef"].validate(valid => {
-    if (valid) {
-      // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰瑙勬牸
-      if (!formState.value.productModelId) {
-        proxy.$modal.msgError("璇烽�夋嫨浜у搧");
-        return;
-      }
-      if (!formState.value.productModelId) {
-        proxy.$modal.msgError("璇烽�夋嫨瑙勬牸");
-        return;
-      }
+  formRef.value.validate(valid => {
+    if (!valid) return;
 
-      addProductOrder(formState.value).then(res => {
-        // 鍏抽棴妯℃�佹
-        isShow.value = false;
-        // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
-        emit('completed');
-        proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
-      })
+    if (!formState.value.productModelId) {
+      proxy.$modal.msgError("璇烽�夋嫨浜у搧");
+      return;
     }
-  })
-};
+    if (!formState.value.routeId) {
+      proxy.$modal.msgError("璇烽�夋嫨宸ヨ壓璺嚎");
+      return;
+    }
+    if (!formState.value.processUserList.length) {
+      proxy.$modal.msgError("褰撳墠宸ヨ壓璺嚎涓嬫病鏈夊伐搴�");
+      return;
+    }
+    if (formState.value.processUserList.some(item => !Array.isArray(item.userIds) || item.userIds.length === 0)) {
+      proxy.$modal.msgError("璇蜂负姣忛亾宸ュ簭閫夋嫨鎶ュ伐浜�");
+      return;
+    }
 
+    addProductOrder({
+      ...formState.value,
+      processRouteItems: buildProcessRouteItems(),
+    }).then(() => {
+      isShow.value = false;
+      emit("completed");
+      proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+    });
+  });
+};
 
 defineExpose({
   closeModal,
@@ -190,3 +378,55 @@
   isShow,
 });
 </script>
+
+<style scoped>
+.process-user-list {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+  padding: 14px;
+  border-radius: 12px;
+  background: #f7f9fc;
+  border: 1px solid #e8eef5;
+}
+
+.process-user-item {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr);
+  gap: 16px;
+  padding: 12px 14px;
+  border-radius: 10px;
+  background: #fff;
+  border: 1px solid #edf2f7;
+}
+
+.process-user-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+}
+
+.process-user-name {
+  color: #1f2d3d;
+  font-weight: 500;
+  line-height: 1.4;
+}
+
+.process-user-remove {
+  flex-shrink: 0;
+  padding: 0;
+}
+
+.process-user-select {
+  width: 100%;
+}
+
+@media (max-width: 768px) {
+  .process-user-item {
+    grid-template-columns: 1fr;
+    gap: 10px;
+  }
+}
+</style>
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index fc64063..451dc34 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -1,43 +1,49 @@
 <template>
   <div class="app-container">
     <div class="search_form">
-      <el-form :model="searchForm"
-               :inline="true">
-        <el-form-item label="瀹㈡埛鍚嶇О:">
-          <el-input v-model="searchForm.customerName"
-                    placeholder="璇疯緭鍏�"
-                    clearable
-                    prefix-icon="Search"
-                    style="width: 160px;"
-                    @change="handleQuery" />
+      <el-form :model="searchForm" :inline="true">
+        <el-form-item label="瀹㈡埛鍚嶇О">
+          <el-input
+            v-model="searchForm.customerName"
+            placeholder="璇疯緭鍏�"
+            clearable
+            :prefix-icon="Search"
+            style="width: 160px;"
+            @change="handleQuery"
+          />
         </el-form-item>
-        <el-form-item label="鍚堝悓鍙�:">
-          <el-input v-model="searchForm.salesContractNo"
-                    placeholder="璇疯緭鍏�"
-                    clearable
-                    prefix-icon="Search"
-                    style="width: 160px;"
-                    @change="handleQuery" />
+        <el-form-item label="閿�鍞悎鍚屽彿">
+          <el-input
+            v-model="searchForm.salesContractNo"
+            placeholder="璇疯緭鍏�"
+            clearable
+            :prefix-icon="Search"
+            style="width: 160px;"
+            @change="handleQuery"
+          />
         </el-form-item>
-        <el-form-item label="浜у搧鍚嶇О:">
-          <el-input v-model="searchForm.productCategory"
-                    placeholder="璇疯緭鍏�"
-                    clearable
-                    prefix-icon="Search"
-                    style="width: 160px;"
-                    @change="handleQuery" />
+        <el-form-item label="浜у搧鍚嶇О">
+          <el-input
+            v-model="searchForm.productCategory"
+            placeholder="璇疯緭鍏�"
+            clearable
+            :prefix-icon="Search"
+            style="width: 160px;"
+            @change="handleQuery"
+          />
         </el-form-item>
-        <el-form-item label="瑙勬牸:">
-          <el-input v-model="searchForm.specificationModel"
-                    placeholder="璇疯緭鍏�"
-                    clearable
-                    prefix-icon="Search"
-                    style="width: 160px;"
-                    @change="handleQuery" />
+        <el-form-item label="瑙勬牸鍨嬪彿">
+          <el-input
+            v-model="searchForm.specificationModel"
+            placeholder="璇疯緭鍏�"
+            clearable
+            :prefix-icon="Search"
+            style="width: 160px;"
+            @change="handleQuery"
+          />
         </el-form-item>
         <el-form-item>
-          <el-button type="primary"
-                     @click="handleQuery">鎼滅储</el-button>
+          <el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
         </el-form-item>
       </el-form>
       <div>
@@ -46,16 +52,19 @@
         <el-button @click="handleOut">瀵煎嚭</el-button>
       </div>
     </div>
+
     <div class="table_list">
-      <PIMTable rowKey="id"
-                :column="tableColumn"
-                :tableData="tableData"
-                :page="page"
-                :tableLoading="tableLoading"
-                :row-class-name="tableRowClassName"
-                :isSelection="true"
-                @selection-change="handleSelectionChange"
-                @pagination="pagination">
+      <PIMTable
+        rowKey="id"
+        :column="tableColumn"
+        :tableData="tableData"
+        :page="page"
+        :tableLoading="tableLoading"
+        :row-class-name="tableRowClassName"
+        :isSelection="true"
+        @selection-change="handleSelectionChange"
+        @pagination="pagination"
+      >
         <template #completionStatus="{ row }">
           <el-progress
             :percentage="toProgressPercentage(row?.completionStatus)"
@@ -65,409 +74,591 @@
         </template>
       </PIMTable>
     </div>
-    <el-dialog v-model="bindRouteDialogVisible"
-               title="缁戝畾宸ヨ壓璺嚎"
-               width="500px">
+
+    <el-dialog
+      v-model="bindRouteDialogVisible"
+      title="缁戝畾宸ヨ壓璺嚎"
+      width="700px"
+    >
       <el-form label-width="90px">
         <el-form-item label="宸ヨ壓璺嚎">
-          <el-select v-model="bindForm.routeId"
-                     placeholder="璇烽�夋嫨宸ヨ壓璺嚎"
-                     style="width: 100%;"
-                     :loading="bindRouteLoading">
-            <el-option v-for="item in routeOptions"
-                       :key="item.id"
-                       :label="`${item.processRouteCode || ''}`"
-                       :value="item.id" />
+          <el-select
+            v-model="bindForm.routeId"
+            placeholder="璇烽�夋嫨宸ヨ壓璺嚎"
+            style="width: 100%;"
+            :loading="bindRouteLoading"
+            @change="handleBindRouteChange"
+          >
+            <el-option
+              v-for="item in routeOptions"
+              :key="item.id"
+              :label="item.processRouteCode || ''"
+              :value="item.id"
+            />
           </el-select>
+        </el-form-item>
+        <el-form-item v-if="bindProcessList.length" label="鎶ュ伐浜哄憳">
+          <div class="process-user-list">
+            <div
+              v-for="(item, index) in bindProcessList"
+              :key="item.id || `${item.processId}-${index}`"
+              class="process-user-item"
+            >
+              <div class="process-user-header">
+                <div class="process-user-name">
+                  {{ item.name || item.processName || item.no || `宸ュ簭${index + 1}` }}
+                </div>
+                <el-button
+                  type="danger"
+                  link
+                  class="process-user-remove"
+                  @click="removeBindProcessItem(index)"
+                >
+                  鍒犻櫎
+                </el-button>
+              </div>
+              <el-select
+                v-model="bindForm.processUserList[index].userIds"
+                class="process-user-select"
+                placeholder="璇烽�夋嫨鎶ュ伐浜哄憳"
+                filterable
+                clearable
+                multiple
+                collapse-tags
+                collapse-tags-tooltip
+                :max-collapse-tags="3"
+                @change="handleBindProcessUserChange(index, $event)"
+              >
+                <el-option
+                  v-for="user in userOptions"
+                  :key="user.userId"
+                  :label="user.nickName"
+                  :value="user.userId"
+                />
+              </el-select>
+            </div>
+          </div>
         </el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
-          <el-button type="primary"
-                     :loading="bindRouteSaving"
-                     @click="handleBindRouteConfirm">纭� 璁�</el-button>
-          <el-button @click="bindRouteDialogVisible = false">鍙� 娑�</el-button>
+          <el-button
+            type="primary"
+            :loading="bindRouteSaving"
+            @click="handleBindRouteConfirm"
+          >
+            纭
+          </el-button>
+          <el-button @click="bindRouteDialogVisible = false">鍙栨秷</el-button>
         </span>
       </template>
     </el-dialog>
 
-    <new-product-order v-if="isShowNewModal"
-                         v-model:visible="isShowNewModal"
-                         @completed="handleQuery" />
+    <new-product-order
+      v-if="isShowNewModal"
+      v-model:visible="isShowNewModal"
+      @completed="handleQuery"
+    />
   </div>
 </template>
 
 <script setup>
-  import { onMounted, ref } from "vue";
-  import { ElMessageBox } from "element-plus";
-  import dayjs from "dayjs";
-  import { useRouter } from "vue-router";
-  import {
-    productOrderListPage,
-    listProcessRoute,
-    bindingRoute,
-    listProcessBom, delProductOrder,
-  } from "@/api/productionManagement/productionOrder.js";
-  import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
-  import {fileDel} from "@/api/financialManagement/revenueManagement.js";
-  import PIMTable from "@/components/PIMTable/PIMTable.vue";
-  const NewProductOrder = defineAsyncComponent(() => import("@/views/productionManagement/productionOrder/New.vue"));
+import { defineAsyncComponent, getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
+import { ElMessageBox } from "element-plus";
+import { Search } from "@element-plus/icons-vue";
+import dayjs from "dayjs";
+import { useRouter } from "vue-router";
+import {
+  bindingRoute,
+  delProductOrder,
+  listProcessRoute,
+  productOrderListPage,
+} from "@/api/productionManagement/productionOrder.js";
+import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
+import { processList } from "@/api/productionManagement/productionProcess.js";
+import { userListNoPageByTenantId } from "@/api/system/user.js";
+import PIMTable from "@/components/PIMTable/PIMTable.vue";
 
-  const { proxy } = getCurrentInstance();
+const NewProductOrder = defineAsyncComponent(() => import("@/views/productionManagement/productionOrder/New.vue"));
 
-  const router = useRouter();
-  const isShowNewModal = ref(false);
+const { proxy } = getCurrentInstance();
+const router = useRouter();
+const isShowNewModal = ref(false);
 
-  const tableColumn = ref([
-    {
-      label: "鐢熶骇璁㈠崟鍙�",
-      prop: "npsNo",
-      width: '120px',
-    },
-    {
-      label: "閿�鍞悎鍚屽彿",
-      prop: "salesContractNo",
-      width: '150px',
-    },
-    {
-      label: "瀹㈡埛鍚嶇О",
-      prop: "customerName",
-      width: '200px',
-    },
-    {
-      label: "浜у搧鍚嶇О",
-      prop: "productCategory",
-      width: '120px',
-    },
-    {
-      label: "瑙勬牸",
-      prop: "specificationModel",
-      width: '120px',
-    },
-    {
-      label: "宸ヨ壓璺嚎缂栧彿",
-      prop: "processRouteCode",
-      width: '200px',
-    },
-    {
-      label: "闇�姹傛暟閲�",
-      prop: "quantity",
-    },
-    {
-      label: "瀹屾垚鏁伴噺",
-      prop: "completeQuantity",
-    },
-    {
-      dataType: "slot",
-      label: "瀹屾垚杩涘害",
-      prop: "completionStatus",
-      slot: "completionStatus",
-      width: 180,
-    },
-    {
-      label: "寮�濮嬫棩鏈�",
-      prop: "startTime",
-      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
-      width: 120,
-    },
-    {
-      label: "缁撴潫鏃ユ湡",
-      prop: "endTime",
-      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
-      width: 120,
-    },
-    {
-      label: "浜や粯鏃ユ湡",
-      prop: "deliveryDate",
-      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
-      width: 120,
-    },
-    {
-      dataType: "action",
-      label: "鎿嶄綔",
-      align: "center",
-      fixed: "right",
-      width: 200,
-      operation: [
-        {
-          name: "宸ヨ壓璺嚎",
-          type: "text",
-          clickFun: row => {
-            showRouteItemModal(row);
-          },
+const tableColumn = ref([
+  {
+    label: "鐢熶骇璁㈠崟鍙�",
+    prop: "npsNo",
+    width: "120px",
+  },
+  {
+    label: "閿�鍞悎鍚屽彿",
+    prop: "salesContractNo",
+    width: "150px",
+  },
+  {
+    label: "瀹㈡埛鍚嶇О",
+    prop: "customerName",
+    width: "200px",
+  },
+  {
+    label: "浜у搧鍚嶇О",
+    prop: "productCategory",
+    width: "120px",
+  },
+  {
+    label: "瑙勬牸鍨嬪彿",
+    prop: "specificationModel",
+    width: "120px",
+  },
+  {
+    label: "宸ヨ壓璺嚎缂栧彿",
+    prop: "processRouteCode",
+    width: "200px",
+  },
+  {
+    label: "闇�姹傛暟閲�",
+    prop: "quantity",
+  },
+  {
+    label: "瀹屾垚鏁伴噺",
+    prop: "completeQuantity",
+  },
+  {
+    dataType: "slot",
+    label: "瀹屾垚杩涘害",
+    prop: "completionStatus",
+    slot: "completionStatus",
+    width: 180,
+  },
+  {
+    label: "寮�濮嬫棩鏈�",
+    prop: "startTime",
+    formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
+    width: 120,
+  },
+  {
+    label: "缁撴潫鏃ユ湡",
+    prop: "endTime",
+    formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
+    width: 120,
+  },
+  {
+    label: "浜や粯鏃ユ湡",
+    prop: "deliveryDate",
+    formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
+    width: 120,
+  },
+  {
+    dataType: "action",
+    label: "鎿嶄綔",
+    align: "center",
+    fixed: "right",
+    width: 200,
+    operation: [
+      {
+        name: "宸ヨ壓璺嚎",
+        type: "text",
+        clickFun: row => {
+          showRouteItemModal(row);
         },
-        {
-          name: "缁戝畾宸ヨ壓璺嚎",
-          type: "text",
-          showHide: row => !row.processRouteCode,
-          clickFun: row => {
-            openBindRouteDialog(row);
-          },
+      },
+      {
+        name: "缁戝畾宸ヨ壓璺嚎",
+        type: "text",
+        showHide: row => !row.processRouteCode,
+        clickFun: row => {
+          openBindRouteDialog(row);
         },
-        {
-          name: "浜у搧缁撴瀯",
-          type: "text",
-          clickFun: row => {
-            showProductStructure(row);
-          },
+      },
+      {
+        name: "浜у搧缁撴瀯",
+        type: "text",
+        clickFun: row => {
+          showProductStructure(row);
         },
-      ],
-    },
-  ]);
-  const tableData = ref([]);
-  const tableLoading = ref(false);
-  const page = reactive({
-    current: 1,
-    size: 100,
-    total: 0,
-  });
-  const selectedRows = ref([]);
+      },
+    ],
+  },
+]);
 
-  const data = reactive({
-    searchForm: {
-      customerName: "",
-      salesContractNo: "",
-      projectName: "",
-      productCategory: "",
-      specificationModel: "",
-    },
-  });
-  const { searchForm } = toRefs(data);
+const tableData = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+  current: 1,
+  size: 100,
+  total: 0,
+});
+const selectedRows = ref([]);
 
-  const toProgressPercentage = val => {
-    const n = Number(val);
-    if (!Number.isFinite(n)) return 0;
-    if (n <= 0) return 0;
-    if (n >= 100) return 100;
-    return Math.round(n);
-  };
+const data = reactive({
+  searchForm: {
+    customerName: "",
+    salesContractNo: "",
+    projectName: "",
+    productCategory: "",
+    specificationModel: "",
+  },
+});
+const { searchForm } = toRefs(data);
 
-  // 30/50/80/100 鍒嗘棰滆壊锛氱孩/姗�/钃�/缁�
-  const progressColor = percentage => {
-    const p = toProgressPercentage(percentage);
-    if (p < 30) return "#f56c6c";
-    if (p < 50) return "#e6a23c";
-    if (p < 80) return "#409eff";
-    return "#67c23a";
-  };
+const toProgressPercentage = val => {
+  const n = Number(val);
+  if (!Number.isFinite(n) || n <= 0) return 0;
+  if (n >= 100) return 100;
+  return Math.round(n);
+};
 
-  // 娣诲姞琛ㄨ绫诲悕鏂规硶
-  const tableRowClassName = ({ row }) => {
-    if (!row.deliveryDate) return '';
-    if (row.isFh) return '';
+const progressColor = percentage => {
+  const p = toProgressPercentage(percentage);
+  if (p < 30) return "#f56c6c";
+  if (p < 50) return "#e6a23c";
+  if (p < 80) return "#409eff";
+  return "#67c23a";
+};
 
-    const diff = row.deliveryDaysDiff;
-    if (diff === 15) {
-      return 'yellow';
-    } else if (diff === 10) {
-      return 'pink';
-    } else if (diff === 2) {
-      return 'purple';
-    } else if (diff < 2) {
-      return 'red';
-    }
-  };
+const tableRowClassName = ({ row }) => {
+  if (!row.deliveryDate || row.isFh) return "";
 
-  // 缁戝畾宸ヨ壓璺嚎寮规
-  const bindRouteDialogVisible = ref(false);
-  const bindRouteLoading = ref(false);
-  const bindRouteSaving = ref(false);
-  const routeOptions = ref([]);
-  const bindForm = reactive({
-    orderId: null,
-    routeId: null,
+  const diff = row.deliveryDaysDiff;
+  if (diff === 15) return "yellow";
+  if (diff === 10) return "pink";
+  if (diff === 2) return "purple";
+  if (diff < 2) return "red";
+  return "";
+};
+
+const bindRouteDialogVisible = ref(false);
+const bindRouteLoading = ref(false);
+const bindRouteSaving = ref(false);
+const routeOptions = ref([]);
+const bindProcessList = ref([]);
+const userOptions = ref([]);
+const userLoading = ref(false);
+const bindForm = reactive({
+  orderId: null,
+  productModelId: null,
+  routeId: null,
+  processUserList: [],
+});
+
+const resetBindProcessUsers = () => {
+  bindProcessList.value = [];
+  bindForm.processUserList = [];
+};
+
+const ensureUserOptions = () => {
+  if (userOptions.value.length || userLoading.value) return;
+
+  userLoading.value = true;
+  userListNoPageByTenantId()
+    .then(res => {
+      userOptions.value = res.data || [];
+    })
+    .finally(() => {
+      userLoading.value = false;
+    });
+};
+
+const createBindProcessUserList = list =>
+  list.map(item => ({
+    processId: item.id,
+    processName: item.name || item.processName || item.no || "",
+    userIds: [],
+    userNames: "",
+  }));
+
+const buildBindProcessRouteItems = () =>
+  bindProcessList.value.map((item, index) => {
+    const processUser = bindForm.processUserList[index] || {};
+    return {
+      productOrderId: bindForm.orderId,
+      productRouteId: bindForm.routeId,
+      processId: item.id,
+      productModelId: bindForm.productModelId,
+      dragSort: item.dragSort ?? index + 1,
+      isQuality: item.isQuality ?? false,
+      reportUserIds: Array.isArray(processUser.userIds) ? processUser.userIds.join(",") : "",
+    };
   });
 
-  const openBindRouteDialog = async row => {
-    bindForm.orderId = row.id;
-    bindForm.routeId = null;
-    bindRouteDialogVisible.value = true;
-    routeOptions.value = [];
-    if (!row.productModelId) {
-      proxy.$modal.msgWarning("褰撳墠璁㈠崟缂哄皯浜у搧鍨嬪彿锛屾棤娉曟煡璇㈠伐鑹鸿矾绾�");
-      bindRouteDialogVisible.value = false;
+const fetchBindProcessList = async routeId => {
+  if (!routeId) {
+    resetBindProcessUsers();
+    return;
+  }
+
+  try {
+    const res = await processList({ routeId });
+    bindProcessList.value = res.data || [];
+    bindForm.processUserList = createBindProcessUserList(bindProcessList.value);
+    ensureUserOptions();
+  } catch (error) {
+    console.error("鑾峰彇宸ュ簭鍒楄〃澶辫触", error);
+    proxy.$modal.msgError("鑾峰彇宸ュ簭鍒楄〃澶辫触");
+    resetBindProcessUsers();
+  }
+};
+
+const handleBindRouteChange = routeId => {
+  fetchBindProcessList(routeId);
+};
+
+const handleBindProcessUserChange = (index, userIds) => {
+  const selectedUsers = userOptions.value.filter(user => Array.isArray(userIds) && userIds.includes(user.userId));
+  bindForm.processUserList[index].userNames = selectedUsers.map(user => user.nickName).join(",");
+};
+
+const removeBindProcessItem = index => {
+  bindProcessList.value.splice(index, 1);
+  bindForm.processUserList.splice(index, 1);
+};
+
+const openBindRouteDialog = async row => {
+  bindForm.orderId = row.id;
+  bindForm.productModelId = row.productModelId ?? null;
+  bindForm.routeId = null;
+  bindForm.processUserList = [];
+  bindRouteDialogVisible.value = true;
+  routeOptions.value = [];
+  resetBindProcessUsers();
+
+  if (!row.productModelId) {
+    proxy.$modal.msgWarning("褰撳墠璁㈠崟缂哄皯浜у搧鍨嬪彿锛屾棤娉曟煡璇㈠伐鑹鸿矾绾�");
+    bindRouteDialogVisible.value = false;
+    return;
+  }
+
+  bindRouteLoading.value = true;
+  try {
+    const res = await listProcessRoute({ productModelId: row.productModelId });
+    routeOptions.value = res.data || [];
+  } catch (error) {
+    console.error("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触", error);
+    proxy.$modal.msgError("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触");
+  } finally {
+    bindRouteLoading.value = false;
+  }
+};
+
+const handleBindRouteConfirm = async () => {
+  if (!bindForm.routeId) {
+    proxy.$modal.msgWarning("璇烽�夋嫨宸ヨ壓璺嚎");
+    return;
+  }
+  if (!bindForm.processUserList.length) {
+    proxy.$modal.msgWarning("褰撳墠宸ヨ壓璺嚎涓嬫病鏈夊伐搴�");
+    return;
+  }
+  if (bindForm.processUserList.some(item => !Array.isArray(item.userIds) || item.userIds.length === 0)) {
+    proxy.$modal.msgWarning("璇蜂负姣忛亾宸ュ簭閫夋嫨鎶ュ伐浜哄憳");
+    return;
+  }
+
+  bindRouteSaving.value = true;
+  try {
+    await bindingRoute({
+      id: bindForm.orderId,
+      routeId: bindForm.routeId,
+      processRouteItems: buildBindProcessRouteItems(),
+      processUserList: bindForm.processUserList.map(item => ({
+        ...item,
+        userIds: item.userIds.join(","),
+      })),
+    });
+    proxy.$modal.msgSuccess("缁戝畾鎴愬姛");
+    bindRouteDialogVisible.value = false;
+    getList();
+  } catch (error) {
+    console.error("缁戝畾宸ヨ壓璺嚎澶辫触", error);
+    proxy.$modal.msgError("缁戝畾宸ヨ壓璺嚎澶辫触");
+  } finally {
+    bindRouteSaving.value = false;
+  }
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const pagination = obj => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  const params = { ...searchForm.value, ...page };
+  params.entryDate = undefined;
+
+  productOrderListPage(params)
+    .then(res => {
+      tableData.value = res.data.records;
+      page.total = res.data.total;
+    })
+    .finally(() => {
+      tableLoading.value = false;
+    });
+};
+
+const showRouteItemModal = async row => {
+  const orderId = row.id;
+  try {
+    const res = await getOrderProcessRouteMain(orderId);
+    const detail = res.data || {};
+    if (!detail.id) {
+      proxy.$modal.msgWarning("鏈壘鍒板叧鑱旂殑宸ヨ壓璺嚎");
       return;
     }
-    bindRouteLoading.value = true;
-    try {
-      const res = await listProcessRoute({ productModelId: row.productModelId });
-      routeOptions.value = res.data || [];
-    } catch (e) {
-      console.error("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触锛�", e);
-      proxy.$modal.msgError("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触");
-    } finally {
-      bindRouteLoading.value = false;
-    }
-  };
 
-  const handleBindRouteConfirm = async () => {
-    if (!bindForm.routeId) {
-      proxy.$modal.msgWarning("璇烽�夋嫨宸ヨ壓璺嚎");
-      return;
-    }
-    bindRouteSaving.value = true;
-    try {
-      await bindingRoute({
-        id: bindForm.orderId,
-        routeId: bindForm.routeId,
-      });
-      proxy.$modal.msgSuccess("缁戝畾鎴愬姛");
-      bindRouteDialogVisible.value = false;
-      getList();
-    } catch (e) {
-      console.error("缁戝畾宸ヨ壓璺嚎澶辫触锛�", e);
-      proxy.$modal.msgError("缁戝畾宸ヨ壓璺嚎澶辫触");
-    } finally {
-      bindRouteSaving.value = false;
-    }
-  };
-
-  // 鏌ヨ鍒楄〃
-  /** 鎼滅储鎸夐挳鎿嶄綔 */
-  const handleQuery = () => {
-    page.current = 1;
-    getList();
-  };
-  const pagination = obj => {
-    page.current = obj.page;
-    page.size = obj.limit;
-    getList();
-  };
-  const changeDaterange = value => {
-    if (value) {
-      searchForm.value.entryDateStart = value[0];
-      searchForm.value.entryDateEnd = value[1];
-    } else {
-      searchForm.value.entryDateStart = undefined;
-      searchForm.value.entryDateEnd = undefined;
-    }
-    handleQuery();
-  };
-  const getList = () => {
-    tableLoading.value = true;
-    // 鏋勯�犱竴涓柊鐨勫璞★紝涓嶅寘鍚玡ntryDate瀛楁
-    const params = { ...searchForm.value, ...page };
-    params.entryDate = undefined;
-    productOrderListPage(params)
-      .then(res => {
-        tableLoading.value = false;
-        tableData.value = res.data.records;
-        page.total = res.data.total;
-      })
-      .catch(() => {
-        tableLoading.value = false;
-      });
-  };
-
-  const showRouteItemModal = async row => {
-    const orderId = row.id;
-    try {
-      const res = await getOrderProcessRouteMain(orderId);
-      const data = res.data || {};
-      if (!data || !data.id) {
-        proxy.$modal.msgWarning("鏈壘鍒板叧鑱旂殑宸ヨ壓璺嚎");
-        return;
-      }
-      router.push({
-        path: "/productionManagement/processRouteItem",
-        query: {
-          id: data.id,
-          processRouteCode: data.processRouteCode || "",
-          productName: data.productName || "",
-          model: data.model || "",
-          bomNo: data.bomNo || "",
-          description: data.description || "",
-          orderId,
-          type: "order",
-        },
-      });
-    } catch (e) {
-      console.error("鑾峰彇宸ヨ壓璺嚎涓讳俊鎭け璐ワ細", e);
-      proxy.$modal.msgError("鑾峰彇宸ヨ壓璺嚎淇℃伅澶辫触");
-    }
-  };
-
-  const showProductStructure = row => {
     router.push({
-      path: "/productionManagement/productStructureDetail",
+      path: "/productionManagement/processRouteItem",
       query: {
-        id: row.id,
-        bomNo: row.bomNo || "",
-        productName: row.productCategory || "",
-        productModelName: row.specificationModel || "",
-        orderId: row.id,
+        id: detail.id,
+        processRouteCode: detail.processRouteCode || "",
+        productName: detail.productName || "",
+        model: detail.model || "",
+        bomNo: detail.bomNo || "",
+        description: detail.description || "",
+        orderId,
         type: "order",
       },
     });
-  };
+  } catch (error) {
+    console.error("鑾峰彇宸ヨ壓璺嚎淇℃伅澶辫触", error);
+    proxy.$modal.msgError("鑾峰彇宸ヨ壓璺嚎淇℃伅澶辫触");
+  }
+};
 
-  // 琛ㄦ牸閫夋嫨鏁版嵁
-  const handleSelectionChange = (selection) => {
-    selectedRows.value = selection;
-  };
-
-  const handleDelete = () => {
-    let ids = [];
-    if (selectedRows.value.length > 0) {
-      ids = selectedRows.value.map((item) => item.id);
-    } else {
-      proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
-      return;
-    }
-    ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
-      confirmButtonText: "纭",
-      cancelButtonText: "鍙栨秷",
-      type: "warning",
-    }).then(() => {
-      delProductOrder(ids).then((res) => {
-        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
-        getList();
-      });
-    }).catch(() => {
-      proxy.$modal.msg("宸插彇娑�");
-    });
-  };
-
-  // 瀵煎嚭
-  const handleOut = () => {
-    ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
-      confirmButtonText: "纭",
-      cancelButtonText: "鍙栨秷",
-      type: "warning",
-    })
-      .then(() => {
-        proxy.download("/productOrder/export", {...searchForm.value}, "鐢熶骇璁㈠崟.xlsx");
-      })
-      .catch(() => {
-        proxy.$modal.msg("宸插彇娑�");
-      });
-  };
-
-  const handleConfirmRoute = () => {};
-
-  onMounted(() => {
-    getList();
+const showProductStructure = row => {
+  router.push({
+    path: "/productionManagement/productStructureDetail",
+    query: {
+      id: row.id,
+      bomNo: row.bomNo || "",
+      productName: row.productCategory || "",
+      productModelName: row.specificationModel || "",
+      orderId: row.id,
+      type: "order",
+    },
   });
+};
+
+const handleSelectionChange = selection => {
+  selectedRows.value = selection;
+};
+
+const handleDelete = () => {
+  if (!selectedRows.value.length) {
+    proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+    return;
+  }
+
+  const ids = selectedRows.value.map(item => item.id);
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => delProductOrder(ids))
+    .then(() => {
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+      getList();
+    })
+    .catch(() => {
+      proxy.$modal.msg("宸插彇娑堝垹闄�");
+    });
+};
+
+const handleOut = () => {
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      proxy.download("/productOrder/export", { ...searchForm.value }, "鐢熶骇璁㈠崟.xlsx");
+    })
+    .catch(() => {
+      proxy.$modal.msg("宸插彇娑堝鍑�");
+    });
+};
+
+onMounted(() => {
+  getList();
+});
 </script>
 
 <style scoped lang="scss">
-.search_form{
+.search_form {
   align-items: start;
 }
 
-::v-deep .yellow {
-  background-color: #FAF0DE;
+:deep(.yellow) {
+  background-color: #faf0de;
 }
 
-::v-deep .pink {
-  background-color: #FAE1DE;
+:deep(.pink) {
+  background-color: #fae1de;
 }
 
-::v-deep .red {
+:deep(.red) {
   background-color: #f80202;
 }
 
-::v-deep .purple{
-  background-color: #F4DEFA;
+:deep(.purple) {
+  background-color: #f4defa;
+}
+
+.process-user-list {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+  padding: 14px;
+  border-radius: 12px;
+  background: #f7f9fc;
+  border: 1px solid #e8eef5;
+}
+
+.process-user-item {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr);
+  gap: 16px;
+  padding: 12px 14px;
+  border-radius: 10px;
+  background: #fff;
+  border: 1px solid #edf2f7;
+}
+
+.process-user-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+}
+
+.process-user-name {
+  color: #1f2d3d;
+  font-weight: 500;
+  line-height: 1.4;
+}
+
+.process-user-remove {
+  flex-shrink: 0;
+  padding: 0;
+}
+
+.process-user-select {
+  width: 100%;
+}
+
+@media (max-width: 768px) {
+  .process-user-item {
+    grid-template-columns: 1fr;
+    gap: 10px;
+  }
 }
 </style>

--
Gitblit v1.9.3