最近公司的业务需要,要做一个后台管理系统的管理系统类似于这样子

功能需求如下:

左边是权限菜单,右边对应的是具体权限.

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 表示取消

样式渲染的时候,只需要判断自己所挂载的权限集合是不是全选中,或者是一些选中,这样子就可以得出自己所需要渲染的样式,不在去考虑子级权限菜单中权限的选中情况.

vue结合Ant Design实现后台系统的权限分配(支持无限子级嵌套)的更多相关文章

  1. 使用Vue+Django+Ant Design做一个留言评论模块

    使用Vue+Django+Ant Design做一个留言评论模块 1.总览 留言的展示参考网络上参见的格式,如掘金社区: 一共分为两层,子孙留言都在第二层中 最终效果如下: 接下是数据库的表结构,如下 ...

  2. 前端自动分环境打包(vue和ant design)

    现实中的问题:有时候版本上线的时候,打包时忘记切换环境,将测试包推上正式服务器,那你就会被批了. 期望:在写打包的命令行的时候就觉得自己在打包正式版本,避免推包时候的,不确信自己的包是否正确. 既然有 ...

  3. vue 自定义侧边栏 递归无限子级菜单

    有很多网站会涉及到导航栏,我自己在开发中用的是element导航组件,并且自定义事件,给大家分享一下. 1.使用递归方法,无限循环子级菜单. 2.使用组件封装,维护方便. 3.使用index作为路由跳 ...

  4. Vue.js高效前端开发 • 【Ant Design of Vue框架基础】

    全部章节 >>>> 文章目录 一.Ant Design of Vue框架 1.Ant Design介绍 2.Ant Design of Vue安装 3.Ant Design o ...

  5. Vue3: 如何以 Vite 创建,以 Vue Router, Vuex, Ant Design 开始应用

    本文代码: https://github.com/ikuokuo/start-vue3 在线演示: https://ikuokuo.github.io/start-vue3/ Vite 创建 Vue ...

  6. ant.design初探

    第一部分: 前言 推荐网站: https://ant.design/docs/spec/introduce-cn ant.design是基于react开发的一个解放ui和前端的工具,它提供了一致的设计 ...

  7. ant design pro (十三)advanced 错误处理

    一.概述 原文地址:https://pro.ant.design/docs/error-cn 二.详细 2.1.页面级报错 2.1.1.应用场景 路由直接引导到报错页面,比如你输入的网址没有匹配到任何 ...

  8. React / Ant Design Pro 实现Canvas画布实时自适应

    如何实现canvas根据父容器进行自适应? Ant Design的组件都提供了强大的自适应能力,为了对齐父组件,镶嵌在Ant Design组件里的canvas也需要能根据父级容器进行自适应的能力,页面 ...

  9. Ant Design Pro (中后台系统)教程

    一.概念:https://pro.ant.design/docs/getting-started-cn(官方网站) 1.Ant Design Pro 是什么:  https://www.cnblogs ...

随机推荐

  1. IOC介绍及其简单实现

    预备知识: Java反射原理,XML及其解析   IOC:Inversion of Control,控制反转,它最主要反映的是与传统面向对象(OO)编程的不同.通常我们编程实现某种功能都需要几个对象相 ...

  2. 概率图模型(PGM) —— 贝叶斯网络(Bayesian Network)

    概率图模型是图论与概率方法的结合产物.Probabilistic graphical models are a joint probability distribution defined over ...

  3. 右键计算机->属性->高级系统设置->高级->环境变量,添加环境变量(推荐)

    (1)右键计算机->属性->高级系统设置->高级->环境变量,添加环境变量(推荐) QTDIR:D:\Software\Qt\Qt5.2.0\5.2.0\msvc2010_op ...

  4. 【Quartz】定时器初步实验(一)

    原文:[Quartz]定时器初步实验(一)     以前就了解了Quartz这个定时框架,但是一直没有认真的去关注他,最近忽然看到已经更新到3.0.4支持异步操作了所以就写个简单的小例子看看好用不. ...

  5. 一款天气app的温度曲线图的实现

    原文:一款天气app的温度曲线图的实现 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/tyhzsd/article/details/50544639 ...

  6. 早期malloc分配时,如果内存耗尽分配不出来,会直接返回NULL。现在分配不出来,直接抛出异常(可使用nothrow关键字)

    今天和同事review代码时,发现这样的一段代码: Manager * pManager = new Manager(); if(NULL == pManager) { //记录日志 return f ...

  7. This problem will occur when running in 64 bit mode with the 32 bit Oracle client components installed.

    Attempt to load Oracle client libraries threw BadImageFormatException. This problem will occur when ...

  8. 【全面解禁!真正的Expression Blend实战开发技巧】第七章 MVVM初体验-在DataGrid行末添加按钮

    原文:[全面解禁!真正的Expression Blend实战开发技巧]第七章 MVVM初体验-在DataGrid行末添加按钮 博客更新较慢,先向各位读者说声抱歉.这一节讲解的依然是开发中经常遇到的一种 ...

  9. Android零基础入门第71节:CardView简单实现卡片式布局

    还记得我们一共学过了多少UI控件了吗?都掌握的怎么样啊. 安卓中一些常用控件学习得差不多了,今天再来学习一个新的控件CardView,在实际开发中也有非常高的地位. 一.CardView简介 Card ...

  10. Rendering in Delphi using TCanvas (FMX)

    BY CRAIG CHAPMAN · PUBLISHED 2015-08-05 · UPDATED 2015-08-20   I have a customer with an application ...