From f3f5a0c0db0b782401a9a0fcf6176312f420d941 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 25 三月 2026 14:01:19 +0800
Subject: [PATCH] 军泰伟业 1.不要树形结构。根据图纸编号为唯一索引添加产品并可以导入 2.要求可以上传图纸 3.产品可以绑定工艺路线 4.工艺路线改成由工序组成,新增编辑是工序可以挪动顺序 5.工序添加报工权限字段,可以多选人员

---
 src/views/productionManagement/processRoute/Form.vue                   |  376 +++++++++++++++++
 src/views/productionManagement/productionProcess/index.vue             |    5 
 /dev/null                                                              |  194 --------
 src/views/productionManagement/processRoute/index.vue                  |  142 +++---
 src/views/basicData/product/index.vue                                  |  180 +++++++
 src/views/productionManagement/processRoute/processRouteItem/index.vue |  122 +++++
 src/api/basicData/productProcess.js                                    |    8 
 src/views/productionManagement/productionProcess/Edit.vue              |  138 +++++
 src/views/productionManagement/productionProcess/New.vue               |  117 +++++
 9 files changed, 975 insertions(+), 307 deletions(-)

diff --git a/src/api/basicData/productProcess.js b/src/api/basicData/productProcess.js
index e0208fa..d2b23b6 100644
--- a/src/api/basicData/productProcess.js
+++ b/src/api/basicData/productProcess.js
@@ -8,3 +8,11 @@
     params: query
   })
 }
+
+// 鑾峰彇閮ㄩ棬鐢ㄦ埛鏍�
+export function listDeptUserTree() {
+  return request({
+    url: '/productProcess/listDeptUserTree',
+    method: 'get'
+  })
+}
diff --git a/src/views/basicData/product/index.vue b/src/views/basicData/product/index.vue
index 24a4659..48d2011 100644
--- a/src/views/basicData/product/index.vue
+++ b/src/views/basicData/product/index.vue
@@ -96,19 +96,17 @@
             clearable
           />
         </el-form-item>
-        <el-form-item label="瑙勬牸鍨嬪彿" prop="drawingNumber">
-          <el-input
-            v-model="modelForm.drawingNumber"
-            placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
-            clearable
-          />
-        </el-form-item>
         <el-form-item label="鍗曚綅" prop="unit">
-          <el-input
+          <el-select
             v-model="modelForm.unit"
-            placeholder="璇疯緭鍏ュ崟浣�"
+            placeholder="璇烽�夋嫨鍗曚綅"
             clearable
-          />
+            style="width: 100%"
+          >
+            <el-option label="浠�" value="浠�" />
+            <el-option label="濂�" value="濂�" />
+            <el-option label="鍙�" value="鍙�" />
+          </el-select>
         </el-form-item>
         <el-form-item label="浜у搧灞炴��" prop="productType">
           <el-select
@@ -121,6 +119,43 @@
             <el-option label="澶栬喘" :value="2" />
             <el-option label="濮斿" :value="3" />
           </el-select>
+        </el-form-item>
+        <el-form-item label="宸ヨ壓璺嚎" prop="routeId">
+          <el-select
+            v-model="modelForm.routeId"
+            placeholder="璇烽�夋嫨宸ヨ壓璺嚎"
+            clearable
+            style="width: 100%"
+            filterable
+          >
+            <el-option
+              v-for="item in processRouteList"
+              :key="item.id"
+              :label="item.processRouteName"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="涓婁紶鍥剧焊" prop="drawingFile">
+          <el-upload
+            v-model:file-list="drawingFileList"
+            :action="upload.url"
+            :headers="upload.headers"
+            :data="upload.data"
+            :on-success="handleDrawingUploadSuccess"
+            :on-remove="handleDrawingRemove"
+            :before-upload="handleDrawingBeforeUpload"
+            :limit="1"
+            accept=".pdf,.jpg,.jpeg,.png,.dwg"
+            list-type="picture-card"
+          >
+            <el-icon class="avatar-uploader-icon"><Plus /></el-icon>
+            <template #tip>
+              <div class="el-upload__tip">
+                鏀寔 pdf銆乯pg銆乯peg銆乸ng銆乨wg 鏍煎紡锛屽ぇ灏忎笉瓒呰繃 10MB
+              </div>
+            </template>
+          </el-upload>
         </el-form-item>
       </el-form>
     </FormDialog>
@@ -162,10 +197,10 @@
 </template>
 
 <script setup>
-import { ref, reactive } from "vue";
+import { ref, reactive, onMounted } from "vue";
 import { ElMessageBox } from "element-plus";
+import { Plus } from "@element-plus/icons-vue";
 import { getToken } from "@/utils/auth.js";
-import { FileUpload } from "@/components/Upload";
 import FormDialog from "@/components/Dialog/FormDialog.vue";
 import {
   addOrEditProductModel,
@@ -173,6 +208,7 @@
   productListPage,
   downloadTemplate,
 } from "@/api/basicData/product.js";
+import { listPage as getProcessRouteList } from "@/api/productionManagement/processRoute.js";
 import ImportExcel from "./ImportExcel/index.vue";
 
 const { proxy } = getCurrentInstance();
@@ -185,6 +221,14 @@
 const tableLoading = ref(false);
 const selectedRows = ref([]);
 const modelFormRef = ref();
+const processRouteList = ref([]);
+const drawingFileList = ref([]);
+
+const upload = reactive({
+  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
+  headers: { Authorization: "Bearer " + getToken() },
+  data: { type: 13 },
+});
 
 const queryForm = reactive({
   productName: "",
@@ -210,8 +254,8 @@
     minWidth: 150,
   },
   {
-    label: "瑙勬牸鍨嬪彿",
-    prop: "drawingNumber",
+    label: "宸ヨ壓璺嚎",
+    prop: "routeName",
     minWidth: 150,
   },
   {
@@ -252,8 +296,11 @@
     productName: "",
     model: "",
     unit: "",
-    drawingNumber: "",
     productType: null,
+    routeId: null,
+    drawingFile: "",
+    tempFileIds: [],
+    salesLedgerFiles: [],
   },
   modelRules: {
     productName: [
@@ -261,8 +308,7 @@
       { max: 50, message: "浜у搧鍚嶇О涓嶈兘瓒呰繃50涓瓧绗�", trigger: "blur" },
     ],
     model: [{ required: true, message: "璇疯緭鍏ュ浘绾哥紪鍙�", trigger: "blur" }],
-    unit: [{ required: true, message: "璇疯緭鍏ュ崟浣�", trigger: "blur" }],
-    drawingNumber: [],
+    unit: [{ required: true, message: "璇烽�夋嫨鍗曚綅", trigger: "change" }],
     productType: [{ required: true, message: "璇烽�夋嫨浜у搧灞炴��", trigger: "change" }],
   },
 });
@@ -334,10 +380,27 @@
   modelForm.value.model = "";
   modelForm.value.id = "";
   modelForm.value.unit = "";
-  modelForm.value.drawingNumber = "";
   modelForm.value.productType = null;
