el-tree 懒加载数据,展开的节点与查询条件联动,且对查询的节点进行定位

文章介绍了如何使用ElementUI的el-tree组件实现地市数据的懒加载树形结构,通过设置node-key确保唯一性,实现数据的自动赋值和联动查询,以及定位查询节点的滚动效果。

效果描述

项目某模块左侧为el-tree树形结构地市数据,右侧有地市查询条件及结果展示
要求:

  1. 点击左侧某地市进行查询时,右侧的地市查询输入框内同步进行赋值(如左侧点击成都,右侧地市查询输入框内,应该自动选中成都)
  2. 同理,右侧地市查询输入框 选中成都时,左侧树形结构成都节点自动展开

实现原理

步骤1:el-tree设置node-key

在这里插入图片描述
首先要知道node-key 是设置展开和选中的必要设置,其绑定的值是整个树唯一的(一般绑定 id 等),如果出现重复,会报错。同时设置展开某个节点时,如果这个节点的 id 存在重复,则会出现展开混乱的现象

备注:这里注意,可能在某些特殊时候,后台返回的树形数据,并不存在唯一值(比如我这里,绑定的字段 id ,但在数据里,存在部分数据 id 会一致的情况),这个时候要么是让后端将 id 处理成唯一值,要么就是我们自己对拿到的数据进行处理了(这里我是自己处理的)

步骤2:懒加载时对数据进行处理,给整个树形数据添加唯一值

因为数据量过大,我这里的el-tree用的是懒加载获取数据的方法(:load=“loadNode”)

<el-tree
  ref="tree"
  node-key="key"
  accordion
  @node-expand="nodeExpandFn"
  :filter-node-method="filterNode"
  :data="treeData"
  :default-expanded-keys="expanded"
  :props="defaultProps"
  :indent="0"
  lazy
  auto-expand-parent
  highlight-current
  :load="loadNode"
  @node-click="handleNodeClick"
>
</el-tree>

可以看到,node-key 我绑定的是 key 字段,这是我给数据中每个对象添加的唯一值:
loadNode方法中,在获取数据时添加唯一值,这里不用关注其他代码,只需关注我根据每个节点的特性,给每个节点对象都增加了一个 key 字段 (我这里第三级的节点数据,每个对象的id是相同的,这也是我选择增加 key 字段的原因)

loadNode(node, resolve) {
 if (node.level === 0) {
   this.resolve = resolve
   this.chooseNode = node
   queryKSHDeviceTree({ type: "0" }).then(res => {
     const treeData = [];
     res.resultValue.forEach(e => {
       e.key = e.id; // key ----------------------
       treeData.push(e);
     });
     resolve(treeData);
   });
 }
 else if (node.level === 1) {
   queryKSHDeviceTree({ type: '1' }).then(res => {
     let treeData = [];
     res.resultValue.forEach(e => {
       e.key = e.id; // key ------------------------
       treeData.push(e);
     });
     resolve(treeData);
   });
 } else if (node.level === 2) {
   queryKSHDeviceTree({ type: '2',pId: node.data.id }).then(res => {
     let treeData = [];
     res.resultValue.forEach(e => {
       // 这里根据当前对象自己的数据,拼出一个唯一值
       e.key = node.data.id + e.pId; // key --------------------
       treeData.push(e);
     });
     resolve(treeData);
   });
 }
}

步骤3:(联动) 点击左侧树形结构,右侧对应查询框自动赋值

这一步相对来说简单一些,在el-tree的点击事件 handleNodeClick 中进行操作

// 如果右侧查询框绑定的字段为 queryForm.cityId
// 点击第二级节点,对其他查询框赋值也同理
handleNodeClick(data, node) {
  if (data.label === "city") {
    this.queryForm.cityId = data.id;
  }
  else if ...
}

步骤4:(联动) 右侧查询条件选择好后,点击查询,左侧树形结构自动展开对应节点

这里 主要用到了 :default-expanded-keys=“expanded” (默认展开节点)
expanded 为 存放 key 值的数组

其实当整个树形数据完整,并且设置了 auto-expand-parent 
(展开子节点的时候自动展开父节点)的情况下,
这个时候expanded只用保存最后一级的key值即可

