From 470ce94b21d780fc6c0b1bb5d9e1672af1f4d66e Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期四, 15 一月 2026 10:44:32 +0800
Subject: [PATCH] fix: BOM功能调整

---
 src/views/basicData/product/ProductSelectDialog.vue                    |   56 ++++++
 src/views/productionManagement/processRoute/processRouteItem/index.vue |   29 +--
 src/api/productionManagement/productBom.js                             |   38 ++++
 src/views/productionManagement/productionProcess/Edit.vue              |   29 +++
 src/views/productionManagement/productStructure/index.vue              |  289 ++++++++++++++++++++++++++++++++----
 5 files changed, 386 insertions(+), 55 deletions(-)

diff --git a/src/api/productionManagement/productBom.js b/src/api/productionManagement/productBom.js
new file mode 100644
index 0000000..03ddd2a
--- /dev/null
+++ b/src/api/productionManagement/productBom.js
@@ -0,0 +1,38 @@
+// 浜у搧BOM椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+  return request({
+    url: "/productBom/listPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏂板
+export function add(data) {
+  return request({
+    url: "/productBom/add",
+    method: "post",
+    data: data,
+  });
+}
+
+// 淇敼
+export function update(data) {
+  return request({
+    url: "/productBom/update",
+    method: "put",
+    data: data,
+  });
+}
+
+// 鎵归噺鍒犻櫎
+export function batchDelete(ids) {
+  return request({
+    url: "/productBom/batchDelete",
+    method: "delete",
+    data: ids,
+  });
+}
diff --git a/src/views/basicData/product/ProductSelectDialog.vue b/src/views/basicData/product/ProductSelectDialog.vue
index 246cd88..9f85785 100644
--- a/src/views/basicData/product/ProductSelectDialog.vue
+++ b/src/views/basicData/product/ProductSelectDialog.vue
@@ -33,12 +33,14 @@
 
     <!-- 鍒楄〃 -->
     <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="index" label="#" width="60"/>
@@ -70,7 +72,7 @@
 </template>
 
 <script setup lang="ts">
-import {computed, onMounted, reactive, ref, watch} from "vue";
+import {computed, onMounted, reactive, ref, watch, nextTick} from "vue";
 import {ElMessage} from "element-plus";
 import {productModelList} from '@/api/basicData/productModel'
 
@@ -83,6 +85,7 @@
 
 const props = defineProps<{
   modelValue: boolean;
+  single?: boolean; // 鏄惁鍙兘閫夋嫨涓�涓紝榛樿false锛堝彲閫夋嫨澶氫釜锛�
 }>();
 
 const emit = defineEmits(['update:modelValue', 'confirm']);
@@ -105,14 +108,48 @@
 const loading = ref(false);
 const tableData = ref<ProductRow[]>([]);
 const total = ref(0);
-const multipleSelection = ref<ProductRow[]>([])
+const multipleSelection = ref<ProductRow[]>([]);
+const tableRef = ref();
 
 function close() {
   visible.value = false;
 }
 
 const handleSelectionChange = (val: ProductRow[]) => {
-  multipleSelection.value = val
+  if (props.single && val.length > 1) {
+    // 濡傛灉闄愬埗涓哄崟涓�夋嫨锛屽彧淇濈暀鏈�鍚庝竴涓�変腑鐨�
+    const lastSelected = val[val.length - 1];
+    multipleSelection.value = [lastSelected];
+    // 娓呯┖琛ㄦ牸閫変腑鐘舵�侊紝鐒跺悗閲嶆柊閫変腑鏈�鍚庝竴涓�
+    nextTick(() => {
+      if (tableRef.value) {
+        tableRef.value.clearSelection();
+        tableRef.value.toggleRowSelection(lastSelected, true);
+      }
+    });
+  } else {
+    multipleSelection.value = val;
+  }
+}
+
+// 澶勭悊鍗曚釜閫夋嫨
+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);
+            }
+          });
+        }
+      });
+    }
+  }
 }
 
 function onSearch() {
@@ -136,7 +173,11 @@
     ElMessage.warning("璇烽�夋嫨涓�鏉′骇鍝�");
     return;
   }
-  emit("confirm", multipleSelection.value);
+  if (props.single && multipleSelection.value.length > 1) {
+    ElMessage.warning("鍙兘閫夋嫨涓�涓骇鍝�");
+    return;
+  }
+  emit("confirm", props.single ? [multipleSelection.value[0]] : multipleSelection.value);
   close();
 }
 
