最近公司的業務需要,要做一個后台管理系統的管理系統類似於這樣子
功能需求如下:
左邊是權限菜單,右邊對應的是具體權限.
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 表示取消
樣式渲染的時候,只需要判斷自己所掛載的權限集合是不是全選中,或者是一些選中,這樣子就可以得出自己所需要渲染的樣式,不在去考慮子級權限菜單中權限的選中情況.
