Дерево
Преобразование плоских данных в дерево и распространение отметок выполняются чистыми функциями в core; компонент отвечает только за рендеринг и работу с клавиатурой.
Базовое использование
<script setup>
import { ref } from 'vue'
const treeData = [
{ key: 'a', label: 'Фронтенд', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Бэкенд', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Девопс', disabled: true },
]
</script>
<template>
<!-- Базовое использование -->
<LhTree :data="treeData" :default-expanded-keys="['a']" />
</template><script>
export default {
data() {
return {
treeData: [
{ key: 'a', label: 'Фронтенд', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Бэкенд', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Девопс', disabled: true },
],
}
},
}
</script>
<template>
<!-- Базовое использование -->
<LhTree :data="treeData" :default-expanded-keys="['a']" />
</template>Отметки (каскадные)
Отметка родителя каскадно распространяется на всех потомков; при выборе всех дочерних узлов родитель отмечается автоматически, а частичный выбор показывается неопределённым состоянием (indeterminate).
<script setup lang="ts">
const treeData = [
{ key: 'a', label: 'Фронтенд', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Бэкенд', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Девопс', disabled: true },
]
</script>
<template>
<!-- Каскадные отметки -->
<LhTree :data="treeData" show-checkbox :default-expanded-keys="['a', 'b']" />
</template><script>
export default {
data() {
return {
treeData: [
{ key: 'a', label: 'Фронтенд', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Бэкенд', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Девопс', disabled: true },
],
}
},
}
</script>
<template>
<!-- Каскадные отметки -->
<LhTree :data="treeData" show-checkbox :default-expanded-keys="['a', 'b']" />
</template>Ленивая загрузка
При включённом lazy разворачивание незагруженного узла вызывает колбэк load (resolve принимает дочерние узлы и разворачивает их, reject отменяет); стрелка загрузки показывает вращающееся состояние. У узлов, которые требуется загружать, необходимо явно указать isLeaf: false (иначе узел без children будет считаться листом и стрелка развёртки не покажется); если загруженные дочерние узлы сами являются ветвями, ожидающими загрузки, для них также указывайте isLeaf: false, а настоящим листьям — isLeaf: true.
<script setup>
import { ref } from 'vue'
const lazyData = ref([
{ key: 'lazy', label: 'Нажмите, чтобы загрузить', isLeaf: false },
])
function loadNodes(node, resolve, reject) {
// Имитация асинхронного запроса
setTimeout(() => {
if (node.key === 'lazy') {
resolve([
{ key: 'lazy-1', label: 'Дочерний узел один', isLeaf: true },
{ key: 'lazy-2', label: 'Дочерний узел два', isLeaf: true },
])
} else {
reject(new Error('nothing to load'))
}
}, 600)
}
</script>
<template>
<!-- Ленивая загрузка -->
<LhTree :data="lazyData" lazy :load="loadNodes" />
</template><script>
export default {
data() {
return {
lazyData: [{ key: 'lazy', label: 'Нажмите, чтобы загрузить', isLeaf: false }],
}
},
methods: {
// Имитация асинхронного запроса
loadNodes(node, resolve, reject) {
setTimeout(() => {
if (node.key === 'lazy') {
resolve([
{ key: 'lazy-1', label: 'Дочерний узел один', isLeaf: true },
{ key: 'lazy-2', label: 'Дочерний узел два', isLeaf: true },
])
} else {
reject(new Error('nothing to load'))
}
}, 600)
},
},
}
</script>
<template>
<!-- Ленивая загрузка -->
<LhTree :data="lazyData" lazy :load="loadNodes" />
</template>Без связи родитель-потомок (checkStrictly)
При включённом checkStrictly отметка действует только на сам узел: без каскада потомкам, без обратного хода к предкам, без неопределённого состояния.
<script setup lang="ts">
const treeData = [
{ key: 'a', label: 'Фронтенд', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Бэкенд', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Девопс', disabled: true },
]
</script>
<template>
<!-- Без связи родитель-потомок -->
<LhTree :data="treeData" show-checkbox check-strictly :default-expanded-keys="['a', 'b']" />
</template><script>
export default {
data() {
return {
treeData: [
{ key: 'a', label: 'Фронтенд', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Бэкенд', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Девопс', disabled: true },
],
}
},
}
</script>
<template>
<!-- Без связи родитель-потомок -->
<LhTree :data="treeData" show-checkbox check-strictly :default-expanded-keys="['a', 'b']" />
</template>Выбор и управляемые ключи
multiple разрешает множественный выбор; expand-on-click-node разворачивает/свёртывает узел по щелчку; default-selected-keys / v-model:selected-keys — управляемый выбор; default-checked-keys / v-model:checked-keys — управляемые отметки; default-expanded-keys / v-model:expanded-keys — управляемое развёртывание; disabled отключает всё дерево.
Выбрано: [ "x" ] | Отмечено: [ "x-1" ]
<script setup>
import { ref } from 'vue'
const selKeys = ref(['x'])
const chkKeys = ref(['x-1'])
const ctrlData = [
{ key: 'x', label: 'Дизайн', children: [
{ key: 'x-1', label: 'Цвет' },
{ key: 'x-2', label: 'Шрифты' },
] },
{ key: 'y', label: 'Инженерия' },
]
</script>
<template>
<!-- Управляемые выбор и отметки -->
<LhTree
v-model:selected-keys="selKeys"
v-model:checked-keys="chkKeys"
:data="ctrlData"
multiple
expand-on-click-node
:default-expanded-keys="['x']"
:default-selected-keys="['x']"
:default-checked-keys="['x-1']"
/>
<p class="demo-tree-log">Выбрано: {{ selKeys }} | Отмечено: {{ chkKeys }}</p>
<!-- Отключение всего дерева -->
<LhTree :data="ctrlData" disabled />
</template><script>
export default {
data() {
return {
selKeys: ['x'],
chkKeys: ['x-1'],
ctrlData: [
{ key: 'x', label: 'Дизайн', children: [
{ key: 'x-1', label: 'Цвет' },
{ key: 'x-2', label: 'Шрифты' },
] },
{ key: 'y', label: 'Инженерия' },
],
}
},
}
</script>
<template>
<!-- Управляемые выбор и отметки -->
<LhTree
v-model:selected-keys="selKeys"
v-model:checked-keys="chkKeys"
:data="ctrlData"
multiple
expand-on-click-node
:default-expanded-keys="['x']"
:default-selected-keys="['x']"
:default-checked-keys="['x-1']"
/>
<p class="demo-tree-log">Выбрано: {{ selKeys }} | Отмечено: {{ chkKeys }}</p>
<!-- Отключение всего дерева -->
<LhTree :data="ctrlData" disabled />
</template>API
LhTree Props
| Свойство | Описание | Тип | Значение по умолчанию |
|---|---|---|---|
| data | данные дерева | [LhTreeNode](#type-LhTreeNode)[] | — |
| showCheckbox | показывать чекбоксы | boolean | false |
| multiple | множественный выбор (щелчок по узлу переключает выбор, а не заменяет его) | boolean | false |
| expandOnClickNode | разворачивать/свёртывать нелистовой узел по щелчку | boolean | true |
| defaultExpandedKeys | развёрнутые по умолчанию ключи (неуправляемые) | string[] | () => [] |
| expandedKeys | управляемые развёрнутые ключи | string[] | — |
| defaultSelectedKeys | выбранные по умолчанию ключи (неуправляемые) | string[] | () => [] |
| selectedKeys | управляемые выбранные ключи | string[] | — |
| defaultCheckedKeys | отмеченные по умолчанию ключи (неуправляемые) | string[] | () => [] |
| checkedKeys | управляемые отмеченные ключи | string[] | — |
| checkStrictly | отметки без связи родитель-потомок (действует только на сам узел, без каскада и неопределённого состояния) | boolean | false |
| lazy | режим ленивой загрузки (вызывает load при разворачивании незагруженного узла) | boolean | false |
| load | колбэк ленивой загрузки (resolve принимает дочерние узлы и разворачивает, reject отменяет) | (node: LhTreeNode, resolve: (children: LhTreeNode[]) => void, reject: (reason?: unknown) => void) => void | — |
| disabled | полное отключение | boolean | false |
| sx | свойства быстрой раскладки sx | LhSxProps | — |
Events
| Событие | Описание | Колбэк |
|---|---|---|
| update:expandedKeys | вызывается при изменении списка развёрнутых ключей, использовать совместно с v-model:expanded-keys | (keys: string[]) |
| update:selectedKeys | вызывается при изменении списка выбранных ключей, использовать совместно с v-model:selected-keys | (keys: string[]) |
| update:checkedKeys | вызывается при изменении списка отмеченных ключей, использовать совместно с v-model:checked-keys | (keys: string[]) |
| select | вызывается при выборе узла щелчком / переключении выбора | (key: string, node: LhTreeNode) |
| check | вызывается после изменения отметок | (keys: string[], node: LhTreeNode) |
| expand | вызывается после разворачивания / сворачивания узла | (key: string, expanded: boolean) |
| nodeLoad | вызывается после возврата дочерних узлов при ленивой загрузке | (node: LhTreeNode, children: LhTreeNode[]) |
Слоты: node (scoped-слот { node, level, expanded, selected }), empty.
LhTreeNode
| Поле | Описание | Тип |
|---|---|---|
| key | уникальный ключ | string |
| label | отображаемый текст | string |
| children | дочерние узлы | LhTreeNode[] |
| disabled | отключено | boolean |
| isLeaf | является ли листом (если не указано, выводится по children) | boolean |
Доступность
role="tree"+role="treeitem", всеaria-level/aria-expanded/aria-selected/aria-disabledэкспонируются полностью- Чекбоксы:
role="checkbox"+aria-checked(неопределённое состояние —mixed) - Клавиатура (паттерн APG tree): ↑/↓ — перемещение между узлами (с пропуском отключённых, зацикленное), → — разворачивает или входит в первого потомка, ← — сворачивает или возвращается к родителю, Home/End — к первому/последнему, Enter/Space — активация; roving tabindex
Определения типов
Ниже приведены структуры типов TypeScript, экспортируемые этим компонентом (автоматически извлечены из packages/components/tree/src/types.ts и исходников @lh-ui/share/core/*; раскройте каждый пункт, чтобы увидеть полное определение; описания свойств — в таблице Props выше):
LhTreeProps
export interface LhTreeProps {
/** данные дерева */
data: LhTreeNode[]
/** показывать чекбоксы */
showCheckbox?: boolean
/** множественный выбор (щелчок по узлу переключает выбор, а не заменяет его) */
multiple?: boolean
/** разворачивать/сворачивать нелистовой узел по щелчку */
expandOnClickNode?: boolean
/** развёрнутые по умолчанию ключи (неуправляемые) */
defaultExpandedKeys?: string[]
/** управляемые развёрнутые ключи */
expandedKeys?: string[]
/** выбранные по умолчанию ключи (неуправляемые) */
defaultSelectedKeys?: string[]
/** управляемые выбранные ключи */
selectedKeys?: string[]
/** отмеченные по умолчанию ключи (неуправляемые) */
defaultCheckedKeys?: string[]
/** управляемые отмеченные ключи */
checkedKeys?: string[]
/** отметки без связи родитель-потомок (действует только на сам узел, без каскада и неопределённого состояния) */
checkStrictly?: boolean
/** режим ленивой загрузки: вызывает load при разворачивании незагруженного нелистового узла */
lazy?: boolean
/** колбэк ленивой загрузки: resolve принимает дочерние узлы и разворачивает, reject (или исключение) отменяет */
load?: (
node: LhTreeNode,
resolve: (children: LhTreeNode[]) => void,
reject: (reason?: unknown) => void,
) => void
/** полное отключение */
disabled?: boolean
/** sx: свойства быстрой раскладки */
sx?: LhSxProps
}LhFlatTreeNode (из @lh-ui/share/core/tree)
/** уплощённый узел (для рендеринга) */
export interface LhFlatTreeNode {
node: LhTreeNode
key: string
level: number
parentKey: string | null
/** цепочка предков (без самого узла) */
ancestorKeys: string[]
/** является ли листом */
isLeaf: boolean
}LhTreeNode (из @lh-ui/share/core/tree)
/** данные узла дерева */
export interface LhTreeNode {
/** уникальный ключ */
key: string
/** отображаемый текст */
label: string
/** дочерние узлы */
children?: LhTreeNode[]
/** отключено */
disabled?: boolean
/** является ли листом (если не указано, выводится по children) */
isLeaf?: boolean
}LhTreeCheckState (из @lh-ui/share/core/tree)
/** состояние отметки */
export type LhTreeCheckState = 'checked' | 'indeterminate' | 'unchecked'