Tree 树形控件

用于展示层级嵌套数据的树形组件,支持展开/折叠、选中、勾选等功能。

组件特性

  • 🌳 层级展示:以树形结构展示嵌套数据,支持展开/折叠。
  • ✔️ 勾选模式:支持 checkable 启用勾选框,内置父子联动。
  • 🔗 选中模式:支持单选和多选模式控制选中节点。
  • 🚫 禁用状态:支持对单个节点或整棵树设置 disabled
  • ➡️ 连接线:支持 showLine 显示层级连接线。
  • 🔍 节点过滤:通过 filterTreeNode 过滤可见节点。
  • 虚拟滚动:启用 virtualScroll 仅渲染可见区域节点。
  • 拖拽排序:支持节点拖拽排序。

基础用法

通过 data 属性传入嵌套数据,每个节点需要唯一的 key 和显示文本 label。点击箭头图标展开/折叠子节点。

<script setup lang="ts">
import { ref } from 'vue'
import { BrTree } from '@breezeui/vue'
import type { TreeNodeData } from '@breezeui/vue'

const treeData = ref<TreeNodeData[]>([
  {
    key: '1',
    label: 'Documents',
    children: [
      {
        key: '1-1',
        label: 'Work',
        children: [
          { key: '1-1-1', label: 'project.pdf' },
          { key: '1-1-2', label: 'report.docx' },
        ],
      },
      {
        key: '1-2',
        label: 'Personal',
        children: [
          { key: '1-2-1', label: 'resume.pdf' },
          { key: '1-2-2', label: 'budget.xlsx' },
        ],
      },
    ],
  },
  {
    key: '2',
    label: 'Pictures',
    children: [
      { key: '2-1', label: 'vacation.jpg' },
      { key: '2-2', label: 'family.png' },
    ],
  },
  {
    key: '3',
    label: 'Music',
    isLeaf: true,
  },
])

const expandedKeys = ref<(string | number)[]>(['1'])
</script>

<template>
  <div class="w-80">
    <BrTree
      v-model:expanded-keys="expandedKeys"
      :data="treeData"
    />
  </div>
</template>

节点选中

使用 v-model:selectedKeys 控制选中节点。点击节点即可选中。设置 selectable"multiple" 启用多选,设为 "none" 禁用选中。

<script setup lang="ts">
import { ref } from 'vue'
import { BrTree } from '@breezeui/vue'
import type { TreeNodeData } from '@breezeui/vue'

const treeData = ref<TreeNodeData[]>([
  {
    key: '1',
    label: 'Documents',
    children: [
      {
        key: '1-1',
        label: 'Work',
        children: [
          { key: '1-1-1', label: 'project.pdf' },
          { key: '1-1-2', label: 'report.docx' },
        ],
      },
      {
        key: '1-2',
        label: 'Personal',
        children: [
          { key: '1-2-1', label: 'resume.pdf' },
          { key: '1-2-2', label: 'budget.xlsx' },
        ],
      },
    ],
  },
  {
    key: '2',
    label: 'Pictures',
    children: [
      { key: '2-1', label: 'vacation.jpg' },
      { key: '2-2', label: 'family.png' },
    ],
  },
])

const expandedKeys = ref<(string | number)[]>(['1'])
const selectedKeys = ref<(string | number)[]>([])

function onSelect(keys: (string | number)[], node: TreeNodeData) {
  console.log('Selected:', keys, node)
}
</script>

<template>
  <div class="w-80">
    <BrTree
      v-model:expanded-keys="expandedKeys"
      v-model:selected-keys="selectedKeys"
      :data="treeData"
      @select="onSelect"
    />
  </div>
</template>

勾选模式

设置 checkable 启用勾选框,通过 v-model:checkedKeys 控制勾选状态。内置父子联动:勾选父节点自动勾选所有子节点,子节点全选自动勾选父节点。

<script setup lang="ts">
import { ref } from 'vue'
import { BrTree } from '@breezeui/vue'
import type { TreeNodeData } from '@breezeui/vue'

const treeData = ref<TreeNodeData[]>([
  {
    key: '1',
    label: 'All Files',
    children: [
      {
        key: '1-1',
        label: 'Documents',
        children: [
          { key: '1-1-1', label: 'project.pdf' },
          { key: '1-1-2', label: 'report.docx' },
          { key: '1-1-3', label: 'notes.txt' },
        ],
      },
      {
        key: '1-2',
        label: 'Pictures',
        children: [
          { key: '1-2-1', label: 'vacation.jpg' },
          { key: '1-2-2', label: 'family.png' },
        ],
      },
      {
        key: '1-3',
        label: 'Music',
        children: [
          { key: '1-3-1', label: 'song.mp3' },
        ],
      },
    ],
  },
])

