ElementUI的table组件实现多级树形结构的双向全选、半选和取消功能

背景

在开发业务时,遇到多级树形的表格的勾选问题,发现ElementUI的table组件没有这个功能,可能是没用对,反正没找到,最后找到篇文章提供了个思路,实现了多级树形结构的双向全选、半选和取消功能。
效果如下:
PixPin_2024-05-04_18-41-29.gif
思路来源:https://www.cnblogs.com/Fancy1486450630/p/16706914.html

大致思路

大致思路就是:

  1. 初始化格式数据,递归给每一层数据内添加一个标识当前行的选中状态的字段(isSelect,用三个值区分:true为全勾选,false为未勾选,''为半选)。添加一个parentId记录父级的id值。
  2. 实现父级向下联动勾选或取消勾选,通过递归children去改变每层的标记值,同时调用table组件的apitoggleRowSelection去改变选中状态。
  3. 实现子级向上操作父级联动勾选、半选或取消勾选,通过初始化的parentId去遍历数据数组,去找到父级数组,然后通过递归获取所以子级的选中态集合,如果都为true则为全选,设置为全选,如果都为false则为取消选中,否则就为半选,把标记置为空字符串。
  4. 半选状态的展示,通过table的api属性给每行和表头添加类名,通过补充CSS样式实现半选效果。

代码示例

<template>
  <el-table
    :data="tableData"
    ref="multipleTable"
    border
    row-key="id"
    :row-class-name="getRowClassName"
    :header-row-class-name="getHeaderRowClassName"
    size="mini"
    max-height="500px"
    style="width: 100%"
    @select="selectFun"
    @select-all="selectAllFun"
    @selection-change="selectionChangeFun"
    :header-cell-style="{ background: '#fafafa' }"
  >
    <el-table-column type="selection" width="55"> </el-table-column>
    <el-table-column prop="date" label="日期" sortable width="180">
    </el-table-column>
    <el-table-column prop="name" label="姓名" sortable width="180">
    </el-table-column>
    <el-table-column prop="address" label="地址"> </el-table-column>
  </el-table>
