**已更新: 现在适合任意个子节点数据结构(不包含子节点,一个子结点,多个子结点,多个孙子节点等等),如果数据结构不是label, value, children形式的,可以通过props属性进行映射
**已更新2023/09/13: 修复全选后, 取消任意子项,选中第一项的问题, 具体操作在文章最下面:
vue+elementUI(cascader)全选功能实现-- 最新版视频--支持多层级数据结构
需求: 树形以及单级下拉选择框, 增加全选功能; el-select的写在另外一篇文章里,自己把方法封装了一下, 适用于一个页面任意多个多选框增加全选vue+elementUI(el-select)实现全选功能
百度查了很久,只有几个相关的语言描述, 然后综合翻阅文档,发现: element中的下拉选择框(el-cascader/el-select)都没有, 获取当前选中的option的方法或者参数, 只有当前已选择的option列表. 个人感觉这个有点不太友好,所以就自己写了全选的功能, 希望能帮到需要的小伙伴!!!
****************如果想要全选同时, 展示的时候不显示 '全选' 这一项,请看文章最后!!!!
<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() {
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: {
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);
},
}
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()
}
}
主要思路就是: 展示的时候暴露 ‘全选’ 项, 隐藏的时候关闭 '全选’项
原因: 没看源码, 感觉上是因为这里是在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]
}
}
}