feat: 角色页成员维护弹窗与用户角色清空

- 角色页新增「成员」动作,弹窗展示姓名/账号/所属组织/状态,支持搜索添加启用用户与移除
- api/system/role 增加 pageRoleMembers/addRoleMember/removeRoleMember,走 THINK_API_URL
- api/system/user 的 updateUser 改走 THINK_API_URL,空角色由后端清空
- 用户编辑表单允许角色为空,保存后可删除该用户全部角色关联
This commit is contained in:
2026-10-30 19:59:48 +08:00
parent 80d7eec4f8
commit 634e1c12af
6 changed files with 321 additions and 13 deletions
@@ -0,0 +1,230 @@
<!-- 角色成员维护弹窗 -->
<template>
<ele-modal
:width="720"
title="角色成员"
:visible="visible"
:body-style="{ paddingBottom: '8px' }"
:footer="null"
@update:visible="updateVisible"
>
<div style="margin-bottom: 12px">
<a-space>
<a-select
v-model:value="selectedUserId"
show-search
allow-clear
:filter-option="false"
:options="userOptions"
:loading="userLoading"
placeholder="输入姓名或账号搜索启用用户"
style="width: 320px"
@search="searchUsers"
/>
<a-button
type="primary"
:loading="adding"
:disabled="!selectedUserId"
@click="addOne"
>
添加成员
</a-button>
</a-space>
<div class="ele-text-secondary" style="margin-top: 6px">
角色:{{ data?.roleName }}({{ data?.roleCode }})
</div>
</div>
<a-table
size="small"
row-key="userId"
:columns="columns"
:data-source="members"
:loading="loading"
:pagination="false"
:scroll="{ y: '46vh' }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'userName'">
{{ record.realName || record.nickname || '-' }}
</template>
<template v-else-if="column.key === 'organizationName'">
{{ record.organizationName || '-' }}
</template>
<template v-else-if="column.key === 'status'">
<a-tag :color="record.status === 0 ? 'green' : 'red'">
{{ record.status === 0 ? '启用' : '停用' }}
</a-tag>
</template>
<template v-else-if="column.key === 'action'">
<a-popconfirm
placement="topRight"
title="确定要移除该成员吗?"
@confirm="removeOne(record)"
>
<a class="ele-text-danger">移除</a>
</a-popconfirm>
</template>
</template>
</a-table>
</ele-modal>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { ColumnItem } from 'ele-admin-pro/es/ele-pro-table/types';
import {
addRoleMember,
pageRoleMembers,
removeRoleMember
} from '@/api/system/role';
import type { Role, RoleMember } from '@/api/system/role/model';
import { pageUsers } from '@/api/system/user';
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 当前角色数据
data?: Role | null;
}>();
// 表格列配置
const columns = ref<ColumnItem[]>([
{ title: '姓名', key: 'userName', width: 120 },
{ title: '账号', dataIndex: 'username', width: 150 },
{ title: '所属组织', key: 'organizationName' },
{ title: '状态', key: 'status', width: 90, align: 'center' },
{ title: '操作', key: 'action', width: 90, align: 'center' }
]);
// 成员列表
const members = ref<RoleMember[]>([]);
// 成员请求状态
const loading = ref(false);
// 添加状态
const adding = ref(false);
// 候选用户请求状态
const userLoading = ref(false);
// 候选用户下拉选项
const userOptions = ref<{ label: string; value: number }[]>([]);
// 选中的候选用户
const selectedUserId = ref<number | undefined>(undefined);
// 用户搜索关键词
const keyword = ref('');
// 当前角色id
const roleId = computed(() => props.data?.roleId);
// 已是成员的候选用户id
const memberIds = computed(() => new Set(members.value.map((d) => d.userId)));
/* 查询角色成员 */
const queryMembers = () => {
if (!roleId.value) {
members.value = [];
return;
}
loading.value = true;
pageRoleMembers({ roleId: roleId.value, page: 1, limit: 2000 })
.then((res) => {
loading.value = false;
members.value = res?.list ?? [];
})
.catch((e) => {
loading.value = false;
members.value = [];
message.error(e.message);
});
};
/* 搜索启用用户作为候选 */
const searchUsers = (value?: string) => {
keyword.value = value ?? '';
userLoading.value = true;
pageUsers({
keywords: keyword.value || undefined,
status: 0,
page: 1,
limit: 20
})
.then((res) => {
userLoading.value = false;
userOptions.value = (res?.list ?? [])
.filter((d) => d.userId != null && !memberIds.value.has(d.userId))
.map((d) => ({
label: `${d.realName || d.nickname || d.username}(${d.username})`,
value: d.userId as number
}));
})
.catch((e) => {
userLoading.value = false;
userOptions.value = [];
message.error(e.message);
});
};
/* 添加成员 */
const addOne = () => {
if (!roleId.value || selectedUserId.value == null) {
return;
}
adding.value = true;
addRoleMember({ roleId: roleId.value, userId: selectedUserId.value })
.then((msg) => {
adding.value = false;
selectedUserId.value = undefined;
message.success(msg);
queryMembers();
searchUsers(keyword.value);
})
.catch((e) => {
adding.value = false;
message.error(e.message);
});
};
/* 移除成员 */
const removeOne = (row: RoleMember) => {
if (!roleId.value || row.userId == null) {
return;
}
removeRoleMember(roleId.value, row.userId)
.then((msg) => {
message.success(msg);
queryMembers();
searchUsers(keyword.value);
})
.catch((e) => {
message.error(e.message);
});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
watch(
() => props.visible,
(visible) => {
if (visible) {
members.value = [];
userOptions.value = [];
selectedUserId.value = undefined;
keyword.value = '';
queryMembers();
searchUsers('');
}
}
);
</script>