Tree Data

View source
Enable tree-mode tables via childrenKey — configure expansion, cascade selection strategies, indentation and read selection results.

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>
Copyright © 2025 - 2026 YiXuan - MIT License