特殊列

View source
选择列、索引列、行固定列与操作列的配置,含树形选择策略、动态按钮与二次确认。

概述

数据列用 accessorKey 绑定字段;特殊列通过 type 区分,无需 accessorKey

type用途
selection行选择(单选/多选,树形支持级联策略)
index行序号
expand树形展开(见树形数据
row-pinning行固定到顶部/底部
actions行操作按钮
所有特殊列同样支持 visibilitypinableresizablefixedsizealign

选择列

mode 单选 / 多选

mode 作用:默认 multiple 自动渲染表头与单元格复选框、v-model:row-selection 同步选中 id 字典;single 关闭多选并显示自定义 header,选中态始终至多保留一个:

姓名部门岗位
李勇
产品
UI 设计师
王超
市场
运营专员
陈娜
设计
全栈工程师
刘丽
运营
数据分析师
杨军
研发
后端工程师
黄涛
产品
产品经理
rowSelection: {}
<script setup lang="ts">
import type { DataTableColumn, RowSelectionState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const props = defineProps<{
  mode: 'multiple' | 'single'
}>()

const data = makePeople(6)
const selection = ref<RowSelectionState>({})

const columns = computed<DataTableColumn<Person>[]>(() => [
  props.mode === 'single'
    ? { type: 'selection', mode: 'single', header: '选择', size: 80 }
    : { type: 'selection' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'role', header: '岗位' }
])
</script>

<template>
  <div class="flex flex-col gap-3">
    <MDataTable v-model:row-selection="selection" row-key="id" :columns="columns" :data="data" />
    <pre class="text-xs p-3 rounded-md bg-muted overflow-auto">rowSelection: {{ selection }}</pre>
  </div>
</template>

strategy 树形选择策略

strategy 在树形模式(children-key)下生效,控制父子勾选关系:cascade 父子级联、isolated 独立勾选、leaf 仅叶子可选(父节点展示为派生态):

cascade(父子级联)
姓名部门
李勇
产品
王超
市场
陈娜
设计
isolated(独立勾选)
姓名部门
李勇
产品
王超
市场
陈娜
设计
leaf(仅叶子)
姓名部门
李勇
产品
王超
市场
陈娜
设计
<script setup lang="ts">
import type { DataTableColumn, DataTableTreeSelectionStrategy, RowSelectionState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const treeData = makePeopleTree(3, 3)
const cascade = ref<RowSelectionState>({})
const isolated = ref<RowSelectionState>({})
const leaf = ref<RowSelectionState>({})

const treeBase: DataTableColumn<Person>[] = [
  { type: 'expand' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' }
]
function strategyColumns(strategy: DataTableTreeSelectionStrategy): DataTableColumn<Person>[] {
  return [{ type: 'selection', strategy }, ...treeBase]
}
</script>

<template>
  <div class="grid grid-cols-1 md:grid-cols-3 gap-3">
    <div class="flex flex-col gap-2">
      <div class="text-xs text-muted">
        cascade(父子级联)
      </div>
      <MDataTable
        v-model:row-selection="cascade"
        row-key="id"
        children-key="children"
        :columns="strategyColumns('cascade')"
        :data="treeData"
      />
    </div>
    <div class="flex flex-col gap-2">
      <div class="text-xs text-muted">
        isolated(独立勾选)
      </div>
      <MDataTable
        v-model:row-selection="isolated"
        row-key="id"
        children-key="children"
        :columns="strategyColumns('isolated')"
        :data="treeData"
      />
    </div>
    <div class="flex flex-col gap-2">
      <div class="text-xs text-muted">
        leaf(仅叶子)
      </div>
      <MDataTable
        v-model:row-selection="leaf"
        row-key="id"
        children-key="children"
        :columns="strategyColumns('leaf')"
        :data="treeData"
      />
    </div>
  </div>
</template>

checkboxProps 函数式配置

checkboxProps 接收上下文返回 props:可按 scope 区分表头/单元格、按 isLeafAggregate 区分父行派生态、按行数据派生 disabled

姓名状态
李勇
leave
王超
offboarded
陈娜
active
<script setup lang="ts">
import type { DataTableColumn, RowSelectionState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const treeData = makePeopleTree(3, 3)
const selection = ref<RowSelectionState>({
  P0101: true,
  P0102: true,
  P0201: true,
  P0202: true,
  P0203: true
})

const columns: DataTableColumn<Person>[] = [
  {
    type: 'selection',
    strategy: 'leaf',
    checkboxProps: (ctx) => {
      if (ctx.scope === 'header') return { color: 'success' }
      if (ctx.isLeafAggregate) return { color: 'neutral' }
      return {
        disabled: ctx.cellContext.row.original.status === 'offboarded',
        color: 'primary'
      }
    }
  },
  { type: 'expand' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'status', header: '状态' }
]
</script>

<template>
  <MDataTable
    v-model:row-selection="selection"
    row-key="id"
    children-key="children"
    :columns="columns"
    :data="treeData"
  />
</template>

rowSelectionKeys 与 treeSelection

v-model:row-selection-keys 用数组形态外部受控;组件 expose 的 treeSelection 派生 leavesparentshalfSelectedstrictlyChecked 等业务视图(通过 useTemplateRef 访问):

姓名部门
李勇
产品
王超
市场
陈娜
设计
treeSelection: {
  "leaves": [],
  "parents": [],
  "halfSelected": []
}
<script setup lang="ts">
import type { DataTableColumn, DataTableExposed } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const treeData = makePeopleTree(3, 3)
const selectionKeys = ref<string[]>([])
const tableRef = useTemplateRef<DataTableExposed<Person>>('tableRef')

const derived = computed(() => {
  const r = tableRef.value?.treeSelection
  return {
    leaves: r?.leaves.map(p => p.name) ?? [],
    parents: r?.parents.map(p => p.name) ?? [],
    halfSelected: r?.halfSelected.map(p => p.name) ?? []
  }
})

const columns: DataTableColumn<Person>[] = [
  { type: 'selection' },
  { type: 'expand' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' }
]
</script>

<template>
  <div class="flex flex-col gap-3">
    <MDataTable
      ref="tableRef"
      v-model:row-selection-keys="selectionKeys"
      row-key="id"
      children-key="children"
      :columns="columns"
      :data="treeData"
    />
    <pre class="text-xs p-3 rounded-md bg-muted overflow-auto">treeSelection: {{ derived }}</pre>
  </div>
</template>

索引列

type: 'index' 默认渲染 # 表头,行号随排序、分页自动重新计算,可通过 header 自定义:

姓名
部门
薪资
1
李勇
产品
8257
2
王超
市场
8514
3
陈娜
设计
8771
4
刘丽
运营
9028
5
杨军
研发
9285
<script setup lang="ts">
import type { DataTableColumn, PaginationState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(80)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })

const columns: DataTableColumn<Person>[] = [
  { type: 'index', header: '' },
  { accessorKey: 'name', header: '姓名', sortable: true },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'salary', header: '薪资', align: 'right', sortable: true }
]
</script>

<template>
  <MDataTable v-model:pagination="pagination" row-key="id" :columns="columns" :data="data" sortable />
</template>

行固定列

position 顶部 / 底部固定

type: 'row-pinning'positiontopbottom,把行钉到表头或表尾,v-model:row-pinning 暴露 topbottom 两组 id:

ID姓名部门岗位
P0001
李勇
产品
UI 设计师
P0002
王超
市场
运营专员
P0003
陈娜
设计
全栈工程师
P0005
杨军
研发
后端工程师
P0006
黄涛
产品
产品经理
P0007
赵伟
市场
前端工程师
P0004
刘丽
运营
数据分析师
P0008
吴敏
设计
UI 设计师
<script setup lang="ts">
import type { DataTableColumn, RowPinningState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(8)
const pinning = ref<RowPinningState>({ top: ['P0001'], bottom: ['P0004', 'P0008'] })

const columns: DataTableColumn<Person>[] = [
  { type: 'row-pinning', position: 'top', header: '' },
  { accessorKey: 'id', header: 'ID' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'role', header: '岗位' }
]
</script>

<template>
  <MDataTable v-model:row-pinning="pinning" row-key="id" :columns="columns" :data="data" :ui="{ root: 'max-h-65' }" />
</template>

buttonProps 动态固定按钮

buttonProps 接收 pinned 状态返回 iconcolorvariant,区分已固定与未固定行:

姓名部门岗位
李勇
产品
UI 设计师
王超
市场
运营专员
陈娜
设计
全栈工程师
刘丽
运营
数据分析师
杨军
研发
后端工程师
黄涛
产品
产品经理
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(6)

const columns: DataTableColumn<Person>[] = [
  {
    type: 'row-pinning',
    buttonProps: ({ pinned }) => pinned
      ? { icon: 'i-lucide-pin-off', color: 'primary', variant: 'outline' }
      : { icon: 'i-lucide-pin', color: 'neutral', variant: 'ghost' }
  },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'role', header: '岗位' }
]
</script>

<template>
  <MDataTable row-key="id" :columns="columns" :data="data" />
</template>

操作列

maxInline 内联与折叠

maxInline 控制平铺按钮数,超出部分折叠到溢出菜单;action.divider 在菜单中插入分隔线:

姓名部门操作
李勇
产品
王超
市场
陈娜
设计
刘丽
运营
杨军
研发
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(5)
const toast = useToast()
const notify = (msg: string): void => { toast.add({ title: msg, duration: 1500 }) }

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  {
    type: 'actions',
    minSize: 80,
    maxInline: 2,
    actions: [
      { key: 'view', buttonProps: { icon: 'i-lucide-eye', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`查看 ${row.name}`) },
      { key: 'edit', buttonProps: { icon: 'i-lucide-pencil', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`编辑 ${row.name}`) },
      { key: 'archive', buttonProps: { icon: 'i-lucide-archive', label: '归档', size: 'xs' }, onClick: ({ row }) => notify(`归档 ${row.name}`) },
      { key: 'delete', divider: true, buttonProps: { icon: 'i-lucide-trash-2', label: '删除', color: 'error', size: 'xs' }, onClick: ({ row }) => notify(`删除 ${row.name}`) }
    ]
  }
]
</script>

<template>
  <MDataTable row-key="id" :columns="columns" :data="data" />
</template>

actions 数组与函数式

actions 既可传静态数组,也可传 (ctx) => DataTableAction[] 按行派生:

姓名部门操作
李勇
产品
王超
市场
陈娜
设计
刘丽
运营
杨军
研发
<script setup lang="ts">
import type { DataTableAction, DataTableActionsColumn, DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(5)
const toast = useToast()
const notify = (msg: string): void => { toast.add({ title: msg, duration: 1500 }) }

const base: DataTableAction<Person>[] = [
  { key: 'view', buttonProps: { icon: 'i-lucide-eye', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`查看 ${row.name}`) },
  { key: 'edit', buttonProps: { icon: 'i-lucide-pencil', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`编辑 ${row.name}`) }
]

const actions: DataTableActionsColumn<Person>['actions'] = ({ row: { original } }) => {
  const extras: DataTableAction<Person>[] = []
  if (original.department === '研发')
    extras.push({ key: 'resetPwd', buttonProps: { icon: 'i-lucide-key-round', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`重置密码 ${row.name}`) })
  if (original.department === '设计')
    extras.push({ key: 'transfer', buttonProps: { icon: 'i-lucide-share-2', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`转交 ${row.name}`) })
  return [...base, ...extras]
}

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { type: 'actions', maxInline: 4, actions, size: 120 }
]
</script>

<template>
  <MDataTable row-key="id" :columns="columns" :data="data" />
</template>

异步动作自动 loading

onClick 返回 Promise 时按钮自动进入 loading,期间禁用并阻止重复触发(仅作用于内联按钮):

姓名部门操作
李勇
产品
王超
市场
陈娜
设计
刘丽
运营
杨军
研发
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(5)
const toast = useToast()
const notify = (msg: string): void => { toast.add({ title: msg, duration: 1500 }) }

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  {
    type: 'actions',
    maxInline: 2,
    size: 120,
    actions: [
      { key: 'view', buttonProps: { icon: 'i-lucide-eye', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`查看 ${row.name}`) },
      {
        key: 'sync',
        buttonProps: { icon: 'i-lucide-refresh-cw', label: '同步', size: 'xs' },
        onClick: async ({ row }) => {
          await new Promise(r => setTimeout(r, 1200))
          notify(`同步完成 ${row.name}`)
        }
      }
    ]
  }
]
</script>

<template>
  <MDataTable row-key="id" :columns="columns" :data="data" />
</template>

confirm 二次确认

confirm: true 在执行前弹出 MessageBox;confirmProps 可动态返回 titletypedescriptionconfirmText

姓名状态操作
李勇
leave
王超
offboarded
陈娜
active
刘丽
leave
杨军
offboarded
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(5)
const toast = useToast()
const notify = (msg: string): void => { toast.add({ title: msg, duration: 1500 }) }

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'status', header: '状态' },
  {
    type: 'actions',
    size: 120,
    actions: [
      { key: 'edit', buttonProps: { icon: 'i-lucide-pencil', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`编辑 ${row.name}`) },
      {
        key: 'delete',
        buttonProps: { icon: 'i-lucide-trash-2', label: '删除', color: 'error', size: 'xs' },
        confirm: true,
        confirmProps: ({ row }) => ({
          type: row.status === 'active' ? 'warning' : 'error',
          title: `删除 ${row.name}`,
          description: row.status === 'active' ? '该员工在职,删除前请二次确认。' : '已离职员工删除后无法恢复。',
          confirmText: '我已确认'
        }),
        onClick: ({ row }) => notify(`已删除 ${row.name}`)
      }
    ]
  }
]
</script>

