| | |
| | | <span>项目基本信息</span> |
| | | </div> |
| | | </template> |
| | | <el-descriptions :column="2" border> |
| | | <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-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-progress :percentage="projectInfo.completionRate" |
| | | :stroke-width="6" /> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="项目描述" :span="2">{{ projectInfo.description || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="项目描述" |
| | | :span="2">{{ projectInfo.description || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-card> |
| | | |
| | | <!-- 项目进度概览 --> |
| | | <el-card class="mb20"> |
| | | <template #header> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- 阶段和任务管理 --> |
| | | <!-- <el-card class="mb20"> |
| | | <template #header> |
| | |
| | | </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> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="handleAddMilestone">添加里程碑</el-button> |
| | | </div> |
| | | </template> |
| | | <milestone-list :project-id="projectId" @refresh="getProjectDetail" :key="`milestone-${refreshProjectId}`"/> |
| | | <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> |
| | | <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}`"/> |
| | | <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" |
| | | <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" |
| | | /> |
| | | 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" |
| | | <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%" |
| | | /> |
| | | 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" |
| | | <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%" |
| | | /> |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="状态" prop="status"> |
| | | <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-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm">确定</el-button> |
| | | <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" |
| | | <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" |
| | | /> |
| | | :value="phase.phaseId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="目标名称" prop="taskName"> |
| | | <el-input |
| | | v-model="goalForm.taskName" |
| | | <el-form-item label="目标名称" |
| | | prop="taskName"> |
| | | <el-input v-model="goalForm.taskName" |
| | | placeholder="请输入目标名称" |
| | | maxlength="50" |
| | | /> |
| | | maxlength="50" /> |
| | | </el-form-item> |
| | | <el-form-item label="目标值" prop="targetValue"> |
| | | <el-input-number |
| | | v-model="goalForm.targetValue" |
| | | <el-form-item label="目标值" |
| | | prop="targetValue"> |
| | | <el-input-number v-model="goalForm.targetValue" |
| | | :min="0" |
| | | :precision="2" |
| | | placeholder="请输入目标值" |
| | | style="width: 100%" |
| | | /> |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="当前值" prop="currentValue"> |
| | | <el-input-number |
| | | v-model="goalForm.currentValue" |
| | | <el-form-item label="当前值" |
| | | prop="currentValue"> |
| | | <el-input-number v-model="goalForm.currentValue" |
| | | :min="0" |
| | | :precision="2" |
| | | placeholder="请输入当前值" |
| | | style="width: 100%" |
| | | /> |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="单位" prop="unit"> |
| | | <el-input |
| | | v-model="goalForm.unit" |
| | | <el-form-item label="单位" |
| | | prop="unit"> |
| | | <el-input v-model="goalForm.unit" |
| | | placeholder="请输入单位" |
| | | maxlength="10" |
| | | /> |
| | | maxlength="10" /> |
| | | </el-form-item> |
| | | <el-form-item label="任务完成日期" prop="targetDate"> |
| | | <el-date-picker |
| | | v-model="goalForm.targetDate" |
| | | <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%" |
| | | /> |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="开始日期" prop="startDate"> |
| | | <el-date-picker |
| | | v-model="goalForm.startDate" |
| | | <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%" |
| | | /> |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="结束日期" prop="endDate"> |
| | | <el-date-picker |
| | | v-model="goalForm.endDate" |
| | | <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%" |
| | | /> |
| | | 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-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-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitGoalForm">确定</el-button> |
| | | <el-button @click="cancelGoal">取消</el-button> |
| | | <el-button type="primary" @click="submitGoalForm">确定</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </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'; |
| | | 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 title = ref(""); |
| | | const projectFormRef = ref(); |
| | | const formRef = ref(); |
| | | // 项目ID |
| | |
| | | |
| | | // 项目信息 |
| | | const projectInfo = reactive({ |
| | | projectId: '', |
| | | projectName: '', |
| | | description: '', |
| | | startDate: '', |
| | | endDate: '', |
| | | managerId: '', |
| | | managerName: '', |
| | | status: 'planning', |
| | | completionRate: 0 |
| | | projectId: "", |
| | | projectName: "", |
| | | description: "", |
| | | startDate: "", |
| | | endDate: "", |
| | | managerId: "", |
| | | managerName: "", |
| | | status: "planning", |
| | | completionRate: 0, |
| | | }); |
| | | |
| | | // 统计信息 |
| | | const statistics = reactive({ |
| | | totalPhases: 0, |
| | | totalTasks: 0, |
| | | completedTasks: 0 |
| | | completedTasks: 0, |
| | | }); |
| | | const form = reactive({ |
| | | phaseId: '', |
| | | phaseName: '', |
| | | startDate: '', |
| | | endDate: '', |
| | | status: 'planning', |
| | | phaseId: "", |
| | | phaseName: "", |
| | | startDate: "", |
| | | endDate: "", |
| | | status: "planning", |
| | | oaProjectId: projectId.value, |
| | | }) |
| | | }); |
| | | |
| | | // 阶段目标相关 |
| | | const goalOpen = ref(false); |
| | | const goalTitle = ref(''); |
| | | const goalTitle = ref(""); |
| | | const goalFormRef = ref(); |
| | | const phaseList = ref([]); |
| | | const goalForm = reactive({ |
| | | taskId: '', |
| | | phaseId: '', |
| | | taskName: '', |
| | | taskId: "", |
| | | phaseId: "", |
| | | taskName: "", |
| | | targetValue: 100, |
| | | currentValue: 0, |
| | | unit: '%', |
| | | targetDate: '', |
| | | startDate: '', |
| | | endDate: '', |
| | | status: 'notStarted', |
| | | completionRate: 0 |
| | | unit: "%", |
| | | targetDate: "", |
| | | startDate: "", |
| | | endDate: "", |
| | | status: "notStarted", |
| | | completionRate: 0, |
| | | }); |
| | | |
| | | // 获取项目详情 |
| | | const getProjectDetail = async () => { |
| | | try { |
| | | getProject().then((res)=>{ |
| | | console.log("项目详情",res) |
| | | getProject().then(res => { |
| | | console.log("项目详情", res); |
| | | const projectData = res.data[projectId.value]; |
| | | // 更新项目信息 |
| | | Object.assign(projectInfo, projectData); |
| | |
| | | // 强制更新DOM以确保子组件能正确刷新 |
| | | // 这里通过触发refreshProjectId事件来强制刷新子组件 |
| | | refreshProjectId.value++; |
| | | }) |
| | | }); |
| | | } catch (error) { |
| | | ElMessage.error('获取项目详情失败'); |
| | | console.error('获取项目详情失败:', error); |
| | | ElMessage.error("获取项目详情失败"); |
| | | console.error("获取项目详情失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 更新统计信息 |
| | | const updateStatistics = (projectData) => { |
| | | 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; |
| | | statistics.completedTasks = projectData.tasks |
| | | ? projectData.tasks.filter(task => task.status === "completed").length |
| | | : 0; |
| | | }; |
| | | |
| | | // 获取项目阶段列表 |
| | |
| | | const { data } = await listProjectPhase(projectId.value); |
| | | phaseList.value = data.rows || data; |
| | | } catch (error) { |
| | | ElMessage.error('获取项目阶段列表失败'); |
| | | console.error('获取项目阶段列表失败:', error); |
| | | ElMessage.error("获取项目阶段列表失败"); |
| | | console.error("获取项目阶段列表失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 计算完成度 |
| | | const calculateCompletionRate = () => { |
| | | if (goalForm.targetValue > 0) { |
| | | goalForm.completionRate = Math.min(Math.round((goalForm.currentValue / goalForm.targetValue) * 100), 100); |
| | | goalForm.completionRate = Math.min( |
| | | Math.round((goalForm.currentValue / goalForm.targetValue) * 100), |
| | | 100 |
| | | ); |
| | | } else { |
| | | goalForm.completionRate = 0; |
| | | } |
| | |
| | | // 添加阶段 |
| | | const handleAddPhase = () => { |
| | | // resetForm(); |
| | | ElMessage.info('添加阶段功能待实现'); |
| | | ElMessage.info("添加阶段功能待实现"); |
| | | }; |
| | | |
| | | // 添加里程碑 |
| | | const handleAddMilestone = () => { |
| | | resetForm(); |
| | | open.value = true; |
| | | title.value = '新增项目阶段'; |
| | | title.value = "新增项目阶段"; |
| | | }; |
| | | |
| | | // 添加阶段任务 |
| | | const handleAddPhaseGoal = () => { |
| | | goalForm.taskId = ''; |
| | | goalForm.phaseId = ''; |
| | | goalForm.taskName = ''; |
| | | goalForm.taskId = ""; |
| | | goalForm.phaseId = ""; |
| | | goalForm.taskName = ""; |
| | | goalForm.targetValue = 0; |
| | | goalForm.currentValue = 0; |
| | | goalForm.unit = '%'; |
| | | goalForm.targetDate = ''; |
| | | goalForm.startDate = ''; |
| | | goalForm.endDate = ''; |
| | | goalForm.status = 'notStarted'; |
| | | goalForm.unit = "%"; |
| | | goalForm.targetDate = ""; |
| | | goalForm.startDate = ""; |
| | | goalForm.endDate = ""; |
| | | goalForm.status = "notStarted"; |
| | | goalForm.completionRate = 0; |
| | | if (goalFormRef.value) { |
| | | goalFormRef.value.resetFields(); |
| | | } |
| | | getPhaseList(); |
| | | goalTitle.value = '新增阶段目标'; |
| | | goalTitle.value = "新增阶段目标"; |
| | | goalOpen.value = true; |
| | | }; |
| | | |
| | |
| | | } else { |
| | | console.log("form",form); |
| | | await addProjectPhase(form); |
| | | ElMessage.success('新增项目阶段成功'); |
| | | ElMessage.success("新增项目阶段成功"); |
| | | getProjectDetail(); |
| | | } |
| | | open.value = false; |
| | | } catch (error) { |
| | | console.error('提交表单失败:', error); |
| | | console.error("提交表单失败:", error); |
| | | } |
| | | }; |
| | | |
| | |
| | | |
| | | const goalData = { |
| | | ...goalForm, |
| | | oaProjectId: projectId.value |
| | | oaProjectId: projectId.value, |
| | | }; |
| | | |
| | | if (goalForm.taskId) { |
| | | await updateProjectTask(goalData); |
| | | ElMessage.success('修改阶段目标成功'); |
| | | |
| | | ElMessage.success("修改阶段目标成功"); |
| | | } else { |
| | | await addProjectTask(goalData); |
| | | ElMessage.success('新增阶段目标成功'); |
| | | |
| | | ElMessage.success("新增阶段目标成功"); |
| | | } |
| | | // 调用getProjectDetail刷新所有相关数据 |
| | | getProjectDetail(); |
| | | goalOpen.value = false; |
| | | |
| | | } catch (error) { |
| | | console.error('提交阶段目标表单失败:', error); |
| | | console.error("提交阶段目标表单失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 重置里程碑表单 |
| | | const resetForm = () => { |
| | | form.phaseId = ''; |
| | | form.phaseName = ''; |
| | | form.startDate = ''; |
| | | form.endDate = ''; |
| | | form.status = 'planning'; |
| | | form.phaseId = ""; |
| | | form.phaseName = ""; |
| | | form.startDate = ""; |
| | | form.endDate = ""; |
| | | form.status = "planning"; |
| | | form.oaProjectId = projectId.value; |
| | | if (formRef.value) { |
| | | formRef.value.resetFields(); |
| | |
| | | open.value = false; |
| | | }; |
| | | // 编辑阶段任务 |
| | | const handleEditPhaseGoal = async (goal) => { |
| | | const handleEditPhaseGoal = async goal => { |
| | | // 复制目标数据到表单 |
| | | Object.assign(goalForm, goal); |
| | | |
| | |
| | | await getPhaseList(); |
| | | |
| | | // 打开编辑弹窗 |
| | | goalTitle.value = '编辑阶段目标'; |
| | | goalTitle.value = "编辑阶段目标"; |
| | | goalOpen.value = true; |
| | | }; |
| | | // 获取状态标签类型 |
| | | const getStatusType = (status) => { |
| | | const getStatusType = status => { |
| | | const statusTypeMap = { |
| | | planning: 'info', |
| | | inProgress: 'primary', |
| | | completed: 'success', |
| | | paused: 'warning' |
| | | planning: "info", |
| | | inProgress: "primary", |
| | | completed: "success", |
| | | paused: "warning", |
| | | }; |
| | | return statusTypeMap[status] || 'default'; |
| | | return statusTypeMap[status] || "default"; |
| | | }; |
| | | |
| | | // 获取状态文本 |
| | | const getStatusText = (status) => { |
| | | const getStatusText = status => { |
| | | const statusTextMap = { |
| | | planning: '规划中', |
| | | inProgress: '进行中', |
| | | completed: '已完成', |
| | | paused: '已暂停' |
| | | planning: "规划中", |
| | | inProgress: "进行中", |
| | | completed: "已完成", |
| | | paused: "已暂停", |
| | | }; |
| | | return statusTextMap[status] || status; |
| | | }; |
| | | |
| | | // 监听路由参数变化 |
| | | watch(() => route.params.projectId, (newProjectId) => { |
| | | watch( |
| | | () => route.params.projectId, |
| | | newProjectId => { |
| | | // console.log('路由参数变化:', projectId); |
| | | if (newProjectId) { |
| | | projectId.value = newProjectId; |
| | | getProjectDetail(); |
| | | } |
| | | }); |
| | | } |
| | | ); |
| | | |
| | | // 监听当前值和目标值变化,重新计算完成度 |
| | | watch(() => [goalForm.currentValue, goalForm.targetValue], () => { |
| | | watch( |
| | | () => [goalForm.currentValue, goalForm.targetValue], |
| | | () => { |
| | | calculateCompletionRate(); |
| | | }); |
| | | } |
| | | ); |
| | | |
| | | // 初始化 |
| | | onMounted(() => { |