From e46811163a11532682fa55f1b54fb9819ffbb594 Mon Sep 17 00:00:00 2001 From: "zangch@mesnac.com" Date: Fri, 21 Nov 2025 16:08:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(erp):=20=E4=BC=98=E5=8C=96=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E8=AE=A1=E5=88=92=E5=8F=98=E6=9B=B4=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E4=B8=8E=E6=96=B0=E5=A2=9E=E5=8F=98=E6=9B=B4=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整项目计划编辑页变更列表显示,变更新旧逻辑更清晰 - 变更时间截取日期部分,统一改为显示年月日格式 - 变更字段列宽和样式优化,使用浅灰色显示无数据占位 - 变更列表在查看模式或审批完成状态均加载,提升数据一致性 - 甘特图增加项目计划变更支持,显示变更前后时间对比 - 甘特图任务列表支持变更信息标记及优先显示变更后时间 - 项目变更列表中仅在最新且已完成变更记录显示“新增变更”按钮 - 变更列表查询处理新增缓存最新完成变更次数,控制按钮显隐 --- src/views/oa/erp/erpProjectChange/index.vue | 93 ++++++++++----- src/views/oa/erp/erpProjectPlan/edit.vue | 61 ++++------ src/views/oa/erp/erpProjectPlan/gantt.vue | 118 ++++++++++++-------- 3 files changed, 161 insertions(+), 111 deletions(-) diff --git a/src/views/oa/erp/erpProjectChange/index.vue b/src/views/oa/erp/erpProjectChange/index.vue index 8ce85bc..3b32e9a 100644 --- a/src/views/oa/erp/erpProjectChange/index.vue +++ b/src/views/oa/erp/erpProjectChange/index.vue @@ -165,8 +165,8 @@ - - + + @@ -204,7 +204,10 @@ const { active_flag, change_type, project_change_status, project_category, wf_bu proxy?.useDict('active_flag', 'change_type', 'project_change_status', 'project_category', 'wf_business_status') ); +// 项目变更列表数据(表格数据源) const erpProjectChangeList = ref([]); +// 记录每个项目最新且已完成的变更次数,用于控制“新增变更”按钮显示 +const latestCompletedChangeMap = ref>({}); const loading = ref(true); const showSearch = ref(true); const ids = ref>([]); @@ -248,39 +251,65 @@ const columns = ref([ { key: 29, label: `更新时间`, visible: false } ]); -const queryParams = reactive({ - pageNum: 1, - pageSize: 10, - projectId: undefined, - projectCode: undefined, - projectName: undefined, - projectCategory: undefined, - changeType: undefined, - changeNumber: undefined, - projectManagerId: undefined, - projectManagerName: undefined, - deptHeadId: undefined, - deptHeadName: undefined, - responsibleVpId: undefined, - responsibleVpName: undefined, - applyChangeDate: undefined, - contractAmount: undefined, - contractNetAmount: undefined, - currentStatus: undefined, - changeReason: undefined, - followUpWork: undefined, - projectChangeStatus: undefined, - flowStatus: undefined, - activeFlag: undefined, - params: {} +// 查询表单参数(分页+筛选条件),与页面搜索区域绑定 +const data = reactive<{ queryParams: ErpProjectChangeQuery }>({ + queryParams: { + pageNum: 1, + pageSize: 10, + projectId: undefined, + projectCode: undefined, + projectName: undefined, + projectCategory: undefined, + changeType: undefined, + changeNumber: undefined, + projectManagerId: undefined, + projectManagerName: undefined, + deptHeadId: undefined, + deptHeadName: undefined, + responsibleVpId: undefined, + responsibleVpName: undefined, + applyChangeDate: undefined, + contractAmount: undefined, + contractNetAmount: undefined, + currentStatus: undefined, + changeReason: undefined, + followUpWork: undefined, + projectChangeStatus: undefined, + flowStatus: undefined, + activeFlag: undefined, + params: {} + } }); +const { queryParams } = toRefs(data); + /** 查询项目变更申请列表 */ const getList = async () => { loading.value = true; const res = await listErpProjectChange(queryParams.value); erpProjectChangeList.value = res.rows; total.value = res.total; + // 先按项目统计每个项目最新的变更次数(changeNumber 最大值) + const latestNumberMap: Record = {}; + erpProjectChangeList.value.forEach((item) => { + const pid = String(item.projectId); + const num = item.changeNumber ?? 0; + if (!latestNumberMap[pid] || num > latestNumberMap[pid]) { + latestNumberMap[pid] = num; + } + }); + // 再从中筛选出“状态为已完成(3)且为最新一次变更”的记录,用于控制是否允许继续新增变更 + const completedMap: Record = {}; + erpProjectChangeList.value.forEach((item) => { + if (item.projectChangeStatus === '3') { + const pid = String(item.projectId); + const num = item.changeNumber ?? 0; + if (latestNumberMap[pid] === num) { + completedMap[pid] = num; + } + } + }); + latestCompletedChangeMap.value = completedMap; loading.value = false; }; @@ -322,6 +351,16 @@ const handleView = (row: ErpProjectChangeVO) => { router.push(`/oa/erp/erpProjectChange/edit/${row.projectChangeId}?type=view`); }; +// 判断当前行是否为该项目“最新且已审批完成”的变更记录 +// 只有满足该条件时,列表中才显示“新增变更”按钮 +const isLatestCompletedChange = (row: ErpProjectChangeVO) => { + if (row.projectChangeStatus !== '3') { + return false; + } + const pid = String(row.projectId); + return latestCompletedChangeMap.value[pid] === row.changeNumber; +}; + /** 新增变更按钮操作 */ const handleAddChange = (row: ErpProjectChangeVO) => { // 从完成状态的变更记录新增变更,传递projectId diff --git a/src/views/oa/erp/erpProjectPlan/edit.vue b/src/views/oa/erp/erpProjectPlan/edit.vue index 56ae31c..fc99831 100644 --- a/src/views/oa/erp/erpProjectPlan/edit.vue +++ b/src/views/oa/erp/erpProjectPlan/edit.vue @@ -210,71 +210,58 @@ 删除 - - + - + - + - - - - - - - + - + - + - --> + @@ -820,8 +807,8 @@ const loadFormData = async () => { syncPaymentMethod: false }); } - // 在查看模式下加载变更记录 - if (isViewMode.value) { + // 在查看模式或审批完成状态下加载变更记录 + if (isViewMode.value || form.value.projectPlanStatus === '3') { await loadProjectChangeList(); } } else { diff --git a/src/views/oa/erp/erpProjectPlan/gantt.vue b/src/views/oa/erp/erpProjectPlan/gantt.vue index 367c305..58762e9 100644 --- a/src/views/oa/erp/erpProjectPlan/gantt.vue +++ b/src/views/oa/erp/erpProjectPlan/gantt.vue @@ -3,11 +3,7 @@
- +
返回 @@ -35,13 +31,7 @@ - + 项目甘特图
@@ -57,12 +47,7 @@ - + @@ -76,6 +61,7 @@ import * as echarts from 'echarts'; import { useResizeObserver } from '@vueuse/core'; import { getErpProjectPlan } from '@/api/oa/erp/erpProjectPlan'; import type { ErpProjectPlanVO } from '@/api/oa/erp/erpProjectPlan/types'; +import { queryProjectChangeByProjectPlanId } from '@/api/oa/erp/erpProjectChange'; interface GanttTask { taskId: string; @@ -88,14 +74,15 @@ interface GanttTask { realEnd?: string; dependencyLabels?: string; remark?: string; + originalPlanStart?: string; // 原计划开始时间 + originalPlanEnd?: string; // 原计划结束时间 + hasChange?: boolean; // 是否有变更 } const { proxy } = getCurrentInstance() as ComponentInternalInstance; const route = useRoute(); const router = useRouter(); -const { project_plan_status, project_phases } = toRefs( - proxy?.useDict('project_plan_status', 'project_phases') -); +const { project_plan_status, project_phases } = toRefs(proxy?.useDict('project_plan_status', 'project_phases')); const loading = ref(false); const showActual = ref(true); @@ -134,7 +121,7 @@ const lastPlanEndDate = computed(() => { return '-'; } let maxTs: number | null = null; - taskList.value.forEach(task => { + taskList.value.forEach((task) => { const ts = toTimestamp(task.planEnd); if (ts !== null) { if (maxTs === null || ts > maxTs) { @@ -195,7 +182,7 @@ const updateChart = () => { return; } - const categories = taskList.value.map(task => task.taskName || '未命名任务'); + const categories = taskList.value.map((task) => task.taskName || '未命名任务'); let min = Number.POSITIVE_INFINITY; let max = Number.NEGATIVE_INFINITY; @@ -314,13 +301,16 @@ const updateChart = () => { if (!task) { return params.name; } - const planText = `计划:${task.planStart || '-'} 至 ${task.planEnd || '-'}`; + let planText = `计划:${task.planStart || '-'} 至 ${task.planEnd || '-'}`; + if (task.hasChange) { + planText += `
(最新变更) 原计划:${task.originalPlanStart || '-'} 至 ${task.originalPlanEnd || '-'}`; + } const actualText = `实际:${task.realStart || '-'} 至 ${task.realEnd || '-'}`; return [params.marker + (task.taskName || '未命名任务'), planText, actualText].join('
'); } }, legend: { - data: series.map(s => s.name), + data: series.map((s) => s.name), top: 0 }, grid: { @@ -363,25 +353,35 @@ watch(showActual, () => { updateChart(); }); -const buildTaskListFromStages = (stages: any[] = []): GanttTask[] => - stages.map(stage => ({ - taskId: String(stage.planStageId ?? generateTaskId()), - taskName: - phaseMap.value[stage.projectPhases as string] || - stage.projectPhases || - stage.scheduleRemark || - '未命名阶段', - phaseName: phaseMap.value[stage.projectPhases as string] || '-', - ownerName: planDetail.value?.managerName, - planStart: normalizeDate(stage.planStartTime), - planEnd: normalizeDate(stage.planEndTime), - realStart: normalizeDate(stage.realStartTime), - realEnd: normalizeDate(stage.realEndTime), - dependencyLabels: Array.isArray(stage.dependencyNames) - ? stage.dependencyNames.join('、') - : stage.dependencyNames || '-', - remark: stage.scheduleRemark || '-' - })); +const buildTaskListFromStages = (stages: any[] = [], changeMap: Map = new Map()): GanttTask[] => + stages.map((stage) => { + const originalStart = normalizeDate(stage.planStartTime); + const originalEnd = normalizeDate(stage.planEndTime); + + // 检查是否有变更 + const changeInfo = changeMap.get(stage.planStageId); + const hasChange = !!changeInfo; + + // 如果有变更,优先使用变更后的时间 + const planStart = hasChange && changeInfo.changedStart ? normalizeDate(changeInfo.changedStart) : originalStart; + const planEnd = hasChange && changeInfo.changedEnd ? normalizeDate(changeInfo.changedEnd) : originalEnd; + + return { + taskId: String(stage.planStageId ?? generateTaskId()), + taskName: phaseMap.value[stage.projectPhases as string] || stage.projectPhases || stage.scheduleRemark || '未命名阶段', + phaseName: phaseMap.value[stage.projectPhases as string] || '-', + ownerName: planDetail.value?.managerName, + planStart, + planEnd, + realStart: normalizeDate(stage.realStartTime), + realEnd: normalizeDate(stage.realEndTime), + dependencyLabels: Array.isArray(stage.dependencyNames) ? stage.dependencyNames.join('、') : stage.dependencyNames || '-', + remark: stage.scheduleRemark || '-', + originalPlanStart: originalStart, + originalPlanEnd: originalEnd, + hasChange + }; + }); const loadPlanDetail = async () => { if (!projectPlanId.value || projectPlanId.value === '0') { @@ -390,13 +390,37 @@ const loadPlanDetail = async () => { } loading.value = true; try { - const res = await getErpProjectPlan(projectPlanId.value); + // 并行加载项目计划和变更记录 + const [res, changeRes] = await Promise.all([ + getErpProjectPlan(projectPlanId.value), + queryProjectChangeByProjectPlanId(projectPlanId.value).catch(() => ({ data: [] })) + ]); + planDetail.value = res.data; + + // 处理变更记录,找到最后一次变更 + const changeList = changeRes.data || []; + const changeMap = new Map(); + + if (changeList.length > 0) { + // 假设最后一条是最新变更(后端按ID或时间排序通常是这样,如果不确定可以先sort) + // 这里简单取列表最后一条 + const lastChange = changeList[changeList.length - 1]; + if (lastChange && lastChange.progressList) { + lastChange.progressList.forEach((p: any) => { + if (p.planStageId) { + changeMap.set(p.planStageId, p); + } + }); + } + } + await nextTick(); - taskList.value = buildTaskListFromStages(res.data.planStageList || []); + taskList.value = buildTaskListFromStages(res.data.planStageList || [], changeMap); updateChart(); } catch (error) { - proxy?.$modal.msgError('获取项目计划失败'); + proxy?.$modal.msgError('获取项目计划数据失败'); + console.error(error); } finally { loading.value = false; }