+  modelForm.value.routeId = null;
+  modelForm.value.drawingFile = "";
+  modelForm.value.tempFileIds = [];
+  modelForm.value.salesLedgerFiles = [];
+  drawingFileList.value = [];
   if (type === "edit") {
     modelForm.value = { ...data };
+    modelForm.value.tempFileIds = data.tempFileIds || [];
+    modelForm.value.salesLedgerFiles = data.salesLedgerFiles || [];
+    if (data.salesLedgerFiles && data.salesLedgerFiles.length > 0) {
+      drawingFileList.value = data.salesLedgerFiles.map(file => ({
+        name: file.name,
+        url: file.url
+      }));
+    } else if (data.drawingFile) {
+      drawingFileList.value = [{
+        name: data.drawingFile.split('/').pop(),
+        url: data.drawingFile
+      }];
+    }
   }
 };
 
@@ -429,7 +492,61 @@
   proxy.download("/basic/product/downloadTemplate", {}, "浜у搧瀵煎叆妯℃澘.xlsx");
 };
 
-getModelList();
+const getProcessRouteListData = () => {
+  getProcessRouteList({ current: 1, size: 1000 }).then((res) => {
+    processRouteList.value = res.data.records || [];
+  }).catch(() => {
+    processRouteList.value = [];
+  });
+};
+
+const handleDrawingBeforeUpload = (file) => {
+  const isAllowed = [
+    'application/pdf',
+    'image/jpeg',
+    'image/jpg',
+    'image/png',
+    'application/dwg'
+  ].includes(file.type) || file.name.endsWith('.dwg');
+  const isLt10M = file.size / 1024 / 1024 < 10;
+  
+  if (!isAllowed) {
+    proxy.$modal.msgError("鍙兘涓婁紶 pdf銆乯pg銆乯peg銆乸ng銆乨wg 鏍煎紡鐨勬枃浠讹紒");
+    return false;
+  }
+  if (!isLt10M) {
+    proxy.$modal.msgError("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃 10MB锛�");
+    return false;
+  }
+  return true;
+};
+
+const handleDrawingUploadSuccess = (response, file, fileList) => {
+  console.log('涓婁紶鎴愬姛鍝嶅簲', response);
+  console.log('response.data', response.data);
+  if (response.code === 200) {
+    modelForm.value.tempFileIds = [response.data?.tempId];
+    modelForm.value.salesLedgerFiles = [{
+      tempId: response.data?.tempId,
+      originalName: response.data?.originalName || file.name,
+      tempPath: response.data?.tempPath,
+      type: response.data?.type || 13
+    }];
+    proxy.$modal.msgSuccess("涓婁紶鎴愬姛");
+  } else {
+    proxy.$modal.msgError(response.msg || "涓婁紶澶辫触");
+  }
+};
+
+const handleDrawingRemove = (file) => {
+  modelForm.value.tempFileIds = [];
+  modelForm.value.salesLedgerFiles = [];
+};
+
+onMounted(() => {
+  getModelList();
+  getProcessRouteListData();
+});
 </script>
 
 <style scoped>
@@ -469,6 +586,31 @@
   margin-top: 16px;
 }
 
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 148px;
+  height: 148px;
+  text-align: center;
+  line-height: 148px;
+}
+
+:deep(.el-upload--picture-card) {
+  width: 148px;
+  height: 148px;
+}
+
+:deep(.el-upload-list__item) {
+  width: 148px;
+  height: 148px;
+}
+
+:deep(.el-upload__tip) {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 8px;
+}
+
 :deep(.el-dialog__body) {
   padding: 20px 24px;
 }
