Документация/Обзор
Таблица данных
Таблица Nuxt UI на 2 000 задач, виртуализированная и с поиском, которая сортируется перетаскиванием строк за их ручки.
Страница переведена ИИ и может содержать неточности. Читать английский оригинал
Загружаем демо…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import TaskTable from './TaskTable.vue';
</script>
<template>
<!-- The row in hand: a table row can't have a shadow, so its overlay content has it. -->
<DnDProvider overlay-class="overflow-hidden rounded-md bg-default shadow-xl ring ring-accented">
<TaskTable />
</DnDProvider>
</template>UTable из Nuxt UI рендерит таблицу, её строки и её виртуализатор: в DOM есть только строки,
которые на экране. Vue DnD Kit их сортирует. Таблица — такая, какой её даёт библиотека, со своими
слотами и классами ui; ничего в ней не переписано.
На что обратить внимание
TaskTable.vue: сортируемый список — это<tbody>таблицы, найденный черезtableRef, который открываетUTable. Таблица получает черновик списка как своиdata, поэтому строки двигаются, пока вы тащите, аget-row-idзадаёт им ключи по задаче, как и список.axis: 'y'держит драг в своей колонке, аrestrictToFirstScrollableAncestor— внутри таблицы: её скролл-контейнер — первый прокручиваемый элемент вокруг строк. Разверните демо: таблица займёт весь экран.RowGrip.vue: строки рендеритUTable, поэтому ни один ваш компонент не является строкой. Ручка в первой ячейке (слот#grip-cell) делает строку вокруг себя элементом списка и сама служит еёhandle.- Поиск — это
filterсписка: таблица показывает подходящие задачи, а бросок записывает обратно весь массив, скрытые задачи — там, где они были. tasks.ts: 2 000 задач вshallowRef: библиотека никогда не заглядывает внутрь них.
Строка, которую рендерит таблица
Ручка вызывает makeSortableItem с геттером строки: <tr> вокруг неё, как только тот
отрендерен. Как ручка, она получает фокус и ARIA-атрибуты, а остальная строка остаётся свободной
для кликов и выделения текста.
const grip = useTemplateRef<HTMLElement>('grip');
makeSortableItem(() => grip.value?.closest('tr') ?? null, {
id: () => props.task.id,
handle: grip,
label: () => props.task.title,
});
<UTable ref="table" :data="items" :get-row-id="(task: Task) => task.id" …>
<template #grip-cell="{ row }">
<RowGrip :task="row.original" />
</template>
</UTable>
Оверлей — это копия строки, сделанная библиотекой, в собственной таблице с ширинами ячеек.
Строка таблицы не рисует тень, поэтому тень содержимому оверлея даёт overlay-class провайдера,
вместе с фоном таблицы и обводкой. Классы ui строки подкрашивают её место, пока она в пути
(data-[dnd-placeholder]:bg-primary/5).
<DnDProvider overlay-class="overflow-hidden rounded-md bg-default shadow-xl ring ring-accented">
<TaskTable />
</DnDProvider>
Виртуализированные строки
virtualize даёт таблице TanStack Virtual: рендерятся строки на экране и ещё 8 вокруг них
(overscan), между двумя строками-распорками.
<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(см. виртуальные списки). Таблица раскладывает строки между строками-распорками, где строка, отрендеренная далеко от остальных, раздвинула бы их, поэтому эта её отпускает.
Найти, затем перетащить
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 содержит весь массив.
- Строка. Слот, который рендерит строку целиком (
#itemу Vuetify,#bodyу Quasar), даёт вам её элемент: сделайте его элементом списка. Где вы получаете только ячейки (#bodyуColumnв PrimeVue, слот по умолчанию колонки Element Plus,renderв Naive UI), поставьте в одну из них ручку и сделайте элементом списка еёclosest('tr'), как здесь. - Список.
<tbody>внутри корневого элемента таблицы: template ref на таблице даёт вам компонент, а его$el— элемент. - Ключи и данные. Задайте строкам ключи по вашему id (
item-value,row-key,dataKey) и передайте таблице черновик списка как её данные. - Её собственный порядок — выключен. Не включайте у таблицы ни собственную перестановку строк, ни сортировку по колонкам: порядок задаёт список.
- Виртуальные таблицы размещают строки между распорками или в абсолютно позиционированных блоках: работает и то и другое, а драг переживает размонтирование своей строки.
@nuxt/ui/vite, Tailwind CSS и UApp вокруг примера. В
приложении Nuxt с модулем @nuxt/ui скопируйте папку и отрендерите index.vue на странице.Клавиатура и скринридеры
Перейдите табом к ручке задачи и нажмите Space, чтобы взять строку.
ArrowUp и ArrowDown двигают её, Space бросает,
Escape возвращает на место. У строк есть label, и у списка тоже: скринридеры
слышат «Fix the login form dropped at position 3 of 2000 in Tasks.», а не ключи.
Подробнее: виртуальные списки · показать часть элементов · стилизация: Tailwind и UI-киты · доступность