@@ -157,6 +198,13 @@
   }
 }
 
+// 鐩戝惉寮圭獥鎵撳紑锛岄噸缃�夋嫨
+watch(() => props.modelValue, (visible) => {
+  if (visible) {
+    multipleSelection.value = [];
+  }
+});
+
 onMounted(() => {
   loadData()
 })
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 641ffff..fa6fbad 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -1,7 +1,7 @@
 <template>
   <div class="app-container">
-    <div class="operate-button">
-      <div style="margin-bottom: 15px;">
+    <PageHeader content="宸ヨ壓璺嚎椤圭洰">
+      <template #right-button>
         <el-button
             type="primary"
             @click="isShowProductSelectDialog = true"
@@ -9,16 +9,16 @@
           閫夋嫨浜у搧
         </el-button>
         <el-button type="primary" @click="handleSubmit">纭</el-button>
-      </div>
-
-      <el-switch
-          v-model="isTable"
-          inline-prompt
-          active-text="琛ㄦ牸"
-          inactive-text="鍒楄〃"
-          @change="handleViewChange"
-      />
-    </div>
+        <el-switch
+            v-model="isTable"
+            inline-prompt
+            active-text="琛ㄦ牸"
+            inactive-text="鍒楄〃"
+            @change="handleViewChange"
+            style="margin-left: 10px;"
+        />
+      </template>
+    </PageHeader>
     <el-table
         v-if="isTable"
         ref="multipleTable"
@@ -404,11 +404,6 @@
   padding: 0 !important;
 }
 
