/** * 模块注册表的纯逻辑:选项构造、module → detailKey 映射、标题回退。 * * 这里刻意不依赖 request / Vue,便于 scripts/selfcheck-module-registry.mjs * 在无单测基建的前端仓库里直接验证行为。副作用(拉取与缓存)在 * src/utils/module-registry.ts。 */ /** 注册表条目(与后端 ModuleRegistry 字段一致) */ export interface RegistryModule { module?: string; title?: string; detailKey?: string; enabled?: number; sortNumber?: number; } /** 下拉选项 */ export interface ModuleOption { label: string; value: string; } /** * 构造模块下拉选项:只含启用模块,按 sortNumber 升序,标题取注册表 title。 * * 停用模块(enabled=0)不出现在「可新建审批链」的下拉里,历史数据仍可查, * 见 docs/adr/0006-approval-chain-convergence.md。 */ export const toModuleOptions = (list?: RegistryModule[]): ModuleOption[] => { return (list || []) .filter((item) => !!item.module && item.enabled === 1) .slice() .sort((a, b) => (a.sortNumber ?? 0) - (b.sortNumber ?? 0)) .map((item) => ({ label: item.title || item.module || '', value: item.module || '' })); }; /** * module → 详情页配置键(moduleDetailConfig.ts 的键)的唯一映射。 * * 注册表里没有该模块时回退 module 本身:停用模块的历史审批链仍能打开详情, * 而注册表的 detail_key 与 module 不一致时以注册表为准。 */ export const resolveDetailKey = ( list: RegistryModule[] | undefined, module?: string ): string | undefined => { if (!module) { return undefined; } const item = (list || []).find((entry) => entry.module === module); return item?.detailKey || module; }; /** 模块标题,注册表未命中时回退 module 本身,避免回显成空白 */ export const resolveModuleTitle = ( list: RegistryModule[] | undefined, module?: string ): string | undefined => { if (!module) { return undefined; } const item = (list || []).find((entry) => entry.module === module); return item?.title || module; }; /** * 编辑存量配置时把当前值补进下拉。 * * 停用模块不在启用清单里,但历史审批链仍要回显标题而不是空白/裸 module 值, * 因此按「标题(已停用)」补一项;已经在清单里则原样返回。 */ export const ensureModuleOption = ( options: ModuleOption[], module?: string, moduleName?: string ): ModuleOption[] => { if (!module || options.some((item) => item.value === module)) { return options; } return [{ label: `${moduleName || module}(已停用)`, value: module }, ...options]; };