**已更新: 现在适合任意个子节点数据结构(不包含子节点,一个子结点,多个子结点,多个孙子节点等等),如果数据结构不是label, value, children形式的,可以通过props属性进行映射

**已更新2023/09/13: 修复全选后, 取消任意子项,选中第一项的问题, 具体操作在文章最下面:

vue+elementUI(cascader)全选功能实现-- 最新版视频--支持多层级数据结构

需求: 树形以及单级下拉选择框, 增加全选功能; el-select的写在另外一篇文章里,自己把方法封装了一下, 适用于一个页面任意多个多选框增加全选vue+elementUI(el-select)实现全选功能

百度查了很久,只有几个相关的语言描述, 然后综合翻阅文档,发现: element中的下拉选择框(el-cascader/el-select)都没有, 获取当前选中的option的方法或者参数, 只有当前已选择的option列表. 个人感觉这个有点不太友好,所以就自己写了全选的功能, 希望能帮到需要的小伙伴!!!

****************如果想要全选同时, 展示的时候不显示 '全选' 这一项,请看文章最后!!!!

el-cascader
<template>
  <div>
    <el-button @click="defaultSelectSomeone">默认选中某项</el-button>
    <el-button @click="defaultSelectAll">默认选中全部</el-button>
    <el-cascader
      v-model="ceshi.value"
      clearable
      filterable
      :options="ceshi.options"
      :props="{
        multiple: true,
        value: 'value',
        label: 'label'
      }"
      :collapse-tags="true"
      placeholder="请选择"
      @change="selectHandle"
    >
      <template slot-scope="{ node, data }">
        <span>{{ data.label }}</span>
        <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
      </template>
    </el-cascader>
  </div>
</template>
data
data() {
  return {
  	preSelected: [], // 上次选中的数据
    originData: [], //  源数据平铺成一级节点
    ceshi: {
      value: [],
      options: [
        // { label: '全选', value: '全选' },
        { label: '层级0', value: '0' },
        { label: '层级1', value: '1', children: [
          { label: '层级11', value: '11', children: [
            { label: '层级111', value: '111', },
            { label: '层级112', value: '112', }
          ] },
          { label: '层级12', value: '12', children: [
            { label: '层级121', value: '121', }
          ] }
        ]},
        { label: '层级2', value: '2', children: [
          { label: '层级21', value: '21' },
          { label: '层级22', value: '22' },
        ]},
        { label: '层级3', value: '3', children: [
          { label: '层级31', value: '31' }
        ]}
      ]
    }
  }
}
methods
methods: {
    defaultSelectSomeone() {
      this.preSelected = []
      this.ceshi.value = []
      this.ceshi.value = [['2', '21']];
    },
    defaultSelectAll() {
      this.preSelected = []
      this.ceshi.value = []
      if (this.ceshi.options[0].label !== '全选') {
        this.ceshi.options.unshift({ label: '全选', value: '全选' })
      }
      this.selectHandle([['全选']])
    },
    judgetAllSelected(node) {
      // 判断是否是全选,也就是看已选择的选中中包不包含"全选"
      let isAllSelected = false
      for(let i = 0; i < node.length; i++) {
          if(node[i][0] === '全选') {
            isAllSelected = true
            break;
          }
      }
      return isAllSelected
    },
    loopFlatData(list = [], parentNode = []) {
      list.length > 0 && list.forEach(e => {
        let pNode = [...parentNode]; // 注意这里必须是深拷贝,否则会由于引用类型赋值的是地址(指针),导致parentNode在pNode更新时,同时被更新
        if(e.children && e.children.length > 0) {
          pNode.push(e.value)// 1 11
          this.loopFlatData(e.children, pNode)
        }else {
          if(e.label !== '全选') {
            if(parentNode.length > 0) {
              pNode.push(e.value)
              this.originData.push({ ...e, parentNode: pNode })
            }else {
              this.originData.push(e)
            }
          }
        }
      })
    },
    loopSelectData(list, parentNode = []) {
      list.length > 0 && list.forEach(e => {
        let pNode = [...parentNode]; // 注意这里必须是深拷贝,否则会由于引用类型赋值的是地址(指针),导致parentNode在pNode更新时,同时被更新
        if(e.children && e.children.length > 0) {
          pNode.push(e.value)// 1 11
          this.loopSelectData(e.children, pNode)
        }else {
          if(parentNode.length > 0) {
            this.ceshi.value.push([...parentNode, e.value])
          }else {
            this.ceshi.value.push([e.value])
          }
        }
      })
    },
    checkIsAddAllSelected() {
      let list = this.ceshi.options; // 原始数据列表
      if(this.originData.length === 0) {
        this.loopFlatData(list) // 把所有的父子级平铺成一个一级列表
      }
      let origin = this.originData;
      let now = [...this.ceshi.value].filter(item => item[0] !== '全选')
      if(origin.length > now.length) {
        // 非全选时, 如果有之前选过全选,要把全选过滤掉
        this.ceshi.value = this.ceshi.value.filter(item => item[0] !== '全选')
      }else {
        // 当所有的数据都选择时, 要自动把全选勾选上
        if(this.ceshi.value[0] && this.ceshi.value[0][0] !== '全选') {
          this.ceshi.value = [['全选'], ...this.ceshi.value]
        }
      }
    },
    async selectHandle(node = []) {
      this.defaultExpandedKeys = []
      this.ceshi.value = []
      // 选中的数据格式: [['全选'], ['0'], ['1', '11', '111'], ['2', '21'],...]
      let list = this.ceshi.options
      let current = []; // 获取当前选中的option, 因为element文档中没有获取当前选中的option的方法,所以我通过上一次和本地的选中数据进行对比来获取
      if(node.length >= this.preSelected.length) {
        let keys = this.preSelected.map(item => JSON.stringify(item))
        current = node.filter(item => !keys.includes(JSON.stringify(item)))
        console.log('选中某项', current);
      }else {
        // 取消选中
        let keys = node.map(item => JSON.stringify(item))
        current = this.preSelected.filter(item => !keys.includes(JSON.stringify(item)))
        console.log('取消选中', current);
        this.defaultExpandedKeys = current[0]
      }
      // 根据element的选中数据格式, 每一个选项都是一个列表, 列表第一项为父级value, 第二项为选中的子级value, ...以此类推
      const currentValue = current.length > 0 ? current[0][0] || '' : ''
      if(currentValue === '全选') {
        if(this.judgetAllSelected(node)) {
          this.loopSelectData(list)
        }else {
          this.ceshi.value = []
        }
      }else {
        this.ceshi.value = node
      }
      this.checkIsAddAllSelected(); // 主要是勾选或取消非“全选”项时,对于全选的逻辑处理
      this.preSelected = this.ceshi.value; // 保存上一次的选择结果
      this.changeHandle();
    },
    changeHandle() {
      // 这里是处理成自己需要的数据格式, 需要把全选的这一选项过滤掉
      // 原始选择的数据格式[['全选'], ['1', '11'], ['2', '21'],...]
      console.log('changeHandle: ', this.ceshi.value);
    },

  }

