Vue el-tree树状图进行懒加载功能实现
动态加载
当选中某一级时,动态加载该级下的选项:
通过lazy开启动态加载,并通过lazyload来设置加载数据源的方法。 lazyload方法有两个参数,第一个参数node为当前点击的节点,第二个resolve为数据加载完成的回调(必须调用)。 为了更准确的显示节点的状态,还可以对节点数据添加是否为叶子节点的标志位 (默认字段为leaf,可通过props.leaf修改)。 否则,将以有无子节点来判断其是否为叶子节点。
实际操作示例:
<template id="TYPESJ">
<div class="com-tree" style="margin: 0; padding: 0; height: 100%">
<div class="fytypemgs">
<!--隐藏组织架构后显示的图标-->
<div v-show="Display" @click="Donotopen()" style="width:25px;height:50px;background-color:rgb(0 191 255/0.4);position:absolute;z-index:999">
<a class="el-icon-arrow-right" style=" line-height: 50px;"></a>
</div>
<el-container v-show="Hide" style="width:250px; height: 100%;border-left: 1px solid #eee !important;border-right: 1px solid #eee !important">
<el-header id="color" style="border-bottom: 1px solid #80c2fc; background-color: #a0d1fc;width:100% !important;display:flex;justify-content:space-between; flex-flow:wrap;height:25px;padding:2px;">
<div>
<span style="line-height:24px;color:#399cee;font-weight:bold;font-size:12px !important;">报价产品类型</span>
</div>
<div>
<a class="el-icon-arrow-left" style="border: 2px solid #399cee;font-size:12px;color:black;margin-top:2px" @click="Unfold()"></a>
<a class="el-icon-refresh" style="border: 2px solid #399cee;font-size:12px;color:black;margin-top:2px" @click="shouzhan(treeData)"></a>
<a class="el-icon-arrow-down" style="border: 2px solid #399cee;font-size:12px;color:black;margin-top:2px" @click="unFoldAll2(treeData)"></a>
<a class="el-icon-arrow-up" style="border: 2px solid #399cee;font-size:12px;color:black;margin-top:2px" @click="collapseAll2(treeData)"></a>
</div>
</el-header>
<div style=" height: 95%;">
<el-scrollbar style="height: 95%;width:100%">
<el-tree :data="treeData"
@node-click="nodeClick"
node-key="value"
:props="{
value: 'value',
label: 'label',
children: 'children',
isLeaf: 'leaf',
}"
highlight-current
ref="staffTree"
lazy :load="handleLoad"
:default-expand-all="false"
:default-expanded-keys="idArr"
:expand-on-click-node="false">
<template #default="scope">
<div class="action-group">
<div class="action-text"
:class="{actived:data.FITEMID==selectId,'node-text':data.parentId!==0}"
style="font-size:12px">
<Icon v-if="data.parentId!==0"
:type="data.FITEMID==selectId?'ios-radio-button-on':'ios-radio-button-off'" />
{{ scope.data.label }}
</div>
</div>
</template>
</el-tree>
</el-scrollbar>
</div>
</el-container>
</div>
<div class="fytypetable" style="width:100%">
<V_QUOTE_ERPITEMTYPE ref="QuoteErp" style="width:100%"></V_QUOTE_ERPITEMTYPE>
</div>
</div>
</template>
<script>
import V_QUOTE_ERPITEMTYPE from "@/views/quote/quote/V_QUOTE_ERPITEMTYPE.vue";
var vueParam = {
components: {
V_QUOTE_ERPITEMTYPE
},
data() {
return {
Hide: true,
Display: false,
istree: true,
//树状图
tableData: [],
data: [],
treeData: [],
openKeys: [],
idArr: [],
count: 1,
res: [],
List_ID: [],//当前选中组织架构的id及其子级的id
//点亮图标
selectId: -1,
}
},
created() {
//初始化一个对象全局缓存起来,当点击左边树形菜单时,把点击菜单的treeId(角色id)存到treeDemo1里
this.$nextTick(function () {
this.load(1);
this.$refs.QuoteErp.$refs.grid.$refs.table.issorting = false;
this.$refs.QuoteErp.$refs.grid.List_ID = this.List_ID.join(",");
console.log(this.List_ID.join(","))
this.$refs.QuoteErp.$refs.grid.refresh();
let that = this;
this.$refs.QuoteErp.$refs.grid.TreeLoad = function (val) {
that.load(1);
}
})
},
methods: {
//隐藏显示组织架构
Unfold() {
this.Hide = false;
this.Display = true;
document.getElementsByClassName("fytypemgs")[0].style.width = "0px";
document.getElementsByClassName("fytypetable")[0].style.width = "100%";
},
//显示
Donotopen() {
this.Display = false;
this.Hide = true;
document.getElementsByClassName("fytypemgs")[0].style.width = "240px";
document.getElementsByClassName("fytypetable")[0].style.width = "85%";
},
//全部展开
unFoldAll2(data) {
let self = this;
data.forEach((el) => {
self.$refs.staffTree.store.nodesMap[el.value].expanded = true;
el.children && el.children.length > 0
? self.unFoldAll2(el.children)
: ""; // 子级递归
});
},
// 全部折叠
collapseAll2(data) {
let self = this;
data.forEach((el) => {
self.$refs.staffTree.store.nodesMap[el.value].expanded = false;
el.children && el.children.length > 0
? self.collapseAll2(el.children)
: ""; // 子级递归
});
},
//收展
shouzhan(data) {
let self = this;
data.forEach((el) => {
self.$refs.staffTree.store.nodesMap[el.value].expanded = true;
el.children && el.children.length > 0
? self.collapseAll2(el.children)
: ""; // 子级递归
});
},
//通知分类树形控件绑定
load(val) {
let that = this;
this.http.post("后台接口", null, "加载中")
.then(result => {
that.data = JSON.parse(result.data);
console.log(result)
console.log(that.data)
let data = {
value: 0,
label: "全部",
FMATERIALGROUPID: -1,
FLEVEL: -1,
children: that.data.map(x => {
return {
FID: x.FID,
value: x.FITEMID,
label: x.FNAME,
FLEVEL: x.FLEVEL,
FGROUP1: x.FGROUP1,
FGROUP2: x.FGROUP2,
leaf: false,
FMATERIALGROUPID: x.FMATERIALGROUPID,
}
})
}
this.idArr = [];
this.idArr.push(0);
this.treeData = [data];
});
},
//懒加载
handleLoad(node, resolve) {
let that = this;
const { level } = node;
console.log(level)
if (level > 0) {
const url = `后台接口?parentId=${node.data.value}`;
this.http.post(url, null, "加载中").then(reslut => {
that.data = JSON.parse(reslut.data);
console.log(that.data)
if (reslut.status) {
let data = that.data.map(x => {
return {
leaf: level,
FID: x.FID,
value: x.FITEMID,
label: x.FNAME,
FLEVEL: x.FLEVEL,
FGROUP1: x.FGROUP1,
FGROUP2: x.FGROUP2,
FMATERIALGROUPID: x.FMATERIALGROUPID,
};
})
resolve(data)
}
})
}
},
// 树节点点击事件
nodeClick(node) {
this.selectId = node.value; // 使用 props.value 作为唯一标识(与 node-key 一致)
this.List_ID = []; // 清空旧数据
// 收集当前节点及其所有已加载子节点的 ID
this.collectChildrenIds(node);
// 传递给表格组件(逗号分隔的 ID 字符串)
this.$refs.QuoteErp.$refs.grid.List_ID = this.List_ID.join(",");
console.log(this.List_ID.join(","))
this.$refs.QuoteErp.$refs.grid.refresh();
},
// 递归收集子节点 ID(核心逻辑)
collectChildrenIds(currentNode) {
// 1. 添加当前节点 ID(使用 value 字段,与 node-key 一致)
if (!this.List_ID.includes(currentNode.value)) {
this.List_ID.push(currentNode.value);
}
// 2. 递归遍历已加载的子节点(children 可能为空,取决于懒加载是否完成)
if (currentNode.children && currentNode.children.length > 0) {
currentNode.children.forEach(childNode => {
this.collectChildrenIds(childNode); // 递归子节点
});
}
},
右侧树状图点击方法
//nodeClick(node, selected) {
// this.List_ID = [];
// console.log(node)
// this.selectId = node.FITEMID;
// this.List_ID = [node.FITEMID]; // 初始化为当前节点 ID
// // 递归获取所有子节点 ID(仅需遍历已加载的节点)
// this.collectChildrenIds(node);
// this.$refs.QuoteErp.$refs.grid.List_ID = this.List_ID.join(",");
// console.log(this.List_ID.join(","))
// this.$refs.QuoteErp.$refs.grid.refresh();
// this.List_ID.push(node.BusinessSort);
// //let LISTJIHE = []
// //this.selectId = node.FITEMID;
// //this.$refs.QuoteErp.$refs.grid.FITEMID = parseInt(node.FITEMID);
// 缓存当前选中的节点
// //LISTJIHE.push(node)
// //this.CyclicNodeList(LISTJIHE)
// //this.$refs.QuoteErp.$refs.grid.List_ID = this.List_ID.join(',');
// //this.$refs.QuoteErp.$refs.grid.FID = node.FITEMID;
// //this.$refs.QuoteErp.$refs.grid.FID = this.selectId;
// //this.$refs.QuoteErp.$refs.grid.refresh();
//},
CyclicNodeList(data) {
data.forEach((el) => {
this.List_ID.push(el.FITEMID);
el.children && el.children.length > 0 ? this.CyclicNodeList(el.children) : ''; // 子级递归
});
},
onPositionChange(treeId) {
//调用table(viewGird.vue)刷新方法
this.$store.getters.data().vGStaff.nodeClick(treeId);
},
树形控件加载事件
//load(val) {
// let that = this;
// this.List_ID = [];
// this.treeData = [];
// this.http.ajax2({
// url: "后台接口", postdata: true, success: function (result) {
// if (result.status) {
// that.data = JSON.parse(result.data);
// console.log(that.data)
// that.data.push({ FITEMID: 0, FNAME: "全部", FMATERIALGROUPID: -1 })
// that.data.forEach((x, index) => {
// console.log(x)
// if (x.FMATERIALGROUPID == -1) {
// //只有页面首次加载才对选中节点改变
// if (val == 1) {
// that.selectId = x.FITEMID;
// }
// x.children = [];
// that.treeData.push(x);
// that.idArr.push(x.FITEMID); // 收集一级节点ID
// that.getTree(x.FITEMID, x, 1);
// }
// });
// that.$nextTick(function () {
// that.$refs.QuoteErp.$refs.grid.List_ID = that.List_ID.join(",");
// that.$refs.staffTree.setCurrentKey(that.selectId)
// that.$refs.QuoteErp.$refs.grid.refresh();
// that.Donotopen()
// })
// }
// },
// type: "post",
// async: false,
// });
//},
接收下级的值
//getTree(val, data, index) {
// if (this.List_ID.indexOf(val) == -1) {
// this.List_ID.push(val)
// }
// this.data.forEach(q => {
// if (q.FMATERIALGROUPID == val) {
// //q.children = [];
// if (!data.children) data.children = [];
// data.children.push(q);
// this.getTree(q.FITEMID, q);
// }
// })
//},
},
}
export default vueParam;
</script>
<style lang="less" scoped>
.el-scrollbar__view {
height: 100% !important
}
.com-tree {
display: flex;
> .fytypemgs
{
width: 240px;
z-index: 999;
font-size: 14px;
}
/* > .City_Table {
flex: 1;
width: 50%
}*/
}
</style>
提示:
- 限制加载层级:在 handleLoad 中,判断当前节点的层级,只加载到指定层级,比如三级,避免加载更深层级的数据,减少请求次数和数据量。
- 缓存已加载的节点:避免重复加载同一节点的子节点,可以使用一个对象来记录哪些节点的子节点已经加载过,下次再展开时直接使用缓存的数据,而不是重新请求。
- 优化数据请求:确保每次请求只获取必要的数据,比如只传递 parentId,并且后端接口能够高效返回子节点数据,可能需要添加索引或优化查询语句。
- 移除 load 方法中的 getTree 调用:因为使用了懒加载,初始化时只需要加载根节点,子节点由 handleLoad 动态加载,所以 load 中的 getTree 递归获取子节点的逻辑可以移除,避免初始化时加载过多数据
- 原代码中的 getTree 方法在 load 中被调用,可能用于初始化子节点,但在懒加载模式下,这可能不必要,可以移除,改为完全由 handleLoad 来动态加载子节点,避免初始化时加载过多数据