目录
背景说明
公司业务要求实现element 级联选择器中对于某一子层级实现全选效果(父子不联动),并且所有数据是默认全部选上的。
最终实现效果
其中一个子节点取消以后
全选判断
因为数据已经是全部选上了,所以默认提供的change事件回调参数和getCheckedNodes方法 没什么用了。
在页面初始化的时候把全选当作一个节点加入mechanismList.list里面(作用是为了方便控制状态,全选节点的信息设置成不会影响业务就行)
下面是这段初始化代码(ps:不是我写的,随便看看就好)
这段代码大致作用就是在除了根节点以外的所有子节点所在层级的第一个位置加上一个全选节点
getBranchTree() {
serve
.getBranchTreeList()
.then((res) => {
mechanismList.data.length = 0
mechanismList.data.push(...res)
mechanismList.list.length = 0
mechanismList.list.push(...res)
mechanismList.list.map((item) => {
if (item.childBranchList) {
item.childBranchList.unshift({ branchName: '全选', branchId: saveBranchId.value })
saveBranchId.value = saveBranchId.value - 1
item.childBranchList.map((item1) => {
if (item1.childBranchList) {
item1.childBranchList.unshift({
branchName: '全选',
branchId: saveBranchId.value
})
saveBranchId.value = saveBranchId.value - 1
item1.childBranchList.map((item2) => {
if (item2.childBranchList) {
item2.childBranchList.unshift({
branchName: '全选',
branchId: saveBranchId.value
})
saveBranchId.value = saveBranchId.value - 1
}
})
}
})
}
})
mechanismList.list.map((item) => {
idNode.parent.push(item.branchId)
saveChangeList.parent.push(item.branchId)
if (item.childBranchList) {
item.childBranchList.map((item1) => {
idNode.parent.push(item1.branchId)
saveChangeList.parent.push(item1.branchId)
if (item1.childBranchList) {
item1.childBranchList.map((item2) => {
idNode.parent.push(item2.branchId)
saveChangeList.parent.push(item2.branchId)
if (item2.childBranchList) {
item2.childBranchList.map((item3) => {
idNode.parent.push(item3.branchId)
saveChangeList.parent.push(item3.branchId)
})
}
})
}
})
}
})
idNode.children = idNode.parent
//idNode.newparent = idNode.parent
saveChangeList.children = saveChangeList.parent
saveChangeList1.parent = saveChangeList.parent
selectLengthOld.value = idNode.parent.length
// saveChangeListnew.children = saveChangeList.children
// saveChangeListnew.parent = saveChangeList.parent
// saveChangeList1new.parent = saveChangeList1.parent
// selectLengthOldnew.value = idNode.newparent.length
mechanismListnew.list.length = 0
mechanismListnew.list.push(...mechanismList.list)
mechanismListnew.data.length = 0
mechanismListnew.data.push(...mechanismList.data)
})
.catch((err) => {})
},
加入这个全选后我们就可以在自定义插槽中比较方便的区分出全选节点和其他节点的区别。
然后我的做法是在级联选择器的插槽中自定义了一个全选选中框(自己放了一个checkedBox进去)
<el-cascader
v-if="casaderBeing"
ref="getNode"
v-model="idNode.parent"
:options="mechanismList.list"
:show-all-levels="false"
:props="props"
:debounce="300"
size="default"
collapse-tags
popper-class="popper"
clearable
filterable
:filter-method="filterMechanism"
@expand-change="expandChange"
>
<template #default="{ node, data }">
<template v-if="data.branchName != '全选'">
<el-checkbox
class="newcheckbox"
v-model="node.checked"
:checked="node.checked"
@click.stop.prevent="select(node, 'parent')"
><span @click.stop.prevent="select(false)">{{
data.branchName
}}</span></el-checkbox
>
</template>
<template v-else>
<el-checkbox
class="newcheckbox"
v-model="node.checked"
:checked="node.checked"
@click.stop.prevent="selectAll(node, 'parent')"
><span>{{ data.branchName }}</span></el-checkbox
>
</template>
</template>
</el-cascader>
const props = {
checkStrictly: true,
multiple: true,
value: 'branchId',
label: 'branchName',
children: 'childBranchList'
}
这么做是因为不使用自定义插槽的话,无法获取到当前点击层级的节点(node)信息(也可能是我不知道别的获取方法)。
在给checkedbox设置上click事件并把node传进去后,我们就能获取当前点击节点的信息了
我们将node打印出来
- 可以看到里面有父节点(parent)的信息和子节点(children)信息,以及一个checked(用于控制checkedbox的选中状态,要将其绑定到我们添加的checkedbox上)
- 有了父节点的信息我们就可以去到父节点来观察其所有子节点的状态(也就当前点击节点所在的层级)
以下代码中所有idNode[val]全当成idNode.parent,即级联多选器已经选中的节点数组
对idNode[val]的push操作相当于选中加进去的节点,splice相当于取消选中
//全选按钮是否勾选
function whetherSelect(node, val) {
//有父节点
if (node.parent) {
//查找全选节点
let selectAllNode = node.parent.children.find((item) => item.label == '全选')
//全选节点在idNode中的下标
let index = idNode[val].findIndex((item) => item == selectAllNode.value)
//判断当前全选状态
if (selectAllNode.checked == false) {
//首先假定为全选状态
selectAllNode.checked = true
if (index != -1) {
idNode[val].push(a.value)
}
}
node.parent.children.forEach((item) => {
if (item.checked == false) {
//判断为非全选状态
selectAllNode.checked = false
}
})
if (selectAllNode.checked == false) {
if (index != -1) {
idNode[val].splice(index, 1)
}
}
}
}
实现单选
实现全选的判断后,非全选节点的单选功能就比较简单了
把节点的状态取反就可以了,在函数最后用上全选判断的函数就可以了
- 在非全选节点上绑定点击事件select(要注意使用stop.prevent阻止checkedbox的默认事件,否则checkedbox点了没效果,因为checkedbox绑定checked后自己也会去修改节点的状态,会和下面在函数里面修改状态产生冲突)
- 可能会出现单选节点的状态被正确修改了,但是级联多选器上的tag并没有增加上去
可以使用级联多选器上面的calculateCheckedValue(重新计算的方法,通过ref去获取),这个方法element组件说明中并没有提到,和同事试了好久才发现的
//单选
/**
*
* @param {当前点击数据节点} node
* @param {处理哪个数组} val
*/
function select(node, val) {
//当node为false时说明是通过点击lable触发的(默认不使用lable)
if (!node) {
return
}
if (node.checked) {
node.checked = false
//勾选状态判断
let index = idNode[val].findIndex((e) => e == node.value)
if (index != -1) {
//是否有找到下标
idNode[val].splice(index, 1)
}
} else {
node.checked = true
idNode[val].push(node.value)
}
//判断全选
whetherSelect(node, val)
//更新选中节点数量
getNode.value.cascaderPanelRef.calculateCheckedValue()
}
全选功能
最后就是去实现全选功能了,也是比较简单
- 先判断全选节点的状态,如果是要取消全选则把这一层所有节点的checked设置为false
- 然后把他们从级联多选器已经选中的节点数组(idNode[val])中去掉就可以了
function selectAll(node, val) {
//勾选状态判断
if (!node.checked) {
//全选
node.parent.children.forEach((nodeItem) => {
if (!nodeItem.checked) {
nodeItem.checked = true
idNode[val].push(nodeItem.value)
}
})
} else {
//取消全选
let index = -1
node.parent.children.forEach((nodeItem) => {
nodeItem.checked = false
index = idNode[val].findIndex((e) => e == nodeItem.value)
if (index != -1) {
idNode[val].splice(index, 1)
}
})
}
//更新选中节点数量
getNode.value.cascaderPanelRef.calculateCheckedValue()
}
使用自定义插槽后的样式问题
在使用了自定义插槽后我们会发现出现了两个checkedbox
我们得想办法把左边的隐藏掉保留我们右边想要的
- 给我们自定义的checkbox添加一个叫newcheckbox的类名
- 通过元素选择器我看能看见左边的checkedbox和我们添加的checkedbox 是相邻的
- 但是我们添加的checkedbox被包裹在了el-cascader-node__label里面
- 所以我们就需要找出所有与包含了newcheckbox的el-cascader-node__label相邻的.el-checkbox并把它们隐藏掉
最后我们使用如下的样式,就可以将左边的checkbox隐藏掉
注意:不可以使用Vue的scoped,这会让样式失效
至于为什么使用了scoped会让样式不起作用,我暂时也没有弄清楚(即使是使用:deep()),希望有大佬能来解答一下
.el-cascader-node > .el-checkbox:has(+ span.el-cascader-node__label > .newcheckbox) {
display: none;
}
添加的checkedbox点lable就能改变状态的问题
如果只想点击选择框去改变状态,可以将checkedbox的文字套一层span标签
然后设置一个没效果的点击事件并阻止默认事件即可
(网上好像并没有找到比较好的解决办法,也没有深入去研究)
<el-checkbox
class="newcheckbox"
v-model="node.checked"
:checked="node.checked"
@click.stop.prevent="select(node, 'parent')"
><span @click.stop.prevent="select(false)">{{
data.branchName
}}</span></el-checkbox
>