diff --git a/src/views/productionManagement/processRoute/Edit.vue b/src/views/productionManagement/processRoute/Edit.vue
deleted file mode 100644
index 0c0fe0f..0000000
--- a/src/views/productionManagement/processRoute/Edit.vue
+++ /dev/null
@@ -1,252 +0,0 @@
-<template>
-  <div>
-    <el-dialog
-        v-model="isShow"
-        title="缂栬緫宸ヨ壓璺嚎"
-        width="400"
-        @close="closeModal"
-    >
-      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
-        <el-form-item
-            label="浜у搧鍚嶇О"
-            prop="productModelId"
-            :rules="[
-                {
-                required: true,
-                message: '璇烽�夋嫨浜у搧',
-                trigger: 'change',
-              }
-            ]"
-        >
-          <el-button type="primary" @click="showProductSelectDialog = true">
-            {{ formState.productName && formState.productModelName 
-              ? `${formState.productName} - ${formState.productModelName}` 
-              : '閫夋嫨浜у搧' }}
-          </el-button>
-        </el-form-item>
-
-        <el-form-item
-            label="BOM"
-            prop="bomId"
-            :rules="[
-                {
-                required: true,
-                message: '璇烽�夋嫨BOM',
-                trigger: 'change',
-              }
-            ]"
-        >
-          <el-select
-              v-model="formState.bomId"
-              placeholder="璇烽�夋嫨BOM"
-              clearable
-              :disabled="!formState.productModelId || bomOptions.length === 0"
-              style="width: 100%"
-          >
-            <el-option
-                v-for="item in bomOptions"
-                :key="item.id"
-                :label="item.bomNo || `BOM-${item.id}`"
-                :value="item.id"
-            />
-          </el-select>
-        </el-form-item>
-
-        <el-form-item label="澶囨敞" prop="description">
-          <el-input v-model="formState.description" type="textarea" />
-        </el-form-item>
-      </el-form>
-      
-      <!-- 浜у搧閫夋嫨寮圭獥 -->
-      <ProductSelectDialog
-          v-model="showProductSelectDialog"
-          @confirm="handleProductSelect"
-          single
-      />
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="handleSubmit">纭</el-button>
-          <el-button @click="closeModal">鍙栨秷</el-button>
-        </div>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-import {ref, computed, getCurrentInstance, onMounted, nextTick, watch} from "vue";
-import {update} from "@/api/productionManagement/processRoute.js";
-import {getByModel} from "@/api/productionManagement/productBom.js";
-import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
-
-const props = defineProps({
-  visible: {
-    type: Boolean,
-    required: true,
-  },
-
-  record: {
-    type: Object,
-    required: true,
-  }
-});
-
-const emit = defineEmits(['update:visible', 'completed']);
-
-// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
-const formState = ref({
-  productId: undefined,
-  productModelId: undefined,
-  productName: "",
-  productModelName: "",
-  bomId: undefined,
-  description: '',
-});
-
-const isShow = computed({
-  get() {
-    return props.visible;
-  },
-  set(val) {
-    emit('update:visible', val);
-  },
-});
-
-const showProductSelectDialog = ref(false);
-const bomOptions = ref([]);
-
-let { proxy } = getCurrentInstance()
-
-const closeModal = () => {
-  isShow.value = false;
-};
-
-// 璁剧疆琛ㄥ崟鏁版嵁
-const setFormData = () => {
-  if (props.record) {
-    formState.value = {
-      ...props.record,
-      productId: props.record.productId,
-      productModelId: props.record.productModelId,
-      productName: props.record.productName || "",
-      // 娉ㄦ剰锛歳ecord涓殑瀛楁鏄痬odel锛岄渶瑕佹槧灏勫埌productModelName
-      productModelName: props.record.model || props.record.productModelName || "",
-      bomId: props.record.bomId,
-      description: props.record.description || '',
-    };
-    // 濡傛灉鏈変骇鍝佸瀷鍙稩D锛屽姞杞紹OM鍒楄〃
-    if (props.record.productModelId) {
-      loadBomList(props.record.productModelId);
-    }
-  }
-}
-
-// 鍔犺浇BOM鍒楄〃
-const loadBomList = async (productModelId) => {
-  if (!productModelId) {
-    bomOptions.value = [];
-    return;
-  }
-  try {
-    const res = await getByModel(productModelId);
-    // 澶勭悊杩斿洖鐨凚OM鏁版嵁锛氬彲鑳芥槸鏁扮粍銆佸璞℃垨鍖呭惈data瀛楁
-    let bomList = [];
-    if (Array.isArray(res)) {
-      bomList = res;
-    } else if (res && res.data) {
-      bomList = Array.isArray(res.data) ? res.data : [res.data];
-    } else if (res && typeof res === 'object') {
-      bomList = [res];
-    }
-    bomOptions.value = bomList;
-  } catch (error) {
-    console.error("鍔犺浇BOM鍒楄〃澶辫触锛�", error);
-    bomOptions.value = [];
-  }
-};
-
-// 浜у搧閫夋嫨澶勭悊
-const handleProductSelect = async (products) => {
-  if (products && products.length > 0) {
-    const product = products[0];
-    // 鍏堟煡璇OM鍒楄〃锛堝繀閫夛級
-    try {
-      const res = await getByModel(product.id);
-      // 澶勭悊杩斿洖鐨凚OM鏁版嵁锛氬彲鑳芥槸鏁扮粍銆佸璞℃垨鍖呭惈data瀛楁
-      let bomList = [];
-      if (Array.isArray(res)) {
-        bomList = res;
-      } else if (res && res.data) {
-        bomList = Array.isArray(res.data) ? res.data : [res.data];
-      } else if (res && typeof res === 'object') {
-        bomList = [res];
-      }
-      
-      if (bomList.length > 0) {
-        formState.value.productModelId = product.id;
-        formState.value.productName = product.productName;
-        formState.value.productModelName = product.model;
-        // 濡傛灉褰撳墠閫夋嫨鐨凚OM涓嶅湪鏂板垪琛ㄤ腑锛屽垯閲嶇疆BOM閫夋嫨
-        const currentBomExists = bomList.some(bom => bom.id === formState.value.bomId);
-        if (!currentBomExists) {
-          formState.value.bomId = undefined;
-        }
-        bomOptions.value = bomList;
-        showProductSelectDialog.value = false;
-        // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
-        proxy.$refs["formRef"]?.validateField('productModelId');
-      } else {
-        proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
-      }
-    } catch (error) {
-      // 濡傛灉鎺ュ彛杩斿洖404鎴栧叾浠栭敊璇紝璇存槑娌℃湁BOM
-      proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
-    }
-  }
-};
-
-const handleSubmit = () => {
-  proxy.$refs["formRef"].validate(valid => {
-    if (valid) {
-      // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰BOM
-      if (!formState.value.productModelId) {
-        proxy.$modal.msgError("璇烽�夋嫨浜у搧");
-        return;
-      }
-      if (!formState.value.bomId) {
-        proxy.$modal.msgError("璇烽�夋嫨BOM");
-        return;
-      }
-      update(formState.value).then(res => {
-        // 鍏抽棴妯℃�佹
-        isShow.value = false;
-        // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
-        emit('completed');
-        proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
-      })
-    }
-  })
-};
-
-defineExpose({
-  closeModal,
-  handleSubmit,
-  isShow,
-});
-
-
-// 鐩戝惉寮圭獥鎵撳紑锛屽垵濮嬪寲琛ㄥ崟鏁版嵁
-watch(() => props.visible, (visible) => {
-  if (visible && props.record) {
-    nextTick(() => {
-      setFormData();
-    });
-  }
-}, { immediate: true });
-
-onMounted(() => {
-  if (props.visible && props.record) {
-    setFormData();
-  }
-});
-</script>
diff --git a/src/views/productionManagement/processRoute/Form.vue b/src/views/productionManagement/processRoute/Form.vue
new file mode 100644
index 0000000..65fba7e
--- /dev/null
+++ b/src/views/productionManagement/processRoute/Form.vue
@@ -0,0 +1,376 @@
+<template>
+  <div>
+    <el-dialog
+        v-model="isShow"
+        :title="isEdit ? '缂栬緫宸ヨ壓璺嚎' : '鍒涘缓宸ヨ壓璺嚎'"
+        width="900px"
+        @close="closeModal"
+    >
+      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+        <el-form-item label="宸ヨ壓璺嚎缂栧彿">
+          <el-input v-model="formState.processRouteCode" placeholder="璇疯緭鍏ワ紝蹇界暐灏嗚嚜鍔ㄧ敓鎴�" clearable />
+        </el-form-item>
+
+        <el-form-item
+            label="宸ヨ壓璺嚎鍚嶇О"
+            prop="processRouteName"
+            :rules="[
+                {
+                required: true,
+                message: '璇疯緭鍏ュ伐鑹鸿矾绾垮悕绉�',
+                trigger: 'blur',
+              }
+            ]"
+        >
+          <el-input v-model="formState.processRouteName" placeholder="璇峰~鍐�" clearable />
+        </el-form-item>
+
+        <div class="section-title">宸ュ簭鍒楄〃</div>
+        
+        <div class="table-actions">
+          <el-button type="primary" link @click="addRow">
+            <el-icon><Plus /></el-icon> 娣诲姞涓�琛�
+          </el-button>
+        </div>
+
+        <el-table
+          ref="tableRef"
+          :data="formState.processRouteItems"
+          border
+          style="width: 100%"
+          class="process-table"
+          row-key="tempId"
+        >
+          <el-table-column label="鎷栨嫿" width="60" align="center">
+            <template #default>
+              <el-icon class="drag-handle"><Rank /></el-icon>
+            </template>
+          </el-table-column>
+          <el-table-column label="宸ュ簭" prop="processId" min-width="200">
+            <template #header>
+              <span class="required">宸ュ簭</span>
+            </template>
+            <template #default="scope">
+              <el-select 
+                v-model="scope.row.processId" 
+                placeholder="璇烽�夋嫨" 
+                clearable
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="item in processOptions"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column label="鏄惁璐ㄦ" prop="isQuality" width="100" align="center">
+            <template #default="scope">
+              <el-switch v-model="scope.row.isQuality" />
+            </template>
+          </el-table-column>
+          <el-table-column label="鎿嶄綔" width="80" align="center" fixed="right">
+            <template #default="scope">
+              <el-button type="danger" link @click="deleteRow(scope.$index)">
+                <el-icon><Delete /></el-icon>
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <el-empty v-if="formState.processRouteItems.length === 0" description="鏆傛棤鏁版嵁" />
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSubmit">纭</el-button>
+          <el-button @click="closeModal">鍙栨秷</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import {ref, computed, getCurrentInstance, onMounted, nextTick, watch, onUnmounted} from "vue";
+import {add, update} from "@/api/productionManagement/processRoute.js";
+import {findProcessRouteItemList} from "@/api/productionManagement/processRouteItem.js";
+import {processList} from "@/api/productionManagement/productionProcess.js";
+import {Plus, Delete, Rank} from '@element-plus/icons-vue';
+import Sortable from 'sortablejs';
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  }
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+const isEdit = computed(() => {
+  return formState.value && formState.value.id;
+});
+
+const isShow = computed({
+  get() {
+    return props.visible;
+  },
+  set(val) {
+    emit('update:visible', val);
+  },
+});
+
+const processOptions = ref([]);
+
+let { proxy } = getCurrentInstance();
+
+const tableRef = ref(null);
+let sortable = null;
+
+let tempIdCounter = 0;
+
+const formState = ref({
+  id: undefined,
+  processRouteCode: '',
+  processRouteName: '',
+  processRouteItems: [],
+});
+
+const initSortable = () => {
+  if (sortable) {
+    sortable.destroy();
+    sortable = null;
+  }
+  
+  nextTick(() => {
+    if (tableRef.value) {
+      const table = tableRef.value.$el.querySelector('.el-table__body-wrapper tbody');
+      if (table) {
+        sortable = Sortable.create(table, {
+          animation: 150,
+          handle: '.drag-handle',
+          ghostClass: 'sortable-ghost',
+          onEnd: (evt) => {
+            const { oldIndex, newIndex } = evt;
+            if (oldIndex !== undefined && newIndex !== undefined && oldIndex !== newIndex) {
+              const item = formState.value.processRouteItems.splice(oldIndex, 1)[0];
+              formState.value.processRouteItems.splice(newIndex, 0, item);
+            }
+          }
+        });
+      }
+    }
+  });
+};
+
+const getProcessList = () => {
+  processList({}).then(res => {
+    processOptions.value = res.data || [];
+  }).catch(err => {
+    console.error("鑾峰彇宸ュ簭鍒楄〃澶辫触锛�", err);
+  });
+};
+
+const closeModal = () => {
+  formState.value = {
+    id: undefined,
+    processRouteCode: '',
+    processRouteName: '',
+    processRouteItems: [],
+  };
+  isShow.value = false;
+};
+
+const setFormData = async () => {
+  if (isEdit.value) {
+    formState.value = {
+      id: props.record.id,
+      processRouteCode: props.record.processRouteCode || '',
+      processRouteName: props.record.processRouteName || '',
+      processRouteItems: (props.record.processRouteItems || []).map((item, index) => ({
+        tempId: item.id || `temp_${tempIdCounter++}`,
+        processId: item.processId,
+        id: item.id,
+        isQuality: item.isQuality !== undefined ? item.isQuality : false,
+        dragSort: index + 1,
+      })),
+    };
+  } else {
+    formState.value = {
+      id: undefined,
+      processRouteCode: '',
+      processRouteName: '',
+      processRouteItems: [],
+    };
+  }
+}
+
+const addRow = () => {
+  formState.value.processRouteItems.push({
+    tempId: `temp_${tempIdCounter++}`,
+    processId: undefined,
+    isQuality: false,
+  });
+  nextTick(() => {
+    initSortable();
+  });
+};
+
+const deleteRow = (index) => {
+  formState.value.processRouteItems.splice(index, 1);
+  nextTick(() => {
+    initSortable();
+  });
+};
+
+const handleSubmit = () => {
+  proxy.$refs["formRef"].validate(valid => {
+    if (valid) {
+      if (formState.value.processRouteItems.length === 0) {
+        proxy.$modal.msgError("璇疯嚦灏戞坊鍔犱竴涓伐搴�");
+        return;
+      }
+      
+      for (let i = 0; i < formState.value.processRouteItems.length; i++) {
+        const row = formState.value.processRouteItems[i];
+        if (!row.processId) {
+          proxy.$modal.msgError(`绗�${i + 1}琛岋細璇烽�夋嫨宸ュ簭`);
+          return;
+        }
+      }
+
+      const submitData = {
+        id: formState.value.id,
+        processRouteCode: formState.value.processRouteCode,
+        processRouteName: formState.value.processRouteName,
+        processRouteItems: formState.value.processRouteItems.map((item, index) => ({
+          id: item.id,
+          routeId: formState.value.id,
+          processId: item.processId,
+          isQuality: item.isQuality,
+          dragSort: index + 1,
+        })),
+      };
+
+      const apiCall = isEdit.value ? update(submitData) : add(submitData);
+
+      apiCall.then(res => {
+        isShow.value = false;
+        emit('completed');
+        proxy.$modal.msgSuccess(isEdit.value ? "缂栬緫鎴愬姛" : "鏂板鎴愬姛");
+      });
+    }
+  });
+};
+
+// 鐩戝惉 visible 鍙樺寲
+watch(() => props.visible, (visible) => {
+  if (visible) {
+    nextTick(() => {
+      initSortable();
+    });
+  }
+});
+
+const setData = async (row) => {
+  if (row) {
+    formState.value = {
+      id: row.id,
+      processRouteCode: row.processRouteCode || '',
+      processRouteName: row.processRouteName || '',
+      processRouteItems: [],
+    };
+    
+    const res = await findProcessRouteItemList({ routeId: row.id });
+    if (res.data && Array.isArray(res.data)) {
+      formState.value.processRouteItems = res.data.map((item, index) => ({
+        tempId: item.id || `temp_${tempIdCounter++}`,
+        processId: item.processId,
+        id: item.id,
+        isQuality: item.isQuality !== undefined ? item.isQuality : false,
+        dragSort: index + 1,
+      }));
+    }
+    
+    nextTick(() => {
+      initSortable();
+    });
+  } else {
+    formState.value = {
+      id: undefined,
+      processRouteCode: '',
+      processRouteName: '',
+      processRouteItems: [],
+    };
+    
+    nextTick(() => {
+      initSortable();
+    });
+  }
+};
+
+onMounted(() => {
+  getProcessList();
+});
+
+onUnmounted(() => {
+  if (sortable) {
+    sortable.destroy();
+    sortable = null;
+  }
+});
+
+defineExpose({
+  closeModal,
+  handleSubmit,
+  isShow,
+  setData,
+});
+</script>
+
+<style scoped>
+.section-title {
+  font-size: 14px;
+  font-weight: bold;
+  margin: 20px 0 10px 0;
+  color: #333;
+}
+
+.table-actions {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 10px;
+}
+
+.process-table {
+  margin-bottom: 20px;
+}
+
+.required::before {
+  content: '*';
+  color: #f56c6c;
+  margin-right: 4px;
+}
+
+:deep(.el-dialog__body) {
+  padding-top: 10px;
+}
+
+.sortable-ghost {
+  opacity: 0.4;
+  background-color: #f5f7fa;
+}
+
+.drag-handle {
+  cursor: move;
+  font-size: 18px;
+  color: #909399;
+}
+
+.drag-handle:hover {
+  color: #409eff;
+}
+</style>
diff --git a/src/views/productionManagement/processRoute/New.vue b/src/views/productionManagement/processRoute/New.vue
deleted file mode 100644
index 62c6873..0000000
--- a/src/views/productionManagement/processRoute/New.vue
+++ /dev/null
@@ -1,194 +0,0 @@
-<template>
-  <div>
-    <el-dialog
-        v-model="isShow"
-        title="鏂板宸ヨ壓璺嚎"
-        width="400"
-        @close="closeModal"
-    >
-      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
-        <el-form-item
-            label="浜у搧鍚嶇О"
-            prop="productModelId"
-            :rules="[
-                {
-                required: true,
-                message: '璇烽�夋嫨浜у搧',
-                trigger: 'change',
-              }
-            ]"
-        >
-          <el-button type="primary" @click="showProductSelectDialog = true">
-            {{ formState.productName && formState.productModelName 
-              ? `${formState.productName} - ${formState.productModelName}` 
-              : '閫夋嫨浜у搧' }}
-          </el-button>
-        </el-form-item>
-
-        <el-form-item
-            label="BOM"
-            prop="bomId"
-            :rules="[
-                {
-                required: true,
-                message: '璇烽�夋嫨BOM',
-                trigger: 'change',
-              }
-            ]"
-        >
-          <el-select
-              v-model="formState.bomId"
-              placeholder="璇烽�夋嫨BOM"
-              clearable
-              :disabled="!formState.productModelId || bomOptions.length === 0"
-              style="width: 100%"
-          >
-            <el-option
-                v-for="item in bomOptions"
-                :key="item.id"
-                :label="item.bomNo || `BOM-${item.id}`"
-                :value="item.id"
-            />
-          </el-select>
-        </el-form-item>
-
-        <el-form-item label="澶囨敞" prop="description">
-          <el-input v-model="formState.description" type="textarea" />
-        </el-form-item>
-      </el-form>
-      
-      <!-- 浜у搧閫夋嫨寮圭獥 -->
-      <ProductSelectDialog
-          v-model="showProductSelectDialog"
-          @confirm="handleProductSelect"
-          single
-      />
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="handleSubmit">纭</el-button>
-          <el-button @click="closeModal">鍙栨秷</el-button>
-        </div>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-import {ref, computed, getCurrentInstance} from "vue";
-import {add} from "@/api/productionManagement/processRoute.js";
-import {getByModel} from "@/api/productionManagement/productBom.js";
-import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
-
-const props = defineProps({
-  visible: {
-    type: Boolean,
-    required: true,
-  },
-});
-
-const emit = defineEmits(['update:visible', 'completed']);
-
-// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
-const formState = ref({
-  productId: undefined,
-  productModelId: undefined,
-  productName: "",
-  productModelName: "",
-  bomId: undefined,
-  description: '',
-});
-
-const isShow = computed({
-  get() {
-    return props.visible;
-  },
-  set(val) {
-    emit('update:visible', val);
-  },
-});
-
-const showProductSelectDialog = ref(false);
-const bomOptions = ref([]);
-
-let { proxy } = getCurrentInstance()
-
-const closeModal = () => {
-  // 閲嶇疆琛ㄥ崟鏁版嵁
-  formState.value = {
-    productId: undefined,
-    productModelId: undefined,
-    productName: "",
-    productModelName: "",
-    bomId: undefined,
-    description: '',
-  };
-  bomOptions.value = [];
-  isShow.value = false;
-};
-
-// 浜у搧閫夋嫨澶勭悊
-const handleProductSelect = async (products) => {
-  if (products && products.length > 0) {
-    const product = products[0];
-    // 鍏堟煡璇OM鍒楄〃锛堝繀閫夛級
-    try {
-      const res = await getByModel(product.id);
-      // 澶勭悊杩斿洖鐨凚OM鏁版嵁锛氬彲鑳芥槸鏁扮粍銆佸璞℃垨鍖呭惈data瀛楁
-      let bomList = [];
-      if (Array.isArray(res)) {
-        bomList = res;
-      } else if (res && res.data) {
-        bomList = Array.isArray(res.data) ? res.data : [res.data];
-      } else if (res && typeof res === 'object') {
-        bomList = [res];
-      }
-      
-      if (bomList.length > 0) {
-        formState.value.productModelId = product.id;
-        formState.value.productName = product.productName;
-        formState.value.productModelName = product.model;
-        formState.value.bomId = undefined; // 閲嶇疆BOM閫夋嫨
-        bomOptions.value = bomList;
-        showProductSelectDialog.value = false;
-        // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
-        proxy.$refs["formRef"]?.validateField('productModelId');
-      } else {
-        proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
-      }
-    } catch (error) {
-      // 濡傛灉鎺ュ彛杩斿洖404鎴栧叾浠栭敊璇紝璇存槑娌℃湁BOM
-      proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
-    }
-  }
-};
-
-const handleSubmit = () => {
-  proxy.$refs["formRef"].validate(valid => {
-    if (valid) {
-      // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰BOM
-      if (!formState.value.productModelId) {
-        proxy.$modal.msgError("璇烽�夋嫨浜у搧");
-        return;
-      }
-      if (!formState.value.bomId) {
-        proxy.$modal.msgError("璇烽�夋嫨BOM");
-        return;
-      }
-      add(formState.value).then(res => {
-        // 鍏抽棴妯℃�佹
-        isShow.value = false;
-        // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
-        emit('completed');
-        proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
-      })
-    }
-  })
-};
-
-
-defineExpose({
-  closeModal,
-  handleSubmit,
-  isShow,
-});
-</script>
diff --git a/src/views/productionManagement/processRoute/index.vue b/src/views/productionManagement/processRoute/index.vue
index 9a4950d..a4a9d3f 100644
--- a/src/views/productionManagement/processRoute/index.vue
+++ b/src/views/productionManagement/processRoute/index.vue
@@ -2,13 +2,25 @@
   <div class="app-container">
     <div class="search_form">
       <el-form :model="searchForm" :inline="true">
