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

功能需求如下:

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

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. JavaScript实现算法

    leetcode算法题(JavaScript实现)   题外话 刷了一段时间的codewars的JavaScript题目之后,它给我最大的感受就是,会帮助你迅速的提升你希望练习的语言的API的熟悉程度 ...

  2. (让你提前知道软件开发33):数据操纵语言(DML)

    文章2部分 数据库SQL语言 数据操纵语言(DML) 数据操纵语言(Data Manipulation Language,DML)包含insert.delete和update语句,用于增.删.改数据. ...

  3. centos 6 防火墙开启端口无效问题

    昨天尝试redis在centos的安装,配置文件都检查了,外网就是不能访问 #添加端口开启 $ iptables -A INPUT -p tcp --dport 6379 -j ACCEPT #保存配 ...

  4. WPF - 图形设计器(Diagram Designer)

    原文:WPF - 图形设计器(Diagram Designer) OpenExpressApp计划中包括建模工具,计划是采用MetaEdit+模型来作为元模型,使用codeproject的<WP ...

  5. c#中的GetUpperBound,GetLowerBound方法

    今天使用数组的时候,用到了几个数组的属性,总结如下: Array的Rank 属性:语法:public int Rank { get; } 得到Array的秩(维数).Array的GetUpperBou ...

  6. WPF 寻找控件模板中的元素

    <Window x:Class="Wpf180706.Window10"        xmlns="http://schemas.microsoft.com/wi ...

  7. C# System.Timers.Timer的使用

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...

  8. 重启网卡的几种方法(命令行,API,

    1.重启windows网卡命令 rem 禁用网卡netsh interface set interface 本地连接 disabledrem 启用网卡 netsh interface set inte ...

  9. ARM中 __IO的作用解析

    __IO在头文件中预定义 #define __IO volatile volatile 影响编译器编译的结果, 指出:volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要 ...

  10. Delphi Android 将Google ZXing 整合(调用Jar文件)

    前篇文章介绍了在delphi App(以下简称App)中可使用intent来调用Google ZXing 条码扫描器(以下简称zx),其各有优缺点,优点是我们不需关注zx本身的细节,只需调用其接口即可 ...