215 lines
6.3 KiB
TypeScript
215 lines
6.3 KiB
TypeScript
import dayjs from 'dayjs';
|
|
|
|
export interface ProjectManagerMemberLike {
|
|
roleId: string;
|
|
}
|
|
|
|
interface ProjectTeamManageContext {
|
|
buttonCodes: readonly string[];
|
|
}
|
|
|
|
interface ProjectLifecycleStatusSummary {
|
|
tone: 'emerald' | 'amber' | 'slate' | 'rose';
|
|
caption: string;
|
|
description: string;
|
|
}
|
|
|
|
interface ProjectLifecycleActionCardMeta {
|
|
tone: 'emerald' | 'amber' | 'slate' | 'rose';
|
|
description: string;
|
|
}
|
|
|
|
export const projectSettingSectionKeys = ['base-info', 'team', 'lifecycle', 'danger'] as const;
|
|
|
|
export type ProjectSettingSectionKey = (typeof projectSettingSectionKeys)[number];
|
|
|
|
const projectBaseInfoReadonlyMessageMap: Partial<Record<Api.Project.ProjectStatusCode, string>> = {
|
|
paused: '当前项目已暂停,基础信息仅支持查看,不可编辑。',
|
|
completed: '当前项目已完成,基础信息仅支持查看,不可编辑。',
|
|
cancelled: '当前项目已取消,基础信息仅支持查看,不可编辑。',
|
|
archived: '当前项目已归档,基础信息仅支持查看,不可编辑。'
|
|
};
|
|
|
|
const projectLifecycleStatusSummaryMap: Record<Api.Project.ProjectStatusCode, ProjectLifecycleStatusSummary> = {
|
|
pending: {
|
|
tone: 'slate',
|
|
caption: '项目待开始',
|
|
description: '项目已创建,尚未进入真实推进阶段。'
|
|
},
|
|
active: {
|
|
tone: 'emerald',
|
|
caption: '项目推进中',
|
|
description: '当前可以暂停、完成或取消项目。'
|
|
},
|
|
paused: {
|
|
tone: 'amber',
|
|
caption: '项目已暂停',
|
|
description: '条件恢复后可重新推进,也可取消项目。'
|
|
},
|
|
completed: {
|
|
tone: 'emerald',
|
|
caption: '项目已完成',
|
|
description: '项目已完成,可按业务需要重新开启或归档。'
|
|
},
|
|
cancelled: {
|
|
tone: 'rose',
|
|
caption: '项目已取消',
|
|
description: '项目已取消,当前暂无可执行生命周期动作。'
|
|
},
|
|
archived: {
|
|
tone: 'slate',
|
|
caption: '项目已归档',
|
|
description: '项目已收口归档,当前暂无可执行生命周期动作。'
|
|
}
|
|
};
|
|
|
|
const projectLifecycleActionCardMetaMap: Record<
|
|
Exclude<Api.Project.ProjectStatusActionCode, 'auto_start'>,
|
|
ProjectLifecycleActionCardMeta
|
|
> = {
|
|
pause: {
|
|
tone: 'amber',
|
|
description: '暂停当前项目,暂停期间不允许普通编辑和成员维护。'
|
|
},
|
|
resume: {
|
|
tone: 'emerald',
|
|
description: '恢复项目推进,重新进入进行中状态。'
|
|
},
|
|
complete: {
|
|
tone: 'emerald',
|
|
description: '确认项目完成,结束当前项目推进。'
|
|
},
|
|
cancel: {
|
|
tone: 'rose',
|
|
description: '取消当前项目,需要填写取消原因。'
|
|
},
|
|
reopen: {
|
|
tone: 'emerald',
|
|
description: '重新开启项目,恢复到进行中状态。'
|
|
},
|
|
archive: {
|
|
tone: 'slate',
|
|
description: '归档已完成项目,保留历史记录。'
|
|
}
|
|
};
|
|
|
|
const projectTeamTableHeaderHeight = 40;
|
|
const projectTeamTableRowHeight = 40;
|
|
|
|
export function shouldRequireProjectManagerHandover(
|
|
targetRoleId: string,
|
|
currentManager: ProjectManagerMemberLike | null | undefined
|
|
) {
|
|
if (!currentManager?.roleId) {
|
|
return false;
|
|
}
|
|
|
|
return targetRoleId === currentManager.roleId;
|
|
}
|
|
|
|
export function getPreviousProjectManagerRoleOptions(
|
|
roleOptions: Api.SystemManage.RoleSimple[],
|
|
managerRoleId: string
|
|
) {
|
|
return roleOptions.filter(role => role.id !== managerRoleId);
|
|
}
|
|
|
|
export function getProjectSettingSectionKeys() {
|
|
return [...projectSettingSectionKeys];
|
|
}
|
|
|
|
export function isProjectBaseInfoEditable(status: Api.Project.ProjectStatusCode | null | undefined) {
|
|
return status === 'pending' || status === 'active';
|
|
}
|
|
|
|
export function resolveVisibleProjectSettingSections(
|
|
sectionKeys: readonly ProjectSettingSectionKey[],
|
|
_buttonCodes: readonly string[]
|
|
) {
|
|
return [...sectionKeys];
|
|
}
|
|
|
|
export function resolveVisibleProjectSettingSectionKey(
|
|
currentKey: ProjectSettingSectionKey | string | null | undefined,
|
|
visibleSectionKeys: readonly ProjectSettingSectionKey[]
|
|
) {
|
|
if (!visibleSectionKeys.length) {
|
|
return 'base-info' satisfies ProjectSettingSectionKey;
|
|
}
|
|
|
|
if (currentKey && visibleSectionKeys.includes(currentKey as ProjectSettingSectionKey)) {
|
|
return currentKey as ProjectSettingSectionKey;
|
|
}
|
|
|
|
return visibleSectionKeys[0];
|
|
}
|
|
|
|
export function getProjectBaseInfoReadonlyMessage(status: Api.Project.ProjectStatusCode | null | undefined) {
|
|
if (!status || isProjectBaseInfoEditable(status)) {
|
|
return '';
|
|
}
|
|
|
|
return projectBaseInfoReadonlyMessageMap[status] || '当前项目状态不允许编辑基础信息。';
|
|
}
|
|
|
|
export function getProjectLifecycleStatusSummary(status: Api.Project.ProjectStatusCode) {
|
|
return projectLifecycleStatusSummaryMap[status];
|
|
}
|
|
|
|
export function getProjectLifecycleActionCardMeta(
|
|
actionCode: Exclude<Api.Project.ProjectStatusActionCode, 'auto_start'>
|
|
) {
|
|
return projectLifecycleActionCardMetaMap[actionCode];
|
|
}
|
|
|
|
export function formatProjectMemberDate(value: string | number | null | undefined) {
|
|
if (value === null || value === undefined || value === '') {
|
|
return '--';
|
|
}
|
|
|
|
const normalizedValue = typeof value === 'string' && /^\d+$/.test(value) ? Number(value) : value;
|
|
const parsedDate = dayjs(normalizedValue);
|
|
|
|
if (!parsedDate.isValid()) {
|
|
return String(value);
|
|
}
|
|
|
|
return parsedDate.format('YYYY-MM-DD');
|
|
}
|
|
|
|
export function filterProjectMembers(
|
|
members: readonly Api.Project.ProjectMember[],
|
|
filters: {
|
|
keyword?: string | null | undefined;
|
|
roleId?: string | null | undefined;
|
|
}
|
|
) {
|
|
const normalizedKeyword = String(filters.keyword || '')
|
|
.trim()
|
|
.toLocaleLowerCase();
|
|
const normalizedRoleId = String(filters.roleId || '').trim();
|
|
|
|
if (!normalizedKeyword && !normalizedRoleId) {
|
|
return [...members];
|
|
}
|
|
|
|
return members.filter(member => {
|
|
const matchesKeyword = !normalizedKeyword || member.userNickname.toLocaleLowerCase().includes(normalizedKeyword);
|
|
const matchesRole = !normalizedRoleId || member.roleId === normalizedRoleId;
|
|
|
|
return matchesKeyword && matchesRole;
|
|
});
|
|
}
|
|
|
|
export function getProjectTeamTableHeight(visibleRows: number) {
|
|
const normalizedRows = Math.max(0, visibleRows);
|
|
|
|
return projectTeamTableHeaderHeight + normalizedRows * projectTeamTableRowHeight;
|
|
}
|
|
|
|
export function canManageProjectTeam(context: ProjectTeamManageContext) {
|
|
return (
|
|
context.buttonCodes.includes('project:project:member') || context.buttonCodes.includes('project:project:update')
|
|
);
|
|
}
|