|
|
|
|
@ -3,11 +3,7 @@
|
|
|
|
|
<el-card shadow="never">
|
|
|
|
|
<div class="mb-3 flex items-center justify-between">
|
|
|
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
|
|
|
<el-switch
|
|
|
|
|
v-model="showActual"
|
|
|
|
|
active-text="显示实际进度"
|
|
|
|
|
inactive-text="隐藏实际进度"
|
|
|
|
|
/>
|
|
|
|
|
<el-switch v-model="showActual" active-text="显示实际进度" inactive-text="隐藏实际进度" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<el-button @click="goBack">返回</el-button>
|
|
|
|
|
@ -35,13 +31,7 @@
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
|
|
<el-alert
|
|
|
|
|
title="任务数据来源于项目阶段,仅供查看。"
|
|
|
|
|
type="info"
|
|
|
|
|
show-icon
|
|
|
|
|
:closable="false"
|
|
|
|
|
class="mb-3"
|
|
|
|
|
/>
|
|
|
|
|
<el-alert title="任务数据来源于项目阶段,仅供查看。" type="info" show-icon :closable="false" class="mb-3" />
|
|
|
|
|
|
|
|
|
|
<el-divider content-position="left">项目甘特图</el-divider>
|
|
|
|
|
<div ref="chartContainerRef" class="gantt-chart" />
|
|
|
|
|
@ -57,12 +47,7 @@
|
|
|
|
|
<el-table-column prop="planEnd" label="计划结束" min-width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column prop="realStart" label="实际开始" min-width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column prop="realEnd" label="实际结束" min-width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column
|
|
|
|
|
prop="dependencyLabels"
|
|
|
|
|
label="依赖任务"
|
|
|
|
|
min-width="160"
|
|
|
|
|
show-overflow-tooltip
|
|
|
|
|
/>
|
|
|
|
|
<el-table-column prop="dependencyLabels" label="依赖任务" min-width="160" show-overflow-tooltip />
|
|
|
|
|
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
|
|
|
|
|
</el-table>
|
|
|
|
|
</el-card>
|
|
|
|
|
@ -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<any>(
|
|
|
|
|
proxy?.useDict('project_plan_status', 'project_phases')
|
|
|
|
|
);
|
|
|
|
|
const { project_plan_status, project_phases } = toRefs<any>(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 += `<br/><span style="color:#e6a23c;font-size:12px;">(最新变更) 原计划:${task.originalPlanStart || '-'} 至 ${task.originalPlanEnd || '-'}</span>`;
|
|
|
|
|
}
|
|
|
|
|
const actualText = `实际:${task.realStart || '-'} 至 ${task.realEnd || '-'}`;
|
|
|
|
|
return [params.marker + (task.taskName || '未命名任务'), planText, actualText].join('<br/>');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
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<any, any> = 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;
|
|
|
|
|
}
|
|
|
|
|
|