注意:如果全选展示的时候不想要展示 ’全选‘,请做以下更改:

element ui select 右侧箭头不变 element ui select 全选_elementui

element ui select 右侧箭头不变 element ui select 全选_javascript_02

1. options 中的  // { label: '全选', value: '全选' } 删掉
2. <el-cascader ... @visible-change="visibleChange"> 新增下拉框显示隐藏监听事件
3. methods 添加 事件处理
	visibleChange(visible) {
      if(visible) {
        this.ceshi.options.unshift({ label: '全选', value: '全选' })
      }else {
        this.ceshi.options.shift()
      }
    }

主要思路就是: 展示的时候暴露 ‘全选’ 项, 隐藏的时候关闭 '全选’项

2023/09/13 fixed: 修复全选后, 取消任意子项,选中第一项的问题

原因: 没看源码, 感觉上是因为这里是在change事件增加全选逻辑, 这个方法猜测应该是内部"勾选"或者"取消勾选"的回调,所以每次"勾选"或者"取消勾选"后,才会执行到这里,然后又走了"全选",所以会自动跳到"全选",但全选是"未选中状态",像又触发了"内部隐藏的一个逻辑" – “自动展开第一个勾选中的节点”

解决方法: 自动触发展开,没有暴露API案例, 只能自己写了

1. dom 新增 ref="cascaderRef"
2. data 新增 defaultExpandedKeys: [] // 展开节点
3. checkIsAddAllSelected 方法替换
async checkIsAddAllSelected() {
      let list = this.ceshi.options; // 原始数据列表
      if (this.originData.length === 0) {
        this.loopFlatData(list) // 把所有的父子级平铺成一个一级列表
      }
      let origin = JSON.parse(JSON.stringify(this.originData)).filter(item => item.label !== '全选')
      let now = JSON.parse(JSON.stringify(this.ceshi.value)).filter(item => item[0] !== '全选')
      // console.log('...xxx', origin.length, now.length);
      if (origin.length > now.length) {
        // 非全选时, 如果有之前选过全选,要把全选过滤掉
        this.ceshi.value = this.ceshi.value.filter(item => item[0] !== '全选')
        // console.log(this.$refs.cascaderRef);
        await this.$nextTick()
        const level = this.defaultExpandedKeys.length
        const l0 = this.ceshi.options.findIndex(e => e.code === this.defaultExpandedKeys[0])
        const menus = this.$refs.cascaderRef.panel.$refs.menu
        // console.log(menus);
        if (menus[0] && l0 >= 0) {
          const firstNodes = menus[0].$el.querySelectorAll('.el-cascader-node[tabindex="-1"]');
          // console.log(firstNodes);
          firstNodes[l0].click()
          await this.$nextTick()
          let l1 = 0
          if (level > 1) {
            const children = this.ceshi.options[l0]?.children || []
            l1 = children.findIndex(e => e.code === this.defaultExpandedKeys[1])
            // console.log(this.defaultExpandedKeys, level, l0, l1);
            if (menus[1] && l1 >= 0) {
              const firstNodes = menus[1].$el.querySelectorAll('.el-cascader-node[tabindex="-1"]');
              // console.log(firstNodes);
              firstNodes[l1].click()
            }
          }
        }
      } else {
        // 当所有的数据都选择时, 要自动把全选勾选上
        if (this.ceshi.value[0] && this.ceshi.value[0][0] !== '全选') {
          this.ceshi.value = [['全选'], ...this.ceshi.value]
        }
      }
 }