Vue2.0权限树组件

发布时间:2018-11-23 08:24:31  访问次数:

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

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