Files
cn-rdms-web/src/service/api/project-shared.ts
hongawen e9214137c1 refactor(project): 重构项目执行模块组件结构和数据管理
- 移除 execution-list-panel.vue 组件并将功能整合到执行区域
- 新增 execution-section.vue 组件替代原有的列表面板
- 将 task-workspace.vue 重命名为 task-workspace-comp.vue 并更新引用
- 引入 useTaskViewContext 组合式 API 进行任务视图上下文管理
- 添加跨执行任务状态统计接口调用和数据处理逻辑
- 重构执行状态筛选和任务创建权限判断逻辑
- 更新执行选择、搜索和重置功能的事件处理方式
- 调整页面布局结构,优化左右分栏的内容组织方式
- 完善执行详情获取和状态操作的业务流程
- 优化执行分配和状态变更的异步处理机制
2026-05-23 14:22:58 +08:00

398 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { normalizeNullableStringId, normalizeStringId } from './shared';
type ProjectStatusCode = Api.Project.ProjectStatusCode;
type ProjectStatusActionCode = Exclude<Api.Project.ProjectStatusActionCode, 'auto_start'>;
type StringIdResponse = string | number;
export type ProjectLocalDateValue = string | number[] | null;
export type LifecycleActionResponse<ActionCode extends string> = Partial<Api.Project.LifecycleAction<ActionCode>> & {
actionCode: ActionCode;
};
export type ProjectExecutionResponse = Omit<
Api.Project.ProjectExecution,
| 'id'
| 'projectId'
| 'projectRequirementId'
| 'ownerId'
| 'availableActions'
| 'plannedStartDate'
| 'plannedEndDate'
| 'actualStartDate'
| 'actualEndDate'
| 'progressRate'
| 'priority'
| 'priorityName'
> & {
id: StringIdResponse;
projectId: StringIdResponse;
projectRequirementId?: StringIdResponse | null;
ownerId: StringIdResponse;
availableActions?: LifecycleActionResponse<Api.Project.ProjectExecutionActionCode>[] | null;
plannedStartDate?: ProjectLocalDateValue;
plannedEndDate?: ProjectLocalDateValue;
actualStartDate?: ProjectLocalDateValue;
actualEndDate?: ProjectLocalDateValue;
progressRate?: number | null;
priority?: string | number | null;
priorityName?: string | null;
};
export type ExecutionAssigneeResponse = Omit<Api.Project.ExecutionAssignee, 'id' | 'executionId' | 'userId'> & {
id: StringIdResponse;
executionId: StringIdResponse;
userId: StringIdResponse;
};
export type ExecutionAssigneeLogResponse = Omit<
Api.Project.ExecutionAssigneeLog,
'id' | 'executionId' | 'userId' | 'operatorUserId'
> & {
id: StringIdResponse;
executionId: StringIdResponse;
userId: StringIdResponse;
operatorUserId: StringIdResponse;
};
type TaskAssigneeRefResponse = Omit<Api.Project.TaskAssigneeRef, 'id' | 'userId'> & {
id: StringIdResponse;
userId: StringIdResponse;
};
/**
* 后端 attachments 项的兼容形态:历史/当前响应字段名是 `id`,前端类型统一用 `fileId`。
* normalizeAttachments 负责把两者归一成 `fileId`。
*/
type AttachmentItemResponse = Omit<Api.Project.AttachmentItem, 'fileId'> & {
fileId?: StringIdResponse;
id?: StringIdResponse;
};
function normalizeAttachments(list?: AttachmentItemResponse[] | null): Api.Project.AttachmentItem[] | null {
if (!list) {
return null;
}
return list.map(item => {
const rawId = item.fileId ?? item.id;
return {
...item,
fileId: rawId === null || rawId === undefined ? '' : String(rawId)
};
});
}
/**
* 5.6 单独接口返的协办人字段(与 5.3 嵌入字段命名口径不一致:返 userNickname 而非 nickname
* 经 normalizeTaskAssignee 归一化后对外统一为 Api.Project.TaskAssigneeRef。
*/
export type TaskAssigneeFromApiResponse = {
id: StringIdResponse;
taskId: StringIdResponse;
userId: StringIdResponse;
userNickname?: string | null;
joinedAt?: string | null;
};
export type TaskAssigneeLogResponse = Omit<
Api.Project.TaskAssigneeLog,
'id' | 'taskId' | 'userId' | 'operatorUserId'
> & {
id: StringIdResponse;
taskId: StringIdResponse;
userId: StringIdResponse;
operatorUserId: StringIdResponse;
};
export type ProjectTaskResponse = Omit<
Api.Project.ProjectTask,
| 'id'
| 'projectId'
| 'executionId'
| 'parentTaskId'
| 'ownerId'
| 'executionOwnerId'
| 'parentTaskOwnerId'
| 'availableActions'
| 'plannedStartDate'
| 'plannedEndDate'
| 'actualStartDate'
| 'actualEndDate'
| 'progressRate'
| 'assignees'
| 'attachments'
| 'priority'
| 'priorityName'
> & {
id: StringIdResponse;
projectId: StringIdResponse;
executionId: StringIdResponse;
executionName?: string | null;
executionStatusCode?: Api.Project.ProjectExecutionStatusCode | null;
parentTaskId?: StringIdResponse | null;
ownerId: StringIdResponse;
executionOwnerId?: StringIdResponse | null;
parentTaskOwnerId?: StringIdResponse | null;
availableActions?: LifecycleActionResponse<Api.Project.ProjectTaskActionCode>[] | null;
plannedStartDate?: ProjectLocalDateValue;
plannedEndDate?: ProjectLocalDateValue;
actualStartDate?: ProjectLocalDateValue;
actualEndDate?: ProjectLocalDateValue;
progressRate?: number | null;
assignees?: TaskAssigneeRefResponse[] | null;
attachments?: AttachmentItemResponse[] | null;
totalSpentHours?: number | null;
priority?: string | number | null;
priorityName?: string | null;
};
export type TaskWorklogResponse = Omit<
Api.Project.TaskWorklog,
'id' | 'taskId' | 'userId' | 'difficulty' | 'attachments' | 'startDate' | 'endDate'
> & {
id: StringIdResponse;
taskId: StringIdResponse;
userId: StringIdResponse;
difficulty?: string | null;
attachments?: AttachmentItemResponse[] | null;
startDate?: ProjectLocalDateValue;
endDate?: ProjectLocalDateValue;
};
export interface ProjectMemberResponse {
id: string | number;
userId: string | number;
userNickname: string;
roleId: string | number;
roleName: string;
roleCode: string;
managerFlag: boolean;
status: 0 | 1;
joinedTime: string;
leftTime?: string | null;
remark?: string | null;
}
const projectLifecycleActionNameMap: Record<ProjectStatusActionCode, string> = {
pause: '暂停项目',
resume: '恢复项目',
complete: '完成项目',
cancel: '取消项目',
reopen: '重新开启',
archive: '归档项目'
};
const projectLifecycleActionReasonRequiredMap: Record<ProjectStatusActionCode, boolean> = {
pause: true,
resume: false,
complete: true,
cancel: true,
reopen: true,
archive: false
};
const projectLifecycleActionMap: Record<ProjectStatusCode, ProjectStatusActionCode[]> = {
pending: ['cancel'],
active: ['pause', 'complete', 'cancel'],
paused: ['resume', 'cancel'],
completed: ['reopen', 'archive'],
cancelled: [],
archived: []
};
export function getProjectLifecycleActions(statusCode: ProjectStatusCode): Api.Project.ProjectLifecycleAction[] {
return projectLifecycleActionMap[statusCode].map(actionCode => ({
actionCode,
actionName: projectLifecycleActionNameMap[actionCode],
needReason: projectLifecycleActionReasonRequiredMap[actionCode]
}));
}
export function normalizeProjectLocalDate(value: ProjectLocalDateValue | undefined) {
if (value === null || value === undefined || value === '') {
return null;
}
if (Array.isArray(value)) {
const [year, month, day] = value;
if (!year || !month || !day) {
return null;
}
return [year, month, day].map(item => String(item).padStart(2, '0')).join('-');
}
return String(value);
}
export function normalizeLifecycleActions<ActionCode extends string>(
actions: LifecycleActionResponse<ActionCode>[] | null | undefined
): Api.Project.LifecycleAction<ActionCode>[] {
return (actions ?? []).map(action => ({
actionCode: action.actionCode,
actionName: action.actionName ?? '',
needReason: Boolean(action.needReason)
}));
}
export function normalizeProjectMember(response: ProjectMemberResponse): Api.Project.ProjectMember {
return {
id: normalizeStringId(response.id),
userId: normalizeStringId(response.userId),
userNickname: response.userNickname || '',
roleId: normalizeStringId(response.roleId),
roleName: response.roleName || '',
roleCode: response.roleCode || '',
managerFlag: Boolean(response.managerFlag),
status: response.status,
joinedTime: response.joinedTime,
leftTime: response.leftTime ?? null,
remark: response.remark ?? null
};
}
function normalizePriority(value: string | number | null | undefined): string {
if (value === null || value === undefined || value === '') {
return '1';
}
return String(value);
}
export function normalizeProjectExecution(response: ProjectExecutionResponse): Api.Project.ProjectExecution {
return {
...response,
id: normalizeStringId(response.id),
projectId: normalizeStringId(response.projectId),
projectRequirementId: normalizeNullableStringId(response.projectRequirementId),
projectRequirementName: response.projectRequirementName ?? null,
projectRequirementStatusCode: response.projectRequirementStatusCode ?? null,
ownerId: normalizeStringId(response.ownerId),
ownerNickname: response.ownerNickname ?? null,
statusName: response.statusName ?? null,
terminal: Boolean(response.terminal),
allowEdit: Boolean(response.allowEdit),
availableActions: normalizeLifecycleActions(response.availableActions),
plannedStartDate: normalizeProjectLocalDate(response.plannedStartDate),
plannedEndDate: normalizeProjectLocalDate(response.plannedEndDate),
actualStartDate: normalizeProjectLocalDate(response.actualStartDate),
actualEndDate: normalizeProjectLocalDate(response.actualEndDate),
progressRate: typeof response.progressRate === 'number' ? response.progressRate : 0,
priority: normalizePriority(response.priority),
priorityName: response.priorityName ?? null,
executionDesc: response.executionDesc ?? null,
lastStatusReason: response.lastStatusReason ?? null
};
}
export function normalizeExecutionAssignee(response: ExecutionAssigneeResponse): Api.Project.ExecutionAssignee {
return {
...response,
id: normalizeStringId(response.id),
executionId: normalizeStringId(response.executionId),
userId: normalizeStringId(response.userId),
userNickname: response.userNickname ?? null,
joinedAt: response.joinedAt ?? null,
removedAt: response.removedAt ?? null,
removedReason: response.removedReason ?? null
};
}
export function normalizeExecutionAssigneeLog(
response: ExecutionAssigneeLogResponse
): Api.Project.ExecutionAssigneeLog {
return {
...response,
id: normalizeStringId(response.id),
executionId: normalizeStringId(response.executionId),
userId: normalizeStringId(response.userId),
operatorUserId: normalizeStringId(response.operatorUserId),
userNicknameSnapshot: response.userNicknameSnapshot ?? null,
operatorNicknameSnapshot: response.operatorNicknameSnapshot ?? null,
reason: response.reason ?? null
};
}
export function normalizeProjectTask(response: ProjectTaskResponse): Api.Project.ProjectTask {
return {
...response,
id: normalizeStringId(response.id),
projectId: normalizeStringId(response.projectId),
executionId: normalizeStringId(response.executionId),
executionName: response.executionName ?? null,
executionStatusCode: response.executionStatusCode ?? null,
parentTaskId: normalizeNullableStringId(response.parentTaskId),
projectRequirementId: normalizeNullableStringId(response.projectRequirementId),
projectRequirementName: response.projectRequirementName ?? null,
projectRequirementStatusCode: response.projectRequirementStatusCode ?? null,
type: response.type ?? '',
ownerId: normalizeStringId(response.ownerId),
ownerNickname: response.ownerNickname ?? null,
executionOwnerId: normalizeNullableStringId(response.executionOwnerId),
parentTaskOwnerId: normalizeNullableStringId(response.parentTaskOwnerId),
statusName: response.statusName ?? null,
terminal: Boolean(response.terminal),
allowEdit: Boolean(response.allowEdit),
availableActions: normalizeLifecycleActions(response.availableActions),
progressRate: typeof response.progressRate === 'number' ? response.progressRate : 0,
plannedStartDate: normalizeProjectLocalDate(response.plannedStartDate),
plannedEndDate: normalizeProjectLocalDate(response.plannedEndDate),
actualStartDate: normalizeProjectLocalDate(response.actualStartDate),
actualEndDate: normalizeProjectLocalDate(response.actualEndDate),
priority: normalizePriority(response.priority),
priorityName: response.priorityName ?? null,
taskDesc: response.taskDesc ?? null,
lastStatusReason: response.lastStatusReason ?? null,
assignees:
response.assignees?.map(item => ({
id: normalizeStringId(item.id),
userId: normalizeStringId(item.userId),
nickname: item.nickname ?? ''
})) ?? null,
attachments: normalizeAttachments(response.attachments),
totalSpentHours: response.totalSpentHours ?? null
};
}
export function normalizeTaskWorklog(response: TaskWorklogResponse): Api.Project.TaskWorklog {
return {
...response,
id: normalizeStringId(response.id),
taskId: normalizeStringId(response.taskId),
userId: normalizeStringId(response.userId),
userNickname: response.userNickname ?? null,
workContent: response.workContent ?? null,
attachments: normalizeAttachments(response.attachments),
progressRate: typeof response.progressRate === 'number' ? response.progressRate : 0,
// 后端 LocalDate 默认序列化为 [year, month, day] 数组,必须归一为 'YYYY-MM-DD' 字符串供 ElDatePicker 使用
startDate: normalizeProjectLocalDate(response.startDate) ?? '',
endDate: normalizeProjectLocalDate(response.endDate) ?? '',
// 历史记录或异常缺失时兜底为字典默认档位 "2"
difficulty: response.difficulty ?? '2',
difficultyName: response.difficultyName ?? null
};
}
export function normalizeTaskAssignee(response: TaskAssigneeFromApiResponse): Api.Project.TaskAssigneeRef {
return {
id: normalizeStringId(response.id),
userId: normalizeStringId(response.userId),
nickname: response.userNickname ?? '',
joinedAt: response.joinedAt ?? null
};
}
export function normalizeTaskAssigneeLog(response: TaskAssigneeLogResponse): Api.Project.TaskAssigneeLog {
return {
...response,
id: normalizeStringId(response.id),
taskId: normalizeStringId(response.taskId),
userId: normalizeStringId(response.userId),
operatorUserId: normalizeStringId(response.operatorUserId),
userNicknameSnapshot: response.userNicknameSnapshot ?? null,
operatorNicknameSnapshot: response.operatorNicknameSnapshot ?? null,
reason: response.reason ?? null
};
}