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>关键代码解释
@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) }- 通过递归遍历所有节点
- 严格限制非叶子节点的选中状态
提交按钮逻辑:
const submit = () => { const checkedNodes = treeRef.value.getCheckedNodes() console.log('选中节点:', checkedNodes) }- 获取所有选中节点
- 可以用于权限管理的提交操作
六、源码解析
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属性 - 对数据进行分页处理
十、最佳实践
使用场景:
- 权限管理系统
- 文件系统选择
- 系统配置管理
- 菜单权限控制
不适用场景:
- 需要选择多级节点的场景
- 需要选择父级节点的场景
- 不需要严格层级控制的场景
推荐方案:
- 使用
check-strictly属性作为基础 - 结合
@check事件进行动态控制 - 对非叶子节点设置
disabled属性 - 对大数据量使用懒加载
- 使用
注意事项:
- 始终在前端和后端进行权限校验
- 处理空节点和异常情况
- 使用防抖处理大数据量场景
十一、总结
实现"el-tree只有最后一级可以选中"的功能需要深入理解组件的工作原理,结合事件处理、数据遍历和状态管理来实现。通过不同的实现方案,可以适应不同的业务需求。在实际开发中,需要根据具体场景选择合适的方案,同时注意性能优化和安全风险。本文提供的完整案例和代码示例,可以帮助开发者更好地理解和应用这个功能,提高开发效率和代码质量。
评论已关闭