☰
Nuxt UI Tree 组件完全指南:层级数据展示、多选、虚拟化与拖拽实战
2026/10/9 5:21:23 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】ui

The Intuitive Vue UI Library powered by Reka UI & Tailwind CSS.

项目地址:https://gitcode.com/gh_mirrors/ui4/ui
点击查看免费下载

本篇技术指南围绕 Nuxt UI(即当前仓库gh_mirrors/ui4/ui对应的开源组件库)中的Tree组件展开。Tree 用于以树形结构展示和交互层级化数据,例如文件目录、组织架构、分类菜单等。读完本文,你将掌握items数据模型、选中/展开的受控用法、扁平化渲染、图标定制、复选框联动、拖拽排序与虚拟化等完整实战能力,并能借助源码与测试理解其底层实现。

组件定位与底层架构

Tree 是一个基于 Reka UI,其中:

  • TreeRoot负责整棵树的选中、展开状态管理与键盘交互;
  • TreeItem负责单个节点的展开/选中行为,并通过as-child透传;
  • TreeVirtualizer在启用虚拟化时按需渲染可见节点。

组件对外暴露的类型TreeItem与TreeProps均在同一文件中定义,TypeScript 泛型T extends TreeItem[]、M extends boolean让多选与非多选场景拥有精确的类型推导(见 test/components/Tree.spec.ts 底部的expectEmitPayloadType断言)。

基础用法:用 items 渲染一棵树

在 Nuxt 中直接使用<UTree>(Vue 场景对应vue.UTree),通过items属性传入层级数据:

<script setup lang="ts"> import type { TreeItem } from '#ui/components/Tree' const items: TreeItem[] = [ { label: 'app/', defaultExpanded: true, children: [ { label: 'composables/', children: [ { label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' }, { label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' } ] }, { label: 'components/', defaultExpanded: true, children: [ { label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' }, { label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' } ] } ] }, { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' }, { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' } ] </script> <template> <UTree :items="items" class="w-60" /> </template>

上例中带children的节点自动渲染为可展开的父节点(文件夹形态),叶子节点渲染为普通条目;defaultExpanded: true让app/与components/默认展开。

items 数据结构与 TreeItem 属性

items是TreeItem[]数组,每个节点支持以下属性:

属性类型说明
iconstring节点前导图标(Iconify 名称),通常用于叶子文件图标
labelstring节点显示文本,也是默认唯一标识来源
trailingIconstring节点右侧图标,用于覆盖父节点的展开箭头
defaultExpandedboolean节点是否默认展开
disabledboolean禁用单个节点
slotstring为节点指定自定义插槽名称,实现逐节点定制
childrenTreeItem[]子节点数组,存在即视为父节点
onToggle(e: TreeItemToggleEvent) => void节点级展开/收起回调
onSelect(e: TreeItemSelectEvent) => void节点级选中回调
classany作用于该节点链接的 class
uiPartial<...>作用于该节点的插槽 class,含item、itemWithChildren、link、linkLeadingIcon、linkLabel、linkTrailing、linkTrailingIcon、listWithChildren

其中class与ui允许对单个节点进行局部样式覆盖;[key: string]: any允许携带任意自定义字段(如id),供getKey或数据逻辑使用。

关于唯一标识的重要约束

每个节点都需要一个唯一标识符。默认情况下组件以label作为标识,若label不唯一(例如多个重名目录),必须通过get-key函数返回唯一标识,或使用label-key指定参与标识的字段。源码中getItemKey的逻辑为:优先使用props.getKey(item),否则回退到getItemLabel(item)(即labelKey字段,默认'label'):

// src/runtime/components/Tree.vue function getItemKey(item: TreeItem): string { return props.getKey ? props.getKey(item) || getItemLabel(item) : getItemLabel(item) }

多选:multiple 与选中值

设置multiple后,v-model(或default-value)的值由单个节点变为节点数组:

<UTree :items="items" multiple default-expanded class="w-60" />

defaultExpanded在这里也接受TreeItem[]以默认展开多个节点。多选与受控选中均可在 test/components/Tree.spec.ts 中找到对应渲染用例(with multiple、with multiple and modelValue、with multiple and defaultValue)。

渲染模式:nested 与扁平列表

nested属性控制树以嵌套 DOM 结构渲染还是以扁平列表渲染,默认true:

<UTree :items="items" :nested="false" class="w-60" />

当nested为false时,所有节点渲染在同一层级,依靠缩进表现层级关系——这正是拖拽与虚拟化场景所依赖的形态。源码中nested的计算是props.virtualize ? false : props.nested,即开启虚拟化时强制扁平化。

扁平模式下的缩进通过内联padding-inline-start计算,由flattenedPaddingFormula按size与节点level推算(xs到xl各尺寸对应不同的基础值与每级递增量,见 src/runtime/components/Tree.vue 第 185–195 行)。因此即使传入自定义主题尺寸也不会抛错——测试does not throw when flattened with a custom theme size专门覆盖了该场景。

