7.5el-tree 组件详解

el-tree

el-tree 是 Element Plus 提供的树形控件,用于展示具有层级关系的数据,如组织架构、文件目录、分类菜单等。


一、基本用法

复制代码
<template>
  <el-tree :data="treeData" :props="defaultProps" @node-click="handleNodeClick" />
</template>

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

// 树形数据
const treeData = ref([
  {
    label: '一级 1',
    children: [
      {
        label: '二级 1-1',
        children: [
          { label: '三级 1-1-1' },
          { label: '三级 1-1-2' }
        ]
      }
    ]
  },
  {
    label: '一级 2',
    children: [
      { label: '二级 2-1' },
      { label: '二级 2-2' }
    ]
  }
])

// 节点渲染配置
const defaultProps = {
  children: 'children',
  label: 'label'
}

const handleNodeClick = (data, node, component) => {
  console.log('节点被点击:', data)
}
</script>

说明

  • data:树的数据源,数组格式。
  • props:配置节点的 labelchildren 字段名。
  • 默认情况下,节点可展开/折叠。

二、核心属性(Props)

属性 类型 说明 默认值
data array 树的数据源 []
props object 配置节点的 labelchildrendisabled 等字段 { label: 'label', children: 'children' }
node-key string 每个节点的唯一标识字段名(用于勾选、展开等状态持久化) -
default-expand-all boolean 是否默认展开所有节点 false
expand-on-click-node boolean 是否在点击节点时展开/折叠节点 true
check-on-click-node boolean 是否在点击节点时选中节点 false
default-expanded-keys string[] 默认展开的节点 key 数组 []
auto-expand-parent boolean 展开子节点时是否自动展开父节点 true
show-checkbox boolean 节点是否可被选择(显示复选框) false
check-strictly boolean 是否严格遵循父子不互相关联 false
default-checked-keys string[] 默认勾选的节点 key 数组 []
highlight-current boolean 是否高亮当前选中节点 false
draggable boolean 是否开启拖拽节点 false
allow-drag Function 判断节点能否被拖拽 -
allow-drop Function 拖拽时判定目标节点能否被放置 -

三、重要事件(Events)

事件名 说明 回调参数
node-click 节点被点击时触发 (data, node, component)
node-contextmenu 右键节点时触发 (event, data, node, component)
check-change 节点选中状态变化时触发 (data, checked, indeterminate)
check 勾选节点时触发(父子联动) (data, checkedStatus)
current-change 当前选中节点变化时触发 (data, node)
node-expand 节点被展开时触发 (data, node, component)
node-collapse 节点被折叠时触发 (data, node, component)
node-drag-start 拖拽开始时触发 (node, event)
node-drag-end 拖拽结束时触发 (node, endDropNode, dropType, event)
node-drop 拖拽放置时触发 (node, dragNode, dropType, event)

四、常用方法(通过 ref 调用)

复制代码
const treeRef = ref(null)

// 获取当前选中节点
treeRef.value.getCurrentKey()

// 设置当前选中节点
treeRef.value.setCurrentKey(key)

// 获取当前选中节点数据
treeRef.value.getCurrentNode()

// 获取被勾选节点(show-checkbox 时)
treeRef.value.getCheckedKeys() // 返回 key 数组
treeRef.value.getCheckedNodes() // 返回节点数据数组

// 设置节点勾选
treeRef.value.setCheckedKeys([key1, key2])

// 获取半选中节点(父子不联动时)
treeRef.value.getHalfCheckedKeys()
treeRef.value.getHalfCheckedNodes()

// 手动展开/折叠节点
treeRef.value.getNode(key).expanded = true

五、高级功能

1. 带复选框的树(可多选)
复制代码
<el-tree
  :data="treeData"
  show-checkbox
  node-key="id"
  :default-checked-keys="[2]"
  @check-change="handleCheckChange"
/>

const handleCheckChange = (data, checked, indeterminate) => {
  console.log(`${data.label} 选中状态: ${checked}, 半选: ${indeterminate}`)
}

🔍 check-strictly:设为 true 时,父子节点选中状态不互相关联。

2. 异步加载树节点

适用于大数据量或按需加载场景。

复制代码
<el-tree
  :data="treeData"
  node-key="id"
  :props="asyncProps"
  :load="loadNode"
  lazy
/>

const asyncProps = {
  children: 'children',
  label: 'label',
  isLeaf: 'leaf' // 标记是否为叶子节点
}

const loadNode = (node, resolve) => {
  if (node.level === 0) {
    return resolve([{ label: '根目录', id: 1 }])
  }
  if (node.level > 3) return resolve([])

  // 模拟异步请求
  setTimeout(() => {
    const data = Array.from({ length: 3 }, (_, i) => ({
      label: `子节点 ${node.data.label}-${i + 1}`,
      id: Math.random(),
      leaf: node.level >= 2 // 2层以下为叶子
    }))
    resolve(data)
  }, 500)
}
3. 自定义节点内容