-        <el-form-item label="瑙勬牸鍚嶇О:">
-          <el-input v-model="searchForm.model" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
-                    style="width: 200px;"
-                    @change="handleQuery" />
+        <el-form-item label="宸ヨ壓璺嚎缂栧彿">
+          <el-input v-model="searchForm.processRouteCode" placeholder="璇疯緭鍏ュ伐鑹鸿矾绾跨紪鍙�" clearable style="width: 200px;" />
+        </el-form-item>
+        <el-form-item label="宸ヨ壓璺嚎鍚嶇О">
+          <el-input v-model="searchForm.processRouteName" placeholder="璇疯緭鍏ュ伐鑹鸿矾绾垮悕绉�" clearable style="width: 200px;" />
+        </el-form-item>
+        <el-form-item label="宸ュ簭鍚嶇О">
+          <el-select v-model="searchForm.processName" placeholder="璇烽�夋嫨宸ュ簭" clearable style="width: 200px;">
+            <el-option
+              v-for="item in processOptions"
+              :key="item.id"
+              :label="item.name"
+              :value="item.name"
+            />
+          </el-select>
         </el-form-item>
         <el-form-item>
           <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+          <el-button @click="resetQuery">閲嶇疆</el-button>
         </el-form-item>
       </el-form>
     </div>
