Rows & Interaction
rowKey Row Identifier
row-key specifies the unique identifier field for a row and automatically derives TanStack's getRowId. It is the foundation for selection, expansion, tree data and other features to correctly track rows. Once set, selection and expansion state are tracked by the business id rather than the array index — reordering data keeps the selected rows pointing to the same records:
| 工号 | 姓名 | 部门 | |
|---|---|---|---|
P0001 | 李勇 | 产品 | |
P0002 | 王超 | 市场 | |
P0003 | 陈娜 | 设计 | |
P0004 | 刘丽 | 运营 | |
P0005 | 杨军 | 研发 | |
P0006 | 黄涛 | 产品 |
selection: {}<script setup lang="ts">
import type { DataTableColumn, RowSelectionState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const rows = ref<Person[]>(makePeople(6))
const selection = ref<RowSelectionState>({})
const columns: DataTableColumn<Person>[] = [
{ type: 'selection' },
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' }
]
function shuffle(): void {
rows.value = [...rows.value].sort(() => Math.random() - 0.5)
}
</script>
<template>
<div class="flex flex-col gap-3">
<div>
<UButton size="xs" variant="soft" icon="i-lucide-shuffle" @click="shuffle">
打乱数据
</UButton>
</div>
<MDataTable v-model:row-selection="selection" row-key="id" :columns="columns" :data="rows" />
<pre class="text-xs p-3 rounded-md bg-muted overflow-auto">selection: {{ selection }}</pre>
</div>
</template>
Basic Row Selection
v-model:row-selection bi-directionally syncs the selected id set; the @select event returns the currently clicked row (DataTableSelectHandler):
| 姓名 | 部门 | 岗位 | |
|---|---|---|---|
李勇 | 产品 | UI 设计师 | |
王超 | 市场 | 运营专员 | |
陈娜 | 设计 | 全栈工程师 | |
刘丽 | 运营 | 数据分析师 | |
杨军 | 研发 | 后端工程师 | |
黄涛 | 产品 | 产品经理 | |
赵伟 | 市场 | 前端工程师 | |
吴敏 | 设计 | UI 设计师 | |
周强 | 运营 | 运营专员 | |
徐洋 | 研发 | 全栈工程师 |
最近点击:(无)
selection: {}<script setup lang="ts">
import type { DataTableColumn, DataTableSelectHandler, RowSelectionState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(10)
const selection = ref<RowSelectionState>({})
const lastSelected = ref('')
const columns: DataTableColumn<Person>[] = [
{ type: 'selection' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
const onSelect: DataTableSelectHandler<Person> = (_e, row) => {
lastSelected.value = row.original.name
}
</script>
<template>
<div class="flex flex-col gap-2">
<MDataTable
v-model:row-selection="selection"
row-key="id"
:columns="columns"
:data="data"
:ui="{ root: 'max-h-[50vh]' }"
@select="onSelect"
/>
<p class="text-xs text-muted">
最近点击:{{ lastSelected || '(无)' }}
</p>
<pre class="text-xs p-3 rounded-md bg-muted overflow-auto">selection: {{ selection }}</pre>
</div>
</template>
Row Expansion and Detail Slot
v-model:expanded controls expanded row ids; the #expanded slot renders the detail area:
type: 'expand' column header renders an "expand all / collapse all" button by default — disable it with toggleAll: false| 姓名 | 部门 | 岗位 | |
|---|---|---|---|
李勇 | 产品 | UI 设计师 | |
王超 | 市场 | 运营专员 | |
陈娜 | 设计 | 全栈工程师 | |
刘丽 | 运营 | 数据分析师 | |
杨军 | 研发 | 后端工程师 | |
黄涛 | 产品 | 产品经理 | |
赵伟 | 市场 | 前端工程师 | |
吴敏 | 设计 | UI 设计师 |
<script setup lang="ts">
import type { DataTableColumn, ExpandedState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(8)
const expanded = ref<ExpandedState>({})
const columns: DataTableColumn<Person>[] = [
{ type: 'expand' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<MDataTable
v-model:expanded="expanded"
row-key="id"
:columns="columns"
:data="data"
:ui="{ root: 'max-h-[50vh]' }"
>
<template #expanded="{ row }">
<div class="px-4 py-3 text-sm bg-elevated/30">
<p>邮箱 {{ row.original.email }} · 入职 {{ row.original.joinedAt }}</p>
<p class="text-muted">
{{ row.original.bio }}
</p>
</div>
</template>
</MDataTable>
</template>
Row Click for Selection and Expansion
select-on-row-click toggles selection on a full-row click; expand-on-row-click toggles expansion on a full-row click. Both can be enabled independently:
| 姓名 | 部门 | ||
|---|---|---|---|
李勇 | 产品 | ||
王超 | 市场 | ||
陈娜 | 设计 | ||
刘丽 | 运营 | ||
杨军 | 研发 | ||
黄涛 | 产品 | ||
赵伟 | 市场 | ||
吴敏 | 设计 |
<script setup lang="ts">
import type { DataTableColumn, ExpandedState, RowSelectionState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(8)
const selection = ref<RowSelectionState>({})
const expanded = ref<ExpandedState>({})
const columns: DataTableColumn<Person>[] = [
{ type: 'selection' },
{ type: 'expand' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' }
]
</script>
<template>
<MDataTable
v-model:row-selection="selection"
v-model:expanded="expanded"
row-key="id"
:columns="columns"
:data="data"
:ui="{ root: 'max-h-[50vh]' }"
>
<template #expanded="{ row }">
<div class="px-4 py-2 text-xs text-muted bg-elevated/30">
{{ row.original.bio }}
</div>
</template>
</MDataTable>
</template>
Dynamic Row Styles
row-class accepts (row) => string; row-style accepts (row) => string | Record<string, string> to derive class names and inline styles from row data:
| 姓名 | 状态 | 薪资 |
|---|---|---|
李勇 | leave | 8257 |
王超 | offboarded | 8514 |
陈娜 | active | 8771 |
刘丽 | leave | 9028 |
杨军 | offboarded | 9285 |
黄涛 | active | 9542 |
赵伟 | leave | 9799 |
吴敏 | offboarded | 10056 |
周强 | active | 10313 |
徐洋 | leave | 10570 |
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(10)
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'status', header: '状态' },
{ accessorKey: 'salary', header: '薪资', align: 'right', size: 110 }
]
function rowClassByStatus(row: Person): string {
if (row.status === 'offboarded') return 'bg-gray-50/40 dark:bg-gray-900/10'
if (row.status === 'leave') return 'bg-warning-50/40 dark:bg-warning-900/10'
return ''
}
function rowStyleBySalary(row: Person): Record<string, string> {
if (row.salary >= 40000) return { fontWeight: '600', color: 'var(--ui-color-error-600, #dc2626)' }
if (row.salary >= 25000) return { fontWeight: '500' }
return {}
}
</script>
<template>
<MDataTable
row-key="id"
:columns="columns"
:data="data"
:row-class="rowClassByStatus"
:row-style="rowStyleBySalary"
:ui="{ root: 'max-h-[50vh]' }"
/>
</template>
Row Hover Tracking
@hover fires on row enter and leave (DataTableHoverHandler); the second argument is a TableRow or null, useful for syncing external highlights:
悬停行:(无)
| 姓名 | 部门 | 岗位 |
|---|---|---|
李勇 | 产品 | UI 设计师 |
王超 | 市场 | 运营专员 |
陈娜 | 设计 | 全栈工程师 |
刘丽 | 运营 | 数据分析师 |
杨军 | 研发 | 后端工程师 |
黄涛 | 产品 | 产品经理 |
赵伟 | 市场 | 前端工程师 |
吴敏 | 设计 | UI 设计师 |
<script setup lang="ts">
import type { DataTableColumn, DataTableHoverHandler } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(8)
const hovered = ref<string | null>(null)
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
const onHover: DataTableHoverHandler<Person> = (_e, row) => {
hovered.value = row ? row.original.name : null
}
</script>
<template>
<div class="flex flex-col gap-2">
<p class="text-xs text-muted">
悬停行:{{ hovered ?? '(无)' }}
</p>
<MDataTable row-key="id" :columns="columns" :data="data" :ui="{ root: 'max-h-[50vh]' }" @hover="onHover" />
</div>
</template>
Row Context Menu
@row-contextmenu intercepts right-click events (DataTableContextmenuHandler); the callback receives the original MouseEvent and the target row, allowing you to prevent the default menu and show a custom one:
| 姓名 | 部门 | 岗位 |
|---|---|---|
李勇 | 产品 | UI 设计师 |
王超 | 市场 | 运营专员 |
陈娜 | 设计 | 全栈工程师 |
刘丽 | 运营 | 数据分析师 |
杨军 | 研发 | 后端工程师 |
黄涛 | 产品 | 产品经理 |
赵伟 | 市场 | 前端工程师 |
吴敏 | 设计 | UI 设计师 |
<script setup lang="ts">
import type { DataTableColumn, DataTableContextmenuHandler } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(8)
const toast = useToast()
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
const onRowContextmenu: DataTableContextmenuHandler<Person> = (e, row) => {
e.preventDefault()
toast.add({ title: `右键 ${row.original.name}`, duration: 1500 })
}
</script>
<template>
<MDataTable
row-key="id"
:columns="columns"
:data="data"
:ui="{ root: 'max-h-[50vh]' }"
@row-contextmenu="onRowContextmenu"
/>
</template>
Imperative Control
Obtain the component instance via useTemplateRef and call the exposed clearSelection() to clear selection or scrollToTop() to scroll back to the top:
| 姓名 | 部门 | 岗位 | |
|---|---|---|---|
李勇 | 产品 | UI 设计师 | |
王超 | 市场 | 运营专员 | |
陈娜 | 设计 | 全栈工程师 | |
刘丽 | 运营 | 数据分析师 | |
杨军 | 研发 | 后端工程师 | |
黄涛 | 产品 | 产品经理 | |
赵伟 | 市场 | 前端工程师 | |
吴敏 | 设计 | UI 设计师 | |
周强 | 运营 | 运营专员 | |
徐洋 | 研发 | 全栈工程师 | |
孙明 | 产品 | 数据分析师 | |
张芳 | 市场 | 后端工程师 | |
李静 | 设计 | 产品经理 | |
王磊 | 运营 | 前端工程师 | |
陈勇 | 研发 | UI 设计师 |
<script setup lang="ts">
import type { DataTableColumn, DataTableExposed, RowSelectionState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(15)
const tableRef = useTemplateRef<DataTableExposed<Person>>('tableRef')
const selection = ref<RowSelectionState>({})
const columns: DataTableColumn<Person>[] = [
{ type: 'selection' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<div class="flex flex-col gap-3">
<div class="flex flex-wrap gap-2">
<UButton size="xs" variant="soft" icon="i-lucide-eraser" @click="tableRef?.clearSelection()">
清空选中
</UButton>
<UButton size="xs" variant="soft" icon="i-lucide-arrow-up-to-line" @click="tableRef?.scrollToTop({ behavior: 'smooth' })">
回到顶部
</UButton>
</div>
<MDataTable
ref="tableRef"
v-model:row-selection="selection"
row-key="id"
:columns="columns"
:data="data"
select-on-row-click
:ui="{ root: 'max-h-[40vh]' }"
/>
</div>
</template>
Special Columns
Configuration for selection, index, row-pinning and actions columns — including tree selection strategies, dynamic button props and confirmation dialogs.
Tree Data
Enable tree-mode tables via childrenKey — configure expansion, cascade selection strategies, indentation and read selection results.