# Conflicts:
#	src/service/api/product.ts
#	src/service/api/project.ts
#	src/typings/api/project.d.ts
This commit is contained in:
dk
2026-05-13 21:20:59 +08:00
59 changed files with 8046 additions and 919 deletions

View File

@@ -14,6 +14,7 @@ interface BackendLoginToken {
interface BackendUserInfoDTO {
userId: string | number;
userName?: string | null;
nickname?: string | null;
roles?: string[] | null;
buttons?: string[] | null;
}
@@ -32,6 +33,7 @@ function mapUserInfo(data: BackendUserInfoDTO): Api.Auth.UserInfo {
return {
userId: String(data.userId ?? ''),
userName: data.userName ?? '',
nickname: data.nickname ?? '',
roles: data.roles ?? [],
buttons: data.buttons ?? []
};

View File

@@ -3,6 +3,13 @@ import { request } from '../request';
const FILE_PREFIX = `${SYSTEM_SERVICE_PREFIX}/file`;
export interface UploadFileResult {
/** infra_file.id 的字符串形式(避免 Long 精度丢失) */
id: string;
/** 文件访问 URL私有桶带签名、公开桶裸 URL */
url: string;
}
/** 上传文件(模式一:后端中转) */
export function uploadFile(file: File, directory?: string) {
const formData = new FormData();
@@ -11,9 +18,38 @@ export function uploadFile(file: File, directory?: string) {
formData.append('directory', directory);
}
return request<string>({
return request<UploadFileResult>({
url: `${FILE_PREFIX}/upload`,
method: 'post',
data: formData
});
}
/**
* 删除文件
*
* 业务表单"取消/关闭/标记删除"场景调用本接口清理孤儿文件。
* 删除已不存在的文件(后端返回错误码 `1001003001`)应由调用方视为成功并吞掉。
*/
export function deleteFile(id: string) {
return request<boolean>({
url: `${FILE_PREFIX}/delete`,
method: 'delete',
params: { id }
});
}
/**
* 下载文件(流)
*
* 走后端代理接口 `/system/file/download?id=xxx`,由后端读取对象存储并以字节流返回。
* 私有桶下不要直接打开 `infra_file.url`,签名地址会过期。
*/
export function downloadFile(id: string) {
return request<Blob, 'blob'>({
url: `${FILE_PREFIX}/download`,
method: 'get',
params: { id },
responseType: 'blob'
});
}

View File

@@ -139,6 +139,18 @@ export async function fetchCreateProduct(data: Api.Product.SaveProductParams) {
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 创建产品(含初始团队,原子接口) */
export async function fetchCreateProductWithTeam(data: Api.Product.CreateProductWithTeamParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/create-with-team`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 更新产品 */
export function fetchUpdateProduct(data: Api.Product.UpdateProductParams) {
return request<boolean>({

View File

@@ -36,14 +36,14 @@ export type ProjectExecutionResponse = Omit<
progressRate?: number | null;
};
export type ExecutionMemberResponse = Omit<Api.Project.ExecutionMember, 'id' | 'executionId' | 'userId'> & {
export type ExecutionAssigneeResponse = Omit<Api.Project.ExecutionAssignee, 'id' | 'executionId' | 'userId'> & {
id: StringIdResponse;
executionId: StringIdResponse;
userId: StringIdResponse;
};
export type ExecutionMemberLogResponse = Omit<
Api.Project.ExecutionMemberLog,
export type ExecutionAssigneeLogResponse = Omit<
Api.Project.ExecutionAssigneeLog,
'id' | 'executionId' | 'userId' | 'operatorUserId'
> & {
id: StringIdResponse;
@@ -52,6 +52,55 @@ export type ExecutionMemberLogResponse = Omit<
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'
@@ -65,6 +114,8 @@ export type ProjectTaskResponse = Omit<
| 'actualStartDate'
| 'actualEndDate'
| 'progressRate'
| 'assignees'
| 'attachments'
> & {
id: StringIdResponse;
projectId: StringIdResponse;
@@ -77,6 +128,16 @@ export type ProjectTaskResponse = Omit<
actualStartDate?: ProjectLocalDateValue;
actualEndDate?: ProjectLocalDateValue;
progressRate?: number | null;
assignees?: TaskAssigneeRefResponse[] | null;
attachments?: AttachmentItemResponse[] | null;
totalSpentHours?: number | null;
};
export type TaskWorklogResponse = Omit<Api.Project.TaskWorklog, 'id' | 'taskId' | 'userId' | 'attachments'> & {
id: StringIdResponse;
taskId: StringIdResponse;
userId: StringIdResponse;
attachments?: AttachmentItemResponse[] | null;
};
export interface ProjectMemberResponse {
@@ -194,7 +255,7 @@ export function normalizeProjectExecution(response: ProjectExecutionResponse): A
};
}
export function normalizeExecutionMember(response: ExecutionMemberResponse): Api.Project.ExecutionMember {
export function normalizeExecutionAssignee(response: ExecutionAssigneeResponse): Api.Project.ExecutionAssignee {
return {
...response,
id: normalizeStringId(response.id),
@@ -207,7 +268,9 @@ export function normalizeExecutionMember(response: ExecutionMemberResponse): Api
};
}
export function normalizeExecutionMemberLog(response: ExecutionMemberLogResponse): Api.Project.ExecutionMemberLog {
export function normalizeExecutionAssigneeLog(
response: ExecutionAssigneeLogResponse
): Api.Project.ExecutionAssigneeLog {
return {
...response,
id: normalizeStringId(response.id),
@@ -239,6 +302,49 @@ export function normalizeProjectTask(response: ProjectTaskResponse): Api.Project
actualStartDate: normalizeProjectLocalDate(response.actualStartDate),
actualEndDate: normalizeProjectLocalDate(response.actualEndDate),
taskDesc: response.taskDesc ?? null,
lastStatusReason: response.lastStatusReason ?? 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
};
}
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
};
}

View File

@@ -8,19 +8,25 @@ import {
safeJsonRequestConfig
} from './shared';
import {
type ExecutionMemberLogResponse,
type ExecutionMemberResponse,
type ExecutionAssigneeLogResponse,
type ExecutionAssigneeResponse,
type ProjectExecutionResponse,
type ProjectLocalDateValue,
type ProjectMemberResponse,
type ProjectTaskResponse,
type TaskAssigneeFromApiResponse,
type TaskAssigneeLogResponse,
type TaskWorklogResponse,
getProjectLifecycleActions,
normalizeExecutionMember,
normalizeExecutionMemberLog,
normalizeExecutionAssignee,
normalizeExecutionAssigneeLog,
normalizeProjectExecution,
normalizeProjectLocalDate,
normalizeProjectMember,
normalizeProjectTask
normalizeProjectTask,
normalizeTaskAssignee,
normalizeTaskAssigneeLog,
normalizeTaskWorklog
} from './project-shared';
const PROJECT_PREFIX = `${WEB_SERVICE_PREFIX}/project/project`;
@@ -159,6 +165,18 @@ export async function fetchCreateProject(data: Api.Project.SaveProjectParams) {
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 创建项目(含初始团队,原子接口) */
export async function fetchCreateProjectWithTeam(data: Api.Project.CreateProjectWithTeamParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${PROJECT_PREFIX}/create-with-team`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 更新项目 */
export function fetchUpdateProject(data: Api.Project.UpdateProjectParams) {
return request<boolean>({
@@ -340,7 +358,7 @@ export async function fetchGetProjectExecution(projectId: string, executionId: s
}
/** 创建项目执行 */
export async function fetchCreateProjectExecution(projectId: string, data: Api.Project.SaveProjectExecutionParams) {
export async function fetchCreateProjectExecution(projectId: string, data: Api.Project.CreateProjectExecutionParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: getExecutionPrefix(projectId),
@@ -355,7 +373,7 @@ export async function fetchCreateProjectExecution(projectId: string, data: Api.P
export function fetchUpdateProjectExecution(
projectId: string,
executionId: string,
data: Api.Project.SaveProjectExecutionParams
data: Api.Project.UpdateProjectExecutionParams
) {
return request<boolean>({
...safeJsonRequestConfig,
@@ -379,6 +397,20 @@ export function fetchChangeProjectExecutionOwner(
});
}
/** 删除项目执行 */
export function fetchDeleteProjectExecution(
projectId: string,
executionId: string,
data: Api.Project.DeleteProjectExecutionParams
) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${getExecutionPrefix(projectId)}/${executionId}`,
method: 'delete',
data
});
}
/** 变更项目执行状态 */
export function fetchChangeProjectExecutionStatus(
projectId: string,
@@ -393,28 +425,28 @@ export function fetchChangeProjectExecutionStatus(
});
}
/** 获取项目执行成员 */
export async function fetchGetProjectExecutionMembers(projectId: string, executionId: string) {
const result = await request<ExecutionMemberResponse[]>({
/** 获取项目执行协办人 */
export async function fetchGetProjectExecutionAssignees(projectId: string, executionId: string) {
const result = await request<ExecutionAssigneeResponse[]>({
...safeJsonRequestConfig,
url: `${getExecutionPrefix(projectId)}/${executionId}/members`,
url: `${getExecutionPrefix(projectId)}/${executionId}/assignees`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<ExecutionMemberResponse[]>, data =>
data.map(normalizeExecutionMember)
return mapServiceResult(result as ServiceRequestResult<ExecutionAssigneeResponse[]>, data =>
data.map(normalizeExecutionAssignee)
);
}
/** 创建项目执行成员 */
export async function fetchCreateProjectExecutionMember(
/** 创建项目执行协办人 */
export async function fetchCreateProjectExecutionAssignee(
projectId: string,
executionId: string,
data: Api.Project.CreateExecutionMemberParams
data: Api.Project.CreateExecutionAssigneeParams
) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${getExecutionPrefix(projectId)}/${executionId}/members`,
url: `${getExecutionPrefix(projectId)}/${executionId}/assignees`,
method: 'post',
data
});
@@ -422,37 +454,40 @@ export async function fetchCreateProjectExecutionMember(
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 移除项目执行成员 */
export function fetchInactiveProjectExecutionMember(
/** 移除项目执行协办人 */
export function fetchInactiveProjectExecutionAssignee(
projectId: string,
executionId: string,
payload: { memberId: string; data: Api.Project.InactiveExecutionMemberParams }
payload: { assigneeId: string; data: Api.Project.InactiveExecutionAssigneeParams }
) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${getExecutionPrefix(projectId)}/${executionId}/members/${payload.memberId}/inactive`,
url: `${getExecutionPrefix(projectId)}/${executionId}/assignees/${payload.assigneeId}/inactive`,
method: 'post',
data: payload.data
});
}
/** 获取项目执行成员变更历史分页 */
export async function fetchGetProjectExecutionMemberLogPage(
/** 获取项目执行协办人变更历史分页 */
export async function fetchGetProjectExecutionAssigneeLogPage(
projectId: string,
executionId: string,
params?: Api.Project.ExecutionMemberLogSearchParams
params?: Api.Project.ExecutionAssigneeLogSearchParams
) {
const result = await request<Api.Project.PageResult<ExecutionMemberLogResponse>>({
const result = await request<Api.Project.PageResult<ExecutionAssigneeLogResponse>>({
...safeJsonRequestConfig,
url: `${getExecutionPrefix(projectId)}/${executionId}/member-logs`,
url: `${getExecutionPrefix(projectId)}/${executionId}/assignee-logs`,
method: 'get',
params
});
return mapServiceResult(result as ServiceRequestResult<Api.Project.PageResult<ExecutionMemberLogResponse>>, data => ({
...data,
list: data.list.map(normalizeExecutionMemberLog)
}));
return mapServiceResult(
result as ServiceRequestResult<Api.Project.PageResult<ExecutionAssigneeLogResponse>>,
data => ({
...data,
list: data.list.map(normalizeExecutionAssigneeLog)
})
);
}
/** 获取项目任务分页 */
@@ -529,6 +564,22 @@ export function fetchUpdateProjectTask(
});
}
/** 删除项目任务 */
// eslint-disable-next-line max-params
export function fetchDeleteProjectTask(
projectId: string,
executionId: string,
taskId: string,
data: Api.Project.DeleteProjectTaskParams
) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${getTaskPrefix(projectId, executionId)}/${taskId}`,
method: 'delete',
data
});
}
/** 变更项目任务状态 */
export function fetchChangeProjectTaskStatus(
projectId: string,
@@ -543,6 +594,151 @@ export function fetchChangeProjectTaskStatus(
});
}
type TaskWorklogPageResponse = Api.Project.PageResult<TaskWorklogResponse>;
function getWorklogPrefix(projectId: string, executionId: string, taskId: string) {
return `${getTaskPrefix(projectId, executionId)}/${taskId}/worklogs`;
}
/** 获取任务工时分页 */
// eslint-disable-next-line max-params
export async function fetchGetProjectTaskWorklogPage(
projectId: string,
executionId: string,
taskId: string,
params?: Api.Project.TaskWorklogSearchParams
) {
const result = await request<TaskWorklogPageResponse>({
...safeJsonRequestConfig,
url: getWorklogPrefix(projectId, executionId, taskId),
method: 'get',
params
});
return mapServiceResult(result as ServiceRequestResult<TaskWorklogPageResponse>, data => ({
...data,
list: data.list.map(normalizeTaskWorklog)
}));
}
/** 新增任务工时 */
// eslint-disable-next-line max-params
export async function fetchCreateProjectTaskWorklog(
projectId: string,
executionId: string,
taskId: string,
data: Api.Project.SaveTaskWorklogParams
) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: getWorklogPrefix(projectId, executionId, taskId),
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 修改任务工时 */
// eslint-disable-next-line max-params
export function fetchUpdateProjectTaskWorklog(
projectId: string,
executionId: string,
taskId: string,
payload: { worklogId: string; data: Api.Project.SaveTaskWorklogParams }
) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${getWorklogPrefix(projectId, executionId, taskId)}/${payload.worklogId}`,
method: 'put',
data: payload.data
});
}
/** 删除任务工时 */
// eslint-disable-next-line max-params
export function fetchDeleteProjectTaskWorklog(
projectId: string,
executionId: string,
taskId: string,
worklogId: string
) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${getWorklogPrefix(projectId, executionId, taskId)}/${worklogId}`,
method: 'delete'
});
}
/** 5.6 获取任务协办人列表(仅当前活跃) */
export async function fetchGetProjectTaskAssignees(projectId: string, executionId: string, taskId: string) {
const result = await request<TaskAssigneeFromApiResponse[]>({
...safeJsonRequestConfig,
url: `${getTaskPrefix(projectId, executionId)}/${taskId}/assignees`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<TaskAssigneeFromApiResponse[]>, data =>
data.map(normalizeTaskAssignee)
);
}
/** 5.7 加入任务协办人 */
// eslint-disable-next-line max-params
export async function fetchCreateProjectTaskAssignee(
projectId: string,
executionId: string,
taskId: string,
data: Api.Project.CreateTaskAssigneeParams
) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${getTaskPrefix(projectId, executionId)}/${taskId}/assignees`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 5.8 退出任务协办人 */
// eslint-disable-next-line max-params
export function fetchInactiveProjectTaskAssignee(
projectId: string,
executionId: string,
taskId: string,
assigneeId: string,
data: Api.Project.InactiveTaskAssigneeParams
) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${getTaskPrefix(projectId, executionId)}/${taskId}/assignees/${assigneeId}/inactive`,
method: 'post',
data
});
}
/** 5.9 任务协办人变更历史分页 */
// eslint-disable-next-line max-params
export async function fetchGetProjectTaskAssigneeLogPage(
projectId: string,
executionId: string,
taskId: string,
params?: Api.Project.TaskAssigneeLogSearchParams
) {
const result = await request<Api.Project.PageResult<TaskAssigneeLogResponse>>({
...safeJsonRequestConfig,
url: `${getTaskPrefix(projectId, executionId)}/${taskId}/assignee-logs`,
method: 'get',
params
});
return mapServiceResult(result as ServiceRequestResult<Api.Project.PageResult<TaskAssigneeLogResponse>>, data => ({
...data,
list: data.list.map(normalizeTaskAssigneeLog)
}));
}
// ========== 项目需求 API ==========
const PROJECT_REQUIREMENT_PREFIX = `${WEB_SERVICE_PREFIX}/project/project/requirement`;