@@ -29,87 +41,65 @@
           :total="page.total"
       />
     </div>
-    <new-process
-        v-if="isShowNewModal"
-        v-model:visible="isShowNewModal"
-        @completed="getList"
-    />
-
-    <edit-process
-        v-if="isShowEditModal"
-        v-model:visible="isShowEditModal"
-        :record="record"
-        @completed="getList"
-    />
-
-    <route-item-form
-        v-if="isShowItemModal"
-        v-model:visible="isShowItemModal"
-        :record="record"
+    <process-form
+        v-if="isShowFormModal"
+        ref="formRef"
+        v-model:visible="isShowFormModal"
         @completed="getList"
     />
   </div>
 </template>
 
 <script setup>
-import {onMounted, ref} from "vue";
-import NewProcess from "@/views/productionManagement/processRoute/New.vue";
-import EditProcess from "@/views/productionManagement/processRoute/Edit.vue";
-import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue";
+import {onMounted, ref, nextTick} from "vue";
+import ProcessForm from "@/views/productionManagement/processRoute/Form.vue";
 import {listPage, del} from "@/api/productionManagement/processRoute.js";
-import { useRouter } from 'vue-router'
+import {processList} from "@/api/productionManagement/productionProcess.js";
 
-const router = useRouter()
 const data = reactive({
   searchForm: {
-    model: "",
+    processRouteCode: '',
+    processRouteName: '',
+    processName: undefined,
   },
 });
 const { searchForm } = toRefs(data);
