| src/api/productionManagement/productBom.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/Edit.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/New.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/processRouteItem/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/productionManagement/productBom.js
@@ -36,3 +36,12 @@ data: ids, }); } // 根据产品型号ID查询BOM export function getByModel(productModelId) { return request({ url: "/productBom/getByModel", method: "get", params: { productModelId }, }); } src/views/productionManagement/processRoute/Edit.vue
@@ -8,46 +8,45 @@ > <el-form label-width="140px" :model="formState" label-position="top" ref="formRef"> <el-form-item label="产品大类:" prop="productId" :rules="[ { required: true, message: '请选择产品大类', } ]" > <el-tree-select v-model="formState.productId" placeholder="请选择" clearable check-strictly @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" /> </el-form-item> <el-form-item label="规格型号:" label="产品名称" prop="productModelId" :rules="[ { required: true, message: '请选择规格型号', 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.productModelId" placeholder="请选择" v-model="formState.bomId" placeholder="请选择BOM" clearable :disabled="!formState.productModelId || bomOptions.length === 0" style="width: 100%" > <el-option v-for="item in productModelsOptions" v-for="item in bomOptions" :key="item.id" :label="item.model" :label="item.bomNo || `BOM-${item.id}`" :value="item.id" /> </el-select> @@ -57,6 +56,13 @@ <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> @@ -68,9 +74,10 @@ </template> <script setup> import {ref, computed, getCurrentInstance, onMounted} from "vue"; import {ref, computed, getCurrentInstance, onMounted, nextTick, watch} from "vue"; import {update} from "@/api/productionManagement/processRoute.js"; import {modelList, productTreeList} from "@/api/basicData/product.js"; import {getByModel} from "@/api/productionManagement/productBom.js"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; const props = defineProps({ visible: { @@ -87,7 +94,14 @@ const emit = defineEmits(['update:visible', 'completed']); // 响应式数据(替代选项式的 data) const formState = ref({}); const formState = ref({ productId: undefined, productModelId: undefined, productName: "", productModelName: "", bomId: undefined, description: '', }); const isShow = computed({ get() { @@ -98,66 +112,111 @@ }, }); const showProductSelectDialog = ref(false); const bomOptions = ref([]); let { proxy } = getCurrentInstance() const productModelsOptions = ref([]) const productOptions = ref([]) const closeModal = () => { isShow.value = false; }; // 设置表单数据 const setFormData = () => { formState.value = props.record } const getProductOptions = () => { productTreeList().then((res) => { productOptions.value = convertIdToValue(res); }); }; const getModels = (value) => { formState.value.productModelId = undefined; productModelsOptions.value = []; if (value) { modelList({ id: value }).then((res) => { productModelsOptions.value = res; }); } }; const findNodeById = (nodes, productId) => { for (let i = 0; i < nodes.length; i++) { if (nodes[i].value === productId) { return nodes[i].label; // 找到节点,返回该节点的label } if (nodes[i].children && nodes[i].children.length > 0) { const foundNode = findNodeById(nodes[i].children, productId); if (foundNode) { return foundNode; // 在子节点中找到,直接返回(已经是label字符串) } } } return null; // 没有找到节点,返回null }; function convertIdToValue(data) { return data.map((item) => { const { id, children, ...rest } = item; const newItem = { ...rest, value: id, // 将 id 改为 value if (props.record) { formState.value = { ...props.record, productId: props.record.productId, productModelId: props.record.productModelId, productName: props.record.productName || "", // 注意:record中的字段是model,需要映射到productModelName productModelName: props.record.model || props.record.productModelName || "", bomId: props.record.bomId, description: props.record.description || '', }; if (children && children.length > 0) { newItem.children = convertIdToValue(children); // 如果有产品型号ID,加载BOM列表 if (props.record.productModelId) { loadBomList(props.record.productModelId); } return newItem; }); } } // 加载BOM列表 const loadBomList = async (productModelId) => { if (!productModelId) { bomOptions.value = []; return; } try { const res = await getByModel(productModelId); // 处理返回的BOM数据:可能是数组、对象或包含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]; // 先查询BOM列表(必选) try { const res = await getByModel(product.id); // 处理返回的BOM数据:可能是数组、对象或包含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; // 如果当前选择的BOM不在新列表中,则重置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("该产品没有BOM,请先创建BOM"); } } catch (error) { // 如果接口返回404或其他错误,说明没有BOM proxy.$modal.msgError("该产品没有BOM,请先创建BOM"); } } }; 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; @@ -176,11 +235,18 @@ }); // 监听弹窗打开,初始化表单数据 watch(() => props.visible, (visible) => { if (visible && props.record) { nextTick(() => { setFormData(); }); } }, { immediate: true }); onMounted(() => { getProductOptions() getModels(props.record.productId) nextTick(() => { setFormData() }); }) if (props.visible && props.record) { setFormData(); } }); </script> src/views/productionManagement/processRoute/New.vue
@@ -8,46 +8,45 @@ > <el-form label-width="140px" :model="formState" label-position="top" ref="formRef"> <el-form-item label="产品大类:" prop="productId" :rules="[ { required: true, message: '请选择产品大类', } ]" > <el-tree-select v-model="formState.productId" placeholder="请选择" clearable check-strictly @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" /> </el-form-item> <el-form-item label="规格型号:" label="产品名称" prop="productModelId" :rules="[ { required: true, message: '请选择规格型号', 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.productModelId" placeholder="请选择" v-model="formState.bomId" placeholder="请选择BOM" clearable :disabled="!formState.productModelId || bomOptions.length === 0" style="width: 100%" > <el-option v-for="item in productModelsOptions" v-for="item in bomOptions" :key="item.id" :label="item.model" :label="item.bomNo || `BOM-${item.id}`" :value="item.id" /> </el-select> @@ -57,6 +56,13 @@ <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> @@ -68,9 +74,10 @@ </template> <script setup> import {ref, computed, getCurrentInstance, onMounted} from "vue"; import {ref, computed, getCurrentInstance} from "vue"; import {add} from "@/api/productionManagement/processRoute.js"; import {modelList, productTreeList} from "@/api/basicData/product.js"; import {getByModel} from "@/api/productionManagement/productBom.js"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; const props = defineProps({ visible: { @@ -85,6 +92,9 @@ const formState = ref({ productId: undefined, productModelId: undefined, productName: "", productModelName: "", bomId: undefined, description: '', }); @@ -97,66 +107,73 @@ }, }); const productModelsOptions = ref([]) const productOptions = ref([]) 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 getProductOptions = () => { productTreeList().then((res) => { productOptions.value = convertIdToValue(res); }); }; const getModels = (value) => { formState.value.productId = undefined; formState.value.productModelId = undefined; productModelsOptions.value = []; if (value) { formState.value.productId = findNodeById(productOptions.value, value) || undefined; modelList({ id: value }).then((res) => { productModelsOptions.value = res; }); } }; const findNodeById = (nodes, productId) => { for (let i = 0; i < nodes.length; i++) { if (nodes[i].value === productId) { return nodes[i].label; // 找到节点,返回该节点的label } if (nodes[i].children && nodes[i].children.length > 0) { const foundNode = findNodeById(nodes[i].children, productId); if (foundNode) { return foundNode; // 在子节点中找到,直接返回(已经是label字符串) // 产品选择处理 const handleProductSelect = async (products) => { if (products && products.length > 0) { const product = products[0]; // 先查询BOM列表(必选) try { const res = await getByModel(product.id); // 处理返回的BOM数据:可能是数组、对象或包含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("该产品没有BOM,请先创建BOM"); } } catch (error) { // 如果接口返回404或其他错误,说明没有BOM proxy.$modal.msgError("该产品没有BOM,请先创建BOM"); } } return null; // 没有找到节点,返回null }; function convertIdToValue(data) { return data.map((item) => { const { id, children, ...rest } = item; const newItem = { ...rest, value: id, // 将 id 改为 value }; if (children && children.length > 0) { newItem.children = convertIdToValue(children); } return newItem; }); } 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; @@ -174,8 +191,4 @@ handleSubmit, isShow, }); onMounted(() => { getProductOptions() }) </script> src/views/productionManagement/processRoute/index.vue
@@ -80,6 +80,10 @@ prop: "model", }, { label: "BOM编号", prop: "bomNo", }, { label: "描述", prop: "description", }, src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -4,9 +4,9 @@ <template #right-button> <el-button type="primary" @click="isShowProductSelectDialog = true" @click="isShowProcessSelectDialog = true" > 选择产品 选择工序 </el-button> <el-button type="primary" @click="handleSubmit">确认</el-button> <el-switch @@ -59,18 +59,7 @@ <template #default="scope" v-else> <template v-if="item.prop === 'processId'"> <el-select v-model="scope.row[item.prop]" style="width: 100%;" @mousedown.stop > <el-option v-for="process in processOptions" :key="process.id" :label="process.name" :value="process.id" /> </el-select> {{ getProcessName(scope.row.processId) || '-' }} </template> <template v-else> {{ scope.row[item.prop] || '-' }} @@ -106,6 +95,7 @@ v-model="item.processId" style="width: 100%;" @mousedown.stop :disabled="true" > <el-option v-for="process in processOptions" @@ -114,6 +104,14 @@ :value="process.id" /> </el-select> <el-button type="primary" size="small" style="margin-top: 8px; width: 100%;" @click.stop="handleSelectProductForRow(item)" > 选择产品 </el-button> </div> <template #footer> <div class="step-card-footer"> @@ -125,9 +123,38 @@ </div> </div> <!-- 工序选择对话框 --> <el-dialog v-model="isShowProcessSelectDialog" title="选择工序" width="400px" > <el-select v-model="selectedProcessId" placeholder="请选择工序(可多选)" style="width: 100%" multiple collapse-tags collapse-tags-tooltip > <el-option v-for="process in processOptions" :key="process.id" :label="process.name" :value="process.id" /> </el-select> <template #footer> <el-button @click="isShowProcessSelectDialog = false">取消</el-button> <el-button type="primary" @click="handleSelectProcess">确定</el-button> </template> </el-dialog> <!-- 产品选择对话框 --> <ProductSelectDialog v-model="isShowProductSelectDialog" @confirm="handelSelectProducts" @confirm="handleSelectProductForCurrentRow" single /> </div> </template> @@ -143,6 +170,9 @@ const processOptions = ref([]); const tableLoading = ref(false); const isShowProductSelectDialog = ref(false); const isShowProcessSelectDialog = ref(false); const selectedProcessId = ref([]); const currentSelectRow = ref(null); const routeItems = ref([]); let tableSortable = null; let stepsSortable = null; @@ -164,17 +194,26 @@ const tableColumn = ref([ { label: "工序名称", prop: "processId", width: 200 }, { label: "产品名称", prop: "productName"}, { label: "规格名称", prop: "model" }, { label: "单位", prop: "unit" }, { label: "工序名称", prop: "processId", width: 200 }, { dataType: "action", label: "操作", align: "center", fixed: "right", width: 100, width: 180, operation: [ { name: "选择产品", type: "primary", link: true, clickFun: (row) => { currentSelectRow.value = row; isShowProductSelectDialog.value = true; } }, { name: "删除", type: "danger", @@ -190,6 +229,13 @@ } ]); // 根据工序ID获取工序名称 const getProcessName = (processId) => { if (!processId) return ''; const process = processOptions.value.find(p => p.id === processId); return process ? process.name : ''; }; const removeItem = (index) => { routeItems.value.splice(index, 1); nextTick(() => initSortable()); @@ -203,36 +249,53 @@ } }; const handelSelectProducts = (products) => { destroySortable(); const newData = products.map(({ id, ...product }) => ({ ...product, productModelId: id, // 选择工序 - 新增多条记录 const handleSelectProcess = () => { if (!selectedProcessId.value || selectedProcessId.value.length === 0) { proxy?.$modal?.msgWarning("请选择工序"); return; } // 为每个选中的工序创建一条记录 const newItems = selectedProcessId.value.map(processId => ({ processId: processId, productName: "", model: "", unit: "", productModelId: undefined, routeId: routeId.value, id: `${Date.now()}-${Math.random().toString(36).slice(2)}`, processId: undefined id: `${Date.now()}-${Math.random().toString(36).slice(2)}-${processId}`, })); console.log('选择产品前数组:', routeItems.value); routeItems.value.push(...newData); routeItems.value = [...routeItems.value]; console.log('选择产品后数组:', routeItems.value); routeItems.value.push(...newItems); // 延迟初始化,确保DOM完全渲染 nextTick(() => { // 强制重新渲染组件 if (proxy?.$forceUpdate) { proxy.$forceUpdate(); } const temp = [...routeItems.value]; routeItems.value = []; nextTick(() => { routeItems.value = temp; initSortable(); }); initSortable(); }); isShowProcessSelectDialog.value = false; selectedProcessId.value = []; }; // 为指定行选择产品 const handleSelectProductForRow = (row) => { currentSelectRow.value = row; isShowProductSelectDialog.value = true; }; // 处理当前行的产品选择 const handleSelectProductForCurrentRow = (products) => { if (products && products.length > 0 && currentSelectRow.value) { const product = products[0]; // 更新当前行的产品信息 currentSelectRow.value.productName = product.productName; currentSelectRow.value.model = product.model; currentSelectRow.value.unit = product.unit || ""; currentSelectRow.value.productModelId = product.id; isShowProductSelectDialog.value = false; currentSelectRow.value = null; } }; const findProcessRouteItems = () => {