需求说明
- 勾选、取消父节点,子节点的状态需跟随改变
- 勾选、取消子节点,父节点的状态保持不变
- 设置某些节点不可编辑
起因
el-tree 官网默认的效果无法满足该需求,父子节点存在关联性,选中与取消都会影响各自选中状态。在网上搜了挺多做法但都不起作用/不符预期效果,所以就自己从头写了一下
思路
- 首先要取消父子节点默认的关联选中,也就是每个节点在选中的时候,都不会去影响其他的节点选中状态,让 每个节点都成为“独立”的个体,通过 check-strictly 属性即可实现
- 父节点选中取消,所有子节点也要跟着改变;而子节点选中取消却不影响父节点状态,那也就是说我们只需要考虑:当选中某个节点时,如果他存在子节点,就** 递归对他所有的子节点重新设置选中状态**
- 设置部分节点不可选中?主要是通过 node.disabled = true 来实现,我们只需要从后台获取一下需要特殊处理的节点id值(这里我讲id作为了 el-tree 的key 值)存储在一个数组中(specialNodeList),在初始化的时候遍历 el-tree 的 data ,将 key/id 值存在 specialNodeList 中的 节点设置 disabled = true 即可
具体
-
第一步
为了使父子节点不互相影响,第一步最关键的是给 el-tree 设置 :check-strictly=“true” 属性
-
第二步
使用 el-tree 的 check-change 响应事件,每次节点选中状态改变时
(网上还有很多使用 node-click、check 事件来实现的,这里看个人)
1.获取当前 el-tree 选中的所有节点信息 (数组保存 - this.categoryList)
this.categoryList = this.$refs.tree.getCheckedKeys()
2.获取当前节点的所有子节点 key (需要过滤掉那些不可选中的节点 key - 按需要来)
let childIds = []
// 整理并过滤掉特殊的子节点 id 数据 / key 值
this.getChildrenId(data,childIds)
getChildrenId(data,childIds){
if (data.children && data.children.length){
data.children.forEach(item=>{
if (this.specialNodeList.indexOf(item.id) === -1){
// 不是特殊节点,才递归获取
childIds.push(item.id)
if (item.children){
this.getChildrenId(item,childIds)
}
}
})
}
},
3.通过 check-change 事件的第二个参数(共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点本身是否被选中、节点的子树中是否有被选中的节点),来判断当前节点是选中还是取消选中
4.如果是选中,就把当前节点的所有子节点 key 加入 this.categoryList,并去重,重新赋值当前 el-tree 选中节点 key
if (checked){
//选中父节点,把所有子节点加入到 this.categoryList 中,并去重
const arr = this.categoryList.concat(childIds)
this.categoryList = [...new Set(arr)]
// 重新赋值选中节点
this.$refs.tree.setCheckedKeys(this.categoryList)
}
5.如果是取消选中,把所有子节点 key 从 this.categoryList 中删除,重新赋值当前 el-tree 选中节点 key
else {
// 取消选中父节点,把所有子节点从 this.categoryList 中去除
childIds.forEach(item=>{
let index = this.categoryList.indexOf(item)
if (index !== -1){
// 选中的节点数组中包含该子节点,将其去除
this.categoryList.splice(index,1)
}
})
// 重新赋值选中节点
this.$refs.tree.setCheckedKeys(this.categoryList)
}
6.补充一下回显效果逻辑
每次打开的时候,由于要回显勾中我们本来就关联的节点,这些节点会隐式的调用handleCheck 响应方法,会引起什么情况?
会引起每个节点都自动把自己的子节点都勾选上了,导致脏数据(多勾选了一些子节点),而且是默认要选中多少个节点,就自动调用多少次响应方法
补上以下代码
//自定义一下 backTime 全局变量
//且每次初始化时都赋值一下 this.backTime = 1
if (this.backTime <= this.defaultList.length){
this.backTime ++;
return;
}
代码
el-tree 定义
<el-tree
ref="tree"
:data="data"
show-checkbox
node-key="id"
default-expand-all
@check-change="handleCheck"
:default-checked-keys="defaultList"
:check-strictly="true"
>
check-change 事件
/**
* 父节点选中状态变化,子节点跟随
* 子节点选中状态变化,父节点不变
* 核心:通过改变当前选中节点信息 this.categoryList 数组中的元素
* 再使用 setCheckedKeys 函数重新赋值选中节点
* @param data
* @param checked
*/
handleCheck(data,checked){
// 这里默认需要选中几个节点,就会调用多少次,达到次数才执行父子节点关联逻辑->才生效自动选中子节点
if (this.backTime <= this.defaultList.length){
this.backTime ++;
return;
}
this.categoryList = this.$refs.tree.getCheckedKeys()
let childIds = []
// 整理并过滤掉特殊的子节点 id 数据 / key 值
this.getChildrenId(data,childIds)
if (checked){
//选中父节点,把所有子节点加入到 this.categoryList 中,并去重
const arr = this.categoryList.concat(childIds)
this.categoryList = [...new Set(arr)]
// 重新赋值选中节点
this.$refs.tree.setCheckedKeys(this.categoryList)
} else {
// 取消选中父节点,把所有子节点从 this.categoryList 中去除
childIds.forEach(item=>{
let index = this.categoryList.indexOf(item)
if (index !== -1){
// 选中的节点数组中包含该子节点,将其去除
this.categoryList.splice(index,1)
}
})
// 重新赋值选中节点
this.$refs.tree.setCheckedKeys(this.categoryList)
}
},
/**
*
* @param data 当前节点
* @param childIds 保存选中节点的所有子节点 key
*/
getChildrenId(data,childIds){
if (data.children && data.children.length){
data.children.forEach(item=>{
// this.specialNodeList 为特殊不可选中的节点 key 集合,看个人需要可去掉此处判断代码
if (this.specialNodeList.indexOf(item.id) === -1){
// 不是特殊节点,才递归获取
childIds.push(item.id)
if (item.children){
this.getChildrenId(item,childIds)
}
}
})
}
},
ps:el-tree 不同响应事件里面,data 跟 node 不是同一个东西… … 所以还是根据自己的需要选择正确的 event 吧。。本实现方式仅达到了实现效果的程度,还有很多更好的方法可以实现,仅记录==
也可以参考这篇文章:el-tree 带效果图
本文实现的效果,仅仅是比此文多了一个:取消子节点选中状态,不取消父节点的选中状态 ==