const expandedKeys = ref<(string | number)[]>(['1', '1-1'])
const checkedKeys = ref<(string | number)[]>(['1-1-1', '1-1-2'])

function onCheck(keys: (string | number)[], node: TreeNodeData) {
  console.log('Checked:', keys, node)
}
</script>

<template>
  <div class="w-80">
    <BrTree
      v-model:expanded-keys="expandedKeys"
      v-model:checked-keys="checkedKeys"
      :data="treeData"
      checkable
      @check="onCheck"
    />
  </div>
</template>

禁用与连接线

对单个节点或整棵树设置 disabled。使用 showLine 显示层级连接线。

<script setup lang="ts">
import { ref } from 'vue'
import { BrTree } from '@breezeui/vue'
import type { TreeNodeData } from '@breezeui/vue'

const treeData = ref<TreeNodeData[]>([
  {
    key: '1',
    label: 'Documents',
    children: [
      {
        key: '1-1',
        label: 'Work',
        children: [
          { key: '1-1-1', label: 'project.pdf' },
          { key: '1-1-2', label: 'report.docx' },
        ],
      },
      {
        key: '1-2',
        label: 'Personal',
        children: [
          { key: '1-2-1', label: 'resume.pdf' },
        ],
      },
    ],
  },
  {
    key: '2',
    label: 'Disabled Folder',
    disabled: true,
    children: [
      { key: '2-1', label: 'locked-file.txt' },
    ],
  },
  {
    key: '3',
    label: 'Pictures',
    children: [
      { key: '3-1', label: 'vacation.jpg' },
      { key: '3-2', label: 'disabled.png', disabled: true },
    ],
  },
])

const expandedKeys = ref<(string | number)[]>(['1'])
</script>

<template>
  <div class="w-80">
    <BrTree
      v-model:expanded-keys="expandedKeys"
      :data="treeData"
      show-line
      default-expand-all
    />
  </div>
</template>

节点过滤

通过 filterTreeNode 过滤可见节点。传入一个返回 true 表示匹配的函数,匹配的节点及其祖先节点会自动显示。

<script setup lang="ts">
import { ref, computed } from 'vue'
import { BrTree, BrInput } from '@breezeui/vue'
import type { TreeNodeData } from '@breezeui/vue'

const treeData = ref<TreeNodeData[]>([
  {
    key: '1',
    label: 'Documents',
    children: [
      {
        key: '1-1',
        label: 'Work',
        children: [
          { key: '1-1-1', label: 'project.pdf' },
          { key: '1-1-2', label: 'report.docx' },
          { key: '1-1-3', label: 'presentation.pptx' },
        ],
      },
      {
        key: '1-2',
        label: 'Personal',
        children: [
          { key: '1-2-1', label: 'resume.pdf' },
          { key: '1-2-2', label: 'budget.xlsx' },
        ],
      },
    ],
  },
  {
    key: '2',
    label: 'Pictures',
    children: [
      {
        key: '2-1',
        label: 'Vacation',
        children: [
          { key: '2-1-1', label: 'beach.jpg' },
          { key: '2-1-2', label: 'sunset.jpg' },
        ],
      },
      { key: '2-2', label: 'family.png' },
    ],
  },
  {
    key: '3',
    label: 'Music',
    isLeaf: true,
  },
])

const searchQuery = ref('')
const filterFn = computed(() => {
  if (!searchQuery.value.trim()) return undefined
  const q = searchQuery.value.toLowerCase()
  return (node: TreeNodeData) =>
    node.label.toLowerCase().includes(q)
})
</script>

<template>
  <div class="space-y-3">
    <div class="w-72">
      <BrInput v-model="searchQuery" placeholder="Type to filter nodes..." size="sm" />
    </div>
    <div class="w-80">
      <BrTree
        :data="treeData"
        :filter-tree-node="filterFn"
        default-expand-all
        show-icon
      />
    </div>
  </div>
</template>

虚拟滚动

当数据量较大时,启用 virtualScroll 仅渲染可见区域的节点。通过 height 设置容器高度,itemHeight 设置每行高度。

<script setup lang="ts">
import { ref } from 'vue'
import { BrTree } from '@breezeui/vue'
import type { TreeNodeData } from '@breezeui/vue'

function generateLargeData(count: number, depth = 0, prefix = ''): TreeNodeData[] {
  const result: TreeNodeData[] = []
  for (let i = 0; i < count; i++) {
    const key = `${prefix}${i}`
    if (depth < 3) {
      result.push({
        key,
        label: `Node ${key}`,
        children: generateLargeData(5, depth + 1, `${key}-`),
      })
    } else {
      result.push({ key, label: `Leaf ${key}`, isLeaf: true })
    }
  }
  return result
}

