Files
ctms/frontend/src/utils/permission.ts
T
Cheng Zhou 939fc70532 权限管理:接入项目级权限矩阵
新增项目级角色权限矩阵,覆盖 PM、CRA、PV、IMP、QA 等项目角色,并通过迁移表持久化各业务模块的读写权限。

后端将参与者、合同费用、物资、启动、里程碑、风险问题、监查稽查、FAQ、共享库、文件版本等接口接入矩阵校验,修复审计日志导出权限和登录 502 的依赖导入问题。

前端新增权限管理页面和项目路由权限映射,按矩阵控制导航显示、直接访问拦截、操作按钮启停,并限制管理后台仅 admin 和授权 PM 可进入。

补充后端权限归一化与接口静态测试、前端路由/权限工具/权限管理页面/工作台等回归测试。
2026-05-13 08:58:03 +08:00

86 lines
3.6 KiB
TypeScript

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"],
"subject.enroll": ["ADMIN"],
"subject.complete": ["ADMIN"],
"subject.drop": ["ADMIN"],
"ae.create": ["ADMIN"],
"ae.close": ["ADMIN"],
"faq.edit": ["ADMIN"],
"faq.create": ["ADMIN"],
"faq.reply": ["ADMIN"],
"project.members.manage": ["ADMIN"],
"site.manage": ["ADMIN"],
"site.cra.bind": ["ADMIN"],
"fees.contract.write": ["ADMIN"],
};
const REASONS: Record<string, string> = {
"subject.enroll": TEXT.modules.permissions.subjectEnroll,
"subject.complete": TEXT.modules.permissions.subjectComplete,
"subject.drop": TEXT.modules.permissions.subjectDrop,
"ae.close": TEXT.modules.permissions.aeClose,
"faq.edit": TEXT.modules.permissions.faqEdit,
"faq.create": TEXT.modules.permissions.faqCreate,
"faq.reply": TEXT.modules.permissions.faqReply,
"project.members.manage": TEXT.modules.permissions.projectMembersManage,
"site.manage": TEXT.modules.permissions.siteManage,
"site.cra.bind": TEXT.modules.permissions.siteCraBind,
"fees.contract.write": TEXT.modules.permissions.feeContractsWrite,
};
export const usePermission = () => {
const auth = useAuthStore();
const study = useStudyStore();
const systemAdmin = computed(() => isSystemAdmin(auth.user));
const projectRole = computed(() => getProjectRole(study.currentStudy, study.currentStudyRole));
const projectPermissions = computed(() => study.currentPermissions);
const can = (action: string): boolean => {
if (systemAdmin.value) return true;
if (!projectRole.value) return false;
const permissionMap: Record<string, { module: string; action: "read" | "write" }> = {
"subject.create": { module: "subjects", action: "write" },
"subject.enroll": { module: "subjects", action: "write" },
"subject.complete": { module: "subjects", action: "write" },
"subject.drop": { module: "subjects", action: "write" },
"ae.create": { module: "risk_issues", action: "write" },
"ae.close": { module: "risk_issues", action: "write" },
"project.overview.read": { module: "project_overview", action: "read" },
"faq.edit": { module: "faq", action: "write" },
"faq.create": { module: "faq", action: "write" },
"faq.reply": { module: "faq", action: "write" },
"knowledge.notes.write": { module: "shared_library", action: "write" },
"shared.library.read": { module: "shared_library", action: "read" },
"shared.library.write": { module: "shared_library", action: "write" },
"project.members.manage": { module: "project_members", action: "write" },
"site.manage": { module: "sites", action: "write" },
"site.cra.bind": { module: "sites", action: "write" },
"fees.contract.write": { module: "fees", action: "write" },
"fees.attachment.delete": { module: "fees", action: "write" },
"file.attachment.delete": { module: "file_versions", action: "write" },
"audit.export.read": { module: "audit_export", action: "read" },
};
const mapped = permissionMap[action];
if (!mapped) return false;
const allowed = projectPermissions.value?.roles?.[projectRole.value]?.[mapped.module]?.[mapped.action];
return !!allowed;
};
const reason = (action: string): string => {
if (systemAdmin.value) return "";
return REASONS[action] || TEXT.modules.permissions.default;
};
return {
can,
reason,
};
};