表格
<template>
<div>
<p>
对于表格,`translate` CSS 函数的偏移量是基于 <strong>行本身的初始位置</strong>。正因为如此,我们需要以不同的方式计算 `translateY` 像素计数,并将其基于 <strong>索引</strong>。
</p>
<div
ref="parentRef"
class="container"
>
<div :style="{ height: `${totalSize}px` }">
<table>
<thead>
<tr
v-for="headerGroup in table.getHeaderGroups()"
:key="headerGroup.id"
>
<th
v-for="header in headerGroup.headers"
:key="header.id"
:colspan="header.colSpan"
:style="{ width: `${header.getSize()}px` }"
>
<div
v-if="!header.isPlaceholder"
:class="[
'text-left',
header.column.getCanSort()
? 'cursor-pointer select-none'
: ''
]"
@click="
getSortingHandler(
$event,
header.column.getToggleSortingHandler()
)
"
>
<FlexRender
:render="header.column.columnDef.header"
:props="header.getContext()"
/>
<span v-if="header.column.getIsSorted() === 'asc'"> 🔼</span>
<span v-if="header.column.getIsSorted() === 'desc'"> 🔽</span>
</div>
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(virtualRow, index) in virtualRows"
:key="virtualRow.key"
:style="{
transform: `translateY(${virtualRow.start - index * virtualRow.size}px)`
}"
>
<td
v-for="cell in rows[virtualRow.index].getVisibleCells()"
:key="cell.id"
>
<FlexRender
:render="cell.column.columnDef.cell"
:props="cell.getContext()"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useVirtualizer } from '@tanstack/vue-virtual'
import type {
ColumnDef,
SortingState } from '@tanstack/vue-table'
import {
FlexRender,
useVueTable,
getCoreRowModel,
getSortedRowModel
} from '@tanstack/vue-table'
import type { Person } from './makeData'
import { makeData } from './makeData'
const data = ref(makeData(100))
const sorting = ref<SortingState>([])
const getSortingHandler = (e: Event, fn: any) => {
return fn(e)
}
const setSorting = (sortingUpdater: any) => {
const newSortVal = sortingUpdater(sorting.value)
sorting.value = newSortVal
}
const columns = computed<ColumnDef<Person>[]>(() => {
return [
{
accessorKey: 'id',
header: 'ID',
size: 60
},
{
accessorKey: 'firstName',
cell: info => info.getValue()
},
{
accessorFn: row => row.lastName,
id: 'lastName',
cell: info => info.getValue(),
header: () => 'Last Name'
},
{
accessorKey: 'age',
header: () => 'Age',
size: 50
},
{
accessorKey: 'visits',
header: () => 'Visits',
size: 50
},
{
accessorKey: 'status',
header: 'Status'
},
{
accessorKey: 'progress',
header: 'Profile Progress',
size: 80
},
{
accessorKey: 'createdAt',
header: 'Created At',
cell: info => info.getValue<Date>().toLocaleString()
}
]
})
const table = useVueTable({
get data() {
return data.value
},
columns: columns.value,
state: {
get sorting() {
return sorting.value
}
},
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
debugTable: true
})
const rows = computed(() => {
return table.getRowModel().rows
})
const parentRef = ref<HTMLElement | null>(null)
const rowVirtualizerOptions = computed(() => {
return {
count: rows.value.length,
getScrollElement: () => parentRef.value,
estimateSize: () => 34,
overscan: 5
}
})
const rowVirtualizer = useVirtualizer(rowVirtualizerOptions)
const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems())
const totalSize = computed(() => rowVirtualizer.value.getTotalSize())
</script>
<style scoped>
.container {
height: 600px;
overflow: auto;
}
.cursor-pointer {
cursor: pointer;
}
.select-none {
user-select: none;
}
.text-left {
text-align: left;
}
</style>