🧮优化账号角色权限

This commit is contained in:
KaneLeung 2024-09-30 08:59:07 +08:00
parent 5ca30dfee7
commit 4612aa6ad2
No known key found for this signature in database
GPG Key ID: 8F0FE0297C3B1256
6 changed files with 334 additions and 18 deletions

View File

@ -25,6 +25,11 @@ public class RoleOutput
/// 编码 /// 编码
/// </summary> /// </summary>
public string Code { get; set; } public string Code { get; set; }
/// <summary>
/// 是否禁用
/// </summary>
public bool Disabled { get; set; } = true;
} }
/// <summary> /// <summary>
@ -36,4 +41,19 @@ public class PageRoleOutput : SysRole
/// 租户名称 /// 租户名称
/// </summary> /// </summary>
public string TenantName { get; set; } public string TenantName { get; set; }
}
/// <summary>
/// 角色已分配可分配输出参数
/// </summary>
public class GrantRoleOutput
{
/// <summary>
/// 以分配
/// </summary>
public IEnumerable<RoleOutput> Granted { get; set; }
/// <summary>
/// 可分配
/// </summary>
public IEnumerable<RoleOutput> Available { get; set; }
} }

View File

@ -79,7 +79,7 @@ public class SysRoleService : IDynamicApiController, ITransient
return await _sysRoleRep.AsQueryable() return await _sysRoleRep.AsQueryable()
.WhereIF(!_userManager.SuperAdmin, u => u.TenantId == _userManager.TenantId) // 若非超管,则只能操作本租户的角色 .WhereIF(!_userManager.SuperAdmin, u => u.TenantId == _userManager.TenantId) // 若非超管,则只能操作本租户的角色
.WhereIF(!_userManager.SuperAdmin && !_userManager.SysAdmin, u => u.CreateUserId == _userManager.UserId || roleIdList.Contains(u.Id)) // 若非超管且非系统管理员,则只显示自己创建和已拥有的角色 .WhereIF(!_userManager.SuperAdmin && !_userManager.SysAdmin, u => u.CreateUserId == _userManager.UserId || roleIdList.Contains(u.Id)) // 若非超管且非系统管理员,则只显示自己创建和已拥有的角色
.OrderBy(u => new { u.OrderNo, u.Id }).Select<RoleOutput>().ToListAsync(); .OrderBy(u => new { u.OrderNo, u.Id }).Select(u => new RoleOutput { Disabled = false }, true).ToListAsync();
} }
/// <summary> /// <summary>

View File

@ -83,6 +83,19 @@ public class SysUserRoleService : ITransient
.Where(u => u.UserId == userId).Select(u => u.RoleId).ToListAsync(); .Where(u => u.UserId == userId).Select(u => u.RoleId).ToListAsync();
} }
/// <summary>
/// 根据用户Id获取角色集合
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<List<RoleOutput>> GetUserRoleInfoList(long userId)
{
return await _sysUserRoleRep.AsQueryable().Includes(u => u.SysRole)
.Where(u => u.UserId == userId)
.Select(u => new RoleOutput { Id = u.RoleId, Code = u.SysRole.Code, Name = u.SysRole.Name })
.ToListAsync();
}
/// <summary> /// <summary>
/// 根据角色Id获取用户Id集合 /// 根据角色Id获取用户Id集合
/// </summary> /// </summary>

View File