</template>
<script>
export default {
  name: 'listSelectDemo',
  data () {
    return {
      // 表格数据
      tableData: [
        {
          id: 1,
          date: '2016-05-02',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1518 弄'
        },
        {
          id: 2,
          date: '2016-05-01',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1519 弄',
          children: [
            {
              id: 3,
              date: '2016-05-01',
              name: '王小虎',
              address: '上海市普陀区金沙江路 1519 弄',
              children: [
                {
                  id: 4,
                  date: '2016-05-01',
                  name: '王小虎',
                  address: '上海市普陀区金沙江路 1519 弄'
                },
                {
                  id: 5,
                  date: '2016-05-01',
                  name: '王小虎',
                  address: '上海市普陀区金沙江路 1519 弄'
                }
              ]
            },
            {
              id: 6,
              date: '2016-05-01',
              name: '王小虎',
              address: '上海市普陀区金沙江路 1519 弄'
            }
          ]
        }
      ],
      // 勾选中的行数据
      selectedData: []
    }
  },
  mounted () {
    this.initData(this.tableData)
  },
  methods: {
    // 初始化数据
    initData (data, parentId) {
      data && data.length && data.forEach((item) => {
        // isSelect状态:true为选中状态,false为未选中状态 ,空字符串为未知状态
        item.isSelect = false // 默认为不选中
        parentId && (item.parentId = parentId)
        if (item.children && item.children.length) {
          this.initData(item.children, item.id)
        }
      })
    },
    // 监听选中事件
    selectionChangeFun (selection) {
      this.selectedData = selection
    },
    // 复选框点击事件
    selectFun (selection, row) {
      this.setRowIsSelect(row)
    },
    // 复选框点击事件
    setRowIsSelect (row) {
      // 为空字符串则对应行半选态,点击则应该变为全选,则为true
      if (row.isSelect === '') {
        this.$refs.multipleTable.toggleRowSelection(row, true)
      }
      row.isSelect = !row.isSelect
      const _this = this
      // 判断操作的是子级点复选框还是父级点复选框,如果是父级点,则控制子级点的全选和不全选

      // 1、只是父级 2、既是子集,又是父级 3、只是子级
      const levelStatus = getCurrentLevel(row)

      if (levelStatus === 1) {
        // 操作的是父级,则所有子集同步父级状态
        selectAllChildren(row.children)
      } else if (levelStatus === 2) {
        selectAllChildren(row.children)
        operateLastLevel(row)
      } else if (levelStatus === 3) {
        operateLastLevel(row)
      }
      // 判断当前操作行的层级状态
      function getCurrentLevel (row) {
        // 1、只是父级 2、既是子集,又是父级 3、只是子级
        if (row.parentId === undefined) {
          return 1
        } else if (row.parentId && !(row.children && row.children.length)) {
          return 3
        } else {
          return 2
        }
      }

      // 递归处理children子级数据的isSelect状态以及对选框状态
      function selectAllChildren (children) {
        children && children.length && children.forEach((item) => {
          item.isSelect = row.isSelect
          _this.toggleRowSelection(item, row.isSelect)
          if (item.children && item.children.length) {
            selectAllChildren(item.children)
          }
        })
      }
      // 子级向上操作父级状态
      function operateLastLevel (row) {
        // 操作的是子节点,则
        // 1、获取父节点
        // 2、判断子节点选中个数,如果全部选中则父节点设为选中状态,如果都不选中,则为不选中状态,如果部分选择,则设为不明确状态
        const item = getExplicitNode(_this.tableData, row.parentId)
        const selectStatusArr = getSelectStatus([], item.children)

        // 判断所有子集选中态,都为true则父级也选中,都为false则不选中,否则为半选
        const allSelected = selectStatusArr.every(selectItem => selectItem === true)
        const allUnselected = selectStatusArr.every(selectItem => selectItem === false)
        if (allSelected) {
          item.isSelect = true
          _this.toggleRowSelection(item, true)
        } else if (allUnselected) {
          item.isSelect = false
          _this.toggleRowSelection(item, false)
        } else {
          item.isSelect = ''
        }
        // 如果当前节点的parentId存在,则还有父级,继续递归往上一层操作
        if (item.parentId !== undefined) {
          operateLastLevel(item)
        }
      }
      // 递归获取所有子级的选中状态数组
      function getSelectStatus (selectStatusArr, children) {
        if (!children || !children.length) {
          return selectStatusArr
        }
        children.forEach((child) => {
          selectStatusArr.push(child.isSelect)
          if (child.children && child.children.length) {
            getSelectStatus(selectStatusArr, child.children)
          }
        })
        return selectStatusArr
      }

      // 获取明确父级的节点
      function getExplicitNode (tableData, parentId) {
        let result = null
        function findNode (data) {
          for (let i = 0; i < data.length; i++) {
            if (data[i].id === parentId) {
              result = data[i]
              break
            }
            if (data[i].children && data[i].children.length) {
              findNode(data[i].children)
            }
          }
        }
        findNode(tableData)
        return result
      }
    },
    // 表格全选事件
    selectAllFun (selection) {
      const firstLevelStatus = this.tableData.map(item => item.isSelect)
      // 判断一级是否是全选.如果一级产品全为true,则设置为取消全选,否则全选
      const isAllSelect = firstLevelStatus.every(status => status)
      // 设置所以一级和二级为选中状态
      this.tableData.forEach((item) => {
        item.isSelect = isAllSelect
        this.toggleRowSelection(item, !isAllSelect)
        this.selectFun(selection, item)
      })
    },
    // 设置当前行的选中态
    toggleRowSelection (row, flag) {
      if (row) {
        this.$nextTick(() => {
          this.$refs.multipleTable &&
              this.$refs.multipleTable.toggleRowSelection(row, flag)
        })
      }
    },
    // 表格行样式 当当前行的状态为不明确状态时,添加样式,使其复选框为不明确状态样式
    getRowClassName ({ row }) {
      if (row.isSelect === '') {
        return 'indeterminate'
      }
    },
    // 表格标题样式 当一级目录有为不明确状态时,添加样式,使其全选复选框为不明确状态样式
    getHeaderRowClassName ({ row }) {
      const isIndeterminate = this.tableData.some(item => item.isSelect === '')
      if (isIndeterminate) {
        return 'indeterminate'
      }
      return ''
    }
  }
}
</script>

