项目使用的饿了么的Element-Ui,权限树使用其树形控件:

<el-tree :data="data" ></el-tree>

刚开始没有特殊需求,三级分支,效果看着还可以。但是接下来的新需求:增加页面操作按钮权限,即达到四级分支,同时要求四级权限布局方式为横向,而且操作按钮权限非固定四级树,但是样式要求一致。这样子就很难操作了,如果单单是四级树为横向,还可以调调样式完成。本来想修改element的tree控件源码来实现,网上查了一些资料,还没有很好的办法生成其编译文件。最终决定自己写组件完成上述需求。

先上效果图:

基本可以满足需求,样式稍微比element差点,后期再优化。

组件代码如下:

<template>
<li :class="[isButton, hasBorder]" style="list-style:none;">
<span @click="toggle" v-show="model.menuLevel!==1" >
<i v-if="isFolder" class="icon" :class="[open ? 'folder-open': 'folder']" style="margin-bottom: 3px;"></i>
<i v-if="!isFolder" class="icon file-text"></i>
<input type="checkbox" class="checkCls" @click.stop="selTree(model)" :id="'menu'+model.id" :class="'group'+label">
{{ model.menuName }}
</span>
<ul v-show="open" v-if="isFolder">
<tree-menu v-for="(item, index) in model.childNode" :model="item" :key="index" :menuList="menuList" :label="label" :selectKeys="selectKeys" ></tree-menu>
</ul>
</li>
</template> <script type="text/ecmascript-6">
import $ from 'jquery'
export default {
name: 'treeMenu',
props: ['model', 'menuList', 'label', 'selectKeys'],
data () {
return {
open: true, // 默认打开彩单树
selAllkeys: []
}
},
computed: {
isFolder: function () {
return this.model.childNode && this.model.childNode.length
},
isButton: function () {
if (this.model.buttonControl === '1') {
return 'btnCls'
} else {
return 'menuCls'
}
},
hasBorder: function () {
if (this.model.menuLevel === 1) {
return 'blk_border'
}
}
},
methods: {
getAllKeys () {
var keys = []
var objs = $('.group' + this.label + ':checked')
for (let i = 0; i < objs.length; i++) {
let id = objs[i].id
id = id.substring(4)
keys.push((id - 0)) // 保存选中菜单id
}
return keys
},
toggle: function () {
if (this.isFolder) {
this.open = !this.open
}
},
// 根据id获取menu对象
getMeunById (id, allMenuList) {
var menu = {}
if (allMenuList.id === id) { // 一级菜单
menu = allMenuList
} else if (allMenuList.childNode && allMenuList.childNode.length) { // 二级菜单
for (let i = 0; i < allMenuList.childNode.length; i++) {
if (allMenuList.childNode[i].id === id) {
menu = allMenuList.childNode[i]
break
} else if (allMenuList.childNode[i].childNode && allMenuList.childNode[i].childNode.length) { // 三级
for (let j = 0; j < allMenuList.childNode[i].childNode.length; j++) {
if (allMenuList.childNode[i].childNode[j].id === id) {
menu = allMenuList.childNode[i].childNode[j]
break
}
}
}
}
}
return menu
},
// checkbox点击事件
selTree (model) {
var obj = $('#menu' + model.id)[0] // checkbox DOM对象
if (obj.checked) { // 选中
// 若存在下级,下级全部选中
if (model.childNode && model.childNode.length) {
this.subMenusOp(model.childNode, 1)
}
// 若存在上级,确认是否需要选中上级CheckBox
if (model.supMenuID !== 0 && model.menuLevel > 2) {
this.supMenusOp(model.supMenuID, 1)
}
} else { // 取消
// 若存在下级,下级全部取消
if (model.childNode && model.childNode.length) {
this.subMenusOp(model.childNode, 0)
}
// 若存在上级,确认是否需要取消上级CheckBox
if (model.supMenuID !== 0 && model.menuLevel > 2) {
this.supMenusOp(model.supMenuID, 0)
}
}
this.getAllKeys()
},
// 下级菜单操作 flag=1为选中,flag=0为取消
subMenusOp (childNodes, flag) {
for (let i = 0; i < childNodes.length; i++) {
var menu = childNodes[i]
var id = menu.id
if (flag === 1) { // 选中
$('#menu' + id)[0].checked = true
} else { // 取消
$('#menu' + id)[0].checked = false
}
if (menu.childNode && menu.childNode.length) {
this.subMenusOp(menu.childNode, flag)
}
}
},
// 上级菜单操作(选中:flag=1,取消:flag=0)
supMenusOp (id, flag) {
var menu = this.getMeunById(id, this.menuList)
if (menu.childNode && menu.childNode.length) {
var childLength = menu.childNode.length // 直接子级个数
var selectCount = 0
for (let i = 0; i < childLength; i++) {
let id1 = menu.childNode[i].id
if ($('#menu' + id1)[0].checked) {
selectCount++
}
}
if (flag === 1) { // 选中
if (childLength === selectCount) {
$('#menu' + id)[0].checked = true
if (menu.supMenuID !== 0 && menu.menuLevel > 2) {
this.supMenusOp(menu.supMenuID, flag)
}
}
} else if (flag === 0) {
if (childLength !== selectCount) {
$('#menu' + id)[0].checked = false
if (menu.supMenuID !== 0 && menu.menuLevel > 2) {
this.supMenusOp(menu.supMenuID, flag)
}
}
}
}
},
// 计算所有下级节点是否全部选中,是返回true,否返回false
isAllSel (childNodes, selectKeys) {
var nodeKeys = [] // 选中的id集合
this.addKeys(childNodes, selectKeys, nodeKeys)
var allKeys = []
this.getNodesCount(childNodes, allKeys)
if (nodeKeys.length === allKeys.length) {
return true
} else {
return false
}
},
// 计算childNodes下选中的id集合
addKeys (childNodes, selectKeys, Arrs) {
for (let i = 0; i < childNodes.length; i++) {
if (selectKeys.indexOf(childNodes[i].id) >= 0) {
Arrs.push(childNodes[i].id)
}
if (childNodes[i].childNode && childNodes[i].childNode.length) {
this.addKeys(childNodes[i].childNode, selectKeys, Arrs)
}
}
},
// 计算childNodes的子级数
getNodesCount (childNodes, allKeys) {
for (let i = 0; i < childNodes.length; i++) {
allKeys.push(childNodes[i].id)
if (childNodes[i].childNode && childNodes[i].childNode.length) {
this.getNodesCount(childNodes[i].childNode, allKeys)
}
}
}
},
mounted () {
// 禁止复选框的冒泡事件
$("input[type='checkbox']").click(function (e) {
e.stopPropagation()
})
// 选中菜单使能
if (this.selectKeys instanceof Array && this.selectKeys.length > 0 && this.selectKeys.indexOf(this.model.id) >= 0) {
if (this.model.childNode && this.model.childNode.length && this.model.menuLevel !== 1) { // 包含子级,一级菜单除外
// 计算所有子节点是否全部选中
if (this.isAllSel(this.model.childNode, this.selectKeys)) {
$('#menu' + this.model.id)[0].checked = true
}
} else {
$('#menu' + this.model.id)[0].checked = true
}
}
}
}
</script> <style>
.blk_border{
border:1px solid #d1dbe5;
padding-bottom: 15px;
}
.blk_border ul{
padding-left: 15px;
}
ul {
list-style: none;
}
i.icon {
display: inline-block;
width: 15px;
height: 15px;
background-repeat: no-repeat;
vertical-align: middle;
}
.icon.folder {
background-image: url(../../images/close.png);
}
.icon.folder-open {
background-image: url(../../images/open.png);
}
.tree-menu li {
line-height: 1.5;
}
li.btnCls {
float: left;
margin-right: 10px;
}
li.menuCls {
clear: both;
line-height:30px;
}
.checkCls {
vertical-align: middle;
}
.el-tabs__content{
color:#48576A;
}
</style>

