清理全局角色残留并修复项目权限判断
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user