清理全局角色残留并修复项目权限判断

This commit is contained in:
Cheng Zhou
2026-05-29 10:20:42 +08:00
parent 63457aab11
commit 44d69c2d7b
63 changed files with 739 additions and 443 deletions
+31 -19
View File
@@ -3,7 +3,7 @@
<div class="main-content-flat unified-shell">
<div class="unified-action-bar actions-only-bar">
<div class="filter-spacer"></div>
<el-button v-if="canManageMembers" type="primary" @click="openAdd">
<el-button v-if="canAddMember" type="primary" @click="openAdd">
{{ TEXT.common.actions.add }}{{ TEXT.modules.adminProjectMembers.memberLabel }}
</el-button>
</div>
@@ -50,7 +50,7 @@
/>
</el-select>
<span v-if="scope.row.effectiveStatus === 'DISABLED_GLOBAL'" class="hint">{{ TEXT.modules.adminProjectMembers.disabledGlobalDesc }}</span>
<el-button link type="danger" size="small" :disabled="!canEditMember(scope.row)" @click="onDelete(scope.row)">{{ TEXT.common.actions.delete }}</el-button>
<el-button link type="danger" size="small" :disabled="!canDeleteProjectMember(scope.row)" @click="onDelete(scope.row)">{{ TEXT.common.actions.delete }}</el-button>
<el-button
link
:type="scope.row.is_active ? 'danger' : 'primary'"
@@ -130,7 +130,12 @@ const newMember = reactive({
user_id: "",
role_in_study: "PM",
});
const canManageMembers = computed(() => permission.can("project.members.manage"));
const canListMembers = computed(() => permission.can("project.members.list"));
const canListMemberCandidates = computed(() => permission.can("project.members.candidates"));
const canCreateMember = computed(() => permission.can("project.members.create"));
const canUpdateMember = computed(() => permission.can("project.members.update"));
const canDeleteMember = computed(() => permission.can("project.members.delete"));
const canAddMember = computed(() => canCreateMember.value && canListMemberCandidates.value);
const projectRole = computed(() => project.value?.role_in_study || "");
const ROLE_KEYS = ["PM", "CRA", "PV", "QA", "CTA", "ADMIN"];
const roleOptions = computed(() => roleOptionsFor(ROLE_KEYS));
@@ -144,13 +149,14 @@ const roleRank: Record<string, number> = {
};
const currentRoleRank = computed(() => auth.user?.is_admin ? Number.POSITIVE_INFINITY : roleRank[projectRole.value] || 0);
const canAssignRole = (role: string) => (auth.user?.is_admin ? true : (roleRank[role] || 0) <= currentRoleRank.value);
const canEditMember = (row: StudyMember) => {
if (!canManageMembers.value) return false;
const canMutateProjectMember = (row: StudyMember) => {
if (row.user?.is_admin) return false;
if (auth.user?.is_admin) return true;
if (row.user_id === auth.user?.id) return false;
return (roleRank[row.role_in_study] || 0) <= currentRoleRank.value;
};
const canEditMember = (row: StudyMember) => canUpdateMember.value && canMutateProjectMember(row);
const canDeleteProjectMember = (row: StudyMember) => canDeleteMember.value && canMutateProjectMember(row);
const addRules = reactive<FormRules>({
user_id: [{ required: true, message: requiredMessage(TEXT.modules.adminProjectMembers.user), trigger: "change" }],
@@ -168,16 +174,12 @@ const loadProject = async () => {
};
const loadMembers = async () => {
if (!projectId.value) return;
if (!canListMembers.value || !projectId.value) return;
loading.value = true;
try {
const { data } = await listMembers(projectId.value, { limit: 500, include_inactive: true });
members.value = Array.isArray(data) ? data : data.items || [];
if (!canManageMembers.value) {
users.value = members.value
.map((member) => member.user)
.filter((user): user is UserInfo => Boolean(user?.id)) as UserInfo[];
}
if (!canListMemberCandidates.value) syncUsersFromMembers();
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || TEXT.modules.adminProjectMembers.loadFailed);
} finally {
@@ -185,9 +187,15 @@ const loadMembers = async () => {
}
};
const syncUsersFromMembers = () => {
users.value = members.value
.map((member) => member.user)
.filter((user): user is UserInfo => Boolean(user?.id)) as UserInfo[];
};
const loadUsers = async () => {
if (!canManageMembers.value || !projectId.value) {
users.value = [];
if (!canListMemberCandidates.value || !projectId.value) {
syncUsersFromMembers();
return;
}
try {
@@ -211,7 +219,7 @@ const memberRows = computed(() =>
);
const openAdd = () => {
if (!canManageMembers.value) return;
if (!canAddMember.value) return;
newMember.user_id = "";
newMember.role_in_study = canAssignRole("PM") ? "PM" : "CRA";
addVisible.value = true;
@@ -219,9 +227,13 @@ const openAdd = () => {
const submitAdd = async () => {
if (!projectId.value) return;
if (!canAddMember.value) {
ElMessage.warning(TEXT.common.messages.noPermission);
return;
}
const decision = evaluateAction({
actorRole: auth.user?.is_admin ? "ADMIN" : null,
requiredPermission: "project.members.manage",
requiredPermission: "project.members.create",
target: { projectId: projectId.value },
});
if (!decision.allowed) {
@@ -256,7 +268,7 @@ const updateRole = async (memberId: string, role: string) => {
}
const decision = evaluateAction({
actorRole: auth.user?.is_admin ? "ADMIN" : null,
requiredPermission: "project.members.manage",
requiredPermission: "project.members.update",
target: { projectId: projectId.value, memberId },
});
if (!decision.allowed) {
@@ -281,7 +293,7 @@ const toggleActive = async (row: StudyMember) => {
}
const decision = evaluateAction({
actorRole: auth.user?.is_admin ? "ADMIN" : null,
requiredPermission: "project.members.manage",
requiredPermission: "project.members.update",
target: { projectId: projectId.value, memberId: row.id },
});
if (!decision.allowed) {
@@ -314,13 +326,13 @@ const toggleActive = async (row: StudyMember) => {
const onDelete = async (row: StudyMember) => {
if (!projectId.value) return;
if (!canEditMember(row)) {
if (!canDeleteProjectMember(row)) {
ElMessage.warning(TEXT.common.messages.noPermission);
return;
}
const decision = evaluateAction({
actorRole: auth.user?.is_admin ? "ADMIN" : null,
requiredPermission: "project.members.manage",
requiredPermission: "project.members.delete",
target: { projectId: projectId.value, memberId: row.id },
});
if (!decision.allowed) {