// ... existing code ...
|
<template>
|
<div class="app-container">
|
<!-- 项目基本信息 -->
|
<el-card class="mb20">
|
<template #header>
|
<div class="card-header">
|
<span>项目基本信息</span>
|
</div>
|
</template>
|
<el-descriptions :column="2" border>
|
<el-descriptions-item label="项目名称">{{ projectInfo.projectName }}</el-descriptions-item>
|
<el-descriptions-item label="项目负责人">{{ projectInfo.managerName }}</el-descriptions-item>
|
<el-descriptions-item label="开始日期">{{ projectInfo.startDate }}</el-descriptions-item>
|
<el-descriptions-item label="结束日期">{{ projectInfo.endDate }}</el-descriptions-item>
|
<el-descriptions-item label="项目状态">
|
<el-tag :type="getStatusType(projectInfo.status)">{{ getStatusText(projectInfo.status) }}</el-tag>
|
</el-descriptions-item>
|
<el-descriptions-item label="完成度">
|
<el-progress :percentage="projectInfo.completionRate" :stroke-width="6" />
|
</el-descriptions-item>
|
<el-descriptions-item label="项目描述" :span="2">{{ projectInfo.description || '-' }}</el-descriptions-item>
|
</el-descriptions>
|
</el-card>
|
|
<!-- 项目进度概览 -->
|
<el-card class="mb20">
|
<template #header>
|
<div class="card-header">
|
<span>项目进度概览</span>
|
</div>
|
</template>
|
<el-row :gutter="20">
|
<el-col :span="6">
|
<div class="progress-item">
|
<div class="progress-title">总体进度</div>
|
<div class="progress-number">{{ projectInfo.completionRate }}%</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="progress-item">
|
<div class="progress-title">阶段总数</div>
|
<div class="progress-number">{{ statistics.totalPhases }}</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="progress-item">
|
<div class="progress-title">任务总数</div>
|
<div class="progress-number">{{ statistics.totalTasks }}</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="progress-item">
|
<div class="progress-title">已完成任务</div>
|
<div class="progress-number">{{ statistics.completedTasks }}</div>
|
</div>
|
</el-col>
|
</el-row>
|
</el-card>
|
|
<!-- 阶段和任务管理 -->
|
<!-- <el-card class="mb20">
|
<template #header>
|
<div class="card-header">
|
<span>项目任务结构</span>
|
<el-button type="primary" size="small" @click="handleAddPhase">添加阶段</el-button>
|
</div>
|
</template>
|
<task-tree :project-id="projectId" @refresh="getProjectDetail" />
|
</el-card> -->
|
|
<!-- 里程碑管理 -->
|
<el-card class="mb20">
|
<template #header>
|
<div class="card-header">
|
<span>项目阶段里程碑</span>
|
<el-button type="primary" size="small" @click="handleAddMilestone">添加里程碑</el-button>
|
</div>
|
</template>
|
<milestone-list :project-id="projectId" @refresh="getProjectDetail" :key="`milestone-${refreshProjectId}`"/>
|
</el-card>
|
|
<!-- 阶段目标管理 -->
|
<el-card>
|
<template #header>
|
<div class="card-header">
|
<span>阶段任务</span>
|
<el-button type="primary" size="small" @click="handleAddPhaseGoal">添加阶段目标</el-button>
|
</div>
|
</template>
|
<phase-goal-list :project-id="projectId" @refresh="getProjectDetail" @editGoal="handleEditPhaseGoal" :key="`phaseGoal-${refreshProjectId}`"/>
|
</el-card>
|
|
<!-- 里程碑管理弹框 -->
|
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
|
<el-form :model="form" ref="formRef" label-width="100px">
|
<el-form-item label="项目阶段名称" prop="phaseName">
|
<el-input
|
v-model="form.phaseName"
|
placeholder="请输入项目阶段名称"
|
maxlength="50"
|
/>
|
</el-form-item>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="开始日期" prop="startDate">
|
<el-date-picker
|
v-model="form.startDate"
|
type="date"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
placeholder="选择开始日期"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="结束日期" prop="endDate">
|
<el-date-picker
|
v-model="form.endDate"
|
type="date"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
placeholder="选择结束日期"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-form-item label="状态" prop="status">
|
<el-radio-group v-model="form.status">
|
<el-radio label="notStarted">未开始</el-radio>
|
<el-radio label="completed">已完成</el-radio>
|
<el-radio label="delayed">已延迟</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="cancel">取消</el-button>
|
<el-button type="primary" @click="submitForm">确定</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
|
<!-- 阶段任务管理弹框 -->
|
<el-dialog :title="goalTitle" v-model="goalOpen" width="600px" append-to-body>
|
<el-form :model="goalForm" ref="goalFormRef" label-width="100px">
|
<el-form-item label="所属阶段" prop="phaseId">
|
<el-select v-model="goalForm.phaseId" placeholder="请选择所属阶段">
|
<el-option
|
v-for="phase in phaseList"
|
:key="phase.phaseId"
|
:label="phase.phaseName"
|
:value="phase.phaseId"
|
/>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="目标名称" prop="taskName">
|
<el-input
|
v-model="goalForm.taskName"
|
placeholder="请输入目标名称"
|
maxlength="50"
|
/>
|
</el-form-item>
|
<el-form-item label="目标值" prop="targetValue">
|
<el-input-number
|
v-model="goalForm.targetValue"
|
:min="0"
|
:precision="2"
|
placeholder="请输入目标值"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
<el-form-item label="当前值" prop="currentValue">
|
<el-input-number
|
v-model="goalForm.currentValue"
|
:min="0"
|
:precision="2"
|
placeholder="请输入当前值"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
<el-form-item label="单位" prop="unit">
|
<el-input
|
v-model="goalForm.unit"
|
placeholder="请输入单位"
|
maxlength="10"
|
/>
|
</el-form-item>
|
<el-form-item label="任务完成日期" prop="targetDate">
|
<el-date-picker
|
v-model="goalForm.targetDate"
|
type="date"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
placeholder="选择目标日期"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
<el-form-item label="开始日期" prop="startDate">
|
<el-date-picker
|
v-model="goalForm.startDate"
|
type="date"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
placeholder="选择目标日期"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
<el-form-item label="结束日期" prop="endDate">
|
<el-date-picker
|
v-model="goalForm.endDate"
|
type="date"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
placeholder="选择目标日期"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
<el-form-item label="状态" prop="status">
|
<el-select v-model="goalForm.status" placeholder="请选择状态">
|
<el-option label="未开始" value="notStarted" />
|
<el-option label="进行中" value="inProgress" />
|
<el-option label="已完成" value="completed" />
|
<el-option label="已延迟" value="delayed" />
|
</el-select>
|
</el-form-item>
|
<!-- <el-form-item label="完成度" prop="completionRate">
|
<el-input-number
|
v-model="goalForm.completionRate"
|
:min="0"
|
:max="100"
|
:precision="2"
|
placeholder="请输入完成度"
|
style="width: 100%"
|
/>
|
</el-form-item> -->
|
</el-form>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="cancelGoal">取消</el-button>
|
<el-button type="primary" @click="submitGoalForm">确定</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, watch } from 'vue';
|
import { useRoute, useRouter } from 'vue-router';
|
import { ElMessage } from 'element-plus';
|
import TaskTree from './components/taskTree.vue';
|
import MilestoneList from './components/milestoneList.vue';
|
import ProjectForm from './components/projectForm.vue';
|
import PhaseGoalList from './components/phaseGoalList.vue';
|
import { getProject, addProjectPhase, listProjectPhase, addProjectTask, updateProjectTask } from '@/api/oaSystem/projectManagement';
|
|
const route = useRoute();
|
const router = useRouter();
|
const open = ref(false);
|
const title = ref('');
|
const projectFormRef = ref();
|
const formRef = ref();
|
// 项目ID
|
// 在其他ref定义附近添加
|
const refreshProjectId = ref(0);
|
|
const projectId = ref(route.params.projectId);
|
|
// 项目信息
|
const projectInfo = reactive({
|
projectId: '',
|
projectName: '',
|
description: '',
|
startDate: '',
|
endDate: '',
|
managerId: '',
|
managerName: '',
|
status: 'planning',
|
completionRate: 0
|
});
|
|
// 统计信息
|
const statistics = reactive({
|
totalPhases: 0,
|
totalTasks: 0,
|
completedTasks: 0
|
});
|
const form = reactive({
|
phaseId: '',
|
phaseName: '',
|
startDate: '',
|
endDate: '',
|
status: 'planning',
|
oaProjectId: projectId.value,
|
})
|
|
// 阶段目标相关
|
const goalOpen = ref(false);
|
const goalTitle = ref('');
|
const goalFormRef = ref();
|
const phaseList = ref([]);
|
const goalForm = reactive({
|
taskId: '',
|
phaseId: '',
|
taskName: '',
|
targetValue: 100,
|
currentValue: 0,
|
unit: '%',
|
targetDate: '',
|
startDate: '',
|
endDate: '',
|
status: 'notStarted',
|
completionRate: 0
|
});
|
|
// 获取项目详情
|
const getProjectDetail = async () => {
|
try {
|
getProject().then((res)=>{
|
console.log("项目详情",res)
|
const projectData = res.data[projectId.value];
|
// 更新项目信息
|
Object.assign(projectInfo, projectData);
|
|
// 更新统计信息
|
updateStatistics(projectData);
|
|
// 强制更新DOM以确保子组件能正确刷新
|
// 这里通过触发refreshProjectId事件来强制刷新子组件
|
refreshProjectId.value++;
|
})
|
} catch (error) {
|
ElMessage.error('获取项目详情失败');
|
console.error('获取项目详情失败:', error);
|
}
|
};
|
|
// 更新统计信息
|
const updateStatistics = (projectData) => {
|
// 这里假设projectData中包含了统计信息
|
// 如果没有,需要单独请求统计数据
|
statistics.totalPhases = projectData.phases ? projectData.phases.length : 0;
|
statistics.totalTasks = projectData.tasks ? projectData.tasks.length : 0;
|
statistics.completedTasks = projectData.tasks ?
|
projectData.tasks.filter(task => task.status === 'completed').length : 0;
|
};
|
|
// 获取项目阶段列表
|
const getPhaseList = async () => {
|
try {
|
const { data } = await listProjectPhase(projectId.value);
|
phaseList.value = data.rows || data;
|
} catch (error) {
|
ElMessage.error('获取项目阶段列表失败');
|
console.error('获取项目阶段列表失败:', error);
|
}
|
};
|
|
// 计算完成度
|
const calculateCompletionRate = () => {
|
if (goalForm.targetValue > 0) {
|
goalForm.completionRate = Math.min(Math.round((goalForm.currentValue / goalForm.targetValue) * 100), 100);
|
} else {
|
goalForm.completionRate = 0;
|
}
|
};
|
|
// 添加阶段
|
const handleAddPhase = () => {
|
// resetForm();
|
ElMessage.info('添加阶段功能待实现');
|
};
|
|
// 添加里程碑
|
const handleAddMilestone = () => {
|
resetForm();
|
open.value = true;
|
title.value = '新增项目阶段';
|
};
|
|
// 添加阶段任务
|
const handleAddPhaseGoal = () => {
|
goalForm.taskId = '';
|
goalForm.phaseId = '';
|
goalForm.taskName = '';
|
goalForm.targetValue = 0;
|
goalForm.currentValue = 0;
|
goalForm.unit = '%';
|
goalForm.targetDate = '';
|
goalForm.startDate = '';
|
goalForm.endDate = '';
|
goalForm.status = 'notStarted';
|
goalForm.completionRate = 0;
|
if (goalFormRef.value) {
|
goalFormRef.value.resetFields();
|
}
|
getPhaseList();
|
goalTitle.value = '新增阶段目标';
|
goalOpen.value = true;
|
};
|
|
// 提交表单
|
const submitForm = async () => {
|
try {
|
await formRef.value.validate();
|
|
if (form.phaseId) {
|
// await updateProject(form);
|
// ElMessage.success('修改项目阶段成功');
|
} else {
|
console.log("form",form);
|
await addProjectPhase(form);
|
ElMessage.success('新增项目阶段成功');
|
getProjectDetail();
|
}
|
open.value = false;
|
} catch (error) {
|
console.error('提交表单失败:', error);
|
}
|
};
|
|
// 提交阶段任务表单
|
const submitGoalForm = async () => {
|
try {
|
await goalFormRef.value.validate();
|
calculateCompletionRate();
|
|
const goalData = {
|
...goalForm,
|
oaProjectId: projectId.value
|
};
|
|
if (goalForm.taskId) {
|
await updateProjectTask(goalData);
|
ElMessage.success('修改阶段目标成功');
|
|
} else {
|
await addProjectTask(goalData);
|
ElMessage.success('新增阶段目标成功');
|
|
}
|
// 调用getProjectDetail刷新所有相关数据
|
getProjectDetail();
|
goalOpen.value = false;
|
|
} catch (error) {
|
console.error('提交阶段目标表单失败:', error);
|
}
|
};
|
|
// 重置里程碑表单
|
const resetForm = () => {
|
form.phaseId = '';
|
form.phaseName = '';
|
form.startDate = '';
|
form.endDate = '';
|
form.status = 'planning';
|
form.oaProjectId = projectId.value;
|
if (formRef.value) {
|
formRef.value.resetFields();
|
}
|
};
|
|
// 取消阶段任务操作
|
const cancelGoal = () => {
|
goalOpen.value = false;
|
};
|
|
// 取消操作
|
const cancel = () => {
|
open.value = false;
|
};
|
// 编辑阶段任务
|
const handleEditPhaseGoal = async (goal) => {
|
// 复制目标数据到表单
|
Object.assign(goalForm, goal);
|
|
// 获取项目阶段列表
|
await getPhaseList();
|
|
// 打开编辑弹窗
|
goalTitle.value = '编辑阶段目标';
|
goalOpen.value = true;
|
};
|
// 获取状态标签类型
|
const getStatusType = (status) => {
|
const statusTypeMap = {
|
planning: 'info',
|
inProgress: 'primary',
|
completed: 'success',
|
paused: 'warning'
|
};
|
return statusTypeMap[status] || 'default';
|
};
|
|
// 获取状态文本
|
const getStatusText = (status) => {
|
const statusTextMap = {
|
planning: '规划中',
|
inProgress: '进行中',
|
completed: '已完成',
|
paused: '已暂停'
|
};
|
return statusTextMap[status] || status;
|
};
|
|
// 监听路由参数变化
|
watch(() => route.params.projectId, (newProjectId) => {
|
// console.log('路由参数变化:', projectId);
|
if (newProjectId) {
|
projectId.value = newProjectId;
|
getProjectDetail();
|
}
|
});
|
|
// 监听当前值和目标值变化,重新计算完成度
|
watch(() => [goalForm.currentValue, goalForm.targetValue], () => {
|
calculateCompletionRate();
|
});
|
|
// 初始化
|
onMounted(() => {
|
if (projectId.value) {
|
getProjectDetail();
|
}
|
});
|
</script>
|
|
<style scoped>
|
.app-container {
|
padding: 20px;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.progress-item {
|
text-align: center;
|
padding: 20px;
|
background-color: #f5f7fa;
|
border-radius: 8px;
|
}
|
|
.progress-title {
|
font-size: 14px;
|
color: #606266;
|
margin-bottom: 10px;
|
}
|
|
.progress-number {
|
font-size: 24px;
|
font-weight: bold;
|
color: #409eff;
|
}
|
|
.mb20 {
|
margin-bottom: 20px;
|
}
|
</style>
|