写点什么

Element-UI 实战系列:Tree 组件的几种使用场景

用户头像
brave heart
关注
发布于: 2020 年 06 月 07 日
Element-UI实战系列:Tree组件的几种使用场景

前言

在使用Vue开发项目时,引入了Element-UI组件库,因为自己开发的部分,很多都是树形结构数据相关的,所以经常使用到Tree组件,在好几次实现需求的过程中,遇到了很多难点,自己也是绞尽脑汁才解决的,这里就把遇到的几种使用场景,归纳总结一下。



一、普通模式 + 全部展开/全部收缩 + 全选/清空

这个主要是用来实现项目中权限功能部分,并且是在弹窗里面的,弹窗组件是使用Element-UI里的Dialog组件,也就是说Dialog组件是包裹Tree组件的,刚开始实现时,已经给Tree组件设置了一个ID引用,也就是ref,但是this.$refs.tree一直显示报错,后来才发现是因为外层使用Dialog组件的缘故,Dialog组件的源码显示使用了v-if,所以才获取不到,如图下所示: 



要获取this.$refs.tree,需要在下一个tick中才能获取到,代码如下所示:

this.$nextTick(() => {
console.log(this.$refs.tree)
});

而且需要注意的是,在v-for中使用Tree组件,也会出现this.$refs.tree报错的问题,其原因,在之前的博文也说明过,不过这里还是把图贴出来,如下图所示:



所以要在v-for中正确获取到this.$refs.tree,需要将其改为this.$refs.tree[0]

效果图



实现过程

全部展开/全部收缩



stretchClicked () {
this.isStretch = !this.isStretch
// tree为Tree组件的ref值,isexpand为true或false
this.$nextTick(() => {
for (var i = 0; i < this.$refs.tree.store._getAllNodes().length; i++) {
this.$refs.tree.store._getAllNodes()[i].expanded = this.isStretch
}
})
},

全选/清空

selectClicked () {
this.isSelect = !this.isSelect
this.$nextTick(() => {
for (var i = 0; i < this.$refs.tree.store._getAllNodes().length; i++) {
this.$refs.tree.store._getAllNodes()[i].checked = this.isSelect
}
})
}

因为Tree组件里设置node-key="id"的原因,TreeData里的数据要有这个ID,理论上从服务器获取回来的数据不会有这个ID的,而且哪怕自己在本地模拟,要手动每个node加上ID,然后还要唯一,还要排序,这个就很大工作量了,所以需要对数据进行处理,在初始化时,给TreeData里的每个node加上ID,代码如下所示:

// 给tree属性设置id
setTreeDataNodeId (treeData) {
var count = 1
function traverse (data) {
data.forEach(item => {
item.id = count
count = count + 1
if (item.children) {
traverse(item.children)
}
})
}
traverse(treeData)
}

二、普通模式 + 单选

Tree组件默认提供的是多选的,这边打算是实现单选功能,图标是在右侧,一开始实现的方式是在每一个节点加上一个字段,比如isChecked来设置选中和未选中的状态,但是发现在点击节点时,已经更改该节点的isChecked值了,但是视图并没有更新,原来在data里初始化的时候,没有默认加上isChecked属性,我是在Creaed()方法里,遍历TreeData加上isChecked的,所以没触发视图更新,其原因是只有当实例被创建时就已经存在于data中的 property 才是响应式的。也就是说如果你添加一个新的property,比如这个isChecked,那么对isChecked的改动将不会触发任何视图的更新,详见Vue.js文档里数据与方法部分,基于这个原因,如果要实现这个功能,难道要给这个TreeData手动加上isChecked么,如果数据量很大呢,或者是从服务器返回的数据呢,显然不现实,为了解决这个问题,我就想到,既然是单选,那可不可以直接使用Tree组件提供的checked字段呢?

效果图



实现过程

其实主要还是实现上面全选功能时使用的那个方式,只要稍微变通下,就很明朗了,每次点击节点时,默认将Tree组件里节点的checked状态重置为false,然后将点击的节点checked设置为true即可,代码如下所示:



