| src/views/basicData/product/ProductSelectDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/processRouteItem/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/productionOrder/New.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/productionOrder/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/views/basicData/product/ProductSelectDialog.vue
@@ -18,7 +18,7 @@ <!-- 列表 --> <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="selection" width="55" :reserve-selection="true" /> <el-table-column type="index" label="序号" width="60" /> <el-table-column prop="productName" label="产品大类" min-width="160" /> <el-table-column prop="model" label="型号名称" min-width="200" /> @@ -54,7 +54,7 @@ const props = defineProps<{ modelValue: boolean; single?: boolean; // 是否只能选择一个,默认false(可选择多个) single?: boolean; }>(); const emit = defineEmits(['update:modelValue', 'confirm']); @@ -78,36 +78,59 @@ const tableData = ref<ProductRow[]>([]); const total = ref(0); const multipleSelection = ref<ProductRow[]>([]); const selectedMap = ref<Map<number, ProductRow>>(new Map()); const tableRef = ref(); function close() { visible.value = false; } function syncCurrentPageSelection() { nextTick(() => { if (!tableRef.value) { return; } tableData.value.forEach((item) => { tableRef.value.toggleRowSelection(item, selectedMap.value.has(item.id)); }); }); } const handleSelectionChange = (val: ProductRow[]) => { if (props.single && val.length > 1) { // 如果限制为单个选择,只保留最后一个选中的 const lastSelected = val[val.length - 1]; multipleSelection.value = [lastSelected]; // 清空表格选中状态,然后重新选中最后一个 selectedMap.value = new Map(lastSelected ? [[lastSelected.id, lastSelected]] : []); nextTick(() => { if (tableRef.value) { tableRef.value.clearSelection(); if (lastSelected) { tableRef.value.toggleRowSelection(lastSelected, true); } }); } else { multipleSelection.value = val; } }); return; } // 处理单个选择 const currentPageIds = new Set(tableData.value.map((item) => item.id)); currentPageIds.forEach((id) => { selectedMap.value.delete(id); }); val.forEach((item) => { selectedMap.value.set(item.id, item); }); multipleSelection.value = Array.from(selectedMap.value.values()); } const handleSelect = (selection: ProductRow[], row: ProductRow) => { if (props.single) { // 如果限制为单个,清空其他选择,只保留当前行 if (!props.single) { return; } if (selection.includes(row)) { // 选中当前行时,清空其他选中 multipleSelection.value = [row]; selectedMap.value = new Map([[row.id, row]]); nextTick(() => { if (tableRef.value) { tableData.value.forEach((item) => { @@ -117,7 +140,6 @@ }); } }); } } } @@ -139,7 +161,7 @@ function onConfirm() { if (multipleSelection.value.length === 0) { ElMessage.warning("请选择一条产品"); ElMessage.warning("请选择产品"); return; } if (props.single && multipleSelection.value.length > 1) { @@ -153,24 +175,26 @@ async function loadData() { loading.value = true; try { multipleSelection.value = []; // 翻页/搜索后清空选择更符合预期 const res: any = await productModelList({ productName: query.productName.trim(), model: query.model.trim(), current: page.pageNum, size: page.pageSize, }); tableData.value = res.records; total.value = res.total; tableData.value = res.records || []; total.value = res.total || 0; syncCurrentPageSelection(); } finally { loading.value = false; } } // 监听弹窗打开,重置选择 watch(() => props.modelValue, (visible) => { if (visible) { multipleSelection.value = []; selectedMap.value = new Map(); page.pageNum = 1; loadData(); } }); src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -179,18 +179,27 @@ </el-select> </el-form-item> <el-form-item label="产品名称" prop="productModelId"> <el-form-item label="产品名称" prop="selectedProducts"> <el-button type="primary" @click="showProductSelectDialog = true"> {{ form.productName && form.model ? `${form.productName} - ${form.model}` : '选择产品' }} {{ form.selectedProducts.length ? '重新选择产品' : '选择产品' }} </el-button> <div v-if="form.selectedProducts.length" class="selected-product-tags"> <el-tag v-for="product in form.selectedProducts" :key="product.id" class="selected-product-tag" type="info" effect="plain" > {{ product.productName }} - {{ product.model }} </el-tag> </div> </el-form-item> <el-form-item label="单位" prop="unit"> <el-input v-model="form.unit" :placeholder="form.productModelId ? '根据选择的产品自动带出' : '请先选择产品'" :placeholder="form.selectedProducts.length === 1 ? '根据选择的产品自动带出' : '多个产品时不展示单个单位'" clearable :disabled="true" /> @@ -211,7 +220,6 @@ <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single /> </div> </template> @@ -269,6 +277,8 @@ routeId: routeId.value, processId: undefined, productModelId: undefined, productModelIds: "", selectedProducts: [], productName: "", model: "", unit: "", @@ -277,94 +287,118 @@ const rules = { processId: [{ required: true, message: '请选择工序', trigger: 'change' }], productModelId: [{ required: true, message: '请选择产品', trigger: 'change' }], selectedProducts: [{ required: true, validator: (_, value, callback) => { if (Array.isArray(value) && value.length > 0) { callback(); return; } callback(new Error('请选择产品')); }, trigger: 'change', }], }; // 根据工序ID获取工序名称 const getProcessName = (processId) => { if (!processId) return ''; const process = processOptions.value.find(p => p.id === processId); const process = processOptions.value.find((p) => p.id === processId); return process ? process.name : ''; }; // 获取列表 const getList = () => { tableLoading.value = true; const listPromise = pageType.value === "order" pageType.value === 'order' ? findProductProcessRouteItemList({ orderId: orderId.value }) : findProcessRouteItemList({ routeId: routeId.value }); listPromise .then(res => { .then((res) => { tableData.value = res.data || []; tableLoading.value = false; // 列表加载完成后初始化拖拽排序 nextTick(() => { initSortable(); }); }) .catch(err => { .catch((err) => { tableLoading.value = false; console.error("获取列表失败:", err); proxy?.$modal?.msgError("获取列表失败"); console.error('获取列表失败:', err); proxy?.$modal?.msgError('获取列表失败'); }); }; // 获取工序列表 const getProcessList = () => { processList({}) .then(res => { .then((res) => { processOptions.value = res.data || []; }) .catch(err => { console.error("获取工序失败:", err); .catch((err) => { console.error('获取工序失败:', err); }); }; // 获取工艺路线详情(从路由参数获取) const getRouteInfo = () => { routeInfo.value = { processRouteCode: route.query.processRouteCode || '', productName: route.query.productName || '', model: route.query.model || '', bomNo: route.query.bomNo || '', description: route.query.description || '' description: route.query.description || '', }; }; // 新增 const getEditSelectedProducts = (row) => { const idList = String(row.productModelIds || row.productModelId || '') .split(',') .map((item) => item.trim()) .filter(Boolean); const nameList = String(row.productName || '') .split(',') .map((item) => item.trim()); const modelList = String(row.model || '') .split(',') .map((item) => item.trim()); return idList.map((id, index) => ({ id: Number(id) || id, productName: nameList[index] || row.productName || '', model: modelList[index] || row.model || '', unit: row.unit || '', })); }; const handleAdd = () => { operationType.value = 'add'; resetForm(); dialogVisible.value = true; }; // 编辑 const handleEdit = (row) => { operationType.value = 'edit'; const selectedProducts = getEditSelectedProducts(row); form.value = { id: row.id, routeId: routeId.value, processId: row.processId, productModelId: row.productModelId, productName: row.productName || "", model: row.model || "", unit: row.unit || "", productModelIds: row.productModelIds || (row.productModelId ? String(row.productModelId) : ''), selectedProducts, productName: row.productName || '', model: row.model || '', unit: row.unit || '', isQuality: row.isQuality, }; dialogVisible.value = true; }; // 删除 const handleDelete = (row) => { ElMessageBox.confirm('确认删除该工艺路线项目?', '提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' type: 'warning', }) .then(() => { // 生产订单下使用 productProcessRoute 的删除接口(路由后拼接 id),其它情况使用工艺路线项目批量删除接口 const deletePromise = pageType.value === 'order' ? deleteRouteItem(row.id) @@ -382,29 +416,29 @@ .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.model = product.model; form.value.unit = product.unit || ""; const firstProduct = products[0]; form.value.selectedProducts = products; form.value.productModelIds = products.map((item) => item.id).join(','); form.value.productModelId = products.length === 1 ? firstProduct.id : undefined; form.value.productName = products.length === 1 ? firstProduct.productName : ''; form.value.model = products.length === 1 ? firstProduct.model : ''; form.value.unit = products.length === 1 ? (firstProduct.unit || '') : ''; showProductSelectDialog.value = false; // 触发表单验证 formRef.value?.validateField('productModelId'); formRef.value?.validateField('selectedProducts'); } }; // 提交 const handleSubmit = () => { formRef.value.validate((valid) => { if (valid) { if (!valid) { return; } submitLoading.value = true; if (operationType.value === 'add') { // 新增:传单个对象,包含dragSort字段 // dragSort = 当前列表长度 + 1,表示新增记录排在最后 const dragSort = tableData.value.length + 1; const isOrderPage = pageType.value === 'order'; @@ -414,6 +448,7 @@ productRouteId: routeId.value, processId: form.value.processId, productModelId: form.value.productModelId, productModelIds: form.value.productModelIds, isQuality: form.value.isQuality, dragSort, }) @@ -421,6 +456,7 @@ routeId: routeId.value, processId: form.value.processId, productModelId: form.value.productModelId, productModelIds: form.value.productModelIds, isQuality: form.value.isQuality, dragSort, }); @@ -437,21 +473,23 @@ .finally(() => { submitLoading.value = false; }); } else { // 编辑:生产订单下使用 productProcessRoute/updateRouteItem,其它情况使用工艺路线项目更新接口 const isOrderPage = pageType.value === 'order'; return; } const isOrderPage = pageType.value === 'order'; const updatePromise = isOrderPage ? addOrUpdateProductProcessRouteItem({ id: form.value.id, processId: form.value.processId, productModelId: form.value.productModelId, productModelIds: form.value.productModelIds, isQuality: form.value.isQuality, }) : addOrUpdateProcessRouteItem({ routeId: routeId.value, processId: form.value.processId, productModelId: form.value.productModelId, productModelIds: form.value.productModelIds, id: form.value.id, isQuality: form.value.isQuality, }); @@ -468,26 +506,25 @@ .finally(() => { submitLoading.value = false; }); } } }); }; // 重置表单 const resetForm = () => { form.value = { id: undefined, routeId: routeId.value, processId: undefined, productModelId: undefined, productName: "", model: "", unit: "", productModelIds: '', selectedProducts: [], productName: '', model: '', unit: '', isQuality: false, }; formRef.value?.resetFields(); }; // 关闭弹窗 const closeDialog = () => { dialogVisible.value = false; resetForm(); @@ -753,6 +790,18 @@ margin: 10px 0; } .selected-product-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; } .selected-product-tag { max-width: 100%; } .card-footer { display: flex; justify-content: space-around; src/views/productionManagement/productionOrder/New.vue
@@ -6,7 +6,12 @@ width="800" @close="closeModal" > <el-form label-width="140px" :model="formState" label-position="top" ref="formRef"> <el-form ref="formRef" :model="formState" label-width="140px" label-position="top" > <el-form-item label="产品名称" prop="productModelId" @@ -15,54 +20,118 @@ required: true, message: '请选择产品', trigger: 'change', } }, ]" > <el-button type="primary" @click="showProductSelectDialog = true"> {{ formState.productName ? formState.productName : '选择产品' }} {{ formState.productName || "选择产品" }} </el-button> </el-form-item> <el-form-item label="规格" prop="productModelName" > <el-form-item label="规格" prop="productModelName"> <el-input v-model="formState.productModelName" disabled /> </el-form-item> <el-form-item label="单位" prop="unit" > <el-form-item label="单位" prop="unit"> <el-input v-model="formState.unit" disabled /> </el-form-item> <el-form-item label="工艺路线"> <el-select v-model="formState.routeId" <el-form-item label="工艺路线" prop="routeId" :rules="[ { required: true, message: '请选择工艺路线', trigger: 'change', }, ]" > <el-select v-model="formState.routeId" placeholder="请选择工艺路线" style="width: 100%;" :loading="bindRouteLoading"> <el-option v-for="item in routeOptions" style="width: 100%" :loading="bindRouteLoading" @change="handleRouteChange" > <el-option v-for="item in routeOptions" :key="item.id" :label="`${item.processRouteCode || ''}`" :value="item.id" /> :label="item.processRouteCode || ''" :value="item.id" /> </el-select> </el-form-item> <el-form-item label="需求数量" prop="quantity" v-if="processListData.length" label="工序报工人" prop="processUserList" :rules="[ { validator: validateProcessUsers, trigger: 'change', }, ]" > <el-input-number v-model="formState.quantity" :step="1" :min="1" style="width: 100%" /> <div class="process-user-list"> <div v-for="(item, index) in processListData" :key="item.id || `${item.processId}-${index}`" class="process-user-item" > <div class="process-user-header"> <div class="process-user-name"> {{ item.name || item.processName || item.no || `工序${index + 1}` }} </div> <el-button type="danger" link class="process-user-remove" @click="removeProcessItem(index)" > 删除 </el-button> </div> <el-select v-model="formState.processUserList[index].userIds" placeholder="请选择报工人" class="process-user-select" filterable clearable multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="3" @change="handleProcessUserChange(index, $event)" > <el-option v-for="user in userOptions" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> </div> </div> </el-form-item> <el-form-item label="需求数量" prop="quantity"> <el-input-number v-model="formState.quantity" :step="1" :min="1" style="width: 100%" /> </el-form-item> </el-form> <!-- 产品选择弹窗 --> <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single @confirm="handleProductSelect" /> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="handleSubmit">确认</el-button> @@ -74,115 +143,234 @@ </template> <script setup> import {ref, computed, getCurrentInstance} from "vue"; import { computed, getCurrentInstance, nextTick, ref } from "vue"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import {addProductOrder, listProcessRoute} from "@/api/productionManagement/productionOrder.js"; import { processList } from "@/api/productionManagement/productionProcess.js"; import { userListNoPageByTenantId } from "@/api/system/user.js"; const props = defineProps({ visible: { type: Boolean, required: true, }, type: { type: String, required: true, default: 'qualified', default: "qualified", }, }); const emit = defineEmits(['update:visible', 'completed']); const emit = defineEmits(["update:visible", "completed"]); // 响应式数据(替代选项式的 data) const formState = ref({ const createDefaultFormState = () => ({ productId: undefined, productModelId: undefined, routeId: undefined, productName: "", productModelName: "", unit: "", quantity: 0, quantity: 1, processUserList: [], }); const formState = ref(createDefaultFormState()); const isShow = computed({ get() { return props.visible; }, set(val) { emit('update:visible', val); emit("update:visible", val); }, }); const showProductSelectDialog = ref(false); const routeOptions = ref([]); const bindRouteLoading = ref(false); const processListData = ref([]); const userOptions = ref([]); const userLoading = ref(false); let { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance(); const formRef = ref(); const validateProcessUserField = async () => { await nextTick(); if (!formRef.value) return; if (processListData.value.length) { formRef.value.validateField("processUserList"); return; } formRef.value.clearValidate("processUserList"); }; const resetProcessUsers = () => { processListData.value = []; formState.value.processUserList = []; }; const closeModal = () => { // 重置表单数据 formState.value = { productId: undefined, productModelId: undefined, routeId: undefined, productName: "", productModelName: "", quantity: '', }; formState.value = createDefaultFormState(); routeOptions.value = []; resetProcessUsers(); isShow.value = false; }; // 产品选择处理 const handleProductSelect = async (products) => { if (products && products.length > 0) { const ensureUserOptions = () => { if (userOptions.value.length || userLoading.value) return; userLoading.value = true; userListNoPageByTenantId() .then(res => { userOptions.value = res.data || []; }) .finally(() => { userLoading.value = false; }); }; const createProcessUserList = list => list.map(item => ({ processId: item.id, processName: item.name || item.processName || item.no || "", userIds: [], userNames: "", })); const fetchProcessList = routeId => { if (!routeId) { resetProcessUsers(); return; } processList({ routeId }) .then(res => { processListData.value = res.data || []; formState.value.processUserList = createProcessUserList(processListData.value); ensureUserOptions(); validateProcessUserField(); }) .catch(() => { resetProcessUsers(); }); }; const handleRouteChange = routeId => { fetchProcessList(routeId); }; const handleProcessUserChange = (index, userIds) => { const selectedUsers = userOptions.value.filter(user => Array.isArray(userIds) && userIds.includes(user.userId)); formState.value.processUserList[index].userNames = selectedUsers.map(user => user.nickName).join(","); validateProcessUserField(); }; const removeProcessItem = index => { processListData.value.splice(index, 1); formState.value.processUserList.splice(index, 1); validateProcessUserField(); }; const validateProcessUsers = (_, value, callback) => { if (!formState.value.routeId) { callback(); return; } if (!processListData.value.length) { callback(new Error("当前工艺路线下没有工序")); return; } if (!Array.isArray(value) || value.length !== processListData.value.length) { callback(new Error("请为每道工序选择报工人")); return; } const hasEmptyUser = value.some(item => !Array.isArray(item.userIds) || item.userIds.length === 0); if (hasEmptyUser) { callback(new Error("请为每道工序选择报工人")); return; } callback(); }; const handleProductSelect = products => { if (!products?.length) return; const product = products[0]; formState.value.productId = product.productId; formState.value.productName = product.productName; formState.value.productModelName = product.model; formState.value.productModelId = product.id; formState.value.unit = product.unit; formState.value.routeId = undefined; routeOptions.value = []; resetProcessUsers(); showProductSelectDialog.value = false; fetchRouteOptions( product.id); // 触发表单验证更新 proxy.$refs["formRef"]?.validateField('productModelId'); } formRef.value?.validateField("productModelId"); }; const routeOptions = ref([]); const bindRouteLoading = ref(false); const fetchRouteOptions = (productModelId) => { const fetchRouteOptions = productModelId => { formState.value.routeId = undefined; routeOptions.value = [] routeOptions.value = []; resetProcessUsers(); bindRouteLoading.value = true; listProcessRoute({ productModelId: productModelId }).then(res => { listProcessRoute({ productModelId }) .then(res => { routeOptions.value = res.data || []; }).finally(() => { bindRouteLoading.value = false; }) } .finally(() => { bindRouteLoading.value = false; }); }; const buildProcessRouteItems = () => processListData.value.map((item, index) => { const processUser = formState.value.processUserList[index] || {}; return { productOrderId: undefined, productRouteId: formState.value.routeId, processId: item.id, productModelId: formState.value.productModelId, dragSort: item.dragSort ?? index + 1, isQuality: item.isQuality ?? false, reportUserIds: Array.isArray(processUser.userIds) ? processUser.userIds.join(",") : "", }; }); const handleSubmit = () => { proxy.$refs["formRef"].validate(valid => { if (valid) { // 验证是否选择了产品和规格 formRef.value.validate(valid => { if (!valid) return; if (!formState.value.productModelId) { proxy.$modal.msgError("请选择产品"); return; } if (!formState.value.productModelId) { proxy.$modal.msgError("请选择规格"); if (!formState.value.routeId) { proxy.$modal.msgError("请选择工艺路线"); return; } if (!formState.value.processUserList.length) { proxy.$modal.msgError("当前工艺路线下没有工序"); return; } if (formState.value.processUserList.some(item => !Array.isArray(item.userIds) || item.userIds.length === 0)) { proxy.$modal.msgError("请为每道工序选择报工人"); return; } addProductOrder(formState.value).then(res => { // 关闭模态框 addProductOrder({ ...formState.value, processRouteItems: buildProcessRouteItems(), }).then(() => { isShow.value = false; // 告知父组件已完成 emit('completed'); emit("completed"); proxy.$modal.msgSuccess("提交成功"); }) } }) }); }); }; defineExpose({ closeModal, @@ -190,3 +378,55 @@ isShow, }); </script> <style scoped> .process-user-list { width: 100%; display: flex; flex-direction: column; gap: 14px; padding: 14px; border-radius: 12px; background: #f7f9fc; border: 1px solid #e8eef5; } .process-user-item { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 12px 14px; border-radius: 10px; background: #fff; border: 1px solid #edf2f7; } .process-user-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .process-user-name { color: #1f2d3d; font-weight: 500; line-height: 1.4; } .process-user-remove { flex-shrink: 0; padding: 0; } .process-user-select { width: 100%; } @media (max-width: 768px) { .process-user-item { grid-template-columns: 1fr; gap: 10px; } } </style> src/views/productionManagement/productionOrder/index.vue
@@ -1,43 +1,49 @@ <template> <div class="app-container"> <div class="search_form"> <el-form :model="searchForm" :inline="true"> <el-form-item label="客户名称:"> <el-input v-model="searchForm.customerName" <el-form :model="searchForm" :inline="true"> <el-form-item label="客户名称"> <el-input v-model="searchForm.customerName" placeholder="请输入" clearable prefix-icon="Search" :prefix-icon="Search" style="width: 160px;" @change="handleQuery" /> @change="handleQuery" /> </el-form-item> <el-form-item label="合同号:"> <el-input v-model="searchForm.salesContractNo" <el-form-item label="销售合同号"> <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search" :prefix-icon="Search" style="width: 160px;" @change="handleQuery" /> @change="handleQuery" /> </el-form-item> <el-form-item label="产品名称:"> <el-input v-model="searchForm.productCategory" <el-form-item label="产品名称"> <el-input v-model="searchForm.productCategory" placeholder="请输入" clearable prefix-icon="Search" :prefix-icon="Search" style="width: 160px;" @change="handleQuery" /> @change="handleQuery" /> </el-form-item> <el-form-item label="规格:"> <el-input v-model="searchForm.specificationModel" <el-form-item label="规格型号"> <el-input v-model="searchForm.specificationModel" placeholder="请输入" clearable prefix-icon="Search" :prefix-icon="Search" style="width: 160px;" @change="handleQuery" /> @change="handleQuery" /> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">搜索</el-button> <el-button type="primary" @click="handleQuery">查询</el-button> </el-form-item> </el-form> <div> @@ -46,8 +52,10 @@ <el-button @click="handleOut">导出</el-button> </div> </div> <div class="table_list"> <PIMTable rowKey="id" <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" @@ -55,7 +63,8 @@ :row-class-name="tableRowClassName" :isSelection="true" @selection-change="handleSelectionChange" @pagination="pagination"> @pagination="pagination" > <template #completionStatus="{ row }"> <el-progress :percentage="toProgressPercentage(row?.completionStatus)" @@ -65,56 +74,114 @@ </template> </PIMTable> </div> <el-dialog v-model="bindRouteDialogVisible" <el-dialog v-model="bindRouteDialogVisible" title="绑定工艺路线" width="500px"> width="700px" > <el-form label-width="90px"> <el-form-item label="工艺路线"> <el-select v-model="bindForm.routeId" <el-select v-model="bindForm.routeId" placeholder="请选择工艺路线" style="width: 100%;" :loading="bindRouteLoading"> <el-option v-for="item in routeOptions" :loading="bindRouteLoading" @change="handleBindRouteChange" > <el-option v-for="item in routeOptions" :key="item.id" :label="`${item.processRouteCode || ''}`" :value="item.id" /> :label="item.processRouteCode || ''" :value="item.id" /> </el-select> </el-form-item> <el-form-item v-if="bindProcessList.length" label="报工人员"> <div class="process-user-list"> <div v-for="(item, index) in bindProcessList" :key="item.id || `${item.processId}-${index}`" class="process-user-item" > <div class="process-user-header"> <div class="process-user-name"> {{ item.name || item.processName || item.no || `工序${index + 1}` }} </div> <el-button type="danger" link class="process-user-remove" @click="removeBindProcessItem(index)" > 删除 </el-button> </div> <el-select v-model="bindForm.processUserList[index].userIds" class="process-user-select" placeholder="请选择报工人员" filterable clearable multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="3" @change="handleBindProcessUserChange(index, $event)" > <el-option v-for="user in userOptions" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> </div> </div> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button type="primary" <el-button type="primary" :loading="bindRouteSaving" @click="handleBindRouteConfirm">确 认</el-button> @click="handleBindRouteConfirm" > 确认 </el-button> <el-button @click="bindRouteDialogVisible = false">取 消</el-button> </span> </template> </el-dialog> <new-product-order v-if="isShowNewModal" <new-product-order v-if="isShowNewModal" v-model:visible="isShowNewModal" @completed="handleQuery" /> @completed="handleQuery" /> </div> </template> <script setup> import { onMounted, ref } from "vue"; import { defineAsyncComponent, getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue"; import { ElMessageBox } from "element-plus"; import { Search } from "@element-plus/icons-vue"; import dayjs from "dayjs"; import { useRouter } from "vue-router"; import { productOrderListPage, listProcessRoute, bindingRoute, listProcessBom, delProductOrder, delProductOrder, listProcessRoute, productOrderListPage, } from "@/api/productionManagement/productionOrder.js"; import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js"; import {fileDel} from "@/api/financialManagement/revenueManagement.js"; import { processList } from "@/api/productionManagement/productionProcess.js"; import { userListNoPageByTenantId } from "@/api/system/user.js"; import PIMTable from "@/components/PIMTable/PIMTable.vue"; const NewProductOrder = defineAsyncComponent(() => import("@/views/productionManagement/productionOrder/New.vue")); const { proxy } = getCurrentInstance(); const router = useRouter(); const isShowNewModal = ref(false); @@ -122,32 +189,32 @@ { label: "生产订单号", prop: "npsNo", width: '120px', width: "120px", }, { label: "销售合同号", prop: "salesContractNo", width: '150px', width: "150px", }, { label: "客户名称", prop: "customerName", width: '200px', width: "200px", }, { label: "产品名称", prop: "productCategory", width: '120px', width: "120px", }, { label: "规格", label: "规格型号", prop: "specificationModel", width: '120px', width: "120px", }, { label: "工艺路线编号", prop: "processRouteCode", width: '200px', width: "200px", }, { label: "需求数量", @@ -214,6 +281,7 @@ ], }, ]); const tableData = ref([]); const tableLoading = ref(false); const page = reactive({ @@ -236,13 +304,11 @@ const toProgressPercentage = val => { const n = Number(val); if (!Number.isFinite(n)) return 0; if (n <= 0) return 0; if (!Number.isFinite(n) || n <= 0) return 0; if (n >= 100) return 100; return Math.round(n); }; // 30/50/80/100 分段颜色:红/橙/蓝/绿 const progressColor = percentage => { const p = toProgressPercentage(percentage); if (p < 30) return "#f56c6c"; @@ -251,49 +317,124 @@ return "#67c23a"; }; // 添加表行类名方法 const tableRowClassName = ({ row }) => { if (!row.deliveryDate) return ''; if (row.isFh) return ''; if (!row.deliveryDate || row.isFh) return ""; const diff = row.deliveryDaysDiff; if (diff === 15) { return 'yellow'; } else if (diff === 10) { return 'pink'; } else if (diff === 2) { return 'purple'; } else if (diff < 2) { return 'red'; } if (diff === 15) return "yellow"; if (diff === 10) return "pink"; if (diff === 2) return "purple"; if (diff < 2) return "red"; return ""; }; // 绑定工艺路线弹框 const bindRouteDialogVisible = ref(false); const bindRouteLoading = ref(false); const bindRouteSaving = ref(false); const routeOptions = ref([]); const bindProcessList = ref([]); const userOptions = ref([]); const userLoading = ref(false); const bindForm = reactive({ orderId: null, productModelId: null, routeId: null, processUserList: [], }); const resetBindProcessUsers = () => { bindProcessList.value = []; bindForm.processUserList = []; }; const ensureUserOptions = () => { if (userOptions.value.length || userLoading.value) return; userLoading.value = true; userListNoPageByTenantId() .then(res => { userOptions.value = res.data || []; }) .finally(() => { userLoading.value = false; }); }; const createBindProcessUserList = list => list.map(item => ({ processId: item.id, processName: item.name || item.processName || item.no || "", userIds: [], userNames: "", })); const buildBindProcessRouteItems = () => bindProcessList.value.map((item, index) => { const processUser = bindForm.processUserList[index] || {}; return { productOrderId: bindForm.orderId, productRouteId: bindForm.routeId, processId: item.id, productModelId: bindForm.productModelId, dragSort: item.dragSort ?? index + 1, isQuality: item.isQuality ?? false, reportUserIds: Array.isArray(processUser.userIds) ? processUser.userIds.join(",") : "", }; }); const fetchBindProcessList = async routeId => { if (!routeId) { resetBindProcessUsers(); return; } try { const res = await processList({ routeId }); bindProcessList.value = res.data || []; bindForm.processUserList = createBindProcessUserList(bindProcessList.value); ensureUserOptions(); } catch (error) { console.error("获取工序列表失败", error); proxy.$modal.msgError("获取工序列表失败"); resetBindProcessUsers(); } }; const handleBindRouteChange = routeId => { fetchBindProcessList(routeId); }; const handleBindProcessUserChange = (index, userIds) => { const selectedUsers = userOptions.value.filter(user => Array.isArray(userIds) && userIds.includes(user.userId)); bindForm.processUserList[index].userNames = selectedUsers.map(user => user.nickName).join(","); }; const removeBindProcessItem = index => { bindProcessList.value.splice(index, 1); bindForm.processUserList.splice(index, 1); }; const openBindRouteDialog = async row => { bindForm.orderId = row.id; bindForm.productModelId = row.productModelId ?? null; bindForm.routeId = null; bindForm.processUserList = []; bindRouteDialogVisible.value = true; routeOptions.value = []; resetBindProcessUsers(); if (!row.productModelId) { proxy.$modal.msgWarning("当前订单缺少产品型号,无法查询工艺路线"); bindRouteDialogVisible.value = false; return; } bindRouteLoading.value = true; try { const res = await listProcessRoute({ productModelId: row.productModelId }); routeOptions.value = res.data || []; } catch (e) { console.error("获取工艺路线列表失败:", e); } catch (error) { console.error("获取工艺路线列表失败", error); proxy.$modal.msgError("获取工艺路线列表失败"); } finally { bindRouteLoading.value = false; @@ -305,56 +446,59 @@ proxy.$modal.msgWarning("请选择工艺路线"); return; } if (!bindForm.processUserList.length) { proxy.$modal.msgWarning("当前工艺路线下没有工序"); return; } if (bindForm.processUserList.some(item => !Array.isArray(item.userIds) || item.userIds.length === 0)) { proxy.$modal.msgWarning("请为每道工序选择报工人员"); return; } bindRouteSaving.value = true; try { await bindingRoute({ id: bindForm.orderId, routeId: bindForm.routeId, processRouteItems: buildBindProcessRouteItems(), processUserList: bindForm.processUserList.map(item => ({ ...item, userIds: item.userIds.join(","), })), }); proxy.$modal.msgSuccess("绑定成功"); bindRouteDialogVisible.value = false; getList(); } catch (e) { console.error("绑定工艺路线失败:", e); } catch (error) { console.error("绑定工艺路线失败", error); proxy.$modal.msgError("绑定工艺路线失败"); } finally { bindRouteSaving.value = false; } }; // 查询列表 /** 搜索按钮操作 */ const handleQuery = () => { page.current = 1; getList(); }; const pagination = obj => { page.current = obj.page; page.size = obj.limit; getList(); }; const changeDaterange = value => { if (value) { searchForm.value.entryDateStart = value[0]; searchForm.value.entryDateEnd = value[1]; } else { searchForm.value.entryDateStart = undefined; searchForm.value.entryDateEnd = undefined; } handleQuery(); }; const getList = () => { tableLoading.value = true; // 构造一个新的对象,不包含entryDate字段 const params = { ...searchForm.value, ...page }; params.entryDate = undefined; productOrderListPage(params) .then(res => { tableLoading.value = false; tableData.value = res.data.records; page.total = res.data.total; }) .catch(() => { .finally(() => { tableLoading.value = false; }); }; @@ -363,26 +507,27 @@ const orderId = row.id; try { const res = await getOrderProcessRouteMain(orderId); const data = res.data || {}; if (!data || !data.id) { const detail = res.data || {}; if (!detail.id) { proxy.$modal.msgWarning("未找到关联的工艺路线"); return; } router.push({ path: "/productionManagement/processRouteItem", query: { id: data.id, processRouteCode: data.processRouteCode || "", productName: data.productName || "", model: data.model || "", bomNo: data.bomNo || "", description: data.description || "", id: detail.id, processRouteCode: detail.processRouteCode || "", productName: detail.productName || "", model: detail.model || "", bomNo: detail.bomNo || "", description: detail.description || "", orderId, type: "order", }, }); } catch (e) { console.error("获取工艺路线主信息失败:", e); } catch (error) { console.error("获取工艺路线信息失败", error); proxy.$modal.msgError("获取工艺路线信息失败"); } }; @@ -401,36 +546,34 @@ }); }; // 表格选择数据 const handleSelectionChange = (selection) => { const handleSelectionChange = selection => { selectedRows.value = selection; }; const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { ids = selectedRows.value.map((item) => item.id); } else { if (!selectedRows.value.length) { proxy.$modal.msgWarning("请选择数据"); return; } ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", { const ids = selectedRows.value.map(item => item.id); ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "提示", { confirmButtonText: "确认", cancelButtonText: "取消", type: "warning", }).then(() => { delProductOrder(ids).then((res) => { }) .then(() => delProductOrder(ids)) .then(() => { proxy.$modal.msgSuccess("删除成功"); getList(); }); }).catch(() => { proxy.$modal.msg("已取消"); }) .catch(() => { proxy.$modal.msg("已取消删除"); }); }; // 导出 const handleOut = () => { ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", { ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "提示", { confirmButtonText: "确认", cancelButtonText: "取消", type: "warning", @@ -439,11 +582,9 @@ proxy.download("/productOrder/export", {...searchForm.value}, "生产订单.xlsx"); }) .catch(() => { proxy.$modal.msg("已取消"); proxy.$modal.msg("已取消导出"); }); }; const handleConfirmRoute = () => {}; onMounted(() => { getList(); @@ -455,19 +596,69 @@ align-items: start; } ::v-deep .yellow { background-color: #FAF0DE; :deep(.yellow) { background-color: #faf0de; } ::v-deep .pink { background-color: #FAE1DE; :deep(.pink) { background-color: #fae1de; } ::v-deep .red { :deep(.red) { background-color: #f80202; } ::v-deep .purple{ background-color: #F4DEFA; :deep(.purple) { background-color: #f4defa; } .process-user-list { width: 100%; display: flex; flex-direction: column; gap: 14px; padding: 14px; border-radius: 12px; background: #f7f9fc; border: 1px solid #e8eef5; } .process-user-item { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 12px 14px; border-radius: 10px; background: #fff; border: 1px solid #edf2f7; } .process-user-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .process-user-name { color: #1f2d3d; font-weight: 500; line-height: 1.4; } .process-user-remove { flex-shrink: 0; padding: 0; } .process-user-select { width: 100%; } @media (max-width: 768px) { .process-user-item { grid-template-columns: 1fr; gap: 10px; } } </style>