+
+const processOptions = ref([]);
 const tableColumn = ref([
   {
     label: "宸ヨ壓璺嚎缂栧彿",
     prop: "processRouteCode",
   },
   {
-    label: "浜у搧鍚嶇О",
-    prop: "productName",
-  },
-	{
-		label: "鍥剧焊缂栧彿",
-		prop: "model",
-	},
-  {
-    label: "瑙勬牸鍚嶇О",
-    prop: "drawingNumber",
+    label: "宸ヨ壓璺嚎鍚嶇О",
+    prop: "processRouteName",
   },
   {
-    label: "BOM缂栧彿",
-    prop: "bomNo",
+    label: "宸ュ簭鍒楄〃",
+    prop: "processName",
+    minWidth: 300,
   },
   {
-    label: "鎻忚堪",
-    prop: "description",
+    label: "鍒涘缓浜�",
+    prop: "createBy",
+  },
+  {
+    label: "鍒涘缓鏃堕棿",
+    prop: "createTime",
   },
   {
     dataType: "action",
     label: "鎿嶄綔",
     align: "center",
     fixed: "right",
-    width: 280,
+    width: 150,
     operation: [
       {
         name: "缂栬緫",
         type: "text",
         clickFun: (row) => {
           showEditModal(row);
-        }
-      },
-      {
-        name: "璺嚎椤圭洰",
-        type: "text",
-        clickFun: (row) => {
-          showItemModal(row);
         }
       }
     ]
@@ -118,10 +108,9 @@
 const tableData = ref([]);
 const selectedRows = ref([]);
 const tableLoading = ref(false);
-const isShowNewModal = ref(false);
-const isShowEditModal = ref(false);
-const isShowItemModal = ref(false);
+const isShowFormModal = ref(false);
 const record = ref({});
+const formRef = ref(null);
 const page = reactive({
   current: 1,
   size: 100,
@@ -132,6 +121,17 @@
 // 鏌ヨ鍒楄〃
 /** 鎼滅储鎸夐挳鎿嶄綔 */
 const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  searchForm.value = {
+    processRouteCode: '',
+    processRouteName: '',
+    processName: undefined,
+  };
   page.current = 1;
   getList();
 };
@@ -162,28 +162,14 @@
 
 // 鎵撳紑鏂板寮规
 const showNewModal = () => {
-  isShowNewModal.value = true
+  isShowFormModal.value = true;
+  record.value = {};
 };
 
-const showEditModal = (row) => {
-  isShowEditModal.value = true
-  record.value = row
-};
-
-const showItemModal = (row) => {
-  router.push({
-    path: '/productionManagement/processRouteItem',
-    query: {
-      id: row.id,
-      processRouteCode: row.processRouteCode || '',
-      drawingNumber: row.drawingNumber || "",
-      productName: row.productName || '',
-      model: row.model || '',
-      bomNo: row.bomNo || '',
-      description: row.description || '',
-      type: 'route',
-    }
-  })
+const showEditModal = async (row) => {
+  isShowFormModal.value = true;
+  await nextTick();
+  await formRef.value && formRef.value.setData({ id: row.id, processRouteCode: row.processRouteCode, processRouteName: row.processRouteName });
 };
 
 // 鍒犻櫎
@@ -202,8 +188,18 @@
 }
 
 onMounted(() => {
+  getProcessList();
   getList();
 });
+
+// 鑾峰彇宸ュ簭鍒楄〃
+const getProcessList = () => {
+  processList({}).then(res => {
+    processOptions.value = res.data || [];
+  }).catch(err => {
+    console.error("鑾峰彇宸ュ簭鍒楄〃澶辫触锛�", err);
+  });
+};
 </script>
 
 <style scoped></style>
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 0f0be43..65035ec 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -96,6 +96,11 @@
           {{scope.row.isQuality ? "鏄�" : "鍚�"}}
         </template>
       </el-table-column>
+      <el-table-column label="鎶ュ伐鏉冮檺" prop="userPower" min-width="200">
+        <template #default="scope">
+          {{ scope.row.userPower || '-' }}
+        </template>
+      </el-table-column>
       <el-table-column label="鎿嶄綔" align="center" fixed="right" width="150">
         <template #default="scope">
           <el-button type="primary" link size="small" @click="handleEdit(scope.row)" :disabled="scope.row.isComplete">缂栬緫</el-button>
@@ -207,7 +212,22 @@
         </el-form-item>
 
         <el-form-item label="鏄惁璐ㄦ" prop="isQuality">
-          <el-switch v-model="form.isQuality" :active-value="true" inactive-value="false"/>
+          <el-switch v-model="form.isQuality" :active-value="true" :inactive-value="false"/>
+        </el-form-item>
+        <el-form-item label="鎶ュ伐鏉冮檺" prop="userPower" :rules="[{ required: true, message: '璇烽�夋嫨鎶ュ伐鏉冮檺', trigger: 'change' }]">
+          <el-tree-select
+            v-model="form.userPower"
+            :data="staffList"
+            :props="treeProps"
+            placeholder="璇烽�夋嫨浜哄憳"
+            multiple
+            show-checkbox
+            collapse-tags
+            collapse-tags-tooltip
+            style="width: 100%"
+            node-key="id"
+            :render-after-expand="false"
+          />
         </el-form-item>
       </el-form>
 
@@ -232,6 +252,7 @@
 import { findProcessRouteItemList, addOrUpdateProcessRouteItem, sortProcessRouteItem, batchDeleteProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
 import { findProductProcessRouteItemList, deleteRouteItem, addRouteItem, addOrUpdateProductProcessRouteItem, sortRouteItem } from "@/api/productionManagement/productProcessRoute.js";
 import { processList } from "@/api/productionManagement/productionProcess.js";
+import { listDeptUserTree } from "@/api/basicData/productProcess.js";
 import { useRoute } from 'vue-router'
 import { ElMessageBox } from 'element-plus'
 import Sortable from 'sortablejs'
@@ -263,6 +284,13 @@
 
 const processOptions = ref([]);
 const showProductSelectDialog = ref(false);
+const staffList = ref([]);
+
+const treeProps = {
+  label: 'label',
+  children: 'children',
+};
+
 let tableSortable = null;
 let cardSortable = null;
 
@@ -284,6 +312,7 @@
   model: "",
   unit: "",
   isQuality: false,
+  userPower: [],
 });
 
 const rules = {
@@ -356,6 +385,12 @@
 // 缂栬緫
 const handleEdit = (row) => {
   operationType.value = 'edit';
+  const userPowerNames = row.userPower ? row.userPower.split(',') : [];
+  const userPowerIds = userPowerNames.map(name => {
+    const user = findUserByName(name);
+    return user ? user.id : null;
+  }).filter(id => id !== null);
+  
   form.value = {
     id: row.id,
     routeId: routeId.value,
@@ -365,8 +400,25 @@
     model: row.model || "",
     unit: row.unit || "",
     isQuality: row.isQuality,
+    userPower: userPowerIds,
   };
   dialogVisible.value = true;
+};
+
+const findUserByName = (name) => {
+  const findInTree = (nodes) => {
+    for (const node of nodes) {
+      if (node.isUser && node.label === name) {
+        return node;
+      }
+      if (node.children && node.children.length > 0) {
+        const found = findInTree(node.children);
+        if (found) return found;
+      }
+    }
+    return null;
+  };
+  return findInTree(staffList.value);
 };
 
 // 鍒犻櫎
@@ -415,6 +467,8 @@
     if (valid) {
       submitLoading.value = true;
       
+      const userPowerNames = getAllUserNamesFromSelection(form.value.userPower);
+      
       if (operationType.value === 'add') {
         // 鏂板锛氫紶鍗曚釜瀵硅薄锛屽寘鍚玠ragSort瀛楁
         // dragSort = 褰撳墠鍒楄〃闀垮害 + 1锛岃〃绀烘柊澧炶褰曟帓鍦ㄦ渶鍚�
@@ -428,6 +482,7 @@
               processId: form.value.processId,
               productModelId: form.value.productModelId,
               isQuality: form.value.isQuality,
+              userPower: userPowerNames.join(','),
               dragSort,
             })
           : addOrUpdateProcessRouteItem({
@@ -435,6 +490,7 @@
               processId: form.value.processId,
               productModelId: form.value.productModelId,
               isQuality: form.value.isQuality,
+              userPower: userPowerNames.join(','),
               dragSort,
             });
 
@@ -460,6 +516,7 @@
               processId: form.value.processId,
               productModelId: form.value.productModelId,
               isQuality: form.value.isQuality,
+              userPower: userPowerNames.join(','),
             })
           : addOrUpdateProcessRouteItem({
               routeId: routeId.value,
@@ -467,6 +524,7 @@
               productModelId: form.value.productModelId,
               id: form.value.id,
               isQuality: form.value.isQuality,
+              userPower: userPowerNames.join(','),
             });
 
         updatePromise
@@ -640,8 +698,70 @@
   getRouteInfo();
   getList();
   getProcessList();
+  getStaffList();
 });
 
