Files
gxtyzx-admin/src/views/system/role/components/role-member.vue
T
weicw 634e1c12af feat: 角色页成员维护弹窗与用户角色清空
- 角色页新增「成员」动作,弹窗展示姓名/账号/所属组织/状态,支持搜索添加启用用户与移除
- api/system/role 增加 pageRoleMembers/addRoleMember/removeRoleMember,走 THINK_API_URL
- api/system/user 的 updateUser 改走 THINK_API_URL,空角色由后端清空
- 用户编辑表单允许角色为空,保存后可删除该用户全部角色关联
2026-10-30 19:59:48 +08:00

231 lines
6.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 角色成员维护弹窗 -->
<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>