vue之el-tree懒加载数据并且实现树的过滤问题
作者:__十七
vue el-tree懒加载数据并且实现树的过滤
树的样式:
过滤效果:
过滤代码实现:
1,如果这里的树数据是全加载,即可使用element-ui中的设置,进行前端过滤。
<el-input placeholder="输入关键字进行过滤" v-model="filterText"> </el-input> <el-tree class="filter-tree" :data="data" :props="defaultProps" default-expand-all :filter-node-method="filterNode" ref="tree"> </el-tree> // 监听输入框中的数据 watch: { filterText(val) { this.$refs.tree.filter(val); } }, methods: { filterNode(value, data) { if (!value) return true; return data.label.indexOf(value) !== -1; } },
2,如果这里的树数据是懒加载,,需要使用后端的模糊加载,返回搜索到的树的节点。
重要的是:filter-node-method=“filterNode”,这个属性
<el-input placeholder="请输入搜索内容" v-model="filterText" class="inputStyle" clearable> </el-input> <el-tree :data="treeData" node-key="id" :filter-node-method="filterNode" ref="dimTree" :props="treeDataDefaultProp" class="tree_Style" :expand-on-click-node="false" :load="loadNode" lazy ></el-tree> data(){ return { filterText: '', keyword:'', } } watch: { // 树节点的过滤 filterText(val) { this.keyword = val this.getTreeData() } methods:{ // 将keyword传入到接口中,从后端返回模糊匹配后的节点,然后赋值给树绑定的数据变量,即可完成。 // 得到树的列表 async getTreeData() { const param = { type: Number(this.cateTabActive), keyword: this.keyword }; const res = await this.$api.get('/api/category', param); if (res.code == 200) { this.treeData = res.info; } else { return false; } }, }
Element el-tree懒加载问题
本文章项目项目全程使用Vue2和Element2!
懒加载:点击节点时才进行该层数据的获取。
注意:使用了懒加载之后,一般情况下就可以不用绑定:data。
基础使用
懒加载需要再指定一个lazy和懒加载数据的方法:load:
<template> <el-tree :props="props" :load="loadNode" lazy></el-tree> </template> <script> export default { data() { return { props: { // 映射配置 label: 'name', // 将获取数组中的name作为显示节点(label)进行展示 children: 'zones', // 将获取数组中的zones作为子节点(children)的展示 isLeaf: 'leaf' // 将获取数组中的leaf作为判断是否是叶子节点(即没有子节点的最底层节点) }, }; }, methods: { loadNode(node, resolve) { // 懒加载数据时载入的方法,只会执行一次 if (node.level === 0) { // 初始的级数(最顶层) return resolve([{ name: 'region' }]); // 最顶层数据渲染为region } if (node.level > 1) return resolve([]); setTimeout(() => { const data = [{ name: 'leaf', leaf: true }, { name: 'zone' }]; resolve(data); }, 500); } } }; </script>
懒加载的方法会得到两个参数,一个是获取的当前节点(node)的信息(包括它的层级数据等);另一个是一个重新渲染当前节点下子节点的方法(resolve),它接收一个数组,该数组也会按照props中的映射关系,进行显示。
注意:懒加载的方法(:load)在初始载入时执行一次,而后每次点击节点前面的箭头获取子节点的时候再次触发;即使初始载入的数据有变换也不会再触发,点击展开子节点后再次点击收缩节点时也不会再触发!!
由于懒加载是一级一级往下获取,所以对每一级来说都要使用resolve来渲染它显示的子节点,如果该节点下没有显示的内容,它则会一直转圈,这个时候需要设置resolve返回一个空数组,这样如果它没有获取到子节点的内容则会在转圈之后显示为空(并去掉前面的向下展开的箭头),不会一直转圈:
return resolve([]);
现实场景:可以在通过node取到每一层的id,根据这个id调用接口得到数据。再通过resolve进行回显,回显的数据为这个id的子节点
二次封装
场景:由于用到树形控件的地方很多,而且需要显示的数据都不一样,所以将树形控件再封装一层,然后根据外部组件传来的不同参数,进行树形图的不同显示。
思路:通过监听外部传入数据的变化,重新渲染树,完成不同数据的显示;但是:load只会初始加载一次并获取当前绑定树上node,如果后面监听数据的时候再次调用loadTree是获取不到它的node和resolve,所以会导致渲染失败。这个时候可以通过:data显示数据,当我们树上有节点时,就可以正常触发:load进行子节点的懒加载了。具体实现如下:
<template> <div class="org-tree"> <el-tree :data="orgList" :props="defaultProps" lazy :load="loadTree" :expand-on-click-node="false" @node-click="nodeClick"> </el-tree> </div> </template> <script> export default { name: 'Tree', props: { logicParams: { // 外部组件传入的参数 type: Object } }, watch: { logicParams: { handler(newVal) { this.logicParams = newVal; if (this.circleI >0) { // 限制首次加载时只显示loadTree加载的树,而不是:data和loadTree加载的都有 this.resetNode(); } this.circleI++; }, immediate: true, deep: true } }, data() { return { defaultProps: { children: 'children', label: 'name', }, circleI: 0, orgList: [] } }, methods: { // 懒加载加载方法,首次加载树的时候会被触发 loadTree(node, resolve) { listByTree(this.logicParams).then(res => { // this.rootNode = node; // this.rootResolve = resolve; let rootMainResolve = resolve; let treedata = []; if(node.level == 0) { return resolve([{ name: res.data[0].name }]) } if (node.level == 1 ) { treedata.push(res.data) return resolve(...treedata) }; if (node.data.isParent && node.data.pId != '') { this.getChild(node.data, node.data.pId, rootMainResolve) } else { return resolve([]) // 防止不停转圈 } }) }, getChild(data, type, resolve) { // 每个节点使用同一个接口获取子节点,只是传入的参数不同,将其抽出来 this.logicChildDataParam.id =data.id; this.logicChildDataParam.type = type+ ";;;"; listByTree(this.logicChildDataParam).then(res => { return resolve(res.data); }) }, // 重新渲染树的根节点 resetNode() { listByTree(this.logicParams).then(res => { this.orgList = [res.data[0]] }) }, } } </script>
数据回显
场景:在懒加载的树上设置复选框,需要将之前添加好的懒加载选中的部分在表格的编辑中回显出来。
思路:由于懒加载的数据是一级一级获取的,所以可以利用default-expanded-keys和default-checked-keys属性,将需要进行回显的节点在渲染树的时候就设置上去。(注意在使用这两个属性的时候)
<template> <div class="org-tree"> <el-tree ref="tree" :props="defaultProps" lazy :load="loadTree" :expand-on-click-node="false" show-checkbox @check-change="checkChange" :default-expanded-keys="defaultExpandKeys" :default-checked-keys="defaultCheckedKeys" node-key="id"> </el-tree> </div> </template> <script> export default { name: 'OrgTree', data() { return { defaultExpandKeys: [], defaultCheckedKeys: [] } }, methods: { // 当节点选中或取消选中的时候触发,可接受三个参数(具体见官网,本项目只用触发这个事件的时机) checkChange(data,state,childChecked) { let selectedAllList = []; let checkedList = []; // 选中所有的节点,包括半选节点(用作展开的节点) selectedAllList = this.$refs.tree.getCheckedNodes(false,true); // 选中所有全选节点,不包括半选(用作选中的节点) checkedList = this.$refs.tree.getCheckedNodes(); // 触发父组件方法,将这两个数组传递出去,并在父组件的添加点击事件中调用添加方法,添加时需拿到这两个节点数组用作数据的回显 this.$emit('selectorg', selectedAllList, checkedList); }, loadTree(node, tree) { if (node.level == 0) { let che = []; let exp = []; // 此处的checkedList和selectedAllList是通过调用编辑接口获取到的数据,为了方便理解写做与checkChange中一样,以下是伪代码 checkedList.forEach(el => { // 遍历选中的节点数组,拿到它们的id if (el.id) { che.push(el.id); } }) selectedAllList.forEach(el => { // 遍历包括半选的节点数组,拿到它们的id(可以将半选节点都筛出来,将所有半选节点作为展开的节点,如果嫌麻烦可以将全选的接节点也展开,不过这样可能会在树的数据量过多的情况下出现延迟和卡顿,影响性能) if (el.id) { exp.push(el.id); } }) this.defaultCheckedKeys = che; // 将得到的回显节点数组赋值给默认选中的数组 this.defaultExpandKeys = exp; // 将得到的展开节点数组赋值给默认展开的数组 } }, } } </script>
回显问题
场景:在使用懒加载进行数据回显时,当添加选中的数据里存在以下情况:一个父节点下的第一和第二个子节点同时被选中,回显时得到的默认选中的节点数组里也只有这两个节点的id,但是最终懒加载回显的数据是这个父节点下的所有子节点全都被选中(获取其他类似情况)。
分析:由于懒加载的树是异步加载的,树在判断子节点是否选中的时候可能由于选中的子节点,而导致其父节点因为关联而被计算判断出选中。
解决一:如果不需要父节点的复选框,或者父节点没有复选框,只有子节点有,或者不需要父子节点关联的情况下,可以使用check-strictly属性,断开父子之间的连接:
<template> <div class="org-tree"> <el-tree ref="tree" :props="defaultProps" lazy :load="loadTree" :expand-on-click-node="false" show-checkbox @check-change="checkChange" :default-expanded-keys="defaultExpandKeys" :default-checked-keys="defaultCheckedKeys" node-key="id" :check-strictly="checkStrictly"> </el-tree> </div> </template> <script> export default { name: 'OrgTree', data() { return { checkStrictly: true, // 根据需要在可不同的位置定义 } } </script>
解决二:对选中节点的回显不使用default-checked-keys,而是利用$nextTick和setCheckedKeys设置节点的选中,此方法必须设置node-key属性:
<template> <div class="org-tree"> <el-tree ref="tree" :props="defaultProps" lazy :load="loadTree" :expand-on-click-node="false" show-checkbox @check-change="checkChange" :default-expanded-keys="defaultExpandKeys" :default-checked-keys="defaultCheckedKeys" node-key="id"> </el-tree> </div> </template> <script> export default { name: 'OrgTree', data() { return { defaultExpandKeys: [], defaultCheckedKeys: [] } }, methods: { // 当节点选中或取消选中的时候触发,可接受三个参数(具体见官网,本项目只用触发这个事件的时机) checkChange(data,state,childChecked) { let selectedAllList = []; let checkedList = []; // 选中所有的节点,包括半选节点(用作展开的节点) selectedAllList = this.$refs.tree.getCheckedNodes(false,true); // 选中所有全选节点,不包括半选(用作选中的节点) checkedList = this.$refs.tree.getCheckedNodes(); // 触发父组件方法,将这两个数组传递出去,并在父组件的添加点击事件中调用添加方法,添加时需拿到这两个节点数组用作数据的回显 this.$emit('selectorg', selectedAllList, checkedList); }, loadTree(node, tree) { if (node.level == 0) { let che = []; let exp = []; // 此处的checkedList和selectedAllList是通过调用编辑接口获取到的数据,为了方便理解写做与checkChange中一样,以下是伪代码 selectedAllList.forEach(el => { // 遍历包括半选的节点数组,拿到它们的id if (el.id) { exp.push(el.id); } }) this.defaultExpandKeys = exp; // 将得到的展开节点数组赋值给默认展开的数组 this.$nextTick(() => { // 利用$nextTick更新节点 checkedList.forEach(el => { // 遍历选中的节点数组,拿到它们的id if (el.id) { che.push(el.id); } }) this.$refs.tree.setCheckedKeys(che); // 手动赋值节点 }) } }, } } </script>
注意:该方法因为在load方法中,所以每次触发load的时候(每次首次下拉节点)都会重新获取一边数据,这会导致之前可能选中的节点又被回显节点重置了。
这种情况如果在层级没有超过2级时,可以通过设置一个计数器,让这个$nextTick只执行一次,但是如果层级过多,下层还是会出现全选的情况。这个问题我暂时无法避免,所以综合考虑下来还是采用每执行一次load就执行一次$nextTick,这样可以保证更深的层级节点能正确显示,对于回显编辑来说,只要做到先下拉节点再选择,就不会出错了。
复选框显隐
场景:现在需要去掉所有的叶子节点(没有子节点的节点)的复选框,默认选中了父节点则其下所有子节点都 不做判断。
思路:由于element的tree并未提供这个属性或方法,需要我们自己手动去修改element内部代码,然后再重新打包,将打好的包替换自己项目中element里的ilb文件夹:
将对应版本的element源码下载下来,安装依赖并查看项目是否启动成功:
npm install npm run dev
运行成功后找到packages/tree/src/tree-node修改源码:
<template> <div class="el-tree-node"> ... <!-- 找到复选框的位置,根据node.data中的某个字段判断(我是根据isParent判断)设置复选框的显隐 --> <el-checkbox v-if="showCheckbox" v-model="node.checked" :style="{ 'display': node.data.isParent || node.parent == null ?'':'none'}" :indeterminate="node.indeterminate" :disabled="!!node.disabled" @click.native.stop @change="handleCheckChange" > </el-checkbox> ... </div> </template>
源码修改好之后,进行打包:
npm run dist
打包完成之后会得到新的lib文件夹,将其替换自己项目中的对应位置的lib文件夹即可。
注意:直接修改自己项目中的packages里的代码是无效的,因为项目中所运行的是lib文件夹里的,packages只是方便查看内部源码!
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。