😎优化角色管理,拆分菜单授权

This commit is contained in:
zuohuaijun 2024-08-13 02:35:31 +08:00
parent 5603e52553
commit 1973f6dbe2
9 changed files with 210 additions and 175 deletions

View File

@ -34,11 +34,6 @@ public class AddRoleInput : SysRole
/// </summary>
[Required(ErrorMessage = "角色名称不能为空")]
public override string Name { get; set; }
/// <summary>
/// 菜单Id集合
/// </summary>
public List<long> MenuIdList { get; set; }
}
public class UpdateRoleInput : AddRoleInput

View File

@ -18,7 +18,6 @@ public class SysRoleService : IDynamicApiController, ITransient
private readonly SysRoleMenuService _sysRoleMenuService;
private readonly SysRoleApiService _sysRoleApiService;
private readonly SysOrgService _sysOrgService;
private readonly SysMenuService _sysMenuService;
private readonly SysUserRoleService _sysUserRoleService;
private readonly SysCacheService _sysCacheService;
private readonly SysCommonService _sysCommonService;
@ -29,7 +28,6 @@ public class SysRoleService : IDynamicApiController, ITransient
SysRoleMenuService sysRoleMenuService,
SysRoleApiService sysRoleApiService,
SysOrgService sysOrgService,
SysMenuService sysMenuService,
SysUserRoleService sysUserRoleService,
SysCacheService sysCacheService,
SysCommonService sysCommonService)
@ -40,7 +38,6 @@ public class SysRoleService : IDynamicApiController, ITransient
_sysRoleMenuService = sysRoleMenuService;
_sysRoleApiService = sysRoleApiService;
_sysOrgService = sysOrgService;
_sysMenuService = sysMenuService;
_sysUserRoleService = sysUserRoleService;
_sysCacheService = sysCacheService;
_sysCommonService = sysCommonService;
@ -97,31 +94,7 @@ public class SysRoleService : IDynamicApiController, ITransient
if (await _sysRoleRep.IsAnyAsync(u => u.Name == input.Name && u.Code == input.Code))
throw Oops.Oh(ErrorCodeEnum.D1006);
var newRole = await _sysRoleRep.AsInsertable(input.Adapt<SysRole>()).ExecuteReturnEntityAsync();
input.Id = newRole.Id;
await UpdateRoleMenu(input);
}
/// <summary>
/// 更新角色菜单权限
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private async Task UpdateRoleMenu(AddRoleInput input)
{
if (input.MenuIdList == null || input.MenuIdList.Count < 1)
return;
// 将父节点为0的菜单排除防止前端全选异常
var pMenuIds = await _sysRoleRep.ChangeRepository<SqlSugarRepository<SysMenu>>().AsQueryable().Where(u => input.MenuIdList.Contains(u.Id) && u.Pid == 0).ToListAsync(u => u.Id);
var menuIds = input.MenuIdList.Except(pMenuIds); // 差集
await GrantMenu(new RoleMenuInput()
{
Id = input.Id,
MenuIdList = menuIds.ToList()
});
await ClearUserApiCache(input.Id);
await _sysRoleRep.InsertAsync(input.Adapt<SysRole>());
}
/// <summary>
@ -138,8 +111,6 @@ public class SysRoleService : IDynamicApiController, ITransient
await _sysRoleRep.AsUpdateable(input.Adapt<SysRole>()).IgnoreColumns(true)
.IgnoreColumns(u => new { u.DataScope }).ExecuteCommandAsync();
await UpdateRoleMenu(input);
}
/// <summary>
@ -182,11 +153,24 @@ public class SysRoleService : IDynamicApiController, ITransient
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[UnitOfWork]
[DisplayName("授权角色菜单")]
public async Task GrantMenu(RoleMenuInput input)
{
if (input.MenuIdList == null || input.MenuIdList.Count < 1)
return;
//// 将父节点为0的菜单排除防止前端全选异常
//var pMenuIds = await _sysRoleRep.ChangeRepository<SqlSugarRepository<SysMenu>>().AsQueryable().Where(u => input.MenuIdList.Contains(u.Id) && u.Pid == 0).ToListAsync(u => u.Id);
//var menuIds = input.MenuIdList.Except(pMenuIds); // 差集
//await _sysRoleMenuService.GrantRoleMenu(new RoleMenuInput()
//{
// Id = input.Id,
// MenuIdList = menuIds.ToList()
//});
await _sysRoleMenuService.GrantRoleMenu(input);
await ClearUserApiCache(input.Id);
}
/// <summary>
@ -233,25 +217,16 @@ public class SysRoleService : IDynamicApiController, ITransient
}
/// <summary>
/// 根据角色Id获取菜单Id集合 🔖
/// 授权角色接口 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("根据角色Id获取菜单Id集合")]
public async Task<List<long>> GetOwnMenuList([FromQuery] RoleInput input)
[UnitOfWork]
[DisplayName("授权角色接口")]
public async Task GrantApi(RoleApiInput input)
{
return await _sysRoleMenuService.GetRoleMenuIdList(new List<long> { input.Id });
}
/// <summary>
/// 根据角色Id获取机构Id集合 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("根据角色Id获取机构Id集合")]
public async Task<List<long>> GetOwnOrgList([FromQuery] RoleInput input)
{
return await _sysRoleOrgService.GetRoleOrgIdList(new List<long> { input.Id });
await ClearUserApiCache(input.Id);
await _sysRoleApiService.GrantRoleApi(input);
}
/// <summary>
@ -272,30 +247,25 @@ public class SysRoleService : IDynamicApiController, ITransient
}
/// <summary>
/// 授权角色接口 🔖
/// 根据角色Id获取菜单Id集合 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[UnitOfWork]
[DisplayName("授权角色接口")]
public async Task GrantApi(RoleApiInput input)
[DisplayName("根据角色Id获取菜单Id集合")]
public async Task<List<long>> GetOwnMenuList([FromQuery] RoleInput input)
{
await ClearUserApiCache(input.Id);
await _sysRoleApiService.GrantRoleApi(input);
return await _sysRoleMenuService.GetRoleMenuIdList(new List<long> { input.Id });
}
/// <summary>
/// 删除与该角色相关的用户接口缓存
/// 根据角色Id获取机构Id集合 🔖
/// </summary>
/// <param name="roleId"></param>
/// <param name="input"></param>
/// <returns></returns>
private async Task ClearUserApiCache(long roleId)
[DisplayName("根据角色Id获取机构Id集合")]
public async Task<List<long>> GetOwnOrgList([FromQuery] RoleInput input)
{
var userIdList = await _sysUserRoleService.GetUserIdList(roleId);
foreach (var userId in userIdList)
{
_sysCacheService.Remove(CacheConst.KeyUserApi + userId);
}
return await _sysRoleOrgService.GetRoleOrgIdList(new List<long> { input.Id });
}
/// <summary>
@ -397,4 +367,18 @@ public class SysRoleService : IDynamicApiController, ITransient
.WhereIF(menuIds.Count > 0, u => menuIds.Contains(u.Id))
.Select(u => u.Permission).ToListAsync();
}
/// <summary>
/// 删除与该角色相关的用户接口缓存
/// </summary>
/// <param name="roleId"></param>
/// <returns></returns>
private async Task ClearUserApiCache(long roleId)
{
var userIdList = await _sysUserRoleService.GetUserIdList(roleId);
foreach (var userId in userIdList)
{
_sysCacheService.Remove(CacheConst.KeyUserApi + userId);
}
}
}

