特殊列
概述
数据列用 accessorKey 绑定字段;特殊列通过 type 区分,无需 accessorKey:
type | 用途 |
|---|---|
selection | 行选择(单选/多选,树形支持级联策略) |
index | 行序号 |
expand | 树形展开(见树形数据) |
row-pinning | 行固定到顶部/底部 |
actions | 行操作按钮 |
visibility、pinable、resizable、fixed、size、align。选择列
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 仅叶子可选(父节点展示为派生态):
| 姓名 | 部门 | ||
|---|---|---|---|
李勇 | 产品 | ||
王超 | 市场 | ||
陈娜 | 设计 |
| 姓名 | 部门 | ||
|---|---|---|---|
李勇 | 产品 | ||
王超 | 市场 | ||
陈娜 | 设计 |
| 姓名 | 部门 | ||
|---|---|---|---|
李勇 | 产品 | ||
王超 | 市场 | ||
陈娜 | 设计 |
<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 派生 leaves、parents、halfSelected、strictlyChecked 等业务视图(通过 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' 的 position 取 top 或 bottom,把行钉到表头或表尾,v-model:row-pinning 暴露 top、bottom 两组 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 状态返回 icon、color、variant,区分已固定与未固定行:
| 姓名 | 部门 | 岗位 | |
|---|---|---|---|
李勇 | 产品 | 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 可动态返回 title、type、description、confirmText:
| 姓名 | 状态 | 操作 |
|---|---|---|
李勇 | 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.disabled 与 action.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>
特殊列的共享字段
selection、index、row-pinning、actions 列与数据列一样支持 visibility、pinable、resizable:
| 姓名 | 部门 | 操作 | ||
|---|---|---|---|---|
李勇 | 产品 | |||
王超 | 市场 | |||
陈娜 | 设计 | |||
刘丽 | 运营 | |||
杨军 | 研发 |
<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>