Compare commits
2 Commits
031dd03a62
...
e10aa07367
| Author | SHA1 | Date | |
|---|---|---|---|
| e10aa07367 | |||
| df6970b71c |
@@ -441,3 +441,167 @@ export const getTaskStatus = (taskId: string) => {
|
||||
`/api/v1/project-init/task/${taskId}`
|
||||
);
|
||||
};
|
||||
|
||||
// ==================== 里程碑管理 API ====================
|
||||
|
||||
/** 里程碑查询参数 */
|
||||
export type MilestoneQueryParams = {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
projectId?: string;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
/** 分页查询里程碑列表 */
|
||||
export const getMilestoneList = (params?: MilestoneQueryParams) => {
|
||||
return http.request<Result<TableDataInfo<ProjectMilestone>>>(
|
||||
"get",
|
||||
"/api/v1/milestone/list",
|
||||
{ params }
|
||||
);
|
||||
};
|
||||
|
||||
/** 根据ID查询里程碑详情 */
|
||||
export const getMilestoneById = (id: string) => {
|
||||
return http.request<Result<ProjectMilestone>>(
|
||||
"get",
|
||||
`/api/v1/milestone/${id}`
|
||||
);
|
||||
};
|
||||
|
||||
/** 新增里程碑 */
|
||||
export const createMilestone = (data: ProjectMilestone) => {
|
||||
return http.request<Result<string>>("post", "/api/v1/milestone", { data });
|
||||
};
|
||||
|
||||
/** 修改里程碑 */
|
||||
export const updateMilestone = (data: ProjectMilestone) => {
|
||||
return http.request<Result<void>>("put", "/api/v1/milestone", { data });
|
||||
};
|
||||
|
||||
/** 删除里程碑 */
|
||||
export const deleteMilestone = (id: string) => {
|
||||
return http.request<Result<void>>("delete", `/api/v1/milestone/${id}`);
|
||||
};
|
||||
|
||||
/** 更新里程碑进度 */
|
||||
export const updateMilestoneProgress = (id: string, progress: number) => {
|
||||
return http.request<Result<void>>("put", `/api/v1/milestone/${id}/progress`, {
|
||||
params: { progress }
|
||||
});
|
||||
};
|
||||
|
||||
/** 更新里程碑状态 */
|
||||
export const updateMilestoneStatus = (id: string, status: string) => {
|
||||
return http.request<Result<void>>("put", `/api/v1/milestone/${id}/status`, {
|
||||
params: { status }
|
||||
});
|
||||
};
|
||||
|
||||
/** 查询已延期的关键里程碑 */
|
||||
export const getDelayedKeyMilestones = (projectId: string) => {
|
||||
return http.request<Result<ProjectMilestone[]>>(
|
||||
"get",
|
||||
"/api/v1/milestone/delayed-key",
|
||||
{ params: { projectId } }
|
||||
);
|
||||
};
|
||||
|
||||
/** 查询即将到期的里程碑 */
|
||||
export const getUpcomingMilestones = (projectId: string, days: number = 7) => {
|
||||
return http.request<Result<ProjectMilestone[]>>(
|
||||
"get",
|
||||
"/api/v1/milestone/upcoming",
|
||||
{ params: { projectId, days } }
|
||||
);
|
||||
};
|
||||
|
||||
/** 查询里程碑完成进度统计 */
|
||||
export const getMilestoneProgressStats = (projectId: string) => {
|
||||
return http.request<Result<Record<string, any>>>(
|
||||
"get",
|
||||
"/api/v1/milestone/stats/progress",
|
||||
{ params: { projectId } }
|
||||
);
|
||||
};
|
||||
|
||||
// ==================== 任务管理 API ====================
|
||||
|
||||
/** 任务查询参数 */
|
||||
export type TaskQueryParams = {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
projectId?: string;
|
||||
milestoneId?: string;
|
||||
assigneeId?: string;
|
||||
status?: string;
|
||||
priority?: string;
|
||||
keyword?: string;
|
||||
};
|
||||
|
||||
/** 分页查询任务列表 */
|
||||
export const getTaskList = (params?: TaskQueryParams) => {
|
||||
return http.request<Result<TableDataInfo<ProjectTask>>>(
|
||||
"get",
|
||||
"/api/v1/task/list",
|
||||
{ params }
|
||||
);
|
||||
};
|
||||
|
||||
/** 根据ID查询任务详情 */
|
||||
export const getTaskById = (id: string) => {
|
||||
return http.request<Result<ProjectTask>>("get", `/api/v1/task/${id}`);
|
||||
};
|
||||
|
||||
/** 新增任务 */
|
||||
export const createTask = (data: ProjectTask) => {
|
||||
return http.request<Result<string>>("post", "/api/v1/task", { data });
|
||||
};
|
||||
|
||||
/** 修改任务 */
|
||||
export const updateTask = (data: ProjectTask) => {
|
||||
return http.request<Result<void>>("put", "/api/v1/task", { data });
|
||||
};
|
||||
|
||||
/** 删除任务 */
|
||||
export const deleteTask = (id: string) => {
|
||||
return http.request<Result<void>>("delete", `/api/v1/task/${id}`);
|
||||
};
|
||||
|
||||
/** 查询我的待办任务 */
|
||||
export const getMyTodoTasks = (userId: string, projectId?: string) => {
|
||||
return http.request<Result<ProjectTask[]>>("get", "/api/v1/task/my-tasks", {
|
||||
params: { userId, projectId }
|
||||
});
|
||||
};
|
||||
|
||||
/** 更新任务进度 */
|
||||
export const updateTaskProgress = (id: string, progress: number) => {
|
||||
return http.request<Result<void>>("put", `/api/v1/task/${id}/progress`, {
|
||||
params: { progress }
|
||||
});
|
||||
};
|
||||
|
||||
/** 更新任务状态 */
|
||||
export const updateTaskStatus = (id: string, status: string) => {
|
||||
return http.request<Result<void>>("put", `/api/v1/task/${id}/status`, {
|
||||
params: { status }
|
||||
});
|
||||
};
|
||||
|
||||
/** 查询任务依赖关系 */
|
||||
export const getTaskDependencies = (id: string) => {
|
||||
return http.request<Result<Record<string, any>[]>>(
|
||||
"get",
|
||||
`/api/v1/task/${id}/dependencies`
|
||||
);
|
||||
};
|
||||
|
||||
/** 统计项目任务状态分布 */
|
||||
export const getTaskStatusStats = (projectId: string) => {
|
||||
return http.request<Result<Record<string, any>[]>>(
|
||||
"get",
|
||||
"/api/v1/task/stats/status",
|
||||
{ params: { projectId } }
|
||||
);
|
||||
};
|
||||
|
||||
@@ -111,9 +111,9 @@ export type RiskStatisticsVO = {
|
||||
highCount: number;
|
||||
mediumCount: number;
|
||||
lowCount: number;
|
||||
categoryStats: Record<string, number>;
|
||||
levelStats: Record<string, number>;
|
||||
trendData: Record<string, number[]>;
|
||||
categoryStats: Record<string, number | string>;
|
||||
levelStats: Record<string, number | string>;
|
||||
trendData: Record<string, number[]> | null;
|
||||
averageRiskScore: number;
|
||||
unresolvedHighCount: number;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -332,31 +332,43 @@ function initPieChart() {
|
||||
updatePieChart();
|
||||
}
|
||||
|
||||
// 更新饼图
|
||||
// 更新饼图 - 使用 categoryStats 分类统计数据
|
||||
function updatePieChart() {
|
||||
if (!pieChart) return;
|
||||
const data = [
|
||||
{
|
||||
value: statistics.value.criticalCount || 0,
|
||||
name: "严重",
|
||||
itemStyle: { color: "#f56c6c" }
|
||||
},
|
||||
{
|
||||
value: statistics.value.highCount || 0,
|
||||
name: "高",
|
||||
itemStyle: { color: "#e6a23c" }
|
||||
},
|
||||
{
|
||||
value: statistics.value.mediumCount || 0,
|
||||
name: "中",
|
||||
itemStyle: { color: "#409eff" }
|
||||
},
|
||||
{
|
||||
value: statistics.value.lowCount || 0,
|
||||
name: "低",
|
||||
itemStyle: { color: "#67c23a" }
|
||||
}
|
||||
].filter(item => item.value > 0);
|
||||
|
||||
// 从 categoryStats 获取分类统计数据
|
||||
const categoryStats = statistics.value.categoryStats || {};
|
||||
const categoryColors: Record<string, string> = {
|
||||
schedule: "#409eff", // 进度 - 蓝色
|
||||
external: "#e6a23c", // 外部 - 橙色
|
||||
technical: "#67c23a", // 技术 - 绿色
|
||||
resource: "#909399", // 资源 - 灰色
|
||||
personnel: "#f56c6c", // 人员 - 红色
|
||||
quality: "#9c27b0", // 质量 - 紫色
|
||||
cost: "#ff9800", // 成本 - 深橙
|
||||
other: "#795548" // 其他 - 棕色
|
||||
};
|
||||
const categoryNames: Record<string, string> = {
|
||||
schedule: "进度风险",
|
||||
external: "外部风险",
|
||||
technical: "技术风险",
|
||||
resource: "资源风险",
|
||||
personnel: "人员风险",
|
||||
quality: "质量风险",
|
||||
cost: "成本风险",
|
||||
other: "其他风险"
|
||||
};
|
||||
|
||||
// 构建饼图数据
|
||||
const data = Object.entries(categoryStats)
|
||||
.map(([key, value]) => ({
|
||||
value: parseInt(String(value)) || 0,
|
||||
name: categoryNames[key] || key,
|
||||
itemStyle: { color: categoryColors[key] || "#909399" }
|
||||
}))
|
||||
.filter(item => item.value > 0)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
@@ -371,7 +383,7 @@ function updatePieChart() {
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "风险分布",
|
||||
name: "风险分类分布",
|
||||
type: "pie",
|
||||
radius: ["50%", "70%"],
|
||||
center: ["50%", "45%"],
|
||||
@@ -394,7 +406,10 @@ function updatePieChart() {
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data
|
||||
data:
|
||||
data.length > 0
|
||||
? data
|
||||
: [{ value: 1, name: "暂无数据", itemStyle: { color: "#e0e0e0" } }]
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -408,34 +423,61 @@ function initTrendChart() {
|
||||
updateTrendChart();
|
||||
}
|
||||
|
||||
// 更新趋势图
|
||||
// 更新趋势图 - 使用风险状态分布数据
|
||||
function updateTrendChart() {
|
||||
if (!trendChart) return;
|
||||
const months = ["1月", "2月", "3月", "4月", "5月", "6月"];
|
||||
|
||||
// 使用状态统计数据展示风险状态分布
|
||||
const statusData = [
|
||||
{
|
||||
name: "已识别",
|
||||
value: statistics.value.identifiedCount || 0,
|
||||
color: "#909399"
|
||||
},
|
||||
{
|
||||
name: "已分派",
|
||||
value: statistics.value.assignedCount || 0,
|
||||
color: "#409eff"
|
||||
},
|
||||
{
|
||||
name: "缓解中",
|
||||
value: statistics.value.mitigatingCount || 0,
|
||||
color: "#e6a23c"
|
||||
},
|
||||
{
|
||||
name: "已解决",
|
||||
value: statistics.value.resolvedCount || 0,
|
||||
color: "#67c23a"
|
||||
},
|
||||
{
|
||||
name: "已关闭",
|
||||
value: statistics.value.closedCount || 0,
|
||||
color: "#13c2c2"
|
||||
}
|
||||
];
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: { type: "shadow" }
|
||||
},
|
||||
legend: {
|
||||
data: ["高风险", "中风险", "低风险"],
|
||||
right: 10,
|
||||
top: 10,
|
||||
itemWidth: 12,
|
||||
itemHeight: 12
|
||||
axisPointer: { type: "shadow" },
|
||||
formatter: (params: any) => {
|
||||
const data = params[0];
|
||||
return `${data.name}: ${data.value} 个`;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: "3%",
|
||||
right: "4%",
|
||||
bottom: "3%",
|
||||
top: "15%",
|
||||
top: "10%",
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: months,
|
||||
data: statusData.map(item => item.name),
|
||||
axisLine: { lineStyle: { color: "#dcdfe6" } },
|
||||
axisLabel: { color: "#606266" }
|
||||
axisLabel: { color: "#606266", fontSize: 12 },
|
||||
axisTick: { show: false }
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
@@ -445,25 +487,23 @@ function updateTrendChart() {
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "高风险",
|
||||
name: "风险数量",
|
||||
type: "bar",
|
||||
stack: "total",
|
||||
data: [5, 8, 6, 10, 12, 8],
|
||||
itemStyle: { color: "#f56c6c", borderRadius: [0, 0, 4, 4] }
|
||||
},
|
||||
{
|
||||
name: "中风险",
|
||||
type: "bar",
|
||||
stack: "total",
|
||||
data: [8, 12, 10, 15, 18, 16],
|
||||
itemStyle: { color: "#e6a23c" }
|
||||
},
|
||||
{
|
||||
name: "低风险",
|
||||
type: "bar",
|
||||
stack: "total",
|
||||
data: [10, 15, 12, 18, 20, 22],
|
||||
itemStyle: { color: "#67c23a", borderRadius: [4, 4, 0, 0] }
|
||||
data: statusData.map((item, index) => ({
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: item.color,
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
})),
|
||||
barWidth: "50%",
|
||||
label: {
|
||||
show: true,
|
||||
position: "top",
|
||||
color: "#606266",
|
||||
fontSize: 12,
|
||||
formatter: "{c}"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -708,7 +748,7 @@ onUnmounted(() => {
|
||||
<el-card shadow="hover" class="chart-card">
|
||||
<template #header>
|
||||
<div class="flex-bc">
|
||||
<span class="font-medium">风险分布</span>
|
||||
<span class="font-medium">风险分类分布</span>
|
||||
<el-button link>
|
||||
<component :is="useRenderIcon(MoreIcon)" />
|
||||
</el-button>
|
||||
@@ -717,28 +757,46 @@ onUnmounted(() => {
|
||||
<div ref="pieChartRef" class="chart-container" />
|
||||
<div class="risk-legend">
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background-color: #f56c6c" />
|
||||
<span>严重</span>
|
||||
<span class="legend-dot" style="background-color: #409eff" />
|
||||
<span>进度</span>
|
||||
<span class="legend-value">{{
|
||||
statistics.criticalCount || 0
|
||||
statistics.categoryStats?.schedule || 0
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background-color: #e6a23c" />
|
||||
<span>高</span>
|
||||
<span class="legend-value">{{ statistics.highCount || 0 }}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background-color: #409eff" />
|
||||
<span>中</span>
|
||||
<span>外部</span>
|
||||
<span class="legend-value">{{
|
||||
statistics.mediumCount || 0
|
||||
statistics.categoryStats?.external || 0
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background-color: #67c23a" />
|
||||
<span>低</span>
|
||||
<span class="legend-value">{{ statistics.lowCount || 0 }}</span>
|
||||
<span>技术</span>
|
||||
<span class="legend-value">{{
|
||||
statistics.categoryStats?.technical || 0
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background-color: #909399" />
|
||||
<span>资源</span>
|
||||
<span class="legend-value">{{
|
||||
statistics.categoryStats?.resource || 0
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background-color: #f56c6c" />
|
||||
<span>人员</span>
|
||||
<span class="legend-value">{{
|
||||
statistics.categoryStats?.personnel || 0
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background-color: #9c27b0" />
|
||||
<span>质量</span>
|
||||
<span class="legend-value">{{
|
||||
statistics.categoryStats?.quality || 0
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -747,7 +805,7 @@ onUnmounted(() => {
|
||||
<el-card shadow="hover" class="chart-card">
|
||||
<template #header>
|
||||
<div class="flex-bc">
|
||||
<span class="font-medium">风险趋势</span>
|
||||
<span class="font-medium">风险状态分布</span>
|
||||
<div class="flex gap-2">
|
||||
<el-radio-group v-model="trendPeriod" size="small">
|
||||
<el-radio-button label="月度" value="month" />
|
||||
|
||||
Reference in New Issue
Block a user