行为与交互

View source
行选择、展开详情、点击行为、动态行样式、悬停、右键菜单与编程式控制。

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) => stringrow-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),第二参数为 TableRownull,可用于联动外部高亮:

悬停行:(无)

姓名部门岗位
李勇
产品
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>
Copyright © 2025 - 2026 YiXuan - MIT License