| | |
| | | <PageHeader content="产品结构详情"> |
| | | <template #right-button> |
| | | <el-button v-if="dataValue.isEdit && !isOrderPage" |
| | | type="primary" |
| | | @click="addItem">添加 |
| | | @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">确认 |
| | | </el-button> |
| | | </template> |
| | | </PageHeader> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | :preserve-expanded-content="false" |
| | | :default-expand-all="true" |
| | | style="width: 100%" |
| | | > |
| | | <el-table :data="tableData" |
| | | border |
| | | :preserve-expanded-content="false" |
| | | :default-expand-all="true" |
| | | style="width: 100%"> |
| | | <el-table-column type="expand"> |
| | | <template #default="props"> |
| | | <el-form ref="form" |
| | | :model="dataValue"> |
| | | <el-table :data="dataValue.dataList" |
| | | row-key="tempId" |
| | | default-expand-all |
| | | :tree-props="{children: 'children', hasChildren: 'hasChildren'}" |
| | | style="width: 100%"> |
| | | <el-table-column prop="productName" |
| | | label="产品"/> |
| | | <el-table-column prop="model" |
| | | label="规格"> |
| | | 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="processName" |
| | | label="工序"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :prop="`dataList.${$index}.model`" |
| | | :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-select v-model="row.model" |
| | | placeholder="请选择规格" |
| | | clearable |
| | | :disabled="!dataValue.isEdit" |
| | | style="width: 100%" |
| | | @visible-change="(v) => { if (v) openDialog($index) }"> |
| | | <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="processId" |
| | | label="消耗工序"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item :prop="`dataList.${$index}.processId`" |
| | | :rules="[{ 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="请选择" |
| | | filterable |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="!dataValue.isEdit"> |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"> |
| | | <el-option v-for="item in dataValue.processOptions" |
| | | :key="item.id" |
| | | :label="item.name" |
| | |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unitQuantity" |
| | | label="单位产出所需数量"> |
| | | label="单位用量"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item :prop="`dataList.${$index}.unitQuantity`" |
| | | :rules="[{ required: true, message: '请输入单位产出所需数量', trigger: ['blur','change'] }]" |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="[{ required: true, message: '请输入单位用量', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-input-number v-model="row.unitQuantity" |
| | | :min="0" |
| | |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="!dataValue.isEdit" /> |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="demandedQuantity" |
| | | <el-table-column v-if="isOrderPage" |
| | | prop="demandedQuantity" |
| | | label="需求总量"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item :prop="`dataList.${$index}.demandedQuantity`" |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="[{ required: true, message: '请输入需求总量', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-input-number v-model="row.demandedQuantity" |
| | |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="!dataValue.isEdit" /> |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unit" |
| | | label="单位"> |
| | | <template #default="{ row, $index }"> |
| | | <el-form-item :prop="`dataList.${$index}.unit`" |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="[{ required: true, message: '请输入单位', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-input v-model="row.unit" |
| | | placeholder="请输入单位" |
| | | clearable |
| | | :disabled="!dataValue.isEdit" /> |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" fixed="right" width="100"> |
| | | <el-table-column label="操作" |
| | | fixed="right" |
| | | width="200"> |
| | | <template #default="{ row, $index }"> |
| | | <el-button v-if="dataValue.isEdit" |
| | | <el-button v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)" |
| | | type="danger" |
| | | text |
| | | @click="dataValue.dataList.splice($index, 1)">删除 |
| | | @click="removeItem(row.tempId)">删除 |
| | | </el-button> |
| | | <el-button v-if="dataValue.isEdit" |
| | | type="primary" |
| | | text |
| | | @click="addItem2(row.tempId)">添加 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | </el-form> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="BOM编号" prop="bomNo" /> |
| | | <el-table-column label="产品名称" prop="productName" /> |
| | | <el-table-column label="规格型号" prop="model" /> |
| | | <el-table-column label="BOM编号" |
| | | prop="bomNo" /> |
| | | <el-table-column label="产品名称" |
| | | prop="productName" /> |
| | | <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> |
| | | |
| | | <script setup lang="ts"> |
| | | import { |
| | | computed, |
| | | defineAsyncComponent, |
| | | defineComponent, |
| | | onMounted, |
| | | reactive, |
| | | ref, |
| | | } from "vue"; |
| | | import { queryList, add } from "@/api/productionManagement/productStructure.js"; |
| | | import { listProcessBom } from "@/api/productionManagement/productionOrder.js"; |
| | | import { list } from "@/api/productionManagement/productionProcess"; |
| | | import { ElMessage } from "element-plus"; |
| | | import {useRoute, useRouter} from "vue-router"; |
| | | import { |
| | | computed, |
| | | defineAsyncComponent, |
| | | defineComponent, |
| | | onMounted, |
| | | reactive, |
| | | ref, |
| | | } from "vue"; |
| | | 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", |
| | | }); |
| | | |
| | | const ProductSelectDialog = defineAsyncComponent( |
| | | () => import("@/views/basicData/product/ProductSelectDialog.vue") |
| | | ); |
| | | const form = ref(); |
| | | |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | const routeId = computed({ |
| | | get() { |
| | | return route.query.id; |
| | | }, |
| | | |
| | | set(val) { |
| | | emit('update:router', val) |
| | | } |
| | | }); |
| | | |
| | | // 从路由参数获取产品信息 |
| | | const routeBomNo = computed(() => route.query.bomNo || ''); |
| | | const routeProductName = computed(() => route.query.productName || ''); |
| | | const routeProductModelName = computed(() => route.query.productModelName || ''); |
| | | const routeOrderId = computed(() => route.query.orderId); |
| | | const pageType = computed(() => route.query.type); |
| | | const isOrderPage = computed(() => pageType.value === 'order' && routeOrderId.value); |
| | | |
| | | const dataValue = reactive({ |
| | | dataList: [], |
| | | productOptions: [], |
| | | processOptions: [], |
| | | showProductDialog: false, |
| | | currentRowIndex: null, |
| | | loading: false, |
| | | isEdit: false, |
| | | }); |
| | | |
| | | const tableData = reactive([ |
| | | { |
| | | productName: "", |
| | | model: "", |
| | | bomNo: "", |
| | | } |
| | | ]) |
| | | |
| | | const openDialog = index => { |
| | | dataValue.currentRowIndex = index; |
| | | dataValue.showProductDialog = true; |
| | | }; |
| | | |
| | | const fetchData = async () => { |
| | | if (isOrderPage.value) { |
| | | // 订单情况:使用订单的产品结构接口 |
| | | const { data } = await listProcessBom({ orderId: routeOrderId.value }); |
| | | dataValue.dataList = data || []; |
| | | } else { |
| | | // 非订单情况:使用原来的接口 |
| | | const { data } = await queryList(routeId.value); |
| | | dataValue.dataList = data || []; |
| | | } |
| | | }; |
| | | |
| | | const fetchProcessOptions = async () => { |
| | | const { data } = await list(routeId.value); |
| | | dataValue.processOptions = data; |
| | | }; |
| | | |
| | | const handleProduct = row => { |
| | | if (row?.length > 1) { |
| | | ElMessage.error("只能选择一个产品"); |
| | | } |
| | | dataValue.dataList[dataValue.currentRowIndex].productName = |
| | | row[0].productName; |
| | | dataValue.dataList[dataValue.currentRowIndex].model = row[0].model; |
| | | dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id; |
| | | dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || ""; |
| | | dataValue.showProductDialog = false; |
| | | }; |
| | | |
| | | const submit = () => { |
| | | form.value |
| | | .validate(valid => { |
| | | dataValue.loading = true; |
| | | if (valid) { |
| | | add({ |
| | | bomId: routeId.value, |
| | | productStructureList: dataValue.dataList || [], |
| | | }).then(res => { |
| | | router.push({ |
| | | path: '/productionManagement/productionManagement/productStructure/index', |
| | | }) |
| | | ElMessage.success("保存成功"); |
| | | dataValue.loading = false; |
| | | }); |
| | | } |
| | | }) |
| | | .finally(() => { |
| | | dataValue.loading = false; |
| | | }); |
| | | }; |
| | | |
| | | const addItem = () => { |
| | | dataValue.dataList.push({ |
| | | productName: "", |
| | | productId: "", |
| | | model: undefined, |
| | | productModelId: undefined, |
| | | processId: "", |
| | | unitQuantity: 0, |
| | | demandedQuantity: 0, |
| | | unit: "", |
| | | defineComponent({ |
| | | name: "StructureEdit", |
| | | }); |
| | | }; |
| | | |
| | | const cancelEdit = () => { |
| | | dataValue.isEdit = false; |
| | | dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined); |
| | | }; |
| | | const ProductSelectDialog = defineAsyncComponent( |
| | | () => import("@/views/basicData/product/ProductSelectDialog.vue") |
| | | ); |
| | | const emit = defineEmits(["update:router"]); |
| | | const form = ref(); |
| | | |
| | | onMounted(() => { |
| | | // 从路由参数回显数据 |
| | | tableData[0].productName = routeProductName.value; |
| | | tableData[0].model = routeProductModelName.value; |
| | | tableData[0].bomNo = routeBomNo.value; |
| | | |
| | | // 订单情况下禁用编辑 |
| | | if (isOrderPage.value) { |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const routeId = computed({ |
| | | get() { |
| | | return route.query.id; |
| | | }, |
| | | |
| | | set(val) { |
| | | emit("update:router", val); |
| | | }, |
| | | }); |
| | | |
| | | // 从路由参数获取产品信息 |
| | | const routeBomNo = computed(() => route.query.bomNo || ""); |
| | | const routeProductName = computed(() => route.query.productName || ""); |
| | | const routeDrawingNumber = computed(() => route.query.drawingNumber || ""); |
| | | const routeProductModelName = computed( |
| | | () => route.query.productModelName || "" |
| | | ); |
| | | const routeOrderId = computed(() => route.query.orderId); |
| | | const pageType = computed(() => route.query.type); |
| | | const isOrderPage = computed( |
| | | () => pageType.value === "order" && routeOrderId.value |
| | | ); |
| | | |
| | | const dataValue = reactive({ |
| | | dataList: [], |
| | | productOptions: [], |
| | | processOptions: [], |
| | | showProductDialog: false, |
| | | currentRowIndex: null, |
| | | currentRowName: null, |
| | | 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([ |
| | | { |
| | | productName: "", |
| | | drawingNumber: "", |
| | | model: "", |
| | | bomNo: "", |
| | | }, |
| | | ]); |
| | | |
| | | const openDialog = (tempId: any) => { |
| | | console.log(tempId, "tempId"); |
| | | dataValue.currentRowName = tempId; |
| | | dataValue.showProductDialog = true; |
| | | }; |
| | | |
| | | const fetchData = async () => { |
| | | if (isOrderPage.value) { |
| | | // 订单情况:使用订单的产品结构接口 |
| | | const { data } = await listProcessBom({ orderId: routeOrderId.value }); |
| | | dataValue.dataList = (data as any) || []; |
| | | } else { |
| | | // 非订单情况:使用原来的接口 |
| | | const { data } = await queryList(routeId.value); |
| | | dataValue.dataList = (data as any) || []; |
| | | // 为所有项及其子项设置name属性 |
| | | const setNameRecursively = (items: any[]) => { |
| | | items.forEach((item: any) => { |
| | | item.tempId = item.id; |
| | | item.processName = |
| | | dataValue.processOptions.find(option => option.id === item.processId) |
| | | ?.name || ""; |
| | | if (item.children && item.children.length > 0) { |
| | | setNameRecursively(item.children); |
| | | } |
| | | }); |
| | | }; |
| | | setNameRecursively(dataValue.dataList); |
| | | console.log(dataValue.dataList, "dataValue.dataList"); |
| | | } |
| | | }; |
| | | |
| | | const fetchProcessOptions = async () => { |
| | | const { data } = await list(); |
| | | dataValue.processOptions = data as any; |
| | | }; |
| | | |
| | | const handleProduct = (row: any) => { |
| | | if (row?.length > 1) { |
| | | ElMessage.error("只能选择一个产品"); |
| | | } |
| | | const productData = row[0]; |
| | | |
| | | // 最外层组件中,与当前产品相同的产品只能有一个 |
| | | const isTopLevel = dataValue.dataList.some( |
| | | item => (item as any).tempId === dataValue.currentRowName |
| | | ); |
| | | if (isTopLevel) { |
| | | if ( |
| | | productData.productName === tableData[0].productName && |
| | | productData.model === tableData[0].model |
| | | ) { |
| | | // 查找是否已经有其他顶层行已经是这个产品 |
| | | const hasOther = dataValue.dataList.some( |
| | | item => |
| | | (item as any).tempId !== dataValue.currentRowName && |
| | | (item as any).productName === tableData[0].productName && |
| | | (item as any).model === tableData[0].model |
| | | ); |
| | | if (hasOther) { |
| | | ElMessage.warning("最外层和当前产品一样的一级只能有一个"); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | // dataValue.dataList[dataValue.currentRowIndex].productName = |
| | | // row[0].productName; |
| | | // dataValue.dataList[dataValue.currentRowIndex].model = row[0].model; |
| | | // dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id; |
| | | // dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || ""; |
| | | dataValue.dataList.map(item => { |
| | | if (item.tempId === dataValue.currentRowName) { |
| | | item.productName = productData.productName; |
| | | item.drawingNumber = productData.drawingNumber || ""; |
| | | item.model = productData.model; |
| | | item.productModelId = productData.id; |
| | | item.unit = productData.unit || ""; |
| | | return; |
| | | } |
| | | childItem(item, dataValue.currentRowName, productData); |
| | | }); |
| | | dataValue.showProductDialog = false; |
| | | }; |
| | | const childItem = (item: any, tempId: any, productData: any) => { |
| | | if (item.tempId === tempId) { |
| | | item.productName = productData.productName; |
| | | item.drawingNumber = productData.drawingNumber || ""; |
| | | item.model = productData.model; |
| | | item.productModelId = productData.id; |
| | | item.unit = productData.unit || ""; |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | for (let child of item.children) { |
| | | if (childItem(child, tempId, productData)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | // 递归校验所有层级的表单数据 |
| | | const validateAll = () => { |
| | | let isValid = true; |
| | | |
| | | // 校验函数 |
| | | const validateItem = (item: any, isTopLevel = false) => { |
| | | // 校验当前项的必填字段 |
| | | if (!item.model) { |
| | | ElMessage.error("请选择规格"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (!item.unitQuantity) { |
| | | ElMessage.error("请输入单位用量"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (isOrderPage.value && !item.demandedQuantity) { |
| | | ElMessage.error("请输入需求总量"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | // if (!item.unit) { |
| | | // ElMessage.error("请输入单位"); |
| | | // isValid = false; |
| | | // return; |
| | | // } |
| | | |
| | | // 递归校验子项 |
| | | if (item.children && item.children.length > 0) { |
| | | item.children.forEach(child => { |
| | | validateItem(child, false); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 遍历所有顶层项 |
| | | dataValue.dataList.forEach(item => { |
| | | validateItem(item, true); |
| | | }); |
| | | |
| | | return isValid; |
| | | }; |
| | | |
| | | const submit = () => { |
| | | dataValue.loading = true; |
| | | |
| | | // 先进行表单校验 |
| | | const valid = validateAll(); |
| | | console.log(dataValue.dataList, "dataValue.dataList"); |
| | | if (valid) { |
| | | add({ |
| | | bomId: routeId.value, |
| | | children: dataValue.dataList || [], |
| | | }) |
| | | .then(res => { |
| | | router.push({ |
| | | path: "/productionManagement/productionManagement/productStructure/index", |
| | | }); |
| | | ElMessage.success("保存成功"); |
| | | dataValue.loading = false; |
| | | }) |
| | | .catch(() => { |
| | | dataValue.loading = false; |
| | | }); |
| | | } else { |
| | | dataValue.loading = false; |
| | | } |
| | | }; |
| | | |
| | | const removeItem = (tempId: string) => { |
| | | // 先尝试从顶层删除 |
| | | const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId); |
| | | if (topIndex !== -1) { |
| | | dataValue.dataList.splice(topIndex, 1); |
| | | return; |
| | | } |
| | | |
| | | // 递归删除子项 |
| | | const delchildItem = (items: any[], tempId: any) => { |
| | | for (let i = 0; i < items.length; i++) { |
| | | const item = items[i]; |
| | | if (item.tempId === tempId) { |
| | | items.splice(i, 1); |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | if (delchildItem(item.children, tempId)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | dataValue.dataList.forEach(item => { |
| | | if (item.children && item.children.length > 0) { |
| | | delchildItem(item.children, tempId); |
| | | } |
| | | }); |
| | | }; |
| | | const addItem2 = tempId => { |
| | | dataValue.dataList.map(item => { |
| | | if (item.tempId === tempId) { |
| | | if (!item.children) { |
| | | item.children = []; |
| | | } |
| | | item.children.push({ |
| | | parentId: item.id || "", |
| | | parentTempId: item.tempId || "", |
| | | productName: "", |
| | | productId: "", |
| | | model: undefined, |
| | | productModelId: undefined, |
| | | processId: "", |
| | | processName: "", |
| | | unitQuantity: 0, |
| | | demandedQuantity: 0, |
| | | unit: "", |
| | | children: [], |
| | | |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | return; |
| | | } |
| | | addchildItem(item, tempId); |
| | | }); |
| | | }; |
| | | const addchildItem = (item: any, tempId: any) => { |
| | | if (item.tempId === tempId) { |
| | | console.log(item, "item"); |
| | | if (!item.children) { |
| | | item.children = []; |
| | | } |
| | | item.children.push({ |
| | | parentId: item.id || "", |
| | | parentTempId: item.tempId || "", |
| | | productName: "", |
| | | productId: "", |
| | | model: undefined, |
| | | productModelId: undefined, |
| | | processId: "", |
| | | unitQuantity: 0, |
| | | demandedQuantity: 0, |
| | | children: [], |
| | | unit: "", |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | for (let child of item.children) { |
| | | if (addchildItem(child, tempId)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | const getPropPath = (row, field) => { |
| | | // 为每个row生成唯一的路径 |
| | | // 使用row.id或索引作为唯一标识 |
| | | let path = "dataList"; |
| | | |
| | | // 简单实现:使用row的id或一个唯一标识 |
| | | const uniqueId = row.id || Math.floor(Math.random() * 10000); |
| | | path += `.${uniqueId}`; |
| | | |
| | | return path + `.${field}`; |
| | | }; |
| | | |
| | | const cancelEdit = () => { |
| | | dataValue.isEdit = false; |
| | | } |
| | | |
| | | fetchData(); |
| | | fetchProcessOptions(); |
| | | }); |
| | | </script> |
| | | // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined); |
| | | 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; |
| | | tableData[0].drawingNumber = routeDrawingNumber.value as string; |
| | | tableData[0].model = routeProductModelName.value as string; |
| | | tableData[0].bomNo = routeBomNo.value as string; |
| | | |
| | | // 订单情况下禁用编辑 |
| | | if (isOrderPage.value) { |
| | | dataValue.isEdit = false; |
| | | } |
| | | |
| | | // 先加载工序选项,再加载数据,确保el-select能够正确回显 |
| | | await fetchProcessOptions(); |
| | | await fetchData(); |
| | | }); |
| | | </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> |