el-tree是Element UI中的一种树形控件,它可以在页面中显示树形数据结构,同时支持懒加载。
懒加载是指在Vue组件渲染的过程中,只加载当前可见的部分数据,而不是一次性加载整个数据。这种方法可以显著提高页面的加载速度和响应性能,特别是在大型数据集上。
要使用el-tree的懒加载功能,需要在树形控件组件中提供一个load方法。load方法会在展开一个父节点时触发,它的参数包含了父节点的数据和一个回调函数。回调函数应该按照Vue的异步调用规则来编写,即使用异步加载数据的方式来获取子节点数据并将结果传递给回调函数。
下面是一个简单的el-tree懒加载示例:
<template>
<el-tree :data="treeData" :load="loadTreeNode"></el-tree>
</template>
<script>
export default {
data() {
return {
treeData: [
{
label: 'Parent',
children: [],
lazy: true
}
]
}
},
methods: {
loadTreeNode(node, resolve) {
setTimeout(() => {
const childNodes = [
{ label: 'Child', children: [] }
]
resolve(childNodes)
}, 1000)
}
}
}
</script>
在上面的示例中,loadTreeNode方法接收一个父节点对象和回调函数resolve。它在1秒后返回一个包含一个子节点对象的数组,这个数组会传递给resolve函数并更新树形控件。
注意:在Vue 3中,load方法已经被弃用,应该使用lazy-load属性来代替。具体用法可以参考Element UI官方文档。
直接上代码。
<div :id="`tree${index}`" class="tree-con">
<vue-easy-tree
ref="tree"
:expand-on-click-node="false"
default-expand-all
node-key="id"
highlight-current
:props="defaultProps"
@node-click="handelDetails"
:load="loadNode"
lazy
>
<div slot-scope="{ node }" class="custom-tree-node">
<!-- 查看更多 -->
<span
v-if="data.id.substring(0,8) ==='loadmore'"
class="tree-node load-more"
@click="loadMoreNode(node,data)"
>
<el-link>{{ node.label }}</el-link>
</span>
<!-- 数据到底了 -->
<span
v-if="data.id ==='disabledload'"
class="tree-node no-data"
style="color: #999; cursor: auto;"
>
{{ node.label }}
</span>
<!-- 普通节点 -->
<el-tooltip
class="item more-bar"
effect="light"
placement="right-start"
>
<div slot="content" style="max-width: 300px;">
{{ node.label }}
</div>
<span v-if="data.id.substring(0,8) !=='loadmore' && data.id!=='disabledload'" class="span-tree-node" @click="loadMoreNode(node,data)">{{ node.label }}</span>
</el-tooltip>
</div>
</vue-easy-tree>
</div>
这里的vue-easy-tree是第三方el-tree,性能上进行了优化。具体哪方面提高了性能,笔者没有实测,拿来主义再说。
这里的树,采用了load方法和node-click2个方法,要达到的效果就是点击一下,才展开下一级的节点,这样懒懒的加载,避免一次加载数据量过多而影响体验。
// el-tree 懒加载load 手动触发load更新
handleNodeExpand (data) {
let _node = this.$refs.tree.getNode(data);
if (_node.level >=1 && _node.data.file_flage && _node.data.file_flage == '0') {
this.loadDirChildren(_node, this.resolve)
}
else if(_node.level >=2 && _node.data.file_flage && _node.data.file_flage == '1'){
this.loadFileChildren(_node, this.resolve, [])
}
// 设置未进行懒加载状态
// _node.loaded = false;
// 重新展开节点就会间接重新触发load达到刷新效果
_node.expand();
},
// 节点单击事件
handleNodeClick (data, node) {
this.activeTable = node.level;
this.handleNodeExpand(data);
},
loadMoreNode(node, data) {
if (node.data.id.substring(0,8) === 'loadmore') {
let nodeParentKey = node.parent.key ? node.parent.key : this.paramNode.parent;
let childNode = {
data: {
id: nodeParentKey,
name: node.parent.data.name
}
}, resolve = '';
let parentNode = node.parent.childNodes.map(({key: id, label: name, disabled})=>{
return { name, id, disabled };
});
// 剔除自定义查看更多option数据
if (parentNode.length > 0) {
parentNode = parentNode.slice(0, -1);
}
// 选取resolve返回
if (parentNode.length <= this.total[nodeParentKey]) {
resolve = this.resolveFunc.filter((item)=>{
return item.id === nodeParentKey;
});
this.curpage[nodeParentKey] += 1
// 调用原生Tree load方法
this.loadNode(node.parent, resolve[0].resolve, parentNode)
}
}
},
async getClassification(resolve){
const tar = `#tree${this.index}`
this.loadOpt.target = tar
const loading = this.$loading(this.loadOpt)
const {code,data} = await queryClassificationTree({ employees_id: this.data.id })
if(code == 200 && data && data.obj!= null){
this.wordData.name = data.obj.name
this.wordData.dept = data.obj.dept_name
this.wordData.number = data.obj.dossier_no
const d = Object.assign([], data.obj.classTree)
this.treeData = this.makeCatalogData(this.makeForChildren(d))
this.getPageNum()
this.getImgData(this.treeData)
loading.close()
this.$nextTick(() => {
this.makeTooltipDisabled(this.treeData)
})
}else{
loading.close()
}
if(resolve){
return resolve(this.treeData)
}else{
return null
}
},
loadNode(node, resolve, parentNode = []) {
const id = node.data?node.data.id || 0 : 0
// 记录当前节点的当前页码
!this.curpage[id] && (this.curpage[id] = 1)
this.curNode = node
// 节点各自的resolve
this.resolveFunc.push({ id: id, resolve: resolve })
this.resolveFunc = this.resolveFunc.filter((item, index, self) => {
return self.findIndex(x => x.id === item.id) === index
})
let that = this;
if (node.level === 0) {
this.resolveFunc0 = resolve
this.getClassification(resolve)
}
// else if (node.data.file_flage && node.data.file_flage == '0') {
// this.loadDirChildren(node, resolve)
// }
// else if(node.level >=2 && node.data.file_flage && node.data.file_flage == '1'){
// this.loadFileChildren(node, resolve, parentNode)
// }
else if(node.level >=2 && node.data.file_flage && node.data.file_flage == '1' && parentNode && parentNode.length > 0){
this.loadFileChildren(node, resolve, parentNode)
}
else{
return resolve([]) // 防止不停转圈
}
},
// 树的懒加载获取子节点
async loadDirChildren(node, resolve) {
let param = {
employees_id: node.data.employess_id,
parent_id: node.data.id,
}
const {code , data} = await queryDirectoryTree(param);
let resArr = [];
let tempTree = [];
if (code === 200) {
if(data && data.obj){
tempTree = this.makeCatalogData(this.makeForChildren(data.obj))
this.$refs.tree.updateKeyChildren(node.data.id, tempTree);
return resolve(tempTree);
}
}
if((node.data.children !== undefined && node.data.children != null && node.data.children.length > 0)
|| (node.data.dir_list !== undefined && node.data.dir_list != null && node.data.dir_list.length > 0)){
let tempChildren = node.data.children || node.data.dir_list
tempChildren.forEach(child =>{
let param = {
employees_id: child.employess_id,
parent_id: child.id,
}
queryDirectoryTree(param).then(res=>{
let resArr = [];
if (res.code === 200) {
if(res.data && res.data.obj){
let tempTree = this.makeCatalogData(this.makeForChildren(res.data.obj))
tempTree.forEach(item => {
item = JSON.parse(JSON.stringify(item));
if(item.dir_list){
item.children = item.dir_list
}
resArr.push(item);
});
this.$refs.tree.updateKeyChildren(child.id, resArr);
}
}
})
})
this.$refs.tree.updateKeyChildren(node.data.id, tempChildren);
return resolve(tempChildren)
}
this.$refs.tree.updateKeyChildren(node.data.id, tempTree);
return resolve(tempTree);
},
async loadFileChildren(node, resolve, parentNode) {
if((node.data.children !== undefined && node.data.children != null && node.data.children.length > 0)
|| (node.data.dir_list !== undefined && node.data.dir_list != null && node.data.dir_list.length > 0)){
if (parentNode && parentNode.length > 0) {
if(node.data.children && node.data.children.length>0){
node.data.children.forEach(item =>{
this.wordData.imgList = this.wordData.imgList.filter(img =>{
return img.id != item.id
})
if(item.original_file_path && item.page && item.page == 1){
let order = this.getOrderFromPath(item.original_file_path)
if(this.chapterList && this.chapterList.includes(order)){
let index = this.chapterList.findIndex(val=>{
return val == order
})
if(index < this.chapterList.length - 1){
for(let i=index + 1 ;i<=this.chapterList.length - 1; i++){
this.chapterOrder[i]['page'] -= this.pageSize
}
}
this.chapterList.splice(index, 1)
this.chapterOrder.splice(index, 1)
}
}
})
}
node.data.children = []
let param = {
directory_id: node.data.id,
}
const {code , data} = await queryFileTree(param);
if (code === 200) {
if(data && data.obj){
let tempTree = this.makeCatalogData(this.makeForChildren(data.obj))
this.getImgData(tempTree)
this.$refs.tree.updateKeyChildren(node.data.id,tempTree);
return resolve(tempTree)
}
}
}else{
let tempChildren = node.data.children || node.data.dir_list
this.$refs.tree.updateKeyChildren(node.data.id, tempChildren);
return resolve(tempChildren)
}
}else{
let param = {
directory_id: node.data.id,
}
const curId = param.directory_id || 0
const {code , data} = await queryFileTree(param);
let resArr = [];
if (code === 200) {
if(data && data.obj){
this.total[curId] = data.obj.length
let tempTree = this.makeCatalogData(this.makeForChildren(data.obj))
if (tempTree.length > this.pageSize && parentNode.length == 0) {
let nearName = tempTree[this.pageSize - 1].original_file_name;
resArr = tempTree.slice(0,this.pageSize);
this.getImgData(resArr)
resArr.push({original_file_name: '查看更多(剩余'+(tempTree.length-this.pageSize)+'页)', id: 'loadmore' + tempTree[this.pageSize-1].id, nearName: nearName, leaf: true, disabled: true});
}
if (parentNode && parentNode.length > 0) {
this.getImgData(tempTree)
this.$refs.tree.updateKeyChildren(node.data.id,tempTree);
return resolve(tempTree)
} else {
if(resArr && resArr.length > 0){
this.$refs.tree.updateKeyChildren(node.data.id,resArr);
return resolve(resArr)
}else{
this.getImgData(tempTree)
this.$refs.tree.updateKeyChildren(node.data.id,tempTree);
return resolve(tempTree)
}
}
}else{
return resolve([])
}
}
}
}
这里有三级节点,分别是一级分类节点,目录节点,文件节点。写的有点乱,可以通过点击然后实现一级级的加载。
loadMoreNode方法是树叶节点如果多于10条记录,就只显示10条,隐藏多余的。需要通过点击【更多】展示完整的叶节点。
在loadData方法里面还传入了parentNode参数就是为了能让叶节点记录其父节点然后实现显示更多的叶节点。
笔者写这段代码的时候,是改造旧的项目。旧项目代码是一步获取完的,树也是一次性加载的。经常会卡得加载不出来树,用户体验不好。后台的数据格式也是完整的树的加载。
为了改为懒加载的方式,后台数据加载也分成了3步,先加载一级分类,再加载目录,最后加载文件。前端代码初步的进行了改造,达到了懒加载的目的。相对而言,加载效率上得到了提升,体验更好一些。