// ... 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 type="primary"
|
@click="submitForm">确定</el-button>
|
<el-button @click="cancel">取消</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 type="primary"
|
@click="submitGoalForm">确定</el-button>
|
<el-button @click="cancelGoal">取消</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>
|