---
title: "行为与交互"
description: "行选择、展开详情、点击行为、动态行样式、悬停、右键菜单与编程式控制。"
seo_title: "Rows & Interaction"
seo_description: "DataTable row interactions — selection, expandable detail slot, row-click behaviors, dynamic row class/style, hover, context menu and imperative control via the exposed API."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/data-table/rows"
---
# 行为与交互

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

## `rowKey` 行标识

`row-key` 指定行唯一标识字段，自动派生 TanStack 的 `getRowId`，是选择、展开、树形等功能正确追踪行的基础。设置后选中态、展开态均按业务 id 而非数组下标追踪——打乱 `data` 顺序后，选中行仍指向同一条记录：

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

```vue [DataTableRowSelectionExample.vue]
<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` 槽位渲染详情区域：

> \[\!NOTE\]
> 
> type: 'expand'
> 
>  列表头默认渲染「全部展开/收起」按钮，可通过 
> 
> toggleAll: false
> 
>  关闭

```vue [DataTableRowExpandExample.vue]
<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` 整行点击切换展开，两者可独立开启：

```vue [DataTableRowClickExample.vue]
<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>`，按行数据派生类名与内联样式：

```vue [DataTableRowStyleExample.vue]
<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`，可用于联动外部高亮：

```vue [DataTableRowHoverExample.vue]
<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` 与目标行，可阻止默认菜单并弹出自定义操作：

```vue [DataTableRowContextmenuExample.vue]
<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()` 滚回顶部：

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


## Sitemap

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