权限树的数据结构有一定要求,比element的tree控件数据结构属性稍多一些,否则实现也不会这么简单了,优化后的权限树数据结构在选中菜单返回上简化了很多,也没有用到vuex。

权限树数据结构为:

{
'childNode': [
{
'childNode': [
{
'icon': '',
'id': 242,
'menuLevel': 3,
'menuName': '旅游订单',
'menuTop': 1,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 241
},
{
'icon': '',
'id': 243,
'menuLevel': 3,
'menuName': '签证订单',
'menuTop': 2,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 241
},
{
'icon': '',
'id': 244,
'menuLevel': 3,
'menuName': '出团通知书',
'menuTop': 3,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 241
}
],
'icon': '',
'id': 241,
'menuLevel': 2,
'menuName': '订单管理',
'menuTop': 1,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 240
},
{
'childNode': [
{
'icon': '',
'id': 246,
'menuLevel': 3,
'menuName': '旅游产品',
'menuTop': 1,
'menuUrl': '/tourProduct',
'buttonControl': '0',
'supMenuID': 245
},
{
'icon': '',
'id': 247,
'menuLevel': 3,
'menuName': '图库',
'menuTop': 2,
'menuUrl': '/basePicStore',
'buttonControl': '0',
'supMenuID': 245
},
{
'icon': '',
'id': 248,
'menuLevel': 3,
'menuName': '签证产品',
'menuTop': 3,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 245
}
],
'icon': '',
'id': 245,
'menuLevel': 2,
'menuName': '产品管理',
'menuTop': 2,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 240
},
{
'childNode': [
{
'icon': '',
'id': 250,
'menuLevel': 3,
'menuName': '旅游广告',
'menuTop': 1,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 249
}
],
'icon': '',
'id': 249,
'menuLevel': 2,
'menuName': '广告管理',
'menuTop': 3,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 240
}
],
'icon': '',
'id': 240,
'menuLevel': 1,
'menuName': '业务中心',
'menuTop': 1,
'menuUrl': '/',
'buttonControl': '0',
'supMenuID': 0
}