外观定制

Color 与 Size

color控制选中态高亮颜色,默认primary;size控制紧凑程度,支持xs/sm/md/lg/xl,默认md:

<UTree :items="items" color="neutral" size="xl" class="w-60" />

主题定义(src/theme/tree.ts)中,color变体为选中态链接生成before:outline-{color}/25轮廓(neutral使用before:outline-inverted/25),size变体则调整内边距、字号与图标尺寸。

Trailing Icon(父节点展开箭头)

父节点右侧默认渲染i-lucide-chevron-down,可通过trailing-icon覆盖:

<UTree :items="items" trailing-icon="i-lucide-arrow-down" class="w-60" />

若某个节点自身声明了trailingIcon,则节点级图标始终优先于组件级trailing-icon。该默认图标定义在appConfig.ui.icons.chevronDown(默认值见 src/theme/icons.ts 的i-lucide-chevron-down),可在 Nuxt 的app.config.ts或 Vue 的vite.config.ts中全局替换。

Expanded / Collapsed Icon(文件夹图标)

父节点未展开时默认显示i-lucide-folder,展开后显示i-lucide-folder-open,分别对应collapsed-icon与expanded-icon:

<UTree :items="items" expanded-icon="i-lucide-book-open" collapsed-icon="i-lucide-book" class="w-60" />

全局默认值位于appConfig.ui.icons.folder/appConfig.ui.icons.folderOpen(src/theme/icons.ts)。渲染优先级在 src/runtime/components/Tree.vue 模板中体现:节点自带icon优先,否则有子节点时按展开状态渲染expandedIcon或collapsedIcon。

Disabled(禁用)

组件级disabled会禁用整棵树的全部交互;也可以只对单个节点设置item.disabled。禁用态的链接样式由主题disabled变体定义(cursor-not-allowed opacity-75):

<UTree :items="items" disabled class="w-60" />

受控用法:选中与展开

控制选中项

通过v-model或default-value受控管理选中项;传入v-model时建议同时用get-key指明取值函数,确保值与数据一一对应:

<script setup lang="ts"> const selected = ref<TreeItem>() const getKey = (item: TreeItem) => item.label </script> <template> <UTree v-model="selected" :items="items" :get-key="getKey" class="w-60" /> </template>

若希望点击父节点时只展开/收起而不选中,可通过节点属性item.onSelect或组件级select事件阻止:

