Compare commits
14 Commits
031dd03a62
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 111515f9ca | |||
| 00c521540e | |||
| 5b96f54d71 | |||
| 5bb9b6d3db | |||
| 5363ec8342 | |||
| dab86a40ff | |||
| d698fae12a | |||
| c7abf48c6a | |||
| 4b30c1350d | |||
| 2735c57778 | |||
| 93ea80a636 | |||
| 9c777ee429 | |||
| e10aa07367 | |||
| df6970b71c |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -23,4 +23,8 @@ tsconfig.tsbuildinfo
|
|||||||
|
|
||||||
#qoder
|
#qoder
|
||||||
|
|
||||||
**.qoder**
|
**.qoder**
|
||||||
|
|
||||||
|
#trae
|
||||||
|
|
||||||
|
**.trae**
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
{
|
|
||||||
"openapi": "3.0.1",
|
|
||||||
"info": {
|
|
||||||
"title": "默认模块",
|
|
||||||
"description": "",
|
|
||||||
"version": "1.0.0"
|
|
||||||
},
|
|
||||||
"tags": [],
|
|
||||||
"paths": {
|
|
||||||
"/api/v1/ai/kb/document/{docId}/chunks": {
|
|
||||||
"get": {
|
|
||||||
"summary": "获取文档分片列表",
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "获取文档分片列表\n获取文档分片列表\n获取指定文档的所有分片信息",
|
|
||||||
"tags": [],
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "docId",
|
|
||||||
"in": "path",
|
|
||||||
"description": "文档UUID",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"description": "",
|
|
||||||
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/BaseResponseListDocumentChunkVO",
|
|
||||||
"description": "分片列表"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/ai/kb/chunk/{chunkId}": {
|
|
||||||
"get": {
|
|
||||||
"summary": "获取分片详情",
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "获取分片详情\n获取分片详情\n获取指定分片的详细信息",
|
|
||||||
"tags": [],
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "chunkId",
|
|
||||||
"in": "path",
|
|
||||||
"description": "分片ID",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"description": "",
|
|
||||||
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/BaseResponseDocumentChunkVO",
|
|
||||||
"description": "分片详情"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"schemas": {
|
|
||||||
"DocumentChunkVO": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "分片ID"
|
|
||||||
},
|
|
||||||
"docId": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "原始文档ID"
|
|
||||||
},
|
|
||||||
"content": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "分片内容"
|
|
||||||
},
|
|
||||||
"chunkIndex": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "分片序号"
|
|
||||||
},
|
|
||||||
"chunkTotal": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "总分片数"
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "文档标题"
|
|
||||||
},
|
|
||||||
"docType": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "文档类型"
|
|
||||||
},
|
|
||||||
"sourceType": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "来源类型"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "状态"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"BaseResponseListDocumentChunkVO": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"code": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/DocumentChunkVO",
|
|
||||||
"description": "文档分片VO"
|
|
||||||
},
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"BaseResponseDocumentChunkVO": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"code": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/components/schemas/DocumentChunkVO",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {},
|
|
||||||
"securitySchemes": {}
|
|
||||||
},
|
|
||||||
"servers": [],
|
|
||||||
"security": []
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,7 @@ export type ProjectItem = {
|
|||||||
projectType?: string;
|
projectType?: string;
|
||||||
managerId?: number;
|
managerId?: number;
|
||||||
managerName?: string;
|
managerName?: string;
|
||||||
|
managerAvatar?: string;
|
||||||
planStartDate?: string;
|
planStartDate?: string;
|
||||||
planEndDate?: string;
|
planEndDate?: string;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
@@ -60,6 +61,60 @@ export const deleteProject = (id: string) => {
|
|||||||
return http.request<Result<void>>("delete", `/api/v1/project/${id}`);
|
return http.request<Result<void>>("delete", `/api/v1/project/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ==================== 项目管理 API ====================
|
||||||
|
|
||||||
|
/** 项目实体 - 根据 OpenAPI 定义(用于编辑) */
|
||||||
|
export type Project = {
|
||||||
|
id?: string;
|
||||||
|
projectCode?: string;
|
||||||
|
projectName?: string;
|
||||||
|
projectType?: string;
|
||||||
|
description?: string;
|
||||||
|
objectives?: string;
|
||||||
|
managerId?: string;
|
||||||
|
sponsorId?: string;
|
||||||
|
planStartDate?: string;
|
||||||
|
planEndDate?: string;
|
||||||
|
actualStartDate?: string;
|
||||||
|
actualEndDate?: string;
|
||||||
|
budget?: number;
|
||||||
|
cost?: number;
|
||||||
|
currency?: string;
|
||||||
|
progress?: number;
|
||||||
|
status?: string; // draft-草稿, planning-规划中, ongoing-进行中, paused-暂停, completed-已完成, cancelled-已取消
|
||||||
|
priority?: string; // critical-关键, high-高, medium-中, low-低
|
||||||
|
riskLevel?: string; // high-高, medium-中, low-低
|
||||||
|
visibility?: number; // 1-公开, 2-部门内, 3-项目成员
|
||||||
|
tags?: string[];
|
||||||
|
extraData?: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 修改项目 */
|
||||||
|
export const updateProject = (data: Project) => {
|
||||||
|
return http.request<Result<void>>("put", "/api/v1/project", { data });
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 更新项目状态 */
|
||||||
|
export const updateProjectStatus = (id: string, status: string) => {
|
||||||
|
return http.request<Result<void>>("put", `/api/v1/project/${id}/status`, {
|
||||||
|
params: { status }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 更新项目进度 */
|
||||||
|
export const updateProjectProgress = (id: string, progress: number) => {
|
||||||
|
return http.request<Result<void>>("put", `/api/v1/project/${id}/progress`, {
|
||||||
|
params: { progress }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 更新项目经理 */
|
||||||
|
export const updateProjectManager = (id: string, managerName: string) => {
|
||||||
|
return http.request<Result<void>>("put", `/api/v1/project/${id}/manager`, {
|
||||||
|
params: { managerName }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// ==================== 项目统计 ====================
|
// ==================== 项目统计 ====================
|
||||||
|
|
||||||
/** 项目统计数据 - 根据 OpenAPI 定义 */
|
/** 项目统计数据 - 根据 OpenAPI 定义 */
|
||||||
@@ -278,6 +333,158 @@ export const getProjectDetail = (projectId: string) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type OverallProgressAssessment = {
|
||||||
|
status?: string;
|
||||||
|
deviationPercentage?: number;
|
||||||
|
description?: string;
|
||||||
|
keyIssues?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DailyReportUpdateSuggestionVO = {
|
||||||
|
suggestionId?: string;
|
||||||
|
targetType?: string;
|
||||||
|
targetId?: string;
|
||||||
|
targetName?: string;
|
||||||
|
currentStatus?: string;
|
||||||
|
currentProgress?: number;
|
||||||
|
suggestedStatus?: string;
|
||||||
|
suggestedProgress?: number;
|
||||||
|
reason?: string;
|
||||||
|
confidence?: number;
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DailyReportAnalysisSuggestionsVO = {
|
||||||
|
analysisId?: string;
|
||||||
|
reportId?: string;
|
||||||
|
projectId?: string;
|
||||||
|
reportDate?: string;
|
||||||
|
overallProgressAssessment?: OverallProgressAssessment;
|
||||||
|
suggestions?: DailyReportUpdateSuggestionVO[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDailyReportAnalysisSuggestions = (params: {
|
||||||
|
projectId: string;
|
||||||
|
reportId?: string;
|
||||||
|
reportDate?: string;
|
||||||
|
submitterUsername?: string;
|
||||||
|
}) => {
|
||||||
|
return http.request<
|
||||||
|
Result<DailyReportAnalysisSuggestionsVO | DailyReportUpdateSuggestionVO[]>
|
||||||
|
>("get", "/api/v1/daily-report/analysis/suggestions", { params });
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApplyDailyReportSuggestionsRequest = {
|
||||||
|
projectId: string;
|
||||||
|
suggestionIds: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyDailyReportAnalysisSuggestions = (
|
||||||
|
data: ApplyDailyReportSuggestionsRequest
|
||||||
|
) => {
|
||||||
|
return http.request<Result<number>>(
|
||||||
|
"post",
|
||||||
|
"/api/v1/daily-report/analysis/suggestions/apply",
|
||||||
|
{ data }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MilestoneRisk = {
|
||||||
|
milestoneId?: string;
|
||||||
|
milestoneName?: string;
|
||||||
|
planDate?: string;
|
||||||
|
riskLevel?: string;
|
||||||
|
description?: string;
|
||||||
|
estimatedDelayDays?: number;
|
||||||
|
suggestion?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResourceNeed = {
|
||||||
|
resourceType?: string;
|
||||||
|
resourceName?: string;
|
||||||
|
quantity?: number;
|
||||||
|
unit?: string;
|
||||||
|
reason?: string;
|
||||||
|
suggestedArrivalDate?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProgressSuggestion = {
|
||||||
|
taskId?: string;
|
||||||
|
taskName?: string;
|
||||||
|
suggestionType?: string;
|
||||||
|
suggestion?: string;
|
||||||
|
priority?: string;
|
||||||
|
expectedEffect?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProgressUpdateRecommendation = {
|
||||||
|
targetType?: string;
|
||||||
|
targetId?: string;
|
||||||
|
suggestedStatus?: string;
|
||||||
|
suggestedProgress?: number;
|
||||||
|
reason?: string;
|
||||||
|
confidence?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IdentifiedRisk = {
|
||||||
|
riskName?: string;
|
||||||
|
category?: string;
|
||||||
|
description?: string;
|
||||||
|
probability?: number;
|
||||||
|
impact?: number;
|
||||||
|
riskLevel?: string;
|
||||||
|
impactScope?: string;
|
||||||
|
triggerCondition?: string;
|
||||||
|
mitigationPlan?: string;
|
||||||
|
contingencyPlan?: string;
|
||||||
|
priority?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DailyReportAnalysisResult = {
|
||||||
|
reportId?: string;
|
||||||
|
projectId?: string;
|
||||||
|
projectName?: string;
|
||||||
|
reportDate?: string;
|
||||||
|
overallProgressAssessment?: OverallProgressAssessment;
|
||||||
|
milestoneRisks?: MilestoneRisk[];
|
||||||
|
resourceNeeds?: ResourceNeed[];
|
||||||
|
progressSuggestions?: ProgressSuggestion[];
|
||||||
|
progressUpdateRecommendations?: ProgressUpdateRecommendation[];
|
||||||
|
identifiedRisks?: IdentifiedRisk[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DailyReportWithAnalysisVO = {
|
||||||
|
reportId?: string;
|
||||||
|
projectId?: string;
|
||||||
|
reportDate?: string;
|
||||||
|
submitterUsername?: string;
|
||||||
|
submitterId?: string;
|
||||||
|
workContent?: string;
|
||||||
|
tomorrowPlan?: string;
|
||||||
|
workIntensity?: number;
|
||||||
|
needHelp?: boolean;
|
||||||
|
helpContent?: string;
|
||||||
|
createTime?: string;
|
||||||
|
analysisId?: string;
|
||||||
|
analysisStatus?: string;
|
||||||
|
analysisResult?: DailyReportAnalysisResult;
|
||||||
|
analysisSuggestions?: DailyReportUpdateSuggestionVO[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDailyReportWithAnalysisReports = (params: {
|
||||||
|
projectId: string;
|
||||||
|
reportDate?: string;
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
suggestionStatus?: string;
|
||||||
|
}) => {
|
||||||
|
return http.request<Result<TableDataInfo<DailyReportWithAnalysisVO>>>(
|
||||||
|
"get",
|
||||||
|
"/api/v1/daily-report/analysis/reports",
|
||||||
|
{ params }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// ==================== 项目初始化(复用 system.ts 中的定义) ====================
|
// ==================== 项目初始化(复用 system.ts 中的定义) ====================
|
||||||
|
|
||||||
/** 项目信息 */
|
/** 项目信息 */
|
||||||
@@ -441,3 +648,312 @@ export const getTaskStatus = (taskId: string) => {
|
|||||||
`/api/v1/project-init/task/${taskId}`
|
`/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 } }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 资源管理 API ====================
|
||||||
|
|
||||||
|
/** 资源实体 - 根据 OpenAPI 定义 */
|
||||||
|
export type Resource = {
|
||||||
|
id?: string;
|
||||||
|
resourceCode?: string;
|
||||||
|
projectId?: string;
|
||||||
|
resourceType?: string; // human-人力, material-物料, equipment-设备, software-软件, finance-资金, other-其他
|
||||||
|
resourceName?: string;
|
||||||
|
description?: string;
|
||||||
|
specification?: string;
|
||||||
|
unit?: string;
|
||||||
|
planQuantity?: number;
|
||||||
|
actualQuantity?: number;
|
||||||
|
unitPrice?: number;
|
||||||
|
currency?: string;
|
||||||
|
supplier?: string;
|
||||||
|
status?: string; // planned-计划中, requested-已申请, approved-已批准, procuring-采购中, arrived-已到货, in_use-使用中, completed-已完成
|
||||||
|
planArriveDate?: string;
|
||||||
|
actualArriveDate?: string;
|
||||||
|
responsibleId?: string;
|
||||||
|
responsibleName?: string;
|
||||||
|
location?: string;
|
||||||
|
tags?: string[];
|
||||||
|
extraData?: Record<string, any>;
|
||||||
|
createBy?: string;
|
||||||
|
createTime?: string;
|
||||||
|
updateBy?: string;
|
||||||
|
updateTime?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 资源更新请求 - 根据 OpenAPI 定义 */
|
||||||
|
export type ResourceUpdateRequest = {
|
||||||
|
id: string;
|
||||||
|
resourceType?: string;
|
||||||
|
resourceName?: string;
|
||||||
|
description?: string;
|
||||||
|
specification?: string;
|
||||||
|
unit?: string;
|
||||||
|
planQuantity?: number;
|
||||||
|
actualQuantity?: number;
|
||||||
|
unitPrice?: number;
|
||||||
|
currency?: string;
|
||||||
|
supplier?: string;
|
||||||
|
status?: string;
|
||||||
|
planArriveDate?: string;
|
||||||
|
actualArriveDate?: string;
|
||||||
|
responsibleId?: string;
|
||||||
|
responsibleName?: string;
|
||||||
|
location?: string;
|
||||||
|
tags?: string[];
|
||||||
|
extraData?: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 资源查询参数 */
|
||||||
|
export type ResourceQueryParams = {
|
||||||
|
pageNum?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
projectId?: string;
|
||||||
|
resourceType?: string;
|
||||||
|
status?: string;
|
||||||
|
keyword?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 分页数据结构(资源列表用) */
|
||||||
|
export type PageResult<T> = {
|
||||||
|
records: T[];
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
current: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 分页查询资源列表 */
|
||||||
|
export const getResourceList = (params?: ResourceQueryParams) => {
|
||||||
|
return http.request<Result<PageResult<Resource>>>(
|
||||||
|
"get",
|
||||||
|
"/api/v1/resource/list",
|
||||||
|
{ params }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 根据ID查询资源详情 */
|
||||||
|
export const getResourceById = (id: string) => {
|
||||||
|
return http.request<Result<Resource>>("get", `/api/v1/resource/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 新增资源 */
|
||||||
|
export const createResource = (data: Resource) => {
|
||||||
|
return http.request<Result<string>>("post", "/api/v1/resource", { data });
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 修改资源 */
|
||||||
|
export const updateResource = (data: ResourceUpdateRequest) => {
|
||||||
|
return http.request<Result<void>>("put", "/api/v1/resource", { data });
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 删除资源 */
|
||||||
|
export const deleteResource = (id: string) => {
|
||||||
|
return http.request<Result<void>>("delete", `/api/v1/resource/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 更新资源状态 */
|
||||||
|
export const updateResourceStatus = (id: string, status: string) => {
|
||||||
|
return http.request<Result<void>>("put", `/api/v1/resource/${id}/status`, {
|
||||||
|
params: { status }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 更新资源数量 */
|
||||||
|
export const updateResourceQuantity = (id: string, actualQuantity: string) => {
|
||||||
|
return http.request<Result<void>>("put", `/api/v1/resource/${id}/quantity`, {
|
||||||
|
params: { actualQuantity }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 查询资源预算汇总 */
|
||||||
|
export const getResourceBudgetStats = (projectId: string) => {
|
||||||
|
return http.request<Result<Record<string, any>[]>>(
|
||||||
|
"get",
|
||||||
|
"/api/v1/resource/stats/budget",
|
||||||
|
{ params: { projectId } }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 查询即将到位的资源 */
|
||||||
|
export const getPendingArrivalResources = (
|
||||||
|
projectId: string,
|
||||||
|
days: number = 7
|
||||||
|
) => {
|
||||||
|
return http.request<Result<Resource[]>>(
|
||||||
|
"get",
|
||||||
|
"/api/v1/resource/pending-arrival",
|
||||||
|
{ params: { projectId, days } }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 查询待审批的资源申请 */
|
||||||
|
export const getPendingApprovalResources = (projectId?: string) => {
|
||||||
|
return http.request<Result<Record<string, any>[]>>(
|
||||||
|
"get",
|
||||||
|
"/api/v1/resource/pending-approval",
|
||||||
|
{ params: { projectId } }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -111,9 +111,9 @@ export type RiskStatisticsVO = {
|
|||||||
highCount: number;
|
highCount: number;
|
||||||
mediumCount: number;
|
mediumCount: number;
|
||||||
lowCount: number;
|
lowCount: number;
|
||||||
categoryStats: Record<string, number>;
|
categoryStats: Record<string, number | string>;
|
||||||
levelStats: Record<string, number>;
|
levelStats: Record<string, number | string>;
|
||||||
trendData: Record<string, number[]>;
|
trendData: Record<string, number[]> | null;
|
||||||
averageRiskScore: number;
|
averageRiskScore: number;
|
||||||
unresolvedHighCount: number;
|
unresolvedHighCount: number;
|
||||||
};
|
};
|
||||||
|
|||||||
547
src/api/日报和分析查询.openapi.json
Normal file
547
src/api/日报和分析查询.openapi.json
Normal file
@@ -0,0 +1,547 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.1",
|
||||||
|
"info": {
|
||||||
|
"title": "默认模块",
|
||||||
|
"description": "",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"tags": [],
|
||||||
|
"paths": {
|
||||||
|
"/api/v1/daily-report/analysis/reports": {
|
||||||
|
"get": {
|
||||||
|
"summary": "分页获取项目日报及分析结果",
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "",
|
||||||
|
"tags": [],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "projectId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reportDate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageNum",
|
||||||
|
"in": "query",
|
||||||
|
"description": "",
|
||||||
|
"required": true,
|
||||||
|
"example": 1,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"in": "query",
|
||||||
|
"description": "",
|
||||||
|
"required": true,
|
||||||
|
"example": 10,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "suggestionStatus",
|
||||||
|
"in": "query",
|
||||||
|
"description": "",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "",
|
||||||
|
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/BaseResponseTableDataInfoDailyReportWithAnalysisVO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"OverallProgressAssessment": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "进度状态:ahead-提前,on_track-正常,delayed-滞后"
|
||||||
|
},
|
||||||
|
"deviationPercentage": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "进度偏差百分比 (正数表示提前,负数表示滞后)"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "评估说明"
|
||||||
|
},
|
||||||
|
"keyIssues": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "关键问题"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DailyReportUpdateSuggestionVO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"suggestionId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"analysisId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"reportId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"reportDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"submitterUsername": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"targetType": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"targetId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"targetName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"currentStatus": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"currentProgress": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"suggestedStatus": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"suggestedProgress": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"confidence": {
|
||||||
|
"type": "number",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MilestoneRisk": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"milestoneId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "里程碑 ID",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"milestoneName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "里程碑名称"
|
||||||
|
},
|
||||||
|
"planDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "计划完成日期"
|
||||||
|
},
|
||||||
|
"riskLevel": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "风险等级:critical-严重,high-高,medium-中,low-低"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "风险描述"
|
||||||
|
},
|
||||||
|
"estimatedDelayDays": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "延期天数 (预估)"
|
||||||
|
},
|
||||||
|
"suggestion": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "建议措施"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ResourceNeed": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"resourceType": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "资源类型:human-人力,material-物料,equipment-设备,other-其他"
|
||||||
|
},
|
||||||
|
"resourceName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "资源名称"
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "需求数量"
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "单位"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "需求原因"
|
||||||
|
},
|
||||||
|
"suggestedArrivalDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "建议到位时间"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProgressSuggestion": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"taskId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "任务 ID (如果有明确关联的任务)",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"taskName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "任务名称"
|
||||||
|
},
|
||||||
|
"suggestionType": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "建议类型:accelerate-加速,adjust_plan-调整计划,add_resource-增加资源,reorder-重新排序"
|
||||||
|
},
|
||||||
|
"suggestion": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "具体建议内容"
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "优先级:critical-紧急,high-高,medium-中,low-低"
|
||||||
|
},
|
||||||
|
"expectedEffect": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "预期效果"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProgressUpdateRecommendation": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"targetType": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "建议作用对象:task / milestone"
|
||||||
|
},
|
||||||
|
"targetId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "任务ID或里程碑ID",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"suggestedStatus": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "建议状态:pending / in_progress / completed / delayed 等(与现有状态体系保持一致)"
|
||||||
|
},
|
||||||
|
"suggestedProgress": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "建议进度 0-100"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "建议理由"
|
||||||
|
},
|
||||||
|
"confidence": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "置信度 0-1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DailyReportAnalysisResult": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"reportId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "日报ID",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"projectId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "项目 ID",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"projectName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "项目名称"
|
||||||
|
},
|
||||||
|
"reportDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "日报日期"
|
||||||
|
},
|
||||||
|
"overallProgressAssessment": {
|
||||||
|
"$ref": "#/components/schemas/OverallProgressAssessment",
|
||||||
|
"description": "整体进度评估"
|
||||||
|
},
|
||||||
|
"milestoneRisks": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/MilestoneRisk",
|
||||||
|
"description": "里程碑风险"
|
||||||
|
},
|
||||||
|
"description": "里程碑风险列表"
|
||||||
|
},
|
||||||
|
"resourceNeeds": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ResourceNeed",
|
||||||
|
"description": "资源需求"
|
||||||
|
},
|
||||||
|
"description": "资源需求列表"
|
||||||
|
},
|
||||||
|
"progressSuggestions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ProgressSuggestion",
|
||||||
|
"description": "进度建议"
|
||||||
|
},
|
||||||
|
"description": "进度建议列表"
|
||||||
|
},
|
||||||
|
"progressUpdateRecommendations": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ProgressUpdateRecommendation",
|
||||||
|
"description": "进度更新建议(可回写)"
|
||||||
|
},
|
||||||
|
"description": "可直接回写到任务/里程碑的进度更新建议(需要用户确认后才执行)"
|
||||||
|
},
|
||||||
|
"identifiedRisks": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/IdentifiedRisk",
|
||||||
|
"description": "识别的风险 (直接入库)"
|
||||||
|
},
|
||||||
|
"description": "识别的风险列表 (直接入库)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DailyReportWithAnalysisVO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"reportId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"projectId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"reportDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"submitterUsername": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"submitterId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"workContent": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"tomorrowPlan": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"workIntensity": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"needHelp": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"helpContent": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"createTime": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"analysisId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"analysisStatus": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"analysisResult": {
|
||||||
|
"$ref": "#/components/schemas/DailyReportAnalysisResult",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"analysisSuggestions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/DailyReportUpdateSuggestionVO",
|
||||||
|
"description": "cn.yinlihupo.domain.vo.DailyReportUpdateSuggestionVO"
|
||||||
|
},
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TableDataInfoDailyReportWithAnalysisVO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"total": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "总记录数",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"rows": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/DailyReportWithAnalysisVO",
|
||||||
|
"description": "cn.yinlihupo.domain.vo.DailyReportWithAnalysisVO"
|
||||||
|
},
|
||||||
|
"description": "列表数据"
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "消息状态码"
|
||||||
|
},
|
||||||
|
"msg": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "消息内容"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"BaseResponseTableDataInfoDailyReportWithAnalysisVO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/components/schemas/TableDataInfoDailyReportWithAnalysisVO",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IdentifiedRisk": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"riskName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "风险名称"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "风险分类: technical-技术风险, schedule-进度风险, cost-成本风险, quality-质量风险, resource-资源风险, external-外部风险, personnel-人员风险"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "风险描述"
|
||||||
|
},
|
||||||
|
"probability": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "发生概率(0-100%)"
|
||||||
|
},
|
||||||
|
"impact": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "影响程度(1-5)"
|
||||||
|
},
|
||||||
|
"riskLevel": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "风险等级:calculated from probability * impact"
|
||||||
|
},
|
||||||
|
"impactScope": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "影响范围"
|
||||||
|
},
|
||||||
|
"triggerCondition": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "触发条件"
|
||||||
|
},
|
||||||
|
"mitigationPlan": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "缓解措施"
|
||||||
|
},
|
||||||
|
"contingencyPlan": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "应急计划"
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "建议优先级: critical-紧急, high-高, medium-中, low-低"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"servers": [],
|
||||||
|
"security": []
|
||||||
|
}
|
||||||
@@ -1,606 +0,0 @@
|
|||||||
{
|
|
||||||
"openapi": "3.0.1",
|
|
||||||
"info": {
|
|
||||||
"title": "默认模块",
|
|
||||||
"description": "",
|
|
||||||
"version": "1.0.0"
|
|
||||||
},
|
|
||||||
"tags": [],
|
|
||||||
"paths": {
|
|
||||||
"/api/v1/project-init/sse/submit-task": {
|
|
||||||
"post": {
|
|
||||||
"summary": "通过 SSE 提交项目初始化任务",
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "使用通用 SSE 通道,通过 userId 推送进度",
|
|
||||||
"tags": [],
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "userId",
|
|
||||||
"in": "query",
|
|
||||||
"description": "用户ID",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"description": "",
|
|
||||||
"example": "Bearer 6bf1de0f-9edf-413f-a98f-1103b8dc30fc",
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer 6bf1de0f-9edf-413f-a98f-1103b8dc30fc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"multipart/form-data": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"file": {
|
|
||||||
"description": "项目资料文件",
|
|
||||||
"type": "string",
|
|
||||||
"format": "binary"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["file"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/BaseResponseMapObject",
|
|
||||||
"description": "提交结果"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/project-init/my-tasks": {
|
|
||||||
"get": {
|
|
||||||
"summary": "查询我的任务列表",
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "根据当前登录用户的token查询其所有任务",
|
|
||||||
"tags": [],
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"description": "",
|
|
||||||
"example": "Bearer 6bf1de0f-9edf-413f-a98f-1103b8dc30fc",
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer 6bf1de0f-9edf-413f-a98f-1103b8dc30fc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/BaseResponseListProjectInitTaskVO",
|
|
||||||
"description": "任务列表"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/project-init/my-tasks/stats": {
|
|
||||||
"get": {
|
|
||||||
"summary": "查询我的任务统计信息",
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "",
|
|
||||||
"tags": [],
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"description": "",
|
|
||||||
"example": "Bearer 6bf1de0f-9edf-413f-a98f-1103b8dc30fc",
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer 6bf1de0f-9edf-413f-a98f-1103b8dc30fc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/BaseResponseMapObject",
|
|
||||||
"description": "统计信息"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/project-init/task/{taskId}": {
|
|
||||||
"get": {
|
|
||||||
"summary": "查询单个任务状态",
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "",
|
|
||||||
"tags": [],
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "taskId",
|
|
||||||
"in": "path",
|
|
||||||
"description": "任务ID",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"description": "",
|
|
||||||
"example": "Bearer 6bf1de0f-9edf-413f-a98f-1103b8dc30fc",
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer 6bf1de0f-9edf-413f-a98f-1103b8dc30fc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/BaseResponseProjectInitTaskVO",
|
|
||||||
"description": "任务状态"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"schemas": {
|
|
||||||
"ProjectInfo": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"project_name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"project_type": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"objectives": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"plan_start_date": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"plan_end_date": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"budget": {
|
|
||||||
"type": "number",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"priority": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"MilestoneInfo": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"milestone_name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"plan_date": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"deliverables": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"owner_role": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"TaskInfo": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"task_id": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"task_name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"parent_task_id": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"plan_start_date": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"plan_end_date": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"estimated_hours": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"priority": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"assignee_role": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"deliverables": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"BaseResponseMapObject": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"code": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"total": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"processing": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"completed": {
|
|
||||||
"type": "null"
|
|
||||||
},
|
|
||||||
"failed": {
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"MemberInfo": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"role_code": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"responsibility": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"department": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"weekly_hours": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ResourceInfo": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"resource_name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"resource_type": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"quantity": {
|
|
||||||
"type": "number",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"unit": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"unit_price": {
|
|
||||||
"type": "number",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"supplier": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"RiskInfo": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"risk_name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"probability": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"impact": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"mitigation_plan": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"TimelineNodeInfo": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"node_name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"node_type": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"plan_date": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"kb_scope": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ProjectInitResult": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"project": {
|
|
||||||
"$ref": "#/components/schemas/ProjectInfo",
|
|
||||||
"description": "项目基本信息"
|
|
||||||
},
|
|
||||||
"milestones": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/MilestoneInfo",
|
|
||||||
"description": "cn.yinlihupo.domain.dto.ProjectInitResult.MilestoneInfo"
|
|
||||||
},
|
|
||||||
"description": "里程碑列表"
|
|
||||||
},
|
|
||||||
"tasks": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/TaskInfo",
|
|
||||||
"description": "cn.yinlihupo.domain.dto.ProjectInitResult.TaskInfo"
|
|
||||||
},
|
|
||||||
"description": "任务清单"
|
|
||||||
},
|
|
||||||
"members": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/MemberInfo",
|
|
||||||
"description": "cn.yinlihupo.domain.dto.ProjectInitResult.MemberInfo"
|
|
||||||
},
|
|
||||||
"description": "项目成员"
|
|
||||||
},
|
|
||||||
"resources": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/ResourceInfo",
|
|
||||||
"description": "cn.yinlihupo.domain.dto.ProjectInitResult.ResourceInfo"
|
|
||||||
},
|
|
||||||
"description": "资源需求"
|
|
||||||
},
|
|
||||||
"risks": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/RiskInfo",
|
|
||||||
"description": "cn.yinlihupo.domain.dto.ProjectInitResult.RiskInfo"
|
|
||||||
},
|
|
||||||
"description": "风险识别"
|
|
||||||
},
|
|
||||||
"timeline_nodes": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/TimelineNodeInfo",
|
|
||||||
"description": "cn.yinlihupo.domain.dto.ProjectInitResult.TimelineNodeInfo"
|
|
||||||
},
|
|
||||||
"description": "时间节点"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ProjectInitTaskVO": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"taskId": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "任务ID"
|
|
||||||
},
|
|
||||||
"userId": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "用户ID(任务所属用户)",
|
|
||||||
"format": "int64"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "任务状态: pending-待处理, processing-处理中, completed-已完成, failed-失败"
|
|
||||||
},
|
|
||||||
"statusDesc": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "状态描述"
|
|
||||||
},
|
|
||||||
"progress": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "当前进度百分比 (0-100)"
|
|
||||||
},
|
|
||||||
"progressMessage": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "进度描述信息"
|
|
||||||
},
|
|
||||||
"originalFilename": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "原始文件名"
|
|
||||||
},
|
|
||||||
"createTime": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "任务创建时间"
|
|
||||||
},
|
|
||||||
"startTime": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "任务开始处理时间"
|
|
||||||
},
|
|
||||||
"completeTime": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "任务完成时间"
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/components/schemas/ProjectInitResult",
|
|
||||||
"description": "处理结果(仅当status=completed时有值)"
|
|
||||||
},
|
|
||||||
"errorMessage": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "错误信息(仅当status=failed时有值)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"BaseResponseListProjectInitTaskVO": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"code": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/ProjectInitTaskVO",
|
|
||||||
"description": "项目初始化异步任务VO"
|
|
||||||
},
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"BaseResponseProjectInitTaskVO": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"code": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/components/schemas/ProjectInitTaskVO",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {},
|
|
||||||
"securitySchemes": {}
|
|
||||||
},
|
|
||||||
"servers": [],
|
|
||||||
"security": []
|
|
||||||
}
|
|
||||||
@@ -1,594 +0,0 @@
|
|||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"data": {
|
|
||||||
"id": "2037831996319100930",
|
|
||||||
"projectCode": "PRJ15F5BF5B5CAD",
|
|
||||||
"projectName": "AIHR 智能简历筛选系统",
|
|
||||||
"projectType": "研发项目",
|
|
||||||
"description": "利用大语言模型(LLM)和自然语言处理(NLP)技术,实现简历的自动化解析、人岗匹配度评分及候选人排序,提升招聘效率。",
|
|
||||||
"objectives": "1. 效率提升:单份处理时间降至10秒内。2. 精准匹配:人岗匹配度评分准确率>90%。3. 系统集成:无缝对接主流招聘网站及内部ATS。4. 合规安全:候选人数据脱敏,符合《个人信息保护法》。",
|
|
||||||
"managerId": null,
|
|
||||||
"managerName": null,
|
|
||||||
"sponsorId": null,
|
|
||||||
"planStartDate": "2026-04-01",
|
|
||||||
"planEndDate": "2026-09-30",
|
|
||||||
"actualStartDate": null,
|
|
||||||
"actualEndDate": null,
|
|
||||||
"budget": 2850000.0,
|
|
||||||
"cost": 0.0,
|
|
||||||
"currency": "CNY",
|
|
||||||
"progress": 0,
|
|
||||||
"status": "planning",
|
|
||||||
"priority": "high",
|
|
||||||
"riskLevel": "low",
|
|
||||||
"visibility": 1,
|
|
||||||
"tags": null,
|
|
||||||
"createTime": null,
|
|
||||||
"updateTime": null,
|
|
||||||
"memberCount": 4,
|
|
||||||
"taskCount": 8,
|
|
||||||
"completedTaskCount": 0,
|
|
||||||
"milestoneCount": 6,
|
|
||||||
"resourceCount": 6,
|
|
||||||
"riskCount": 4,
|
|
||||||
"highRiskCount": 0,
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"id": "2037831996608507907",
|
|
||||||
"userId": null,
|
|
||||||
"userName": null,
|
|
||||||
"realName": null,
|
|
||||||
"avatar": null,
|
|
||||||
"roleCode": "manager",
|
|
||||||
"department": null,
|
|
||||||
"responsibility": "负责整体项目规划、进度控制、风险管理及跨部门协调",
|
|
||||||
"weeklyHours": 40.0,
|
|
||||||
"joinDate": null,
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996646256642",
|
|
||||||
"userId": null,
|
|
||||||
"userName": null,
|
|
||||||
"realName": null,
|
|
||||||
"avatar": null,
|
|
||||||
"roleCode": "leader",
|
|
||||||
"department": null,
|
|
||||||
"responsibility": "负责需求梳理、原型设计、用户故事编写及验收测试",
|
|
||||||
"weeklyHours": 40.0,
|
|
||||||
"joinDate": null,
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996646256643",
|
|
||||||
"userId": null,
|
|
||||||
"userName": null,
|
|
||||||
"realName": null,
|
|
||||||
"avatar": null,
|
|
||||||
"roleCode": "leader",
|
|
||||||
"department": null,
|
|
||||||
"responsibility": "负责系统整体技术选型、架构设计、核心技术难点攻关",
|
|
||||||
"weeklyHours": 20.0,
|
|
||||||
"joinDate": null,
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996646256644",
|
|
||||||
"userId": null,
|
|
||||||
"userName": null,
|
|
||||||
"realName": null,
|
|
||||||
"avatar": null,
|
|
||||||
"roleCode": "member",
|
|
||||||
"department": null,
|
|
||||||
"responsibility": "负责测试用例编写、功能测试、性能测试及自动化测试脚本",
|
|
||||||
"weeklyHours": 40.0,
|
|
||||||
"joinDate": null,
|
|
||||||
"status": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"milestones": [
|
|
||||||
{
|
|
||||||
"id": "2037831996319100931",
|
|
||||||
"milestoneName": "需求分析与架构设计",
|
|
||||||
"description": "完成需求评审及技术架构可行性验证",
|
|
||||||
"planDate": "2026-04-30",
|
|
||||||
"actualDate": null,
|
|
||||||
"status": "pending",
|
|
||||||
"progress": 0,
|
|
||||||
"isKey": 0,
|
|
||||||
"deliverables": null,
|
|
||||||
"sortOrder": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996319100932",
|
|
||||||
"milestoneName": "核心算法模型训练与验证",
|
|
||||||
"description": "模型在测试集上的准确率>85%,解析字段覆盖率>95%",
|
|
||||||
"planDate": "2026-06-15",
|
|
||||||
"actualDate": null,
|
|
||||||
"status": "pending",
|
|
||||||
"progress": 0,
|
|
||||||
"isKey": 0,
|
|
||||||
"deliverables": null,
|
|
||||||
"sortOrder": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996319100933",
|
|
||||||
"milestoneName": "系统功能开发完成 (Alpha 版)",
|
|
||||||
"description": "核心功能(上传、解析、评分、搜索)闭环跑通",
|
|
||||||
"planDate": "2026-07-31",
|
|
||||||
"actualDate": null,
|
|
||||||
"status": "pending",
|
|
||||||
"progress": 0,
|
|
||||||
"isKey": 0,
|
|
||||||
"deliverables": null,
|
|
||||||
"sortOrder": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996386209793",
|
|
||||||
"milestoneName": "系统集成与内部测试 (Beta 版)",
|
|
||||||
"description": "支持并发用户数>50,响应时间<2秒,无严重 Bug",
|
|
||||||
"planDate": "2026-08-31",
|
|
||||||
"actualDate": null,
|
|
||||||
"status": "pending",
|
|
||||||
"progress": 0,
|
|
||||||
"isKey": 0,
|
|
||||||
"deliverables": null,
|
|
||||||
"sortOrder": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996386209794",
|
|
||||||
"milestoneName": "用户验收测试 (UAT) 与试点",
|
|
||||||
"description": "试点部门(人力资源部)确认功能满足业务需求",
|
|
||||||
"planDate": "2026-09-15",
|
|
||||||
"actualDate": null,
|
|
||||||
"status": "pending",
|
|
||||||
"progress": 0,
|
|
||||||
"isKey": 0,
|
|
||||||
"deliverables": null,
|
|
||||||
"sortOrder": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996386209795",
|
|
||||||
"milestoneName": "正式上线与交付",
|
|
||||||
"description": "系统正式部署至生产环境,完成全员培训",
|
|
||||||
"planDate": "2026-09-30",
|
|
||||||
"actualDate": null,
|
|
||||||
"status": "pending",
|
|
||||||
"progress": 0,
|
|
||||||
"isKey": 0,
|
|
||||||
"deliverables": null,
|
|
||||||
"sortOrder": 5
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"id": "2037831996386209796",
|
|
||||||
"taskCode": null,
|
|
||||||
"taskName": "需求分析与产品设计",
|
|
||||||
"description": "梳理业务需求,输出原型设计与需求规格说明书",
|
|
||||||
"taskType": null,
|
|
||||||
"milestoneId": "2037831996319100931",
|
|
||||||
"assigneeId": null,
|
|
||||||
"assigneeName": null,
|
|
||||||
"planStartDate": "2026-04-01",
|
|
||||||
"planEndDate": "2026-04-20",
|
|
||||||
"actualStartDate": null,
|
|
||||||
"actualEndDate": null,
|
|
||||||
"planHours": 120.0,
|
|
||||||
"actualHours": null,
|
|
||||||
"progress": 0,
|
|
||||||
"priority": "high",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996449124353",
|
|
||||||
"taskCode": null,
|
|
||||||
"taskName": "系统架构与数据库设计",
|
|
||||||
"description": "完成技术选型、架构设计及数据库表结构设计",
|
|
||||||
"taskType": null,
|
|
||||||
"milestoneId": "2037831996319100932",
|
|
||||||
"assigneeId": null,
|
|
||||||
"assigneeName": null,
|
|
||||||
"planStartDate": "2026-04-15",
|
|
||||||
"planEndDate": "2026-04-30",
|
|
||||||
"actualStartDate": null,
|
|
||||||
"actualEndDate": null,
|
|
||||||
"planHours": 80.0,
|
|
||||||
"actualHours": null,
|
|
||||||
"progress": 0,
|
|
||||||
"priority": "high",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996449124354",
|
|
||||||
"taskCode": null,
|
|
||||||
"taskName": "数据准备与标注",
|
|
||||||
"description": "处理10万份历史简历并完成5000对简历-JD匹配标注",
|
|
||||||
"taskType": null,
|
|
||||||
"milestoneId": "2037831996319100933",
|
|
||||||
"assigneeId": null,
|
|
||||||
"assigneeName": null,
|
|
||||||
"planStartDate": "2026-04-20",
|
|
||||||
"planEndDate": "2026-05-20",
|
|
||||||
"actualStartDate": null,
|
|
||||||
"actualEndDate": null,
|
|
||||||
"planHours": 160.0,
|
|
||||||
"actualHours": null,
|
|
||||||
"progress": 0,
|
|
||||||
"priority": "high",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996449124355",
|
|
||||||
"taskCode": null,
|
|
||||||
"taskName": "核心算法模型训练",
|
|
||||||
"description": "基于大模型进行简历解析引擎与匹配算法的训练和调优",
|
|
||||||
"taskType": null,
|
|
||||||
"milestoneId": "2037831996386209793",
|
|
||||||
"assigneeId": null,
|
|
||||||
"assigneeName": null,
|
|
||||||
"planStartDate": "2026-05-01",
|
|
||||||
"planEndDate": "2026-06-15",
|
|
||||||
"actualStartDate": null,
|
|
||||||
"actualEndDate": null,
|
|
||||||
"planHours": 240.0,
|
|
||||||
"actualHours": null,
|
|
||||||
"progress": 0,
|
|
||||||
"priority": "high",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996449124356",
|
|
||||||
"taskCode": null,
|
|
||||||
"taskName": "系统前后端功能开发",
|
|
||||||
"description": "开发后端服务及前端管理界面,并完成API对接",
|
|
||||||
"taskType": null,
|
|
||||||
"milestoneId": "2037831996386209794",
|
|
||||||
"assigneeId": null,
|
|
||||||
"assigneeName": null,
|
|
||||||
"planStartDate": "2026-06-01",
|
|
||||||
"planEndDate": "2026-07-31",
|
|
||||||
"actualStartDate": null,
|
|
||||||
"actualEndDate": null,
|
|
||||||
"planHours": 320.0,
|
|
||||||
"actualHours": null,
|
|
||||||
"progress": 0,
|
|
||||||
"priority": "high",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996449124357",
|
|
||||||
"taskCode": null,
|
|
||||||
"taskName": "系统集成与内测",
|
|
||||||
"description": "系统整体集成、性能测试及缺陷修复",
|
|
||||||
"taskType": null,
|
|
||||||
"milestoneId": "2037831996386209795",
|
|
||||||
"assigneeId": null,
|
|
||||||
"assigneeName": null,
|
|
||||||
"planStartDate": "2026-08-01",
|
|
||||||
"planEndDate": "2026-08-31",
|
|
||||||
"actualStartDate": null,
|
|
||||||
"actualEndDate": null,
|
|
||||||
"planHours": 160.0,
|
|
||||||
"actualHours": null,
|
|
||||||
"progress": 0,
|
|
||||||
"priority": "high",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996449124358",
|
|
||||||
"taskCode": null,
|
|
||||||
"taskName": "用户验收测试与试点",
|
|
||||||
"description": "人力资源部介入试用系统,收集反馈并进行优化",
|
|
||||||
"taskType": null,
|
|
||||||
"milestoneId": "2037831996319100931",
|
|
||||||
"assigneeId": null,
|
|
||||||
"assigneeName": null,
|
|
||||||
"planStartDate": "2026-09-01",
|
|
||||||
"planEndDate": "2026-09-15",
|
|
||||||
"actualStartDate": null,
|
|
||||||
"actualEndDate": null,
|
|
||||||
"planHours": 80.0,
|
|
||||||
"actualHours": null,
|
|
||||||
"progress": 0,
|
|
||||||
"priority": "medium",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996516233217",
|
|
||||||
"taskCode": null,
|
|
||||||
"taskName": "生产部署与交付",
|
|
||||||
"description": "系统部署上线,组织全员培训,输出总结报告",
|
|
||||||
"taskType": null,
|
|
||||||
"milestoneId": "2037831996319100932",
|
|
||||||
"assigneeId": null,
|
|
||||||
"assigneeName": null,
|
|
||||||
"planStartDate": "2026-09-16",
|
|
||||||
"planEndDate": "2026-09-30",
|
|
||||||
"actualStartDate": null,
|
|
||||||
"actualEndDate": null,
|
|
||||||
"planHours": 80.0,
|
|
||||||
"actualHours": null,
|
|
||||||
"progress": 0,
|
|
||||||
"priority": "high",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 7
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resources": [
|
|
||||||
{
|
|
||||||
"id": "2037831996646256645",
|
|
||||||
"resourceCode": null,
|
|
||||||
"resourceType": "equipment",
|
|
||||||
"resourceName": "高性能云服务器 (16核64G)",
|
|
||||||
"description": null,
|
|
||||||
"specification": null,
|
|
||||||
"unit": "台",
|
|
||||||
"planQuantity": 5.0,
|
|
||||||
"actualQuantity": null,
|
|
||||||
"unitPrice": 2000.0,
|
|
||||||
"currency": "CNY",
|
|
||||||
"supplier": "云服务提供商",
|
|
||||||
"status": "planned",
|
|
||||||
"planArriveDate": null,
|
|
||||||
"actualArriveDate": null,
|
|
||||||
"responsibleId": null,
|
|
||||||
"location": null,
|
|
||||||
"tags": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996709171202",
|
|
||||||
"resourceCode": null,
|
|
||||||
"resourceType": "equipment",
|
|
||||||
"resourceName": "云端 GPU 算力资源 (NVIDIA A100/A800)",
|
|
||||||
"description": null,
|
|
||||||
"specification": null,
|
|
||||||
"unit": "小时",
|
|
||||||
"planQuantity": 2000.0,
|
|
||||||
"actualQuantity": null,
|
|
||||||
"unitPrice": 50.0,
|
|
||||||
"currency": "CNY",
|
|
||||||
"supplier": "云算力平台",
|
|
||||||
"status": "planned",
|
|
||||||
"planArriveDate": null,
|
|
||||||
"actualArriveDate": null,
|
|
||||||
"responsibleId": null,
|
|
||||||
"location": null,
|
|
||||||
"tags": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996709171203",
|
|
||||||
"resourceCode": null,
|
|
||||||
"resourceType": "equipment",
|
|
||||||
"resourceName": "对象存储空间",
|
|
||||||
"description": null,
|
|
||||||
"specification": null,
|
|
||||||
"unit": "TB",
|
|
||||||
"planQuantity": 5.0,
|
|
||||||
"actualQuantity": null,
|
|
||||||
"unitPrice": 300.0,
|
|
||||||
"currency": "CNY",
|
|
||||||
"supplier": "云服务提供商",
|
|
||||||
"status": "planned",
|
|
||||||
"planArriveDate": null,
|
|
||||||
"actualArriveDate": null,
|
|
||||||
"responsibleId": null,
|
|
||||||
"location": null,
|
|
||||||
"tags": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996709171204",
|
|
||||||
"resourceCode": null,
|
|
||||||
"resourceType": "material",
|
|
||||||
"resourceName": "历史简历数据",
|
|
||||||
"description": null,
|
|
||||||
"specification": null,
|
|
||||||
"unit": "份",
|
|
||||||
"planQuantity": 100000.0,
|
|
||||||
"actualQuantity": null,
|
|
||||||
"unitPrice": 0.0,
|
|
||||||
"currency": "CNY",
|
|
||||||
"supplier": "企业内部",
|
|
||||||
"status": "planned",
|
|
||||||
"planArriveDate": null,
|
|
||||||
"actualArriveDate": null,
|
|
||||||
"responsibleId": null,
|
|
||||||
"location": null,
|
|
||||||
"tags": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996709171205",
|
|
||||||
"resourceCode": null,
|
|
||||||
"resourceType": "material",
|
|
||||||
"resourceName": "高质量匹配标注数据集",
|
|
||||||
"description": null,
|
|
||||||
"specification": null,
|
|
||||||
"unit": "对",
|
|
||||||
"planQuantity": 5000.0,
|
|
||||||
"actualQuantity": null,
|
|
||||||
"unitPrice": 10.0,
|
|
||||||
"currency": "CNY",
|
|
||||||
"supplier": "数据标注专员",
|
|
||||||
"status": "planned",
|
|
||||||
"planArriveDate": null,
|
|
||||||
"actualArriveDate": null,
|
|
||||||
"responsibleId": null,
|
|
||||||
"location": null,
|
|
||||||
"tags": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996709171206",
|
|
||||||
"resourceCode": null,
|
|
||||||
"resourceType": "human",
|
|
||||||
"resourceName": "法律合规咨询服务",
|
|
||||||
"description": null,
|
|
||||||
"specification": null,
|
|
||||||
"unit": "项",
|
|
||||||
"planQuantity": 1.0,
|
|
||||||
"actualQuantity": null,
|
|
||||||
"unitPrice": 50000.0,
|
|
||||||
"currency": "CNY",
|
|
||||||
"supplier": "外部法律顾问",
|
|
||||||
"status": "planned",
|
|
||||||
"planArriveDate": null,
|
|
||||||
"actualArriveDate": null,
|
|
||||||
"responsibleId": null,
|
|
||||||
"location": null,
|
|
||||||
"tags": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"risks": [
|
|
||||||
{
|
|
||||||
"id": "2037831996780474370",
|
|
||||||
"riskCode": null,
|
|
||||||
"category": "technical",
|
|
||||||
"riskName": "模型准确率不达标风险",
|
|
||||||
"description": "大语言模型可能出现幻觉或对复杂简历解析不准,导致人岗匹配准确率低于90%",
|
|
||||||
"probability": 40.0,
|
|
||||||
"impact": 4.0,
|
|
||||||
"riskScore": 1.6,
|
|
||||||
"riskLevel": "medium",
|
|
||||||
"status": "identified",
|
|
||||||
"ownerId": null,
|
|
||||||
"mitigationPlan": "引入高质量标注数据集微调模型,增加人工专家复核机制,持续优化算法",
|
|
||||||
"dueDate": null,
|
|
||||||
"discoverTime": "2026-03-28T17:59:43.784116"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996780474369",
|
|
||||||
"riskCode": null,
|
|
||||||
"category": "external",
|
|
||||||
"riskName": "数据隐私与合规风险",
|
|
||||||
"description": "简历数据包含大量个人敏感信息,处理不当可能违反《个人信息保护法》",
|
|
||||||
"probability": 30.0,
|
|
||||||
"impact": 5.0,
|
|
||||||
"riskScore": 1.5,
|
|
||||||
"riskLevel": "medium",
|
|
||||||
"status": "identified",
|
|
||||||
"ownerId": null,
|
|
||||||
"mitigationPlan": "聘请法律顾问审核全流程合规性,严格实施数据脱敏处理,建立完善的数据访问权限控制",
|
|
||||||
"dueDate": null,
|
|
||||||
"discoverTime": "2026-03-28T17:59:43.778244"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996780474371",
|
|
||||||
"riskCode": null,
|
|
||||||
"category": "technical",
|
|
||||||
"riskName": "第三方ATS系统集成风险",
|
|
||||||
"description": "对接主流招聘网站及内部ATS时可能遇到API限制或数据格式不兼容问题",
|
|
||||||
"probability": 50.0,
|
|
||||||
"impact": 3.0,
|
|
||||||
"riskScore": 1.5,
|
|
||||||
"riskLevel": "medium",
|
|
||||||
"status": "identified",
|
|
||||||
"ownerId": null,
|
|
||||||
"mitigationPlan": "提前获取第三方接口文档并进行技术验证,设计高兼容性的中间件适配层",
|
|
||||||
"dueDate": null,
|
|
||||||
"discoverTime": "2026-03-28T17:59:43.786088"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996780474372",
|
|
||||||
"riskCode": null,
|
|
||||||
"category": "resource",
|
|
||||||
"riskName": "算力资源短缺风险",
|
|
||||||
"description": "云端高端GPU(如A100/A800)可能存在排队或租赁不到位的情况,影响训练进度",
|
|
||||||
"probability": 25.0,
|
|
||||||
"impact": 4.0,
|
|
||||||
"riskScore": 1.0,
|
|
||||||
"riskLevel": "medium",
|
|
||||||
"status": "identified",
|
|
||||||
"ownerId": null,
|
|
||||||
"mitigationPlan": "提前锁定云资源供应商并签订算力保障协议,准备备用算力平台方案",
|
|
||||||
"dueDate": null,
|
|
||||||
"discoverTime": "2026-03-28T17:59:43.789088"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timelineNodes": [
|
|
||||||
{
|
|
||||||
"id": "2037831996843388930",
|
|
||||||
"nodeName": "项目启动",
|
|
||||||
"nodeType": "event",
|
|
||||||
"planDate": "2026-04-01",
|
|
||||||
"actualDate": null,
|
|
||||||
"description": "项目正式启动,团队入场",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 0,
|
|
||||||
"kbScope": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996843388931",
|
|
||||||
"nodeName": "M1: 需求分析与架构设计完成",
|
|
||||||
"nodeType": "milestone",
|
|
||||||
"planDate": "2026-04-30",
|
|
||||||
"actualDate": null,
|
|
||||||
"description": "完成需求评审及技术架构可行性验证",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 1,
|
|
||||||
"kbScope": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996843388932",
|
|
||||||
"nodeName": "M2: 核心算法验证通过",
|
|
||||||
"nodeType": "milestone",
|
|
||||||
"planDate": "2026-06-15",
|
|
||||||
"actualDate": null,
|
|
||||||
"description": "模型准确率达标,解析引擎V1.0产出",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 2,
|
|
||||||
"kbScope": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996843388933",
|
|
||||||
"nodeName": "M3: Alpha版开发完成",
|
|
||||||
"nodeType": "milestone",
|
|
||||||
"planDate": "2026-07-31",
|
|
||||||
"actualDate": null,
|
|
||||||
"description": "核心功能闭环跑通",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 3,
|
|
||||||
"kbScope": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996843388934",
|
|
||||||
"nodeName": "M4: Beta版内测完成",
|
|
||||||
"nodeType": "milestone",
|
|
||||||
"planDate": "2026-08-31",
|
|
||||||
"actualDate": null,
|
|
||||||
"description": "性能达标,无严重缺陷",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 4,
|
|
||||||
"kbScope": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996906303490",
|
|
||||||
"nodeName": "M5: UAT与试点完成",
|
|
||||||
"nodeType": "milestone",
|
|
||||||
"planDate": "2026-09-15",
|
|
||||||
"actualDate": null,
|
|
||||||
"description": "人力资源部验收确认",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 5,
|
|
||||||
"kbScope": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2037831996906303491",
|
|
||||||
"nodeName": "M6: 系统正式上线与交付",
|
|
||||||
"nodeType": "milestone",
|
|
||||||
"planDate": "2026-09-30",
|
|
||||||
"actualDate": null,
|
|
||||||
"description": "全员培训完成,项目结项",
|
|
||||||
"status": "pending",
|
|
||||||
"sortOrder": 6,
|
|
||||||
"kbScope": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"message": "查询成功"
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,413 +0,0 @@
|
|||||||
{
|
|
||||||
"project": {
|
|
||||||
"project_name": "AIHR 智能简历筛选系统",
|
|
||||||
"project_type": "研发项目",
|
|
||||||
"description": "利用大语言模型(LLM)和自然语言处理(NLP)技术,实现简历的自动化解析、人岗匹配度评分及候选人排序,提升招聘效率。",
|
|
||||||
"objectives": "1. 效率提升:单份简历平均处理时间降低至10秒以内。2. 精准匹配:人岗匹配度评分准确率达到90%以上。3. 系统集成:无缝对接主流招聘网站及企业内部ATS。4. 合规安全:确保候选人数据脱敏处理,符合《个人信息保护法》要求。",
|
|
||||||
"plan_start_date": "2026-04-01",
|
|
||||||
"plan_end_date": "2026-09-30",
|
|
||||||
"budget": 2850000,
|
|
||||||
"currency": "CNY",
|
|
||||||
"priority": "high",
|
|
||||||
"tags": ["AI", "NLP", "LLM", "人力资源", "招聘系统"]
|
|
||||||
},
|
|
||||||
"milestones": [
|
|
||||||
{
|
|
||||||
"milestone_name": "M1 需求分析与架构设计",
|
|
||||||
"description": "需求评审通过,技术架构可行性验证完成。",
|
|
||||||
"plan_date": "2026-04-30",
|
|
||||||
"deliverables": "《需求规格说明书》、《系统架构设计文档》、《数据库设计文档》",
|
|
||||||
"owner_role": "产品负责人"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"milestone_name": "M2 核心算法模型训练与验证",
|
|
||||||
"description": "模型在测试集上的准确率>85%,解析字段覆盖率>95%。",
|
|
||||||
"plan_date": "2026-06-15",
|
|
||||||
"deliverables": "简历解析引擎 V1.0、人岗匹配算法模型、测试报告",
|
|
||||||
"owner_role": "AI算法工程师"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"milestone_name": "M3 系统功能开发完成 (Alpha 版)",
|
|
||||||
"description": "核心功能(上传、解析、评分、搜索)闭环跑通。",
|
|
||||||
"plan_date": "2026-07-31",
|
|
||||||
"deliverables": "可运行的后端服务、前端管理界面、API 接口文档",
|
|
||||||
"owner_role": "项目经理"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"milestone_name": "M4 系统集成与内部测试 (Beta 版)",
|
|
||||||
"description": "支持并发用户数>50,响应时间<2秒,无严重 Bug。",
|
|
||||||
"plan_date": "2026-08-31",
|
|
||||||
"deliverables": "集成测试报告、性能测试报告、用户操作手册",
|
|
||||||
"owner_role": "测试工程师"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"milestone_name": "M5 用户验收测试 (UAT) 与试点",
|
|
||||||
"description": "试点部门(人力资源部)确认功能满足业务需求。",
|
|
||||||
"plan_date": "2026-09-15",
|
|
||||||
"deliverables": "UAT 验收报告、试点运行反馈报告",
|
|
||||||
"owner_role": "产品负责人"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"milestone_name": "M6 正式上线与交付",
|
|
||||||
"description": "系统正式部署至生产环境,完成全员培训。",
|
|
||||||
"plan_date": "2026-09-30",
|
|
||||||
"deliverables": "最终源代码、部署文档、运维手册、项目总结报告",
|
|
||||||
"owner_role": "项目经理"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"task_id": "T001",
|
|
||||||
"task_name": "需求分析与产品设计",
|
|
||||||
"parent_task_id": null,
|
|
||||||
"description": "完成需求梳理、原型设计及用户故事编写",
|
|
||||||
"plan_start_date": "2026-04-01",
|
|
||||||
"plan_end_date": "2026-04-15",
|
|
||||||
"estimated_hours": 80,
|
|
||||||
"priority": "high",
|
|
||||||
"assignee_role": "产品负责人",
|
|
||||||
"dependencies": [],
|
|
||||||
"deliverables": "《需求规格说明书》、原型图"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"task_id": "T002",
|
|
||||||
"task_name": "技术架构与数据库设计",
|
|
||||||
"parent_task_id": null,
|
|
||||||
"description": "系统整体技术选型、架构设计及数据库设计",
|
|
||||||
"plan_start_date": "2026-04-16",
|
|
||||||
"plan_end_date": "2026-04-30",
|
|
||||||
"estimated_hours": 80,
|
|
||||||
"priority": "high",
|
|
||||||
"assignee_role": "技术架构师",
|
|
||||||
"dependencies": ["T001"],
|
|
||||||
"deliverables": "《系统架构设计文档》、《数据库设计文档》"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"task_id": "T003",
|
|
||||||
"task_name": "数据清洗与标注",
|
|
||||||
"parent_task_id": null,
|
|
||||||
"description": "历史简历数据的清洗、标注及训练数据集构建",
|
|
||||||
"plan_start_date": "2026-04-10",
|
|
||||||
"plan_end_date": "2026-05-15",
|
|
||||||
"estimated_hours": 100,
|
|
||||||
"priority": "high",
|
|
||||||
"assignee_role": "数据标注专员",
|
|
||||||
"dependencies": ["T001"],
|
|
||||||
"deliverables": "训练数据集"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"task_id": "T004",
|
|
||||||
"task_name": "算法模型训练与调优",
|
|
||||||
"parent_task_id": null,
|
|
||||||
"description": "NLP模型选型、简历解析算法开发及匹配模型训练",
|
|
||||||
"plan_start_date": "2026-05-01",
|
|
||||||
"plan_end_date": "2026-06-15",
|
|
||||||
"estimated_hours": 260,
|
|
||||||
"priority": "high",
|
|
||||||
"assignee_role": "AI算法工程师",
|
|
||||||
"dependencies": ["T003"],
|
|
||||||
"deliverables": "简历解析引擎、算法模型"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"task_id": "T005",
|
|
||||||
"task_name": "后端功能开发",
|
|
||||||
"parent_task_id": null,
|
|
||||||
"description": "后端API开发、数据库对接及高并发处理优化",
|
|
||||||
"plan_start_date": "2026-05-01",
|
|
||||||
"plan_end_date": "2026-07-15",
|
|
||||||
"estimated_hours": 320,
|
|
||||||
"priority": "high",
|
|
||||||
"assignee_role": "后端开发工程师",
|
|
||||||
"dependencies": ["T002"],
|
|
||||||
"deliverables": "后端服务、API接口文档"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"task_id": "T006",
|
|
||||||
"task_name": "前端页面开发",
|
|
||||||
"parent_task_id": null,
|
|
||||||
"description": "管理后台前端页面开发、数据可视化展示及交互实现",
|
|
||||||
"plan_start_date": "2026-05-15",
|
|
||||||
"plan_end_date": "2026-07-15",
|
|
||||||
"estimated_hours": 320,
|
|
||||||
"priority": "high",
|
|
||||||
"assignee_role": "前端开发工程师",
|
|
||||||
"dependencies": ["T002"],
|
|
||||||
"deliverables": "前端管理界面"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"task_id": "T007",
|
|
||||||
"task_name": "系统联调与Alpha版发布",
|
|
||||||
"parent_task_id": null,
|
|
||||||
"description": "前后端联调、算法模型集成及核心功能闭环测试",
|
|
||||||
"plan_start_date": "2026-07-16",
|
|
||||||
"plan_end_date": "2026-07-31",
|
|
||||||
"estimated_hours": 120,
|
|
||||||
"priority": "high",
|
|
||||||
"assignee_role": "后端开发工程师",
|
|
||||||
"dependencies": ["T004", "T005", "T006"],
|
|
||||||
"deliverables": "Alpha版系统"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"task_id": "T008",
|
|
||||||
"task_name": "系统集成与性能测试",
|
|
||||||
"parent_task_id": null,
|
|
||||||
"description": "功能测试、性能测试及自动化测试脚本编写",
|
|
||||||
"plan_start_date": "2026-08-01",
|
|
||||||
"plan_end_date": "2026-08-31",
|
|
||||||
"estimated_hours": 160,
|
|
||||||
"priority": "high",
|
|
||||||
"assignee_role": "测试工程师",
|
|
||||||
"dependencies": ["T007"],
|
|
||||||
"deliverables": "集成测试报告、性能测试报告"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"task_id": "T009",
|
|
||||||
"task_name": "UAT验收与试点运行",
|
|
||||||
"parent_task_id": null,
|
|
||||||
"description": "组织人力资源部进行用户验收测试并收集反馈",
|
|
||||||
"plan_start_date": "2026-09-01",
|
|
||||||
"plan_end_date": "2026-09-15",
|
|
||||||
"estimated_hours": 80,
|
|
||||||
"priority": "medium",
|
|
||||||
"assignee_role": "产品负责人",
|
|
||||||
"dependencies": ["T008"],
|
|
||||||
"deliverables": "UAT验收报告"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"task_id": "T010",
|
|
||||||
"task_name": "正式上线与培训部署",
|
|
||||||
"parent_task_id": null,
|
|
||||||
"description": "系统生产环境部署、全员培训及总结报告编写",
|
|
||||||
"plan_start_date": "2026-09-16",
|
|
||||||
"plan_end_date": "2026-09-30",
|
|
||||||
"estimated_hours": 80,
|
|
||||||
"priority": "high",
|
|
||||||
"assignee_role": "项目经理",
|
|
||||||
"dependencies": ["T009"],
|
|
||||||
"deliverables": "部署文档、运维手册、最终源代码"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"name": "张伟",
|
|
||||||
"role_code": "manager",
|
|
||||||
"responsibility": "负责整体项目规划、进度控制、风险管理及跨部门协调。",
|
|
||||||
"department": "未来科技研发部",
|
|
||||||
"weekly_hours": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "李娜",
|
|
||||||
"role_code": "leader",
|
|
||||||
"responsibility": "负责需求梳理、原型设计、用户故事编写及验收测试。",
|
|
||||||
"department": "未来科技研发部",
|
|
||||||
"weekly_hours": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "王强",
|
|
||||||
"role_code": "leader",
|
|
||||||
"responsibility": "负责系统整体技术选型、架构设计、核心技术难点攻关。",
|
|
||||||
"department": "未来科技研发部",
|
|
||||||
"weekly_hours": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "陈思",
|
|
||||||
"role_code": "member",
|
|
||||||
"responsibility": "负责 NLP 模型选型、简历解析算法开发、匹配模型训练与调优。",
|
|
||||||
"department": "未来科技研发部",
|
|
||||||
"weekly_hours": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "赵杰",
|
|
||||||
"role_code": "member",
|
|
||||||
"responsibility": "负责后端 API 开发、数据库设计、第三方系统接口对接。",
|
|
||||||
"department": "未来科技研发部",
|
|
||||||
"weekly_hours": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "刘洋",
|
|
||||||
"role_code": "member",
|
|
||||||
"responsibility": "协助后端开发,负责高并发处理及缓存策略优化。",
|
|
||||||
"department": "未来科技研发部",
|
|
||||||
"weekly_hours": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "孙丽",
|
|
||||||
"role_code": "member",
|
|
||||||
"responsibility": "负责管理后台前端页面开发、数据可视化展示及交互实现。",
|
|
||||||
"department": "未来科技研发部",
|
|
||||||
"weekly_hours": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "周敏",
|
|
||||||
"role_code": "member",
|
|
||||||
"responsibility": "负责测试用例编写、功能测试、性能测试及自动化测试脚本。",
|
|
||||||
"department": "未来科技研发部",
|
|
||||||
"weekly_hours": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "吴凯",
|
|
||||||
"role_code": "member",
|
|
||||||
"responsibility": "负责系统界面设计、用户体验优化及交互规范制定。",
|
|
||||||
"department": "未来科技研发部",
|
|
||||||
"weekly_hours": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "郑浩",
|
|
||||||
"role_code": "member",
|
|
||||||
"responsibility": "负责历史简历数据的清洗、标注及训练数据集构建。",
|
|
||||||
"department": "未来科技研发部",
|
|
||||||
"weekly_hours": 20
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resources": [
|
|
||||||
{
|
|
||||||
"resource_name": "高性能云服务器",
|
|
||||||
"resource_type": "equipment",
|
|
||||||
"quantity": 5,
|
|
||||||
"unit": "台",
|
|
||||||
"unit_price": 0,
|
|
||||||
"supplier": "云服务商"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resource_name": "云端GPU算力(A100/A800)",
|
|
||||||
"resource_type": "equipment",
|
|
||||||
"quantity": 2000,
|
|
||||||
"unit": "小时",
|
|
||||||
"unit_price": 0,
|
|
||||||
"supplier": "GPU云服务提供商"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resource_name": "对象存储",
|
|
||||||
"resource_type": "equipment",
|
|
||||||
"quantity": 5,
|
|
||||||
"unit": "TB",
|
|
||||||
"unit_price": 0,
|
|
||||||
"supplier": "云服务商"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resource_name": "历史简历数据",
|
|
||||||
"resource_type": "material",
|
|
||||||
"quantity": 100000,
|
|
||||||
"unit": "份",
|
|
||||||
"unit_price": 0,
|
|
||||||
"supplier": "企业内部"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resource_name": "简历-JD匹配对标注数据集",
|
|
||||||
"resource_type": "material",
|
|
||||||
"quantity": 5000,
|
|
||||||
"unit": "对",
|
|
||||||
"unit_price": 0,
|
|
||||||
"supplier": "内部构建"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resource_name": "OCR识别 API",
|
|
||||||
"resource_type": "software",
|
|
||||||
"quantity": 1,
|
|
||||||
"unit": "套",
|
|
||||||
"unit_price": 0,
|
|
||||||
"supplier": "第三方服务商"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resource_name": "大模型调用 API",
|
|
||||||
"resource_type": "software",
|
|
||||||
"quantity": 1,
|
|
||||||
"unit": "套",
|
|
||||||
"unit_price": 0,
|
|
||||||
"supplier": "第三方大模型服务商"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resource_name": "法律合规咨询服务",
|
|
||||||
"resource_type": "human",
|
|
||||||
"quantity": 1,
|
|
||||||
"unit": "项",
|
|
||||||
"unit_price": 0,
|
|
||||||
"supplier": "外部法律顾问"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"risks": [
|
|
||||||
{
|
|
||||||
"risk_name": "数据合规与隐私泄露风险",
|
|
||||||
"category": "other",
|
|
||||||
"description": "处理大量包含个人隐私的简历数据,可能违反《个人信息保护法》或引发数据泄露。",
|
|
||||||
"probability": 5,
|
|
||||||
"impact": 5,
|
|
||||||
"mitigation_plan": "聘请专业法律顾问审核数据采集、存储及使用流程;对所有简历数据进行严格的脱敏处理和加密存储。"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"risk_name": "模型匹配准确率不达标",
|
|
||||||
"category": "technical",
|
|
||||||
"description": "人岗匹配度评分准确率难以达到预期的90%以上目标。",
|
|
||||||
"probability": 40,
|
|
||||||
"impact": 4,
|
|
||||||
"mitigation_plan": "构建高质量的专家标注数据集,引入人力资源专家进行人工抽检复核,持续微调和优化算法模型。"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"risk_name": "系统并发性能瓶颈",
|
|
||||||
"category": "technical",
|
|
||||||
"description": "在处理大量并发解析请求时,系统响应时间可能超过2秒或出现崩溃。",
|
|
||||||
"probability": 35,
|
|
||||||
"impact": 4,
|
|
||||||
"mitigation_plan": "优化系统架构,引入Redis缓存和负载均衡机制,并在Beta阶段进行充分的压力测试和性能调优。"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"risk_name": "第三方API依赖及限流风险",
|
|
||||||
"category": "schedule",
|
|
||||||
"description": "过度依赖外部大模型API和OCR服务,可能因网络延迟或服务商限流影响项目进度和系统稳定性。",
|
|
||||||
"probability": 25,
|
|
||||||
"impact": 3,
|
|
||||||
"mitigation_plan": "设计灵活的接口适配层以支持多家API服务商切换,并在必要时考虑本地部署轻量级备用模型。"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeline_nodes": [
|
|
||||||
{
|
|
||||||
"node_name": "项目正式启动",
|
|
||||||
"node_type": "event",
|
|
||||||
"plan_date": "2026-04-01",
|
|
||||||
"description": "项目团队组建完毕,进入需求分析阶段",
|
|
||||||
"kb_scope": ["file"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node_name": "需求与架构设计完成",
|
|
||||||
"node_type": "milestone",
|
|
||||||
"plan_date": "2026-04-30",
|
|
||||||
"description": "完成M1里程碑,架构设计通过评审",
|
|
||||||
"kb_scope": ["report", "file"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node_name": "核心算法就绪",
|
|
||||||
"node_type": "milestone",
|
|
||||||
"plan_date": "2026-06-15",
|
|
||||||
"description": "完成M2里程碑,模型准确率达标",
|
|
||||||
"kb_scope": ["report", "file", "risk"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node_name": "Alpha版发布",
|
|
||||||
"node_type": "milestone",
|
|
||||||
"plan_date": "2026-07-31",
|
|
||||||
"description": "完成M3里程碑,系统核心功能闭环跑通",
|
|
||||||
"kb_scope": ["report", "ticket"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node_name": "Beta版发布",
|
|
||||||
"node_type": "milestone",
|
|
||||||
"plan_date": "2026-08-31",
|
|
||||||
"description": "完成M4里程碑,内部集成测试与性能测试完成",
|
|
||||||
"kb_scope": ["report", "ticket", "risk"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node_name": "UAT验收通过",
|
|
||||||
"node_type": "milestone",
|
|
||||||
"plan_date": "2026-09-15",
|
|
||||||
"description": "完成M5里程碑,试点部门确认功能满足需求",
|
|
||||||
"kb_scope": ["report", "ticket"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node_name": "项目正式上线交付",
|
|
||||||
"node_type": "milestone",
|
|
||||||
"plan_date": "2026-09-30",
|
|
||||||
"description": "完成M6里程碑,生产环境部署及全员培训完成",
|
|
||||||
"kb_scope": ["report", "file"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, nextTick, onUnmounted, watch } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
import {
|
import {
|
||||||
getChatSessions,
|
getChatSessions,
|
||||||
getSessionMessages,
|
getSessionMessages,
|
||||||
deleteChatSession,
|
deleteChatSession,
|
||||||
createChatSession,
|
|
||||||
buildSSEUrl,
|
buildSSEUrl,
|
||||||
type ChatSessionVO,
|
type ChatSessionVO,
|
||||||
type ChatMessageVO,
|
type ChatMessageVO,
|
||||||
type ReferencedDocVO,
|
|
||||||
type SSEStartData,
|
type SSEStartData,
|
||||||
type SSEChunkData,
|
type SSEChunkData,
|
||||||
type SSEReferencesData,
|
|
||||||
type SSECompleteData,
|
type SSECompleteData,
|
||||||
type SSEErrorData
|
type SSEErrorData
|
||||||
} from "@/api/ai-chat";
|
} from "@/api/ai-chat";
|
||||||
import { getProjectList, type ProjectItem } from "@/api/project";
|
import { getProjectList, type ProjectItem } from "@/api/project";
|
||||||
import { createSSEConnection } from "@/utils/sse/chatSSE";
|
import { createSSEConnection } from "@/utils/sse/chatSSE";
|
||||||
|
import ChatGPT from "@/views/chatai/components/ChatGPT.vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import SendIcon from "~icons/ri/send-plane-fill";
|
|
||||||
import AddIcon from "~icons/ri/add-line";
|
import AddIcon from "~icons/ri/add-line";
|
||||||
import DeleteIcon from "~icons/ep/delete";
|
import DeleteIcon from "~icons/ep/delete";
|
||||||
import DocumentIcon from "~icons/ri/file-text-line";
|
|
||||||
import RefreshIcon from "~icons/ri/refresh-line";
|
import RefreshIcon from "~icons/ri/refresh-line";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -36,22 +32,19 @@ const loading = ref(false);
|
|||||||
const sessions = ref<ChatSessionVO[]>([]);
|
const sessions = ref<ChatSessionVO[]>([]);
|
||||||
const currentSession = ref<ChatSessionVO | null>(null);
|
const currentSession = ref<ChatSessionVO | null>(null);
|
||||||
const messages = ref<ChatMessageVO[]>([]);
|
const messages = ref<ChatMessageVO[]>([]);
|
||||||
const inputMessage = ref("");
|
|
||||||
const sending = ref(false);
|
const sending = ref(false);
|
||||||
const projects = ref<ProjectItem[]>([]);
|
const projects = ref<ProjectItem[]>([]);
|
||||||
const projectLoading = ref(false);
|
const projectLoading = ref(false);
|
||||||
const showProjectSelect = ref(false);
|
const currentProjectId = ref<string>("");
|
||||||
|
const selectedProjectId = ref<string>("");
|
||||||
|
const didInitProjectAndSessions = ref(false);
|
||||||
|
const activeSessionKey = ref<string>("");
|
||||||
|
const chatRenderKey = ref(0);
|
||||||
|
const deepChatHistory = ref<Array<{ text: string; role: "user" | "ai" }>>([]);
|
||||||
|
|
||||||
// SSE连接关闭函数
|
// SSE连接关闭函数
|
||||||
let abortSSE: (() => void) | null = null;
|
let abortSSE: (() => void) | null = null;
|
||||||
|
|
||||||
// 消息容器引用
|
|
||||||
const messagesContainer = ref<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
// 会话中的临时消息(用于流式显示)
|
|
||||||
const streamingMessage = ref<string>("");
|
|
||||||
const streamingReferences = ref<ReferencedDocVO[]>([]);
|
|
||||||
|
|
||||||
// 加载项目列表
|
// 加载项目列表
|
||||||
async function loadProjects() {
|
async function loadProjects() {
|
||||||
projectLoading.value = true;
|
projectLoading.value = true;
|
||||||
@@ -69,11 +62,18 @@ async function loadProjects() {
|
|||||||
|
|
||||||
// 加载会话列表
|
// 加载会话列表
|
||||||
async function loadSessions(projectId?: string) {
|
async function loadSessions(projectId?: string) {
|
||||||
|
const pid =
|
||||||
|
projectId ||
|
||||||
|
currentProjectId.value ||
|
||||||
|
currentSession.value?.projectId ||
|
||||||
|
"";
|
||||||
|
if (!pid) {
|
||||||
|
sessions.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await getChatSessions(
|
const res = await getChatSessions(pid);
|
||||||
projectId || currentSession.value?.projectId || ""
|
|
||||||
);
|
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
sessions.value = res.data || [];
|
sessions.value = res.data || [];
|
||||||
}
|
}
|
||||||
@@ -84,19 +84,108 @@ async function loadSessions(projectId?: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setCurrentProject(project: ProjectItem) {
|
||||||
|
const pid = String(project.id || "");
|
||||||
|
if (!pid) return;
|
||||||
|
|
||||||
|
currentProjectId.value = pid;
|
||||||
|
if (!currentSession.value) {
|
||||||
|
currentSession.value = {
|
||||||
|
sessionId: "",
|
||||||
|
sessionTitle: "新对话",
|
||||||
|
projectId: pid,
|
||||||
|
projectName: project.projectName || "",
|
||||||
|
lastMessageTime: new Date().toISOString(),
|
||||||
|
messageCount: 0,
|
||||||
|
createTime: new Date().toISOString()
|
||||||
|
};
|
||||||
|
} else if (!currentSession.value.sessionId) {
|
||||||
|
currentSession.value = {
|
||||||
|
...currentSession.value,
|
||||||
|
projectId: pid,
|
||||||
|
projectName: project.projectName || ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setDraftByProjectId(projectId: string) {
|
||||||
|
const pid = String(projectId || "");
|
||||||
|
if (!pid) return;
|
||||||
|
|
||||||
|
const project = projects.value.find(p => String(p.id || "") === pid);
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
currentProjectId.value = pid;
|
||||||
|
selectedProjectId.value = pid;
|
||||||
|
|
||||||
|
currentSession.value = {
|
||||||
|
sessionId: "",
|
||||||
|
sessionTitle: "新对话",
|
||||||
|
projectId: pid,
|
||||||
|
projectName: project.projectName || "",
|
||||||
|
lastMessageTime: new Date().toISOString(),
|
||||||
|
messageCount: 0,
|
||||||
|
createTime: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
messages.value = [];
|
||||||
|
deepChatHistory.value = [];
|
||||||
|
activeSessionKey.value = `draft-${pid}-${Date.now()}`;
|
||||||
|
chatRenderKey.value += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyProjectSelection(projectId: string) {
|
||||||
|
await setDraftByProjectId(projectId);
|
||||||
|
if (!currentProjectId.value) return;
|
||||||
|
|
||||||
|
const pid = currentProjectId.value;
|
||||||
|
await loadSessions(pid);
|
||||||
|
const firstSession = sessions.value[0];
|
||||||
|
if (firstSession?.sessionId) {
|
||||||
|
await selectSession(firstSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startNewDraftSession() {
|
||||||
|
const pid = String(
|
||||||
|
currentProjectId.value ||
|
||||||
|
selectedProjectId.value ||
|
||||||
|
projects.value[0]?.id ||
|
||||||
|
""
|
||||||
|
);
|
||||||
|
if (!pid) {
|
||||||
|
ElMessage.warning("请先选择项目");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await setDraftByProjectId(pid);
|
||||||
|
await loadSessions(pid);
|
||||||
|
}
|
||||||
|
|
||||||
// 选择会话
|
// 选择会话
|
||||||
async function selectSession(session: ChatSessionVO) {
|
async function selectSession(session: ChatSessionVO) {
|
||||||
currentSession.value = session;
|
currentSession.value = session;
|
||||||
|
currentProjectId.value = String(session.projectId || "");
|
||||||
|
selectedProjectId.value = String(session.projectId || "");
|
||||||
|
activeSessionKey.value = String(session.sessionId || "");
|
||||||
|
deepChatHistory.value = [];
|
||||||
|
chatRenderKey.value += 1;
|
||||||
await loadMessages(session.sessionId);
|
await loadMessages(session.sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载会话消息
|
// 加载会话消息
|
||||||
async function loadMessages(sessionId: string) {
|
async function loadMessages(sessionId: string) {
|
||||||
|
if (!sessionId) return;
|
||||||
try {
|
try {
|
||||||
const res = await getSessionMessages(sessionId);
|
const res = await getSessionMessages(sessionId);
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
messages.value = res.data || [];
|
messages.value = res.data || [];
|
||||||
scrollToBottom();
|
deepChatHistory.value = (messages.value || [])
|
||||||
|
.filter(m => m.role === "user" || m.role === "assistant")
|
||||||
|
.map(m => ({
|
||||||
|
text: m.content,
|
||||||
|
role: m.role === "user" ? "user" : "ai"
|
||||||
|
}));
|
||||||
|
chatRenderKey.value += 1;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("加载消息失败:", error);
|
console.error("加载消息失败:", error);
|
||||||
@@ -105,145 +194,77 @@ async function loadMessages(sessionId: string) {
|
|||||||
|
|
||||||
// 创建新会话
|
// 创建新会话
|
||||||
async function createNewSession() {
|
async function createNewSession() {
|
||||||
if (!currentSession.value?.projectId && projects.value.length === 0) {
|
if (!projects.value.length) await loadProjects();
|
||||||
await loadProjects();
|
await startNewDraftSession();
|
||||||
}
|
|
||||||
showProjectSelect.value = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择项目后创建会话
|
async function requestAssistantReply(messageText: string): Promise<string> {
|
||||||
async function handleProjectSelect(project: ProjectItem) {
|
if (!messageText.trim()) return "";
|
||||||
showProjectSelect.value = false;
|
|
||||||
currentSession.value = {
|
|
||||||
sessionId: "",
|
|
||||||
sessionTitle: "新对话",
|
|
||||||
projectId: String(project.id),
|
|
||||||
projectName: project.projectName || "",
|
|
||||||
lastMessageTime: new Date().toISOString(),
|
|
||||||
messageCount: 0,
|
|
||||||
createTime: new Date().toISOString()
|
|
||||||
};
|
|
||||||
messages.value = [];
|
|
||||||
streamingMessage.value = "";
|
|
||||||
streamingReferences.value = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送消息
|
|
||||||
async function sendMessage() {
|
|
||||||
if (!inputMessage.value.trim() || sending.value) return;
|
|
||||||
|
|
||||||
const message = inputMessage.value.trim();
|
|
||||||
inputMessage.value = "";
|
|
||||||
|
|
||||||
// 如果没有会话,需要先创建
|
|
||||||
if (!currentSession.value?.projectId) {
|
if (!currentSession.value?.projectId) {
|
||||||
ElMessage.warning("请先选择项目");
|
ElMessage.warning("请先选择项目");
|
||||||
return;
|
return "请先选择项目";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加用户消息
|
|
||||||
const userMessage: ChatMessageVO = {
|
|
||||||
id: Date.now(),
|
|
||||||
role: "user",
|
|
||||||
content: message,
|
|
||||||
messageIndex: messages.value.length + 1,
|
|
||||||
createTime: new Date().toISOString()
|
|
||||||
};
|
|
||||||
messages.value.push(userMessage);
|
|
||||||
|
|
||||||
sending.value = true;
|
sending.value = true;
|
||||||
streamingMessage.value = "";
|
|
||||||
streamingReferences.value = [];
|
|
||||||
|
|
||||||
// 建立SSE连接
|
|
||||||
const sseUrl = buildSSEUrl({
|
const sseUrl = buildSSEUrl({
|
||||||
sessionId: currentSession.value.sessionId || undefined,
|
sessionId: currentSession.value.sessionId || undefined,
|
||||||
projectId: currentSession.value.projectId,
|
projectId: currentSession.value.projectId,
|
||||||
message: message,
|
message: messageText,
|
||||||
useRag: true
|
useRag: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// 关闭之前的连接
|
|
||||||
if (abortSSE) {
|
if (abortSSE) {
|
||||||
abortSSE();
|
abortSSE();
|
||||||
abortSSE = null;
|
abortSSE = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用带鉴权Header的SSE连接
|
let fullText = "";
|
||||||
abortSSE = await createSSEConnection({
|
const replyText = await new Promise<string>(async resolve => {
|
||||||
url: sseUrl,
|
abortSSE = await createSSEConnection({
|
||||||
onEvent: (eventName: string, data: any) => {
|
url: sseUrl,
|
||||||
switch (eventName) {
|
onEvent: (eventName: string, data: any) => {
|
||||||
case "start": {
|
switch (eventName) {
|
||||||
const startData = data as SSEStartData;
|
case "start": {
|
||||||
if (startData.isNewSession || !currentSession.value?.sessionId) {
|
const startData = data as SSEStartData;
|
||||||
currentSession.value = {
|
if (startData.isNewSession || !currentSession.value?.sessionId) {
|
||||||
...currentSession.value!,
|
currentSession.value = {
|
||||||
sessionId: startData.sessionId,
|
...currentSession.value!,
|
||||||
sessionTitle: message.slice(0, 20)
|
sessionId: startData.sessionId,
|
||||||
};
|
sessionTitle: messageText.slice(0, 20)
|
||||||
loadSessions(currentSession.value.projectId);
|
};
|
||||||
|
loadSessions(currentSession.value.projectId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case "chunk": {
|
||||||
}
|
const chunkData = data as SSEChunkData;
|
||||||
case "chunk": {
|
fullText += chunkData.content;
|
||||||
const chunkData = data as SSEChunkData;
|
break;
|
||||||
streamingMessage.value += chunkData.content;
|
}
|
||||||
scrollToBottom();
|
case "complete": {
|
||||||
break;
|
resolve(fullText);
|
||||||
}
|
break;
|
||||||
case "references": {
|
}
|
||||||
const refData = data as SSEReferencesData;
|
case "error": {
|
||||||
streamingReferences.value = refData.docs || [];
|
const errorData = data as SSEErrorData;
|
||||||
break;
|
resolve(errorData.message || "对话发生错误");
|
||||||
}
|
break;
|
||||||
case "complete": {
|
|
||||||
const completeData = data as SSECompleteData;
|
|
||||||
|
|
||||||
// 添加AI回复消息
|
|
||||||
const assistantMessage: ChatMessageVO = {
|
|
||||||
id: completeData.messageId,
|
|
||||||
role: "assistant",
|
|
||||||
content: streamingMessage.value,
|
|
||||||
referencedDocs: streamingReferences.value,
|
|
||||||
tokensUsed: completeData.tokensUsed,
|
|
||||||
messageIndex: messages.value.length + 1,
|
|
||||||
createTime: new Date().toISOString()
|
|
||||||
};
|
|
||||||
messages.value.push(assistantMessage);
|
|
||||||
|
|
||||||
// 重置状态
|
|
||||||
streamingMessage.value = "";
|
|
||||||
streamingReferences.value = [];
|
|
||||||
sending.value = false;
|
|
||||||
abortSSE = null;
|
|
||||||
|
|
||||||
loadSessions(currentSession.value?.projectId);
|
|
||||||
scrollToBottom();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "error": {
|
|
||||||
const errorData = data as SSEErrorData;
|
|
||||||
ElMessage.error(errorData.message || "对话发生错误");
|
|
||||||
sending.value = false;
|
|
||||||
streamingMessage.value = "";
|
|
||||||
streamingReferences.value = [];
|
|
||||||
if (abortSSE) {
|
|
||||||
abortSSE();
|
|
||||||
abortSSE = null;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
resolve("连接服务器失败: " + error.message);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
onError: (error: Error) => {
|
|
||||||
ElMessage.error("连接服务器失败: " + error.message);
|
|
||||||
sending.value = false;
|
|
||||||
streamingMessage.value = "";
|
|
||||||
streamingReferences.value = [];
|
|
||||||
abortSSE = null;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sending.value = false;
|
||||||
|
if (abortSSE) {
|
||||||
|
abortSSE();
|
||||||
|
abortSSE = null;
|
||||||
|
}
|
||||||
|
loadSessions(currentSession.value?.projectId);
|
||||||
|
return replyText;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除会话
|
// 删除会话
|
||||||
@@ -261,8 +282,16 @@ async function handleDeleteSession(session: ChatSessionVO) {
|
|||||||
loadSessions(currentSession.value?.projectId);
|
loadSessions(currentSession.value?.projectId);
|
||||||
|
|
||||||
if (currentSession.value?.sessionId === session.sessionId) {
|
if (currentSession.value?.sessionId === session.sessionId) {
|
||||||
currentSession.value = null;
|
|
||||||
messages.value = [];
|
messages.value = [];
|
||||||
|
deepChatHistory.value = [];
|
||||||
|
const defaultProject = projects.value[0];
|
||||||
|
if (defaultProject?.id) {
|
||||||
|
await applyProjectSelection(String(defaultProject.id));
|
||||||
|
} else {
|
||||||
|
currentSession.value = null;
|
||||||
|
activeSessionKey.value = "";
|
||||||
|
chatRenderKey.value += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -272,27 +301,11 @@ async function handleDeleteSession(session: ChatSessionVO) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 滚动到底部
|
|
||||||
function scrollToBottom() {
|
|
||||||
nextTick(() => {
|
|
||||||
if (messagesContainer.value) {
|
|
||||||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化时间
|
// 格式化时间
|
||||||
function formatTime(time: string) {
|
function formatTime(time: string) {
|
||||||
return dayjs(time).format("MM-DD HH:mm");
|
return dayjs(time).format("MM-DD HH:mm");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化文件大小
|
|
||||||
function formatFileSize(bytes: number) {
|
|
||||||
if (bytes < 1024) return bytes + " B";
|
|
||||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
|
||||||
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 组件卸载时关闭SSE连接
|
// 组件卸载时关闭SSE连接
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (abortSSE) {
|
if (abortSSE) {
|
||||||
@@ -301,8 +314,19 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 初始化加载
|
async function initPage() {
|
||||||
loadProjects();
|
await loadProjects();
|
||||||
|
if (!didInitProjectAndSessions.value && projects.value.length > 0) {
|
||||||
|
selectedProjectId.value = String(projects.value[0]?.id || "");
|
||||||
|
didInitProjectAndSessions.value = true;
|
||||||
|
if (selectedProjectId.value)
|
||||||
|
await applyProjectSelection(selectedProjectId.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initPage();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -320,25 +344,22 @@ loadProjects();
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 项目选择 -->
|
<div class="project-select">
|
||||||
<div v-if="showProjectSelect" class="project-select-panel">
|
<el-select
|
||||||
<div class="panel-header">
|
v-model="selectedProjectId"
|
||||||
<span>选择项目</span>
|
placeholder="选择项目"
|
||||||
<el-button link @click="showProjectSelect = false">
|
filterable
|
||||||
<component :is="useRenderIcon('ri/close-line')" />
|
:loading="projectLoading"
|
||||||
</el-button>
|
style="width: 100%"
|
||||||
</div>
|
@change="applyProjectSelection"
|
||||||
<el-scrollbar height="300px">
|
>
|
||||||
<div
|
<el-option
|
||||||
v-for="project in projects"
|
v-for="project in projects"
|
||||||
:key="project.id"
|
:key="String(project.id || '')"
|
||||||
class="project-item"
|
:label="project.projectName || ''"
|
||||||
@click="handleProjectSelect(project)"
|
:value="String(project.id || '')"
|
||||||
>
|
/>
|
||||||
<component :is="useRenderIcon('ri/folder-line')" class="mr-2" />
|
</el-select>
|
||||||
{{ project.projectName }}
|
|
||||||
</div>
|
|
||||||
</el-scrollbar>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 会话列表 -->
|
<!-- 会话列表 -->
|
||||||
@@ -394,118 +415,19 @@ loadProjects();
|
|||||||
</div>
|
</div>
|
||||||
<el-button
|
<el-button
|
||||||
:icon="useRenderIcon(RefreshIcon)"
|
:icon="useRenderIcon(RefreshIcon)"
|
||||||
|
:disabled="!currentSession.sessionId"
|
||||||
@click="loadMessages(currentSession.sessionId)"
|
@click="loadMessages(currentSession.sessionId)"
|
||||||
>
|
>
|
||||||
刷新
|
刷新
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 消息列表 -->
|
<div class="chat-body">
|
||||||
<div ref="messagesContainer" class="messages-container">
|
<ChatGPT
|
||||||
<div
|
:key="`${activeSessionKey}-${chatRenderKey}`"
|
||||||
v-for="msg in messages"
|
:history="deepChatHistory"
|
||||||
:key="msg.id"
|
:onRequest="requestAssistantReply"
|
||||||
:class="['message-item', msg.role]"
|
|
||||||
>
|
|
||||||
<div class="message-avatar">
|
|
||||||
<el-avatar v-if="msg.role === 'user'" :size="32">
|
|
||||||
<component :is="useRenderIcon('ri/user-line')" />
|
|
||||||
</el-avatar>
|
|
||||||
<el-avatar
|
|
||||||
v-else
|
|
||||||
:size="32"
|
|
||||||
style="
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<component :is="useRenderIcon('ri/robot-line')" />
|
|
||||||
</el-avatar>
|
|
||||||
</div>
|
|
||||||
<div class="message-content">
|
|
||||||
<div
|
|
||||||
class="message-text"
|
|
||||||
v-html="msg.content.replace(/\n/g, '<br>')"
|
|
||||||
/>
|
|
||||||
<!-- 引用文档 -->
|
|
||||||
<div v-if="msg.referencedDocs?.length" class="referenced-docs">
|
|
||||||
<div class="ref-title">
|
|
||||||
<component :is="useRenderIcon(DocumentIcon)" />
|
|
||||||
参考文档
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="doc in msg.referencedDocs"
|
|
||||||
:key="doc.id"
|
|
||||||
class="ref-doc"
|
|
||||||
>
|
|
||||||
<span class="doc-title">{{ doc.title }}</span>
|
|
||||||
<el-tag size="small" type="info"
|
|
||||||
>相关度: {{ (doc.score * 100).toFixed(0) }}%</el-tag
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="message-time">{{ formatTime(msg.createTime) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 流式输出中的消息 -->
|
|
||||||
<div v-if="streamingMessage" class="message-item assistant streaming">
|
|
||||||
<div class="message-avatar">
|
|
||||||
<el-avatar
|
|
||||||
:size="32"
|
|
||||||
style="
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<component :is="useRenderIcon('ri/robot-line')" />
|
|
||||||
</el-avatar>
|
|
||||||
</div>
|
|
||||||
<div class="message-content">
|
|
||||||
<div
|
|
||||||
class="message-text"
|
|
||||||
v-html="streamingMessage.replace(/\n/g, '<br>')"
|
|
||||||
/>
|
|
||||||
<div v-if="streamingReferences.length" class="referenced-docs">
|
|
||||||
<div class="ref-title">
|
|
||||||
<component :is="useRenderIcon(DocumentIcon)" />
|
|
||||||
参考文档
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="doc in streamingReferences"
|
|
||||||
:key="doc.id"
|
|
||||||
class="ref-doc"
|
|
||||||
>
|
|
||||||
<span class="doc-title">{{ doc.title }}</span>
|
|
||||||
<el-tag size="small" type="info"
|
|
||||||
>相关度: {{ (doc.score * 100).toFixed(0) }}%</el-tag
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 输入区域 -->
|
|
||||||
<div class="input-area">
|
|
||||||
<el-input
|
|
||||||
v-model="inputMessage"
|
|
||||||
type="textarea"
|
|
||||||
:rows="3"
|
|
||||||
placeholder="输入您的问题..."
|
|
||||||
:disabled="sending"
|
|
||||||
@keydown.enter.ctrl="sendMessage"
|
|
||||||
/>
|
/>
|
||||||
<div class="input-actions">
|
|
||||||
<span class="tip">Ctrl + Enter 发送</span>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
:icon="useRenderIcon(SendIcon)"
|
|
||||||
:loading="sending"
|
|
||||||
:disabled="!inputMessage.trim()"
|
|
||||||
@click="sendMessage"
|
|
||||||
>
|
|
||||||
发送
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -559,28 +481,9 @@ loadProjects();
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-select-panel {
|
.project-select {
|
||||||
|
padding: 12px 16px;
|
||||||
border-bottom: 1px solid var(--el-border-color-light);
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
|
|
||||||
.panel-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 12px 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--el-fill-color-light);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.session-list {
|
.session-list {
|
||||||
@@ -669,109 +572,17 @@ loadProjects();
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages-container {
|
.chat-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
|
|
||||||
.message-item {
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&.user {
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
|
|
||||||
.message-content {
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-text {
|
|
||||||
color: #fff;
|
|
||||||
background: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.streaming {
|
|
||||||
.message-text {
|
|
||||||
background: var(--el-fill-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-avatar {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 70%;
|
|
||||||
|
|
||||||
.message-text {
|
|
||||||
padding: 12px 16px;
|
|
||||||
line-height: 1.6;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
background: var(--el-fill-color);
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.referenced-docs {
|
|
||||||
padding: 8px 12px;
|
|
||||||
margin-top: 8px;
|
|
||||||
background: var(--el-fill-color-lighter);
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
.ref-title {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ref-doc {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 4px 0;
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
.doc-title {
|
|
||||||
flex: 1;
|
|
||||||
margin-right: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-time {
|
|
||||||
margin-top: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-area {
|
.chat-body :deep(deep-chat) {
|
||||||
padding: 16px 20px;
|
display: block;
|
||||||
background: var(--el-fill-color-blank);
|
width: 100%;
|
||||||
border-top: 1px solid var(--el-border-color-light);
|
max-width: none;
|
||||||
|
height: 100%;
|
||||||
.input-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
.tip {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,60 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import "deep-chat";
|
import "deep-chat";
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted, watch } from "vue";
|
||||||
|
|
||||||
const chatRef = ref();
|
type HistoryItem = { text: string; role: "user" | "ai" };
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
history?: HistoryItem[];
|
||||||
|
onRequest?: (messageText: string) => Promise<string> | string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
history: () => []
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const chatRef = ref<any>();
|
||||||
|
|
||||||
|
function normalizeUserText(rawMessage: any): string {
|
||||||
|
if (typeof rawMessage === "string") return rawMessage;
|
||||||
|
if (rawMessage?.text) return String(rawMessage.text);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyHistory() {
|
||||||
|
if (!chatRef.value) return;
|
||||||
|
chatRef.value.history = props.history || [];
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
applyHistory();
|
||||||
chatRef.value.demo = {
|
chatRef.value.demo = {
|
||||||
response: message => {
|
response: async (rawMessage: any) => {
|
||||||
console.log(message);
|
const messageText = normalizeUserText(rawMessage);
|
||||||
|
if (props.onRequest) {
|
||||||
|
const replyText = await props.onRequest(messageText);
|
||||||
|
return { text: replyText };
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
text: "仅演示,如需AI服务,请参考 https://deepchat.dev/docs/connect"
|
text: "仅演示,如需AI服务,请参考 https://deepchat.dev/docs/connect"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.history,
|
||||||
|
() => {
|
||||||
|
applyHistory();
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<deep-chat
|
<deep-chat
|
||||||
ref="chatRef"
|
ref="chatRef"
|
||||||
style="border-radius: 10px"
|
style="width: 100%; height: 100%; border-radius: 10px"
|
||||||
:messageStyles="{
|
:messageStyles="{
|
||||||
default: {
|
default: {
|
||||||
shared: {
|
shared: {
|
||||||
@@ -101,13 +136,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
:textInput="{ placeholder: { text: '发送消息' } }"
|
:textInput="{ placeholder: { text: '发送消息' } }"
|
||||||
:history="[
|
:history="props.history"
|
||||||
{ text: '李白是谁?', role: 'user' },
|
|
||||||
{
|
|
||||||
text: '李白(701年2月28日-762年),号青莲居士,又号“谪仙人”,是唐代著名的浪漫主义诗人,被后人誉为“诗仙”。',
|
|
||||||
role: 'ai'
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
:demo="true"
|
:demo="true"
|
||||||
:connect="{ stream: true }"
|
:connect="{ stream: true }"
|
||||||
/>
|
/>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useProject } from "./utils/hook";
|
import { useProject } from "./utils/hook";
|
||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
|
import { hasPerms } from "@/utils/auth";
|
||||||
|
import { message } from "@/utils/message";
|
||||||
|
import {
|
||||||
|
updateProject,
|
||||||
|
updateProjectStatus,
|
||||||
|
updateProjectManager,
|
||||||
|
type Project,
|
||||||
|
type ProjectItem
|
||||||
|
} from "@/api/project";
|
||||||
import CreateProjectWizard from "./components/CreateProjectWizard.vue";
|
import CreateProjectWizard from "./components/CreateProjectWizard.vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
@@ -23,6 +32,34 @@ defineOptions({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const wizardVisible = ref(false);
|
const wizardVisible = ref(false);
|
||||||
|
|
||||||
|
// 权限控制
|
||||||
|
const canEditProject = computed(() => hasPerms("project:center:update"));
|
||||||
|
const canDeleteProject = computed(() => hasPerms("project:center:delete"));
|
||||||
|
const canUpdateProjectStatus = computed(() =>
|
||||||
|
hasPerms("project:center:update")
|
||||||
|
);
|
||||||
|
|
||||||
|
// 项目编辑模态框
|
||||||
|
const projectEditModal = ref(false);
|
||||||
|
const projectEditForm = ref<Project>({});
|
||||||
|
const projectEditLoading = ref(false);
|
||||||
|
|
||||||
|
// 状态更新模态框
|
||||||
|
const statusUpdateModal = ref(false);
|
||||||
|
const statusUpdateForm = ref({
|
||||||
|
id: "",
|
||||||
|
status: ""
|
||||||
|
});
|
||||||
|
const statusUpdateLoading = ref(false);
|
||||||
|
|
||||||
|
// 项目经理更新模态框
|
||||||
|
const managerUpdateModal = ref(false);
|
||||||
|
const managerUpdateForm = ref({
|
||||||
|
id: "",
|
||||||
|
managerName: ""
|
||||||
|
});
|
||||||
|
const managerUpdateLoading = ref(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
form,
|
form,
|
||||||
formRef,
|
formRef,
|
||||||
@@ -59,8 +96,106 @@ function handleView(row: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 编辑项目
|
// 编辑项目
|
||||||
function handleEdit(row: any) {
|
function handleEdit(row: ProjectItem) {
|
||||||
console.log("编辑项目", row);
|
projectEditForm.value = {
|
||||||
|
id: row.id,
|
||||||
|
projectCode: row.projectCode,
|
||||||
|
projectName: row.projectName,
|
||||||
|
projectType: row.projectType,
|
||||||
|
planStartDate: row.planStartDate,
|
||||||
|
planEndDate: row.planEndDate,
|
||||||
|
progress: row.progress,
|
||||||
|
status: row.status,
|
||||||
|
priority: row.priority,
|
||||||
|
riskLevel: row.riskLevel,
|
||||||
|
budget: row.budget,
|
||||||
|
cost: row.cost
|
||||||
|
};
|
||||||
|
projectEditModal.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存项目编辑 */
|
||||||
|
async function saveProjectEdit() {
|
||||||
|
if (!projectEditForm.value.projectName) {
|
||||||
|
message("请输入项目名称", { type: "warning" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
projectEditLoading.value = true;
|
||||||
|
try {
|
||||||
|
await updateProject(projectEditForm.value);
|
||||||
|
message("项目更新成功", { type: "success" });
|
||||||
|
projectEditModal.value = false;
|
||||||
|
onSearch();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("更新项目失败:", error);
|
||||||
|
message("更新项目失败", { type: "error" });
|
||||||
|
} finally {
|
||||||
|
projectEditLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开状态更新模态框 */
|
||||||
|
function openStatusModal(row: ProjectItem) {
|
||||||
|
statusUpdateForm.value = {
|
||||||
|
id: row.id!,
|
||||||
|
status: row.status || "ongoing"
|
||||||
|
};
|
||||||
|
statusUpdateModal.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存状态更新 */
|
||||||
|
async function saveStatusUpdate() {
|
||||||
|
if (!statusUpdateForm.value.status) {
|
||||||
|
message("请选择状态", { type: "warning" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
statusUpdateLoading.value = true;
|
||||||
|
try {
|
||||||
|
await updateProjectStatus(
|
||||||
|
statusUpdateForm.value.id,
|
||||||
|
statusUpdateForm.value.status
|
||||||
|
);
|
||||||
|
message("状态更新成功", { type: "success" });
|
||||||
|
statusUpdateModal.value = false;
|
||||||
|
onSearch();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("更新状态失败:", error);
|
||||||
|
message("更新状态失败", { type: "error" });
|
||||||
|
} finally {
|
||||||
|
statusUpdateLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开项目经理更新模态框 */
|
||||||
|
function openManagerModal(row: ProjectItem) {
|
||||||
|
managerUpdateForm.value = {
|
||||||
|
id: row.id!,
|
||||||
|
managerName: ""
|
||||||
|
};
|
||||||
|
managerUpdateModal.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存项目经理更新 */
|
||||||
|
async function saveManagerUpdate() {
|
||||||
|
if (!managerUpdateForm.value.managerName) {
|
||||||
|
message("请输入项目经理姓名", { type: "warning" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
managerUpdateLoading.value = true;
|
||||||
|
try {
|
||||||
|
await updateProjectManager(
|
||||||
|
managerUpdateForm.value.id,
|
||||||
|
managerUpdateForm.value.managerName
|
||||||
|
);
|
||||||
|
message("项目经理更新成功", { type: "success" });
|
||||||
|
managerUpdateModal.value = false;
|
||||||
|
onSearch();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("更新项目经理失败:", error);
|
||||||
|
message("更新项目经理失败", { type: "error" });
|
||||||
|
} finally {
|
||||||
|
managerUpdateLoading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态标签类型
|
// 获取状态标签类型
|
||||||
@@ -94,6 +229,19 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
|||||||
return "success";
|
return "success";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取状态文本
|
||||||
|
function getStatusText(status?: string): string {
|
||||||
|
const statusMap: Record<string, string> = {
|
||||||
|
draft: "草稿",
|
||||||
|
planning: "规划中",
|
||||||
|
ongoing: "进行中",
|
||||||
|
paused: "已暂停",
|
||||||
|
completed: "已完成",
|
||||||
|
cancelled: "已取消"
|
||||||
|
};
|
||||||
|
return statusMap[status || ""] || status || "未知";
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -344,11 +492,38 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
|||||||
<component :is="useRenderIcon(ViewIcon)" class="mr-2" />
|
<component :is="useRenderIcon(ViewIcon)" class="mr-2" />
|
||||||
查看详情
|
查看详情
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item @click="handleEdit(item)">
|
<el-dropdown-item
|
||||||
|
v-if="canEditProject"
|
||||||
|
@click="handleEdit(item)"
|
||||||
|
>
|
||||||
<component :is="useRenderIcon(EditPenIcon)" class="mr-2" />
|
<component :is="useRenderIcon(EditPenIcon)" class="mr-2" />
|
||||||
编辑项目
|
编辑项目
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item divided @click="handleDelete(item)">
|
<el-dropdown-item
|
||||||
|
v-if="canUpdateProjectStatus"
|
||||||
|
@click="openStatusModal(item)"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="useRenderIcon('ri/settings-3-line')"
|
||||||
|
class="mr-2"
|
||||||
|
/>
|
||||||
|
更新状态
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
v-if="canEditProject"
|
||||||
|
@click="openManagerModal(item)"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="useRenderIcon('ri-user-settings-line')"
|
||||||
|
class="mr-2"
|
||||||
|
/>
|
||||||
|
更换项目经理
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
v-if="canDeleteProject"
|
||||||
|
divided
|
||||||
|
@click="handleDelete(item)"
|
||||||
|
>
|
||||||
<component :is="useRenderIcon(DeleteIcon)" class="mr-2" />
|
<component :is="useRenderIcon(DeleteIcon)" class="mr-2" />
|
||||||
<span class="text-red-500">删除项目</span>
|
<span class="text-red-500">删除项目</span>
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
@@ -363,7 +538,7 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
|||||||
size="small"
|
size="small"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
>
|
>
|
||||||
{{ item.status || "未知" }}
|
{{ getStatusText(item.status) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<el-tag
|
<el-tag
|
||||||
:type="getRiskType(item.riskLevel)"
|
:type="getRiskType(item.riskLevel)"
|
||||||
@@ -374,13 +549,6 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
class="text-sm text-gray-500 mb-3 line-clamp-2"
|
|
||||||
style="min-height: 40px"
|
|
||||||
>
|
|
||||||
{{ item.myRole ? `我的角色: ${item.myRole}` : "暂无角色信息" }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-4 text-xs text-gray-400 mb-3">
|
<div class="flex items-center gap-4 text-xs text-gray-400 mb-3">
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
<component :is="useRenderIcon(CalendarIcon)" />
|
<component :is="useRenderIcon(CalendarIcon)" />
|
||||||
@@ -400,7 +568,10 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
|||||||
|
|
||||||
<div class="flex-bc">
|
<div class="flex-bc">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<el-avatar :size="28">
|
<el-avatar v-if="item.managerAvatar" :size="28">
|
||||||
|
<img :src="item.managerAvatar" alt="负责人头像" />
|
||||||
|
</el-avatar>
|
||||||
|
<el-avatar v-else :size="28">
|
||||||
<component :is="useRenderIcon(UserIcon)" />
|
<component :is="useRenderIcon(UserIcon)" />
|
||||||
</el-avatar>
|
</el-avatar>
|
||||||
<span class="text-sm">{{ item.managerName || "未分配" }}</span>
|
<span class="text-sm">{{ item.managerName || "未分配" }}</span>
|
||||||
@@ -436,6 +607,207 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
|||||||
v-model:visible="wizardVisible"
|
v-model:visible="wizardVisible"
|
||||||
@success="handleWizardSuccess"
|
@success="handleWizardSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 项目编辑模态框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="projectEditModal"
|
||||||
|
title="编辑项目"
|
||||||
|
width="600px"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="projectFormRef"
|
||||||
|
:model="projectEditForm"
|
||||||
|
label-width="100px"
|
||||||
|
class="project-edit-form"
|
||||||
|
>
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="项目名称" prop="projectName" required>
|
||||||
|
<el-input
|
||||||
|
v-model="projectEditForm.projectName"
|
||||||
|
placeholder="请输入项目名称"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="项目编号">
|
||||||
|
<el-input
|
||||||
|
v-model="projectEditForm.projectCode"
|
||||||
|
placeholder="请输入项目编号"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="项目类型">
|
||||||
|
<el-input
|
||||||
|
v-model="projectEditForm.projectType"
|
||||||
|
placeholder="请输入项目类型"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="计划开始日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="projectEditForm.planStartDate"
|
||||||
|
type="date"
|
||||||
|
placeholder="选择日期"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="计划结束日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="projectEditForm.planEndDate"
|
||||||
|
type="date"
|
||||||
|
placeholder="选择日期"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="优先级">
|
||||||
|
<el-select
|
||||||
|
v-model="projectEditForm.priority"
|
||||||
|
placeholder="请选择优先级"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option label="关键" value="critical" />
|
||||||
|
<el-option label="高" value="high" />
|
||||||
|
<el-option label="中" value="medium" />
|
||||||
|
<el-option label="低" value="low" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="风险等级">
|
||||||
|
<el-select
|
||||||
|
v-model="projectEditForm.riskLevel"
|
||||||
|
placeholder="请选择风险等级"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option label="高" value="high" />
|
||||||
|
<el-option label="中" value="medium" />
|
||||||
|
<el-option label="低" value="low" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="项目预算">
|
||||||
|
<el-input-number
|
||||||
|
v-model="projectEditForm.budget"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
placeholder="预算"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="已花费金额">
|
||||||
|
<el-input-number
|
||||||
|
v-model="projectEditForm.cost"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
placeholder="已花费"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="项目进度">
|
||||||
|
<el-slider
|
||||||
|
v-model="projectEditForm.progress"
|
||||||
|
:max="100"
|
||||||
|
show-input
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="projectEditModal = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="projectEditLoading"
|
||||||
|
@click="saveProjectEdit"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 状态更新模态框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="statusUpdateModal"
|
||||||
|
title="更新项目状态"
|
||||||
|
width="400px"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-form :model="statusUpdateForm" label-width="80px">
|
||||||
|
<el-form-item label="项目状态">
|
||||||
|
<el-select
|
||||||
|
v-model="statusUpdateForm.status"
|
||||||
|
placeholder="请选择状态"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option label="草稿" value="draft" />
|
||||||
|
<el-option label="规划中" value="planning" />
|
||||||
|
<el-option label="进行中" value="ongoing" />
|
||||||
|
<el-option label="已暂停" value="paused" />
|
||||||
|
<el-option label="已完成" value="completed" />
|
||||||
|
<el-option label="已取消" value="cancelled" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="statusUpdateModal = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="statusUpdateLoading"
|
||||||
|
@click="saveStatusUpdate"
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 项目经理更新模态框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="managerUpdateModal"
|
||||||
|
title="更换项目经理"
|
||||||
|
width="450px"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-form :model="managerUpdateForm" label-width="100px">
|
||||||
|
<el-form-item label="当前负责人">
|
||||||
|
<el-input
|
||||||
|
:model-value="managerUpdateForm.managerName"
|
||||||
|
disabled
|
||||||
|
placeholder="暂无"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="新项目经理" required>
|
||||||
|
<el-input
|
||||||
|
v-model="managerUpdateForm.managerName"
|
||||||
|
placeholder="请输入新项目经理的姓名"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="managerUpdateModal = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="managerUpdateLoading"
|
||||||
|
@click="saveManagerUpdate"
|
||||||
|
>
|
||||||
|
确认更换
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -261,15 +261,11 @@ async function loadRiskList() {
|
|||||||
params.projectId = String(queryParams.value.projectId);
|
params.projectId = String(queryParams.value.projectId);
|
||||||
}
|
}
|
||||||
const res = await getRiskList(params);
|
const res = await getRiskList(params);
|
||||||
// res.data 直接是业务数据 { total, rows, code, msg }
|
|
||||||
const businessData = res.data as any;
|
const businessData = res.data as any;
|
||||||
console.log("API业务数据:", businessData);
|
|
||||||
if (businessData.code === 200) {
|
if (businessData.code === 200) {
|
||||||
const rows = businessData.rows || [];
|
const rows = businessData.rows || [];
|
||||||
dataList.value = rows;
|
dataList.value = rows;
|
||||||
// 处理字符串类型的total
|
|
||||||
pagination.value.total = parseInt(businessData.total) || rows.length || 0;
|
pagination.value.total = parseInt(businessData.total) || rows.length || 0;
|
||||||
console.log("风险列表数据:", rows);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message("加载风险列表失败", { type: "error" });
|
message("加载风险列表失败", { type: "error" });
|
||||||
@@ -278,85 +274,91 @@ async function loadRiskList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载项目列表
|
|
||||||
async function loadProjectList() {
|
async function loadProjectList() {
|
||||||
try {
|
try {
|
||||||
const res = await getProjectList({ pageNum: 1, pageSize: 100 });
|
const res = await getProjectList({ pageNum: 1, pageSize: 100 });
|
||||||
// res.data 直接是业务数据 { total, rows, code, msg }
|
|
||||||
const businessData = res.data as any;
|
const businessData = res.data as any;
|
||||||
if (businessData.code === 200) {
|
if (businessData.code === 200) {
|
||||||
projectList.value = businessData.rows || [];
|
projectList.value = businessData.rows || [];
|
||||||
console.log("项目列表:", projectList.value);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("加载项目列表失败", error);
|
console.error("加载项目列表失败", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载统计数据
|
|
||||||
async function loadStatistics() {
|
async function loadStatistics() {
|
||||||
try {
|
try {
|
||||||
const res = await getRiskStatistics();
|
const res = await getRiskStatistics();
|
||||||
const statsResponse = res.data as any;
|
const data = res.data as any;
|
||||||
if (statsResponse.code === 200 && statsResponse.data) {
|
|
||||||
const data = statsResponse.data;
|
statistics.value = {
|
||||||
// 处理字符串类型的统计数据
|
totalCount: parseInt(data.totalCount) || 0,
|
||||||
statistics.value = {
|
identifiedCount: parseInt(data.identifiedCount) || 0,
|
||||||
totalCount: parseInt(data.totalCount) || 0,
|
assignedCount: parseInt(data.assignedCount) || 0,
|
||||||
identifiedCount: parseInt(data.identifiedCount) || 0,
|
mitigatingCount: parseInt(data.mitigatingCount) || 0,
|
||||||
assignedCount: parseInt(data.assignedCount) || 0,
|
resolvedCount: parseInt(data.resolvedCount) || 0,
|
||||||
mitigatingCount: parseInt(data.mitigatingCount) || 0,
|
closedCount: parseInt(data.closedCount) || 0,
|
||||||
resolvedCount: parseInt(data.resolvedCount) || 0,
|
criticalCount: parseInt(data.criticalCount) || 0,
|
||||||
closedCount: parseInt(data.closedCount) || 0,
|
highCount: parseInt(data.highCount) || 0,
|
||||||
criticalCount: parseInt(data.criticalCount) || 0,
|
mediumCount: parseInt(data.mediumCount) || 0,
|
||||||
highCount: parseInt(data.highCount) || 0,
|
lowCount: parseInt(data.lowCount) || 0,
|
||||||
mediumCount: parseInt(data.mediumCount) || 0,
|
categoryStats: data.categoryStats || {},
|
||||||
lowCount: parseInt(data.lowCount) || 0,
|
levelStats: data.levelStats || {},
|
||||||
categoryStats: data.categoryStats || {},
|
trendData: data.trendData || {},
|
||||||
levelStats: data.levelStats || {},
|
averageRiskScore: parseFloat(data.averageRiskScore) || 0,
|
||||||
trendData: data.trendData || {},
|
unresolvedHighCount: parseInt(data.unresolvedHighCount) || 0
|
||||||
averageRiskScore: parseFloat(data.averageRiskScore) || 0,
|
};
|
||||||
unresolvedHighCount: parseInt(data.unresolvedHighCount) || 0
|
setTimeout(() => {
|
||||||
};
|
|
||||||
updateCharts();
|
updateCharts();
|
||||||
}
|
}, 100);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("加载统计数据失败", error);
|
console.error("加载统计数据失败", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化饼图
|
// 更新饼图 - 使用 categoryStats 分类统计数据
|
||||||
function initPieChart() {
|
|
||||||
if (!pieChartRef.value) return;
|
|
||||||
pieChart = echarts.init(pieChartRef.value);
|
|
||||||
updatePieChart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新饼图
|
|
||||||
function updatePieChart() {
|
function updatePieChart() {
|
||||||
if (!pieChart) return;
|
if (!pieChartRef.value) return;
|
||||||
const data = [
|
if (!pieChart) {
|
||||||
{
|
pieChart = echarts.init(pieChartRef.value);
|
||||||
value: statistics.value.criticalCount || 0,
|
}
|
||||||
name: "严重",
|
|
||||||
itemStyle: { color: "#f56c6c" }
|
const categoryStats = statistics.value.categoryStats || {};
|
||||||
},
|
const categoryColors: Record<string, string> = {
|
||||||
{
|
schedule: "#409eff", // 进度 - 蓝色
|
||||||
value: statistics.value.highCount || 0,
|
external: "#e6a23c", // 外部 - 橙色
|
||||||
name: "高",
|
technical: "#67c23a", // 技术 - 绿色
|
||||||
itemStyle: { color: "#e6a23c" }
|
resource: "#909399", // 资源 - 灰色
|
||||||
},
|
personnel: "#f56c6c", // 人员 - 红色
|
||||||
{
|
quality: "#9c27b0", // 质量 - 紫色
|
||||||
value: statistics.value.mediumCount || 0,
|
cost: "#ff9800", // 成本 - 深橙
|
||||||
name: "中",
|
other: "#795548" // 其他 - 棕色
|
||||||
itemStyle: { color: "#409eff" }
|
};
|
||||||
},
|
const categoryNames: Record<string, string> = {
|
||||||
{
|
schedule: "进度风险",
|
||||||
value: statistics.value.lowCount || 0,
|
external: "外部风险",
|
||||||
name: "低",
|
technical: "技术风险",
|
||||||
itemStyle: { color: "#67c23a" }
|
resource: "资源风险",
|
||||||
}
|
personnel: "人员风险",
|
||||||
].filter(item => item.value > 0);
|
quality: "质量风险",
|
||||||
|
cost: "成本风险",
|
||||||
|
other: "其他风险"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 构建饼图数据 - 处理字符串类型的值
|
||||||
|
const data = Object.entries(categoryStats)
|
||||||
|
.map(([key, value]) => {
|
||||||
|
const numValue =
|
||||||
|
typeof value === "string" ? parseInt(value) : (value as number) || 0;
|
||||||
|
return {
|
||||||
|
value: numValue,
|
||||||
|
name: categoryNames[key] || key,
|
||||||
|
itemStyle: { color: categoryColors[key] || "#909399" }
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(item => item.value > 0)
|
||||||
|
.sort((a, b) => b.value - a.value);
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "item",
|
trigger: "item",
|
||||||
@@ -371,7 +373,7 @@ function updatePieChart() {
|
|||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: "风险分布",
|
name: "风险分类分布",
|
||||||
type: "pie",
|
type: "pie",
|
||||||
radius: ["50%", "70%"],
|
radius: ["50%", "70%"],
|
||||||
center: ["50%", "45%"],
|
center: ["50%", "45%"],
|
||||||
@@ -394,48 +396,86 @@ function updatePieChart() {
|
|||||||
labelLine: {
|
labelLine: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
data
|
data:
|
||||||
|
data.length > 0
|
||||||
|
? data
|
||||||
|
: [{ value: 1, name: "暂无数据", itemStyle: { color: "#e0e0e0" } }]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
pieChart.setOption(option);
|
pieChart.setOption(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化趋势图
|
|
||||||
function initTrendChart() {
|
function initTrendChart() {
|
||||||
if (!trendChartRef.value) return;
|
if (!trendChartRef.value) return;
|
||||||
trendChart = echarts.init(trendChartRef.value);
|
trendChart = echarts.init(trendChartRef.value);
|
||||||
updateTrendChart();
|
updateTrendChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新趋势图
|
|
||||||
function updateTrendChart() {
|
function updateTrendChart() {
|
||||||
if (!trendChart) return;
|
if (!trendChartRef.value) return;
|
||||||
const months = ["1月", "2月", "3月", "4月", "5月", "6月"];
|
if (!trendChart) {
|
||||||
|
trendChart = echarts.init(trendChartRef.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const levelStats = statistics.value.levelStats || {};
|
||||||
|
const levelData = [
|
||||||
|
{
|
||||||
|
name: "严重",
|
||||||
|
value:
|
||||||
|
typeof levelStats.critical === "string"
|
||||||
|
? parseInt(levelStats.critical)
|
||||||
|
: (levelStats.critical as number) || 0,
|
||||||
|
color: "#f56c6c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "高",
|
||||||
|
value:
|
||||||
|
typeof levelStats.high === "string"
|
||||||
|
? parseInt(levelStats.high)
|
||||||
|
: (levelStats.high as number) || 0,
|
||||||
|
color: "#e6a23c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "中",
|
||||||
|
value:
|
||||||
|
typeof levelStats.medium === "string"
|
||||||
|
? parseInt(levelStats.medium)
|
||||||
|
: (levelStats.medium as number) || 0,
|
||||||
|
color: "#409eff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "低",
|
||||||
|
value:
|
||||||
|
typeof levelStats.low === "string"
|
||||||
|
? parseInt(levelStats.low)
|
||||||
|
: (levelStats.low as number) || 0,
|
||||||
|
color: "#67c23a"
|
||||||
|
}
|
||||||
|
].filter(item => item.value > 0);
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: "axis",
|
||||||
axisPointer: { type: "shadow" }
|
axisPointer: { type: "shadow" },
|
||||||
},
|
formatter: (params: any) => {
|
||||||
legend: {
|
const data = params[0];
|
||||||
data: ["高风险", "中风险", "低风险"],
|
return `${data.name}: ${data.value} 个`;
|
||||||
right: 10,
|
}
|
||||||
top: 10,
|
|
||||||
itemWidth: 12,
|
|
||||||
itemHeight: 12
|
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: "3%",
|
left: "3%",
|
||||||
right: "4%",
|
right: "4%",
|
||||||
bottom: "3%",
|
bottom: "3%",
|
||||||
top: "15%",
|
top: "10%",
|
||||||
containLabel: true
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: "category",
|
type: "category",
|
||||||
data: months,
|
data: levelData.map(item => item.name),
|
||||||
axisLine: { lineStyle: { color: "#dcdfe6" } },
|
axisLine: { lineStyle: { color: "#dcdfe6" } },
|
||||||
axisLabel: { color: "#606266" }
|
axisLabel: { color: "#606266", fontSize: 12 },
|
||||||
|
axisTick: { show: false }
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: "value",
|
type: "value",
|
||||||
@@ -445,32 +485,29 @@ function updateTrendChart() {
|
|||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: "高风险",
|
name: "风险数量",
|
||||||
type: "bar",
|
type: "bar",
|
||||||
stack: "total",
|
data: levelData.map((item, index) => ({
|
||||||
data: [5, 8, 6, 10, 12, 8],
|
value: item.value,
|
||||||
itemStyle: { color: "#f56c6c", borderRadius: [0, 0, 4, 4] }
|
itemStyle: {
|
||||||
},
|
color: item.color,
|
||||||
{
|
borderRadius: [4, 4, 0, 0]
|
||||||
name: "中风险",
|
}
|
||||||
type: "bar",
|
})),
|
||||||
stack: "total",
|
barWidth: "50%",
|
||||||
data: [8, 12, 10, 15, 18, 16],
|
label: {
|
||||||
itemStyle: { color: "#e6a23c" }
|
show: true,
|
||||||
},
|
position: "top",
|
||||||
{
|
color: "#606266",
|
||||||
name: "低风险",
|
fontSize: 12,
|
||||||
type: "bar",
|
formatter: "{c}"
|
||||||
stack: "total",
|
}
|
||||||
data: [10, 15, 12, 18, 20, 22],
|
|
||||||
itemStyle: { color: "#67c23a", borderRadius: [4, 4, 0, 0] }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
trendChart.setOption(option);
|
trendChart.setOption(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新图表
|
|
||||||
function updateCharts() {
|
function updateCharts() {
|
||||||
updatePieChart();
|
updatePieChart();
|
||||||
updateTrendChart();
|
updateTrendChart();
|
||||||
@@ -526,24 +563,15 @@ async function handleCreate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 直接传递字符串ID,避免精度丢失
|
|
||||||
const res = await submitRiskAssessment(String(queryParams.value.projectId));
|
const res = await submitRiskAssessment(String(queryParams.value.projectId));
|
||||||
console.log("风险评估API响应:", res);
|
|
||||||
console.log("res.data:", res.data);
|
|
||||||
const responseData = res.data as any;
|
const responseData = res.data as any;
|
||||||
// 扁平化结构:res.data 直接是 { code: 200, data: {...}, message: "..." }
|
|
||||||
if (responseData.code === 200) {
|
if (responseData.code === 200) {
|
||||||
message("风险评估任务已提交,AI正在分析中...", { type: "success" });
|
message("风险评估任务已提交,AI 正在分析中...", { type: "success" });
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
|
||||||
"响应code不是200:",
|
|
||||||
responseData.code,
|
|
||||||
responseData.message
|
|
||||||
);
|
|
||||||
message(responseData.message || "提交失败", { type: "error" });
|
message(responseData.message || "提交失败", { type: "error" });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("风险评估请求异常:", error);
|
console.error("风险评估请求异常", error);
|
||||||
message("提交风险评估任务失败", { type: "error" });
|
message("提交风险评估任务失败", { type: "error" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -591,8 +619,7 @@ onMounted(() => {
|
|||||||
loadProjectList();
|
loadProjectList();
|
||||||
loadRiskList();
|
loadRiskList();
|
||||||
loadStatistics();
|
loadStatistics();
|
||||||
initPieChart();
|
// 初始化的图表会在数据加载后通过 updateCharts 自动渲染
|
||||||
initTrendChart();
|
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -708,7 +735,7 @@ onUnmounted(() => {
|
|||||||
<el-card shadow="hover" class="chart-card">
|
<el-card shadow="hover" class="chart-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex-bc">
|
<div class="flex-bc">
|
||||||
<span class="font-medium">风险分布</span>
|
<span class="font-medium">风险分类分布</span>
|
||||||
<el-button link>
|
<el-button link>
|
||||||
<component :is="useRenderIcon(MoreIcon)" />
|
<component :is="useRenderIcon(MoreIcon)" />
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -717,28 +744,46 @@ onUnmounted(() => {
|
|||||||
<div ref="pieChartRef" class="chart-container" />
|
<div ref="pieChartRef" class="chart-container" />
|
||||||
<div class="risk-legend">
|
<div class="risk-legend">
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<span class="legend-dot" style="background-color: #f56c6c" />
|
<span class="legend-dot" style="background-color: #409eff" />
|
||||||
<span>严重</span>
|
<span>进度</span>
|
||||||
<span class="legend-value">{{
|
<span class="legend-value">{{
|
||||||
statistics.criticalCount || 0
|
statistics.categoryStats?.schedule || 0
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<span class="legend-dot" style="background-color: #e6a23c" />
|
<span class="legend-dot" style="background-color: #e6a23c" />
|
||||||
<span>高</span>
|
<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 class="legend-value">{{
|
<span class="legend-value">{{
|
||||||
statistics.mediumCount || 0
|
statistics.categoryStats?.external || 0
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<span class="legend-dot" style="background-color: #67c23a" />
|
<span class="legend-dot" style="background-color: #67c23a" />
|
||||||
<span>低</span>
|
<span>技术</span>
|
||||||
<span class="legend-value">{{ statistics.lowCount || 0 }}</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>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -747,7 +792,7 @@ onUnmounted(() => {
|
|||||||
<el-card shadow="hover" class="chart-card">
|
<el-card shadow="hover" class="chart-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex-bc">
|
<div class="flex-bc">
|
||||||
<span class="font-medium">风险趋势</span>
|
<span class="font-medium">风险状态分布</span>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<el-radio-group v-model="trendPeriod" size="small">
|
<el-radio-group v-model="trendPeriod" size="small">
|
||||||
<el-radio-button label="月度" value="month" />
|
<el-radio-button label="月度" value="month" />
|
||||||
|
|||||||
@@ -137,7 +137,11 @@ const {
|
|||||||
background: 'var(--el-fill-color-light)',
|
background: 'var(--el-fill-color-light)',
|
||||||
color: 'var(--el-text-color-primary)'
|
color: 'var(--el-text-color-primary)'
|
||||||
}"
|
}"
|
||||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
:tree-props="{
|
||||||
|
children: 'children',
|
||||||
|
hasChildren: 'hasChildren',
|
||||||
|
checkStrictly: true
|
||||||
|
}"
|
||||||
row-class-name="permission-table-row"
|
row-class-name="permission-table-row"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
@page-size-change="handleSizeChange"
|
@page-size-change="handleSizeChange"
|
||||||
|
|||||||
Reference in New Issue
Block a user