Why: 方案 r1 U6/R4/KTD5——RDM #266 已交付与实际判定同源的矩阵解释读面,项目设置需消费入口(UI 不重算,服务端解释)。 Evidence: 红:3 failed(导出缺失,/tmp/fe-matrix-red.txt);绿:vitest 25 文件/127 测试全过(含 design-guard)+ npm run build 成功(tsc -b 干净);收据 /tmp/fe-matrix-vitest.txt、/tmp/fe-matrix-build.txt。 Impact: rdmManagement 增 RDM_PERM_MATRIX_COMMANDS/rdmPermMatrixReady/permCheckPayload/describeSource;RdmPermMatrixSection(成员选择默认自省+12 动作矩阵表+来源标签+拒绝原因);项目设置「项目权限」页签(rdmPermMatrixAvailable 门控);store 同源计算标志。样式仅复用既有类。 Refs: RDM #266;方案 r1 U6/R4
120 lines
5.1 KiB
TypeScript
120 lines
5.1 KiB
TypeScript
// U6/KTD5:RDM 管理与项目页面的纯逻辑层——能力门控(与 rdmAuditReady 同源语义:
|
|
// 命令清单不可用=未就绪,fail-closed)与命令载荷构造。页面组件只消费本模块与
|
|
// api.rdmCommand,不在组件里散落命令 id / 参数拼写;身份一律经认证会话,载荷不
|
|
// 携带任何身份字段(G1-5)。
|
|
|
|
/** 系统管理「RDM 角色权限」分区消费的命令面。 */
|
|
export const RDM_ROLES_COMMANDS = [
|
|
'rdm.role.create',
|
|
'rdm.role.action',
|
|
'rdm.role.assign',
|
|
'rdm.role.disable',
|
|
'rdm.role.list',
|
|
] as const
|
|
|
|
/** 项目设置「成员」页消费的命令面。 */
|
|
export const RDM_MEMBER_COMMANDS = [
|
|
'rdm.project.member.list',
|
|
'rdm.project.member.add',
|
|
'rdm.project.member.remove',
|
|
] as const
|
|
|
|
/** 项目设置「项目权限」页消费的命令面(矩阵解释 + 成员清单)。 */
|
|
export const RDM_PERM_MATRIX_COMMANDS = [
|
|
'rdm.perm.check',
|
|
'rdm.project.member.list',
|
|
] as const
|
|
|
|
/** 项目设置「文档职责 · 整文审签」页消费的命令面。 */
|
|
export const RDM_DOC_REVIEW_COMMANDS = [
|
|
'rdm.review.assign.doc',
|
|
'rdm.review.doc.submit',
|
|
'rdm.review.doc.approve',
|
|
'rdm.review.doc.reject',
|
|
'rdm.review.doc.final',
|
|
'rdm.review.doc.status',
|
|
] as const
|
|
|
|
export type CommandListItem = { id?: string; enabled?: boolean } | null | undefined
|
|
|
|
function ready(cmds: CommandListItem[] | null | undefined, need: readonly string[]): boolean {
|
|
if (!Array.isArray(cmds)) return false
|
|
return need.every(n => cmds.some(c => c?.id === n && c.enabled === true))
|
|
}
|
|
|
|
/** 角色分区是否就绪(普通基座无 rdm.role.* → 隐藏)。 */
|
|
export const rdmRolesReady = (cmds: CommandListItem[] | null | undefined) =>
|
|
ready(cmds, RDM_ROLES_COMMANDS)
|
|
|
|
/** 项目成员页是否就绪。 */
|
|
export const rdmMembersReady = (cmds: CommandListItem[] | null | undefined) =>
|
|
ready(cmds, RDM_MEMBER_COMMANDS)
|
|
|
|
/** 文档职责/整文审签页是否就绪。 */
|
|
export const rdmDocReviewReady = (cmds: CommandListItem[] | null | undefined) =>
|
|
ready(cmds, RDM_DOC_REVIEW_COMMANDS)
|
|
|
|
/** 项目权限矩阵页是否就绪。 */
|
|
export const rdmPermMatrixReady = (cmds: CommandListItem[] | null | undefined) =>
|
|
ready(cmds, RDM_PERM_MATRIX_COMMANDS)
|
|
|
|
/** RDM 动作清单(12 项,与 RDM perms.AllActions 对齐;角色动作编辑器选项)。 */
|
|
export const ROLE_ACTIONS = [
|
|
'project.create', 'project.archive', 'member.manage', 'duty.assign',
|
|
'stage.advance', 'doc.read', 'doc.write', 'doc.publish', 'review.participate',
|
|
'trace.edit', 'knowledge.publish', 'project.settings',
|
|
] as const
|
|
|
|
// ---- 载荷构造(字段名与 RDM 命令 schema 一一对应) ----
|
|
|
|
export const roleCreatePayload = (name: string) => ({ name })
|
|
export const roleActionPayload = (roleId: string, action: string, def: boolean, cap: boolean) =>
|
|
({ roleId, action, default: def, cap })
|
|
export const roleAssignPayload = (owner: string, roleId: string) => ({ owner, roleId })
|
|
export const roleDisablePayload = (roleId: string, disabled: boolean) => ({ roleId, disabled })
|
|
|
|
export const memberAddPayload = (projectId: string, member: string, role: 'owner' | 'member' = 'member') =>
|
|
({ projectId, member, role })
|
|
export const memberRemovePayload = (projectId: string, member: string) => ({ projectId, member })
|
|
|
|
export const assignDocDutiesPayload = (
|
|
projectId: string, docId: string, author: string, reviewers: string[], approver: string,
|
|
) => ({ projectId, docId, author, reviewers, approver })
|
|
|
|
export const docSubmitPayload = (projectId: string, docId: string) => ({ projectId, docId })
|
|
export const docApprovePayload = (projectId: string, docId: string, note?: string) =>
|
|
note ? { projectId, docId, note } : { projectId, docId }
|
|
export const docRejectPayload = (projectId: string, docId: string, reason?: string) =>
|
|
reason ? { projectId, docId, reason } : { projectId, docId }
|
|
/** round=0(未指定)不携带——服务端只认在途轮次,旧轮次传参即拒。 */
|
|
export const docFinalPayload = (projectId: string, docId: string, round = 0) =>
|
|
round > 0 ? { projectId, docId, round } : { projectId, docId }
|
|
export const docStatusPayload = (projectId: string, docId: string) => ({ projectId, docId })
|
|
|
|
export const permCheckPayload = (projectId: string, member: string) => ({ projectId, member })
|
|
|
|
/** 矩阵来源标签(人话映射;未知来源回退原文——服务端新增来源时不至于空白)。 */
|
|
export function describeSource(source: string): string {
|
|
switch (source) {
|
|
case 'admin': return '系统管理员'
|
|
case 'prof-role-default': return '专业角色默认'
|
|
case 'override': return '项目内覆盖'
|
|
case 'relation-default': return '项目关系默认'
|
|
case 'non-member': return '非项目成员'
|
|
default: return source
|
|
}
|
|
}
|
|
|
|
/** 审阅人 CSV 解析:去空白、去空项、保序去重(表单输入 → reviewers 数组)。 */
|
|
export function parseReviewers(csv: string): string[] {
|
|
const seen = new Set<string>()
|
|
const out: string[] = []
|
|
for (const part of csv.split(',')) {
|
|
const v = part.trim()
|
|
if (!v || seen.has(v)) continue
|
|
seen.add(v)
|
|
out.push(v)
|
|
}
|
|
return out
|
|
}
|