清理全局角色残留并修复项目权限判断
This commit is contained in:
@@ -73,7 +73,7 @@
|
||||
<el-tag v-if="memberRows.length" effect="plain" size="small" round>{{ memberRows.length }} 名成员</el-tag>
|
||||
</div>
|
||||
<div class="sub-tab-actions">
|
||||
<el-button type="primary" @click="openAddMember">
|
||||
<el-button v-if="canAddProjectMember" type="primary" @click="openAddMember">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 14px; height: 14px; margin-right: 4px"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/></svg>
|
||||
添加成员
|
||||
</el-button>
|
||||
@@ -96,7 +96,7 @@
|
||||
size="small"
|
||||
style="width: 130px"
|
||||
@change="(val: string) => updateMemberRole(row.id, val)"
|
||||
:disabled="!canEditMember(row) || !row.is_active || row.effectiveStatus === 'DISABLED_GLOBAL'"
|
||||
:disabled="!canUpdateProjectMember(row) || !row.is_active || row.effectiveStatus === 'DISABLED_GLOBAL'"
|
||||
>
|
||||
<el-option
|
||||
v-for="(label, val) in memberRoleLabels(row)"
|
||||
@@ -128,12 +128,12 @@
|
||||
link
|
||||
:type="row.is_active ? 'warning' : 'primary'"
|
||||
size="small"
|
||||
:disabled="!canEditMember(row) || row.effectiveStatus === 'DISABLED_GLOBAL'"
|
||||
:disabled="!canUpdateProjectMember(row) || row.effectiveStatus === 'DISABLED_GLOBAL'"
|
||||
@click="toggleMemberActive(row)"
|
||||
>
|
||||
{{ row.is_active ? "停用" : "启用" }}
|
||||
</el-button>
|
||||
<el-button link type="danger" size="small" :disabled="!canEditMember(row)" @click="deleteMember(row)">
|
||||
<el-button link type="danger" size="small" :disabled="!canDeleteProjectMember(row)" @click="deleteMember(row)">
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -480,6 +480,18 @@ const isAdmin = computed(() => isSystemAdmin(auth.user));
|
||||
const selectedProjectRole = computed(() => selectedStudy.value?.role_in_study || "");
|
||||
const isSelectedProjectPm = computed(() => selectedProjectRole.value === "PM");
|
||||
const canManageSelectedProject = computed(() => isAdmin.value || isSelectedProjectPm.value);
|
||||
const selectedProjectPermissionAllowed = (operationKey: string) => {
|
||||
if (isAdmin.value) return true;
|
||||
const role = selectedProjectRole.value;
|
||||
if (!role || !apiMatrix.value) return false;
|
||||
return isApiPermissionAllowed(apiMatrix.value?.[role]?.[operationKey]);
|
||||
};
|
||||
const canListProjectMembers = computed(() => selectedProjectPermissionAllowed("project_members:list"));
|
||||
const canListProjectMemberCandidates = computed(() => selectedProjectPermissionAllowed("project_members:candidates"));
|
||||
const canCreateProjectMember = computed(() => selectedProjectPermissionAllowed("project_members:create"));
|
||||
const canUpdateProjectMembers = computed(() => selectedProjectPermissionAllowed("project_members:update"));
|
||||
const canDeleteProjectMembers = computed(() => selectedProjectPermissionAllowed("project_members:delete"));
|
||||
const canAddProjectMember = computed(() => canCreateProjectMember.value && canListProjectMemberCandidates.value);
|
||||
|
||||
const { roleLabel, roleDescription, roleIcon, compareRolesByTemplateOrder, loadRoleTemplates } = useRoleTemplateMeta();
|
||||
const ROLE_RANK: Record<string, number> = {
|
||||
@@ -495,7 +507,7 @@ const assignableRoleLabels = computed(() => {
|
||||
return result;
|
||||
});
|
||||
const memberRoleLabels = (row: MemberRoleRow) => {
|
||||
if (canEditMember(row)) return assignableRoleLabels.value;
|
||||
if (canUpdateProjectMember(row)) return assignableRoleLabels.value;
|
||||
return {
|
||||
...assignableRoleLabels.value,
|
||||
[row.role_in_study]: roleLabel(row.role_in_study),
|
||||
@@ -574,7 +586,8 @@ const onStudyChange = async (id: string) => {
|
||||
candidates.value = [];
|
||||
activeRolesDraft.value = [];
|
||||
router.replace({ query: { ...route.query, projectId: id } });
|
||||
await Promise.all([loadPermissionData(), loadMembers(), loadActiveRoles()]);
|
||||
await loadPermissionData();
|
||||
await Promise.all([loadMembers(), loadActiveRoles()]);
|
||||
loadCandidates();
|
||||
};
|
||||
|
||||
@@ -617,13 +630,14 @@ const addMemberRules: FormRules = {
|
||||
role_in_study: [{ required: true, message: "请选择角色", trigger: "change" }],
|
||||
};
|
||||
|
||||
const canEditMember = (row: MemberRoleRow) => {
|
||||
if (!canManageSelectedProject.value) return false;
|
||||
const canMutateProjectMember = (row: MemberRoleRow) => {
|
||||
if (row.user_id === auth.user?.id) return false;
|
||||
if (row.user?.is_admin) return false;
|
||||
if (!isAdmin.value && (ROLE_RANK[row.role_in_study] ?? 0) >= ROLE_RANK.PM) return false;
|
||||
return true;
|
||||
};
|
||||
const canUpdateProjectMember = (row: MemberRoleRow) => canUpdateProjectMembers.value && canMutateProjectMember(row);
|
||||
const canDeleteProjectMember = (row: MemberRoleRow) => canDeleteProjectMembers.value && canMutateProjectMember(row);
|
||||
|
||||
const memberRows = computed(() =>
|
||||
members.value.map((m) => {
|
||||
@@ -639,7 +653,7 @@ const availableCandidates = computed(() => {
|
||||
});
|
||||
|
||||
const loadMembers = async () => {
|
||||
if (!selectedStudyId.value) return;
|
||||
if (!canListProjectMembers.value || !selectedStudyId.value) return;
|
||||
membersLoading.value = true;
|
||||
try {
|
||||
const { data } = await listMembers(selectedStudyId.value, { limit: 500, include_inactive: true });
|
||||
@@ -652,7 +666,10 @@ const loadMembers = async () => {
|
||||
};
|
||||
|
||||
const loadCandidates = async () => {
|
||||
if (!selectedStudyId.value) return;
|
||||
if (!canListProjectMemberCandidates.value || !selectedStudyId.value) {
|
||||
candidates.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { data } = await listMemberCandidates(selectedStudyId.value, { limit: 500 });
|
||||
candidates.value = data ?? [];
|
||||
@@ -662,6 +679,7 @@ const loadCandidates = async () => {
|
||||
};
|
||||
|
||||
const openAddMember = () => {
|
||||
if (!canAddProjectMember.value) return;
|
||||
addMemberForm.user_id = "";
|
||||
addMemberForm.role_in_study = Object.keys(assignableRoleLabels.value)[0] || "";
|
||||
addMemberVisible.value = true;
|
||||
@@ -670,6 +688,10 @@ const openAddMember = () => {
|
||||
const resetAddMemberForm = () => { addMemberFormRef.value?.clearValidate(); };
|
||||
|
||||
const submitAddMember = async () => {
|
||||
if (!canAddProjectMember.value) {
|
||||
ElMessage.warning("无权限添加成员");
|
||||
return;
|
||||
}
|
||||
const valid = await addMemberFormRef.value?.validate().catch(() => false);
|
||||
if (!valid) return;
|
||||
addMemberSaving.value = true;
|
||||
@@ -686,6 +708,12 @@ const submitAddMember = async () => {
|
||||
};
|
||||
|
||||
const updateMemberRole = async (memberId: string, role: string) => {
|
||||
const row = members.value.find((item) => item.id === memberId);
|
||||
if (!row || !canUpdateProjectMember(row)) {
|
||||
ElMessage.warning("无权限更新成员");
|
||||
loadMembers();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await updateMember(selectedStudyId.value, memberId, { role_in_study: role });
|
||||
ElMessage.success("角色已更新");
|
||||
@@ -696,6 +724,10 @@ const updateMemberRole = async (memberId: string, role: string) => {
|
||||
};
|
||||
|
||||
const toggleMemberActive = async (row: any) => {
|
||||
if (!canUpdateProjectMember(row)) {
|
||||
ElMessage.warning("无权限更新成员");
|
||||
return;
|
||||
}
|
||||
if (row.is_active) {
|
||||
const ok = await ElMessageBox.confirm("确认停用该成员?", "停用成员", { type: "warning" }).catch(() => null);
|
||||
if (!ok) return;
|
||||
@@ -710,6 +742,10 @@ const toggleMemberActive = async (row: any) => {
|
||||
};
|
||||
|
||||
const deleteMember = async (row: any) => {
|
||||
if (!canDeleteProjectMember(row)) {
|
||||
ElMessage.warning("无权限移除成员");
|
||||
return;
|
||||
}
|
||||
const ok = await ElMessageBox.confirm("确认将该账号从本项目移除?账号本身不会被删除。", "移除成员", {
|
||||
type: "warning",
|
||||
}).catch(() => null);
|
||||
@@ -973,8 +1009,10 @@ const PERMISSION_SECTION_LABELS: Record<string, string> = {
|
||||
subject_histories: "病史",
|
||||
monitoring_issues: "监查访视问题",
|
||||
project_milestones: "项目里程碑",
|
||||
startup_initiation: "立项记录",
|
||||
startup_ethics: "伦理记录",
|
||||
startup_initiation: "立项",
|
||||
startup_initiation_attachments: "立项",
|
||||
startup_ethics: "伦理",
|
||||
startup_ethics_attachments: "伦理",
|
||||
precautions: "注意事项",
|
||||
precautions_attachments: "注意事项",
|
||||
faq: "医学咨询/FAQ",
|
||||
@@ -1147,7 +1185,8 @@ onMounted(async () => {
|
||||
if (initialStudyId) {
|
||||
selectedStudyId.value = initialStudyId;
|
||||
if (qSub === "members") projectSubTab.value = "members";
|
||||
await Promise.all([loadPermissionData(), loadMembers(), loadActiveRoles()]);
|
||||
await loadPermissionData();
|
||||
await Promise.all([loadMembers(), loadActiveRoles()]);
|
||||
loadCandidates();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user