但是,由于我这里的数据是懒加载的,意味着虽然你右侧选了最后一级的数据,点击查询,
但我左侧的树形数据里,还并未加载子级数据,所以这个时候,
即时你将key存入expanded里,它找不到,所以也是没有用的

***所以这个时候,我们需要把每一级要展开的节点key字段都拿到,并存入expanded中
***这样,el-tree在自动展开某一节点时,它会根据懒加载方法,自动获取子节点的数据
***当子节点数据获取到后,它也会将每个节点的key自动去匹配expanded中的值,若存在相同的,
就会接着展开
!!!(简单的说,就是把每一级要展开的节点key字段都拿到,并存入expanded中,
同时懒加载方法写好,其他的交给el-tree自己就行)

经过测试,在展开节点后,对expanded重新赋值,已展开的节点不会自动关闭,所以这里也写了方法,先将el-tree所有的节点进行关闭

// 查询方法
queryClick() {
  let obj = {
    refName: 'tree2',
    cityId: this.queryForm.cityId,
    twoId: this.queryForm.twoId,
    threeId: this.queryForm.threeId || ''
  }
  this.treeNodeLinkage(obj);
}

// 查询条件和el-tree显示联动
// cityId 为第一级id,twoId 为第二级,threeId 为第三级
treeNodeLinkage({refName, cityId, twoId , threeId}) {
  // 先将el-tree所有节点关闭
  let nodesMap = this.$refs[refName].store.nodesMap; // 拿到全部的node
  for(let key in nodesMap) {
    nodesMap[key].expanded = false;
  }
  // 这里的第二级id,对应懒加载方法里 node.level === 2时候
  // 所以这里的newId拼接方法,按照当时key的拼接方法来
  let newId = cityId + twoId;
  this.expanded = [cityId, newId];
  if(threeId) {
    this.expanded.push(threeId);
  }
},

步骤5:定位到查询的节点

添加定位方法

思路:通过当前选中的节点样式   '.is-current' 进行定位,这里通过this.$refs.tree.setCurrentKey(key)方法,
对查询的节点进行选中。

下面的’.treeDome’ 为滚动的父元素 的 class名

// 首先在上面的treeNodeLinkage方法里进行一些操作
/**  this.$refs.tree.setCurrentKey(null); // 清除上次的选中
	document.querySelector('.treeDome').scrollTop = 0; // 父盒子滚动条回到顶部
	this.$refs.tree.setCurrentKey(threeId); // 设置选中
	setTimeout(() => {
        this.toPositionTree();
    }, 500)
*/ 
treeNodeLinkage({refName, cityId, twoId , threeId}) {
  // 先将el-tree所有节点关闭
  let nodesMap = this.$refs[refName].store.nodesMap; // 拿到全部的node
  for(let key in nodesMap) {
    nodesMap[key].expanded = false;
  }
  // 这里的第二级id,对应懒加载方法里 node.level === 2时候
  // 所以这里的newId拼接方法,按照当时key的拼接方法来
  let newId = cityId + twoId;
  this.expanded = [cityId, newId];
  this.$refs.tree2.setCurrentKey(null); // 清除上次的选中
  document.querySelector('.treeDome').scrollTop = 0; // 父盒子滚动条回到顶部
  if(threeId) {
    this.expanded.push(threeId);
    /*
    	因为数据已加载,则可直接设置选中,如果未加载,可能会出现执行这段代码时,el-tree里数据还未拿到,
    	会导致下面的toPositionTree方法,一直拿不到'.is-current'元素,
    	所以在 :load="loadNode" 拿到数据的地方加上
	 	setTimeout(() => {
	      this.$refs.tree.setCurrentKey(this.filterText2);
	    }, 500)
	   执行完这个后,元素就能成功被选中,toPositionTree方法也能成功执行
	 */ 
    this.$refs.tree.setCurrentKey(threeId); 
  }
},

