前言
当树形图含有大量的数据时,一次性全部加载出来会很慢,用户体验不好,这时需要做数据的分级加载,也就是懒加载,但如果每级的数据量也很大,就需要对每一级都做分页加载了。
思路
- 点击一般父层级节点组件会自动触发懒加载, 而点击
LOAD_MORE_NODES
节点需在节点点击事件handleNodeClick
触发懒加载请求数据; LOAD_MORE_NODES
节点数据需要额外添加三个字段:
isLoadingMoreNode
为区分加载更多节点的标识;
isloading
记录节点是否正在加载,用于避免数据请求时避免重复点击;
pagination
用于记录当前层级下的分页状态
export const LOAD_MORE_NODES = ({ nodeId, currentPage, pageSize }) => {
return {
id: 'load-more-id',
label: '加载更多',
parentId: nodeId,
leaf: true,
isLoadingMoreNode: true, // 区分加载更多节点的标识
isloading: false, // 记录节点是否正在加载
pagination: { // 记录分页数据
currentPage,
pageSize,
},
}
}
- 在懒加载方法中,
请求数据前, 需要先判断LOAD_MORE_NODES
的状态: 根据节点的isloading
字段状态来做避免重复点击处理。
若为true
则退出, 否则就将该字段状态更新为true
。
此外,LOAD_MORE_NODES
对应的数据页码参数需传当前的下一页, 其他的则为初始值第一页。
// 在懒加载请求数据方法里, 对 loadmore 节点进行额外的处理
if (isLoadingMoreNode) {
// 若正在加载更多, 则退出方法, 避免重复请求
if (isloading) return
// 若首次点击加载更多, 则更新状态
node.data.isloading = true
}
// loadmore 节点请求的是上一级节点的数据, 传给后端的参数为 parentId
let nodeId = isLoadingMoreNode ? parentId : id
// 只有加载更多节点有 pagination 当点击别的节点传默认值
let { currentPage = 1, pageSize = 5 } = pagination || {}
const params = {
displayTable: true,
parentId: nodeId,
pageNo: isLoadingMoreNode ? ++currentPage : 1,
pageSize,
}
在请求的回调方法里, 需要根据返回的数据条数来判断是否还需显示加载更多节点, 在移除原本的加载更多节点后, 再调用 el-tree
的 append
方法依次遍历增加新的节点数据。
// 处理节点列表
const nodesList = res.list?.map(CONST.MAP_NODES_LIST(isMarket))
// 若返回条数等于每页条数, 表明当前目录可能存在更多节点,则添加加载更多节点
if (res.list?.length == pageSize)
nodesList.push(
CONST.LOAD_MORE_NODES({ nodeId, currentPage, pageSize })
)
// 若点击的节点是加载更多, 需先移除更多节点, 再添加新节点
if (isLoadingMoreNode) {
const treeRef = this.$refs.tree
const parentNode = treeRef.getNode(parentId)
// 移除加载更多节点
parentNode.removeChild(node)
if (_.isEmpty(nodesList)) return
// 添加新节点
for (let el of nodesList) {
treeRef.append(el, parentNode)
}
- 做请求报错恢复点击处理
若请求错误, 则在 catch 方法里将状态更新为 false, 以恢复节点的点击
api
.queryDirectoryLazyLoad(params)
.then((res) => {
...
.catch((error) => {
console.log(error)
// 若请求报错, 则恢复更多加载的点击
node.data.isloading = false
})
全部代码
<el-tree
ref="tree"
lazy
:data="directoryTreeData"
:load="queryTreeData"
:props="CONST.TREE_DEFAULT_PROPS"
node-key="id"
:expand-on-click-node="false"
@node-click="handleNodeClick"
... // 省略其他代码
>
<div class="custom-tree-node" slot-scope="{ node, data }">
.... // 省略其他代码
<div
class="custom-tree-node-label-box"
:class="{ isLoadingMoreNode: data.isLoadingMoreNode }"
>
... // 省略其他代码
</div>
</div>
</el-tree>
handleNodeClick(data, node) {
if (data.isLoadingMoreNode) {
this.queryTreeData(node)
return
}
... // 省略其他代码
}
// 获取目录数据
queryTreeData(node, resolve) {
// 这里根节点无需请求数据
if (node.level == 0) {
resolve([CONST.ROOT_NODE])
return
}
const {
parentId,
id,
pagination,
isLoadingMoreNode,
isloading,
isMarket,
} = node.data || {}
// 在懒加载请求数据方法里, 对 loadmore 节点进行额外的处理
if (isLoadingMoreNode) {
// 若正在加载更多, 则退出方法, 避免重复请求
if (isloading) return
// 若首次点击加载更多, 则更新状态
node.data.isloading = true
}
// loadmore 节点请求的是上一级节点的数据, 传给后端的参数为 parentId
let nodeId = isLoadingMoreNode ? parentId : id
// 只有加载更多节点有 pagination 当点击别的节点传默认值
let { currentPage = 1, pageSize = 5 } = pagination || {}
const params = {
displayTable: true,
parentId: nodeId,
pageNo: isLoadingMoreNode ? ++currentPage : 1,
pageSize,
}
api
.queryDirectoryLazyLoad(params)
.then((res) => {
// 处理节点列表
const nodesList = res.list?.map(CONST.MAP_NODES_LIST(isMarket))
// 若返回条数等于每页条数, 表明当前目录可能存在更多节点,则添加加载更多节点
if (res.list?.length == pageSize)
nodesList.push(
CONST.LOAD_MORE_NODES({ nodeId, currentPage, pageSize })
)
// 若点击的节点是加载更多, 需先移除更多节点, 再添加新节点
if (isLoadingMoreNode) {
const treeRef = this.$refs.tree
const parentNode = treeRef.getNode(parentId)
// 移除加载更多节点
parentNode.removeChild(node)
if (_.isEmpty(nodesList)) return
// 添加新节点
for (let el of nodesList) {
treeRef.append(el, parentNode)
}
} else {
// 普通节点直接返回
resolve(nodesList)
}
})
.catch((error) => {
console.log(error)
// 若请求报错, 则恢复更多加载的点击
node.data.isloading = false
})
},
// const.js
// 目录树 - 默认props
export const TREE_DEFAULT_PROPS = {
children: 'children',
label: 'label',
isLeaf: 'leaf',
}
// 目录树 - 根节点
export const ROOT_NODE = {
id: '-1',
label: '全部',
leaf: false,
isFolder: true,
}
// 目录树 - 默认节点
export const MAP_NODES_LIST = (isMarket) => {
return ({ id, name, hasChildren, nodeType, code }) => ({
id,
label: nodeType == 1 ? name : name || code,
leaf: !hasChildren,
isFolder: nodeType == 1,
isMarket: isMarket || name == '数据集市',
})
}
// 目录树 - 加载更多节点
export const LOAD_MORE_NODES = ({ nodeId, currentPage, pageSize }) => {
return {
id: 'load-more-id',
label: '加载更多',
parentId: nodeId,
isLoadingMoreNode: true,
leaf: true,
pagination: {
currentPage,
pageSize,
},
}
}
// 加载更多样式
.custom-tree-node-label-box.isLoadingMoreNode {
color: #2590ff;
}
::v-deep .el-tree-node__content:has(.isLoadingMoreNode):hover,
::v-deep .el-upload-list__item:has(.isLoadingMoreNode):hover,
::v-deep .el-tree-node__content:has(.isLoadingMoreNode) {
background-color: transparent !important;
}