---
title: "数据表格"
description: "一个包含 2,000 个任务的 Nuxt UI 表格，虚拟化、可搜索，通过抓手拖动行来排序。"
canonical_url: "https://zizigy.github.io/vue-dnd-kit/zh/docs/examples/data-table"
---

> Index of every page on this site: [llms.txt](https://zizigy.github.io/vue-dnd-kit/llms.txt)

<example height="34rem" hint="通过抓手拖动一个任务；先搜索再拖动：隐藏的任务保持原位" name="ui-table" title="数据表格">



</example>

Nuxt UI 的 `UTable` 渲染表格、它的行和它的虚拟化器：只有屏幕上的行在 DOM 中。Vue DnD Kit 负责给它们排序。表格就是这个 UI 库原样提供的表格，带着它的插槽和 `ui` 类；没有任何部分被重写。

## 看点

- **TaskTable.vue**：可排序列表是表格的 `<tbody>`，通过 `UTable` 暴露的 `tableRef` 找到。表格把列表的草稿作为它的 `data`，所以拖拽时行会移动，`get-row-id` 按任务给行设置 key，与列表一致。`axis: 'y'` 让拖拽留在它的列中，`restrictToFirstScrollableAncestor` 让它留在表格内部：表格的滚动容器是行外面第一个可滚动的元素。展开演示：表格会占满整个屏幕。
- **RowGrip.vue**：行由 `UTable` 渲染，所以没有一个行是你自己的组件。第一个单元格中的抓手（`#grip-cell` 插槽）把它外面的行变成项，它本身就是这一项的手柄。
- **搜索**就是列表的 `filter`：表格显示匹配的任务，放置时把整个数组写回，隐藏的任务留在原处。
- **tasks.ts**：放在 `shallowRef` 中的 2,000 个任务：库从不查看它们的内部。

## 由表格渲染的行

抓手调用 `makeSortableItem` 时传入一个返回行的 getter：也就是它外面的 `<tr>`（在它渲染出来之后）。作为手柄，抓手获得焦点和 ARIA 属性，行的其余部分仍可自由用于点击和选择文本。

```ts [RowGrip.vue]
const grip = useTemplateRef<HTMLElement>('grip');
makeSortableItem(() => grip.value?.closest('tr') ?? null, {
  id: () => props.task.id,
  handle: grip,
  label: () => props.task.title,
});
```

```vue [TaskTable.vue]
<UTable ref="table" :data="items" :get-row-id="(task: Task) => task.id" …>
  <template #grip-cell="{ row }">
    <RowGrip :task="row.original" />
  </template>
</UTable>
```

浮层是库对这一行的复制，放在一个单独的表格里，单元格宽度保持不变。表格行不绘制阴影，所以由 provider 的 `overlay-class` 给浮层的内容加上阴影，以及表格的背景和一圈边框（ring）。行移动期间，行的 `ui` 类给它的位置着色（`data-[dnd-placeholder]:bg-primary/5`）。

```vue [index.vue]
<DnDProvider overlay-class="overflow-hidden rounded-md bg-default shadow-xl ring ring-accented">
  <TaskTable />
</DnDProvider>
```

## 虚拟化的行

`virtualize` 给表格加上 TanStack Virtual：渲染屏幕上的行以及它们周围另外 8 行（`overscan`），位于两个间隔行之间。

```vue [TaskTable.vue]
<UTable
  :data="items"
  :watch-options="{ deep: false }"
  :virtualize="{ estimateSize: 45, overscan: 8, onChange: () => captureLayout() }"
  class="min-h-0 flex-1"
/>
```

- `estimateSize` 是初始估计：虚拟化器会在每一行渲染时测量它。一次测量会移动它下面的行，`onChange` 调用列表的 `captureLayout()`，这样在拖拽期间库会为这次移动添加动画，而不是让它跳过去。
- 表格就是滚动容器，手中的行留在它内部：靠近它的上边缘或下边缘时，拖拽会让它滚动，新的行在进入视口时渲染。
- 每次移动后草稿都是一个新数组，所以表格不需要监听它的内部（`deep: false`）。
- 滚动到离你拿着的行很远的地方，它就会离开渲染范围并被卸载。拖拽继续进行：浮层仍然显示它，它会落到草稿给它安排的位置。你自己的虚拟列表可以用 `rangeExtractor` 让它保持挂载（参见[虚拟列表](https://zizigy.github.io/docs/guides/virtual-lists#tanstack-virtual)）。表格把行布置在间隔行之间，在那里一个远离其他行渲染的行会把它们挤开，所以这个表格让它卸载。

## 先搜索，再拖动

```ts [TaskTable.vue]
const { items, captureLayout } = makeSortableList(() => table.value?.tableRef?.tBodies[0] ?? null, {
  items: tasks,
  filter: (task) => {
    const text = query.value.trim().toLowerCase();
    return !text || `${task.id} ${task.title} ${task.assignee}`.toLowerCase().includes(text);
  },
  axis: 'y',
  modifiers: [restrictToFirstScrollableAncestor],
  label: 'Tasks',
});
```

搜索“Mia”，把她的一个任务移到另一个任务上方，然后清空搜索：她的任务按新的顺序排列，其他每个任务都还在原处。放置事件的 `e.to.index` 按显示出来的行计数；`e.result` 包含整个数组。

<tip>

**其他 UI 库**。Nuxt UI 渲染普通元素，并把你的类和属性传递下去，它所基于的 Reka UI 原语也是如此：只需要一个 ref、一个插槽和一个类。在其他库中，表格同样自己渲染行，同样的方法也适用：

- **行**。渲染整行的插槽（Vuetify 的 `#item`、Quasar 的 `#body`）会给你行的元素：把它作为项。如果只能拿到单元格（PrimeVue `Column` 的 `#body`、Element Plus 列的默认插槽、Naive UI 的 `render`），就在其中一个单元格里放一个抓手，把它的 `closest('tr')` 作为项，就像这里一样。
- **列表**。表格根元素内部的 `<tbody>`：表格上的模板引用给你组件，它的 `$el` 给你元素。
- **key 和数据**。按你的 id 给行设置 key（`item-value`、`row-key`、`dataKey`），并把列表的草稿作为表格的数据。
- **关闭它自己的排序**。不要启用表格自带的行重新排序和列排序：顺序由列表决定。
- **虚拟表格**把行放在间隔元素之间，或放在绝对定位的盒子里：两种都可以，而且拖拽在它的行被卸载后依然继续。

</tip>

<note>

StackBlitz 按钮会在一个按 Nuxt UI 文档中 Vue 用法配置好的 Vite 项目里打开这些文件：`@nuxt/ui/vite` 插件、Tailwind CSS，以及包裹示例的 `UApp`。在带有 `@nuxt/ui` 模块的 Nuxt 应用中，复制文件夹并在某个页面上渲染 `index.vue` 即可。

</note>

## 键盘和屏幕阅读器

用 Tab 移到一个任务的抓手上，按 <kbd value="Space">



</kbd>

 拿起这一行。:kbd{value="ArrowUp"} 和 <kbd value="ArrowDown">



</kbd>

 移动它，:kbd{value="Space"} 放下它，:kbd{value="Escape"} 把它放回原处。行有 `label`，列表也有：屏幕阅读器听到的是“Fix the login form dropped at position 3 of 2000 in Tasks.”，而不是 key。

延伸阅读：[虚拟列表](https://zizigy.github.io/docs/guides/virtual-lists) · [只显示部分项](https://zizigy.github.io/docs/guides/sortable-lists#show-some-of-the-items) · [样式：Tailwind 和 UI 库](https://zizigy.github.io/docs/guides/styling#tailwind-css) · [无障碍](https://zizigy.github.io/docs/guides/accessibility)

## Sitemap

See the full [sitemap](https://zizigy.github.io/vue-dnd-kit/sitemap.md) for all pages.
