移除QA预设角色并收紧角色模板编辑
This commit is contained in:
@@ -47,10 +47,30 @@ describe("permission management custom roles", () => {
|
||||
|
||||
expect(source).toContain("const roleLabel = (role: string) => ROLE_LABELS[role] || role;");
|
||||
expect(source).toContain("const templateRoleName = (row: PermissionTemplate) => row.category ? roleLabel(row.category) : row.name;");
|
||||
expect(source).toContain("name: templateRoleName(row), description: row.description ?? \"\",");
|
||||
expect(source).toContain("roleDisplayName(row)");
|
||||
});
|
||||
|
||||
it("locks role type for preset edits and creates custom roles only", () => {
|
||||
const source = readSource();
|
||||
const dialogStart = source.indexOf("<!-- 新增/编辑角色对话框 -->");
|
||||
const dialogEnd = source.indexOf("<!-- 新增成员对话框 -->", dialogStart);
|
||||
const dialogSource = source.slice(dialogStart, dialogEnd);
|
||||
|
||||
expect(dialogSource).toContain(':disabled="Boolean(editingTemplateId)"');
|
||||
expect(source).toContain("const openCreateTemplate = () =>");
|
||||
expect(source).toContain("const openEditTemplate = (row: PermissionTemplate) =>");
|
||||
expect(source).toContain('template_type: "CUSTOM"');
|
||||
expect(dialogSource).toContain('<el-option label="预设角色" value="ROLE" :disabled="!editingTemplateId" />');
|
||||
expect(dialogSource).toContain('<el-option label="自定义角色" value="CUSTOM" :disabled="Boolean(editingTemplateId)" />');
|
||||
expect(dialogSource).not.toContain('<el-option label="场景角色" value="SCENARIO" />');
|
||||
expect(dialogSource).not.toContain('label="分类"');
|
||||
expect(dialogSource).not.toContain('label="推荐角色"');
|
||||
expect(dialogSource).not.toContain('label="标签"');
|
||||
expect(dialogSource).not.toContain("如 PM / CRA / PV");
|
||||
expect(dialogSource).not.toContain("如 PM,CRA");
|
||||
expect(dialogSource).not.toContain("可选标签");
|
||||
});
|
||||
|
||||
it("keeps member role selects bound to role keys used by the permission matrix", () => {
|
||||
const source = readSource();
|
||||
|
||||
@@ -61,6 +81,18 @@ describe("permission management custom roles", () => {
|
||||
expect(source).toContain("await addMember(selectedStudyId.value, addMemberForm);");
|
||||
});
|
||||
|
||||
it("does not offer QA as a preset project permission role", () => {
|
||||
const source = readSource();
|
||||
|
||||
const allRolesStart = source.indexOf("const ALL_ROLES = [");
|
||||
const allRolesEnd = source.indexOf("];", allRolesStart);
|
||||
const allRolesSource = source.slice(allRolesStart, allRolesEnd);
|
||||
|
||||
expect(allRolesSource).not.toContain('key: "QA"');
|
||||
expect(source).not.toContain('QA: "QA"');
|
||||
expect(source).not.toContain("QA: 60");
|
||||
});
|
||||
|
||||
it("submits only the selected role from role editor saves", () => {
|
||||
const source = readSource();
|
||||
|
||||
@@ -69,6 +101,24 @@ describe("permission management custom roles", () => {
|
||||
expect(source).not.toContain("const payload = flattenMatrix(apiMatrix.value, { includePm: isAdmin.value });");
|
||||
});
|
||||
|
||||
it("submits only editable metadata when saving an existing role template", () => {
|
||||
const source = readSource();
|
||||
const updateBuilderStart = source.indexOf("const buildTemplateUpdatePayload = () =>");
|
||||
const createBuilderStart = source.indexOf("const buildTemplateCreatePayload = ()");
|
||||
const submitStart = source.indexOf("const submitTemplateForm = async () =>", createBuilderStart);
|
||||
const updateBuilderSource = source.slice(updateBuilderStart, createBuilderStart);
|
||||
const createBuilderSource = source.slice(createBuilderStart, submitStart);
|
||||
|
||||
expect(updateBuilderSource).toContain("name: templateForm.value.name");
|
||||
expect(updateBuilderSource).toContain("description: templateForm.value.description || undefined");
|
||||
expect(updateBuilderSource).not.toContain("template_type");
|
||||
expect(updateBuilderSource).not.toContain("permissions");
|
||||
expect(updateBuilderSource).not.toContain("category");
|
||||
expect(createBuilderSource).toContain("permissions: {}");
|
||||
expect(source).toContain("const payload = buildTemplateUpdatePayload();");
|
||||
expect(source).toContain("const payload = buildTemplateCreatePayload();");
|
||||
});
|
||||
|
||||
it("renders setup_config as a Chinese module name in the role editor", () => {
|
||||
const source = readSource();
|
||||
|
||||
|
||||
@@ -385,21 +385,11 @@
|
||||
<el-input v-model="templateForm.description" type="textarea" :rows="2" placeholder="可选描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色类型" prop="template_type">
|
||||
<el-select v-model="templateForm.template_type" style="width: 100%">
|
||||
<el-option label="预设角色" value="ROLE" />
|
||||
<el-option label="场景角色" value="SCENARIO" />
|
||||
<el-option label="自定义角色" value="CUSTOM" />
|
||||
<el-select v-model="templateForm.template_type" style="width: 100%" :disabled="Boolean(editingTemplateId)">
|
||||
<el-option label="预设角色" value="ROLE" :disabled="!editingTemplateId" />
|
||||
<el-option label="自定义角色" value="CUSTOM" :disabled="Boolean(editingTemplateId)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-input v-model="templateForm.category" placeholder="如 PM / CRA / PV" />
|
||||
</el-form-item>
|
||||
<el-form-item label="推荐角色">
|
||||
<el-input v-model="templateForm.recommended_roles" placeholder="如 PM,CRA" />
|
||||
</el-form-item>
|
||||
<el-form-item label="标签">
|
||||
<el-input v-model="templateForm.tags" placeholder="可选标签" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="templateDialogVisible = false">取消</el-button>
|
||||
@@ -461,6 +451,7 @@ import {
|
||||
fetchActiveRoles,
|
||||
updateActiveRoles,
|
||||
type PermissionTemplate,
|
||||
type PermissionTemplateCreate,
|
||||
type SystemPermissionItem,
|
||||
} from "@/api/projectPermissions";
|
||||
import { addMember, listMemberCandidates, listMembers, removeMember, updateMember } from "@/api/members";
|
||||
@@ -483,7 +474,7 @@ const canManageSelectedProject = computed(() => isAdmin.value || isSelectedProje
|
||||
|
||||
const ROLE_LABELS: Record<string, string> = {
|
||||
PM: "项目负责人", CRA: "CRA", PV: "PV",
|
||||
MEDICAL_REVIEW: "医学审核", IMP: "药品管理员", QA: "QA",
|
||||
MEDICAL_REVIEW: "医学审核", IMP: "药品管理员",
|
||||
};
|
||||
const roleLabel = (role: string) => ROLE_LABELS[role] || role;
|
||||
|
||||
@@ -495,7 +486,7 @@ const activeRoleLabels = computed(() => {
|
||||
return result;
|
||||
});
|
||||
const ROLE_RANK: Record<string, number> = {
|
||||
ADMIN: 100, PM: 80, QA: 60, PV: 50, MEDICAL_REVIEW: 50, CRA: 40, IMP: 40,
|
||||
ADMIN: 100, PM: 80, PV: 50, MEDICAL_REVIEW: 50, CRA: 40, IMP: 40,
|
||||
};
|
||||
type MemberRoleRow = { user_id: string; role_in_study: string; user?: any };
|
||||
const canAssignProjectRole = (role: string) => isAdmin.value || (ROLE_RANK[role] ?? 0) < ROLE_RANK.PM;
|
||||
@@ -778,7 +769,6 @@ const ALL_ROLES = [
|
||||
{ key: "PV", label: "PV", icon: "🔍", desc: "访视和参与者管理人员" },
|
||||
{ key: "MEDICAL_REVIEW", label: "医学审核", icon: "🏥", desc: "医学审核人员" },
|
||||
{ key: "IMP", label: "药品管理员", icon: "📦", desc: "物资和设备管理人员" },
|
||||
{ key: "QA", label: "QA", icon: "✅", desc: "质量检查和审计人员" },
|
||||
];
|
||||
|
||||
const activeRolesDraft = ref<string[]>([]);
|
||||
@@ -898,25 +888,31 @@ const resetTemplateForm = () => {
|
||||
templateFormRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
const buildTemplateUpdatePayload = () => ({
|
||||
name: templateForm.value.name,
|
||||
description: templateForm.value.description || undefined,
|
||||
});
|
||||
|
||||
const buildTemplateCreatePayload = (): PermissionTemplateCreate => ({
|
||||
...templateForm.value,
|
||||
description: templateForm.value.description || undefined,
|
||||
category: templateForm.value.category || undefined,
|
||||
recommended_roles: templateForm.value.recommended_roles || undefined,
|
||||
tags: templateForm.value.tags || undefined,
|
||||
permissions: {},
|
||||
});
|
||||
|
||||
const submitTemplateForm = async () => {
|
||||
const valid = await templateFormRef.value?.validate().catch(() => false);
|
||||
if (!valid) return;
|
||||
templateSaving.value = true;
|
||||
try {
|
||||
const payload = {
|
||||
...templateForm.value,
|
||||
description: templateForm.value.description || undefined,
|
||||
category: templateForm.value.category || undefined,
|
||||
recommended_roles: templateForm.value.recommended_roles || undefined,
|
||||
tags: templateForm.value.tags || undefined,
|
||||
permissions: editingTemplateId.value
|
||||
? (templates.value.find((t) => t.id === editingTemplateId.value)?.permissions ?? {})
|
||||
: {},
|
||||
};
|
||||
if (editingTemplateId.value) {
|
||||
const payload = buildTemplateUpdatePayload();
|
||||
await updatePermissionTemplate(editingTemplateId.value, payload);
|
||||
ElMessage.success("角色已更新");
|
||||
} else {
|
||||
const payload = buildTemplateCreatePayload();
|
||||
await createPermissionTemplate(payload);
|
||||
ElMessage.success("角色已创建");
|
||||
}
|
||||
|
||||
@@ -35,4 +35,12 @@ describe("ProjectMembers user directory access", () => {
|
||||
expect(source).toContain(':disabled="!canAssignRole(\'ADMIN\')"');
|
||||
expect(source).toContain(':disabled="!canEditMember(scope.row)');
|
||||
});
|
||||
|
||||
it("does not offer QA as a project member role preset", () => {
|
||||
const source = readProjectMembers();
|
||||
|
||||
expect(source).not.toContain('value="QA"');
|
||||
expect(source).not.toContain("canAssignRole('QA')");
|
||||
expect(source).not.toContain("QA: 60");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
<el-option :label="TEXT.enums.userRole.PV" value="PV" :disabled="!canAssignRole('PV')" />
|
||||
<el-option :label="TEXT.enums.userRole.MEDICAL_REVIEW" value="MEDICAL_REVIEW" :disabled="!canAssignRole('MEDICAL_REVIEW')" />
|
||||
<el-option :label="TEXT.enums.userRole.IMP" value="IMP" :disabled="!canAssignRole('IMP')" />
|
||||
<el-option :label="TEXT.enums.userRole.QA" value="QA" :disabled="!canAssignRole('QA')" />
|
||||
<el-option :label="TEXT.enums.userRole.ADMIN" value="ADMIN" :disabled="!canAssignRole('ADMIN')" />
|
||||
</el-select>
|
||||
<span v-if="scope.row.effectiveStatus === 'DISABLED_GLOBAL'" class="hint">{{ TEXT.modules.adminProjectMembers.disabledGlobalDesc }}</span>
|
||||
@@ -86,7 +85,6 @@
|
||||
<el-option :label="TEXT.enums.userRole.PV" value="PV" :disabled="!canAssignRole('PV')" />
|
||||
<el-option :label="TEXT.enums.userRole.MEDICAL_REVIEW" value="MEDICAL_REVIEW" :disabled="!canAssignRole('MEDICAL_REVIEW')" />
|
||||
<el-option :label="TEXT.enums.userRole.IMP" value="IMP" :disabled="!canAssignRole('IMP')" />
|
||||
<el-option :label="TEXT.enums.userRole.QA" value="QA" :disabled="!canAssignRole('QA')" />
|
||||
<el-option :label="TEXT.enums.userRole.ADMIN" value="ADMIN" :disabled="!canAssignRole('ADMIN')" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -133,7 +131,6 @@ const projectRole = computed(() => project.value?.role_in_study || "");
|
||||
const roleRank: Record<string, number> = {
|
||||
ADMIN: 100,
|
||||
PM: 80,
|
||||
QA: 60,
|
||||
PV: 50,
|
||||
MEDICAL_REVIEW: 50,
|
||||
CRA: 40,
|
||||
|
||||
Reference in New Issue
Block a user