Files
ctms/frontend/src/utils/permission.ts
T
Cheng Zhou ab4f0d93ed 统一项目权限读取模型
1、合并列表与详情读取权限,统一使用 subjects:read、project_members:read、monitoring_issues:read 等业务读取 key。

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

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

4、补充项目角色启停保护与抽屉未保存变更守卫,防止停用 PM 或仍有关联成员的角色。
2026-06-04 11:06:03 +08:00

174 lines
7.4 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";
import { isApiPermissionAllowed } from "./apiPermissionValue";
const PERMISSIONS: Record<string, string[]> = {
"subject.create": ["ADMIN"],
"subject.enroll": ["ADMIN"],
"subject.complete": ["ADMIN"],
"subject.drop": ["ADMIN"],
"subject.delete": ["ADMIN"],
"ae.create": ["ADMIN"],
"ae.close": ["ADMIN"],
"faq.edit": ["ADMIN"],
"faq.update": ["ADMIN"],
"faq.delete": ["ADMIN"],
"faq.create": ["ADMIN"],
"faq.reply": ["ADMIN"],
"faq.reply.delete": ["ADMIN"],
"faq.category.read": ["ADMIN"],
"faq.category.create": ["ADMIN"],
"faq.category.update": ["ADMIN"],
"faq.category.delete": ["ADMIN"],
"precautions.create": ["ADMIN"],
"precautions.update": ["ADMIN"],
"precautions.delete": ["ADMIN"],
"startup.initiation.create": ["ADMIN"],
"startup.initiation.update": ["ADMIN"],
"startup.initiation.delete": ["ADMIN"],
"startup.ethics.create": ["ADMIN"],
"startup.ethics.update": ["ADMIN"],
"startup.ethics.delete": ["ADMIN"],
"startup.auth.create": ["ADMIN"],
"startup.auth.update": ["ADMIN"],
"startup.auth.delete": ["ADMIN"],
"project.milestones.update": ["ADMIN"],
"project.members.list": ["ADMIN"],
"project.members.candidates": ["ADMIN"],
"project.members.create": ["ADMIN"],
"project.members.update": ["ADMIN"],
"project.members.delete": ["ADMIN"],
"site.manage": ["ADMIN"],
"site.cra.bind": ["ADMIN"],
"fees.contract.create": ["ADMIN"],
"fees.contract.update": ["ADMIN"],
"fees.contract.delete": ["ADMIN"],
"documents.read": ["ADMIN"],
"documents.create": ["ADMIN"],
"documents.update": ["ADMIN"],
"documents.delete": ["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.update": TEXT.modules.permissions.faqEdit,
"faq.delete": TEXT.modules.permissions.faqEdit,
"faq.create": TEXT.modules.permissions.faqCreate,
"faq.reply": TEXT.modules.permissions.faqReply,
"faq.reply.delete": TEXT.modules.permissions.faqReply,
"faq.category.read": TEXT.modules.permissions.default,
"faq.category.create": TEXT.modules.permissions.faqEdit,
"faq.category.update": TEXT.modules.permissions.faqEdit,
"faq.category.delete": TEXT.modules.permissions.faqEdit,
"precautions.create": TEXT.modules.permissions.default,
"precautions.update": TEXT.modules.permissions.default,
"precautions.delete": TEXT.modules.permissions.default,
"startup.initiation.create": TEXT.modules.permissions.default,
"startup.initiation.update": TEXT.modules.permissions.default,
"startup.initiation.delete": TEXT.modules.permissions.default,
"startup.ethics.create": TEXT.modules.permissions.default,
"startup.ethics.update": TEXT.modules.permissions.default,
"startup.ethics.delete": TEXT.modules.permissions.default,
"startup.auth.create": TEXT.modules.permissions.default,
"startup.auth.update": TEXT.modules.permissions.default,
"startup.auth.delete": TEXT.modules.permissions.default,
"project.milestones.update": TEXT.modules.permissions.default,
"project.members.list": TEXT.modules.permissions.projectMembersManage,
"project.members.candidates": TEXT.modules.permissions.projectMembersManage,
"project.members.create": TEXT.modules.permissions.projectMembersManage,
"project.members.update": TEXT.modules.permissions.projectMembersManage,
"project.members.delete": TEXT.modules.permissions.projectMembersManage,
"site.manage": TEXT.modules.permissions.siteManage,
"site.cra.bind": TEXT.modules.permissions.siteCraBind,
"fees.contract.create": TEXT.modules.permissions.feeContractsWrite,
"fees.contract.update": TEXT.modules.permissions.feeContractsWrite,
"fees.contract.delete": TEXT.modules.permissions.feeContractsWrite,
"documents.read": TEXT.modules.permissions.default,
"documents.create": TEXT.modules.permissions.default,
"documents.update": TEXT.modules.permissions.default,
"documents.delete": TEXT.modules.permissions.default,
};
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, string> = {
"subject.create": "subjects:create",
"subject.enroll": "subjects:update",
"subject.complete": "subjects:update",
"subject.drop": "subjects:update",
"subject.delete": "subjects:delete",
"ae.create": "subject_aes:create",
"ae.close": "subject_aes:update",
"project.overview.read": "project_overview:read",
"faq.edit": "faq:update",
"faq.update": "faq:update",
"faq.delete": "faq:delete",
"faq.create": "faq:create",
"faq.reply": "faq_reply:create",
"faq.reply.delete": "faq_reply:delete",
"faq.category.read": "faq_category:read",
"faq.category.create": "faq_category:create",
"faq.category.update": "faq_category:update",
"faq.category.delete": "faq_category:delete",
"precautions.read": "precautions:read",
"precautions.create": "precautions:create",
"precautions.update": "precautions:update",
"precautions.delete": "precautions:delete",
"startup.initiation.create": "startup_initiation:create",
"startup.initiation.update": "startup_initiation:update",
"startup.initiation.delete": "startup_initiation:delete",
"startup.ethics.create": "startup_ethics:create",
"startup.ethics.update": "startup_ethics:update",
"startup.ethics.delete": "startup_ethics:delete",
"startup.auth.create": "startup_auth:create",
"startup.auth.update": "startup_auth:update",
"startup.auth.delete": "startup_auth:delete",
"project.milestones.update": "project_milestones:update",
"project.members.list": "project_members:read",
"project.members.candidates": "project_members:read",
"project.members.create": "project_members:create",
"project.members.update": "project_members:update",
"project.members.delete": "project_members:delete",
"site.manage": "sites:update",
"site.cra.bind": "sites:update",
"fees.contract.create": "fees_contracts:create",
"fees.contract.update": "fees_contracts:update",
"fees.contract.delete": "fees_contracts:delete",
"documents.read": "documents:read",
"documents.create": "documents:create",
"documents.update": "documents:update",
"documents.delete": "documents:delete",
"audit.export.read": "audit_logs:read",
};
const operationKey = permissionMap[action];
if (!operationKey) return false;
return isApiPermissionAllowed(projectPermissions.value?.[projectRole.value]?.[operationKey]);
};
const reason = (action: string): string => {
if (systemAdmin.value) return "";
return REASONS[action] || TEXT.modules.permissions.default;
};
return {
can,
reason,
};
};