From 7d6c13e935ae257d23d761e7a38ab404fda534ed Mon Sep 17 00:00:00 2001 From: taoge1020 Date: Wed, 27 Aug 2025 19:50:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/catalog.xlsx | Bin 10371 -> 10398 bytes src/api/materials/batchPlan/index.ts | 10 + src/views/design/Professional/index.vue | 15 +- src/views/design/Professional/indexEdit.vue | 4 +- src/views/design/received/index.vue | 2 +- src/views/materials/batchPlan/index.vue | 624 +++++++++++++----- .../materialIssue/index.vue | 292 ++++++-- .../materialReceive/index.vue | 280 +++++--- src/views/materials/purchaseDoc/index.vue | 12 + src/views/patch/index.vue | 4 +- .../BasicData/landBlock/index.vue | 408 +++++++----- .../landTransferLedger/index.vue | 31 +- 12 files changed, 1212 insertions(+), 470 deletions(-) diff --git a/public/catalog.xlsx b/public/catalog.xlsx index 1cca4665b79f23a62eb39ced46de4a87c96baf4a..7ebf378fe38d1c080fa8619f4f5892002c54a5a0 100644 GIT binary patch delta 3872 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 LaH@HMf875A5y46x diff --git a/src/api/materials/batchPlan/index.ts b/src/api/materials/batchPlan/index.ts index c9231af..5b9690b 100644 --- a/src/api/materials/batchPlan/index.ts +++ b/src/api/materials/batchPlan/index.ts @@ -160,3 +160,13 @@ export const obtainTheVersion = (query: any) => { params: query }); }; +/** + * 获取到物资剩余量 + */ +export const mrpBaseRemaining = (query: any) => { + return request({ + url: '/cailiaoshebei/mrpBase/remaining', + method: 'get', + params: query + }); +}; diff --git a/src/views/design/Professional/index.vue b/src/views/design/Professional/index.vue index 017ce41..27952ed 100644 --- a/src/views/design/Professional/index.vue +++ b/src/views/design/Professional/index.vue @@ -38,7 +38,14 @@ - - @@ -103,7 +100,7 @@ - + 基础信息 @@ -125,63 +122,91 @@ 主要信息 - - + + + + - + + - + + - + + - + + - - + + + + + + + - - - + + + - + + + @@ -206,10 +231,47 @@ import { listSelectCailiaoshebei, obtainTheVersion, getDictList, - coryEngineeringList + coryEngineeringList, + mrpBaseRemaining } from '@/api/materials/batchPlan'; import { CailiaoshebeiVO, CailiaoshebeiQuery, CailiaoshebeiForm } from '@/api/materials/batchPlan/types'; import { useUserStoreHook } from '@/store/modules/user'; +import { getCurrentInstance, ComponentInternalInstance, watch, onMounted, onUnmounted } from 'vue'; +import type { ElFormInstance } from 'element-plus'; + +// 类型定义补充 +interface DialogOption { + visible: boolean; + title: string; +} + +interface PlanListItem { + id?: number | undefined; + name?: string | undefined; + specification?: string | undefined; + unit?: string | undefined; + suppliespriceId?: number | undefined; + demandQuantity?: number | undefined; + qs?: string | undefined; + arrivalTime?: string | undefined; + remark?: string | undefined; + Remaining: number; // 剩余量(必存在) + quantityError: string; // 数量错误提示 + versions?: string | undefined; // 版本号 + duplicateError: string; // 重复错误提示 +} + +interface FormData { + mrpBaseBo: { + id?: number | undefined; + preparedDate?: string | undefined; + planCode?: string | undefined; + matCat?: string | undefined; + status?: string | undefined; + projectId?: number | undefined; + }; + planList: PlanListItem[]; +} const { proxy } = getCurrentInstance() as ComponentInternalInstance; // 获取用户 store @@ -227,17 +289,17 @@ const multiple = ref(true); const total = ref(0); const mainTotal = ref(0); const batchOptions = ref([]); -const { wf_business_status } = toRefs(proxy?.useDict('wf_business_status')); +const { wf_business_status } = toRefs(proxy?.useDict('wf_business_status') || {}); const route = useRoute(); const queryFormRef = ref(); const cailiaoshebeiFormRef = ref(); - const dialog = reactive({ visible: false, title: '' }); -const initFormData: any = { +// 初始化表单数据(补充版本号、重复错误提示字段) +const initFormData: FormData = { mrpBaseBo: { id: undefined, preparedDate: undefined, @@ -246,7 +308,6 @@ const initFormData: any = { status: undefined, projectId: currentProject.value?.id }, - planList: [ { id: undefined, @@ -257,18 +318,22 @@ const initFormData: any = { demandQuantity: undefined, qs: undefined, arrivalTime: undefined, - remark: undefined + remark: undefined, + Remaining: 0, // 初始化剩余量 + quantityError: '', // 初始化数量错误提示 + versions: undefined, // 初始化版本号 + duplicateError: '' // 初始化重复错误提示 } ] }; + const data = reactive({ - form: { ...initFormData }, + form: { ...initFormData } as FormData, queryParams: { batchData: { pageNum: 1, pageSize: 10, planCode: undefined, - projectId: currentProject.value?.id }, mainData: { @@ -285,77 +350,184 @@ const data = reactive({ 'mrpBaseBo.matCat': [{ required: true, message: '物资分类不能为空', trigger: 'blur' }] } }); + const batchNumber = ref(''); const { queryParams, form, rules } = toRefs(data); -const nameList = ref([]); -const versionList = ref([]); +const nameList = ref([]); +const versionList = ref([]); + /** 查询物资-材料设备列表 */ const getList = async (type?: string) => { loading.value = true; - const res = await listBatch(queryParams.value.batchData); - batchOptions.value = res.rows; - if (res.rows && res.rows.length > 0 && !queryParams.value.mainData.mrpBaseId) { - batchTreeRef.value.setCurrentKey(res.rows[0].id); - queryParams.value.mainData.mrpBaseId = res.rows[0].id; - form.value.mrpBaseBo.status = res.rows[0].status; + try { + const res = await listBatch(queryParams.value.batchData); + batchOptions.value = res.rows || []; + // 自动选中第一条数据(如果存在且未选中) + if (batchOptions.value.length > 0 && !queryParams.value.mainData.mrpBaseId) { + batchTreeRef.value?.setCurrentKey(batchOptions.value[0].id); + queryParams.value.mainData.mrpBaseId = batchOptions.value[0].id; + form.value.mrpBaseBo.status = batchOptions.value[0].status; + } + total.value = res.total || 0; + } catch (error) { + proxy?.$modal.msgError('获取批次列表失败'); + } finally { + loading.value = false; + // 非搜索场景下同步加载主列表 + if (type !== 'search') { + getMainList(); + } } - total.value = res.total; - loading.value = false; - if (type === 'search') return; - getMainList(); }; -const selectName = (val: any, row: any) => { - const selected = nameList.value.find((item) => item.id === val); - if (selected) { - row.name = selected.name; - row.specification = selected.specification; - row.unit = selected.unit; - row.qs = selected.qs; - row.demandQuantity = selected.quantity; - row.remark = selected.remark; - row.arrivalTime = selected.arrivalTime; +/** 数量校验:必须≤剩余量,且为合法数字 */ +const validateDemandQuantity = (row: PlanListItem, index: number) => { + // 1. 清除之前的错误信息 + row.quantityError = ''; + + // 2. 处理空值(若需必填可补充:row.quantityError = '数量不能为空') + if (row.demandQuantity === null || row.demandQuantity === undefined || row.demandQuantity === '') { + return; } + + // 3. 处理非数字 + if (typeof row.demandQuantity !== 'number' || isNaN(row.demandQuantity)) { + row.quantityError = '请输入合法数字'; + return; + } + + // 4. 处理负数 + if (row.demandQuantity < 0) { + row.quantityError = '数量不能为负数'; + return; + } + + // 5. 核心校验:数量≤剩余量 + if (row.demandQuantity > row.Remaining) { + row.quantityError = `数量不能超过剩余量${row.Remaining}`; + } +}; + +/** 获取剩余量 */ +const getMrpBaseRemaining = async (suppliespriceId: number, row: PlanListItem) => { + try { + const res = await mrpBaseRemaining({ suppliespriceId }); + row.Remaining = res.data || 0; + // 剩余量更新后,重新校验当前数量 + validateDemandQuantity(row, 0); + } catch (error) { + proxy?.$modal.msgError('获取剩余量失败'); + row.Remaining = 0; + } +}; + +/** 校验重复数据:版本号+物资名称不能重复 */ +const checkDuplicate = () => { + const planList = form.value.planList; + let hasDuplicate = false; + + // 1. 清除所有重复错误提示 + planList.forEach((item) => { + item.duplicateError = ''; + }); + + // 2. 遍历校验重复(只校验版本号和物资名称都存在的行) + for (let i = 0; i < planList.length; i++) { + const current = planList[i]; + // 跳过版本号或物资名称为空的行 + if (!current.versions || !current.suppliespriceId) continue; + + for (let j = i + 1; j < planList.length; j++) { + const compare = planList[j]; + if (!compare.versions || !compare.suppliespriceId) continue; + + // 版本号和物资ID都相同则判定为重复 + if (current.versions === compare.versions && current.suppliespriceId === compare.suppliespriceId) { + current.duplicateError = `与第${j + 1}行重复(同版本+同物资)`; + compare.duplicateError = `与第${i + 1}行重复(同版本+同物资)`; + hasDuplicate = true; + } + } + } + + return hasDuplicate; +}; + +/** 选择物资名称触发(新增索引参数,用于触发重复校验) */ +const selectName = (val: number, row: PlanListItem, index: number) => { + // 1. 获取剩余量并更新基础信息 + getMrpBaseRemaining(val, row).then(() => { + const selected = nameList.value.find((item: any) => item.id === val); + if (selected) { + row.name = selected.name; + row.specification = selected.specification; + row.unit = selected.unit; + row.qs = selected.qs || ''; + row.remark = selected.remark || ''; + row.arrivalTime = selected.arrivalTime || ''; + } + + // 2. 触发重复校验 + checkDuplicate(); + }); }; /** 节点单击事件 */ const handleNodeClick = (data: any) => { queryParams.value.mainData.mrpBaseId = data.id; form.value.mrpBaseBo.status = data.status; - getMainList(); }; +/** 获取主列表数据 */ const getMainList = async () => { if (!queryParams.value.mainData.mrpBaseId) return; - const res = await getBatch(queryParams.value.mainData); - cailiaoshebeiList.value = res.rows; - mainTotal.value = res.total; + loading.value = true; + try { + const res = await getBatch(queryParams.value.mainData); + cailiaoshebeiList.value = res.rows || []; + mainTotal.value = res.total || 0; + } catch (error) { + proxy?.$modal.msgError('获取物资列表失败'); + } finally { + loading.value = false; + } }; +/** 搜索批次列表 */ const searchBatchList = async () => { queryParams.value.batchData.planCode = batchNumber.value; getList('search'); }; -//删除 +/** 删除表格行 */ const delRow = (index: number) => { - if (form.value.planList.length <= 1) return proxy?.$modal.msgWarning('请至少保留一项'); + if (form.value.planList.length <= 1) { + return proxy?.$modal.msgWarning('请至少保留一项物资数据'); + } form.value.planList.splice(index, 1); + // 删除后重新校验重复(避免删除重复行后错误提示残留) + checkDuplicate(); }; -//新增 +/** 新增表格行 */ const addRow = () => { - form.value.planList.push({ + const newRow: PlanListItem = { name: undefined, specification: undefined, unit: undefined, + suppliespriceId: undefined, demandQuantity: undefined, qs: undefined, arrivalTime: undefined, - remark: undefined - }); + remark: undefined, + Remaining: 0, + quantityError: '', + versions: undefined, + duplicateError: '' + }; + form.value.planList.push(newRow); }; /** 取消按钮 */ @@ -367,41 +539,22 @@ const cancel = () => { /** 表单重置 */ const reset = () => { const status = form.value.mrpBaseBo.status; - form.value = { ...initFormData, status }; // 重置但保留 + // 重置表单(保留状态字段) + form.value = { + ...JSON.parse(JSON.stringify(initFormData)), + mrpBaseBo: { ...initFormData.mrpBaseBo, status } + }; + // 重置表单验证状态 cailiaoshebeiFormRef.value?.resetFields(); + // 重置项目ID form.value.mrpBaseBo.projectId = currentProject.value?.id; - form.value.planList = [ - { - name: undefined, - specification: undefined, - unit: undefined, - suppliespriceId: undefined, - - demandQuantity: undefined, - qs: undefined, - arrivalTime: undefined, - remark: undefined - } - ]; }; -// /** 搜索按钮操作 */ -// const handleQuery = () => { -// queryParams.value.pageNum = 1; -// getList(); -// }; - -// /** 重置按钮操作 */ -// const resetQuery = () => { -// queryFormRef.value?.resetFields(); -// handleQuery(); -// }; - /** 多选框选中数据 */ const handleSelectionChange = (selection: CailiaoshebeiVO[]) => { ids.value = selection.map((item) => item.id); - single.value = selection.length != 1; - multiple.value = !selection.length; + single.value = selection.length !== 1; + multiple.value = selection.length === 0; }; /** 新增按钮操作 */ @@ -411,93 +564,154 @@ const handleAdd = () => { dialog.title = '新增物资-需求'; }; +/** 修改按钮操作 */ const handleUpdata = () => { - reset(); - getCailiaoshebei(queryParams.value.mainData.mrpBaseId).then((res: any) => { - form.value.mrpBaseBo = res.data.mrpBaseBo; - const allowedKeys = Object.keys(initFormData.planList[0]); - form.value.planList = res.data.planList.map((item) => { - return allowedKeys.reduce((obj, key) => { - obj[key] = item[key] ?? undefined; - return obj; - }, {}); - }); - - console.log(form.value.planList); - }); - dialog.visible = true; - dialog.title = '修改物资-需求'; -}; - -/** 提交数据 */ -const submitTransferForm = async () => { - const result = validateAndClean(form.value.planList); - if (!result.valid) { - proxy?.$modal.msgError(result.message); - return; + if (!queryParams.value.mainData.mrpBaseId) { + return proxy?.$modal.msgError('请先选择批次'); } + reset(); + loading.value = true; + getCailiaoshebei(queryParams.value.mainData.mrpBaseId) + .then((res: any) => { + // 1. 更新基础信息 + form.value.mrpBaseBo = res.data.mrpBaseBo || initFormData.mrpBaseBo; + // 2. 更新表格数据(补充缺失字段) + form.value.planList = (res.data.planList || []).map((item: any) => ({ + id: item.id, + name: item.name, + specification: item.specification, + unit: item.unit, + suppliespriceId: item.suppliespriceId, + demandQuantity: item.demandQuantity, + qs: item.qs, + arrivalTime: item.arrivalTime, + remark: item.remark, + Remaining: item.Remaining || 0, + quantityError: '', + versions: item.versions, + duplicateError: '' + })); + // 3. 打开对话框 + dialog.visible = true; + dialog.title = '修改物资-需求'; + }) + .catch(() => { + proxy?.$modal.msgError('获取详情失败'); + }) + .finally(() => { + loading.value = false; + }); +}; + +/** 提交数据(整合所有校验) */ +const submitTransferForm = async () => { + // 1. 先校验重复数据 + const hasDuplicate = checkDuplicate(); + if (hasDuplicate) { + return proxy?.$modal.msgError('存在重复的版本号+物资组合,请修正后提交'); + } + + // 2. 校验数量合法性(检查是否有数量错误) + const hasQuantityError = form.value.planList.some((row) => row.quantityError); + if (hasQuantityError) { + return proxy?.$modal.msgError('存在非法数量,请修正后提交'); + } + + // 3. 执行表单基础验证 + const result = validateAndClean(form.value.planList); + if (!result.valid) { + return proxy?.$modal.msgError(result.message); + } + + // 4. 表单组件验证 cailiaoshebeiFormRef.value?.validate(async (valid: boolean) => { - if (valid) { - buttonLoading.value = true; - form.value.planList = result.data; - await updateCailiaoshebei(form.value).finally(() => (buttonLoading.value = false)); + if (!valid) return; + + buttonLoading.value = true; + try { + // 5. 提交数据 + await updateCailiaoshebei({ + ...form.value, + planList: result.data // 使用清洗后的数据 + }); proxy?.$modal.msgSuccess('操作成功'); dialog.visible = false; + // 6. 刷新列表 await getList(); + } catch (error) { + proxy?.$modal.msgError('操作失败,请重试'); + } finally { + buttonLoading.value = false; } }); }; + /** 删除批次 */ const handleDeleteBatch = async () => { - const _ids = batchTreeRef.value.getCurrentNode()?.id; - await proxy?.$modal.confirm('是否确认删除批次编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false)); - await delBatch(_ids); - proxy?.$modal.msgSuccess('删除成功'); - queryParams.value.mainData.mrpBaseId = undefined; - await getList(); + const _id = batchTreeRef.value?.getCurrentNode()?.id; + if (!_id) { + return proxy?.$modal.msgError('请先选择批次'); + } + + try { + await proxy?.$modal.confirm('是否确认删除该批次?删除后不可恢复!'); + await delBatch(_id); + proxy?.$modal.msgSuccess('删除成功'); + // 重置选中状态并刷新列表 + queryParams.value.mainData.mrpBaseId = undefined; + form.value.mrpBaseBo.status = undefined; + await getList(); + } catch (error) { + // 取消确认时不提示错误 + if (error !== 'cancel') { + proxy?.$modal.msgError('删除失败'); + } + } }; -interface ValidateResult { - valid: boolean; - data: any[]; - errors: { index: number; field: string; message: string }[]; -} +/** 数据清洗与基础校验 */ +function validateAndClean(arr: PlanListItem[]) { + // 1. 过滤掉全空的数据项 + const cleanedArr = arr.filter((item) => !Object.values(item).every((v) => v === '' || v == null || v === undefined)); -function validateAndClean(arr: any[]) { - // 过滤掉全空的数据项 - const cleanedArr = arr.filter((item) => !Object.values(item).every((v) => v === '' || v == null)); - - let hasFullItem = false; // 是否有至少一条全填数据 + let hasFullItem = false; // 是否有至少一条完整数据 + // 2. 逐行校验必填项 for (let idx = 0; idx < cleanedArr.length; idx++) { const item = cleanedArr[idx]; - const keys = Object.keys(item).filter((k) => k !== 'remark' && k !== 'id'); - const allFilled = keys.every((k) => item[k] !== '' && item[k] != null); - const allEmpty = Object.values(item).every((v) => v === '' || v == null); + // 校验版本号 + if (!item.versions) { + return { valid: false, message: `第${idx + 1}行:版本号不能为空`, data: cleanedArr }; + } - // 单独检查 qs 和 arrivalTime + // 校验物资选择 + if (!item.suppliespriceId) { + return { valid: false, message: `第${idx + 1}行:请选择物资名称`, data: cleanedArr }; + } + + // 校验质量标准 if (!item.qs) { return { valid: false, message: `第${idx + 1}行:质量标准不能为空`, data: cleanedArr }; } + + // 校验需求到货时间 if (!item.arrivalTime) { return { valid: false, message: `第${idx + 1}行:需求到货时间不能为空`, data: cleanedArr }; } - // 检查其他字段是否部分填 - if (!allFilled && !allEmpty) { - return { valid: false, message: `第${idx + 1}行:主要信息存在部分字段缺失的数据项`, data: cleanedArr }; + // 校验数量(必填且≥0) + if (item.demandQuantity === null || item.demandQuantity === undefined || item.demandQuantity < 0) { + return { valid: false, message: `第${idx + 1}行:请输入合法的需求数量`, data: cleanedArr }; } - if (allFilled) { - hasFullItem = true; - } + hasFullItem = true; } - // 检查是否至少有一条完整的数据 + // 3. 检查是否至少有一条完整数据 if (!hasFullItem) { - return { valid: false, message: '至少需要一条完整的数据', data: cleanedArr }; + return { valid: false, message: '至少需要填写一条完整的物资数据', data: cleanedArr }; } return { valid: true, message: '', data: cleanedArr }; @@ -506,54 +720,95 @@ function validateAndClean(arr: any[]) { /** 审核按钮操作 */ const handleAudit = async () => { if (!form.value.mrpBaseBo.status) { - proxy?.$modal.msgError('请选择批次号'); - return; + return proxy?.$modal.msgError('请选择批次号'); } + if (!queryParams.value.mainData.mrpBaseId) { + return proxy?.$modal.msgError('请选择批次号'); + } + + // 关闭当前页并打开审核页 proxy?.$tab.closePage(route); proxy?.$tab.openPage('/approval/batchPlan/indexEdit', '审核物资设备批次需求计划', { id: queryParams.value.mainData.mrpBaseId, - status: form.value.mrpBaseBo.status + '_batchRequirements', + status: `${form.value.mrpBaseBo.status}_batchRequirements`, type: 'update' }); }; -const getNameList = (versions) => { - coryEngineeringList({ projectId: currentProject.value?.id, versions }).then((res) => { - nameList.value = res.data; - }); -}; -// 获取版本号 -const getVersion = () => { - obtainTheVersion({ projectId: currentProject.value?.id }).then((res) => { - versionList.value = res.data; - }); -}; -const selectNameVersion = (val, row) => { - row.suppliespriceId = undefined; - getNameList(val); +/** 获取物资列表(按版本号筛选) */ +const getNameList = (versions: string) => { + coryEngineeringList({ + projectId: currentProject.value?.id, + versions + }) + .then((res: any) => { + nameList.value = res.data || []; + }) + .catch(() => { + nameList.value = []; + proxy?.$modal.msgError('获取物资列表失败'); + }); }; +/** 获取版本号列表 */ +const getVersion = () => { + obtainTheVersion({ projectId: currentProject.value?.id }) + .then((res: any) => { + versionList.value = res.data || []; + }) + .catch(() => { + versionList.value = []; + proxy?.$modal.msgError('获取版本号失败'); + }); +}; + +/** 选择版本号触发(新增索引参数,用于触发重复校验) */ +const selectNameVersion = (val: string, row: PlanListItem, index: number) => { + row.versions = val; + row.suppliespriceId = undefined; // 切换版本号时清空物资选择 + row.name = undefined; + row.specification = undefined; + row.unit = undefined; + row.qs = undefined; + row.remark = undefined; + row.arrivalTime = undefined; + row.demandQuantity = undefined; + row.quantityError = ''; + row.duplicateError = ''; + + // 1. 获取对应版本的物资列表 + getNameList(val); + + // 2. 触发重复校验(清空物资后可能消除重复) + checkDuplicate(); +}; + +/** 页面挂载时初始化 */ onMounted(() => { getList(); - // getNameList(); getVersion(); }); -//监听项目id刷新数据 +/** 监听项目ID变化,刷新数据 */ const listeningProject = watch( () => currentProject.value?.id, - (nid, oid) => { - queryParams.value.mainData.projectId = nid; - queryParams.value.batchData.projectId = nid; - form.value.mrpBaseBo.projectId = nid; - getList(); + (newId, oldId) => { + if (newId !== oldId && newId) { + queryParams.value.mainData.projectId = newId; + queryParams.value.batchData.projectId = newId; + form.value.mrpBaseBo.projectId = newId; + getList(); + getVersion(); // 重新获取对应项目的版本号 + } } ); +/** 页面卸载时清理监听 */ onUnmounted(() => { listeningProject(); }); + diff --git a/src/views/materials/materialsEquipment/materialIssue/index.vue b/src/views/materials/materialsEquipment/materialIssue/index.vue index ec1364d..88837ff 100644 --- a/src/views/materials/materialsEquipment/materialIssue/index.vue +++ b/src/views/materials/materialsEquipment/materialIssue/index.vue @@ -156,18 +156,38 @@ - + - + @@ -176,7 +196,7 @@ :prop="`itemList.${index}.remainingQuantity`" :rules="[{ required: true, message: '剩余数量不能为空', trigger: 'blur' }]" > - + @@ -248,6 +268,9 @@ import { import { MaterialIssueVO, MaterialIssueQuery, MaterialIssueForm } from '@/api/materials/materialIssue/types'; import { useUserStoreHook } from '@/store/modules/user'; import wordllssue from './word/index.vue'; +import { watch, onMounted, onUnmounted, ref, reactive, computed, getCurrentInstance } from 'vue'; +import type { ComponentInternalInstance, ElFormInstance, DialogOption } from 'element-plus'; + const { proxy } = getCurrentInstance() as ComponentInternalInstance; // 获取用户 store const userStore = useUserStoreHook(); @@ -265,6 +288,8 @@ const total = ref(0); const wordllssueRef = ref>(); const queryFormRef = ref(); const materialIssueFormRef = ref(); +// 存储每个条目的watch停止函数 +const itemWatchStopFns = ref void>>([]); const dialog = reactive({ visible: false, @@ -303,12 +328,14 @@ const getInitFormData = () => { issuedQuantity: undefined, remainingQuantity: undefined, name: undefined, - remark: undefined + remark: undefined, + materialsId: undefined } ] }; }; -const data = reactive>({ + +const data = reactive({ form: getInitFormData(), queryParams: { pageNum: 1, @@ -326,7 +353,13 @@ const data = reactive>({ params: {} }, rules: { - id: [{ required: true, message: '主键id不能为空', trigger: 'blur' }] + formCode: [{ required: true, message: '请输入表单编号', trigger: 'blur' }], + projectName: [{ required: true, message: '请输入工程名称', trigger: 'blur' }], + materialName: [{ required: true, message: '请输入设备材料名称', trigger: 'blur' }], + orderingUnit: [{ required: true, message: '请输入订货单位', trigger: 'blur' }], + supplierUnit: [{ required: true, message: '请输入供货单位', trigger: 'blur' }], + issueUnit: [{ required: true, message: '请输入领料单位', trigger: 'blur' }], + storageUnit: [{ required: true, message: '请输入保管单位', trigger: 'blur' }] } }); @@ -335,34 +368,94 @@ const { queryParams, form, rules } = toRefs(data); /** 查询物料领料单列表 */ const getList = async () => { loading.value = true; - const res = await listMaterialIssue(queryParams.value); - materialIssueList.value = res.rows; - total.value = res.total; - loading.value = false; -}; -const optionsName: any = ref([]); - -//获取一起名称 -const getName = async () => { - const res = await getMaterialName(currentProject.value.id); - console.log(res); - if (res.code == 200) { - optionsName.value = res.data; + try { + const res = await listMaterialIssue(queryParams.value); + materialIssueList.value = res.rows; + total.value = res.total; + } finally { + loading.value = false; } }; +const optionsName: any = ref([]); + +// 获取材料名称列表 +const getName = async () => { + try { + const res = await getMaterialName(currentProject.value.id); + if (res.code == 200) { + optionsName.value = res.data; + } + } catch (error) { + proxy?.$modal.msgError('获取材料名称失败'); + } +}; + +// 材料名称选择变化处理 const getNameChange = (value, index, item) => { - // 这里可以添加处理逻辑 - console.log(value); - const data = optionsName.value.find((item) => item.id == value); - console.log(data); + if (data) { + form.value.itemList[index].name = data.materialsName; + form.value.itemList[index].materialsId = data.id; + form.value.itemList[index].specification = data.typeSpecificationName; + form.value.itemList[index].unit = data.weightId; + form.value.itemList[index].stockQuantity = data.inventoryNumber; + // 触发剩余数量计算 + calculateRemaining(index); + } +}; - form.value.itemList[index].name = data.materialsName; - form.value.itemList[index].materialsId = data.id; - form.value.itemList[index].specification = data.typeSpecificationName; - form.value.itemList[index].unit = data.weightId; - form.value.itemList[index].stockQuantity = data.inventoryNumber; +/** 验证领取数量不能超过库存 */ +const validateIssuedQuantity = (rule, value, callback, index) => { + const item = form.value.itemList[index]; + const stock = Number(item.stockQuantity) || 0; + const issued = Number(value) || 0; + + if (stock === 0) { + callback(); + return; + } + + if (issued > stock) { + callback(new Error(`领取数量不能超过库存(${stock})`)); + } else { + callback(); + } +}; + +/** 计算剩余数量(库存 - 领取数量) */ +const calculateRemaining = (index: number) => { + const item = form.value.itemList[index]; + const stock = Number(item.stockQuantity) || 0; + const issued = Number(item.issuedQuantity) || 0; + + // 确保领取数量不超过库存 + if (issued > stock) { + item.issuedQuantity = stock; + proxy?.$modal.msgWarning(`领取数量不能超过库存(${stock}),已自动调整`); + } + + // 计算剩余数量 + item.remainingQuantity = stock - (item.issuedQuantity || 0); +}; + +/** 库存变化时重新计算剩余数量 */ +const handleStockChange = (index: number) => { + calculateRemaining(index); + // 触发验证 + if (materialIssueFormRef.value) { + materialIssueFormRef.value.validateField(`itemList.${index}.issuedQuantity`); + materialIssueFormRef.value.validateField(`itemList.${index}.remainingQuantity`); + } +}; + +/** 领取数量变化时重新计算剩余数量 */ +const handleIssuedChange = (index: number) => { + calculateRemaining(index); + // 触发验证 + if (materialIssueFormRef.value) { + materialIssueFormRef.value.validateField(`itemList.${index}.remainingQuantity`); + } }; /** 取消按钮 */ @@ -373,8 +466,36 @@ const cancel = () => { /** 表单重置 */ const reset = () => { + // 停止所有监听 + itemWatchStopFns.value.forEach((stop) => stop()); + itemWatchStopFns.value = []; + form.value = getInitFormData(); materialIssueFormRef.value?.resetFields(); + + // 重新监听初始条目 + if (form.value.itemList.length > 0) { + watchItemChanges(0); + } +}; + +/** 监听条目变化,自动计算剩余数量 */ +const watchItemChanges = (index: number) => { + // 停止已有监听 + if (itemWatchStopFns.value[index]) { + itemWatchStopFns.value[index](); + } + + // 监听库存和领取数量变化 + const stop = watch( + () => [form.value.itemList[index].stockQuantity, form.value.itemList[index].issuedQuantity], + () => { + calculateRemaining(index); + }, + { immediate: true } + ); + + itemWatchStopFns.value[index] = stop; }; /** 搜索按钮操作 */ @@ -407,10 +528,42 @@ const handleAdd = () => { const handleUpdate = async (row?: MaterialIssueVO) => { reset(); const _id = row?.id || ids.value[0]; - const res = await getMaterialIssue(_id); - Object.assign(form.value, res.data); - dialog.visible = true; - dialog.title = '修改物料领料单'; + try { + const res = await getMaterialIssue(_id); + Object.assign(form.value, res.data); + + // 确保itemList存在 + if (!form.value.itemList) { + form.value.itemList = []; + } + + // 转换数据类型并计算剩余数量(关键修复) + form.value.itemList = form.value.itemList.map((item) => ({ + ...item, + stockQuantity: Number(item.stockQuantity) || 0, + issuedQuantity: Number(item.issuedQuantity) || 0, + remainingQuantity: Number(item.remainingQuantity) || 0 + })); + + // 为每个条目添加监听并强制计算剩余数量 + form.value.itemList.forEach((_, index) => { + watchItemChanges(index); + calculateRemaining(index); // 强制计算确保数据一致性 + }); + // 手动触发一次验证(关键修复) + setTimeout(() => { + if (materialIssueFormRef.value) { + form.value.itemList.forEach((_, index) => { + materialIssueFormRef.value.validateField(`itemList.${index}.issuedQuantity`); + materialIssueFormRef.value.validateField(`itemList.${index}.remainingQuantity`); + }); + } + }, 0); + dialog.visible = true; + dialog.title = '修改物料领料单'; + } catch (error) { + proxy?.$modal.msgError('获取详情失败'); + } }; /** 提交按钮 */ @@ -418,21 +571,38 @@ const submitForm = () => { materialIssueFormRef.value?.validate(async (valid: boolean) => { if (valid) { buttonLoading.value = true; - if (form.value.id) { - await updateMaterialIssue(form.value).finally(() => (buttonLoading.value = false)); - } else { - await addMaterialIssue(form.value).finally(() => (buttonLoading.value = false)); + try { + // 处理提交数据,确保数量为数字类型 + const submitData = { + ...form.value, + itemList: form.value.itemList.map((item) => ({ + ...item, + stockQuantity: Number(item.stockQuantity), + issuedQuantity: Number(item.issuedQuantity), + remainingQuantity: Number(item.remainingQuantity) + })) + }; + + if (form.value.id) { + await updateMaterialIssue(submitData); + } else { + await addMaterialIssue(submitData); + } + proxy?.$modal.msgSuccess('操作成功'); + dialog.visible = false; + await getList(); + } catch (error) { + proxy?.$modal.msgError('操作失败'); + } finally { + buttonLoading.value = false; } - proxy?.$modal.msgSuccess('操作成功'); - dialog.visible = false; - await getList(); } }); }; // 添加数量验收条目 const addItem = () => { - form.value.itemList.push({ + const newItem = { id: undefined, specification: undefined, unit: undefined, @@ -440,46 +610,68 @@ const addItem = () => { issuedQuantity: undefined, remainingQuantity: undefined, name: undefined, - remark: undefined - }); + remark: undefined, + materialsId: undefined + }; + form.value.itemList.push(newItem); + // 监听新条目 + watchItemChanges(form.value.itemList.length - 1); }; // 删除数量验收条目 const removeItem = (index: number) => { if (form.value.itemList.length > 1) { + // 停止该条目的监听 + if (itemWatchStopFns.value[index]) { + itemWatchStopFns.value[index](); + } form.value.itemList.splice(index, 1); + itemWatchStopFns.value.splice(index, 1); } else { proxy?.$modal.msgWarning('至少需要保留一条数量验收记录'); } }; + /** 删除按钮操作 */ const handleDelete = async (row?: MaterialIssueVO) => { const _ids = row?.id || ids.value; - await proxy?.$modal.confirm('是否确认删除物料领料单编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false)); - await delMaterialIssue(_ids); - proxy?.$modal.msgSuccess('删除成功'); - await getList(); + try { + await proxy?.$modal.confirm(`是否确认删除物料领料单编号为"${_ids}"的数据项?`); + await delMaterialIssue(_ids); + proxy?.$modal.msgSuccess('删除成功'); + await getList(); + } catch (error) { + // 取消删除不提示 + } finally { + loading.value = false; + } }; + const handleView = (row) => { // 查看详情 wordllssueRef.value?.openDialog(row); }; + onMounted(() => { getList(); getName(); }); -//监听项目id刷新数据 + +// 监听项目id刷新数据 const listeningProject = watch( () => currentProject.value?.id, - (nid, oid) => { + (nid) => { queryParams.value.projectId = nid; form.value.projectId = nid; getList(); + getName(); } ); onUnmounted(() => { listeningProject(); + // 清理所有监听 + itemWatchStopFns.value.forEach((stop) => stop()); });