feat: 角色页成员维护弹窗与用户角色清空
- 角色页新增「成员」动作,弹窗展示姓名/账号/所属组织/状态,支持搜索添加启用用户与移除 - api/system/role 增加 pageRoleMembers/addRoleMember/removeRoleMember,走 THINK_API_URL - api/system/user 的 updateUser 改走 THINK_API_URL,空角色由后端清空 - 用户编辑表单允许角色为空,保存后可删除该用户全部角色关联
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { Role, RoleParam } from './model';
|
||||
import type { Role, RoleMember, RoleMemberParam, RoleParam } from './model';
|
||||
import type { Menu } from '../menu/model';
|
||||
import { SERVER_API_URL } from '@/config/setting';
|
||||
import { SERVER_API_URL, THINK_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询角色
|
||||
@@ -117,3 +117,45 @@ export async function updateRoleMenus(roleId?: number, data?: number[]) {
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询角色成员
|
||||
*/
|
||||
export async function pageRoleMembers(params: RoleMemberParam) {
|
||||
const res = await request.get<ApiResult<PageResult<RoleMember>>>(
|
||||
THINK_API_URL + '/system/role-member/page',
|
||||
{ params }
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加角色成员
|
||||
*/
|
||||
export async function addRoleMember(data: RoleMemberParam) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
THINK_API_URL + '/system/role-member',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除角色成员
|
||||
*/
|
||||
export async function removeRoleMember(roleId?: number, userId?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
THINK_API_URL + '/system/role-member',
|
||||
{ params: { roleId, userId } }
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
@@ -25,3 +25,31 @@ export interface RoleParam extends PageParam {
|
||||
roleCode?: string;
|
||||
comments?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色成员
|
||||
*/
|
||||
export interface RoleMember {
|
||||
// 用户id
|
||||
userId?: number;
|
||||
// 账号
|
||||
username?: string;
|
||||
// 真实姓名
|
||||
realName?: string;
|
||||
// 昵称
|
||||
nickname?: string;
|
||||
// 所属组织id
|
||||
organizationId?: number;
|
||||
// 所属组织名称
|
||||
organizationName?: string;
|
||||
// 状态, 0启用, 1停用
|
||||
status?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色成员查询/操作参数
|
||||
*/
|
||||
export interface RoleMemberParam extends PageParam {
|
||||
roleId?: number;
|
||||
userId?: number;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { User, UserParam } from './model';
|
||||
import { SERVER_API_URL } from '@/config/setting';
|
||||
import { SERVER_API_URL, THINK_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询用户
|
||||
@@ -97,7 +97,7 @@ export async function addUser(data: User) {
|
||||
*/
|
||||
export async function updateUser(data: User) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
SERVER_API_URL + '/system/user',
|
||||
THINK_API_URL + '/system/user',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
|
||||
@@ -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>
|
||||
@@ -39,6 +39,8 @@
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="openMember(record)">成员</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="openAuth(record)">分配权限</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
@@ -57,6 +59,8 @@
|
||||
<role-edit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<!-- 权限分配弹窗 -->
|
||||
<role-auth v-model:visible="showAuth" :data="current" />
|
||||
<!-- 成员维护弹窗 -->
|
||||
<role-member v-model:visible="showMember" :data="current" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -77,6 +81,7 @@
|
||||
import RoleSearch from './components/role-search.vue';
|
||||
import RoleEdit from './components/role-edit.vue';
|
||||
import RoleAuth from './components/role-auth.vue';
|
||||
import RoleMember from './components/role-member.vue';
|
||||
import { pageRoles, removeRole, removeRoles } from '@/api/system/role';
|
||||
import type { Role, RoleParam } from '@/api/system/role/model';
|
||||
|
||||
@@ -120,7 +125,7 @@
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
width: 260,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
@@ -137,6 +142,9 @@
|
||||
// 是否显示权限分配弹窗
|
||||
const showAuth = ref(false);
|
||||
|
||||
// 是否显示成员维护弹窗
|
||||
const showMember = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageRoles({ ...where, ...orders, page, limit });
|
||||
@@ -160,6 +168,12 @@
|
||||
showAuth.value = true;
|
||||
};
|
||||
|
||||
/* 打开成员维护弹窗 */
|
||||
const openMember = (row?: Role) => {
|
||||
current.value = row ?? null;
|
||||
showMember.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Role) => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
|
||||
@@ -255,14 +255,8 @@
|
||||
// trigger: 'blur'
|
||||
// }
|
||||
// ],
|
||||
roles: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择角色',
|
||||
type: 'array',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
// 角色可以为空:清空并保存表示删除该用户的全部角色关联
|
||||
roles: [],
|
||||
email: [
|
||||
{
|
||||
pattern: emailReg,
|
||||
|
||||
Reference in New Issue
Block a user