Compare commits

..

12 Commits

Author SHA1 Message Date
111515f9ca fix(project): 项目管理修改提交人展示
Some checks failed
Lint Code / Lint Code (push) Failing after 2m58s
2026-04-01 16:48:12 +08:00
00c521540e refactor(project-detail): 调整里程碑和日报组件的布局结构
All checks were successful
Lint Code / Lint Code (push) Successful in 2m46s
- 简化里程碑名称和删除按钮的模板语法,移除不必要的换行
- 将项目日报卡片从独立行移至里程碑卡片下方,使用 `mt-4` 间距分隔
- 保持原有功能和样式不变,仅优化视觉层级和代码结构
2026-04-01 16:22:28 +08:00
5b96f54d71 feat(ai-chat): 重构项目选择逻辑并合并OpenAPI定义文件
Some checks failed
Lint Code / Lint Code (push) Failing after 17m14s
- 将项目选择面板改为下拉选择器,简化UI交互
- 新增 `setDraftByProjectId` 和 `applyProjectSelection` 函数统一处理项目切换
- 删除分散的OpenAPI JSON文件,合并为统一的日报分析查询接口定义
- 在 `project.ts` 中添加 `getDailyReportWithAnalysisReports` API及相关类型定义
- 修复ChatGPT组件样式空格格式问题
2026-04-01 16:03:26 +08:00
5bb9b6d3db feat(ai-chat): 集成DeepChat组件重构聊天界面
All checks were successful
Lint Code / Lint Code (push) Successful in 3m55s
- 将自定义消息列表和输入区域替换为DeepChat组件
- 新增ChatGPT组件支持历史记录传递和自定义请求处理
- 重构消息处理逻辑,简化SSE连接管理
- 改进项目选择和会话管理流程
2026-04-01 14:51:02 +08:00
5363ec8342 fix: 修正日报建议接口返回类型处理逻辑
Some checks failed
Lint Code / Lint Code (push) Failing after 1m35s
修复日报分析建议接口返回类型不一致的问题。当接口返回数组类型时,将其包装为包含 suggestions 字段的对象,以保持前端数据结构的统一性。
2026-04-01 11:30:26 +08:00
dab86a40ff feat(project): 添加日报进度分析建议功能
All checks were successful
Lint Code / Lint Code (push) Successful in 3m0s
- 在项目详情页新增进度更新建议面板,展示AI分析的进度评估和具体建议
- 添加获取和应用日报建议的API接口及类型定义
- 支持批量选择和同意建议,自动更新项目状态
- 优化权限管理表格的树形选择配置,启用严格模式
- 更新.gitignore文件,排除.trae相关文件
2026-04-01 11:02:04 +08:00
d698fae12a fix(risk-assessment): 修正统计数据处理及图表更新逻辑
Some checks failed
Lint Code / Lint Code (push) Failing after 3m6s
- 修正 getRiskStatistics 接口返回数据结构,去除 code 和 data 包装,直接使用业务数据
- 添加详细日志输出统计数据和图表输入数据,便于调试追踪
- 优化饼图和趋势图初始化逻辑,改为懒初始化实例,避免无效初始化
- 饼图数据转换中新增字符串转数字的兼容处理,过滤数值为0的项
- 趋势图调整为使用风险等级分布数据 levelStats,支持字符串及数字类型转换
- 延迟更新图表,确保 DOM 渲染完成后初始化和设置图表
- 移除无用的图表初始化调用,改为数据加载完成后统一更新图表
- 增加 DOM 元素存在性检查及相关警告,防止渲染时出现异常
2026-03-31 18:38:46 +08:00
c7abf48c6a refactor(project): 移除角色信息展示代码
Some checks failed
Lint Code / Lint Code (push) Failing after 1m40s
- 删除了项目列表中显示用户角色的文本块
- 简化了项目项的UI布局
- 减少了冗余的DOM元素和样式声明
- 保持UI整体风格一致性
2026-03-31 18:23:12 +08:00
4b30c1350d feat(project): 添加项目编辑和状态管理功能
Some checks failed
Lint Code / Lint Code (push) Has been cancelled
- 新增 Project 类型定义,完善项目数据结构
- 新增 updateProject、updateProjectStatus 等接口封装
- 添加权限控制,实现基于角色的编辑、删除、状态更新等操作权限判断
- 实现项目编辑模态框,支持项目基本信息、预算、进度等字段的修改
- 实现项目状态更新模态框,支持项目状态的切换操作
- 实现项目经理更换模态框,支持更新项目负责人信息
- 更新项目列表操作菜单,添加编辑、状态更新、项目经理更换等功能入口
- 优化项目状态显示,新增状态中文映射文本显示
- 完善项目编辑和状态更新的保存逻辑,增加操作成功与失败提示信息
- 引入表单校验和操作Loading状态,提升交互体验
2026-03-31 18:22:01 +08:00
2735c57778 feat(project): 增加资源类型的图标和颜色展示
Some checks failed
Lint Code / Lint Code (push) Failing after 3m30s
- 扩展资源类型,新增软件、资金和其他类别
- 实现获取资源类型图标的函数,提供对应图标映射
- 实现获取资源类型颜色的函数,定义各类型对应颜色
- 在资源名称栏增加图标显示,根据类型渲染对应颜色和图标
- 新增资源图标包装器样式,优化图标展示效果
- 保持鼠标悬停样式和整体界面风格一致
2026-03-31 17:43:31 +08:00
93ea80a636 feat(resource): 新增资源管理模块及相关接口和界面
Some checks failed
Lint Code / Lint Code (push) Failing after 3m1s
- 定义资源实体类型及相关请求参数类型
- 实现资源增删改查及状态、数量更新接口
- 添加资源预算汇总、待审批和即将到位资源查询接口
- 在项目详情页增加资源权限控制相关计算属性
- 实现资源编辑模态框及新增、编辑、保存、删除功能
- 资源列表新增操作列支持资源编辑和删除权限控制
- 在界面中显示资源数量及新增资源按钮
- 移除冗余操作按钮,统一资源操作权限管理
2026-03-31 17:39:21 +08:00
9c777ee429 refactor(gantt): 优化任务数据转换及甘特图日期范围计算
Some checks failed
Lint Code / Lint Code (push) Failing after 1m35s
- 调整任务标签显示逻辑,左侧标签加入负责人括号显示
- 任务条标签独立显示任务名,保持界面简洁
- 计算甘特图日期范围时,优先使用任务实际日期动态计算
- 对日期范围添加了前后7天的边距,提高展示灵活性
- 兼容无项目结束日期情况,默认显示30天跨度
- 保持代码结构清晰,增强日期计算的鲁棒性
2026-03-31 17:14:15 +08:00
14 changed files with 3049 additions and 5177 deletions

