行为与交互
行选择、展开详情、点击行为、动态行样式、悬停、右键菜单与编程式控制。
rowKey 行标识
row-key 指定行唯一标识字段,自动派生 TanStack 的 getRowId,是选择、展开、树形等功能正确追踪行的基础。设置后选中态、展开态均按业务 id 而非数组下标追踪——打乱 data 顺序后,选中行仍指向同一条记录:
| 工号 | 姓名 | 部门 | |
|---|---|---|---|
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>
基础行选择
v-model:row-selection 双向同步选中 id 集合;@select 事件返回当前点击行(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>
行展开与详情槽位
v-model:expanded 控制展开行 id,#expanded 槽位渲染详情区域:
type: 'expand' 列表头默认渲染「全部展开/收起」按钮,可通过 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>
点行触发选择与展开
select-on-row-click 整行点击切换选中,expand-on-row-click 整行点击切换展开,两者可独立开启:
| 姓名 | 部门 | ||
|---|---|---|---|
李勇 | 产品 | ||
王超 | 市场 | ||
陈娜 | 设计 | ||
刘丽 | 运营 | ||
杨军 | 研发 | ||
黄涛 | 产品 | ||
赵伟 | 市场 | ||
吴敏 | 设计 |
<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>
动态行样式
row-class 接受 (row) => string,row-style 接受 (row) => string | Record<string, string>,按行数据派生类名与内联样式:
| 姓名 | 状态 | 薪资 |
|---|---|---|
李勇 | 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>
行悬停追踪
@hover 在进入和离开行时触发(DataTableHoverHandler),第二参数为 TableRow 或 null,可用于联动外部高亮:
悬停行:(无)
| 姓名 | 部门 | 岗位 |
|---|---|---|
李勇 | 产品 | 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-contextmenu 拦截右键事件(DataTableContextmenuHandler),回调入参含原始 MouseEvent 与目标行,可阻止默认菜单并弹出自定义操作:
| 姓名 | 部门 | 岗位 |
|---|---|---|
李勇 | 产品 | 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>
编程式控制
通过 useTemplateRef 拿到组件实例,调用 expose 的 clearSelection() 清空选中、scrollToTop() 滚回顶部:
| 姓名 | 部门 | 岗位 | |
|---|---|---|---|
李勇 | 产品 | 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>