统一项目权限读取模型

1、合并列表与详情读取权限,统一使用 subjects:read、project_members:read、monitoring_issues:read 等业务读取 key。

2、增加历史读取权限 key 的兼容映射,确保存量项目权限覆盖值不会丢失。

3、同步前端权限工具、路由权限与权限管理页面,避免继续引用已合并的旧权限 key。

4、补充项目角色启停保护与抽屉未保存变更守卫,防止停用 PM 或仍有关联成员的角色。
This commit is contained in:
Cheng Zhou
2026-06-04 11:06:03 +08:00
parent 44d69c2d7b
commit ab4f0d93ed
22 changed files with 852 additions and 349 deletions
@@ -237,24 +237,24 @@ describe("ApiEndpointPermissions.vue", () => {
});
(wrapper.vm as any).operations = [
{ operation_key: "subject_aes:list", module: "subjects", action: "read", description: "查询参与者AE列表", default_roles: [] },
{ operation_key: "subject_aes:read", module: "subjects", action: "read", description: "查询参与者AE列表", default_roles: [] },
{ operation_key: "subject_pds:list", module: "subjects", action: "read", description: "查询参与者PD列表", default_roles: [] },
{ operation_key: "monitoring_issues:list", module: "risk_issues", action: "read", description: "查询监查访视问题列表", default_roles: [] },
{ operation_key: "monitoring_issues:read", module: "risk_issues", action: "read", description: "查询监查访视问题列表", default_roles: [] },
];
const riskRows = (wrapper.vm as any).filteredOperations.filter((operation: any) => operation.module === "risk_issues");
expect(riskRows.map((operation: any) => operation.operation_key)).toEqual([
"subject_aes:list",
"subject_aes:read",
"subject_pds:list",
"monitoring_issues:list",
"monitoring_issues:read",
]);
expect(riskRows.map((operation: any) => (wrapper.vm as any).getOperationSection(operation))).toEqual([
"AE/SAE",
"PD",
"监查访视问题",
]);
expect((wrapper.vm as any).filteredOperations.filter((operation: any) => operation.operation_key === "subject_aes:list")).toHaveLength(2);
expect((wrapper.vm as any).filteredOperations.filter((operation: any) => operation.operation_key === "subject_aes:read")).toHaveLength(2);
expect((wrapper.vm as any).filteredOperations.filter((operation: any) => operation.operation_key === "subject_pds:list")).toHaveLength(2);
});
@@ -309,6 +309,13 @@ describe("ApiEndpointPermissions.vue", () => {
description: "查询药品流向详情",
default_roles: [],
})).toBe("药品流向管理");
expect((wrapper.vm as any).getOperationSection({
operation_key: "drug_shipments_attachments:delete",
module: "materials",
action: "write",
description: "删除药物发货附件",
default_roles: [],
})).toBe("药品流向管理");
expect((wrapper.vm as any).getOperationSection({
operation_key: "material_equipments:read",
module: "materials",
@@ -316,9 +323,16 @@ describe("ApiEndpointPermissions.vue", () => {
description: "查询设备详情",
default_roles: [],
})).toBe("设备管理");
expect((wrapper.vm as any).getOperationSection({
operation_key: "material_equipments_attachments:delete",
module: "materials",
action: "write",
description: "删除物资设备附件",
default_roles: [],
})).toBe("设备管理");
});
it("groups startup ethics permissions with visible business names", () => {
it("groups startup ethics permissions into initiation and ethics sections", () => {
const wrapper = mount(ApiEndpointPermissions, {
props: {
project: { id: "test-id", name: "Test Project" },
@@ -338,14 +352,28 @@ describe("ApiEndpointPermissions.vue", () => {
action: "read",
description: "查询立项记录详情",
default_roles: [],
})).toBe("立项记录");
})).toBe("立项");
expect((wrapper.vm as any).getOperationSection({
operation_key: "startup_initiation_attachments:delete",
module: "startup_ethics",
action: "write",
description: "删除立项记录附件",
default_roles: [],
})).toBe("立项");
expect((wrapper.vm as any).getOperationSection({
operation_key: "startup_ethics:read",
module: "startup_ethics",
action: "read",
description: "查询伦理记录详情",
default_roles: [],
})).toBe("伦理记录");
})).toBe("伦理");
expect((wrapper.vm as any).getOperationSection({
operation_key: "startup_ethics_attachments:delete",
module: "startup_ethics",
action: "write",
description: "删除伦理记录附件",
default_roles: [],
})).toBe("伦理");
});
it("filters endpoints by search text", async () => {
@@ -340,9 +340,13 @@ const SECTION_LABELS: Record<string, string> = {
monitoring_issues: "监查访视问题",
project_milestones: "项目里程碑",
drug_shipments: "药品流向管理",
drug_shipments_attachments: "药品流向管理",
material_equipments: "设备管理",
startup_initiation: "立项记录",
startup_ethics: "伦理记录",
material_equipments_attachments: "设备管理",
startup_initiation: "立项",
startup_initiation_attachments: "立项",
startup_ethics: "伦理",
startup_ethics_attachments: "伦理",
precautions: "注意事项",
precautions_attachments: "注意事项",
faq: "医学咨询/FAQ",