<template>
  <MDataTable row-key="id" :columns="columns" :data="data" />
</template>

动态 disabled 与 visibility

action.disabledaction.visibility 接受布尔或 (ctx) => boolean,按行派生禁用或隐藏:

姓名状态操作
李勇
leave
王超
offboarded
陈娜
active
刘丽
leave
杨军
offboarded
黄涛
active
赵伟
leave
吴敏
offboarded
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(8)
const toast = useToast()
const notify = (msg: string): void => { toast.add({ title: msg, duration: 1500 }) }

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'status', header: '状态' },
  {
    type: 'actions',
    maxInline: 3,
    size: 150,
    actions: [
      { key: 'view', buttonProps: { icon: 'i-lucide-eye', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`查看 ${row.name}`) },
      { key: 'edit', buttonProps: { icon: 'i-lucide-pencil', variant: 'ghost', size: 'xs' }, disabled: ({ row }) => row.status !== 'active', onClick: ({ row }) => notify(`编辑 ${row.name}`) },
      { key: 'recall', buttonProps: { icon: 'i-lucide-undo-2', label: '召回', size: 'xs' }, visibility: ({ row }) => row.status === 'offboarded', onClick: ({ row }) => notify(`召回 ${row.name}`) }
    ]
  }
]
</script>

<template>
  <MDataTable row-key="id" :columns="columns" :data="data" />
</template>

特殊列的共享字段

selectionindexrow-pinningactions 列与数据列一样支持 visibilitypinableresizable

姓名
部门
操作
李勇
产品
王超
市场
陈娜
设计
刘丽
运营
杨军
研发
<script setup lang="ts">
import type { DataTableAction, DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(5)
const toast = useToast()
const actions: DataTableAction<Person>[] = [
  { key: 'view', buttonProps: { icon: 'i-lucide-eye', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => { toast.add({ title: `查看 ${row.name}`, duration: 1500 }) } }
]

const columns: DataTableColumn<Person>[] = [
  { type: 'selection', pinable: true, resizable: true },
  { type: 'index', visibility: false },
  { type: 'row-pinning', pinable: false },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门', resizable: true },
  { type: 'actions', size: 80, actions }
]
</script>

<template>
  <MDataTable row-key="id" :columns="columns" :data="data" />
</template>
Copyright © 2025 - 2026 YiXuan - MIT License