+const getStaffList = () => {
+  listDeptUserTree().then(res => {
+    const buildTree = (nodes) => {
+      return nodes.map(node => {
+        const deptNode = {
+          id: `dept_${node.deptId}`,
+          label: node.deptName,
+          isUser: false,
+          children: []
+        };
+        
+        if (node.userList && node.userList.length > 0) {
+          node.userList.forEach(user => {
+            deptNode.children.push({
+              id: user.userId,
+              label: user.nickName || user.userName,
+              isUser: true,
+              userName: user.userName,
+              nickName: user.nickName
+            });
+          });
+        }
+        
+        if (node.childrenList && node.childrenList.length > 0) {
+          const childNodes = buildTree(node.childrenList);
+          deptNode.children = deptNode.children.concat(childNodes);
+        }
+        
+        return deptNode;
+      });
+    };
+    staffList.value = buildTree(res.data || []);
+  }).catch(() => {
+    staffList.value = [];
+  });
+};
+
+const getAllUserNamesFromSelection = (selectedIds) => {
+  const names = [];
+  const processNode = (node) => {
+    if (selectedIds.includes(node.id)) {
+      if (node.isUser) {
+        names.push(node.label);
+      } else {
+        if (node.children && node.children.length > 0) {
+          node.children.forEach(child => {
+            if (child.isUser) {
+              names.push(child.label);
+            }
+          });
+        }
+      }
+    }
+    if (node.children && node.children.length > 0) {
+      node.children.forEach(child => processNode(child));
+    }
+  };
+  staffList.value.forEach(node => processNode(node));
+  return [...new Set(names)];
+};
+
 onUnmounted(() => {
   destroySortable();
 });
diff --git a/src/views/productionManagement/productionProcess/Edit.vue b/src/views/productionManagement/productionProcess/Edit.vue
index a9b266d..0920e4f 100644
--- a/src/views/productionManagement/productionProcess/Edit.vue
+++ b/src/views/productionManagement/productionProcess/Edit.vue
@@ -31,6 +31,21 @@
         <el-form-item label="鏄惁璐ㄦ" prop="isQuality">
           <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
         </el-form-item>
+        <el-form-item label="鎶ュ伐鏉冮檺" prop="userPower" :rules="[{ required: true, message: '璇烽�夋嫨鎶ュ伐鏉冮檺', trigger: 'change' }]">
+          <el-tree-select
+            v-model="formState.userPower"
+            :data="staffList"
+            :props="treeProps"
+            placeholder="璇烽�夋嫨浜哄憳"
+            multiple
+            show-checkbox
+            collapse-tags
+            collapse-tags-tooltip
+            style="width: 100%"
+            node-key="id"
+            :render-after-expand="false"
+          />
+        </el-form-item>
         <el-form-item label="澶囨敞" prop="remark">
           <el-input v-model="formState.remark" type="textarea" />
         </el-form-item>
@@ -46,8 +61,9 @@
 </template>
 
 <script setup>
-import { ref, computed, getCurrentInstance, watch } from "vue";
+import { ref, computed, getCurrentInstance, watch, onMounted } from "vue";
 import {update} from "@/api/productionManagement/productionProcess.js";
