树形数据

查看源码
通过 childrenKey 启用树形表格,配置展开、级联选择策略、缩进与选中结果读取。

树形数据基础

设置 children-key 即启用树形模式,配合 row-key 派生行 id、expand 列渲染折叠按钮。父级缺失字段(如职级)可由 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 级联选择策略

selection 列的 strategy 决定父子勾选联动:cascade 父子级联、isolated 父子独立、leaf 仅叶子可勾(父节点展示为子孙派生态):

成员部门岗位
李勇
产品
团队负责人
王超
市场
团队负责人
陈娜
设计
团队负责人
<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 单选与多选

mode: 'single' 时整树仅单行勾选(通常配合 isolated);multiple 时配合 strategy 联动:

成员部门岗位
李勇
产品
团队负责人
王超
市场
团队负责人
陈娜
设计
团队负责人
<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>

选中结果读取

v-model:row-selection-keys 双向同步选中 id 数组;组件 expose 的 treeSelection 派生 leavesparentshalfSelectedstrictlyChecked 四类业务视图,并提供 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 树形缩进

indentSize 支持字符串、数字(px)与函数三种形态,函数形态可按 row.depth 等上下文动态调整:

成员部门岗位
李勇
产品
团队负责人
杨娜
产品
团队负责人
黄丽
市场
团队负责人
赵军
设计
团队负责人
王超
市场
团队负责人
周军
产品
团队负责人
徐涛
市场
团队负责人
孙伟
设计
团队负责人
陈娜
设计
团队负责人
李伟
产品
团队负责人
王敏
市场
团队负责人
陈强
设计
团队负责人
<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-expanded 在未提供 expanded / expanded-keys(受控优先)时决定初始展开:true 展开全部父级行、number 展开 depth 小于该值的父级行(与 expandToDepth(n) 同义)、函数 (row, depth) => boolean 按行与深度自定义,无需手写收集父级 id:

成员部门岗位
李勇
产品
团队负责人
杨娜
产品
团队负责人
周伟
产品
团队负责人
李勇
产品
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>

展开行为控制

expand 列的 buttonPropstoggleAllButtonProps 分别定制单元格与表头按钮(接收 isExpanded/isAllExpanded);expand-on-row-click 整行触发展开;expose 的 expandToDepth(n) 按层展开、collapseAll() 收起全部,v-model:expanded-keys 同步展开 id:

成员部门岗位
李勇
产品
团队负责人
王超
市场
团队负责人
陈娜
设计
团队负责人
<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