Compare commits
12 Commits
e10aa07367
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 111515f9ca | |||
| 00c521540e | |||
| 5b96f54d71 | |||
| 5bb9b6d3db | |||
| 5363ec8342 | |||
| dab86a40ff | |||
| d698fae12a | |||
| c7abf48c6a | |||
| 4b30c1350d | |||
| 2735c57778 | |||
| 93ea80a636 | |||
| 9c777ee429 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -24,3 +24,7 @@ tsconfig.tsbuildinfo
|
||||
#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;
|
||||
managerId?: number;
|
||||
managerName?: string;
|
||||
managerAvatar?: string;
|
||||
planStartDate?: string;
|
||||
planEndDate?: string;
|
||||
progress?: number;
|
||||
@@ -60,6 +61,60 @@ export const deleteProject = (id: string) => {
|
||||
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 定义 */
|
||||
@@ -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 中的定义) ====================
|
||||
|
||||
/** 项目信息 */
|
||||
@@ -605,3 +812,148 @@ export const getTaskStatusStats = (projectId: string) => {
|
||||
{ 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 } }
|
||||
);
|
||||
};
|
||||
|
||||
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
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, onUnmounted, watch } from "vue";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import {
|
||||
getChatSessions,
|
||||
getSessionMessages,
|
||||
deleteChatSession,
|
||||
createChatSession,
|
||||
buildSSEUrl,
|
||||
type ChatSessionVO,
|
||||
type ChatMessageVO,
|
||||
type ReferencedDocVO,
|
||||
type SSEStartData,
|
||||
type SSEChunkData,
|
||||
type SSEReferencesData,
|
||||
type SSECompleteData,
|
||||
type SSEErrorData
|
||||
} from "@/api/ai-chat";
|
||||
import { getProjectList, type ProjectItem } from "@/api/project";
|
||||
import { createSSEConnection } from "@/utils/sse/chatSSE";
|
||||
import ChatGPT from "@/views/chatai/components/ChatGPT.vue";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import SendIcon from "~icons/ri/send-plane-fill";
|
||||
import AddIcon from "~icons/ri/add-line";
|
||||
import DeleteIcon from "~icons/ep/delete";
|
||||
import DocumentIcon from "~icons/ri/file-text-line";
|
||||
import RefreshIcon from "~icons/ri/refresh-line";
|
||||
|
||||
defineOptions({
|
||||
@@ -36,22 +32,19 @@ const loading = ref(false);
|
||||
const sessions = ref<ChatSessionVO[]>([]);
|
||||
const currentSession = ref<ChatSessionVO | null>(null);
|
||||
const messages = ref<ChatMessageVO[]>([]);
|
||||
const inputMessage = ref("");
|
||||
const sending = ref(false);
|
||||
const projects = ref<ProjectItem[]>([]);
|
||||
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连接关闭函数
|
||||
let abortSSE: (() => void) | null = null;
|
||||
|
||||
// 消息容器引用
|
||||
const messagesContainer = ref<HTMLElement | null>(null);
|
||||
|
||||
// 会话中的临时消息(用于流式显示)
|
||||
const streamingMessage = ref<string>("");
|
||||
const streamingReferences = ref<ReferencedDocVO[]>([]);
|
||||
|
||||
// 加载项目列表
|
||||
async function loadProjects() {
|
||||
projectLoading.value = true;
|
||||
@@ -69,11 +62,18 @@ async function loadProjects() {
|
||||
|
||||
// 加载会话列表
|
||||
async function loadSessions(projectId?: string) {
|
||||
const pid =
|
||||
projectId ||
|
||||
currentProjectId.value ||
|
||||
currentSession.value?.projectId ||
|
||||
"";
|
||||
if (!pid) {
|
||||
sessions.value = [];
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getChatSessions(
|
||||
projectId || currentSession.value?.projectId || ""
|
||||
);
|
||||
const res = await getChatSessions(pid);
|
||||
if (res.code === 200) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
// 加载会话消息
|
||||
async function loadMessages(sessionId: string) {
|
||||
if (!sessionId) return;
|
||||
try {
|
||||
const res = await getSessionMessages(sessionId);
|
||||
if (res.code === 200) {
|
||||
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) {
|
||||
console.error("加载消息失败:", error);
|
||||
@@ -105,145 +194,77 @@ async function loadMessages(sessionId: string) {
|
||||
|
||||
// 创建新会话
|
||||
async function createNewSession() {
|
||||
if (!currentSession.value?.projectId && projects.value.length === 0) {
|
||||
await loadProjects();
|
||||
}
|
||||
showProjectSelect.value = true;
|
||||
if (!projects.value.length) await loadProjects();
|
||||
await startNewDraftSession();
|
||||
}
|
||||
|
||||
// 选择项目后创建会话
|
||||
async function handleProjectSelect(project: ProjectItem) {
|
||||
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 = "";
|
||||
|
||||
// 如果没有会话,需要先创建
|
||||
async function requestAssistantReply(messageText: string): Promise<string> {
|
||||
if (!messageText.trim()) return "";
|
||||
if (!currentSession.value?.projectId) {
|
||||
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;
|
||||
streamingMessage.value = "";
|
||||
streamingReferences.value = [];
|
||||
|
||||
// 建立SSE连接
|
||||
const sseUrl = buildSSEUrl({
|
||||
sessionId: currentSession.value.sessionId || undefined,
|
||||
projectId: currentSession.value.projectId,
|
||||
message: message,
|
||||
message: messageText,
|
||||
useRag: true
|
||||
});
|
||||
|
||||
// 关闭之前的连接
|
||||
if (abortSSE) {
|
||||
abortSSE();
|
||||
abortSSE = null;
|
||||
}
|
||||
|
||||
// 使用带鉴权Header的SSE连接
|
||||
abortSSE = await createSSEConnection({
|
||||
url: sseUrl,
|
||||
onEvent: (eventName: string, data: any) => {
|
||||
switch (eventName) {
|
||||
case "start": {
|
||||
const startData = data as SSEStartData;
|
||||
if (startData.isNewSession || !currentSession.value?.sessionId) {
|
||||
currentSession.value = {
|
||||
...currentSession.value!,
|
||||
sessionId: startData.sessionId,
|
||||
sessionTitle: message.slice(0, 20)
|
||||
};
|
||||
loadSessions(currentSession.value.projectId);
|
||||
let fullText = "";
|
||||
const replyText = await new Promise<string>(async resolve => {
|
||||
abortSSE = await createSSEConnection({
|
||||
url: sseUrl,
|
||||
onEvent: (eventName: string, data: any) => {
|
||||
switch (eventName) {
|
||||
case "start": {
|
||||
const startData = data as SSEStartData;
|
||||
if (startData.isNewSession || !currentSession.value?.sessionId) {
|
||||
currentSession.value = {
|
||||
...currentSession.value!,
|
||||
sessionId: startData.sessionId,
|
||||
sessionTitle: messageText.slice(0, 20)
|
||||
};
|
||||
loadSessions(currentSession.value.projectId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "chunk": {
|
||||
const chunkData = data as SSEChunkData;
|
||||
streamingMessage.value += chunkData.content;
|
||||
scrollToBottom();
|
||||
break;
|
||||
}
|
||||
case "references": {
|
||||
const refData = data as SSEReferencesData;
|
||||
streamingReferences.value = refData.docs || [];
|
||||
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;
|
||||
case "chunk": {
|
||||
const chunkData = data as SSEChunkData;
|
||||
fullText += chunkData.content;
|
||||
break;
|
||||
}
|
||||
case "complete": {
|
||||
resolve(fullText);
|
||||
break;
|
||||
}
|
||||
case "error": {
|
||||
const errorData = data as SSEErrorData;
|
||||
resolve(errorData.message || "对话发生错误");
|
||||
break;
|
||||
}
|
||||
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);
|
||||
|
||||
if (currentSession.value?.sessionId === session.sessionId) {
|
||||
currentSession.value = null;
|
||||
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) {
|
||||
@@ -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) {
|
||||
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连接
|
||||
onUnmounted(() => {
|
||||
if (abortSSE) {
|
||||
@@ -301,8 +314,19 @@ onUnmounted(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化加载
|
||||
loadProjects();
|
||||
async function initPage() {
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -320,25 +344,22 @@ loadProjects();
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 项目选择 -->
|
||||
<div v-if="showProjectSelect" class="project-select-panel">
|
||||
<div class="panel-header">
|
||||
<span>选择项目</span>
|
||||
<el-button link @click="showProjectSelect = false">
|
||||
<component :is="useRenderIcon('ri/close-line')" />
|
||||
</el-button>
|
||||
</div>
|
||||
<el-scrollbar height="300px">
|
||||
<div
|
||||
<div class="project-select">
|
||||
<el-select
|
||||
v-model="selectedProjectId"
|
||||
placeholder="选择项目"
|
||||
filterable
|
||||
:loading="projectLoading"
|
||||
style="width: 100%"
|
||||
@change="applyProjectSelection"
|
||||
>
|
||||
<el-option
|
||||
v-for="project in projects"
|
||||
:key="project.id"
|
||||
class="project-item"
|
||||
@click="handleProjectSelect(project)"
|
||||
>
|
||||
<component :is="useRenderIcon('ri/folder-line')" class="mr-2" />
|
||||
{{ project.projectName }}
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
:key="String(project.id || '')"
|
||||
:label="project.projectName || ''"
|
||||
:value="String(project.id || '')"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 会话列表 -->
|
||||
@@ -394,118 +415,19 @@ loadProjects();
|
||||
</div>
|
||||
<el-button
|
||||
:icon="useRenderIcon(RefreshIcon)"
|
||||
:disabled="!currentSession.sessionId"
|
||||
@click="loadMessages(currentSession.sessionId)"
|
||||
>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<div ref="messagesContainer" class="messages-container">
|
||||
<div
|
||||
v-for="msg in messages"
|
||||
:key="msg.id"
|
||||
: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="chat-body">
|
||||
<ChatGPT
|
||||
:key="`${activeSessionKey}-${chatRenderKey}`"
|
||||
:history="deepChatHistory"
|
||||
:onRequest="requestAssistantReply"
|
||||
/>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@@ -559,28 +481,9 @@ loadProjects();
|
||||
}
|
||||
}
|
||||
|
||||
.project-select-panel {
|
||||
.project-select {
|
||||
padding: 12px 16px;
|
||||
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 {
|
||||
@@ -669,109 +572,17 @@ loadProjects();
|
||||
}
|
||||
}
|
||||
|
||||
.messages-container {
|
||||
.chat-body {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
padding: 16px 20px;
|
||||
background: var(--el-fill-color-blank);
|
||||
border-top: 1px solid var(--el-border-color-light);
|
||||
|
||||
.input-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
|
||||
.tip {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
.chat-body :deep(deep-chat) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
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(() => {
|
||||
applyHistory();
|
||||
chatRef.value.demo = {
|
||||
response: message => {
|
||||
console.log(message);
|
||||
response: async (rawMessage: any) => {
|
||||
const messageText = normalizeUserText(rawMessage);
|
||||
if (props.onRequest) {
|
||||
const replyText = await props.onRequest(messageText);
|
||||
return { text: replyText };
|
||||
}
|
||||
return {
|
||||
text: "仅演示,如需AI服务,请参考 https://deepchat.dev/docs/connect"
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.history,
|
||||
() => {
|
||||
applyHistory();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<deep-chat
|
||||
ref="chatRef"
|
||||
style="border-radius: 10px"
|
||||
style="width: 100%; height: 100%; border-radius: 10px"
|
||||
:messageStyles="{
|
||||
default: {
|
||||
shared: {
|
||||
@@ -101,13 +136,7 @@ onMounted(() => {
|
||||
}
|
||||
}"
|
||||
:textInput="{ placeholder: { text: '发送消息' } }"
|
||||
:history="[
|
||||
{ text: '李白是谁?', role: 'user' },
|
||||
{
|
||||
text: '李白(701年2月28日-762年),号青莲居士,又号“谪仙人”,是唐代著名的浪漫主义诗人,被后人誉为“诗仙”。',
|
||||
role: 'ai'
|
||||
}
|
||||
]"
|
||||
:history="props.history"
|
||||
:demo="true"
|
||||
:connect="{ stream: true }"
|
||||
/>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ref, computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useProject } from "./utils/hook";
|
||||
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 dayjs from "dayjs";
|
||||
|
||||
@@ -23,6 +32,34 @@ defineOptions({
|
||||
const router = useRouter();
|
||||
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 {
|
||||
form,
|
||||
formRef,
|
||||
@@ -59,8 +96,106 @@ function handleView(row: any) {
|
||||
}
|
||||
|
||||
// 编辑项目
|
||||
function handleEdit(row: any) {
|
||||
console.log("编辑项目", row);
|
||||
function handleEdit(row: ProjectItem) {
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
function getStatusText(status?: string): string {
|
||||
const statusMap: Record<string, string> = {
|
||||
draft: "草稿",
|
||||
planning: "规划中",
|
||||
ongoing: "进行中",
|
||||
paused: "已暂停",
|
||||
completed: "已完成",
|
||||
cancelled: "已取消"
|
||||
};
|
||||
return statusMap[status || ""] || status || "未知";
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -344,11 +492,38 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
||||
<component :is="useRenderIcon(ViewIcon)" class="mr-2" />
|
||||
查看详情
|
||||
</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" />
|
||||
编辑项目
|
||||
</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" />
|
||||
<span class="text-red-500">删除项目</span>
|
||||
</el-dropdown-item>
|
||||
@@ -363,7 +538,7 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
||||
size="small"
|
||||
class="mr-2"
|
||||
>
|
||||
{{ item.status || "未知" }}
|
||||
{{ getStatusText(item.status) }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
:type="getRiskType(item.riskLevel)"
|
||||
@@ -374,13 +549,6 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
||||
</el-tag>
|
||||
</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">
|
||||
<span class="flex items-center gap-1">
|
||||
<component :is="useRenderIcon(CalendarIcon)" />
|
||||
@@ -400,7 +568,10 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
||||
|
||||
<div class="flex-bc">
|
||||
<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)" />
|
||||
</el-avatar>
|
||||
<span class="text-sm">{{ item.managerName || "未分配" }}</span>
|
||||
@@ -436,6 +607,207 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
||||
v-model:visible="wizardVisible"
|
||||
@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>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -261,15 +261,11 @@ async function loadRiskList() {
|
||||
params.projectId = String(queryParams.value.projectId);
|
||||
}
|
||||
const res = await getRiskList(params);
|
||||
// res.data 直接是业务数据 { total, rows, code, msg }
|
||||
const businessData = res.data as any;
|
||||
console.log("API业务数据:", businessData);
|
||||
if (businessData.code === 200) {
|
||||
const rows = businessData.rows || [];
|
||||
dataList.value = rows;
|
||||
// 处理字符串类型的total
|
||||
pagination.value.total = parseInt(businessData.total) || rows.length || 0;
|
||||
console.log("风险列表数据:", rows);
|
||||
}
|
||||
} catch (error) {
|
||||
message("加载风险列表失败", { type: "error" });
|
||||
@@ -278,65 +274,55 @@ async function loadRiskList() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载项目列表
|
||||
async function loadProjectList() {
|
||||
try {
|
||||
const res = await getProjectList({ pageNum: 1, pageSize: 100 });
|
||||
// res.data 直接是业务数据 { total, rows, code, msg }
|
||||
const businessData = res.data as any;
|
||||
if (businessData.code === 200) {
|
||||
projectList.value = businessData.rows || [];
|
||||
console.log("项目列表:", projectList.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载项目列表失败", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载统计数据
|
||||
async function loadStatistics() {
|
||||
try {
|
||||
const res = await getRiskStatistics();
|
||||
const statsResponse = res.data as any;
|
||||
if (statsResponse.code === 200 && statsResponse.data) {
|
||||
const data = statsResponse.data;
|
||||
// 处理字符串类型的统计数据
|
||||
statistics.value = {
|
||||
totalCount: parseInt(data.totalCount) || 0,
|
||||
identifiedCount: parseInt(data.identifiedCount) || 0,
|
||||
assignedCount: parseInt(data.assignedCount) || 0,
|
||||
mitigatingCount: parseInt(data.mitigatingCount) || 0,
|
||||
resolvedCount: parseInt(data.resolvedCount) || 0,
|
||||
closedCount: parseInt(data.closedCount) || 0,
|
||||
criticalCount: parseInt(data.criticalCount) || 0,
|
||||
highCount: parseInt(data.highCount) || 0,
|
||||
mediumCount: parseInt(data.mediumCount) || 0,
|
||||
lowCount: parseInt(data.lowCount) || 0,
|
||||
categoryStats: data.categoryStats || {},
|
||||
levelStats: data.levelStats || {},
|
||||
trendData: data.trendData || {},
|
||||
averageRiskScore: parseFloat(data.averageRiskScore) || 0,
|
||||
unresolvedHighCount: parseInt(data.unresolvedHighCount) || 0
|
||||
};
|
||||
const data = res.data as any;
|
||||
|
||||
statistics.value = {
|
||||
totalCount: parseInt(data.totalCount) || 0,
|
||||
identifiedCount: parseInt(data.identifiedCount) || 0,
|
||||
assignedCount: parseInt(data.assignedCount) || 0,
|
||||
mitigatingCount: parseInt(data.mitigatingCount) || 0,
|
||||
resolvedCount: parseInt(data.resolvedCount) || 0,
|
||||
closedCount: parseInt(data.closedCount) || 0,
|
||||
criticalCount: parseInt(data.criticalCount) || 0,
|
||||
highCount: parseInt(data.highCount) || 0,
|
||||
mediumCount: parseInt(data.mediumCount) || 0,
|
||||
lowCount: parseInt(data.lowCount) || 0,
|
||||
categoryStats: data.categoryStats || {},
|
||||
levelStats: data.levelStats || {},
|
||||
trendData: data.trendData || {},
|
||||
averageRiskScore: parseFloat(data.averageRiskScore) || 0,
|
||||
unresolvedHighCount: parseInt(data.unresolvedHighCount) || 0
|
||||
};
|
||||
setTimeout(() => {
|
||||
updateCharts();
|
||||
}
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error("加载统计数据失败", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化饼图
|
||||
function initPieChart() {
|
||||
if (!pieChartRef.value) return;
|
||||
pieChart = echarts.init(pieChartRef.value);
|
||||
updatePieChart();
|
||||
}
|
||||
|
||||
// 更新饼图 - 使用 categoryStats 分类统计数据
|
||||
function updatePieChart() {
|
||||
if (!pieChart) return;
|
||||
if (!pieChartRef.value) return;
|
||||
if (!pieChart) {
|
||||
pieChart = echarts.init(pieChartRef.value);
|
||||
}
|
||||
|
||||
// 从 categoryStats 获取分类统计数据
|
||||
const categoryStats = statistics.value.categoryStats || {};
|
||||
const categoryColors: Record<string, string> = {
|
||||
schedule: "#409eff", // 进度 - 蓝色
|
||||
@@ -359,13 +345,17 @@ function updatePieChart() {
|
||||
other: "其他风险"
|
||||
};
|
||||
|
||||
// 构建饼图数据
|
||||
// 构建饼图数据 - 处理字符串类型的值
|
||||
const data = Object.entries(categoryStats)
|
||||
.map(([key, value]) => ({
|
||||
value: parseInt(String(value)) || 0,
|
||||
name: categoryNames[key] || key,
|
||||
itemStyle: { color: categoryColors[key] || "#909399" }
|
||||
}))
|
||||
.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);
|
||||
|
||||
@@ -416,45 +406,53 @@ function updatePieChart() {
|
||||
pieChart.setOption(option);
|
||||
}
|
||||
|
||||
// 初始化趋势图
|
||||
function initTrendChart() {
|
||||
if (!trendChartRef.value) return;
|
||||
trendChart = echarts.init(trendChartRef.value);
|
||||
updateTrendChart();
|
||||
}
|
||||
|
||||
// 更新趋势图 - 使用风险状态分布数据
|
||||
function updateTrendChart() {
|
||||
if (!trendChart) return;
|
||||
if (!trendChartRef.value) return;
|
||||
if (!trendChart) {
|
||||
trendChart = echarts.init(trendChartRef.value);
|
||||
}
|
||||
|
||||
// 使用状态统计数据展示风险状态分布
|
||||
const statusData = [
|
||||
const levelStats = statistics.value.levelStats || {};
|
||||
const levelData = [
|
||||
{
|
||||
name: "已识别",
|
||||
value: statistics.value.identifiedCount || 0,
|
||||
color: "#909399"
|
||||
name: "严重",
|
||||
value:
|
||||
typeof levelStats.critical === "string"
|
||||
? parseInt(levelStats.critical)
|
||||
: (levelStats.critical as number) || 0,
|
||||
color: "#f56c6c"
|
||||
},
|
||||
{
|
||||
name: "已分派",
|
||||
value: statistics.value.assignedCount || 0,
|
||||
color: "#409eff"
|
||||
},
|
||||
{
|
||||
name: "缓解中",
|
||||
value: statistics.value.mitigatingCount || 0,
|
||||
name: "高",
|
||||
value:
|
||||
typeof levelStats.high === "string"
|
||||
? parseInt(levelStats.high)
|
||||
: (levelStats.high as number) || 0,
|
||||
color: "#e6a23c"
|
||||
},
|
||||
{
|
||||
name: "已解决",
|
||||
value: statistics.value.resolvedCount || 0,
|
||||
color: "#67c23a"
|
||||
name: "中",
|
||||
value:
|
||||
typeof levelStats.medium === "string"
|
||||
? parseInt(levelStats.medium)
|
||||
: (levelStats.medium as number) || 0,
|
||||
color: "#409eff"
|
||||
},
|
||||
{
|
||||
name: "已关闭",
|
||||
value: statistics.value.closedCount || 0,
|
||||
color: "#13c2c2"
|
||||
name: "低",
|
||||
value:
|
||||
typeof levelStats.low === "string"
|
||||
? parseInt(levelStats.low)
|
||||
: (levelStats.low as number) || 0,
|
||||
color: "#67c23a"
|
||||
}
|
||||
];
|
||||
].filter(item => item.value > 0);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
@@ -474,7 +472,7 @@ function updateTrendChart() {
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: statusData.map(item => item.name),
|
||||
data: levelData.map(item => item.name),
|
||||
axisLine: { lineStyle: { color: "#dcdfe6" } },
|
||||
axisLabel: { color: "#606266", fontSize: 12 },
|
||||
axisTick: { show: false }
|
||||
@@ -489,7 +487,7 @@ function updateTrendChart() {
|
||||
{
|
||||
name: "风险数量",
|
||||
type: "bar",
|
||||
data: statusData.map((item, index) => ({
|
||||
data: levelData.map((item, index) => ({
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: item.color,
|
||||
@@ -510,7 +508,6 @@ function updateTrendChart() {
|
||||
trendChart.setOption(option);
|
||||
}
|
||||
|
||||
// 更新图表
|
||||
function updateCharts() {
|
||||
updatePieChart();
|
||||
updateTrendChart();
|
||||
@@ -566,24 +563,15 @@ async function handleCreate() {
|
||||
}
|
||||
|
||||
try {
|
||||
// 直接传递字符串ID,避免精度丢失
|
||||
const res = await submitRiskAssessment(String(queryParams.value.projectId));
|
||||
console.log("风险评估API响应:", res);
|
||||
console.log("res.data:", res.data);
|
||||
const responseData = res.data as any;
|
||||
// 扁平化结构:res.data 直接是 { code: 200, data: {...}, message: "..." }
|
||||
if (responseData.code === 200) {
|
||||
message("风险评估任务已提交,AI正在分析中...", { type: "success" });
|
||||
message("风险评估任务已提交,AI 正在分析中...", { type: "success" });
|
||||
} else {
|
||||
console.error(
|
||||
"响应code不是200:",
|
||||
responseData.code,
|
||||
responseData.message
|
||||
);
|
||||
message(responseData.message || "提交失败", { type: "error" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("风险评估请求异常:", error);
|
||||
console.error("风险评估请求异常", error);
|
||||
message("提交风险评估任务失败", { type: "error" });
|
||||
}
|
||||
}
|
||||
@@ -631,8 +619,7 @@ onMounted(() => {
|
||||
loadProjectList();
|
||||
loadRiskList();
|
||||
loadStatistics();
|
||||
initPieChart();
|
||||
initTrendChart();
|
||||
// 初始化的图表会在数据加载后通过 updateCharts 自动渲染
|
||||
window.addEventListener("resize", handleResize);
|
||||
});
|
||||
|
||||
|
||||
@@ -137,7 +137,11 @@ const {
|
||||
background: 'var(--el-fill-color-light)',
|
||||
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"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
|
||||
Reference in New Issue
Block a user