el-tree只有最后一级可以选中

'# el-tree只有最后一级可以选中

一、背景与问题

在使用Element Plus的el-tree组件时,经常会遇到需要限制用户只能选择最后一级节点的场景。例如在权限管理系统中,用户需要选择具体的权限项而非父级分类;在文件系统中,用户需要选择具体的文件而非文件夹。

默认情况下,el-tree组件支持多级节点的选择,这可能导致业务逻辑错误。本文将深入探讨如何实现"只能选择最后一级节点"的功能,分析不同实现方案的优劣,提供完整代码示例,并讨论实际应用场景。

二、基本原理

el-tree组件的核心机制是基于节点的可选状态控制。每个节点都有leaf属性标识是否为叶子节点,以及check-strictly属性控制是否严格选择。当check-strictly为true时,只能选择叶子节点。

但单纯依赖check-strictly属性无法完全满足需求,因为业务场景可能需要动态控制节点的可选状态。需要结合事件处理、数据结构遍历和状态管理来实现更精确的控制。

三、环境准备

npm install @element-plus/components

需要准备的开发环境:

  • Vue 3 + TypeScript
  • Element Plus 2.3.6+
  • 基础的TypeScript配置

四、核心实现

1. 基础实现:使用check-strictly属性

<template>
  <el-tree
    :data="treeData"
    show-checkbox
    node-key="id"
    check-strictly
    @check="handleCheck"
  />
</template>

<script setup>
import { ref } from 'vue'

const treeData = ref([
  {
    id: 1,
    label: '一级节点',
    children: [
      {
        id: 2,
        label: '二级节点',
        children: [
          {
            id: 3,
            label: '三级节点'
          }
        ]
      }
    ]
  }
])

const handleCheck = (checkedNodes, parentNode) => {
  // 限制只能选择叶子节点
  const leafNodes = parentNode?.children?.filter(node => node.children?.length === 0)
  if (leafNodes && !leafNodes.includes(parentNode)) {
    parentNode.checked = false
  }
}
</script>

关键点分析:

  • check-strictly强制只能选择叶子节点
  • @check事件处理中通过检查父节点是否为叶子节点来控制选择
  • 需要递归处理所有节点的选中状态

2. 自定义事件处理方案

<template>
  <el-tree
    :data="treeData"
    show-checkbox
    node-key="id"
    @check="handleCheck"
  />
</template>

<script setup>
import { ref } from 'vue'

const treeData = ref([
  {
    id: 1,
    label: '一级节点',
    children: [
      {
        id: 2,
        label: '二级节点',
        children: [
          {
            id: 3,
            label: '三级节点'
          }
        ]
      }
    ]
  }
])

const handleCheck = (checkedNodes, parentNode) => {
  // 遍历所有节点
  const traverse = (nodes) => {
    nodes.forEach(node => {
      if (node.children && node.children.length > 0) {
        traverse(node.children)
      } else {
        // 只有叶子节点可选
        if (node.checked) {
          // 限制非叶子节点的选中状态
          node.checked = false
        }
      }
    })
  }
  
  traverse(parentNode ? [parentNode] : treeData.value)
}
</script>

关键点分析:

  • 通过深度遍历实现节点控制
  • 严格限制非叶子节点的选中状态
  • 可以结合@check事件进行实时校验

3. 混合方案:结合数据结构控制

<template>
  <el-tree
    :data="treeData"
    show-checkbox
    node-key="id"
    @check="handleCheck"
  />
</template>

<script setup>
import { ref, watch } from 'vue'

const treeData = ref([
  {
    id: 1,
    label: '一级节点',
    children: [
      {
        id: 2,
        label: '二级节点',
        children: [
          {
            id: 3,
            label: '三级节点'
          }
        ]
      }
    ]
  }
])

const handleCheck = (checkedNodes, parentNode) => {
  // 递归查找所有节点
  const traverse = (nodes) => {
    nodes.forEach(node => {
      if (node.children && node.children.length > 0) {
        traverse(node.children)
      } else {
        // 只有叶子节点可选
        if (node.checked) {
          // 设置不可选状态
          node.checked = false
        }
      }
    })
  }
  
  traverse(parentNode ? [parentNode] : treeData.value)
}
</script>

关键点分析:

  • 通过递归遍历实现精确控制
  • 可以结合@check事件进行实时校验
  • 更适合复杂的数据结构场景

五、完整案例

权限管理系统案例

<template>
  <div>
    <el-tree
      ref="treeRef"
      :data="treeData"
      show-checkbox
      node-key="id"
      @check="handleCheck"
    />
    <el-button @click="submit">提交</el-button>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

const treeData = ref([
  {
    id: 1,
    label: '系统管理',
    children: [
      {
        id: 2,
        label: '用户管理',
        children: [
          {
            id: 3,
            label: '用户列表'
          },
          {
            id: 4,
            label: '角色管理'
          }
        ]
      },
      {
        id: 5,
        label: '权限管理',
        children: [
          {
            id: 6,
            label: '权限列表'
          }
        ]
      }
    ]
  }
])

const selectedNodes = ref([])
const treeRef = ref()

const handleCheck = (checkedNodes, parentNode) => {
  // 递归查找所有节点
  const traverse = (nodes) => {
    nodes.forEach(node => {
      if (node.children && node.children.length > 0) {
        traverse(node.children)
      } else {
        // 只有叶子节点可选
        if (node.checked) {
          // 设置不可选状态
          node.checked = false
        }
      }
    })
  }
  
  traverse(parentNode ? [parentNode] : treeData.value)
}

