增加用户授权、公司授权 以及优化部门、角色、用户接口

This commit is contained in:
龙运模
2024-07-08 19:58:03 +08:00
parent c2fe2415a4
commit e5ebcbcdbe
25 changed files with 2243 additions and 380 deletions
@@ -0,0 +1,74 @@
<template>
<el-container class="mainBox">
<el-header>
<div class="left-panel">
<div class="companyName">{{name}}</div>
</div>
<div class="right-panel">
<div class="right-panel-search">
<el-input :size="size" v-model="params.keyword" placeholder="关键字" clearable></el-input>
<el-button :size="size" type="primary" icon="el-icon-search" @click="upsearch"></el-button>
</div>
</div>
</el-header>
<el-main class="nopadding">
<scTable
ref="table"
tableName="listCustomColumn"
@selection-change="selectionChange"
@sizeChange="sizeChange"
:params="params"
:apiObj="list.apiObj"
:column="list.column"
row-key="id"
stripe
>
<el-table-column type="selection" width="50"></el-table-column>
<el-table-column label="序号" type="index" width="50"></el-table-column>
</scTable>
</el-main>
</el-container>
</template>
<script>
export default {
name: "company-user-list",
data(){
return{
size:'small',
id: this.$route.query.id,
name:this.$route.query.name,
list: {
apiObj: this.$API.system.user.userCompanyList,
column: [],
},
selection: [],
params:{
company_id:this.$route.query.id,
url:'company.user.list',
keyword: '',
},
}
},
mounted() {
},
methods:{
//搜索
upsearch() {
this.$refs.table.upData(this.search);
},
//表格选择后回调事件
selectionChange(selection) {
this.selection = selection;
},
}
}
</script>
<style scoped lang="scss">
.companyName{
color: var(--el-color-primary);
font-size: 14px;
}
</style>
+82 -35
View File
@@ -1,6 +1,6 @@
<template>
<el-container class="mainBox ">
<el-aside width="200px" v-loading="showGrouploading">
<el-aside width="200px" v-loading="showGroupLoading">
<el-container>
<el-header>
<el-input placeholder="输入关键字进行过滤" :size="size" v-model="groupFilterText" clearable></el-input>
@@ -13,20 +13,20 @@
<el-container>
<el-header>
<div class="left-panel">
<el-button type="primary" :size="size" icon="el-icon-plus" @click="add"></el-button>
<el-button type="primary" :size="size" icon="el-icon-plus" @click="add">新增用户</el-button>
<el-button type="danger" plain :size="size" icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
<el-button type="primary" plain :size="size" :disabled="selection.length==0">分配角色</el-button>
<el-button type="primary" plain :size="size" :disabled="selection.length==0">密码重置</el-button>
<el-button type="primary" plain :size="size" :disabled="selection.length==0" @click="role">分配角色</el-button>
<el-button type="primary" plain :size="size" :disabled="selection.length==0" @click="rePwd">密码重置</el-button>
</div>
<div class="right-panel">
<div class="right-panel-search">
<el-input v-model="search.name" :size="size" placeholder="关键字" clearable></el-input>
<el-input v-model="params.name" :size="size" placeholder="关键字" clearable></el-input>
<el-button type="primary" :size="size" icon="el-icon-search" @click="upSearch"></el-button>
</div>
</div>
</el-header>
<el-main class="nopadding">
<scTable ref="table" :apiObj="apiObj" @selection-change="selectionChange" stripe :size="size" remoteSort remoteFilter>
<scTable ref="table" :apiObj="list.apiObj" :column="list.column" @selection-change="selectionChange" stripe :size="size">
<el-table-column type="selection" align="center" width="45"></el-table-column>
<el-table-column label="序号" type="index" align="center" width="50"></el-table-column>
<template #avatar="scope">
@@ -34,6 +34,13 @@
<el-avatar :src="scope.row.avatar" size="small"></el-avatar>
</div>
</template>
<template #roles="scope">
<span v-if="scope.row.user_roles">
<span v-for="(em,index) in scope.row.user_roles.role_names" :key="index">
<span>{{em}}</span><span v-if="index+1<scope.row.user_roles.role_names.length">、</span>
</span>
</span>
</template>
<template #active_status="scope">
<el-switch :size="size" v-model="scope.row.active_status" @change="changeSwitch($event, scope.row)" :loading="scope.row.$switch_status" :active-value="true" :inactive-value="false"></el-switch>
</template>
@@ -45,6 +52,7 @@
<el-dropdown-menu>
<el-dropdown-item @click="table_show(scope.row, 'show')" icon="sc-icon-See">用户详情</el-dropdown-item>
<el-dropdown-item @click="table_edit(scope.row, 'edit')" icon="sc-icon-Edit">编辑用户</el-dropdown-item>
<el-dropdown-item @click="table_empower(scope.row)" icon="sc-icon-AbilityAuthorization" divided>查看权限</el-dropdown-item>
<el-dropdown-item @click="table_del(scope.row, 'delete')" icon="sc-icon-Delete">删除用户</el-dropdown-item>
</el-dropdown-menu>
</template>
@@ -58,29 +66,38 @@
</el-container>
<save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSuccess" @closed="dialog.save=false"></save-dialog>
<re-pwd-dialog v-if="dialog.rePwdD" ref="rePwdDialog" @success="handleSuccess" @closed="dialog.rePwdD=false"></re-pwd-dialog>
<dis-role-dialog v-if="dialog.role" ref="diRoleDialog" @success="handleSuccess" @closed="dialog.role=false"></dis-role-dialog>
</template>
<script>
import saveDialog from './save'
import rePwdDialog from './rePwd'
import disRoleDialog from './role'
export default {
name: 'user',
components: {
saveDialog
saveDialog, rePwdDialog, disRoleDialog
},
data() {
return {
size:'small',
dialog: {
save: false
save: false,
rePwdD: false,
role: false
},
showGrouploading: false,
showGroupLoading: false,
groupFilterText: '',
group: [],
apiObj: this.$API.system.user.list,
list:{
apiObj: this.$API.system.user.list,
column:[]
},
selection: [],
search: {
params: {
name: null
}
}
@@ -94,6 +111,28 @@
this.getGroup()
},
methods: {
rePwd() {
this.dialog.rePwdD = true
this.$nextTick(() => {
const id = this.selection[0]['id']
this.$refs.rePwdDialog.open('add', id)
})
},
role() {
this.dialog.role = true
this.$nextTick(() => {
const data = this.selection;
const row = {
id: data[0]['id'],
login_name: data[0]['login_name'],
name: data[0]['name'],
group: data[0]['user_roles'],
mobile: data[0]['mobile'],
avatar:data[0]['avatar']
}
this.$refs.diRoleDialog.open().setData(row)
})
},
//添加
add(){
this.dialog.save = true
@@ -116,48 +155,55 @@
})
},
//删除
async table_del(row, index){
var reqData = {id: row.id}
var res = await this.$API.demo.post.post(reqData);
async table_del(row){
const reqData = {ids: [row.id]};
const res = await this.$API.system.user.delete.post(reqData);
if(res.code == 200){
//这里选择刷新整个表格 OR 插入/编辑现有表格数据
this.$refs.table.tableData.splice(index, 1);
this.$refs.table.refresh();
this.$message.success("删除成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
}
},
//批量删除
async batch_del(){
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?`, '提示', {
type: 'warning'
}).then(() => {
}).then(async () => {
const loading = this.$loading();
this.selection.forEach(item => {
this.$refs.table.tableData.forEach((itemI, indexI) => {
if (item.id === itemI.id) {
this.$refs.table.tableData.splice(indexI, 1)
}
})
})
const params = {ids: this.selection.map(em => em.id)};
const res = await this.$API.system.user.delete.post(params);
if (res.code == 200) {
this.$refs.table.refresh();
this.$message.success("删除成功");
}
loading.close();
this.$message.success("操作成功")
}).catch(() => {
})
},
// 查看用户权限
table_empower(row){
this.$router.push({
path: '/setting/user/view-permission',
query: {
id: row.id,
name:row.name,
}
})
},
//表格选择后回调事件
selectionChange(selection){
this.selection = selection;
},
//加载树数据
async getGroup(){
this.showGrouploading = true;
this.showGroupLoading = true;
const res = await this.$API.system.dept.active.post();
this.showGrouploading = false;
const allNode = {id: '', label: '所有'};
res.data.unshift(allNode);
this.group = res.data;
if(res.code == 200){
const allNode = {id: '', label: '所有'};
res.data.unshift(allNode);
this.group = res.data;
}
this.showGroupLoading = false;
},
//树过滤
groupFilterNode(value, data){
@@ -183,23 +229,24 @@
if(res.code !=200){
row.active_status = !row.active_status;
}
delete row.$switch_status;
delete row.$switch_yx;
}, 500);
},
//搜索
upSearch(){
this.$refs.table.upData(this.search)
this.$refs.table.upData(this.params)
},
//本地更新数据
handleSuccess(data, mode){
if(mode=='add'){
data.id = new Date().getTime()
this.$refs.table.tableData.unshift(data)
this.$refs.table.refresh();
}else if(mode=='edit'){
this.$refs.table.tableData.filter(item => item.id===data.id ).forEach(item => {
Object.assign(item, data)
})
}
this.$refs.table.refresh();
}
}
}
+99
View File
@@ -0,0 +1,99 @@
<template>
<el-dialog title="重置用户密码" v-model="visible" draggable :width="500">
<el-form :model="form" :rules="rules" :disabled="mode==='show'" ref="dialogForm" label-width="100px"
label-position="right">
<el-form-item label="当前密码" prop="old_pwd">
<el-input v-model="form.old_pwd" type="password" show-password
placeholder="请输入当前密码"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="new_pwd">
<el-input v-model="form.new_pwd" type="password" show-password placeholder="请输入新密码"></el-input>
<sc-password-strength v-model="form.new_pwd"></sc-password-strength>
<div class="el-form-item-msg">请输入包含英文、数字的8位以上密码</div>
</el-form-item>
<el-form-item label="确认新密码" prop="conf_pwd">
<el-input v-model="form.conf_pwd" type="password" show-password
placeholder="请再次输入新密码"></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible=false">取 消</el-button>
<el-button v-if="mode!=='show'" type="primary" :loading="isSave" @click="submit()">保 存</el-button>
</template>
</el-dialog>
</template>
<script>
import scPasswordStrength from '@/components/scPasswordStrength'
export default {
emits: ['success', 'closed'],
components: {
scPasswordStrength
},
data() {
return {
form: {
old_pwd:"",
new_pwd: "",
conf_pwd: "",
id: ""
},
mode: "add",
show: false,
isSave: false,
visible: false,
rules: {
old_pwd: [
{required: true, message: '请输入当前密码'}
],
new_pwd: [
{required: true, message: '请输入新密码'}
],
conf_pwd: [
{required: true, message: '请再次输入新密码'},
{
validator: (rule, value, callback) => {
if (value !== this.form.new_pwd) {
callback(new Error('两次输入密码不一致'));
} else {
callback();
}
}
}
]
}
}
},
methods: {
open(mode = 'add', id) {
this.form.id = id;
this.mode = mode;
this.visible = true;
this.show = true;
return this
},
submit() {
this.$refs.dialogForm.validate(async (valid) => {
if (valid) {
const reqData = {
user_id: this.form.id,
old_pwd:this.form.old_pwd,
conf_pwd: this.form.conf_pwd
};
const res = await this.$API.user.editPass.post(reqData);
if (res.code === 200) {
this.visible = false;
this.$message.success("密码重置成功")
}
} else {
return false;
}
})
}
}
}
</script>
<style>
</style>
+107
View File
@@ -0,0 +1,107 @@
<template>
<el-dialog title="分配角色" v-model="visible" draggable :width="500">
<el-form :model="form" :rules="rules" :disabled="mode==='show'" ref="dialogForm" label-width="100px"
label-position="right">
<el-form-item label="头像" prop="avatar">
<ossImgUpload @parentParams="parentParams" :url="form.avatar" />
</el-form-item>
<el-form-item label="登录账号" prop="login_name">
<el-input v-model="form.login_name" placeholder="用于登录系统" clearable :disabled="show"></el-input>
<div class="el-form-item-msg">账号信息用于登录,系统不允许修改</div>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入完整的真实姓名" clearable :disabled="show"></el-input>
</el-form-item>
<el-form-item label="所属角色" prop="role_ids">
<el-select v-model="form.role_ids" multiple filterable style="width: 100%">
<el-option v-for="item in groups" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible=false">取 消</el-button>
<el-button v-if="mode!=='show'" type="primary" :loading="isSave" @click="submit()">保 存</el-button>
</template>
</el-dialog>
</template>
<script>
export default {
emits: ['success', 'closed'],
data() {
return {
show: false,
isSave: false,
visible: false,
form: {
avatar:"",
user_id: "",
login_name: "",
name: "",
role_ids: []
},
rules: {
role_ids: [
{required: true, message: '请选择所属角色', trigger: 'change'}
]
},
groups: [],
groupsProps: {
value: "id",
multiple: true,
checkStrictly: true
},
}
},
mounted() {
this.getGroup()
},
methods: {
open() {
this.visible = true;
this.show = true;
return this
},
async getGroup() {
const res = await this.$API.system.role.list.get();
if(res.code == 200){
this.groups = res.data.rows;
}
},
// 图片上传成功回调
parentParams(files){
this.form.avatar = files;
},
submit() {
this.$refs.dialogForm.validate(async (valid) => {
if (valid) {
this.isSave = true;
const res = await this.$API.system.user.allocatRole.post(this.form);
if (res.code === 200) {
this.$emit('success', this.form, this.mode)
this.visible = false;
this.$message.success("分配成功")
}
this.isSave = false;
} else {
this.isSave = false;
return false;
}
})
},
setData(data) {
this.form.user_id = data.id
this.form.avatar = data.avatar;
this.form.login_name = data.login_name
this.form.name = data.name
this.form.role_ids = data.group?data.group.role_ids:[]
this.form.mobile = data.mobile
}
}
}
</script>
<style scoped>
</style>
+43 -16
View File
@@ -1,14 +1,26 @@
<template>
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" draggable destroy-on-close @closed="$emit('closed')">
<el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px" label-position="right">
<el-form-item label="头像" prop="avatar">
<ossImgUpload @parentParams="parentParams" :url="form.avatar" />
</el-form-item>
<el-form-item label="登录账号" prop="login_name">
<el-input v-model="form.login_name" placeholder="请填写登录账号" clearable></el-input>
<el-input v-model="form.login_name" placeholder="请填写登录账号" :disabled="mode!='add'?true:false" clearable></el-input>
<p class="el-form-item-msg" v-if="mode == 'edit'">账号信息用于登录,系统不允许修改</p>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="请填写真实姓名" clearable></el-input>
<el-form-item label="姓名/工号" required>
<el-row :gutter="24" justify="space-between">
<el-col :span="12">
<el-form-item prop="name">
<el-input v-model="form.name" placeholder="请填写真实姓名" clearable></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="emp_id">
<el-input v-model="form.emp_id" placeholder="请填写工号" clearable></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="联系电话" prop="mobile">
<el-input v-model="form.mobile" placeholder="请填写联系电话" clearable></el-input>
@@ -16,7 +28,7 @@
<el-form-item label="电子邮件" prop="email">
<el-input v-model="form.email" placeholder="请填写电子邮件" clearable></el-input>
</el-form-item>
<el-form-item label="登录密码" prop="password">
<el-form-item label="登录密码" prop="password" v-if="mode == 'add'">
<el-input type="password" v-model="form.password" placeholder="请填写密码" clearable show-password></el-input>
</el-form-item>
<el-form-item label="所属部门" prop="dept_id">
@@ -53,6 +65,7 @@
id:"",
login_name: "",
name:"",
emp_id:"",
mobile:"",
email:"",
avatar: "",
@@ -71,6 +84,15 @@
name: [
{required: true, message: '请输入真实姓名'}
],
emp_id:[
{required: true, message: '请输入工号'}
],
mobile: [
{required: true, message: '请输入联系方式'}
],
email: [
{required: true, message: '请输入邮箱地址'}
],
password: [
{required: true, message: '请输入登录密码'},
{validator: (rule, value, callback) => {
@@ -80,7 +102,7 @@
callback();
}}
],
password2: [
password_cnf: [
{required: true, message: '请再次输入密码'},
{validator: (rule, value, callback) => {
if (value !== this.form.password) {
@@ -90,10 +112,10 @@
}
}}
],
dept: [
dept_id: [
{required: true, message: '请选择所属部门'}
],
group: [
role_ids: [
{required: true, message: '请选择所属角色', trigger: 'change'}
]
},
@@ -110,7 +132,6 @@
checkStrictly: false,
emitPath:false
}
// checkStrictly: false,
}
},
mounted() {
@@ -127,11 +148,15 @@
//加载树数据
async getGroup(){
const res = await this.$API.system.role.list.get();
this.groups = res.data.rows;
if(res.code == 200){
this.groups = res.data.rows;
}
},
async getDept(){
const res = await this.$API.system.dept.active.post();
this.depts = res.data;
if(res.code == 200){
this.depts = res.data;
}
},
parentParams(item){
this.form.avatar = item;
@@ -156,14 +181,16 @@
//表单注入数据
setData(data){
this.form.id = data.id
this.form.userName = data.userName
this.form.avatar = data.avatar
this.form.login_name = data.login_name
this.form.name = data.name
this.form.emp_id = data.emp_id
this.form.mobile = data.mobile
this.form.email = data.email
this.form.avatar = data.avatar
this.form.dept_id = data.dept_id
this.form.group = data.group
this.form.dept = data.dept
this.form.role_ids = data.user_roles && data.user_roles.role_ids?data.user_roles.role_ids:[]
//可以和上面一样单个注入,也可以像下面一样直接合并进去
//Object.assign(this.form, data)
}
}
}
+246
View File
@@ -0,0 +1,246 @@
<template>
<el-container class="mainBox">
<el-header>
<div class="left-panel">
<span class="roleName">{{name}}</span>角色信息:
<template v-for="(item,index) in user_roles" :key="index">
<span class="roleTag">
<el-tag class="ml-2" :type="index==1?'success':index==2?'info':index==3?'danger':index==4?'warning':''">{{item}}</el-tag>
</span>
</template>
</div>
<div class="right-panel">
<el-alert title="个人用户权限不可编辑,需要编辑请到到角色编辑对应的权限" type="warning" :closable="false" />
</div>
</el-header>
<el-main class="nopadding">
<div class="treeTable">
<div :class="list.length===0?'thenTitle thenTitleBottom':'thenTitle'">
<div class="tr">
<div class="th module">功能模块</div>
<div class="th check"><el-checkbox :indeterminate="isCheckFun" @change="allCheckChange" :disabled="showPermission" v-model="checkedFun" :size="size" /></div>
<div class="th thFlex">
<div class="menu name">菜单</div>
<div class="th authority">数据操作权限</div>
</div>
</div>
</div>
<div class="tableBody">
<div class="tr" v-for="(item,index) in list" :key="index">
<div class="td module">{{item.meta.title}}</div>
<div class="td check"><el-checkbox v-model="item.checked" :indeterminate="item.isCheck" @change="menuCheckChange(item)" :disabled="showPermission" :size="size" /></div>
<div class="td tdChild">
<div class="nextTd" v-for="(em,ind) in item.children" :key="ind">
<div class="tdName menu">
<el-checkbox @change="moduleCheckChange(item,em)" :indeterminate="em.isCheck" v-model="em.checked" :disabled="showPermission" :size="size">{{em.meta.title}}</el-checkbox>
</div>
<div class="tdName authority">
<div class="childTr">
<div class="preBox" v-for="(li,lidex) in em.meta.data_permission" :key="lidex">
<el-checkbox @change="checkChange(item,em,li)" v-model="li.checked" :disabled="showPermission" :size="size">{{li.title}}</el-checkbox>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</el-main>
</el-container>
</template>
<script>
export default {
name: "view-permission",
data(){
return{
size:'default',
id: this.$route.query.id,
name:this.$route.query.name,
user_roles:[],
list:[],
showPermission:false
}
},
computed:{
isCheckFun(){
let isTrue = false;
let num = this.list.length;
let checkNum = this.list.filter((item)=>item.checked).length;
let isCheckNum = this.list.filter((item)=>item.isCheck).length;
isTrue = checkNum>0 && checkNum == num?false:checkNum>0 || isCheckNum>0 ? true:false;
return isTrue
},
checkedFun(){
let isTrue = false;
let num = this.list.length;
let checkNum = this.list.filter((item)=>item.checked).length;
isTrue = checkNum>0 && checkNum == num?true:false;
return isTrue
}
},
mounted() {
this.getPermission();
},
methods:{
async getPermission() {
let params = {
user_id:this.id
}
const res = await this.$API.system.user.userPermission.post(params);
this.user_roles = res.data.role;
res.data.menu.forEach(item=>{
if(item.children){
item.children.forEach(em=>{
if(em.meta.data_permission){
let check = em.meta.data_permission.filter((li)=>li.checked).length;
let ed = em.meta.data_permission.length;
em.isCheck = check>0 && check != ed?true:false;
if(ed>0){
em.checked = check>0 && check== ed?true:false;
}
}
})
let check = item.children.filter((em)=>em.checked).length;
let isCheck = item.children.filter((em)=>em.isCheck).length;
let ed = item.children.length;
item.isCheck = (check>0 && check != ed) || (isCheck>0 && check !=ed)?true:false;
item.checked = check>0 && check== ed?true:false;
}
})
this.list = res.data.menu;
},
allCheckChange(){
return
},
menuCheckChange(em){
em.checked = !em.checked;
return
},
moduleCheckChange(_a,em){
em.checked = !em.checked;
return
},
checkChange(_a,_b,em){
em.checked = !em.checked;
return
}
}
}
</script>
<style lang="scss" scoped>
.nopadding{
padding: 10px 0;
}
.module{
flex-basis: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.check{
flex-basis: 60px;
display: flex;
align-items: center;
justify-content: center;
}
.menu{
flex-basis: 180px;
}
.authority{
flex:1;
text-align: center;
}
.treeTable{
border: 1px solid var(--el-border-color-light);
}
.thenTitle .tr {
display: flex;
align-items: stretch;
border-bottom:1px solid var(--el-border-color-light);
height: 40px;
background: var(--el-bg-color-overlay);
.th{
padding: 0 10px;
border-left: 1px solid var(--el-border-color-light);
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.th:first-child{
border-left: none;
}
.thFlex{
display: flex;
flex: 1;
padding: 0;
.name{
flex-basis: 180px;
padding: 10px;
}
}
}
.thenTitleBottom .tr{
border-bottom: 0;
}
.tableBody .tr{
display: flex;
align-items: stretch;
justify-content: space-between;
border-bottom:1px solid var(--el-border-color-light);
.td{
padding: 0 10px;
border-left: 1px solid var(--el-border-color-light);
.nextTd{
display: flex;
align-items: center;
border-bottom:1px solid var(--el-border-color-light);
}
.nextTd:last-child{
border-bottom: 0;
}
}
.td:first-child{
border-left: 0;
}
.tdChild{
flex: 1;
padding: 0;
border-left: 1px solid var(--el-border-color-light);
.tdName:last-child{
border-left: 1px solid var(--el-border-color-light);
}
.tdName{
padding: 0 10px;
min-height: 33px;
}
.childTr{
display: flex;
flex-wrap: wrap;
flex: 1;
.preBox{
display: flex;
align-items: center;
flex-wrap: wrap;
margin-right: 20px;
width: 110px;
}
}
}
}
.tableBody .tr:last-child{
border-bottom: 0;
}
.roleName{
color: var(--el-color-primary);
margin-right: 10px;
}
.roleTag{
margin-right: 10px;
}
</style>