中文:收口权限与中心/立项配置改造

This commit is contained in:
Cheng Zhou
2026-05-12 10:16:52 +08:00
parent 6e90370a5f
commit 77e842637d
67 changed files with 706 additions and 669 deletions
+25
View File
@@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readPermissionSource = () => readFileSync(resolve(__dirname, "./permission.ts"), "utf8");
describe("permission project role model", () => {
it("does not fall back to non-admin global roles for project permissions", () => {
const source = readPermissionSource();
expect(source).toContain("isSystemAdmin");
expect(source).toContain("getProjectRole");
expect(source).toContain("study.currentStudyRole");
expect(source).not.toContain("role_in_study || auth.user?.role");
expect(source).not.toContain("role_in_study || userRole.value");
});
it("allows project administrators and PMs to manage project configuration", () => {
const source = readPermissionSource();
expect(source).toContain('"project.members.manage": ["ADMIN", "PM"]');
expect(source).toContain('"site.manage": ["ADMIN", "PM"]');
expect(source).toContain('"site.cra.bind": ["ADMIN", "PM"]');
});
});
+5 -6
View File
@@ -2,6 +2,7 @@ import { computed } from "vue";
import { useAuthStore } from "../store/auth";
import { useStudyStore } from "../store/study";
import { TEXT } from "../locales";
import { getProjectRole, isSystemAdmin } from "./roles";
const PERMISSIONS: Record<string, string[]> = {
"subject.create": ["ADMIN", "PM", "CRA"],
@@ -37,13 +38,11 @@ export const usePermission = () => {
const auth = useAuthStore();
const study = useStudyStore();
const userRole = computed(() => auth.user?.role || null);
const projectRole = computed(
() => study.currentStudyRole || (study.currentStudy as any)?.role_in_study || auth.user?.role || null
);
const systemAdmin = computed(() => isSystemAdmin(auth.user));
const projectRole = computed(() => getProjectRole(study.currentStudy, study.currentStudyRole));
const can = (action: string): boolean => {
if (userRole.value === "ADMIN") return true;
if (systemAdmin.value) return true;
const allowList = PERMISSIONS[action];
if (!allowList) return false;
if (!projectRole.value) return false;
@@ -51,7 +50,7 @@ export const usePermission = () => {
};
const reason = (action: string): string => {
if (userRole.value === "ADMIN") return "";
if (systemAdmin.value) return "";
return REASONS[action] || TEXT.modules.permissions.default;
};
+18
View File
@@ -0,0 +1,18 @@
import { describe, expect, it } from "vitest";
import { getProjectRole, getSystemRole, isProjectAdminOrPm, isSystemAdmin } from "./roles";
describe("role helpers", () => {
it("keeps system admin separate from project admin", () => {
expect(isSystemAdmin({ role: "ADMIN" } as any)).toBe(true);
expect(getSystemRole({ role: "ADMIN" } as any)).toBe("ADMIN");
expect(getProjectRole({ role_in_study: null } as any, null)).toBeNull();
});
it("uses only project role fields for project permissions", () => {
expect(getProjectRole({ role_in_study: "PM" } as any, null)).toBe("PM");
expect(getProjectRole({ role_in_study: null } as any, "ADMIN")).toBe("ADMIN");
expect(isProjectAdminOrPm("ADMIN")).toBe(true);
expect(isProjectAdminOrPm("PM")).toBe(true);
expect(isProjectAdminOrPm("CRA")).toBe(false);
});
});
+10
View File
@@ -0,0 +1,10 @@
import type { Study, UserInfo } from "../types/api";
export const getSystemRole = (user?: Pick<UserInfo, "role"> | null) => user?.role || null;
export const isSystemAdmin = (user?: Pick<UserInfo, "role"> | null) => getSystemRole(user) === "ADMIN";
export const getProjectRole = (study?: Pick<Study, "role_in_study"> | null, currentStudyRole?: string | null) =>
currentStudyRole || study?.role_in_study || null;
export const isProjectAdminOrPm = (role?: string | null) => role === "ADMIN" || role === "PM";
-9
View File
@@ -90,14 +90,6 @@ const setupFieldLabelMap: Record<keyof SetupConfigDraft, Record<string, string>>
note: "备注",
stageBreakdown: "分阶段计划",
},
monitoringStrategies: {
id: "ID",
strategyType: "监查类型",
detail: "策略详情",
frequency: "监查次数",
updatedAt: "更新时间",
enabled: "是否启用",
},
centerConfirm: {
id: "ID",
siteId: "中心ID",
@@ -124,7 +116,6 @@ const getArrayRowIdentity = (moduleKey: keyof SetupConfigDraft, value: unknown):
if (moduleKey === "projectMilestones") return String(row.name || "").trim();
if (moduleKey === "siteMilestones") return String(row.milestone || "").trim();
if (moduleKey === "siteEnrollmentPlans") return String(row.siteName || row.siteId || "").trim();
if (moduleKey === "monitoringStrategies") return String(row.strategyType || "").trim();
if (moduleKey === "centerConfirm") return String(row.siteName || row.siteId || "").trim();
return "";
};