+import { listDeptUserTree } from "@/api/basicData/productProcess.js";
 
 const props = defineProps({
   visible: {
@@ -71,7 +87,15 @@
   remark: props.record.remark,
   salaryQuota: props.record.salaryQuota,
   isQuality: props.record.isQuality,
+  userPower: props.record.userPower ? props.record.userPower.split(',') : [],
 });
+
+const staffList = ref([]);
+
+const treeProps = {
+  label: 'label',
+  children: 'children',
+};
 
 const isShow = computed({
   get() {
@@ -85,30 +109,23 @@
 // 鐩戝惉 record 鍙樺寲锛屾洿鏂拌〃鍗曟暟鎹�
 watch(() => props.record, (newRecord) => {
   if (newRecord && isShow.value) {
+    const userPowerNames = newRecord.userPower ? newRecord.userPower.split(',') : [];
+    const userPowerIds = userPowerNames.map(name => {
+      const user = findUserByName(name);
+      return user ? user.id : null;
+    }).filter(id => id !== null);
+    
     formState.value = {
       id: newRecord.id,
       name: newRecord.name || '',
       no: newRecord.no || '',
       remark: newRecord.remark || '',
       salaryQuota: newRecord.salaryQuota || '',
-      isQuality: props.record.isQuality,
+      isQuality: newRecord.isQuality,
+      userPower: userPowerIds,
     };
   }
 }, { immediate: true, deep: true });
-
-// 鐩戝惉寮圭獥鎵撳紑锛岄噸鏂板垵濮嬪寲琛ㄥ崟鏁版嵁
-watch(() => props.visible, (visible) => {
-  if (visible && props.record) {
-    formState.value = {
-      id: props.record.id,
-      name: props.record.name || '',
-      no: props.record.no || '',
-      remark: props.record.remark || '',
-      salaryQuota: props.record.salaryQuota || '',
-      isQuality: props.record.isQuality,
-    };
-  }
-});
 
 let { proxy } = getCurrentInstance()
 
@@ -119,7 +136,13 @@
 const handleSubmit = () => {
   proxy.$refs["formRef"].validate(valid => {
     if (valid) {
-      update(formState.value).then(res => {
+      const userPowerNames = getAllUserNamesFromSelection(formState.value.userPower);
+      
+      const submitData = {
+        ...formState.value,
+        userPower: userPowerNames.join(',')
+      };
+      update(submitData).then(res => {
         // 鍏抽棴妯℃�佹
         isShow.value = false;
         // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
@@ -130,6 +153,87 @@
   })
 };
 
+const findUserById = (userId) => {
+  const findInTree = (nodes) => {
+    for (const node of nodes) {
+      if (node.id === userId) {
+        return node;
+      }
+      if (node.children && node.children.length > 0) {
+        const found = findInTree(node.children);
+        if (found) return found;
+      }
+    }
+    return null;
+  };
+  return findInTree(staffList.value);
+};
+
+const getStaffList = () => {
+  listDeptUserTree().then(res => {
+    const buildTree = (nodes) => {
+      return nodes.map(node => {
+        const deptNode = {
+          id: `dept_${node.deptId}`,
+          label: node.deptName,
+          isUser: false,
+          children: []
+        };
+        
+        if (node.userList && node.userList.length > 0) {
+          node.userList.forEach(user => {
+            deptNode.children.push({
+              id: user.userId,
+              label: user.nickName || user.userName,
+              isUser: true,
+              userName: user.userName,
+              nickName: user.nickName
+            });
+          });
+        }
+        
+        if (node.childrenList && node.childrenList.length > 0) {
+          const childNodes = buildTree(node.childrenList);
+          deptNode.children = deptNode.children.concat(childNodes);
+        }
+        
+        return deptNode;
+      });
+    };
+    staffList.value = buildTree(res.data || []);
+  }).catch(() => {
+    staffList.value = [];
+  });
+};
+
+const getAllUserNamesFromSelection = (selectedIds) => {
+  const names = [];
+  const processNode = (node) => {
+    if (selectedIds.includes(node.id)) {
+      if (node.isUser) {
+        names.push(node.label);
+      } else {
+        if (node.children && node.children.length > 0) {
+          node.children.forEach(child => {
+            if (child.isUser) {
+              names.push(child.label);
+            }
+          });
+        }
+      }
+    }
+    if (node.children && node.children.length > 0) {
+      node.children.forEach(child => processNode(child));
+    }
+  };
+  staffList.value.forEach(node => processNode(node));
+  return [...new Set(names)];
+};
+
+onMounted(() => {
+  getStaffList();
+});
+
 defineExpose({
   closeModal,
   handleSubmit,
diff --git a/src/views/productionManagement/productionProcess/New.vue b/src/views/productionManagement/productionProcess/New.vue
index a5924e5..db69fbc 100644
--- a/src/views/productionManagement/productionProcess/New.vue
+++ b/src/views/productionManagement/productionProcess/New.vue
@@ -29,7 +29,22 @@
 <!--          <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />-->
 <!--        </el-form-item>-->
         <el-form-item label="鏄惁璐ㄦ" prop="isQuality">
-          <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
+          <el-switch v-model="formState.isQuality" :active-value="true" :inactive-value="false"/>
+        </el-form-item>
+        <el-form-item label="鎶ュ伐鏉冮檺" prop="userPower" :rules="[{ required: true, message: '璇烽�夋嫨鎶ュ伐鏉冮檺', trigger: 'change' }]">
+          <el-tree-select
+            v-model="formState.userPower"
+            :data="staffList"
+            :props="treeProps"
+            placeholder="璇烽�夋嫨浜哄憳"
+            multiple
+            show-checkbox
+            collapse-tags
+            collapse-tags-tooltip
+            style="width: 100%"
+            node-key="id"
+            :render-after-expand="false"
+          />
         </el-form-item>
         <el-form-item label="澶囨敞" prop="remark">
           <el-input v-model="formState.remark" type="textarea" />
@@ -46,8 +61,9 @@
 </template>
 
 <script setup>
-import { ref, computed, getCurrentInstance } from "vue";
+import { ref, computed, getCurrentInstance, onMounted } from "vue";
 import {add} from "@/api/productionManagement/productionProcess.js";
+import { listDeptUserTree } from "@/api/basicData/productProcess.js";
 
 const props = defineProps({
   visible: {
@@ -64,7 +80,15 @@
   remark: '',
   salaryQuota:  '',
   isQuality: false,
+  userPower: [],
 });
+
+const staffList = ref([]);
+
+const treeProps = {
+  label: 'label',
+  children: 'children',
+};
 
 const isShow = computed({
   get() {
@@ -84,7 +108,13 @@
 const handleSubmit = () => {
   proxy.$refs["formRef"].validate(valid => {
     if (valid) {
-      add(formState.value).then(res => {
+      const userPowerNames = getAllUserNamesFromSelection(formState.value.userPower);
+      
+      const submitData = {
+        ...formState.value,
+        userPower: userPowerNames.join(',')
+      };
+      add(submitData).then(res => {
         // 鍏抽棴妯℃�佹
         isShow.value = false;
         // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
@@ -95,6 +125,87 @@
   })
 };
 
+const findUserById = (userId) => {
+  const findInTree = (nodes) => {
+    for (const node of nodes) {
+      if (node.id === userId) {
+        return node;
+      }
+      if (node.children && node.children.length > 0) {
+        const found = findInTree(node.children);
+        if (found) return found;
+      }
+    }
+    return null;
+  };
+  return findInTree(staffList.value);
+};
+
+const getStaffList = () => {
+  listDeptUserTree().then(res => {
+    const buildTree = (nodes) => {
+      return nodes.map(node => {
+        const deptNode = {
+          id: `dept_${node.deptId}`,
+          label: node.deptName,
+          isUser: false,
+          children: []
+        };
+        
+        if (node.userList && node.userList.length > 0) {
+          node.userList.forEach(user => {
+            deptNode.children.push({
+              id: user.userId,
+              label: user.nickName || user.userName,
+              isUser: true,
+              userName: user.userName,
+              nickName: user.nickName
+            });
+          });
+        }
+        
+        if (node.childrenList && node.childrenList.length > 0) {
+          const childNodes = buildTree(node.childrenList);
+          deptNode.children = deptNode.children.concat(childNodes);
+        }
+        
+        return deptNode;
+      });
+    };
+    staffList.value = buildTree(res.data || []);
+  }).catch(() => {
+    staffList.value = [];
+  });
+};
+
+const getAllUserNamesFromSelection = (selectedIds) => {
+  const names = [];
+  const processNode = (node) => {
+    if (selectedIds.includes(node.id)) {
+      if (node.isUser) {
+        names.push(node.label);
+      } else {
+        if (node.children && node.children.length > 0) {
+          node.children.forEach(child => {
+            if (child.isUser) {
+              names.push(child.label);
+            }
+          });
+        }
+      }
+    }
+    if (node.children && node.children.length > 0) {
+      node.children.forEach(child => processNode(child));
+    }
+  };
+  staffList.value.forEach(node => processNode(node));
+  return [...new Set(names)];
+};
+
+onMounted(() => {
+  getStaffList();
+});
+
 defineExpose({
   closeModal,
   handleSubmit,
diff --git a/src/views/productionManagement/productionProcess/index.vue b/src/views/productionManagement/productionProcess/index.vue
index f8521c6..bdbebff 100644
--- a/src/views/productionManagement/productionProcess/index.vue
+++ b/src/views/productionManagement/productionProcess/index.vue
@@ -98,6 +98,11 @@
       label: "宸ュ簭鍚嶇О",
       prop: "name",
     },
+    {
+      label: "鎶ュ伐鏉冮檺",
+      prop: "userPower",
+      width: 200,
+    },
     // {
     //   label: "宸ヨ祫瀹氶",
     //   prop: "salaryQuota",

--
Gitblit v1.9.3