---
title: "特殊列"
description: "选择列、索引列、行固定列与操作列的配置，含树形选择策略、动态按钮与二次确认。"
seo_title: "Special Columns"
seo_description: "Configure DataTable special columns — selection (single/multiple, tree strategies, custom checkbox props), index, row-pinning and actions (inline/overflow, async loading, confirm, dynamic state)."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/data-table/special-columns"
---
# 特殊列

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

## 概述

数据列用 `accessorKey` 绑定字段；特殊列通过 `type` 区分，无需 `accessorKey`：

| `type`        | 用途                                   |
| ------------- | ------------------------------------ |
| `selection`   | 行选择（单选/多选，树形支持级联策略）                  |
| `index`       | 行序号                                  |
| `expand`      | 树形展开（见[树形数据](/docs/data-table/tree)） |
| `row-pinning` | 行固定到顶部/底部                            |
| `actions`     | 行操作按钮                                |

> \[\!TIP\]
> 
> 所有特殊列同样支持 
> 
> visibility
> 
> 、
> 
> pinable
> 
> 、
> 
> resizable
> 
> 、
> 
> fixed
> 
> 、
> 
> size
> 
> 、
> 
> align
> 
> 。

## 选择列

### `mode` 单选 / 多选

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

```vue [DataTableSelectionExample.vue]
<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` 仅叶子可选（父节点展示为派生态）：

```vue [DataTableSelectionStrategyExample.vue]
<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`：

```vue [DataTableSelectionCheckboxPropsExample.vue]
<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` 派生 `leaves`、`parents`、`halfSelected`、`strictlyChecked` 等业务视图（通过 `useTemplateRef` 访问）：

```vue [DataTableSelectionKeysExample.vue]
<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` 自定义：

```vue [DataTableIndexExample.vue]
<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'` 的 `position` 取 `top` 或 `bottom`，把行钉到表头或表尾，`v-model:row-pinning` 暴露 `top`、`bottom` 两组 id：

```vue [DataTableRowPinningExample.vue]
<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` 状态返回 `icon`、`color`、`variant`，区分已固定与未固定行：

```vue [DataTableRowPinningDynamicExample.vue]
<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` 在菜单中插入分隔线：

```vue [DataTableActionsInlineExample.vue]
<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[]` 按行派生：

```vue [DataTableActionsFunctionalExample.vue]
<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，期间禁用并阻止重复触发（仅作用于内联按钮）：

```vue [DataTableActionsAsyncExample.vue]
<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` 可动态返回 `title`、`type`、`description`、`confirmText`：

```vue [DataTableActionsConfirmExample.vue]
<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.disabled` 与 `action.visibility` 接受布尔或 `(ctx) => boolean`，按行派生禁用或隐藏：

```vue [DataTableActionsDynamicStateExample.vue]
<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>
```

## 特殊列的共享字段

`selection`、`index`、`row-pinning`、`actions` 列与数据列一样支持 `visibility`、`pinable`、`resizable`、`size`、`minSize`、`maxSize`、`align`、`fixed`。特殊列默认固定宽 60；给 `minSize` / `maxSize`（且不设 `size`）即改为按内容自适应——`minSize` 为下限、`maxSize` 为拖拽上限。右侧唯一的操作列（`fixed: 'right'`）设 `minSize` 可让其随按钮数撑开，详见[数据列的列宽规则](/docs/data-table/columns)。

```vue [DataTableSpecialSharedExample.vue]
<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>
```


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
