Tree Data
Tree Data Basics
Setting children-key enables tree mode. Combine with row-key to derive row ids and an expand column to render collapse buttons. Missing fields on parent rows (e.g. level) can be filled by emptyCell:
| 成员 | 部门 | 岗位 | 职级 | |
|---|---|---|---|---|
李勇 | 产品 | 团队负责人 | — | |
王超 | 市场 | 团队负责人 | — | |
陈娜 | 设计 | 团队负责人 | — |
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const treeData = makePeopleTree(3, 3, 2)
const columns: DataTableColumn<Person>[] = [
{ type: 'expand' },
{ accessorKey: 'name', header: '成员' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' },
{ accessorKey: 'level', header: '职级', emptyCell: '—' }
]
</script>
<template>
<MDataTable :data="treeData" :columns="columns" children-key="children" row-key="id" :ui="{ root: 'max-h-[50vh]' }" />
</template>
strategy Cascade Selection Strategy
The strategy of the selection column determines parent–child check synchronization: cascade for parent–child cascade, isolated for independent selection, leaf for leaf-only selection (parent nodes show a derived state from their descendants):
| 成员 | 部门 | 岗位 | ||
|---|---|---|---|---|
李勇 | 产品 | 团队负责人 | ||
王超 | 市场 | 团队负责人 | ||
陈娜 | 设计 | 团队负责人 |
<script setup lang="ts">
import type { DataTableColumn, DataTableSelectionColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const treeData = makePeopleTree(3, 3, 2)
const strategy = ref<DataTableSelectionColumn['strategy']>('cascade')
const columns = computed<DataTableColumn<Person>[]>(() => [
{ type: 'selection', strategy: strategy.value },
{ type: 'expand' },
{ accessorKey: 'name', header: '成员' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
])
</script>
<template>
<div class="flex flex-col gap-3">
<USelect
v-model="strategy"
:items="[
{ label: 'cascade 父子级联', value: 'cascade' },
{ label: 'isolated 父子独立', value: 'isolated' },
{ label: 'leaf 仅叶子可勾', value: 'leaf' }
]"
value-key="value"
size="xs"
class="w-56"
/>
<MDataTable :data="treeData" :columns="columns" children-key="children" row-key="id" />
</div>
</template>
mode Single and Multiple Selection
With mode: 'single', the entire tree allows only one row to be checked at a time (usually paired with isolated); with multiple, the strategy drives the cascade behavior:
| 成员 | 部门 | 岗位 | ||
|---|---|---|---|---|
李勇 | 产品 | 团队负责人 | ||
王超 | 市场 | 团队负责人 | ||
陈娜 | 设计 | 团队负责人 |
<script setup lang="ts">
import type { DataTableColumn, DataTableSelectionColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const treeData = makePeopleTree(3, 3, 2)
const mode = ref<DataTableSelectionColumn['mode']>('multiple')
const columns = computed<DataTableColumn<Person>[]>(() => [
{ type: 'selection', mode: mode.value, strategy: mode.value === 'single' ? 'isolated' : 'cascade' },
{ type: 'expand' },
{ accessorKey: 'name', header: '成员' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
])
</script>
<template>
<div class="flex flex-col gap-3">
<USwitch
:model-value="mode === 'single'"
label="single 单选"
@update:model-value="mode = $event ? 'single' : 'multiple'"
/>
<MDataTable :data="treeData" :columns="columns" children-key="children" row-key="id" />
</div>
</template>
Reading Selection Results
v-model:row-selection-keys bi-directionally syncs the selected id array; the component-exposed treeSelection derives four business views — leaves, parents, halfSelected and strictlyChecked — and provides clearSelection():
| 成员 | 部门 | ||
|---|---|---|---|
李勇 | 产品 | ||
王超 | 市场 | ||
陈娜 | 设计 |
{}<script setup lang="ts">
import type { DataTableColumn, DataTableExposed } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const treeData = makePeopleTree(3, 3, 2)
const tableRef = useTemplateRef<DataTableExposed<Person>>('tableRef')
const selectionKeys = ref<string[]>([])
const names = (rows: Person[]) => rows.map(p => p.name)
const result = computed(() => {
const r = tableRef.value?.treeSelection
if (!r) return {}
return {
selectionKeys: selectionKeys.value,
leaves: names(r.leaves),
parents: names(r.parents),
halfSelected: names(r.halfSelected),
strictlyChecked: names(r.strictlyChecked)
}
})
const columns: DataTableColumn<Person>[] = [
{ type: 'selection', strategy: 'cascade' },
{ type: 'expand' },
{ accessorKey: 'name', header: '成员' },
{ accessorKey: 'department', header: '部门' }
]
</script>
<template>
<div class="flex flex-col gap-3">
<UButton size="xs" variant="soft" class="self-start" @click="tableRef?.clearSelection()">
清空选中
</UButton>
<MDataTable
ref="tableRef"
v-model:row-selection-keys="selectionKeys"
:data="treeData"
:columns="columns"
children-key="children"
row-key="id"
/>
<pre class="text-xs p-3 rounded-md bg-muted overflow-auto">{{ result }}</pre>
</div>
</template>
indentSize Tree Indentation
indentSize supports string, number (px) and function forms; the function form can dynamically adjust based on row.depth and other context:
| 成员 | 部门 | 岗位 | |
|---|---|---|---|
李勇 | 产品 | 团队负责人 | |
杨娜 | 产品 | 团队负责人 | |
黄丽 | 市场 | 团队负责人 | |
赵军 | 设计 | 团队负责人 | |
王超 | 市场 | 团队负责人 | |
周军 | 产品 | 团队负责人 | |
徐涛 | 市场 | 团队负责人 | |
孙伟 | 设计 | 团队负责人 | |
陈娜 | 设计 | 团队负责人 | |
李伟 | 产品 | 团队负责人 | |
王敏 | 市场 | 团队负责人 | |
陈强 | 设计 | 团队负责人 | |
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const treeData = makePeopleTree(3, 3, 3)
const columns: DataTableColumn<Person>[] = [
{ type: 'expand' },
{ accessorKey: 'name', header: '成员' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<MDataTable :default-expanded="1" :data="treeData" :columns="columns" children-key="children" row-key="id" />
</template>
defaultExpanded Default Expansion
default-expanded determines the initial expansion state when neither expanded nor expanded-keys (controlled mode takes priority) is provided: true expands all parent rows; a number expands parent rows whose depth is less than that value (equivalent to expandToDepth(n)); a function (row, depth) => boolean customizes expansion per row and depth, without having to manually collect parent ids:
| 成员 | 部门 | 岗位 | |
|---|---|---|---|
李勇 | 产品 | 团队负责人 | |
杨娜 | 产品 | 团队负责人 | |
周伟 | 产品 | 团队负责人 | |
李勇 | 产品 | UI 设计师 | |
王超 | 市场 | 运营专员 | |
陈娜 | 设计 | 全栈工程师 | |
徐敏 | 市场 | 团队负责人 | |
杨娜 | 产品 | 全栈工程师 | |
黄丽 | 市场 | 数据分析师 | |
赵军 | 设计 | 后端工程师 | |
孙强 | 设计 | 团队负责人 | |
周军 | 产品 | 后端工程师 | |
徐涛 | 市场 | 产品经理 | |
孙伟 | 设计 | 前端工程师 | |
黄丽 | 市场 | 团队负责人 | |
李强 | 产品 | 团队负责人 | |
杨军 | 产品 | 后端工程师 | |
黄涛 | 市场 | 产品经理 | |
赵伟 | 设计 | 前端工程师 | |
王洋 | 市场 | 团队负责人 | |
周伟 | 产品 | 前端工程师 | |
徐敏 | 市场 | UI 设计师 | |
孙强 | 设计 | 运营专员 | |
陈明 | 设计 | 团队负责人 | |
李强 | 产品 | 运营专员 | |
王洋 | 市场 | 全栈工程师 | |
陈明 | 设计 | 数据分析师 | |
赵军 | 设计 | 团队负责人 | |
杨明 | 产品 | 团队负责人 | |
周强 | 产品 | 运营专员 | |
徐洋 | 市场 | 全栈工程师 | |
孙明 | 设计 | 数据分析师 | |
黄芳 | 市场 | 团队负责人 | |
李明 | 产品 | 数据分析师 | |
王芳 | 市场 | 后端工程师 | |
陈静 | 设计 | 产品经理 | |
赵静 | 设计 | 团队负责人 | |
杨静 | 产品 | 产品经理 | |
黄磊 | 市场 | 前端工程师 | |
赵勇 | 设计 | UI 设计师 | |
王超 | 市场 | 团队负责人 | |
周军 | 产品 | 团队负责人 | |
李明 | 产品 | 团队负责人 | |
杨强 | 产品 | 运营专员 | |
黄洋 | 市场 | 全栈工程师 | |
赵明 | 设计 | 数据分析师 | |
王芳 | 市场 | 团队负责人 | |
周明 | 产品 | 数据分析师 | |
徐芳 | 市场 | 后端工程师 | |
孙静 | 设计 | 产品经理 | |
陈静 | 设计 | 团队负责人 | |
李静 | 产品 | 产品经理 | |
王磊 | 市场 | 前端工程师 | |
陈勇 | 设计 | UI 设计师 | |
徐涛 | 市场 | 团队负责人 | |
杨静 | 产品 | 团队负责人 | |
周静 | 产品 | 产品经理 | |
徐磊 | 市场 | 前端工程师 | |
孙勇 | 设计 | UI 设计师 | |
黄磊 | 市场 | 团队负责人 | |
李勇 | 产品 | UI 设计师 | |
王超 | 市场 | 运营专员 | |
陈娜 | 设计 | 全栈工程师 | |
赵勇 | 设计 | 团队负责人 | |
杨娜 | 产品 | 全栈工程师 | |
黄丽 | 市场 | 数据分析师 | |
赵军 | 设计 | 后端工程师 | |
孙伟 | 设计 | 团队负责人 | |
周勇 | 产品 | 团队负责人 | |
李娜 | 产品 | 全栈工程师 | |
王丽 | 市场 | 数据分析师 | |
陈军 | 设计 | 后端工程师 | |
徐超 | 市场 | 团队负责人 | |
杨军 | 产品 | 后端工程师 | |
黄涛 | 市场 | 产品经理 | |
赵伟 | 设计 | 前端工程师 | |
孙娜 | 设计 | 团队负责人 | |
周伟 | 产品 | 前端工程师 | |
徐敏 | 市场 | UI 设计师 | |
孙强 | 设计 | 运营专员 | |
陈娜 | 设计 | 团队负责人 | |
李伟 | 产品 | 团队负责人 | |
杨勇 | 产品 | 团队负责人 | |
周娜 | 产品 | 全栈工程师 | |
徐丽 | 市场 | 数据分析师 | |
孙军 | 设计 | 后端工程师 | |
黄超 | 市场 | 团队负责人 | |
李军 | 产品 | 后端工程师 | |
王涛 | 市场 | 产品经理 | |
陈伟 | 设计 | 前端工程师 | |
赵娜 | 设计 | 团队负责人 | |
杨伟 | 产品 | 前端工程师 | |
黄敏 | 市场 | UI 设计师 | |
赵强 | 设计 | 运营专员 | |
王敏 | 市场 | 团队负责人 | |
周娜 | 产品 | 团队负责人 | |
李伟 | 产品 | 前端工程师 | |
王敏 | 市场 | UI 设计师 | |
陈强 | 设计 | 运营专员 | |
徐丽 | 市场 | 团队负责人 | |
杨强 | 产品 | 运营专员 | |
黄洋 | 市场 | 全栈工程师 | |
赵明 | 设计 | 数据分析师 | |
孙军 | 设计 | 团队负责人 | |
周明 | 产品 | 数据分析师 | |
徐芳 | 市场 | 后端工程师 | |
孙静 | 设计 | 产品经理 | |
陈强 | 设计 | 团队负责人 | |
李军 | 产品 | 团队负责人 | |
杨明 | 产品 | 数据分析师 | |
黄芳 | 市场 | 后端工程师 | |
赵静 | 设计 | 产品经理 | |
王涛 | 市场 | 团队负责人 | |
周静 | 产品 | 产品经理 | |
徐磊 | 市场 | 前端工程师 | |
孙勇 | 设计 | UI 设计师 | |
陈伟 | 设计 | 团队负责人 | |
李勇 | 产品 | UI 设计师 | |
王超 | 市场 | 运营专员 | |
陈娜 | 设计 | 全栈工程师 | |
<script setup lang="ts">
import type { DataTableColumn, DataTableProps } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const props = withDefaults(defineProps<{
mode?: 'all' | 'depth1' | 'depth2' | 'fn'
}>(), { mode: 'all' })
const treeData = makePeopleTree(3, 3, 3)
const columns: DataTableColumn<Person>[] = [
{ type: 'expand' },
{ accessorKey: 'name', header: '成员' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
const defaultExpanded = computed<DataTableProps<Person>['defaultExpanded']>(() => {
if (props.mode === 'depth1') return 1
if (props.mode === 'depth2') return 2
if (props.mode === 'fn') return row => row.department === '设计'
return true
})
</script>
<template>
<MDataTable
:key="props.mode"
:default-expanded="defaultExpanded"
:data="treeData"
:columns="columns"
children-key="children"
row-key="id"
class="max-h-150"
/>
</template>
Expansion Behavior Control
The expand column's buttonProps and toggleAllButtonProps customize the cell and header buttons respectively (receiving isExpanded / isAllExpanded); expand-on-row-click triggers expansion on a full-row click; the exposed expandToDepth(n) expands by layer and collapseAll() collapses everything; v-model:expanded-keys syncs the expanded id array:
| 成员 | 部门 | 岗位 | |
|---|---|---|---|
李勇 | 产品 | 团队负责人 | |
王超 | 市场 | 团队负责人 | |
陈娜 | 设计 | 团队负责人 |
<script setup lang="ts">
import type { DataTableColumn, DataTableExposed } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const treeData = makePeopleTree(3, 3, 3)
const tableRef = useTemplateRef<DataTableExposed<Person>>('tableRef')
const expandOnRowClick = ref(false)
const columns: DataTableColumn<Person>[] = [
{
type: 'expand',
buttonProps: ctx => ({
icon: ctx.isExpanded ? 'i-lucide-folder-open' : 'i-lucide-folder',
color: ctx.isExpanded ? 'primary' : 'neutral',
variant: 'soft'
}),
toggleAllButtonProps: ctx => ({
icon: ctx.isAllExpanded ? 'i-lucide-chevrons-down-up' : 'i-lucide-chevrons-up-down',
variant: 'soft',
color: 'primary'
})
},
{ accessorKey: 'name', header: '成员' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<div class="flex flex-col gap-3">
<div class="flex flex-wrap items-center gap-2">
<USwitch v-model="expandOnRowClick" label="expandOnRowClick" />
<UButton size="xs" variant="soft" @click="tableRef?.expandToDepth(1)">
展开 1 级
</UButton>
<UButton size="xs" variant="soft" @click="tableRef?.expandToDepth(2)">
展开 2 级
</UButton>
<UButton size="xs" variant="soft" @click="tableRef?.collapseAll()">
收起全部
</UButton>
</div>
<MDataTable
ref="tableRef"
:data="treeData"
:columns="columns"
children-key="children"
row-key="id"
:expand-on-row-click="expandOnRowClick"
/>
</div>
</template>