e78bb04c4b
- 登录成功后按用户恢复上次项目,管理员优先选择启用项目\n- 项目详情路由增加 setup_config:read 权限校验,未授权时回到可访问页面\n- 项目管理操作按当前项目行角色读取权限,避免误用首个角色权限\n- 角色模板排序中固定 PM 优先,保持角色列表、生效管理和权限编辑器顺序一致\n- 补充登录、路由、项目管理和角色排序相关测试
96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
import { computed, ref } from "vue";
|
|
import { fetchPermissionTemplates, type PermissionTemplate } from "@/api/projectPermissions";
|
|
import { compareProjectPermissionRoles } from "@/utils/projectPermissionRoles";
|
|
|
|
const FALLBACK_ROLE_META: Record<string, { label: string; icon: string; desc: string }> = {
|
|
ADMIN: { label: "管理员", icon: "🛡️", desc: "系统管理员" },
|
|
PM: { label: "PM", icon: "👑", desc: "项目负责人,统筹项目全局,协调进度、资源与关键决策。" },
|
|
CRA: { label: "CRA", icon: "📋", desc: "负责各中心临床监查执行,跟进现场质量、数据和问题闭环。" },
|
|
PV: { label: "PV", icon: "🔍", desc: "负责药物警戒相关工作,跟踪安全性事件并支持风险评估。" },
|
|
QA: { label: "QA", icon: "🏥", desc: "负责医学审核与稽查,关注质量风险、合规性和医学一致性。" },
|
|
CTA: { label: "CTA", icon: "📦", desc: "负责合同、药品及相关项目事务管理,保障执行支持与物资协同。" },
|
|
};
|
|
|
|
const templates = ref<PermissionTemplate[]>([]);
|
|
const loading = ref(false);
|
|
let pendingLoad: Promise<void> | null = null;
|
|
|
|
export const useRoleTemplateMeta = () => {
|
|
const templateByRole = computed(() => {
|
|
const result: Record<string, PermissionTemplate> = {};
|
|
for (const template of templates.value) {
|
|
if (template.category) result[template.category] = template;
|
|
}
|
|
return result;
|
|
});
|
|
|
|
const roleLabel = (role?: string | null) => {
|
|
if (!role) return "";
|
|
return templateByRole.value[role]?.name || FALLBACK_ROLE_META[role]?.label || role;
|
|
};
|
|
|
|
const roleDescription = (role?: string | null) => {
|
|
if (!role) return "";
|
|
return templateByRole.value[role]?.description || FALLBACK_ROLE_META[role]?.desc || "自定义项目角色";
|
|
};
|
|
|
|
const roleIcon = (role?: string | null) => {
|
|
if (!role) return "🔧";
|
|
return FALLBACK_ROLE_META[role]?.icon || "🔧";
|
|
};
|
|
|
|
const roleOptionsFor = (roles: string[]) =>
|
|
roles.map((role) => ({
|
|
value: role,
|
|
key: role,
|
|
label: roleLabel(role),
|
|
icon: roleIcon(role),
|
|
desc: roleDescription(role),
|
|
}));
|
|
|
|
const roleOrder = computed(() => {
|
|
const result: Record<string, number> = {};
|
|
templates.value.forEach((template, index) => {
|
|
if (template.category && result[template.category] === undefined) {
|
|
result[template.category] = index;
|
|
}
|
|
});
|
|
return result;
|
|
});
|
|
|
|
const compareRolesByTemplateOrder = (a: string, b: string) => {
|
|
if (a === "PM" || b === "PM") return compareProjectPermissionRoles(a, b);
|
|
const order = roleOrder.value;
|
|
const aOrder = order[a] ?? Number.MAX_SAFE_INTEGER;
|
|
const bOrder = order[b] ?? Number.MAX_SAFE_INTEGER;
|
|
if (aOrder !== bOrder) return aOrder - bOrder;
|
|
return a.localeCompare(b);
|
|
};
|
|
|
|
const loadRoleTemplates = async () => {
|
|
if (pendingLoad) return pendingLoad;
|
|
loading.value = true;
|
|
pendingLoad = fetchPermissionTemplates()
|
|
.then((res) => {
|
|
templates.value = res.data;
|
|
})
|
|
.finally(() => {
|
|
loading.value = false;
|
|
pendingLoad = null;
|
|
});
|
|
return pendingLoad;
|
|
};
|
|
|
|
return {
|
|
templates,
|
|
loading,
|
|
templateByRole,
|
|
roleLabel,
|
|
roleDescription,
|
|
roleIcon,
|
|
roleOptionsFor,
|
|
compareRolesByTemplateOrder,
|
|
loadRoleTemplates,
|
|
};
|
|
};
|