feat(product): 新增产品管理模块与字典组件功能
- 新增产品管理相关路由和页面(dashboard、list、requirement、setting) - 实现产品基础信息编辑弹窗组件(base-info-dialog.vue) - 添加运行时字典功能(dict-select、dict-text、dict-tag组件) - 集成字典管理store和API调用 - 规范ID类型定义为string避免精度丢失问题 - 完善国际化资源文件支持中英文对照 - 新增对象上下文业务域入口页导航实现说明 - 添加Vue DevTools浮动入口注释说明 - 统一权限控制支持全局和对象作用域区分 - 规范分页查询参数类型定义与使用方式
This commit is contained in:
233
src/views/product/setting/shared.ts
Normal file
233
src/views/product/setting/shared.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export interface ProductManagerMemberLike {
|
||||
roleId: string;
|
||||
}
|
||||
|
||||
interface ProductTeamManageContext {
|
||||
buttonCodes: readonly string[];
|
||||
loginUserId: string | null | undefined;
|
||||
currentManagerUserId: string | null | undefined;
|
||||
}
|
||||
|
||||
interface ProductLifecycleStatusSummary {
|
||||
tone: 'emerald' | 'amber' | 'slate' | 'rose';
|
||||
caption: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface ProductLifecycleActionCardMeta {
|
||||
tone: 'emerald' | 'amber' | 'slate' | 'rose';
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const productSettingSectionKeys = ['base-info', 'team', 'lifecycle', 'danger'] as const;
|
||||
|
||||
export type ProductSettingSectionKey = (typeof productSettingSectionKeys)[number];
|
||||
|
||||
const productSettingSectionAuthCodeMap: Partial<Record<ProductSettingSectionKey, string>> = {
|
||||
lifecycle: 'project:product:status',
|
||||
danger: 'project:product:delete'
|
||||
};
|
||||
|
||||
const productBaseInfoReadonlyMessageMap: Partial<Record<Api.Product.ProductStatusCode, string>> = {
|
||||
paused: '当前产品已暂停,基础信息仅支持查看,不可编辑。',
|
||||
archived: '当前产品已归档,基础信息仅支持查看,不可编辑。',
|
||||
abandoned: '当前产品已废弃,基础信息仅支持查看,不可编辑。'
|
||||
};
|
||||
|
||||
const productLifecycleStatusSummaryMap: Record<Api.Product.ProductStatusCode, ProductLifecycleStatusSummary> = {
|
||||
active: {
|
||||
tone: 'emerald',
|
||||
caption: '产品正常服务中',
|
||||
description: '当前可以执行暂停、归档或废弃。'
|
||||
},
|
||||
paused: {
|
||||
tone: 'amber',
|
||||
caption: '产品已暂停推进',
|
||||
description: '条件恢复后可重新启用,也可继续归档或废弃。'
|
||||
},
|
||||
archived: {
|
||||
tone: 'slate',
|
||||
caption: '产品已收口归档',
|
||||
description: '保留历史信息,当前不再开放新的生命周期动作。'
|
||||
},
|
||||
abandoned: {
|
||||
tone: 'rose',
|
||||
caption: '产品已停止建设',
|
||||
description: '产品已结束推进,当前不再开放新的生命周期动作。'
|
||||
}
|
||||
};
|
||||
|
||||
const productLifecycleActionCardMetaMap: Record<Api.Product.ProductStatusActionCode, ProductLifecycleActionCardMeta> = {
|
||||
pause: {
|
||||
tone: 'amber',
|
||||
description: '暂停当前产品,后续仍可恢复或归档。'
|
||||
},
|
||||
resume: {
|
||||
tone: 'emerald',
|
||||
description: '恢复启用后,继续推进产品协作。'
|
||||
},
|
||||
archive: {
|
||||
tone: 'slate',
|
||||
description: '收口当前产品,保留历史记录并结束维护。'
|
||||
},
|
||||
abandon: {
|
||||
tone: 'rose',
|
||||
description: '终止当前产品建设,请谨慎确认。'
|
||||
}
|
||||
};
|
||||
|
||||
const productSettingErrorMessageMap: Record<string, string> = {
|
||||
'1008001002': '产品名称已存在,请更换名称',
|
||||
'1008001007': '当前产品状态不允许编辑基础信息',
|
||||
'1008001008': '当前产品已暂停,基础信息仅支持查看,不可编辑。',
|
||||
'1008001013': '请选择原产品经理交接后的角色',
|
||||
'1008001014': '当前产品经理不能直接移出,请先完成经理交接',
|
||||
'1008001015': '当前产品经理不能直接调整为非经理角色,请先完成经理转交',
|
||||
'1008001004': '当前状态不支持该动作',
|
||||
'1008001005': '当前动作必须填写原因',
|
||||
'1008001006': '删除确认名称与当前产品名称不一致'
|
||||
};
|
||||
|
||||
const productTeamTableHeaderHeight = 40;
|
||||
const productTeamTableRowHeight = 40;
|
||||
|
||||
export function shouldRequireManagerHandover(
|
||||
targetRoleId: string,
|
||||
currentManager: ProductManagerMemberLike | null | undefined
|
||||
) {
|
||||
if (!currentManager?.roleId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return targetRoleId === currentManager.roleId;
|
||||
}
|
||||
|
||||
export function getPreviousManagerRoleOptions(roleOptions: Api.SystemManage.RoleSimple[], managerRoleId: string) {
|
||||
return roleOptions.filter(role => role.id !== managerRoleId);
|
||||
}
|
||||
|
||||
export function getProductSettingSectionKeys() {
|
||||
return [...productSettingSectionKeys];
|
||||
}
|
||||
|
||||
export function isProductBaseInfoEditable(status: Api.Product.ProductStatusCode | null | undefined) {
|
||||
return status === 'active';
|
||||
}
|
||||
|
||||
export function resolveVisibleProductSettingSections(
|
||||
sectionKeys: readonly ProductSettingSectionKey[],
|
||||
buttonCodes: readonly string[]
|
||||
) {
|
||||
return sectionKeys.filter(sectionKey => {
|
||||
const authCode = productSettingSectionAuthCodeMap[sectionKey];
|
||||
|
||||
if (!authCode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return buttonCodes.includes(authCode);
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveVisibleProductSettingSectionKey(
|
||||
currentKey: ProductSettingSectionKey | string | null | undefined,
|
||||
visibleSectionKeys: readonly ProductSettingSectionKey[]
|
||||
) {
|
||||
if (!visibleSectionKeys.length) {
|
||||
return 'base-info' satisfies ProductSettingSectionKey;
|
||||
}
|
||||
|
||||
if (currentKey && visibleSectionKeys.includes(currentKey as ProductSettingSectionKey)) {
|
||||
return currentKey as ProductSettingSectionKey;
|
||||
}
|
||||
|
||||
return visibleSectionKeys[0];
|
||||
}
|
||||
|
||||
export function getProductBaseInfoReadonlyMessage(status: Api.Product.ProductStatusCode | null | undefined) {
|
||||
if (!status || isProductBaseInfoEditable(status)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return productBaseInfoReadonlyMessageMap[status] || '当前产品状态不允许编辑基础信息。';
|
||||
}
|
||||
|
||||
export function getProductLifecycleStatusSummary(status: Api.Product.ProductStatusCode) {
|
||||
return productLifecycleStatusSummaryMap[status];
|
||||
}
|
||||
|
||||
export function formatProductMemberDate(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 filterProductMembersByKeyword(
|
||||
members: readonly Api.Product.ProductMember[],
|
||||
keyword: string | null | undefined
|
||||
) {
|
||||
return filterProductMembers(members, { keyword });
|
||||
}
|
||||
|
||||
export function filterProductMembers(
|
||||
members: readonly Api.Product.ProductMember[],
|
||||
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 getProductTeamTableHeight(visibleRows: number) {
|
||||
const normalizedRows = Math.max(0, visibleRows);
|
||||
|
||||
return productTeamTableHeaderHeight + normalizedRows * productTeamTableRowHeight;
|
||||
}
|
||||
|
||||
export function getProductLifecycleActionCardMeta(actionCode: Api.Product.ProductStatusActionCode) {
|
||||
return productLifecycleActionCardMetaMap[actionCode];
|
||||
}
|
||||
|
||||
export function canManageProductTeam(context: ProductTeamManageContext) {
|
||||
const hasUpdateAuth = context.buttonCodes.includes('project:product:update');
|
||||
const loginUserId = String(context.loginUserId || '');
|
||||
const currentManagerUserId = String(context.currentManagerUserId || '');
|
||||
|
||||
if (!hasUpdateAuth || !loginUserId || !currentManagerUserId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return loginUserId === currentManagerUserId;
|
||||
}
|
||||
|
||||
export function getProductSettingErrorMessage(code: string | number | null | undefined, backendMessage: string) {
|
||||
const normalizedCode = String(code || '');
|
||||
|
||||
return productSettingErrorMessageMap[normalizedCode] || backendMessage;
|
||||
}
|
||||
Reference in New Issue
Block a user