nodeClicked (data, node) {
this.$nextTick(() => {
// 每次点击节点时,默认将Tree组件的勾选状态设置为false
for (var i = 0; i < this.$refs.tree.store._getAllNodes().length; i++) {
this.$refs.tree.store._getAllNodes()[i].checked = false
}
// 给选中的节点checked设置为true
this.$refs.tree.store._getAllNodes()[node.key - 1].checked = true
})
}

但是,通过观察this.$refs.tree.store,可以发现更好的实现方式,代码如下所示:

nodeClicked (data, node) {
this.$refs.tree.setCheckedKeys([])
this.$refs.tree.store.currentNode.checked = true
}

至于动态切换Tree组件普通模式和懒加载模式,起先我是想着:lazy="isLazy",但是发现没能成功,后来研究一番,才发现可以使用这个方式实现,代码如下所示:

this.$refs.tree.store.lazy = true

我本来是打算,将普通模式和懒加载模式结合起来,然后通过切换,也一样可以复用单选功能的,但是,研究了好久,还是觉得分开来写,分开来实现更为现实。



三、懒加载 + 单选

在实现普通模式+单选功能的时候,可以直接使用Tree组件提供的checked字段,但在懒加载的情况下,发现实现很难,这样只能使用新加isChecked字段来实现了。

效果图

实现过程

Tree的懒加载模式,使用起来要麻烦一点,实现单选功能,也就是通过新增的isChecked来切换勾选状态,首先要在Tree组件加上懒加载模式,设置代码如下所示:



<el-tree ref="tree"
class="tree-wrapper"
node-key="id"
:props="props"
:load="loadNode"
@node-click="nodeClicked"
lazy>
<el-tree>

然后在loadNode方法里,当node.level0时,新增一个根节点,同时设置ID,代码如下所示:

// 初始化数据
if (node.level === 0) {
this.treeData = [{
label: 'lazy-' + (node.level + 1),
isChecked: false,
children: []
}]
resolve(this.treeData)
this.setTreeDataNodeId(this.treeData)
return
}

接着,当点击节点前面三角箭头,加载数据时,加上子节点,并且将TreeData,遍历设置ID值,加上延时500ms加载,是为了模拟懒加载呈现的效果,代码如下所示:

setTimeout(() => {
const data = [
{
label: 'lazy-1-' + node.level,
isChecked: false,
children: []
},
{
label: 'lazy-1-2',
isChecked: false,
children: [],
leaf: true
}]
node.data.children = data
resolve(data)
this.setTreeDataNodeId(this.treeData)
}, 500)

最后,就是点击节点,实现单选功能的处理,实现的逻辑也是一样的,每次点击该节点,就要将当前TreeData全部节点的isChecked重置为false,然后,点击了这个节点,就将对应节点的isChecked设置为true,这样就实现了单选效果,代码如下所示:

nodeClicked (data, node) {
this.setTreeDataNodeCheckedAllFalse(this.treeData)
data.isChecked = true
},
setTreeDataNodeCheckedAllFalse (treeData) {
function traverse (data) {
data.forEach(item => {
item.isChecked = false
if (item.children && item.children.length > 0) {
traverse(item.children)
}
})
}
traverse(treeData)
}

四、普通模式/懒加载 + 添加/编辑/删除

这个场景工作中也遇到了,具体的需求就是,当鼠标移动到节点上时,右侧就会出现三个图标,分别是添加、编辑和删除,然后这三个功能就是对Tree的操作了,只要上面几种场景都明白了,这个场景也不会难的,本来想着把这个实现一下,但是后来想想,意义不大,也就罢了。



写在最后

写这个东西其实有点痛苦,一来觉得很繁琐,二来虽然当时给自己带来了很大麻烦,但很担心可能是自己技术太菜才会觉得这个东西难,不过成长基于积累,从小事做起就好,也就看开了,希望自己能够坚持下来,不要轻视任何一个知识点。



示例代码:https://github.com/BRAVE2HEART/infoq-vue-example

发布于: 2020 年 06 月 07 日阅读数: 249
用户头像

brave heart

关注

唯一不变的是变化本身。 2018.04.17 加入

🗡 她只唱只想这首止战之殇。

评论

发布
暂无评论
Element-UI实战系列:Tree组件的几种使用场景