最近公司的业务需要,要做一个后台管理系统的管理系统类似于这样子
功能需求如下:
左边是权限菜单,右边对应的是具体权限.
1.父级权限菜单选中,父级权限菜单的权限包括其中所有子级权限菜单的权限也要选中,父级权限菜单取消选中,同理. 如下图所示
2.父级权限中所有的权限没有全部选中,父级权限菜单属于半选中状态(注意这里父级权限菜单和子级权限菜单是相对的,父级权限菜单可以是子级权限菜单,子级权限菜单也可以是父级权限菜单),如下图所示
3.最后记录当前所选权限的数量,发送给后台.
是不是感觉很简单,那就撸起袖子开干.
首先就要有一个思路,这个项目最多只有两级权限菜单,于是我就想我直接写死不就行了嘛?,当然就顺利的完成了.但是产品经理告诉我后期要扩展,可能有很多嵌套的层级.
嗯…好吧.这样子一来,嵌套一个子级权限菜单,我岂不是就要修改一次代码,这对于我来说是不允许出现的,怎么能写出这种垃圾代码了,于是乎点了一支烟,思考了一下,我还是放弃吧!
开玩笑的,没办法啊,想要工资就只有干啊,产品需求也是合情合理的,我还有什么话说.
接下来就直接给出源码,源码后面有思路的具体解释.
<template>
<div class="addRole">
<ACard
title="新增角色权限"
>
<template slot="extra">
<AButton type="primary" @click="goBack">返回</AButton>
</template>
<ARow style="margin-top: 30px;height: 500px;">
<ACol :span="6" style="height: 100%">
<div class="role-table" style="height: 100%">
<div class="role-list">权限明细</div>
<div class="role-tree">
<ATree
checkable
:autoExpandParent="autoExpandParent"
:expandedKeys="expandedKeys"
class="scroll"
@expand="onExpand"
v-model="checkedKeys"
checkStrictly
@check="checkBoxCheck"
:treeData="permissionTreeData"
>
<template slot="title" slot-scope="record">
<span @click="select(record.key)">{
{
record.name}}</span>
</template>
</ATree>
</div>
</div>
</ACol>
<ACol :span="1" style="height: 100%">
<div style="height: 100%;text-align: center;position: relative;">
<img :src="publicPath+$app.arrowPath" style="position: absolute;left: 0;right: 0;top: 0;bottom: 0;margin: auto;"/>
</div>
</ACol>
<ACol :span="17" style="height: 100%">
<div style="height: 100%">
<div class="role-list">操作权限 (完成编辑后请点击保存按钮保存设置)</div>
<div class="role-tree">
<!--<ACheckboxGroup v-model="permissionIds">-->
<ACheckbox :checked="item.checked" @change="permissChange(item,$event)" :value="item.id" v-for="item in permissionChecks" :key="item.name">{
{
item.name}}</ACheckbox>
<!--</ACheckboxGroup>-->
</div>
</div>
</ACol>
</ARow>
<div class="submit">
<p>当前已选择<span style="color: #1890ff">{
{
permissionIds.length}}</span>项权限</p>
<div>
<AButton style="margin-right: 16px;" @click="resetPermissItemState">取消</AButton>
<AButton type="primary" @click="sendData">保存</AButton>
</div>
</div>
</ACard>
</div>
</template>
<script>
import {
getPermissiontypes,getPermissionList,addRoles} from '@/api/AddRole'
import {
getJurisdictions} from '@/api/Role'
export default {
name: 'AddRole',
data(){
return{
publicPath: process.env.BASE_URL,
form:this.$form.createForm(this),
rules:{
},
//显示的树权限
permissionTreeData:[],
expandedKeys:[],
autoExpandParent:true,
//选中权限分类的id
selectKey:'',
selectedKeys: [],
//选中权限的id的集合
permissionIds:[],
//权限复选框集合
permissionChecks:[],
//权限集合
permissionArr:[],
//全选和半选样式
checkedKeys:{
checked:[],
halfChecked:[]
},
//保存所有的权限类目id
permissTypeIds:[],
checkedLength:0
}
},
mounted(){
this.setRules();
//获取全部权限
getJurisdictions()
.then(res=>{
if(res&&res.data){
//设置树形控件需要显示的数据格式
this.permissionTreeData = this.setData(res.data);
//挂载每个权限类目的权限
this.setPermissTypePermissArrs();
}
})
.catch(err=>{
this.$message.error(err.msg||err.message||err.errors ||err.error)});
},
methods:{
//取消选中状态
resetPermissItemState(){
this.permissionIds = [];
this.permissionArr.forEach(item=>{
this.$set(item,"checked",false);
});
//挂载每个权限类目的权限
this.setPermissTypePermissArrs();
},
//复选框变化的时候的回调函数
permissChange(item,e){
if(e.target.checked){
this.$set(item,'checked',true)
}
else {
this.$set(item,'checked',false)
}
//重新挂载每个权限类目的权限
this.setPermissTypePermissArrs();
//获取选中的权限
this.getCheckedPermiss();
},
//获取选中的权限
getCheckedPermiss(){
this.permissionIds = [];
this.permissionArr.forEach(item=>{
if(item.checked){
this.permissionIds.push(item.id);
}
})
},
//挂载每个权限类目的权限
setPermissTypePermissArrs(){
//顶级类目检测
this.permissionTreeData.forEach(item=>{
this.everyPermiss(item);
//设置权限类目的状态
this.setTreeDataState(item);
});
//设置权限类目的状态
this.clearAndSetCheckState();
},
//每个类目的权限集合的挂载
everyPermiss(data){
let permissArrs = [];
//权限挂载
data.permissArrs = this.itemCheck(data, permissArrs)
},
//获取每一个权限类目本身及其子级类目中的所有权限,并且挂载到自身属性上
itemCheck(data,permissArrs){
//挂载自己的权限
data.permission.forEach(item=>{
permissArrs.push(item);
});
data.children.forEach(item=>{
//挂载子级的权限
this.itemCheck(item,permissArrs);
//子级也要挂载
this.everyPermiss(item);
});
return permissArrs;
},
//设置权限类目的状态
setTreeDataState(item){
let checkState = {
checkAll:false,checked:false};
//是否全选
checkState.checkAll = item.permissArrs.every(item1=>{
return item1.checked === true});
//是否有选中,但没有全选
checkState.checked = item.permissArrs.some(item1=>{
return item1.checked === true});
if(checkState.checkAll){
this.$set(item,'checkedAll',true);
this.$set(item,'halfChecked',false);
}
else {
if(checkState.checked){
this.$set(item,'checkedAll',false);
this.$set(item,'halfChecked',true);
}
else {
this.$set(item,'checkedAll',false);
this.$set(item,'halfChecked',false);
}
}
//子级递归调用
item.children.forEach(item1=>{
this.setTreeDataState(item1)})
},
//清空之前的权限类目选中状态 在设置新的选中状态
clearAndSetCheckState(){
//清楚选中状态
this.checkedKeys={
checked:[],
halfChecked:[]
};
let checked = [];
let halfChecked = [];
//设置新的选中状态
this.permissionTreeData.forEach(item=>{
this.setNewCheckState(item,checked,halfChecked)
});
this.$set(this.checkedKeys,'checked',checked);
this.$set(this.checkedKeys,'halfChecked',halfChecked);
},
//设置新的选中状态
setNewCheckState(data,checked,halfChecked){
//全选状态
if(data.checkedAll){
checked.push(data.id);
}
else {
//部分选中状态
if(data.halfChecked){
halfChecked.push(data.id);
}
}
//子级递归调用
data.children.forEach(item=>{
this.setNewCheckState(item,checked,halfChecked)})
},
//点击权限类目
select(key){
this.permissionChecks = [];
this.permissionArr.forEach(item=>{
if(item.permission_type_id === key){
this.permissionChecks.push(item);
}
})
},
//点击权限类目前面的checkbox 只操作全选中和未选中状态 半选状态不负责
checkBoxCheck(checkedState,{
checked}){
if(!checked){
let surplusPermissTypeIdsChecked = [];
let surplusPermissTypeIdsHalf = [];
let permissTypeItems = [];
//1.获取全部的权限类目id中除了全选的类目权限id之外,剩余的权限类目
surplusPermissTypeIdsChecked= checkedState.checked.concat(this.permissTypeIds).filter(function(v, i, arr) {
return arr.indexOf(v) === arr.lastIndexOf(v);
});
//2.获取全部的权限类目id中除了全选和半选的类目权限id之外,剩余的权限类目
surplusPermissTypeIdsHalf= checkedState.halfChecked.concat(surplusPermissTypeIdsChecked).filter(function(v, i, arr) {
return arr.indexOf(v) === arr.lastIndexOf(v);
});
if(surplusPermissTypeIdsHalf.length>0){
//根据权限类目id查找权限类目
this.useIdFindPermissType(surplusPermissTypeIdsHalf,permissTypeItems);
permissTypeItems.forEach(item=>{
//重置没有在选中也没有在全选中的权限类目的状态及其所属的权限的状态
this.resetPermissSatate(item);
});
}
}
// // // checkedState.checked里面对应的key为权限类目的id
// // //全部选中的类目
else {
if(checkedState.checked.length>0){
checkedState.checked.forEach(key=>{
this.permissionTreeData.forEach(item2=>{
//查找权限类目的选中状态 并且设置其中所包含的权限集合的选中状态为true
this.findPermissChecked(key,item2);
});
});
}
}
// 重新挂载每个权限类目的权限
this.setPermissTypePermissArrs();
this.getCheckedPermiss();
},
//根据权限类目id查找权限类目
useIdFindPermissType(ids,permissTypeItems){
ids.forEach(id=>{
this.permissionTreeData.forEach(item2=>{
this.useIdFindPermissItem(item2,id,permissTypeItems);
})
});
},
//根据权限类目id和权限类目,查找需要操作的权限类目
useIdFindPermissItem(data,id,permissTypeItems){
if(data.id === id){
permissTypeItems.push(data);
}
data.children.forEach(item=>{
this.useIdFindPermissItem(item,id,permissTypeItems);
})
},
//重置没有在选中也没有在全选中的权限类目的状态及其所属的权限的状态
resetPermissSatate(data){
data.permissArrs.forEach(item=>{
this.$set(item,'checked',false);
});
data.children.forEach(item=>{
this.resetPermissSatate(item)});
},
//递归设置权限类目的状态和及其本身所有的权限的选中状态
findPermissChecked(key,data){
//父级和子级必有一个被选中
//当前权限类目被选中
if(key === data.id){
//设置权限类目本身的权限选中
this.setPermissItemChecked(data);
}
else {
//当前权限类目未选中 向子级去查找
data.children.forEach(item=>{
this.findPermissChecked(key,item);
})
}
},
//设置权限类目本身的权限选中
setPermissItemChecked(data){
data.permission.forEach(item=>{
this.$set(item,'checked',true);
});
data.children.forEach(item1=>{
this.setPermissItemChecked(item1);
})
},
onExpand(keys){
this.expandedKeys =keys;
this.autoExpandParent = false
},
//返回
goBack(){
this.$router.back();
},
//提交数据
sendData(){
this.form.validateFields((err,values)=>{
if(err){
return false
}
values.permissions = this.permissionIds;
if(values.permissions.length === 0){
this.$message.error("请选择权限!");
return false
}
console.log(values.permissions);
})
},
setData(data){
data.forEach(item=>{
this.toTreeData(item);
});
console.log(data);
return data;
},
toTreeData(data){
data.key = data.id;
this.permissTypeIds.push(data.id);
data.scopedSlots={
title: 'title'};
this.permissionArr = [...this.permissionArr,...data.permission];
if(data.child&&data.child.length>0){
data.child.forEach(item1=>{
this.toTreeData(item1);
});
}
data.children =data.child || [];
}
}
}
</script>
<style lang="less">
.systemSetup{
.addRole{
.role-table{
}
.role-tree{
padding: 20px;
border-radius:0 0 5px 5px;
border: 1px solid #ccc;
height: 450px;
overflow-y: scroll;
}
.role-list{
background-color: #1890ff;
text-align: center;
height: 50px;
line-height: 50px;
color: #fff;
border-radius: 5px 5px 0 0;
}
.submit{
display: flex;
justify-content: space-between;
padding: 10px;
margin-top: 20px;
border: 1px solid #ccc;
border-radius: 5px;
p{
transform: translateY(50%);
}
}
}
}
</style>
代码需要注意部分:
ant中树形菜单的配置
checkable和checkStrictly的配置,就让树形组件的父子脱离关系,达到自定义的选中状态样式的显示
checkedKeys是绑定的树形组件的选中值,后面我们会改变这个值,来达到我们上述的需求
思路:
之前想的是根据子级权限菜单中的权限情况,决定子级权限菜单的选中样式,同时传递给父级权限菜单,父级权限菜单在根据自己所属权限的选中情况,决定自己的选中状态,但是发现很多级嵌套的时候,就会非常麻烦,还要判断是否存在父级,一级一级的判断,有点晕.
最后想了一种自己认为很简单的方法,不去管什么父级子级权限菜单,每一个权限菜单都是一个父子级权限菜单(同时是一个父级权限菜单也是一个子级权限菜单),一个权限菜单中就挂载自己本身的权限和其所属自己权限菜单的权限,这样子就把权限组成一个集合,
当选中一个权限的时候,就给这个权限给一个checked的标志,设置为true,代表选中,反之设置为false 表示取消
样式渲染的时候,只需要判断自己所挂载的权限集合是不是全选中,或者是一些选中,这样子就可以得出自己所需要渲染的样式,不在去考虑子级权限菜单中权限的选中情况.