-.operate-button {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-}
 
 /* 淇敼锛氳嚜瀹氫箟姝ラ鏉″鍣ㄦ牱寮� */
 .custom-steps {
diff --git a/src/views/productionManagement/productStructure/index.vue b/src/views/productionManagement/productStructure/index.vue
index e32ff8d..08faf7c 100644
--- a/src/views/productionManagement/productStructure/index.vue
+++ b/src/views/productionManagement/productStructure/index.vue
@@ -1,5 +1,9 @@
 <template>
   <div class="app-container">
+    <div style="text-align: right; margin-bottom: 10px;">
+      <el-button type="primary" @click="handleAdd">鏂板</el-button>
+      <el-button type="danger" plain @click="handleBatchDelete" :disabled="selectedRows.length === 0">鍒犻櫎</el-button>
+    </div>
     <PIMTable
         rowKey="id"
         :column="tableColumn"
@@ -19,95 +23,314 @@
       </template>
     </PIMTable>
     <StructureEdit v-if="showEdit" v-model:show-model="showEdit" :record="currentRow"/>
+    
+    <!-- 鏂板/缂栬緫寮圭獥 -->
+    <el-dialog
+        v-model="dialogVisible"
+        :title="operationType === 'add' ? '鏂板BOM' : '缂栬緫BOM'"
+        width="600px"
+        @close="closeDialog"
+    >
+      <el-form
+          ref="formRef"
+          :model="form"
+          :rules="rules"
+          label-width="120px"
+      >
+        <el-form-item label="浜у搧鍚嶇О" prop="productModelId">
+          <el-button type="primary" @click="showProductSelectDialog = true">
+            {{ form.productName || '閫夋嫨浜у搧' }}
+          </el-button>
+        </el-form-item>
+        <el-form-item label="鐗堟湰鍙�" prop="version">
+          <el-input v-model="form.version" placeholder="璇疯緭鍏ョ増鏈彿" clearable />
+        </el-form-item>
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input
+              v-model="form.remark"
+              type="textarea"
+              :rows="3"
+              placeholder="璇疯緭鍏ュ娉�"
+              clearable
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="closeDialog">鍙栨秷</el-button>
+        <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+      </template>
+    </el-dialog>
+    
+    <!-- 浜у搧閫夋嫨寮圭獥 -->
+    <ProductSelectDialog
+        v-model="showProductSelectDialog"
+        @confirm="handleProductSelect"
+        single
+    />
   </div>
 </template>
 
 <script setup>
-import {ref} from "vue";
-import {productModelList} from "@/api/basicData/productModel.js";
+import { ref, reactive, toRefs, onMounted, getCurrentInstance, defineAsyncComponent } from "vue";
+import { listPage, add, update, batchDelete } from "@/api/productionManagement/productBom.js";
 import { useRouter } from 'vue-router'
+import { ElMessageBox } from 'element-plus'
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
 
 const router = useRouter()
+const { proxy } = getCurrentInstance()
 const StructureEdit = defineAsyncComponent(() => import('@/views/productionManagement/productStructure/StructureEdit.vue'))
 
 const tableColumn = ref([
   {
-    label: "浜у搧缂栫爜",
-    prop: "productCode",
-    slot: "detail"
+    label: "BOM缂栧彿",
+    prop: "bomNo",
+    minWidth: 140
   },
   {
     label: "浜у搧鍚嶇О",
     prop: "productName",
     dataType: 'slot',
-    slot: "detail"
+    slot: "detail",
+    minWidth: 160
   },
   {
     label: "瑙勬牸鍨嬪彿",
-    prop: "model",
+    prop: "productModelName",
+    minWidth: 140
   },
   {
-    label: "鍗曚綅",
-    prop: "unit",
+    label: "鐗堟湰鍙�",
+    prop: "version",
+    width: 100
+  },
+  {
+    label: "澶囨敞",
+    prop: "remark",
+    minWidth: 160
+  },
+  {
+    dataType: "action",
+    label: "鎿嶄綔",
+    align: "center",
+    fixed: "right",
+    width: 150,
+    operation: [
+      {
+        name: "缂栬緫",
+        type: "text",
+        clickFun: (row) => {
+          handleEdit(row)
+        }
+      },
+      {
+        name: "鍒犻櫎",
+        type: "danger",
+        link: true,
+        clickFun: (row) => {
+          handleDelete(row)
+        }
+      }
+    ]
   }
 ]);
+
 const tableData = ref([]);
 const tableLoading = ref(false);
 const showEdit = ref(false);
 const selectedRows = ref([]);
 const currentRow = ref({});
+const dialogVisible = ref(false);
+const operationType = ref('add'); // add | edit
+const formRef = ref(null);
+const showProductSelectDialog = ref(false);
+
 const page = reactive({
   current: 1,
   size: 10,
   total: 0,
 });
+
 const data = reactive({
   form: {
+    id: undefined,
     productName: "",
+    productModelName: "",
+    productModelId: "",
+    remark: "",
+    version: ""
   },
   rules: {
-    productName: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
-  },
-  modelForm: {
-    otherModel: '',
-    model: "",
-    unit: "",
-    speculativeTradingName: [],
-  },
+    productModelId: [{ required: true, message: "璇烽�夋嫨浜у搧", trigger: "change" }],
+    version: [{ required: true, message: "璇疯緭鍏ョ増鏈彿", trigger: "blur" }]
+  }
 });
-const {form, rules} = toRefs(data);
+
+const { form, rules } = toRefs(data);
+
 // 琛ㄦ牸閫夋嫨鏁版嵁
 const handleSelectionChange = (selection) => {
   selectedRows.value = selection;
 };
 
-// 鏌ヨ瑙勬牸鍨嬪彿
+// 鍒嗛〉
 const pagination = (obj) => {
   page.current = obj.page;
   page.size = obj.limit;
-  getModelList();
+  getList();
 };
 