toPositionTree() {
 // 这里因为数据是懒加载,可能一次找不到当前的节点,所以进行重复查询,查到为止

 if(document.querySelector('.is-current')) {
   let nodeOffsetTop = document.querySelector('.is-current').offsetTop;
   // console.log('.is-current', nodeOffsetTop);
   let tempH = document.querySelector('.treeDome').clientHeight / 2;
   if (nodeOffsetTop > tempH) {
     document.querySelector('.treeDome').scrollTop = nodeOffsetTop - tempH + 150;
     // console.log('.treeDome', document.querySelector('.treeDome').scrollTop);
   }
 } else {
   setTimeout(() => {
     this.toPositionTree();
   },500)
 }
},

总结

其实这个联动,主要是搞懂el-tree的 node-keydefault-expanded-keys 属性就行。
简单来说,就是你想展开哪个节点,就把该节点node-key对应的字段放入default-expanded-keys对应的数组中。

其他的就是对数据的处理
把接口获取到的数据处理成你想要的样子,这是前端在很多时候都要做的事情

<think> 我们参考了提供的引用内容,并从中提取关键信息来解决用户的问题:在Vue2中使用el-tree组件进行懒加载并回显数据时,实现全选和半选功能。 根据引用[1]:关键点在于利用懒加载的`resolve`方法完成后,通过操作节点的`checked`和`indeterminate`属性来实现回显。引用[1]提到,`store`属性中的`nodesMap`存储了所有已展开节点的信息,我们可以通过改变节点在`nodesMap`中的状态来实现回显。 引用[2]提到了懒加载解决大数据量卡顿的问题,同时提到了如何通过`isLeaf`属性控制箭头显示。 引用[3]则展示了一种通过`setChecked`方法设置选中项的方法,但需要注意的是,在懒加载模式下,由于节点是动态加载的,直接设置可能无效,需要等待节点加载完成。 结合以上引用和用户的问题(懒加载模式下的回显),我们提出以下方法: 步骤1:在懒加载模式下,我们需要在节点加载完成后再进行回显操作。 步骤2:利用`nodesMap`存储的节点信息,设置节点的选中状态(全选)和半选状态。 具体实现思路: 1.懒加载函数`loadNode`中,当调用`resolve`方法加载节点数据后,再执行回显操作。 2. 我们需要一个存储需要回显的节点key(id)的数组(例如`checkedKeys`),以及可能需要半选的节点数组(但通常半选状态是由子节点部分选中决定的,所以我们通常只需要设置全选节点,半选状态会自动计算?但注意在懒加载父子不关联的情况下,可能需要手动设置)。 3. 但是,el-tree懒加载模式下,节点是动态加载的,我们无法在初始时就设置所有节点的状态。因此,回显操作需要在节点加载后立即设置该节点的状态。 然而,引用[3]提供了一种使用`setChecked`方法的方式,但它在懒加载模式下可能无法直接使用,因为节点还未加载。所以我们需要调整:在懒加载的每个节点加载完成后,检查该节点是否在需要选中的节点数组中,如果是,则设置该节点为选中状态。 但是,如何设置半选状态?在el-tree中,半选状态(indeterminate)是由子节点的选中状态决定的。在懒加载的情况下,如果某个节点有子节点部分子节点被选中,那么该节点就会显示半选。但由于我们是懒加载,一开始可能只加载了父节点,子节点还未加载,所以无法自动计算半选状态。因此,我们需要在加载父节点时,如果该父节点需要半选,我们可能需要手动设置其`indeterminate`属性。 然而,根据引用[1]中的描述,我们可以通过直接操作`nodesMap`中节点的`checked`和`indeterminate`属性来设置状态。具体步骤如下: 1.懒加载的回调函数中,当节点数据加载完成(即调用`resolve`之后),我们访问tree的store,从nodesMap中获取当前节点node对象。 2. 判断当前节点的id是否在需要选中的id数组(全选数组)中,如果在,则设置`node.checked = true`。 3. 同时,如果当前节点的id在需要半选的id数组中(或者我们需要设置半选状态),则设置`node.indeterminate = true`。 但是,注意:在el-tree中,通常我们只需要设置全选节点,半选状态是自动计算的。但在懒加载节点未完全加载的情况下,自动计算可能不准确。因此,我们可能需要预先知道哪些节点应该处于半选状态(通常由后端返回,或者我们自己记录)。 另一种做法(推荐):在懒加载模式下,我们只回显已经加载的节点。对于未加载的节点,我们记录需要选中的节点key,当用户展开节点时,触发懒加载,在节点加载完成后,再设置该节点的选中状态。这样,当父节点的子节点部分被选中时,父节点会自动变为半选状态(前提是父子节点关联)。 如果我们使用的是父子不关联(`check-strictly`),那么就不存在半选状态,每个节点独立选择。所以,通常我们使用父子关联的情况。 因此,我们假设用户使用的是父子关联(即不设置`check-strictly`或设置为false)。 实现方案: 1. 定义需要回显的节点key数组(全选)`defaultCheckedKeys`。 2.懒加载函数中,节点数据加载完成后,检查当前节点是否在`defaultCheckedKeys`中,如果在,则设置该节点为选中状态。 3. 同时,由于父子关联,父节点的半选状态会自动根据子节点计算。但是,如果父节点还没有加载子节点,那么父节点不会显示半选(因为子节点还没加载,无法计算)。当用户展开节点,子节点加载后,父节点的半选状态就会自动更新。 然而,用户可能需要一开始就看到某些父节点是半选状态(即使子节点还没加载)。这需要我们在父节点加载的时候就设置其半选状态。但是,设置半选状态需要知道该父节点的子节点中部分被选中,而我们可能在没有加载子节点的情况下就知道这个信息(比如从后端获取的权限数据中,父节点被标记为半选)。因此,我们可以: -节点数据中增加一个字段(如`indeterminate`)来表示该节点是否应该半选(从后端获取的数据中指定)。 -懒加载节点数据时,除了返回子节点列表,还设置该节点的`indeterminate`属性(通过操作node对象)。 具体代码步骤: 1.懒加载函数`loadNode`中,获取节点数据,然后调用`resolve`方法传入子节点数组。 2. 在调用`resolve`之后,立即获取当前节点node对象(通过`loadNode`的第一个参数node,或者通过tree的store.nodesMap通过节点key获取)。 3. 检查当前节点key是否在`defaultCheckedKeys`中,如果在,则设置`node.checked = true`。 4. 同时,如果有一个单独的半选节点key数组(或一个标记该节点需要半选的对象),判断当前节点是否需要半选,如果需要,则设置`node.indeterminate = true`。 注意:同时设置`node.checked`和`node.indeterminate`可能会有冲突,所以通常我们只设置选中,而半选状态由内部计算。但如果我们确实需要手动设置半选,那么应该避免同时设置选中(全选)和半选。 因此,更合理的做法是: - 只设置节点的选中状态(全选),半选状态由el-tree内部根据子节点的选中情况自动计算。 - 但是,对于未加载子节点的父节点,如果我们知道它应该半选,我们可以设置`node.indeterminate=true`,同时不设置`node.checked`(即保持false)。 然而,这样做的风险是:当用户展开节点后,子节点加载,内部重新计算状态,可能会覆盖我们手动设置的半选状态。所以,我们需要确保在子节点加载后,父节点的半选状态能够正确更新。 另一种思路:在懒加载时,对于需要半选的父节点,我们同时加载其子节点(即使没有展开)。但这样可能会影响性能。 考虑到实现的复杂性,通常的做法是只回显叶子节点的选中状态,父节点的状态由内部计算。这样,在懒加载模式下,父节点在未展开时不会显示半选状态,只有当用户展开节点,加载子节点后,父节点才会更新为半选状态。 如果我们确实需要在未展开时显示父节点的半选状态,可以尝试以下方法: 1.懒加载节点数据中,为父节点设置`indeterminate: true`属性。 2. 在`loadNode`函数中,调用`resolve`之后,获取该节点node对象,并设置`node.indeterminate = true`。 但注意:这可能会导致内部状态不一致,因为后续加载子节点后,内部会重新计算。 因此,我们可能需要结合el-tree的API和内部属性,谨慎操作。 考虑到引用[1]中提到的通过操作`store.nodesMap`来实现,我们可以尝试: 在懒加载函数中: ```javascript loadNode(node, resolve) { // 根据nodekey去获取子节点数据 fetchChildren(node.key).then(children => { // 解析子节点 resolve(children); // 在resolve之后,节点已经添加到nodesMap中 // 获取当前节点node对象(实际上参数node就是当前节点,可以直接使用) // 检查当前节点是否需要半选 if (this.halfCheckedKeys.includes(node.key)) { // 设置半选状态 node.indeterminate = true; } // 检查当前节点是否需要全选 if (this.defaultCheckedKeys.includes(node.key)) { node.checked = true; } }); } ``` 但是,这里有一个问题:设置`node.checked=true`会触发el-tree内部的状态更新,可能会影响其他节点。而,在懒加载模式下,设置父节点的选中状态可能会导致子节点也被选中(父子关联的情况下),但子节点还未加载,所以可能不会立即体现。 另外,如果节点已经被设置为半选,再设置`node.checked=true`会覆盖半选状态吗?实际上,在el-tree中,一个节点不能同时为全选和半选。 因此,我们可能需要根据需求,只设置全选和半选中的一个。 总结: 1. 对于全选:我们设置节点的`checked`为true。 2. 对于半选:我们设置节点的`indeterminate`为true。 注意:这两个状态是互斥的。 另外,由于el-tree在父子关联时,设置父节点为全选状态会递归选中所有子节点,设置半选状态则不会。所以,我们在回显时,通常只设置叶子节点的选中状态,而父节点的状态自动计算。但这样在懒加载下,父节点的状态在展开前不会更新。 如果我们能够从后端获取到所有需要选中的叶子节点,以及需要半选的父节点(半选状态通常是由部分子节点选中决定的,所以后端可以计算出哪些父节点是半选状态),那么我们就可以在节点加载时设置半选状态。 具体实现: 1. 准备两个数组:`checkedKeys`(全选的节点key数组)和`halfCheckedKeys`(半选的节点key数组)。 2.懒加载函数中,节点加载完成后,判断该节点key是否在`checkedKeys`中,在则设置`node.checked=true`;如果在`halfCheckedKeys`中,则设置`node.indeterminate=true`。 代码示例: ```html <el-tree ref="tree" :props="props" :load="loadNode" lazy show-checkbox node-key="id"> </el-tree> ``` ```javascript export default { data() { return { props: { label: 'name', children: 'children', isLeaf: 'leaf' // 用于指示是否为叶子节点 }, checkedKeys: [1, 3, 5], // 全选节点id数组 halfCheckedKeys: [2], // 半选节点id数组 // 注意:同一个节点不能同时出现在两个数组中 }; }, methods: { loadNode(node, resolve) { // 第一层,node.level为0 if (node.level === 0) { // 加载根节点 fetchRootNodes().then(data => resolve(data)); } else { // 加载子节点 fetchChildren(node.key).then(children => { resolve(children); // 回显当前节点状态 // 当前节点node // 检查全选 if (this.checkedKeys.includes(node.key)) { node.checked = true; } // 检查半选 if (this.halfCheckedKeys.includes(node.key)) { node.indeterminate = true; } }); } } } }; ``` 注意:根节点在初始化时可能无法直接设置,因为根节点在初始化时就会加载,而我们在加载根节点时也需要进行回显。 但是,根节点的加载在`loadNode`中,我们同样在resolve后设置状态即可。 另外,对于已经展开过的节点,再次展开不会触发loadNode,所以我们需要确保在第一次加载节点时就设置好状态。 这个方案可以实现在懒加载模式下回显全选和半选状态。但需要注意,操作node的`checked`和`indeterminate`属性是直接修改节点状态,可能会触发el-tree的选中事件,因此需要确保不会引起事件处理的死循环。 最后,引用[3]中提到了使用`setChecked`方法,并配合`$nextTick`和`setTimeout`来确保节点渲染完成后再设置。但在懒加载模式下,由于节点是异步加载的,我们可以参考在节点加载完成的回调中设置状态,而不需要延时。 综上所述,在Vue2中el-tree懒加载回显数据实现全选和半选的方法主要是:在懒加载的`resolve`方法返回数据后,立即操作当前节点的`checked`和`indeterminate`属性。 相关问题: §§相关问题§§ 1.el-tree懒加载模式下,如何避免重复加载节点? 2. 如何动态更新el-tree的选中状态(包括全选和半选)? 3. el-tree懒加载时,如何实现节点的默认展开? </think> 在 Vue2 中使用 Element UI 的 `el-tree` 组件实现懒加载数据回显的全选/半选功能,核心在于**节点状态管理**和**异步加载时序控制**。以下是具体实现方法: --- ### 解决方案要点 1. **状态存储回显机制** ```javascript // 存储需要回显的节点ID defaultCheckedKeys: [1, 3, 5], // 全选节点ID halfCheckedKeys: [2], // 半选节点ID(需后端提供或自行计算) ``` 2. **节点加载后状态注入** ```javascript async loadNode(node, resolve) { const childNodes = await fetchChildren(node.key); resolve(childNodes); // 关键:在resolve后操作节点状态 this.$nextTick(() => { const treeStore = this.$refs.tree.store; const nodeObj = treeStore.nodesMap[node.key]; if (this.defaultCheckedKeys.includes(node.key)) { nodeObj.checked = true; // 设置全选 } if (this.halfCheckedKeys.includes(node.key)) { nodeObj.indeterminate = true; // 设置半选 } }); } ``` 3. **动态更新节点状态(展开节点时)** ```javascript watch: { expandedKeys(newVal) { newVal.forEach(key => { const node = this.$refs.tree.getNode(key); if (!node.loaded) node.load(); // 强制加载未展开节点 }); } } ``` --- ### 完整实现步骤 1. **配置懒加载树** ```vue <el-tree ref="tree" lazy :load="loadNode" show-checkbox node-key="id" :props="{ label: 'name', isLeaf: 'leaf' }" /> ``` 2. **实现懒加载方法** ```javascript methods: { async loadNode(node, resolve) { try { // 1. 获取子节点数据 const res = await api.getChildren(node.level === 0 ? null : node.key); const nodes = res.data.map(item => ({ ...item, leaf: !item.hasChildren // 标识是否为叶子节点 })); // 2. 解析节点数据 resolve(nodes); // 3. 回显节点状态(必须写在resolve后) this.$nextTick(() => this.setNodeStatus(node.key)); } catch (error) { resolve([]); } }, setNodeStatus(nodeKey) { const nodeObj = this.$refs.tree.store.nodesMap[nodeKey]; if (!nodeObj) return; // 设置全选/半选状态 nodeObj.checked = this.defaultCheckedKeys.includes(nodeKey); nodeObj.indeterminate = this.halfCheckedKeys.includes(nodeKey); } } ``` 3. **初始化回显数据** ```javascript mounted() { // 首次加载根节点 const rootNode = this.$refs.tree.store.root; rootNode.loaded = false; rootNode.load(); } ``` --- ### 关键注意事项 1. **时序控制** 必须确保在懒加载的 `resolve()` 执行**完成后**再操作节点状态,否则无效[^1]。 2. **节点标识规则** - 全选节点:`checked: true, indeterminate: false` - 半选节点:`checked: false, indeterminate: true` - 避免同时设置 `checked` 和 `indeterminate` 为 `true` 3. **性能优化** ```javascript // 按需展开节点(减少不必要渲染) expandedKeys: [101, 203] // 仅预展开关键路径 ``` 4. **动态更新** ```javascript // 数据变化后刷新状态 updateCheckedKeys() { this.$refs.tree.store.nodesMap = {}; this.$refs.tree.store.root.loaded = false; this.$refs.tree.store.root.load(); } ``` --- ### 适用场景 此方案适用于: - 层级深、数据量大的权限树/目录树 - 动态权限分配场景 - 需要保持父子节点联动状态的懒加载树 > **实践建议**:半选节点列表(`halfCheckedKeys`)通常需后端计算返回,前端可通过递归比对 `defaultCheckedKeys` 全量树结构生成(但懒加载模式下不推荐)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值