Tree
Компонент иерархического дерева для отображения вложенных данных с раскрытием/сворачиванием, выбором, чекбоксами, drag & drop, фильтрацией, ленивой загрузкой, inline-редактированием и другими возможностями.
Basic
Кликните по стрелке узла-ветви, чтобы раскрыть или свернуть её.
<script setup>
import { VxTree } from 'veloxui'
const data = [
{
key: '1', label: 'Documents',
children: [
{ key: '1-1', label: 'Work',
children: [
{ key: '1-1-1', label: 'Reports.xlsx' },
{ key: '1-1-2', label: 'Presentation.pptx' },
],
},
{ key: '1-2', label: 'Personal',
children: [
{ key: '1-2-1', label: 'Resume.pdf' },
],
},
],
},
]
</script>
<template>
<VxTree :data="data" />
</template>Selection
Используйте v-model:selected, чтобы связать ключ выбранного узла. Включено по умолчанию.
Selected: none
<script setup>
import { ref } from 'vue'
const selectedKey = ref(null)
</script>
<template>
<VxTree :data="data" v-model:selected="selectedKey" />
</template>Multiple Selection
Включите multiple, чтобы выбирать несколько узлов. Удерживайте Ctrl (или Cmd на Mac) и кликайте, чтобы переключать отдельные узлы.
Selected: none
<script setup>
import { ref } from 'vue'
const selected = ref([])
</script>
<template>
<VxTree :data="data" v-model:selected="selected" multiple default-expand-all />
</template>Checkboxes
Добавьте чекбоксы через checkable. Родительские узлы показывают неопределённое состояние при частичном выборе.
Checked: none
<script setup>
import { ref } from 'vue'
const checkedKeys = ref([])
</script>
<template>
<VxTree :data="data" checkable v-model:checked="checkedKeys" default-expand-all />
</template>Show Lines
Показывайте соединительные линии через show-line. Используйте line-style, чтобы выбрать solid, dashed или dotted.
<template>
<VxTree :data="data" show-line default-expand-all />
<VxTree :data="data" show-line line-style="dashed" default-expand-all />
<VxTree :data="data" show-line line-style="dotted" default-expand-all />
</template>Filter with Highlight
Используйте filter-text, чтобы отфильтровать видимые узлы. Совпадающий текст подсвечивается автоматически. Дерево автоматически раскрывается, чтобы показать совпадающих потомков, и восстанавливает прежнее состояние при очистке фильтра.
<script setup>
import { ref } from 'vue'
const filterText = ref('')
</script>
<template>
<VxInput v-model="filterText" placeholder="Type to filter..." />
<VxTree :data="data" :filter-text="filterText" default-expand-all />
</template>Default Expand All
Используйте default-expand-all, чтобы раскрыть все ветви при монтировании.
Accordion
Одновременно может быть раскрыт только один узел на уровне. Раскрытие узла сворачивает его соседей.
<template>
<VxTree :data="data" accordion />
</template>Node Icons
Используйте свойство icon в данных узла или настройте через слот #icon.
<script setup>
const data = [
{
key: 'src', label: 'src', icon: '📁',
children: [
{ key: 'index', label: 'index.ts', icon: '📄' },
{ key: 'styles', label: 'styles.css', icon: '🎨' },
],
},
{ key: 'pkg', label: 'package.json', icon: '📦' },
]
</script>
<template>
<VxTree :data="data" default-expand-all />
</template>Используйте слот #icon для полного контроля:
<template>
<VxTree :data="data">
<template #icon="{ node, expanded }">
<MyIcon :name="node.children ? (expanded ? 'folder-open' : 'folder') : 'file'" />
</template>
</VxTree>
</template>Children Count
Показывайте бейдж с количеством потомков через show-count. Настраивается через слот #count.
<template>
<VxTree :data="data" show-count default-expand-all />
</template>Node Actions
Используйте слот #actions, чтобы добавить к каждому узлу кнопки действий, появляющиеся при наведении.
<template>
<VxTree :data="data" default-expand-all>
<template #actions="{ node }">
<button @click="editNode(node)">Edit</button>
<button @click="deleteNode(node)">Delete</button>
</template>
</VxTree>
</template>Drag & Drop
Включите draggable, чтобы разрешить переупорядочивание узлов. Событие node-drop предоставляет dragNode, dropNode и position (before, inside или after), чтобы вы могли обновить данные.
<script setup>
import { ref } from 'vue'
const data = ref([...])
function onDrop(info) {
console.log(info.dragNode.label, info.position, info.dropNode.label)
// Handle data mutation here
}
</script>
<template>
<VxTree :data="data" draggable @node-drop="onDrop" />
</template>Настройте ручку перетаскивания через слот #drag-handle:
<template>
<VxTree :data="data" draggable>
<template #drag-handle="{ node }">
<MyGripIcon />
</template>
</VxTree>
</template>Lazy Loading
Передайте функцию load, чтобы загружать потомков по требованию. Просто предоставьте данные и загрузчик — дерево сделает всё остальное:
- Во время загрузки отображается индикатор
- Пустые результаты автоматически помечают узел как лист (больше попыток загрузки не будет)
- Ошибки показываются inline с кнопкой Retry
- Уже загруженные узлы кэшируются — повторное раскрытие не приведёт к повторной загрузке
- Используйте
isLeaf: trueна узлах, которые точно являются листьями, чтобы пропустить загрузку
Basic
Click the arrows to load children on demand. "Empty Folder" returns no results and becomes a leaf.
<script setup>
import { ref } from 'vue'
const data = ref([
{ key: 'folder-a', label: 'Folder A' },
{ key: 'folder-b', label: 'Folder B' },
{ key: 'empty', label: 'Empty Folder' },
{ key: 'readme', label: 'readme.txt', isLeaf: true },
])
async function load(node) {
const res = await fetch(`/api/${node.key}/children`)
return await res.json() // return TreeNodeData[]
}
</script>
<template>
<VxTree :data="data" :load="load" />
</template>Error Handling & Retry
Если load выбросит исключение, дерево покажет сообщение об ошибке с кнопкой повтора. Кликните по «Orders» ниже, чтобы увидеть состояние ошибки.
<script setup>
async function load(node) {
const res = await fetch(`/api/${node.key}/children`)
if (!res.ok) throw new Error('Failed to fetch')
return await res.json()
}
</script>
<template>
<!-- Errors show inline with a Retry button -->
<VxTree :data="data" :load="load" />
</template>Editable Labels
Включите editable, чтобы разрешить переименование на месте. Двойной клик по подписи начинает редактирование. Нажмите Enter, чтобы сохранить, Escape, чтобы отменить. Обработайте событие node-edit, чтобы применить изменения.
Double-click a label to rename it
<script setup>
function onEdit({ node, newLabel, oldLabel }) {
node.label = newLabel // apply the change
}
</script>
<template>
<VxTree :data="data" editable @node-edit="onEdit" />
</template>Context Menu
Используйте слот #context-menu, чтобы добавить меню правого клика. Слот предоставляет node и close.
Right-click any node to see the context menu
<template>
<VxTree :data="data">
<template #context-menu="{ node, close }">
<div class="menu">
<button @click="openNode(node); close()">Open</button>
<button @click="renameNode(node); close()">Rename</button>
<button @click="deleteNode(node); close()">Delete</button>
</div>
</template>
</VxTree>
</template>Striped Rows
Добавьте striped для чередования фонов строк, чтобы улучшить читаемость.
<template>
<VxTree :data="data" striped default-expand-all />
</template>Virtual Scrolling
Для больших наборов данных включите virtual, чтобы отрисовывать только видимые узлы. Задайте virtual-height для высоты контейнера и item-height для высоты каждой строки.
50 categories × 20 items = 1,000 nodes
<script setup>
// Generate 1,000 nodes
const data = Array.from({ length: 50 }, (_, i) => ({
key: `cat-${i}`,
label: `Category ${i + 1}`,
children: Array.from({ length: 20 }, (_, j) => ({
key: `cat-${i}-item-${j}`,
label: `Item ${i + 1}-${j + 1}`,
isLeaf: true,
})),
}))
</script>
<template>
<VxTree :data="data" virtual :virtual-height="300" :item-height="30" default-expand-all />
</template>Disabled Nodes
Узлы с disabled: true нельзя выбрать или отметить.
Custom Content
Используйте слот по умолчанию для полностью произвольной отрисовки узла. Получает node, expanded, selected и checkState.
<template>
<VxTree :data="data" default-expand-all>
<template #default="{ node, expanded }">
<span>{{ node.children ? (expanded ? '📂' : '📁') : '📄' }} {{ node.label }}</span>
</template>
</VxTree>
</template>API
VxTree Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | TreeNodeData[] | [] | Данные дерева |
selected | string | number | null | array | null | Выбранный ключ(и) (v-model) |
checked | (string | number)[] | [] | Отмеченные ключи (v-model) |
expanded | (string | number)[] | — | Раскрытые ключи (v-model) |
default-expand-all | boolean | false | Раскрыть всё при монтировании |
default-expanded-keys | (string | number)[] | [] | Ключи для раскрытия при монтировании |
checkable | boolean | false | Показать чекбоксы |
selectable | boolean | true | Включить выбор |
multiple | boolean | false | Разрешить множественный выбор (Ctrl/Cmd+Click) |
show-line | boolean | false | Показать соединительные линии |
line-style | 'solid' | 'dashed' | 'dotted' | 'solid' | Стиль линии |
accordion | boolean | false | Один узел на уровне |
indent | number | 24 | Отступ на уровень (px) |
filter-text | string | '' | Фильтр по подписи |
highlight-filter | boolean | true | Подсвечивать совпадения фильтра |
draggable | boolean | false | Включить drag & drop |
editable | boolean | false | Включить переименование двойным кликом |
show-count | boolean | false | Показать бейдж количества потомков |
striped | boolean | false | Чередующиеся фоны строк |
load | (node) => Promise<TreeNodeData[]> | — | Асинхронный загрузчик потомков |
virtual | boolean | false | Включить виртуальную прокрутку |
virtual-height | number | 400 | Высота виртуального контейнера (px) |
item-height | number | 30 | Высота виртуального элемента (px) |
TreeNodeData
| Property | Type | Required | Description |
|---|---|---|---|
key | string | number | Yes | Уникальный идентификатор |
label | string | Yes | Отображаемый текст |
children | TreeNodeData[] | No | Дочерние узлы |
disabled | boolean | No | Отключить взаимодействие |
isLeaf | boolean | No | Принудительный лист (пропустить ленивую загрузку) |
icon | string | No | Иконка узла (emoji или текст) |
Events
| Event | Payload | Description |
|---|---|---|
update:selected | key | keys[] | Выбор изменён |
update:checked | keys[] | Состояние отметки изменено |
update:expanded | keys[] | Состояние раскрытия изменено |
node-click | node | Клик по узлу |
node-expand | node | Узел раскрыт |
node-collapse | node | Узел свёрнут |
node-drop | { dragNode, dropNode, position } | Узел сброшен (drag & drop) |
node-drag-start | node | Перетаскивание начато |
node-edit | { node, newLabel, oldLabel } | Подпись отредактирована |
node-context-menu | { node, event } | Правый клик по узлу |
node-loaded | node, children | Ленивая загрузка завершена |
Slots
| Slot | Props | Description |
|---|---|---|
default | { node, expanded, selected, checkState } | Пользовательское содержимое узла |
icon | { node, expanded } | Пользовательская иконка узла |
actions | { node } | Кнопки действий при наведении |
drag-handle | { node } | Пользовательская ручка перетаскивания |
count | { node, count } | Пользовательский бейдж количества |
context-menu | { node, close } | Контекстное меню правого клика |
empty | — | Содержимое при пустых данных |