| | |
| | | <div class="app-container"> |
| | | <PageHeader content="产品结构详情"> |
| | | <template #right-button> |
| | | <el-button v-if="dataValue.isEdit && !isOrderPage" |
| | | @click="cancelEdit">取消 |
| | | </el-button> |
| | | <el-button v-if="!dataValue.isEdit && !isOrderPage" |
| | | type="primary" |
| | | @click="dataValue.isEdit = true">编辑 |
| | | </el-button> |
| | | <el-button v-if="dataValue.isEdit && !isOrderPage" |
| | | type="primary" |
| | | @click="cancelEdit">取消 |
| | | @click="dataValue.isEdit = true">点击进行修改 |
| | | </el-button> |
| | | <el-button v-if="!isOrderPage" |
| | | type="primary" |
| | | @click="openBomAddDialog" |
| | | :disabled="!dataValue.isEdit"> |
| | | <el-icon><Document /></el-icon> 按BOM添加 |
| | | </el-button> |
| | | <el-button v-if="!isOrderPage" |
| | | type="success" |
| | | :loading="dataValue.loading" |
| | | @click="submit" |
| | | :disabled="!dataValue.isEdit">确认 |
| | |
| | | style="width: 100%"> |
| | | <el-table-column prop="productName" |
| | | label="产品" /> |
| | | <el-table-column prop="drawingNumber" |
| | | label="产品图纸编号" /> |
| | | <el-table-column prop="model" |
| | | label="规格"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-select v-model="row.model" |
| | | placeholder="请选择规格" |
| | | clearable |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" |
| | | style="width: 100%" |
| | | @visible-change="(v) => { if (v) openDialog(row.tempId) }"> |
| | | <el-option v-if="row.model" |
| | | :label="row.model" |
| | | :value="row.model" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="model" |
| | | label="图纸编号"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-select v-model="row.model" |
| | | placeholder="请选择规格" |
| | | clearable |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" |
| | | style="width: 100%" |
| | | @visible-change="(v) => { if (v) openDialog(row.tempId) }"> |
| | | <el-option v-if="row.model" |
| | | :label="row.model" |
| | | :value="row.model" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="processName" |
| | | label="消耗工序"> |
| | | label="工序"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '请选择消耗工序', trigger: 'change' }]" |
| | | :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '请选工序', trigger: 'change' }]" |
| | | style="margin: 0"> |
| | | <el-select v-model="row.processId" |
| | | placeholder="请选择" |
| | |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unitQuantity" |
| | | label="单位产出所需数量"> |
| | | label="单位用量"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="[{ required: true, message: '请输入单位产出所需数量', trigger: ['blur','change'] }]" |
| | | :rules="[{ required: true, message: '请输入单位用量', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-input-number v-model="row.unitQuantity" |
| | | :min="0" |
| | |
| | | prop="bomNo" /> |
| | | <el-table-column label="产品名称" |
| | | prop="productName" /> |
| | | <el-table-column label="产品图纸编号" |
| | | prop="drawingNumber" /> |
| | | <el-table-column label="规格型号" |
| | | prop="model" /> |
| | | <el-table-column label="图纸编号" |
| | | prop="model" /> |
| | | </el-table> |
| | | <product-select-dialog v-if="dataValue.showProductDialog" |
| | | v-model:model-value="dataValue.showProductDialog" |
| | | @confirm="handleProduct" /> |
| | | |
| | | <!-- 按BOM添加弹窗 --> |
| | | <el-dialog v-model="bomAddDialogVisible" title="按BOM添加" width="500px" @close="closeBomAddDialog"> |
| | | <el-form ref="bomAddFormRef" :model="bomAddForm" :rules="bomAddRules" label-width="100px"> |
| | | <el-form-item label="父项产品" prop="parentProductId"> |
| | | <el-select v-model="bomAddForm.parentProductId" placeholder="请选择" clearable filterable |
| | | style="width: 100%" @change="handleBomParentProductChange"> |
| | | <el-option v-for="item in parentProductOptions" :key="item.id" |
| | | :label="`${item.model || item.productCode || item.id} | ${item.productName}`" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <!-- 选中产品后展示产品信息 --> |
| | | <div v-if="selectedBomProduct" class="selected-product-info"> |
| | | <div class="product-info-row"> |
| | | <span class="info-label">产品编号</span> |
| | | <span class="info-value">{{ selectedBomProduct.model || selectedBomProduct.productCode || '-' }}</span> |
| | | </div> |
| | | <div class="product-info-row"> |
| | | <span class="info-label">产品名称</span> |
| | | <span class="info-value">{{ selectedBomProduct.productName || '-' }}</span> |
| | | </div> |
| | | <div class="product-info-row"> |
| | | <span class="info-label">产品规格</span> |
| | | <span class="info-value">{{ selectedBomProduct.spec || selectedBomProduct.drawingNumber || '-' }}</span> |
| | | </div> |
| | | <div class="stock-info-box"> |
| | | <div class="stock-number">{{ selectedBomProduct.stockQuantity || 0 }}</div> |
| | | <div class="stock-label">库存数量(台)</div> |
| | | </div> |
| | | </div> |
| | | <el-form-item label="用量系数" prop="coefficient" style="margin-top: 20px;"> |
| | | <el-input-number v-model="bomAddForm.coefficient" :min="0.01" :precision="2" :step="1" |
| | | controls-position="right" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitBomAdd">确定</el-button> |
| | | <el-button @click="closeBomAddDialog">取消</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 高级选择产品弹窗 --> |
| | | <product-select-dialog v-if="showAdvancedProductDialog" v-model:model-value="showAdvancedProductDialog" |
| | | @confirm="handleAdvancedProductSelect" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | reactive, |
| | | ref, |
| | | } from "vue"; |
| | | import { queryList, add } from "@/api/productionManagement/productStructure.js"; |
| | | import { queryList, add, listByBomIdIsParent } from "@/api/productionManagement/productStructure.js"; |
| | | import { listProcessBom } from "@/api/productionManagement/productionOrder.js"; |
| | | import { list } from "@/api/productionManagement/productionProcess"; |
| | | import { productListPage } from "@/api/basicData/product.js"; |
| | | import { listPage as listBomPage } from "@/api/productionManagement/productBom.js"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { useRoute, useRouter } from "vue-router"; |
| | | import { Search, Document } from '@element-plus/icons-vue' |
| | | |
| | | defineComponent({ |
| | | name: "StructureEdit", |
| | |
| | | loading: false, |
| | | isEdit: false, |
| | | }); |
| | | |
| | | // 按BOM添加相关 |
| | | const bomAddDialogVisible = ref(false); |
| | | const bomAddFormRef = ref(); |
| | | const parentProductOptions = ref([]); |
| | | const selectedBomProduct = ref(null); |
| | | const selectedBomTreeData = ref([]); // 保存选中产品时获取的BOM树数据 |
| | | const bomAddForm = reactive({ |
| | | parentProductId: undefined, |
| | | coefficient:1 |
| | | }); |
| | | const bomAddRules = { |
| | | parentProductId: [{ required: true, message: "请选择父项产品", trigger: "change" }], |
| | | coefficient: [{ required: true, message: "请输入用量系数", trigger: "blur" }] |
| | | }; |
| | | |
| | | const tableData = reactive([ |
| | | { |
| | |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (!isTopLevel && !item.processId) { |
| | | ElMessage.error("请选择消耗工序"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (!item.unitQuantity) { |
| | | ElMessage.error("请输入单位产出所需数量"); |
| | | ElMessage.error("请输入单位用量"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | |
| | | fetchData(); |
| | | }; |
| | | |
| | | // 获取父项产品列表 |
| | | const getParentProductList = async () => { |
| | | try { |
| | | const { data } = await listBomPage({ current: 1, size: 1000 }); |
| | | parentProductOptions.value = data?.records || []; |
| | | } catch (error) { |
| | | console.error("获取父项产品列表失败:", error); |
| | | parentProductOptions.value = []; |
| | | } |
| | | }; |
| | | |
| | | // 打开按BOM添加弹窗 |
| | | const openBomAddDialog = () => { |
| | | bomAddDialogVisible.value = true; |
| | | bomAddForm.parentProductId = undefined; |
| | | bomAddForm.coefficient = 1; |
| | | selectedBomProduct.value = null; |
| | | getParentProductList(); |
| | | }; |
| | | |
| | | // 关闭按BOM添加弹窗 |
| | | const closeBomAddDialog = () => { |
| | | bomAddDialogVisible.value = false; |
| | | bomAddFormRef.value?.resetFields(); |
| | | selectedBomProduct.value = null; |
| | | }; |
| | | |
| | | // 打开高级选择 |
| | | const openAdvancedSelect = () => { |
| | | showAdvancedProductDialog.value = true; |
| | | }; |
| | | |
| | | // 父项产品变更 |
| | | const handleBomParentProductChange = async (val) => { |
| | | if (val) { |
| | | const product = parentProductOptions.value.find(item => item.id === val); |
| | | selectedBomProduct.value = product || null; |
| | | |
| | | // 直接用选中的 BOM ID 调用 listByBomIdIsParent |
| | | const { data: treeData } = await listByBomIdIsParent(val); |
| | | selectedBomTreeData.value = treeData || []; |
| | | // 将二级树中的产品添加到父项产品选项(从第二级开始) |
| | | const addTreeToOptions = (items: any[]) => { |
| | | items.forEach((item: any) => { |
| | | // 跳过第一级,只添加第二级及以下的产品 |
| | | if (item.children && item.children.length > 0) { |
| | | item.children.forEach((child: any) => { |
| | | const exists = parentProductOptions.value.some(opt => opt.id === child.id); |
| | | if (!exists) { |
| | | parentProductOptions.value.push({ |
| | | id: child.id, |
| | | productName: child.productName, |
| | | model: child.model, |
| | | productCode: child.model, |
| | | spec: child.drawingNumber, |
| | | drawingNumber: child.drawingNumber, |
| | | stockQuantity: child.stockQuantity || 0 |
| | | }); |
| | | } |
| | | // 递归添加子项 |
| | | if (child.children && child.children.length > 0) { |
| | | addTreeToOptions([child]); |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | if (selectedBomTreeData.value.length > 0) { |
| | | addTreeToOptions(selectedBomTreeData.value); |
| | | } |
| | | } else { |
| | | selectedBomProduct.value = null; |
| | | selectedBomTreeData.value = []; |
| | | } |
| | | }; |
| | | |
| | | // 提交按BOM添加 |
| | | const submitBomAdd = () => { |
| | | bomAddFormRef.value.validate(async (valid) => { |
| | | if (!valid) return; |
| | | |
| | | const product = parentProductOptions.value.find(item => item.id === bomAddForm.parentProductId); |
| | | if (!product) { |
| | | ElMessage.error("未找到选中的产品"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 使用选择产品时保存的BOM树数据 |
| | | const bomItems = selectedBomTreeData.value || []; |
| | | |
| | | if (bomItems.length === 0) { |
| | | ElMessage.warning("该产品没有BOM信息"); |
| | | return; |
| | | } |
| | | |
| | | // 列表的第一级已经存在,把BOM数据作为第一级的子项(第二级)添加 |
| | | if (dataValue.dataList.length > 0) { |
| | | const firstLevelItem = dataValue.dataList[0]; |
| | | |
| | | // 把BOM数据添加到第一级的children中 |
| | | const addBomItemsRecursively = (items: any[], parentItem: any) => { |
| | | items.forEach((item: any) => { |
| | | const newItem: any = { |
| | | parentId: item.parentId || "", |
| | | parentTempId: parentItem.tempId || "", |
| | | productName: item.productName || "", |
| | | productId: item.productId || item.productModelId || "", |
| | | model: item.model || "", |
| | | productModelId: item.productModelId || "", |
| | | drawingNumber: item.drawingNumber || "", |
| | | processId: item.processId || "", |
| | | processName: item.processName || "", |
| | | unitQuantity: (item.unitQuantity || 0) * bomAddForm.coefficient, |
| | | demandedQuantity: (item.demandedQuantity || 0) * bomAddForm.coefficient, |
| | | unit: item.unit || "", |
| | | children: [], |
| | | tempId: new Date().getTime() + Math.random(), |
| | | }; |
| | | |
| | | // 添加到父项的children |
| | | if (!parentItem.children) { |
| | | parentItem.children = []; |
| | | } |
| | | parentItem.children.push(newItem); |
| | | |
| | | // 递归处理子项 |
| | | if (item.children && item.children.length > 0) { |
| | | addBomItemsRecursively(item.children, newItem); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | addBomItemsRecursively(bomItems, firstLevelItem); |
| | | } |
| | | |
| | | ElMessage.success("添加成功"); |
| | | closeBomAddDialog(); |
| | | } catch (error) { |
| | | console.error("按BOM添加失败:", error); |
| | | ElMessage.error("添加失败"); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | // 从路由参数回显数据 |
| | | tableData[0].productName = routeProductName.value as string; |
| | |
| | | await fetchProcessOptions(); |
| | | await fetchData(); |
| | | }); |
| | | </script> |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .selected-product-info { |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | padding: 16px; |
| | | margin: 10px 0; |
| | | position: relative; |
| | | } |
| | | |
| | | .product-info-row { |
| | | display: flex; |
| | | margin-bottom: 8px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .product-info-row:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .info-label { |
| | | color: #909399; |
| | | width: 70px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .info-value { |
| | | color: #303133; |
| | | flex: 1; |
| | | } |
| | | |
| | | .stock-info-box { |
| | | position: absolute; |
| | | right: 16px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | text-align: center; |
| | | } |
| | | |
| | | .stock-number { |
| | | font-size: 28px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | line-height: 1; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .stock-label { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | </style> |