+// 鏌ヨ鍒楄〃
+const getList = () => {
+  tableLoading.value = true;
+  listPage({
+    current: page.current,
+    size: page.size,
+  })
+    .then((res) => {
+      const records = res?.data?.records || [];
+      tableData.value = records;
+      page.total = res?.data?.total || 0;
+    })
+    .catch((err) => {
+      console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+    })
+    .finally(() => {
+      tableLoading.value = false;
+    });
+};
+
+// 鏂板
+const handleAdd = () => {
+  operationType.value = 'add';
+  Object.assign(form.value, {
+    id: undefined,
+    productName: "",
+    productModelName: "",
+    productModelId: "",
+    remark: "",
+    version: ""
+  });
+  dialogVisible.value = true;
+};
+
+// 缂栬緫
+const handleEdit = (row) => {
+  operationType.value = 'edit';
+  Object.assign(form.value, {
+    id: row.id,
+    productName: row.productName || "",
+    productModelName: row.productModelName || "",
+    productModelId: row.productModelId || "",
+    remark: row.remark || "",
+    version: row.version || ""
+  });
+  dialogVisible.value = true;
+};
+
+// 鍒犻櫎锛堝崟鏉★級
+const handleDelete = (row) => {
+  ElMessageBox.confirm('纭鍒犻櫎璇OM锛�', '鎻愮ず', {
+    confirmButtonText: '纭',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning'
+  })
+    .then(() => {
+      batchDelete([row.id])
+        .then(() => {
+          proxy.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+          getList();
+        })
+        .catch(() => {
+          proxy.$modal.msgError('鍒犻櫎澶辫触');
+        });
+    })
+    .catch(() => {});
+};
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+  if (!selectedRows.value.length) {
+    proxy.$modal.msgWarning('璇烽�夋嫨鏁版嵁');
+    return;
+  }
+  const ids = selectedRows.value.map(item => item.id);
+  ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', {
+    confirmButtonText: '纭',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning'
+  })
+    .then(() => {
+      batchDelete(ids)
+        .then(() => {
+          proxy.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+          getList();
+        })
+        .catch(() => {
+          proxy.$modal.msgError('鍒犻櫎澶辫触');
+        });
+    })
+    .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.productModelName = product.model;
+  }
+  showProductSelectDialog.value = false;
+};
+
+// 鎻愪氦琛ㄥ崟
+const handleSubmit = () => {
+  formRef.value.validate((valid) => {
+    if (valid) {
+      const payload = { ...form.value };
+      if (operationType.value === 'add') {
+        add(payload)
+          .then(() => {
+            proxy.$modal.msgSuccess('鏂板鎴愬姛');
+            closeDialog();
+            getList();
+          })
+          .catch(() => {
+            proxy.$modal.msgError('鏂板澶辫触');
+          });
+      } else {
+        update(payload)
+          .then(() => {
+            proxy.$modal.msgSuccess('淇敼鎴愬姛');
+            closeDialog();
+            getList();
+          })
+          .catch(() => {
+            proxy.$modal.msgError('淇敼澶辫触');
+          });
+      }
+    }
+  });
+};
+
+// 鍏抽棴寮圭獥
+const closeDialog = () => {
+  dialogVisible.value = false;
+  formRef.value?.resetFields();
+};
+
+// 鏌ョ湅璇︽儏
 const showDetail = (id) => {
   router.push({
     path: '/productionManagement/productStructureDetail',
     query: {
       id: id
     }
-  })
-}
-const getModelList = () => {
-  tableLoading.value = true;
-  productModelList({
-    current: page.current,
-    size: page.size,
-  }).then((res) => {
-    tableData.value = res.records;
-    page.total = res.total;
-    tableLoading.value = false;
   });
 };
+
 onMounted(() => {
-  getModelList();
-})
+  getList();
+});
 </script>
diff --git a/src/views/productionManagement/productionProcess/Edit.vue b/src/views/productionManagement/productionProcess/Edit.vue
index 340624f..f979d51 100644
--- a/src/views/productionManagement/productionProcess/Edit.vue
+++ b/src/views/productionManagement/productionProcess/Edit.vue
@@ -43,7 +43,7 @@
 </template>
 
 <script setup>
-import { ref, computed, getCurrentInstance } from "vue";
+import { ref, computed, getCurrentInstance, watch } from "vue";
 import {update} from "@/api/productionManagement/productionProcess.js";
 
 const props = defineProps({
@@ -64,6 +64,7 @@
 const formState = ref({
   id: props.record.id,
   name: props.record.name,
+  no: props.record.no,
   remark: props.record.remark,
   salaryQuota: props.record.salaryQuota,
 });
@@ -77,6 +78,32 @@
   },
 });
 
+// 鐩戝惉 record 鍙樺寲锛屾洿鏂拌〃鍗曟暟鎹�
+watch(() => props.record, (newRecord) => {
+  if (newRecord && isShow.value) {
+    formState.value = {
+      id: newRecord.id,
+      name: newRecord.name || '',
+      no: newRecord.no || '',
+      remark: newRecord.remark || '',
+      salaryQuota: newRecord.salaryQuota || '',
+    };
+  }
+}, { 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 || '',
+    };
+  }
+});
+
 let { proxy } = getCurrentInstance()
 
 const closeModal = () => {

--
Gitblit v1.9.3