实际数据为上述对象的数组。

这里主要增加了buttonControl和supMenuId,方便实现按钮权限的样式判断和选中、取消操作的checkbox级联操作。

引用组件代码:

      <el-tab-pane v-for="(menu, index) in theModel" :key="index"  :label="menu.menuName">
<my-tree :model="menu" ref="tree" :menuList="menu" :label="index" :selectKeys="selectKeys"></my-tree>
</el-tab-pane>

theModel即为权限树数组,selectKeys为选中的权限数组集合,即id集合。

mounted()实现初始化操作:禁止checkbox的冒泡时间,selectKeys的赋值操作。

其实权限树或者说菜单树的要点就在递归算法上,按钮的选中或取消,都需要执行递归操作。这里使用jquery来协助操作,简化了许多事情,应该还是数据绑定的精神没有掌握好吧。getAllKeys()获取checkbox为true的权限id返回。

实际获取选中的权限菜单的数据如下图:

Vue2.0权限树组件的更多相关文章

  1. 基于vue2.0的分页组件开发

    今天安排的任务是写基于vue2.0的分页组件,好吧,我一开始是觉得超级简单的,但是越写越写不出来,写的最后乱七八糟的都不知道下句该写什么了,所以重新捋了思路,小结一下- 首先写组件需要考虑: 要从父组 ...

  2. Vue2.0的通用组件

    饿了么基于Vue2.0的通用组件开发之路(分享会记录)   Element:一套通用组件库的开发之路 Element 是由饿了么UED设计.饿了么大前端开发的一套基于 Vue 2.0 的桌面端组件库. ...

  3. vue2.0实现分页组件

    最近使用vue2.0重构项目, 需要实现一个分页的表格, 没有找到合适的组件, 就自己写了一个, 效果如下: 该项目是使用 vue-cli搭建的, 如果你的项目中没有使用webpack,请根据代码自己 ...

  4. 饿了么基于Vue2.0的通用组件开发之路(分享会记录)

    Element:一套通用组件库的开发之路 Element 是由饿了么UED设计.饿了么大前端开发的一套基于 Vue 2.0 的桌面端组件库.今天我们要分享的就是开发 Element 的一些心得. 官网 ...

  5. vue2.0 如何自定义组件(vue组件的封装)

    一.前言 之前的博客聊过 vue2.0和react的技术选型:聊过vue的axios封装和vuex使用.今天简单聊聊 vue 组件的封装. vue 的ui框架现在是很多的,但是鉴于移动设备的复杂性,兼 ...

  6. 用webpack2.0构建vue2.0单文件组件超级详细精简实例

    npm init -y 初始化项目  //-y 为自动生成package.json,如果需要自行配置,去掉-y即可 安装各种依赖项 npm install --save vue 安装vue2.0 np ...

  7. vue2.0 $emit $on组件通信

    在vue1.0中父子组件通信使用$dispatch 和 $broadcast,但是在vue2.0中$dispatch 和 $broadcast 已经被弃用. 因为基于组件树结构的事件流方式实在是让人难 ...

  8. 一款基于vue2.0的分页组件---写在页面内

    通过 Vue2.0 实现的分页 可自由设置分页显示的多少.上一页.下一页.省略号等,也可直接输入跳转到的页码进行跳转,分页的样式可自由调整 // 1.页面的 head 部分,需要设计好页面的样式 .p ...

  9. vue2.0使用动态组件实现tab切换效果(vue-cli)

    <template> <div> <div>#动态组件实现tab切换效果#</div><br><br><br> &l ...

