Vue2.0权限树组件实现代码

项目使用的饿了么的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
 } 

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

这里主要增加了buttonControlsupMenuId,方便实现按钮权限的样式判断和选中、取消操作的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权限树组件实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 详解vue.js2.0父组件点击触发子组件方法

    之前关于vue.js2.0父组件的一点学习,最近需要回顾,就顺便发到随笔上了 <body> <div id="counter-event-example"> <p>{{ total }}</p> <button-counter v-on:ee="incrementTotal"></button-counter> <button-counter v-on:ee="increment

  • Vue2.0表单校验组件vee-validate的使用详解

    vee-validate使用教程 本文适合有一定Vue2.0基础的同学参考,根据项目的实际情况来使用,关于Vue的使用不做多余解释.本人也是一边学习一边使用,如果错误之处敬请批评指出* 一.安装 npm install vee-validate@next --save 注意:@next,不然是Vue1.0版本 bower install vee-validate#2.0.0-beta.13 --save 二.引用 import Vue from 'vue'; import VeeValidate

  • vue 2.0组件与v-model详解

    前言 大家可能乍一看这个标题,可能会有疑问:v-model和组件也能扯到一起?在打算写这篇文章的时候,也是这么想的.咱们按简历的那一套STAR法则来梳理一下这篇文章: 情景[Situation]: 编写通用的输入组件时,子组件要绑定到父组件的某个变量上dataA,当父组件要拿到自组件的值时不能通过this.$children.xxx取值然后付给dataA, 而是父组件可以直接this.dataA就可以取到当前子组件最新值. 任务[Task]: 实现在父组件直接this.dataA就可以取到当前子

  • Vue2.0组件间数据传递示例

    Vue1.0组件间传递 使用$on()监听事件: 使用$emit()在它上面触发事件: 使用$dispatch()派发事件,事件沿着父链冒泡: 使用$broadcast()广播事件,事件向下传导给所有的后代 Vue2.0后$dispatch(),$broadcast()被弃用,见https://cn.vuejs.org/v2/guide/migration.html#dispatch-和-broadcast-替换 1,父组件向子组件传递场景:Father上一个输入框,根据输入传递到Child组件

  • Vue2.0利用 v-model 实现组件props双向绑定的优美解决方案

    在项目中开始使用vue2来构建项目了,跟 vue1 很大的一处不同在于2 取消了props 的双向绑定,改成只能从父级传到子级的单向数据流,初衷当然是好的,为了避免双向绑定在项目中容易造成的数据混乱. 解决方案一 然后开始参考网上和github上的方案等等,发现很多解决方案是这样的 用data对象中创建一个props属性的副本 watch props属性 赋予data副本 来同步组件外对props的修改 watch data副本,emit一个函数 通知到组件外 这里以最常见的 modal为例子:

  • 基于vue2.0+vuex的日期选择组件功能实现

    calendar vue日期选择组件 一个选择日期的vue组件 基于vue2.0 + vuex 原本是想找这样的一个组件的,查看了vuex后,发现vuex的写法还不是基于2.0的,所以就自己动手做了 demo展示&&项目中的使用 目录结构 demo 用vue-cli 的webpack-simple构建的 calendar |--dist build生成的目录 |--doc 展示图片 |--src |--assets 资源 |--components |--calendar 日期组件 |--

  • vue2.0父子组件及非父子组件之间的通信方法

    1.父组件传递数据给子组件 父组件数据如何传递给子组件呢?可以通过props属性来实现 父组件: <parent> <child :child-msg="msg"></child>//这里必须要用 - 代替驼峰 </parent> data(){ return { msg: [1,2,3] }; } 子组件通过props来接收数据: 方式1: props: ['childMsg'] 方式2 : props: { childMsg: Arr

  • Vue2.0权限树组件实现代码

    项目使用的饿了么的Element-Ui,权限树使用其树形控件: <el-tree :data="data" ></el-tree> 刚开始没有特殊需求,三级分支,效果看着还可以.但是接下来的新需求:增加页面操作按钮权限,即达到四级分支,同时要求四级权限布局方式为横向,而且操作按钮权限非固定四级树,但是样式要求一致.这样子就很难操作了,如果单单是四级树为横向,还可以调调样式完成.本来想修改element的tree控件源码来实现,网上查了一些资料,还没有很好的办法生

  • vue2.0实现分页组件的实例代码

    最近使用vue2.0重构项目, 需要实现一个分页的表格, 没有找到合适的分页组件, 就自己写了一个, 效果如下: 该项目是使用 vue-cli搭建的, 如果你的项目中没有使用webpack,请根据代码自己调整: 首先新建pagination.vue文件, 所有组件的代码都写在这里, 写这样的组件并没有什么太大的难度, 主要是解决父子组件之间值传递的问题 <template> <nav> <ul class="pagination"> <li :

  • vue2.0使用swiper组件实现轮播的示例代码

    1.安装swiper npm install swiper@3.4.1 --save-dev 2.引用组件 import Swiper from 'swiper'; import 'swiper/dist/css/swiper.min.css'; 3.html页面代码 <div class="swiper-container" id="swiper"> <div class="swiper-wrapper"> <di

  • 详解Vue2.0之去掉组件click事件的native修饰

    这个是在组件开发中遇到的问题,当时我在编写button的组件,模板是这样的: <template> <button class="disable-hover button ion-button" :class="[modeClass,typeClass,shapeClass,sizeClass,colorClass,roleClass,strongClass]"> <span class="button-inner"

  • vue2.0 如何把子组件的数据传给父组件(推荐)

    在父组件 App.vue 中引用子组件 A.vue,把 A中的数据传给App. ps:没看父组件传给子组件的先看看去. 1.代码 子组件 A.vue <template> <div> <h3>这里是子组件的内容</h3> <button v-on:click="spot">点一下就传</button> </div> </template> <script> export defa

  • Vue2.0子同级组件之间数据交互方法

    熟悉了Vue.js的同级组件之间通信,写此文章,以便记录. Vue是一个轻量级的渐进式框架,对于它的一些特性和优点,请在官网上进行查看,不再赘述. 使用NPM及相关命令行工具初始化的Vue工程,目录结构如下 接着我们进入Demo,首先我们可以删除掉模板项目中src/components/Hello.vue,然后在App.vue中删除对于Hello子组件的注册和使用还有一些其他无关紧要的东西,此时的App.vue应为这样 一.我们先来创建中央事件总线,在src/assets/下创建一个eventB

  • 基于Vue2.0的分页组件

    本文实例为大家分享了Vue2.0分页组件的具体实现代码,供大家参考,具体内容如下 整个示例打包了,有需要的可以下载,有不对的地方欢迎指出:vue分页组件 组件部分代码: Vue.component('zpagenav', { template: `<nav class="zpagenav">` + `<ul class="page-ul">` + `<li v-bind:key="index" v-for="

  • vue2.0使用swiper组件实现轮播效果

    轻松实现vue2.0轮播效果,供大家参考,具体内容如下 1.安装swiper npm install swiper@3.4.1 --save-dev 2.引用组件 import Swiper from 'swiper'; import 'swiper/dist/css/swiper.min.css'; 3.html页面代码 <div class="swiper-container" id="swiper"> <div class="swi

  • 详解vue2.0 使用动态组件实现 Tab 标签页切换效果(vue-cli)

    在 vue 中,实现 Tab 切换主要有三种方式:使用动态组件,使用 vue-router 路由,使用第三方插件. 因为这次完成的功能只是简单切换组件,再则觉得使用路由切换需要改变地址略微麻烦,所以使用的是动态组件实现,如果是在大型应用上,可能使用 vue-router 会方便一些. 先看下最终实现的效果,结构比较简单,顶部的三个 Tab 标签用于切换,内容区域分别为三个子组件. 效果预览 关键代码及分析如下: <template> // 每一个 tab 绑定了一个点击事件,传入的参数对应着

  • Vue2.0基于vue-cli+webpack父子组件通信(实例讲解)

    在git命令行下,执行以下命令完成环境的搭建: 1,npm install --global vue-cli 安装vue命令行工具 2,vue init webpack vue-demo 使用vue命令生成一个webpack项目,项目名称为vue-demo 3,cd vue-demo 切入项目 4,npm install安装package.json中的所有依赖包 5,npm run dev运行项目 一.父组件向子组件传递数据 然后删除默认的Hello.vue组件,把App.vue整理成以下样子:

随机推荐