4
.gitignore vendored
View File

@@ -24,3 +24,7 @@ tsconfig.tsbuildinfo
#qoder
**.qoder**
#trae
**.trae**

View File

@@ -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": []
}

View File

@@ -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 } }
);
};

View 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": []
}

View File

@@ -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": []
}

View File

@@ -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

View File

@@ -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,71 +194,32 @@ 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连接
let fullText = "";
const replyText = await new Promise<string>(async resolve => {
abortSSE = await createSSEConnection({
url: sseUrl,
onEvent: (eventName: string, data: any) => {
@@ -180,7 +230,7 @@ async function sendMessage() {
currentSession.value = {
...currentSession.value!,
sessionId: startData.sessionId,
sessionTitle: message.slice(0, 20)
sessionTitle: messageText.slice(0, 20)
};
loadSessions(currentSession.value.projectId);
}
@@ -188,62 +238,33 @@ async function sendMessage() {
}
case "chunk": {
const chunkData = data as SSEChunkData;
streamingMessage.value += chunkData.content;
scrollToBottom();
break;
}
case "references": {
const refData = data as SSEReferencesData;
streamingReferences.value = refData.docs || [];
fullText += chunkData.content;
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();
resolve(fullText);
break;
}
case "error": {
const errorData = data as SSEErrorData;
ElMessage.error(errorData.message || "对话发生错误");
sending.value = false;
streamingMessage.value = "";
streamingReferences.value = [];
if (abortSSE) {
abortSSE();
abortSSE = null;
}
resolve(errorData.message || "对话发生错误");
break;
}
}
},
onError: (error: Error) => {
ElMessage.error("连接服务器失败: " + error.message);
sending.value = false;
streamingMessage.value = "";
streamingReferences.value = [];
abortSSE = null;
resolve("连接服务器失败: " + error.message);
}
});
});
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
v-for="project in projects"
:key="project.id"
class="project-item"
@click="handleProjectSelect(project)"
<div class="project-select">
<el-select
v-model="selectedProjectId"
placeholder="选择项目"
filterable
:loading="projectLoading"
style="width: 100%"
@change="applyProjectSelection"
>
<component :is="useRenderIcon('ri/folder-line')" class="mr-2" />
{{ project.projectName }}
</div>
</el-scrollbar>
<el-option
v-for="project in projects"
: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 class="chat-body">
<ChatGPT
:key="`${activeSessionKey}-${chatRenderKey}`"
:history="deepChatHistory"
:onRequest="requestAssistantReply"
/>
<!-- 引用文档 -->
<div v-if="msg.referencedDocs?.length" class="referenced-docs">
<div class="ref-title">
<component :is="useRenderIcon(DocumentIcon)" />
参考文档
</div>
<div
v-for="doc in msg.referencedDocs"
:key="doc.id"
class="ref-doc"
>
<span class="doc-title">{{ doc.title }}</span>
<el-tag size="small" type="info"
>相关度: {{ (doc.score * 100).toFixed(0) }}%</el-tag
>
</div>
</div>
<div class="message-time">{{ formatTime(msg.createTime) }}</div>
</div>
</div>
<!-- 流式输出中的消息 -->
<div v-if="streamingMessage" class="message-item assistant streaming">
<div class="message-avatar">
<el-avatar
:size="32"
style="
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
"
>
<component :is="useRenderIcon('ri/robot-line')" />
</el-avatar>
</div>
<div class="message-content">
<div
class="message-text"
v-html="streamingMessage.replace(/\n/g, '<br>')"
/>
<div v-if="streamingReferences.length" class="referenced-docs">
<div class="ref-title">
<component :is="useRenderIcon(DocumentIcon)" />
参考文档
</div>
<div
v-for="doc in streamingReferences"
:key="doc.id"
class="ref-doc"
>
<span class="doc-title">{{ doc.title }}</span>
<el-tag size="small" type="info"
>相关度: {{ (doc.score * 100).toFixed(0) }}%</el-tag
>
</div>
</div>
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="input-area">
<el-input
v-model="inputMessage"
type="textarea"
:rows="3"
placeholder="输入您的问题..."
:disabled="sending"
@keydown.enter.ctrl="sendMessage"
/>
<div class="input-actions">
<span class="tip">Ctrl + Enter 发送</span>
<el-button
type="primary"
:icon="useRenderIcon(SendIcon)"
:loading="sending"
:disabled="!inputMessage.trim()"
@click="sendMessage"
>
发送
</el-button>
</div>
</div>
</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);
}
}
}
}
.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%;
}
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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,29 +274,23 @@ 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;
// 处理字符串类型的统计数据
const data = res.data as any;
statistics.value = {
totalCount: parseInt(data.totalCount) || 0,
identifiedCount: parseInt(data.identifiedCount) || 0,
@@ -318,25 +308,21 @@ async function loadStatistics() {
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,
.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" });
} 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);
});

View File

@@ -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"