zoukankan      html  css  js  c++  java
  • 基于elementui checkbox树形多级联动

    背景

    公司业务有个角色权限设置的需求,数据可能有5到6层的权限,本来是想直接使用elementuiel-tree组件的,奈何ui难以修改,要做成公司想要的样子,只好自己写了。

    数据结构

    后台返回的数据结构是这样的:

    接口权限数据
    {
      code: 0,
      msg: null,
      data: [
        {
          applicationModule: 'xxx',
          menuTreeList: [
            {
              id: 40000,
              parentId: -1,
              children: [
                {
                  id: 40005,
                  parentId: 40000,
                  children: [],
                  name: 'xxx',
                  label: 'xxx',
                },
                {
                  id: 40002,
                  parentId: 40000,
                  children: [
                    {
                      id: 40004,
                      parentId: 40002,
                      children: [
                        {
                          id: 40006,
                          parentId: 40004,
                          children: [],
    
                          name: 'xxx',
    
                          label: 'xxx',
                        },
                        {
                          id: 40007,
                          parentId: 40004,
                          children: [],
    
                          name: 'xxx',
    
                          label: 'xxx',
                        },
                      ],
    
                      name: 'xxx',
    
                      label: 'xxx',
                    },
                    {
                      id: 40003,
                      parentId: 40002,
                      children: [],
    
                      name: 'xxx',
    
                      label: 'xxx',
                    },
                  ],
    
                  name: 'xxx',
    
                  label: 'xxx',
                },
                {
                  id: 40001,
                  parentId: 40000,
                  children: [
                    {
                      id: 40012,
                      parentId: 40001,
                      children: [],
    
                      name: 'xxx',
    
                      label: 'xxx',
                    },
                    {
                      id: 40009,
                      parentId: 40001,
                      children: [
                        {
                          id: 40015,
                          parentId: 40009,
                          children: [],
    
                          name: 'xxx',
    
                          label: 'xxx',
                        },
                        {
                          id: 40017,
                          parentId: 40009,
                          children: [],
    
                          name: 'xxx',
    
                          label: 'xxx',
                        },
                        {
                          id: 40016,
                          parentId: 40009,
                          children: [],
    
                          name: 'xxx',
    
                          label: 'xxx',
                        },
                      ],
    
                      name: 'xxx',
    
                      label: 'xxx',
                    },
                    {
                      id: 40014,
                      parentId: 40001,
                      children: [
                        {
                          id: 40021,
                          parentId: 40014,
                          children: [],
    
                          name: 'xxx',
    
                          label: 'xxx',
                        },
                        {
                          id: 40020,
                          parentId: 40014,
                          children: [],
    
                          name: 'xxx',
    
                          label: 'xxx',
                        },
                      ],
    
                      name: 'xxx',
    
                      label: 'xxx',
                    },
                    {
                      id: 40011,
                      parentId: 40001,
                      children: [],
    
                      name: 'xxx',
    
                      label: 'xxx',
                    },
                    {
                      id: 40008,
                      parentId: 40001,
                      children: [],
                      icon: null,
                      name: 'xxx',
    
                      label: 'xxx',
                    },
                    {
                      id: 40013,
                      parentId: 40001,
                      children: [
                        {
                          id: 40018,
                          parentId: 40013,
                          children: [],
    
                          name: 'xxx',
    
                          label: 'xxx',
                        },
                        {
                          id: 40019,
                          parentId: 40013,
                          children: [],
    
                          name: 'xxx',
    
                          label: 'xxx',
                        },
                      ],
    
                      name: 'xxx',
    
                      label: 'xxx',
                    },
                    {
                      id: 40010,
                      parentId: 40001,
                      children: [],
                      name: 'xxx',
                      label: 'xxx',
                    },
                  ],
                  name: 'xxx',
                  label: 'xxx',
                },
              ],
              name: 'xxx',
              label: 'xxx',
            },
          ],
        },
      ],
    }
    

    后台会返回一个数组,每个数组对象对应一个菜单,权限数据都在menuTreeList数组里。

    权限选择的ui大概的样子:

    拆分组件

    父组件

    • 引入封装好的组件checkboxTree,将需要的数据传入。
    <checkboxTree ref="checkTreeRef" :role-list="tableData"></checkboxTree>
    
    • 编辑回显时,调用子组件的方法
    this.$refs.checkTreeRef.refurbishTreeCheckStatus(res.data, this.tableData)
    
    • 初次拿到数据时,将后台返回的数据重新设置一下,给予初始的选中以及半选状态
    this.tableData = this.$refs.checkTreeRef.formatTreeData(res.data)
    
    • 保存权限时,拿到所有已选择权限的roleId
    params.menuIds = this.$refs.checkTreeRef.returnAllCheckIds(this.tableData)
    

    checkboxTree组件

    html部分,写第一级的权限

    <template>
      <div>
        <template v-for="item in roleList">
          <template v-for="treeData in item.menuTreeList">
            <div :key="treeData.id">
              <p class="check-group">
                <el-checkbox v-model="treeData.mychecked" :indeterminate="treeData.isIndeterminate" @change="handleCheckAllChange({ val: treeData, checked: $event })">
                  {{ treeData.name }}
                </el-checkbox>
              </p>
              <checkboxTreeRender :tree-data="treeData" @handle-check-all-change="handleCheckAllChange"></checkboxTreeRender>
            </div>
          </template>
        </template>
      </div>
    </template>
    

    点击任何checkbox,都会进入到handleCheckAllChange方法,再通过findChildrenfindParent方法不断递归设置整个数据的选中以及半选状态,代码如下:

          handleCheckAllChange(data) {
            let { val, checked } = data
            if (val.children.length > 0) {
              // 处理下级
              this.findChildren(val.children, checked)
            } else {
              // 处理本级
              val.children.forEach((v) => {
                v.mychecked = checked
              })
            }
            if (val.parentId !== -1) {
              // 处理上级
              this.findParent(this.roleList, val.parentId)
            }
            val.isIndeterminate = false
          },
          // 设置子级
          findChildren(list, checked) {
            list.forEach((child) => {
              child.mychecked = checked
              child.isIndeterminate = false
              if (child.children.length > 0) {
                this.findChildren(child.children, checked)
              }
            })
          },
          // 设置这一整条线
          findParent(list, parentId) {
            list.forEach((k) => {
              if (k.menuTreeList) {
                k.menuTreeList.forEach((child) => {
                  this.handleList(child, parentId)
                })
              } else {
                this.handleList(k, parentId)
              }
            })
          },
          // 设置这一整条线具体方法
          handleList(child, parentId) {
            let parentCheckedLength = 0
            let parentIndeterminateLength = 0
            if (child.id === parentId) {
              child.children.forEach((children) => {
                if (children.isIndeterminate) {
                  parentIndeterminateLength++
                } else if (children.mychecked) {
                  parentCheckedLength++
                }
              })
              child.mychecked = parentCheckedLength === child.children.length
              child.isIndeterminate = (parentIndeterminateLength > 0 || parentCheckedLength > 0) && parentCheckedLength < child.children.length
              if (child.parentId !== -1) {
                this.findParent(this.roleList, child.parentId)
              }
            } else if (child.children.length > 0) {
              this.findParent(child.children, parentId)
            }
          },
    

    这是主要checkbox选择交互的联动逻辑,下面是一些工具方法,主要是用于业务保存时需要传递权限id,以及初始拿到后台数据时需要format一下,代码如下:

      const returnCheckTree = (data, checkArr = []) => {
        data.forEach((v) => {
          if (v.mychecked || v.isIndeterminate) {
            !checkArr.includes(v.id) && checkArr.push(v.id)
          }
    
          if (v.children && v.children.length) {
            returnCheckTree(v.children, checkArr)
          }
        })
    
        return checkArr
      }
    
      const fmtTreeData = (data) => {
        data.forEach((v) => {
          v.mychecked = false
          v.isIndeterminate = false
    
          if (v.children && v.children.length > 0) {
            fmtTreeData(v.children)
          }
        })
        return data
      }
    
      // 返回所有已选或权限的role
      returnAllCheckIds(currentData) {
        let roleIds = []
        currentData.forEach((k) => {
          roleIds = [...returnCheckTree(k.menuTreeList), ...roleIds]
        })
    
        return roleIds.join(',')
      },
      // 初始化树状数据
      formatTreeData(currentData) {
        currentData.forEach((k) => {
          fmtTreeData(k.menuTreeList)
        })
    
        return currentData
      },
    

    最后,编辑角色时需要回显角色权限,后台返回给我的数据结构和全部权限是一致的,只是只会返回已经选择的权限数据,当然,对我来说,什么结构都无所谓,因为我这种做法,实际上是要递归把所有权限id丢到一个数组里面,
    我的思路是先拿到所有的权限id数组放到roleIds里,然后将所有权限idroleIds里的对象设置为已选,再重新去设置半选,当前对象是已选,但children对象的已选比children的长度少,说明当前对象是半选。代码如下:

          const returnEditRoleTreeIds = (data, checkArr = []) => {
            data.forEach((v) => {
              !checkArr.includes(v.id) && checkArr.push(v.id)
    
              if (v.children && v.children.length) {
                returnEditRoleTreeIds(v.children, checkArr)
              }
            })
    
            return checkArr
          }
          
          // 编辑时回显权限数据
          refurbishTreeCheckStatus(checkData, allData) {
            let roleIds = []
            let firstLevelIds = []
            let notFirstLevelIds = []
            checkData.forEach((k) => {
              roleIds = [...returnEditRoleTreeIds(k.menuTreeList), ...roleIds]
            })
            allData.forEach((k) => {
              this.setTreeCheckStatus(k.menuTreeList, roleIds)
            })
    
            allData.forEach((k) => {
              this.setTreeIndeterminateStatus(k.menuTreeList)
            })
          },
          // 所有已选择的role全部设置为已选
          setTreeCheckStatus(data, roleIds = []) {
            data.forEach((v) => {
              if (roleIds.includes(v.id)) {
                v.mychecked = true
              }
    
              if (v.children && v.children.length) {
                this.setTreeCheckStatus(v.children, roleIds)
              }
            })
          },
          // 重新递归设置半选状态
          setTreeIndeterminateStatus(data) {
            data.forEach((v) => {
              let parentCheckedLength = 0
              let parentIndeterminateLength = 0
              v.children.forEach((children) => {
                if (children.isIndeterminate) {
                  parentIndeterminateLength++
                } else if (children.mychecked) {
                  parentCheckedLength++
                }
              })
              v.isIndeterminate = (parentIndeterminateLength > 0 || parentCheckedLength > 0) && parentCheckedLength < v.children.length
    
              if (v.children && v.children.length) {
                this.setTreeIndeterminateStatus(v.children)
              }
            })
          },
    

    应该不是最好的思路,各位有更好的建议可以在评论区告诉我。

    checkboxTreeRender组件

    这个组件主要是递归组件,去渲染树形dom结构。

    <template>
      <div>
        <div v-if="treeData.children && treeData.children.length" style="padding-left: 24px">
          <div v-for="childrenData in treeData.children" :key="childrenData.id" :style="returnStyle(childrenData.children)">
            <el-checkbox
              v-model="childrenData.mychecked"
              style="margin-bottom: 15px"
              :indeterminate="childrenData.isIndeterminate"
              :label="childrenData.id"
              @change="handleCheckAllChange({ val: childrenData, checked: $event })"
            >
              {{ childrenData.name }}
            </el-checkbox>
            <checkboxTreeRender :tree-data="childrenData" @handle-check-all-change="handleCheckAllChange"></checkboxTreeRender>
          </div>
        </div>
      </div>
    </template>
    

    接收一个数据对象

        props: {
          treeData: {
            type: Object,
            default: function () {
              return {}
            },
          },
        },
    

    以及将checkbox变化的方法抛给父组件去处理,这个组件只负责渲染

          returnStyle(child) {
            const premise = child && child.length
            return {
              display: premise ? '' : 'inline-block',
              marginRight: premise ? '' : '30px',
            }
          },
          handleCheckAllChange(data) {
            this.$emit('handle-check-all-change', data)
          },
    

    至此,一个基于elementui的多层checkbox树形联动组件就写好了。

    结语

    最开始需求是说最多只有三层结构,所以我就写了一版写死的三层联动的逻辑,使用了checkboxGroup,只需要在checkboxGroup上进行监听就能拿到下面所有选择的checkbox。后面说要支持更多层,发现当初这样子已经无法实现,当初写的太呆了,
    于是重新写了一版,通过这次对递归的使用也有了一些理解,因为以前很少使用这个,也算是学习到了,记录一下。
    全部源码放到github上了,传送门

  • 相关阅读:
    我的第一个Python爬虫——谈心得
    python写第一个网页
    科学计算和可视化
    类和正则表达(自动更正,和代数运算)
    图片处理
    jieba
    汉诺塔问题
    Python核心编程(第二版)第十章习题答案
    python官方文档之open()函数的翻译
    python打开文件时提示“File was loaded in the wrong encoding:’UTF-8”根因调查
  • 原文地址:https://www.cnblogs.com/wangxi01/p/14034320.html
Copyright © 2011-2022 走看看