zoukankan      html  css  js  c++  java
  • 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;
       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返回。

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


  • 相关阅读:
    TextField 属性与注意
    as3:获取系统信息
    转:As3 优化总结,代码写法和api使用事项。
    文本编辑器制作(1):2种方案实现
    FlashBuilder编译参数
    as3 动态类库使用
    webgame:版本更新与本地缓存
    A*
    FlashBuilder方便的调试UI插件Monster Debugger
    sourcemate flex插件
  • 原文地址:https://www.cnblogs.com/archermeng/p/7537067.html
Copyright © 2011-2022 走看看