随机推荐

  1. 10 分钟彻底理解 Redis 的持久化和主从复制

    在这篇文章,我们继续有关Redis方面知识的学习,一起了解一下其中一个非常重要的内容:Redis的持久化机制. 什么是Redis持久化? Redis作为一个键值对内存数据库(NoSQL),数据都存储在 ...

  2. 文本编辑器vim/vi——模式切换及输入模式

    vim一共有三种模式:命令模式.输入模式.末行模式 要从命令模式切换到输入模式:a,i,o a——append 属于在后面追加内容:i——insert 属于插入,在前面插入内容:o——other 属于 ...

  3. 公司网络能ping通ip,不能ping域名

    第一天去公司,就因为网络问题弄了大半天.只能ping通ip,不能ping域名.网上查了各种方式都不行,刷新dns缓存.说是什么域名解析问题.dns的问题.公司里好几个人都看了看不能解决.领导让重装系统 ...

  4. JavaWeb开发校园二手平台项目 源码

    开发环境: Windows操作系统开发工具:MyEclipse/Eclipse + JDK+ Tomcat + MySQL 数据库 项目简介: JAVAWEB校园二手平台项目,基本功能包括:个人信息. ...

  5. 微信小程序添加背景图片的坑

    给微信小程序页面加载背景图片解决方案 直接附上原文地址: 给微信小程序页面加载背景图片解决方案 - YUSIR 完美CODING世界 - CSDN博客  https://blog.csdn.net/y ...

  6. crontab 实现Linux系统上定时任务的关键命令

    附上原文链接 https://www.cnblogs.com/ftl1012/p/crontab.html 原文真是写得非常好,所谓他山之石可以攻玉~,我就不客气啦,哈哈

  7. Elasticsearch 删除文档

    章节 Elasticsearch 基本概念 Elasticsearch 安装 Elasticsearch 使用集群 Elasticsearch 健康检查 Elasticsearch 列出索引 Elas ...

  8. ACM-数细胞

    题目描述:数细胞 一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数.编程需要用到的队列及其相关函数已经实现,你只需要完成 ...

  9. 关于博主 5ab

    博主是 5ab,一个 ZJ 初一大蒟蒻. 以 5ab 及类似名号在各大 OJ 出没. 欢迎来到 5ab 这个超级大蒟蒻的博客!!! My luogu blog 关于 5ab 的码风 大括号换行!!! ...

  10. HTTP TCP UDP ICMP IP ARP 协议详解(10.15 第二十一天)

    ARP协议 ARP(Address Resolution Protocol)协议 地址解析协议 把网络层的IP地址翻译成在数据链路层寻址的48位硬件地址(MAC地址) 在OSI模型中ARP协议属于链路 ...