const onSelect = (event: TreeItemSelectEvent<TreeItem>) => { if (event.originalEvent.type === 'click' && event.node.children) { // 阻止选中,仅切换展开状态 } }

控制展开项

同理,展开状态可通过v-model(数组)或default-expanded受控;item.onToggle或组件级toggle事件可用于阻止展开/收起,例如"选中父节点但不展开其子项"的场景。

高级示例(4.1+)

节点内嵌复选框

在item-leading插槽中放置 Checkbox 组件,配合multiple、propagate-select(选中向父/子节点传播)与bubble-select属性即可实现带父子联动关系的勾选树,再通过select、toggle事件同步选中与展开状态。由于Checkbox本身渲染为button,需要通过as属性将树节点由button改为div以规避嵌套按钮的无障碍问题:

<UTree :items="items" multiple propagate-select bubble-select :as="{ root: 'ul', link: 'div' }" class="w-60" />

拖拽排序

使用@vueuse/integrations提供的useSortable(内部封装 Sortable.js)可让树支持拖拽。前提是设置nested: false将树渲染为扁平列表,使每个节点成为可被 Sortable 识别/移动的同级元素:

import { useSortable } from '@vueuse/integrations/useSortable' const treeRef = ref<HTMLElement>() const { } = useSortable(treeRef, items, { animation: 150 })
<UTree ref="treeRef" :items="items" :nested="false" class="w-60" />

虚拟化:大数据量渲染

virtualize属性可为大列表启用虚拟化,传true或配置对象{ estimateSize, overscan }:

<UTree :items="items" :virtualize="{ estimateSize: 32, overscan: 12 }" class="w-60" />
  • estimateSize:每项预估高度(像素),默认32,也可以是(index) => number函数;
  • overscan:可视区外额外渲染的项数,默认12。

需要注意:虚拟化开启时树结构会被扁平化(效果等同nested: false),且滚动容器由主题virtualize: true变体提供(root: 'overflow-y-auto')。源码通过getEstimateSize(items, size)(见 src/utils/virtualizer.ts)依据当前size自动推算默认预估高度,实际渲染由 Reka UI 的TreeVirtualizer完成。

自定义插槽

节点级slot属性可指定个性化插槽,每个具名插槽又衍生出五个子插槽:

  • #{item.slot}-wrapper:包裹整个节点
  • #{item.slot}:节点主体内容
  • #{item.slot}-leading:前导区域(图标)
  • #{item.slot}-label:文本标签区域
  • #{item.slot}-trailing:尾部区域(展开箭头)

未指定slot时对应基础插槽item-wrapper/item/item-leading/item-label/item-trailing。所有插槽均暴露以下作用域属性:

属性说明
item当前节点数据
index节点在同级中的索引
level节点所在层级(从 1 开始)
expanded/selected/indeterminate展开、选中、半选状态
handleSelect/handleToggle手动触发选中/展开的行为函数
ui当前主题的ui对象(含插槽 class)

示例:为slot: 'app'的节点定制内容:

<template #app-label="{ item }"> <span class="font-bold">{{ item.label }}</span> </template>

主题定制

Tree 的主题结构定义在 src/theme/tree.ts,包含插槽样式与变体:

插槽默认样式要点
rootrelative isolate(虚拟化时追加overflow-y-auto)
itemw-full
listWithChildrenborder-s border-default(左侧层级线)
itemWithChildrenps-1.5 -ms-px(对齐层级线)
link弹性布局、text-sm、before伪元素圆角高亮底
linkLeadingIcon/linkTrailingIconshrink-0,展开箭头带group-data-expanded:rotate-180旋转动画
linkLabeltruncate(文本截断)
linkTrailingms-auto inline-flex gap-1.5

变体方面支持virtualize、color、size、selected、disabled,默认变体为color: 'primary'、size: 'md';selected与color的组合通过compoundVariants生成选中态文字颜色(如text-primary),未选中且未禁用时提供hover:text-highlighted悬停反馈。通过app.config.ts(Nuxt)或vite.config.ts(Vue)中的ui.tree键即可覆盖上述任意插槽 class,也可利用ui.icons.chevronDown、ui.icons.folder、ui.icons.folderOpen全局更换默认图标。

API 汇总

Props(核心)

属性类型默认值说明
asany \| { root, link }'ul'/'button'根节点与链接的渲染元素
itemsTreeItem[]—树数据
color'primary' \| 'neutral' \| ...'primary'选中态颜色
size'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl''md'紧凑程度
getKey(item) => string—唯一标识函数
labelKeystring'label'作为标识/文本的字段
multiplebooleanfalse是否多选
modelValue/defaultValueTreeItem \| TreeItem[]—受控/非受控选中值
nestedbooleantrue嵌套 DOM 还是扁平列表
virtualizeboolean \| { estimateSize?, overscan? }false虚拟化开关与参数
trailingIconstringui.icons.chevronDown父节点展开箭头
expandedIcon/collapsedIconstringui.icons.folderOpen/ui.icons.folder展开/收起态文件夹图标
disabledbooleanfalse禁用整棵树
expanded/defaultExpandedTreeItem[]—展开状态受控/默认值
selectionBehavior/propagateSelect/bubbleSelect/loop继承自 Reka UI—选择与焦点行为
onSelect/onToggle回调—组件级选中/展开回调

Slots

item-wrapper、item、item-leading、item-label、item-trailing,以及由item.slot派生的动态插槽(#{slot}-wrapper、#{slot}、#{slot}-leading、#{slot}-label、#{slot}-trailing)。所有插槽共享上文列出的作用域属性。

Emits

组件发出继承自 Reka UITreeRoot的事件,主要包括update:modelValue(v-model选中值)、select(选中某节点)、toggle(展开/收起某节点)等,且携带对应节点数据,供item.onSelect/item.onToggle之外的全局拦截使用。

测试与可访问性保障

test/components/Tree.spec.ts 以快照渲染方式覆盖了本文提及的绝大部分能力:items、modelValue、defaultValue、expanded、defaultExpanded、labelKey、getKey、multiple、disabled、nested、virtualize、三类图标、全部size变体、neutral颜色、as、class、ui覆盖,以及item-wrapper/item/item-leading/item-trailing与动态插槽的渲染。此外还包含两项关键断言:

  • 通过axe无障碍审计(passes accessibility tests),验证了基于 Reka UI 的 WAI-ARIA 语义、键盘导航与焦点管理;
  • 通过expectEmitPayloadType验证update:modelValue在普通与getKey场景下的载荷类型推导。

结合文档与测试,可以确认 Tree 组件在交互语义与视觉表现上均具备生产可用性。建议在实际项目中优先使用getKey保证数据唯一性,并在数据量较大时开启virtualize以获得稳定性能。

提示:本文对应的官方交互式示例(选中控制、展开控制、复选框树、拖拽、虚拟化、自定义插槽)均可通过仓库文档目录 docs/content/docs/2.components/tree.md 中嵌入的 component-example 宏查看源码与实时效果。

  • 前端
  • UI组件

【免费下载链接】ui

The Intuitive Vue UI Library powered by Reka UI & Tailwind CSS.

项目地址:https://gitcode.com/gh_mirrors/ui4/ui
点击查看免费下载

相关推荐

上一篇:Ente Locker 集合(Collections)使用指南:用多标签式组织管理你的加密文档
下一篇:Prometheus Operator 官方 CLI 参考:operator 二进制全部命令与参数详解

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询