<style lang="scss">
.indeterminate .el-checkbox__input .el-checkbox__inner {
    background-color: #409eff !important;
    border-color: #409eff !important;
    color: #fff !important;
}

.indeterminate .el-checkbox__input.is-checked .el-checkbox__inner::after {
    transform: scale(0.5);
}

.indeterminate .el-checkbox__input .el-checkbox__inner {
    background-color: #f2f6fc;
    border-color: #dcdfe6;
}

.indeterminate .el-checkbox__input .el-checkbox__inner::after {
    border-color: #c0c4cc !important;
    background-color: #c0c4cc;
}

.indeterminate .el-checkbox__input .el-checkbox__inner::after {
    content: '';
    position: absolute;
    display: block;
    background-color: #fff;
    height: 2px;
    transform: scale(0.5);
    left: 0;
    right: 0;
    top: 5px;
    width: auto !important;
}
</style>

总结

实现内容思路大致与参考博文一致,在原文基础上修改为不需要手动设置parentId,自动格式化生成,以及简化层级判断等各个函数的代码逻辑,优化代码效率,实现效果还算不错,仍然有一些问题,通过table组件的selection-change事件去获取当前选中数组时,会多次触发。使用建议是提取一个公共的mixin来导入到需要的表格组件使用。

有更好的解决方案希望大家在评论区留言指正。

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
针对你的问题,elementUI 的级联选择器(Cascader)组件提供了一些属性和方法,可以实现全选/取消全选和按勾选顺序回显排序的功能。 1. 全选/取消全选 elementUI 的级联选择器(Cascader)组件提供了一个 checkStrictly 属性,可以控制级联选择器是否可以全选取消全选。当 checkStrictly 为 true 时,级联选择器可以全选取消全选;当 checkStrictly 为 false 时,级联选择器只能单选或多选。 2. 按勾选顺序回显排序 elementUI 的级联选择器(Cascader)组件提供了一个 value 属性,可以设置级联选择器的选中项。当级联选择器的选项是固定的时候,可以通过设置 value 属性来回显选中项;当级联选择器的选项是动态生成的时候,可以通过监听级联选择器的 change 事件,获取选中项的顺序和值,并进行排序和回显。 下面是一个简单的示例代码,实现全选/取消全选和按勾选顺序回显排序的功能: ```html <template> <el-cascader :options="options" :check-strictly="true" :value="selectedItems" @change="handleChange" @expand-change="handleExpandChange" @clear="handleClear" ></el-cascader> </template> <script> export default { data() { return { options: [ { value: 'zhinan', label: '指南', children: [ { value: 'shejiyuanze', label: '设计原则', children: [ { value: 'yizhi', label: '一致' }, { value: 'fankui', label: '反馈' }, { value: 'xiaolv', label: '效率' }, { value: 'kekong', label: '可控' }, ], }, { value: 'daohang', label: '导航', children: [ { value: 'cexiangdaohang', label: '侧向导航' }, { value: 'dingbudaohang', label: '顶部导航' }, ], }, ], }, ], selectedItems: [], }; }, methods: { handleChange(value, selectedItems) { this.selectedItems = selectedItems; }, handleExpandChange() { // do something }, handleClear() { this.selectedItems = []; }, }, }; </script> ``` 在上面的示例代码中,我们通过设置 checkStrictly 属性为 true,开启了全选/取消全选功能。同时,我们监听了级联选择器的 change 事件,在选中项发生变化时,更新 selectedItems 数组的值,并实现了按勾选顺序回显排序的功能。 希望这个回答对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值