View File

@ -144,12 +144,4 @@ export interface AddRoleInput {
* @memberof AddRoleInput
*/
name: string;
/**
* Id集合
*
* @type {Array<number>}
* @memberof AddRoleInput
*/
menuIdList?: Array<number> | null;
}

View File

@ -144,12 +144,4 @@ export interface UpdateRoleInput {
* @memberof UpdateRoleInput
*/
name: string;
/**
* Id集合
*
* @type {Array<number>}
* @memberof UpdateRoleInput
*/
menuIdList?: Array<number> | null;
}

View File

@ -1,6 +1,6 @@
<template>
<div class="sys-role-container">
<el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false">
<el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false" width="700px">
<template #header>
<div style="color: #fff">
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Edit /> </el-icon>
@ -37,21 +37,6 @@
<el-input v-model="state.ruleForm.remark" placeholder="请输入备注内容" clearable type="textarea" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="菜单权限" v-loading="state.loading">
<el-tree
ref="treeRef"
:data="state.menuData"
node-key="id"
show-checkbox
:props="{ children: 'children', label: 'title', class: treeNodeClass }"
icon="ele-Menu"
highlight-current
default-expand-all
style="height: 500px; overflow-y: auto; width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
@ -65,46 +50,27 @@
</template>
<script lang="ts" setup name="sysEditRole">
import { onMounted, reactive, ref } from 'vue';
import type { ElTree } from 'element-plus';
import { reactive, ref } from 'vue';
import { getAPI } from '/@/utils/axios-utils';
import { SysMenuApi, SysRoleApi } from '/@/api-services/api';
import { SysMenu, UpdateRoleInput } from '/@/api-services/models';
import { SysRoleApi } from '/@/api-services/api';
import { UpdateRoleInput } from '/@/api-services/models';
const props = defineProps({
title: String,
});
const emits = defineEmits(['handleQuery']);
const ruleFormRef = ref();
const treeRef = ref<InstanceType<typeof ElTree>>();
const state = reactive({
loading: false,
isShowDialog: false,
ruleForm: {} as UpdateRoleInput,
menuData: [] as Array<SysMenu>, //
});
//
onMounted(async () => {
state.loading = true;
var res = await getAPI(SysMenuApi).apiSysMenuListGet();
state.menuData = res.data.result ?? [];
state.loading = false;
});
//
const openDialog = async (row: any) => {
ruleFormRef.value?.resetFields();
treeRef.value?.setCheckedKeys([]); //
state.ruleForm = JSON.parse(JSON.stringify(row));
if (row.id != undefined) {
var res = await getAPI(SysRoleApi).apiSysRoleOwnMenuListGet(row.id);
setTimeout(() => {
treeRef.value?.setCheckedKeys(res.data.result ?? []);
}, 100);
}
state.isShowDialog = true;
ruleFormRef.value?.resetFields();
};
//
@ -119,10 +85,9 @@ const cancel = () => {
};
//
const submit = () => {
const submit = async () => {
ruleFormRef.value.validate(async (valid: boolean) => {
if (!valid) return;
state.ruleForm.menuIdList = treeRef.value?.getCheckedKeys() as Array<number>; //.concat(treeRef.value?.getHalfCheckedKeys());
if (state.ruleForm.id != undefined && state.ruleForm.id > 0) {
await getAPI(SysRoleApi).apiSysRoleUpdatePost(state.ruleForm);
} else {
@ -132,49 +97,6 @@ const submit = () => {
});
};
//
const treeNodeClass = (node: SysMenu) => {
let addClass = true; //
for (var key in node.children) {
//
if (node.children[key].children?.length ?? 0 > 0) {
addClass = false;
break;
}
}
return addClass ? 'penultimate-node' : '';
};
//
defineExpose({ openDialog });
</script>
<style lang="scss" scoped>
.menu-data-tree {
width: 100%;
border: 1px solid var(--el-border-color);
border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
padding: 5px;
}
:deep(.penultimate-node) {
.el-tree-node__children {
padding-left: 40px;
white-space: pre-wrap;
line-height: 100%;
.el-tree-node {
display: inline-block;
}
.el-tree-node__content {
padding-left: 5px !important;
padding-right: 5px;
// .el-tree-node__expand-icon {
// display: none;
// }
}
}
}
</style>

View File

@ -95,9 +95,7 @@ const initTreeData = async () => {
state.loading = true;
var res = await getAPI(SysCommonApi).apiSysCommonApiListGet();
var tData = res.data.result ?? [];
treeRef.value?.setCheckedKeys([]); //
state.allApiData = tData;
state.loading = false;
};
@ -106,10 +104,9 @@ const initTreeData = async () => {
const openDrawer = async (row: any) => {
state.selectedTabName = 0;
state.roleId = row.id;
state.drawerTitle = '授权角色接口资源【' + row.name + '】';
state.isVisible = true;
state.loading = false;
state.drawerTitle = '设置角色接口黑名单【' + row.name + '】';
state.loading = true;
//
var res1 = await getAPI(SysRoleApi).apiSysRoleRoleApiListGet(state.roleId);
state.ownApiList = res1.data.result ?? [];
@ -119,6 +116,7 @@ const openDrawer = async (row: any) => {
}, 200);
state.loading = false;
state.isVisible = true;
};
//

View File

@ -4,7 +4,7 @@
<template #header>
<div style="color: #fff">
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Edit /> </el-icon>
<span> 授权数据范围 </span>
<span> {{ state.title }} </span>
</div>
</template>
<el-form :model="state.ruleForm" label-position="top">
@ -47,6 +47,7 @@ const emits = defineEmits(['handleQuery']);
const orgTreeRef = ref();
const state = reactive({
isShowDialog: false,
title: '',
ruleForm: {} as RoleOrgInput,
dataScopeType: [
{ value: 1, label: '全部数据' },
@ -59,6 +60,8 @@ const state = reactive({
//
const openDialog = async (row: any) => {
state.title = '授权角色数据范围【' + row.name + '】';
state.ruleForm = JSON.parse(JSON.stringify(row));
var res = await getAPI(SysRoleApi).apiSysRoleOwnOrgListGet(row.id);
setTimeout(() => {

View File

@ -0,0 +1,141 @@
<template>
<div class="sys-grantMenu-container">
<el-drawer v-model="state.isVisible" size="55%">
<template #header>
<div style="color: #fff">
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Menu /> </el-icon>
<span>{{ state.drawerTitle }}</span>
</div>
</template>
<div>
<NoticeBar leftIcon="iconfont icon-tongzhi2" text="非超管角色禁止授权系统核心模块菜单资源!!!" :scrollable="true" style="margin: 5px" />
</div>
<div v-loading="state.loading">
<el-tree
ref="treeRef"
:data="state.menuData"
node-key="id"
show-checkbox
:props="{ children: 'children', label: 'title', class: treeNodeClass }"
icon="ele-Menu"
highlight-current
default-expand-all
style="margin: 10px"
/>
</div>
<template #footer>
<div style="margin-bottom: 20px; margin-right: 20px">
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="submit"> </el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted, ref } from 'vue';
import type { ElTree } from 'element-plus';
import NoticeBar from '/@/components/noticeBar/index.vue';
import { getAPI } from '/@/utils/axios-utils';
import { SysMenuApi, SysRoleApi } from '/@/api-services/api';
import { SysMenu, UpdateRoleInput } from '/@/api-services/models';
const ruleFormRef = ref();
const treeRef = ref<InstanceType<typeof ElTree>>();
const state = reactive({
loading: false,
isVisible: false,
drawerTitle: '',
menuIdList: [] as any,
roleId: 0,
ruleForm: {} as UpdateRoleInput,
menuData: [] as Array<SysMenu>, //
});
//
onMounted(async () => {
state.loading = true;
var res = await getAPI(SysMenuApi).apiSysMenuListGet();
state.menuData = res.data.result ?? [];
state.loading = false;
});
//
const openDrawer = async (row: any) => {
state.roleId = row.id;
state.drawerTitle = '授权角色菜单【' + row.name + '】';
state.loading = true;
ruleFormRef.value?.resetFields();
treeRef.value?.setCheckedKeys([]); //
state.ruleForm = JSON.parse(JSON.stringify(row));
if (row.id != undefined) {
var res = await getAPI(SysRoleApi).apiSysRoleOwnMenuListGet(row.id);
setTimeout(() => {
treeRef.value?.setCheckedKeys(res.data.result ?? []);
}, 100);
}
state.loading = false;
state.isVisible = true;
};
//
const cancel = () => {
state.isVisible = false;
};
//
const submit = async () => {
state.menuIdList = treeRef.value?.getCheckedKeys() as Array<number>; //.concat(treeRef.value?.getHalfCheckedKeys());
await getAPI(SysRoleApi).apiSysRoleGrantMenuPost({ id: state.roleId, menuIdList: state.menuIdList });
cancel();
};
//
const treeNodeClass = (node: SysMenu) => {
let addClass = true; //
for (var key in node.children) {
//
if (node.children[key].children?.length ?? 0 > 0) {
addClass = false;
break;
}
}
return addClass ? 'penultimate-node' : '';
};
//
defineExpose({ openDrawer });
</script>
<style lang="scss" scoped>
// .menu-data-tree {
// width: 100%;
// border: 1px solid var(--el-border-color);
// border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
// padding: 5px;
// }
:deep(.penultimate-node) {
.el-tree-node__children {
padding-left: 35px;
white-space: pre-wrap;
line-height: 100%;
.el-tree-node {
display: inline-block;
}
.el-tree-node__content {
padding-left: 5px !important;
padding-right: 5px;
// .el-tree-node__expand-icon {
// display: none;
// }
}
}
}
</style>

View File

@ -58,16 +58,17 @@
<el-tooltip content="删除" placement="top">
<el-button icon="ele-Delete" size="small" text type="danger" @click="handleDelete(row)" v-auth="'sysRole/delete'"></el-button>
</el-tooltip>
<el-button icon="ele-OfficeBuilding" size="small" text type="primary" @click="openGrantData(row)" v-auth="'sysRole/grantDataScope'">数据范围</el-button>
<el-button icon="ele-Menu" size="small" text type="primary" @click="openGrantMenu(row)" v-auth="'sysRole/grantMenu'">授权菜单</el-button>
<el-button icon="ele-OfficeBuilding" size="small" text type="primary" @click="openGrantData(row)" v-auth="'sysRole/grantDataScope'">授权数据</el-button>
<el-button icon="ele-Link" size="small" text type="primary" @click="openGrantApi(row)" v-auth="'sysRole/grantApi'"> 接口黑名单 </el-button>
</template>
</vxe-grid>
</el-card>
<EditRole ref="editRoleRef" :title="state.title" @handleQuery="handleQuery" />
<GrantMenu ref="grantMenuRef" />
<GrantData ref="grantDataRef" @handleQuery="handleQuery" />
<GrantApi ref="grantApiRef" @handleQuery="handleQuery" />
<BaseApi ref="baseApiRef" />
<GrantApi ref="grantApiRef" />
</div>
</template>
@ -79,6 +80,7 @@ import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';
import EditRole from '/@/views/system/role/component/editRole.vue';
import GrantMenu from '/@/views/system/role/component/grantMenu.vue';
import GrantData from '/@/views/system/role/component/grantData.vue';
import GrantApi from '/@/views/system/role/component/grantApi.vue';
import ModifyRecord from '/@/components/table/modifyRecord.vue';
@ -89,6 +91,7 @@ import { PageRoleInput, PageRoleOutput } from '/@/api-services/models';
const xGrid = ref<VxeGridInstance>();
const editRoleRef = ref<InstanceType<typeof EditRole>>();
const grantMenuRef = ref<InstanceType<typeof GrantMenu>>();
const grantDataRef = ref<InstanceType<typeof GrantData>>();
const grantApiRef = ref<InstanceType<typeof GrantApi>>();
const state = reactive({
@ -121,7 +124,7 @@ const options = useVxeTable<PageRoleOutput>(
{ field: 'orderNo', title: '排序', width: 80, showOverflow: 'tooltip' },
{ field: 'status', title: '状态', width: 80, showOverflow: 'tooltip', slots: { default: 'row_status' } },
{ field: '', title: '修改记录', width: 100, showOverflow: 'tooltip', slots: { default: 'row_record' } },
{ title: '操作', fixed: 'right', width: 280, showOverflow: true, slots: { default: 'row_buttons' } },
{ title: '操作', fixed: 'right', width: 360, showOverflow: true, slots: { default: 'row_buttons' } },
],
},
// vxeGrid()vxe-table
@ -203,6 +206,11 @@ const gridEvents: VxeGridListeners<PageRoleOutput> = {
},
};
//
const openGrantMenu = (row: any) => {
grantMenuRef.value?.openDrawer(row);
};
//
const openGrantData = (row: any) => {
grantDataRef.value?.openDialog(row);