使用 #default 插槽自定义节点渲染。

复制代码
<el-tree :data="treeData" :props="defaultProps">
  <template #default="{ node, data }">
    <span class="custom-tree-node">
      <span>{{ node.label }}</span>
      <span>
        <el-button size="small" @click="() => append(data)">新增</el-button>
        <el-button size="small" type="danger" @click="() => remove(node, data)">删除</el-button>
      </span>
    </span>
  </template>
</el-tree>
4. 拖拽排序
复制代码
<el-tree
  :data="treeData"
  node-key="id"
  draggable
  :allow-drop="allowDrop"
  :allow-drag="allowDrag"
  @node-drop="handleDrop"
/>

const allowDrop = (draggingNode, dropNode, type) => {
  // 不允许将父节点拖拽到子节点下
  if (type === 'inner' && draggingNode.data.children) {
    return false
  }
  return true
}

const allowDrag = (draggingNode) => {
  return draggingNode.data.label.indexOf('不可拖拽') === -1
}

const handleDrop = (dragNode, dropNode, dropType, ev) => {
  ElMessage.success(`拖拽成功: ${dropType}`)
}

六、 完整案例

复制代码
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'

// 树引用
const treeRef = ref(null)

// 树数据
const treeData = ref([
  {
    id: 1,
    label: '部门A',
    children: [
      { id: 2, label: '小组A1' },
      { id: 3, label: '小组A2' }
    ]
  },
  {
    id: 4,
    label: '部门B',
    children: [
      { id: 5, label: '小组B1' }
    ]
  }
])

// 配置
const defaultProps = {
  children: 'children',
  label: 'label'
}

// 事件
const handleNodeClick = (data) => {
  ElMessage.info(`点击了: ${data.label}`)
}

const handleCheckChange = (data, checked) => {
  console.log(`${data.label} 勾选状态: ${checked}`)
}

const getCheckedKeys = () => {
  const keys = treeRef.value.getCheckedKeys()
  ElMessage.info('已勾选: ' + keys.join(', '))
}

const append = (data) => {
  const newChild = { id: Date.now(), label: '新节点' }
  if (!data.children) data.children = []
  data.children.push(newChild)
}

const remove = (node, data) => {
  const parent = node.parent
  const children = parent.data.children || parent.data
  const index = children.findIndex(d => d.id === data.id)
  children.splice(index, 1)
}

onMounted(() => {
  // 可在此处加载异步数据
})
</script>

<template>
  <div class="p-4">
    <h3 class="mb-4">组织架构树</h3>

    <el-button size="small" @click="getCheckedKeys" class="mb-4">获取勾选节点</el-button>

    <el-tree
      ref="treeRef"
      :data="treeData"
      :props="defaultProps"
      node-key="id"
      show-checkbox
      default-expand-all
      highlight-current
      @node-click="handleNode-click"
      @check-change="handleCheckChange"
    >
      <template #default="{ node, data }">
        <span class="flex items-center">
          <span>{{ node.label }}</span>
          <span class="ml-2">
            <el-button type="primary" link size="small" @click="append(data)">新增</el-button>
            <el-button type="danger" link size="small" @click="remove(node, data)">删除</el-button>
          </span>
        </span>
      </template>
    </el-tree>
  </div>
</template>

<style scoped>
.custom-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
}
</style>

✅ 最佳实践建议

  1. 必须设置 node-key:用于状态持久化,否则勾选/展开状态会错乱。
  2. 大数据量使用 lazy 懒加载:避免一次性渲染卡顿。
  3. 合理使用 check-strictly:根据业务决定父子是否联动。
  4. 拖拽功能谨慎使用:需配合后端保存新顺序。
相关推荐
日月之行_2 小时前
codeReview不再头疼AI代码审查让你的MR质量瞬间提升
前端
鹏多多3 小时前
React使用react-fastclick解决移动端触摸延迟300ms
前端
雪山上的小灰熊3 小时前
UNIAPP如何自定义全局方法?
javascript·typescript·uni-app·vue·vue3·vite·hooks
江城开朗的豌豆3 小时前
React Ref揭秘:直接操作DOM的"秘密通道"
前端·react.js
江城开朗的豌豆3 小时前
何时该请出Redux?前端状态管理的正确打开方式
前端·javascript·react.js
玲小珑3 小时前
LangChain.js 完全开发手册(十二)高性能 AI 应用优化技术
前端·langchain·ai编程
小岛前端3 小时前
Vue3 生态再一次加强,网站开发无敌!
前端·vue.js·前端框架
答案answer3 小时前
历时180多天,浅谈我对自由职业的初次探索
前端·程序员·three.js
江城开朗的豌豆3 小时前
Redux的双面人生:天使还是恶魔?
前端·javascript·react.js
JarvanMo3 小时前
为什么 Google 同时投资 Kotlin Multiplatform 和 Flutter
前端