@ -15,6 +15,7 @@ public class SysUserService : IDynamicApiController, ITransient
private readonly UserManager _userManager; private readonly UserManager _userManager;
private readonly SysOrgService _sysOrgService; private readonly SysOrgService _sysOrgService;
private readonly SysUserExtOrgService _sysUserExtOrgService; private readonly SysUserExtOrgService _sysUserExtOrgService;
private readonly SysRoleService _sysRoleService;
private readonly SysUserRoleService _sysUserRoleService; private readonly SysUserRoleService _sysUserRoleService;
private readonly SysConfigService _sysConfigService; private readonly SysConfigService _sysConfigService;
private readonly SysOnlineUserService _sysOnlineUserService; private readonly SysOnlineUserService _sysOnlineUserService;
@ -25,6 +26,7 @@ public class SysUserService : IDynamicApiController, ITransient
public SysUserService(UserManager userManager, public SysUserService(UserManager userManager,
SysOrgService sysOrgService, SysOrgService sysOrgService,
SysUserExtOrgService sysUserExtOrgService, SysUserExtOrgService sysUserExtOrgService,
SysRoleService sysRoleService,
SysUserRoleService sysUserRoleService, SysUserRoleService sysUserRoleService,
SysConfigService sysConfigService, SysConfigService sysConfigService,
SysOnlineUserService sysOnlineUserService, SysOnlineUserService sysOnlineUserService,
@ -35,6 +37,7 @@ public class SysUserService : IDynamicApiController, ITransient
_userManager = userManager; _userManager = userManager;
_sysOrgService = sysOrgService; _sysOrgService = sysOrgService;
_sysUserExtOrgService = sysUserExtOrgService; _sysUserExtOrgService = sysUserExtOrgService;
_sysRoleService = sysRoleService;
_sysUserRoleService = sysUserRoleService; _sysUserRoleService = sysUserRoleService;
_sysConfigService = sysConfigService; _sysConfigService = sysConfigService;
_sysOnlineUserService = sysOnlineUserService; _sysOnlineUserService = sysOnlineUserService;
@ -136,7 +139,7 @@ public class SysUserService : IDynamicApiController, ITransient
// 若账号的角色和组织架构发生变化,则强制下线账号进行权限更新 // 若账号的角色和组织架构发生变化,则强制下线账号进行权限更新
var user = await _sysUserRep.AsQueryable().ClearFilter().FirstAsync(u => u.Id == input.Id); var user = await _sysUserRep.AsQueryable().ClearFilter().FirstAsync(u => u.Id == input.Id);
var roleIds = await GetOwnRoleList(input.Id); var roleIds = await _sysUserRoleService.GetUserRoleIdList(input.Id);
if (input.OrgId != user.OrgId || !input.RoleIdList.OrderBy(u => u).SequenceEqual(roleIds.OrderBy(u => u))) if (input.OrgId != user.OrgId || !input.RoleIdList.OrderBy(u => u).SequenceEqual(roleIds.OrderBy(u => u)))
await _sysOnlineUserService.ForceOffline(input.Id); await _sysOnlineUserService.ForceOffline(input.Id);
// 更新域账号 // 更新域账号
@ -353,9 +356,17 @@ public class SysUserService : IDynamicApiController, ITransient
/// <param name="userId"></param> /// <param name="userId"></param>
/// <returns></returns> /// <returns></returns>
[DisplayName("获取用户拥有角色集合")] [DisplayName("获取用户拥有角色集合")]
public async Task<List<long>> GetOwnRoleList(long userId) public async Task<GrantRoleOutput> GetOwnRoleList(long userId)
{ {
return await _sysUserRoleService.GetUserRoleIdList(userId); // 获取当前分配用户的角色
var granted = (await _sysUserRoleService.GetUserRoleInfoList(userId));
// 获取当前用户的角色
var available = await _sysRoleService.GetList();
// 改变分配用户的角色可分配状态
granted.ForEach(u => u.Disabled = !available.Any(e => e.Id == u.Id));
// 排除已分配的角色
available = available.ExceptBy(granted.Select(e => e.Id), e => e.Id).ToList();
return new GrantRoleOutput { Granted = granted, Available = available };
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,264 @@
<template>
<el-row :gutter="10">
<el-col :span="10">
<div class="transfer-panel">
<p class="transfer-panel__header">
<el-checkbox v-model="state.leftAllChecked" :indeterminate="leftIndeterminate" :validate-event="false" @change="handleLeftAllChecked"> {{ props.leftTitle }} </el-checkbox>
<span>{{ state.leftChecked.length }}/{{ props.leftData.length }}</span>
</p>
<div class="transfer-panel__body">
<el-input class="transfer-panel__filter" v-model="state.leftKeyword" placeholder="搜索" :prefix-icon="Search" clearable :validate-event="false" />
<el-checkbox-group v-show="true" v-model="state.leftChecked" :validate-event="false" class="transfer-panel__list">
<el-checkbox
v-for="(i, k) in leftFilterData"
:key="k"
:value="i[props.options.value]"
:label="i[props.options.label]"
:disabled="i[props.options.disabled]"
:validate-event="false"
class="transfer-panel__item"
>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</el-col>
<el-col :span="4" class="transfer-buttons">
<div class="transfer-buttons__item">
<el-button type="primary" style="width: 100%" :icon="ArrowRight" @click="toRight">往右</el-button>
</div>
<div class="transfer-buttons__item">
<el-button type="primary" style="width: 100%" :icon="ArrowLeft" @click="toLeft">往左</el-button>
</div>
<div class="transfer-buttons__item">
<el-button type="primary" style="width: 100%" :icon="DArrowRight" @click="allToRight">全部往右</el-button>
</div>
<div class="transfer-buttons__item">
<el-button type="primary" style="width: 100%" :icon="DArrowLeft" @click="allToLeft">全部往左</el-button>
</div>
</el-col>
<el-col :span="10">
<div class="transfer-panel">
<p class="transfer-panel__header">
<el-checkbox v-model="state.rightAllChecked" :indeterminate="rightIndeterminate" :validate-event="false" @change="handleRightAllChecked"> {{ props.rightTitle }} </el-checkbox>
<span>{{ state.rightChecked.length }}/{{ props.rightData.length }}</span>
</p>
<div class="transfer-panel__body">
<el-input class="transfer-panel__filter" v-model="state.rightKeyword" placeholder="搜索" :prefix-icon="Search" clearable :validate-event="false" />
<el-checkbox-group v-show="true" v-model="state.rightChecked" :validate-event="false" class="transfer-panel__list">
<el-checkbox
v-for="(i, k) in rightFilterData"
:key="k"
:value="i[props.options.value]"
:label="i[props.options.label]"
:disabled="i[props.options.disabled]"
:validate-event="false"
class="transfer-panel__item"
>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</el-col>
</el-row>
</template>
<script lang="ts" setup name="transfer">
import { watch, reactive, computed } from 'vue';
const props = defineProps({
leftTitle: String,
rightTitle: String,
options: {
type: Object,
default: () => ({
value: 'id',
label: 'name',
disabled: 'disabled',
}),
},
leftData: { type: Array, default: () => [] }, //
rightData: { type: Array, default: () => [] }, //
});
const emits = defineEmits(['left', 'right', 'allLeft', 'allRight', 'update:leftData', 'update:rightData']);
const state = reactive({
leftAllChecked: false, //
leftKeyword: '', //
leftChecked: [], //
rightAllChecked: false, //
rightKeyword: '', //
rightChecked: [], //
});
//
const leftFilterData = computed(() => {
let result = props.leftData.filter((e) => e[props.options.label].toLowerCase().includes(state.leftKeyword.toLowerCase()));
if (state.leftChecked.length > 0) {
for (let i = state.leftChecked.length - 1; i >= 0; i--) {
const index = result.findIndex((e) => e[props.options.value] == state.leftChecked[i]);
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
if (index == -1) state.leftChecked.splice(i, 1);
}
}
return result;
});
//
const handleLeftAllChecked = (value: any) => {
state.leftChecked = value ? leftFilterData.value.filter((e) => e[props.options.disabled] == false).map((e) => e[props.options.value]) : [];
};
//
const leftIndeterminate = computed(() => {
const checkedLength = state.leftChecked.length;
const result = checkedLength > 0 && checkedLength < leftFilterData.value.filter((e) => e[props.options.disabled] == false).length;
return result;
});
watch(
() => state.leftChecked,
(val: any[]) => {
state.leftAllChecked = val.length > 0 && val.length == leftFilterData.value.filter((e) => e[props.options.disabled] == false).length;
}
);
//
const rightFilterData = computed(() => {
let result = props.rightData.filter((e) => e[props.options.label].toLowerCase().includes(state.rightKeyword.toLowerCase()));
if (state.rightChecked.length > 0) {
for (let i = state.rightChecked.length - 1; i >= 0; i--) {
const index = result.findIndex((e) => e[props.options.value] == state.rightChecked[i]);
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
if (index == -1) state.rightChecked.splice(i, 1);
}
}
return result;
});
//
const handleRightAllChecked = (value: any) => {
state.rightChecked = value ? rightFilterData.value.filter((e) => e[props.options.disabled] == false).map((e) => e[props.options.value]) : [];
};
//
const rightIndeterminate = computed(() => {
const checkedLength = state.rightChecked.length;
const result = checkedLength > 0 && checkedLength < rightFilterData.value.filter((e) => e[props.options.disabled] == false).length;
return result;
});
watch(
() => state.rightChecked,
(val: any[]) => {
state.rightAllChecked = val.length > 0 && val.length == rightFilterData.value.filter((e) => e[props.options.disabled] == false).length;
}
);
//
const toRight = () => {
if (state.leftChecked?.length > 0) {
//
let adds = props.leftData.filter((e) => state.leftChecked.some((x) => x == e[props.options.value]));
//
let cuts = props.leftData.filter((e) => state.leftChecked.every((x) => x != e[props.options.value]));
emits('update:leftData', cuts);
emits('update:rightData', props.rightData.concat(adds));
emits('right');
state.leftChecked = [];
}
};
//
const allToRight = () => {
if (leftFilterData.value?.length > 0) {
let temp = leftFilterData.value.filter((e) => e[props.options.disabled] == false);
//
let adds = props.leftData.filter((e) => temp.some((x) => x[props.options.value] == e[props.options.value]));
//
let cuts = props.leftData.filter((e) => temp.every((x) => x[props.options.value] != e[props.options.value]));
emits('update:leftData', cuts);
emits('update:rightData', props.rightData.concat(adds));
emits('allRight');
state.leftChecked = [];
}
};
//
const toLeft = () => {
if (state.rightChecked?.length > 0) {
//
let adds = props.rightData.filter((e) => state.rightChecked.some((x) => x == e[props.options.value]));
//
let cuts = props.rightData.filter((e) => state.rightChecked.every((x) => x != e[props.options.value]));
emits('update:leftData', props.leftData.concat(adds));
emits('update:rightData', cuts);
emits('left');
state.rightChecked = [];
}
};
//
const allToLeft = () => {
if (rightFilterData.value?.length > 0) {
let temp = rightFilterData.value.filter((e) => e[props.options.disabled] == false);
//
let adds = props.rightData.filter((e) => temp.some((x) => x[props.options.value] == e[props.options.value]));
//
let cuts = props.rightData.filter((e) => temp.every((x) => x[props.options.value] != e[props.options.value]));
emits('update:leftData', props.leftData.concat(adds));
emits('update:rightData', cuts);
emits('allLeft');
state.rightChecked = [];
}
};
</script>
<style lang="scss" scoped>
.transfer-panel {
overflow: hidden;
display: inline-block;
text-align: left;
vertical-align: middle;
width: 100%;
max-height: 100%;
box-sizing: border-box;
position: relative;
border: 1px solid #ebeef5;
&__header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
background: #f5f7fa;
padding: 3px 6px;
border-bottom: 1px solid #ebeef5;
}
&__body {
height: 300px;
.transfer-panel__filter {
padding: 6px;
}
.transfer-panel__list {
overflow: auto;
height: calc(100% - 36px);
.transfer-panel__item {
display: block !important;
padding-left: 6px;
}
}
}
}
.transfer-buttons {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&__item {
padding-top: 10px;
width: 100%;
}
}
</style>

View File

@ -31,13 +31,6 @@
<el-input v-model="state.ruleForm.realName" placeholder="真实姓名" clearable /> <el-input v-model="state.ruleForm.realName" placeholder="真实姓名" clearable />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色集合" prop="roleIdList" :rules="[{ required: true, message: '角色集合不能为空', trigger: 'blur' }]">
<el-select v-model="state.ruleForm.roleIdList" multiple value-key="id" clearable placeholder="角色集合" collapse-tags collapse-tags-tooltip class="w100" filterable>
<el-option v-for="item in state.roleData" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账号类型" prop="accountType" :rules="[{ required: true, message: '账号类型不能为空', trigger: 'blur' }]"> <el-form-item label="账号类型" prop="accountType" :rules="[{ required: true, message: '账号类型不能为空', trigger: 'blur' }]">
<el-select v-model="state.ruleForm.accountType" placeholder="账号类型" collapse-tags collapse-tags-tooltip class="w100"> <el-select v-model="state.ruleForm.accountType" placeholder="账号类型" collapse-tags collapse-tags-tooltip class="w100">
@ -130,6 +123,9 @@
</el-row> </el-row>
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="角色授权" style="height: 550px; overflow-y: auto; overflow-x: hidden">
<Transfer left-title="未授权" right-title="已授权" v-model:leftData="state.available" v-model:rightData="state.granted" />
</el-tab-pane>
<el-tab-pane label="档案信息" style="height: 550px; overflow-y: auto; overflow-x: hidden"> <el-tab-pane label="档案信息" style="height: 550px; overflow-y: auto; overflow-x: hidden">
<el-form :model="state.ruleForm" label-width="auto"> <el-form :model="state.ruleForm" label-width="auto">
<el-row :gutter="10"> <el-row :gutter="10">
@ -253,7 +249,9 @@ import { useUserInfo } from '/@/stores/userInfo';
import { getAPI } from '/@/utils/axios-utils'; import { getAPI } from '/@/utils/axios-utils';
import { SysPosApi, SysRoleApi, SysUserApi } from '/@/api-services/api'; import { SysPosApi, SysRoleApi, SysUserApi } from '/@/api-services/api';
import { RoleOutput, SysOrg, PagePosOutput, UpdateUserInput } from '/@/api-services/models'; import { SysOrg, PagePosOutput, UpdateUserInput } from '/@/api-services/models';
import Transfer from '/@/components/transfer/index.vue';
import { ElMessage } from 'element-plus';
const props = defineProps({ const props = defineProps({
title: String, title: String,
@ -269,7 +267,8 @@ const state = reactive({
selectedTabName: '0', // tab selectedTabName: '0', // tab
ruleForm: {} as UpdateUserInput, ruleForm: {} as UpdateUserInput,
posData: [] as Array<PagePosOutput>, // posData: [] as Array<PagePosOutput>, //
roleData: [] as Array<RoleOutput>, // available: [], //
granted: [], //
}); });
// //
const cascaderProps = { checkStrictly: true, emitPath: false, value: 'id', label: 'name', expandTrigger: 'hover' }; const cascaderProps = { checkStrictly: true, emitPath: false, value: 'id', label: 'name', expandTrigger: 'hover' };
@ -279,8 +278,6 @@ onMounted(async () => {
state.loading = true; state.loading = true;
const { data } = await getAPI(SysPosApi).apiSysPosListGet(); const { data } = await getAPI(SysPosApi).apiSysPosListGet();
state.posData = data.result ?? []; state.posData = data.result ?? [];
const { data: res } = await getAPI(SysRoleApi).apiSysRoleListGet();
state.roleData = res.result ?? [];
state.loading = false; state.loading = false;
}); });
@ -291,11 +288,17 @@ const openDialog = async (row: any) => {
state.selectedTabName = '0'; // tab state.selectedTabName = '0'; // tab
state.ruleForm = JSON.parse(JSON.stringify(row)); state.ruleForm = JSON.parse(JSON.stringify(row));
if (row.id != undefined) { if (row.id != undefined) {
var resRole = await getAPI(SysUserApi).apiSysUserOwnRoleListUserIdGet(row.id); const { data } = await getAPI(SysUserApi).apiSysUserOwnRoleListUserIdGet(row.id);
state.ruleForm.roleIdList = resRole.data.result; state.available = data.result?.available;
state.granted = data.result?.granted;
var resExtOrg = await getAPI(SysUserApi).apiSysUserOwnExtOrgListUserIdGet(row.id); var resExtOrg = await getAPI(SysUserApi).apiSysUserOwnExtOrgListUserIdGet(row.id);
state.ruleForm.extOrgIdList = resExtOrg.data.result; state.ruleForm.extOrgIdList = resExtOrg.data.result;
} else state.ruleForm.accountType = 777; // } else {
state.ruleForm.accountType = 777; //
const { data } = await getAPI(SysRoleApi).apiSysRoleListGet();
state.available = data.result ?? [];
state.granted = [];
}
state.isShowDialog = true; state.isShowDialog = true;
}; };
@ -314,6 +317,11 @@ const cancel = () => {
const submit = () => { const submit = () => {
ruleFormRef.value.validate(async (valid: boolean) => { ruleFormRef.value.validate(async (valid: boolean) => {
if (!valid) return; if (!valid) return;
if (state.granted?.length > 0) state.ruleForm.roleIdList = state.granted.map((e) => e.id);
else {
ElMessage.error(`角色尚未分配`);
return;
}
if (state.ruleForm.id != undefined && state.ruleForm.id > 0) { if (state.ruleForm.id != undefined && state.ruleForm.id > 0) {
await getAPI(SysUserApi).apiSysUserUpdatePost(state.ruleForm); await getAPI(SysUserApi).apiSysUserUpdatePost(state.ruleForm);
} else { } else {