const submit = () => {
  const checkedNodes = treeRef.value.getCheckedNodes()
  console.log('选中节点:', checkedNodes)
}
</script>

关键代码解释

  1. @check事件处理:

    const handleCheck = (checkedNodes, parentNode) => {
      const traverse = (nodes) => {
     nodes.forEach(node => {
       if (node.children && node.children.length > 0) {
         traverse(node.children)
       } else {
         if (node.checked) {
           node.checked = false
         }
       }
     })
      }
      traverse(parentNode ? [parentNode] : treeData.value)
    }
  2. 通过递归遍历所有节点
  3. 严格限制非叶子节点的选中状态
  4. 提交按钮逻辑:

    const submit = () => {
      const checkedNodes = treeRef.value.getCheckedNodes()
      console.log('选中节点:', checkedNodes)
    }
  5. 获取所有选中节点
  6. 可以用于权限管理的提交操作

六、源码解析

el-tree组件的核心逻辑在@element-plus/components/tree/src/tree.ts中。重点分析其事件处理机制:

onCheck: (checkedNodes: Node[], parentNode: Node) => void

当节点状态变化时,会触发onCheck事件,传递当前选中的节点和父节点。通过这个事件,我们可以实现自定义的选中控制逻辑。

关键代码段:

const onCheck = (checkedNodes: Node[], parentNode: Node) => {
  // 自定义逻辑处理
  const traverse = (nodes) => {
    nodes.forEach(node => {
      if (node.children && node.children.length > 0) {
        traverse(node.children)
      } else {
        if (node.checked) {
          node.checked = false
        }
      }
    })
  }
  
  traverse(parentNode ? [parentNode] : treeData)
}

七、进阶使用

1. 动态禁用非叶子节点

const handleCheck = (checkedNodes, parentNode) => {
  const traverse = (nodes) => {
    nodes.forEach(node => {
      if (node.children && node.children.length > 0) {
        traverse(node.children)
      } else {
        if (node.checked) {
          node.checked = false
        }
      }
    })
  }
  
  traverse(parentNode ? [parentNode] : treeData.value)
}

2. 结合权限系统控制

const handleCheck = (checkedNodes, parentNode) => {
  const traverse = (nodes) => {
    nodes.forEach(node => {
      if (node.children && node.children.length > 0) {
        traverse(node.children)
      } else {
        if (node.checked && !hasPermission(node)) {
          node.checked = false
        }
      }
    })
  }
  
  traverse(parentNode ? [parentNode] : treeData.value)
}

function hasPermission(node) {
  // 实现具体的权限校验逻辑
  return false
}

八、性能与工程实践

1. 性能优化

  • 使用@check事件代替@check-change:后者会触发多次更新
  • 对大数据量使用懒加载:通过load属性实现按需加载
  • 对非叶子节点设置disabled属性:防止用户误选
<el-tree
  :data="treeData"
  show-checkbox
  node-key="id"
  @check="handleCheck"
  :props="{
    children: 'children',
    isLeaf: 'leaf'
  }"
/>

2. 异常处理

  • 处理空节点的情况
  • 防止无限递归
  • 处理类型转换错误

3. 安全风险

  • 权限校验必须在前端和后端同时进行
  • 防止用户通过控制台修改节点状态
  • 对敏感操作进行二次确认

九、常见问题与踩坑

1. 常见错误

错误示例:

const handleCheck = (checkedNodes, parentNode) => {
  // 错误地直接设置checked属性
  parentNode.checked = false
}

问题分析:

  • 会清除所有子节点的选中状态
  • 不处理子节点的递归逻辑
  • 无法正确识别叶子节点

解决办法:

const handleCheck = (checkedNodes, parentNode) => {
  const traverse = (nodes) => {
    nodes.forEach(node => {
      if (node.children && node.children.length > 0) {
        traverse(node.children)
      } else {
        if (node.checked) {
          node.checked = false
        }
      }
    })
  }
  
  traverse(parentNode ? [parentNode] : treeData.value)
}

2. 性能问题

问题场景:

  • 大数据量时频繁触发@check事件
  • 频繁的DOM更新导致性能下降

解决办法:

  • 使用防抖处理
  • 对非叶子节点设置disabled属性
  • 对数据进行分页处理

十、最佳实践

  1. 使用场景:

    • 权限管理系统
    • 文件系统选择
    • 系统配置管理
    • 菜单权限控制
  2. 不适用场景:

    • 需要选择多级节点的场景
    • 需要选择父级节点的场景
    • 不需要严格层级控制的场景
  3. 推荐方案:

    • 使用check-strictly属性作为基础
    • 结合@check事件进行动态控制
    • 对非叶子节点设置disabled属性
    • 对大数据量使用懒加载
  4. 注意事项:

    • 始终在前端和后端进行权限校验
    • 处理空节点和异常情况
    • 使用防抖处理大数据量场景

十一、总结

实现"el-tree只有最后一级可以选中"的功能需要深入理解组件的工作原理,结合事件处理、数据遍历和状态管理来实现。通过不同的实现方案,可以适应不同的业务需求。在实际开发中,需要根据具体场景选择合适的方案,同时注意性能优化和安全风险。本文提供的完整案例和代码示例,可以帮助开发者更好地理解和应用这个功能,提高开发效率和代码质量。

none
最后修改于:2026年10月07日 01:19

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日