const virtualData = ref<TreeNodeData[]>(generateLargeData(20))
</script>

<template>
  <div class="w-96">
    <BrTree
      :data="virtualData"
      virtual-scroll
      :item-height="32"
      :height="400"
      default-expand-all
      show-icon
    />
  </div>
</template>

拖拽排序

设置 draggable 启用拖拽排序。监听 drop 事件处理节点的新位置。使用 allowDrop 可限制有效的放置目标。

<script setup lang="ts">
import { ref } from 'vue'
import { BrTree } from '@breezeui/vue'
import type { TreeNodeData, DropInfo } from '@breezeui/vue'

const treeData = ref<TreeNodeData[]>([
  {
    key: 'd1',
    label: 'Root A',
    children: [
      { key: 'd1-1', label: 'Child A1' },
      { key: 'd1-2', label: 'Child A2' },
      { key: 'd1-3', label: 'Child A3' },
    ],
  },
  {
    key: 'd2',
    label: 'Root B',
    children: [
      { key: 'd2-1', label: 'Child B1' },
      { key: 'd2-2', label: 'Child B2' },
    ],
  },
  {
    key: 'd3',
    label: 'Root C',
    children: [
      { key: 'd3-1', label: 'Child C1' },
    ],
  },
])

const expandedKeys = ref<(string | number)[]>(['d1', 'd2', 'd3'])

function handleDrop(info: DropInfo) {
  console.log('Drop:', info)
}
</script>

<template>
  <div class="w-80">
    <BrTree
      v-model:expanded-keys="expandedKeys"
      :data="treeData"
      draggable
      show-icon
      @drop="handleDrop"
    />
  </div>
</template>

主题定制

可以通过 Tailwind CSS 类或覆盖 CSS 变量来自定义外观。

Tailwind 定制

使用 class 属性添加自定义样式:

<BrTree class="border rounded-md p-2" :data="treeData" />

API

BrTree Props

属性名类型默认值说明
dataTreeNodeData[][]树形数据源。
expandedKeys(string | number)[]-展开的节点 key(受控,v-model:expandedKeys)。
selectedKeys(string | number)[]-选中的节点 key(受控,v-model:selectedKeys)。
checkedKeys(string | number)[]-勾选的节点 key(受控,v-model:checkedKeys)。
checkablebooleanfalse是否显示勾选框。
selectableboolean | 'single' | 'multiple' | 'none'true选中模式。
showLinebooleanfalse是否显示连接线。
showIconbooleantrue是否显示节点图标。
defaultExpandAllbooleanfalse是否默认展开所有节点。
defaultExpandedKeys(string | number)[][]默认展开的节点 key(非受控)。
defaultSelectedKeys(string | number)[][]默认选中的节点 key(非受控)。
defaultCheckedKeys(string | number)[][]默认勾选的节点 key(非受控)。
disabledbooleanfalse是否禁用整棵树。
draggablebooleanfalse节点是否可拖拽。
loadData(node: TreeNodeData) => Promise<TreeNodeData[]>-异步加载子节点回调。
filterTreeNode(node: TreeNodeData) => boolean-节点过滤函数。
size'xs' | 'sm' | 'default' | 'md' | 'lg' | '2xl''default'组件尺寸。
indentnumber24每层缩进像素数。
virtualScrollbooleanfalse是否启用虚拟滚动(大数据量场景)。
itemHeightnumber32虚拟滚动模式下每行高度(像素)。
heightnumber | string400虚拟滚动容器高度。
allowDrop(options: AllowDropOptions) => boolean-控制允许放置位置的回调函数。

TreeNodeData 数据结构

属性名类型说明
keystring | number节点唯一标识。
labelstring显示文本。
childrenTreeNodeData[]子节点。
disabledboolean是否禁用该节点。
isLeafboolean强制为叶子节点(不显示展开箭头)。
iconComponent自定义图标组件。
loadingboolean加载状态。

Events 事件

名称参数说明
update:expandedKeys(string | number)[]展开节点变化时触发。
update:selectedKeys(string | number)[]选中节点变化时触发。
update:checkedKeys(string | number)[]勾选节点变化时触发。
expand(keys, node)节点展开/折叠时触发。
select(keys, node)节点选中时触发。
check(keys, node)节点勾选/取消勾选时触发。
dragStart(node, event)拖拽开始时触发。
dragEnd(node, event)拖拽结束时触发。
drop(info: DropInfo)节点放置时触发。DropInfo 包含 { dragNode, dropNode, dropPosition }

Slots 插槽

名称参数说明
switcher{ expanded, node }自定义展开/折叠图标。
icon{ node, expanded }自定义节点图标。
label{ node }自定义节点内容。