refactor(frontend): remove non-virtual DataTable path, always-on virtual scroll

This commit is contained in:
erio
2026-03-18 23:14:32 +08:00
parent e3dbae9ad4
commit 32889e879e
2 changed files with 7 additions and 78 deletions
+7 -77
View File
@@ -33,7 +33,7 @@
<template v-else>
<div
v-for="(row, index) in displayedData"
v-for="(row, index) in sortedData"
:key="resolveRowKey(row, index)"
class="rounded-lg border border-gray-200 bg-white p-4 dark:border-dark-700 dark:bg-dark-900"
>
@@ -65,8 +65,7 @@
class="table-wrapper hidden md:block"
:class="{
'actions-expanded': actionsExpanded,
'is-scrollable': isScrollable,
'virtual-scroll-container': virtualScroll
'is-scrollable': isScrollable
}"
>
<table class="min-w-full divide-y divide-gray-200 dark:divide-dark-700">
@@ -148,31 +147,7 @@
</td>
</tr>
<!-- Data rows: non-virtual mode (original behavior) -->
<template v-else-if="!virtualScroll">
<tr
v-for="(row, index) in displayedData"
:key="resolveRowKey(row, index)"
:data-row-id="resolveRowKey(row, index)"
class="hover:bg-gray-50 dark:hover:bg-dark-800"
>
<td
v-for="(column, colIndex) in columns"
:key="column.key"
:class="[
'whitespace-nowrap py-4 text-sm text-gray-900 dark:text-gray-100',
getAdaptivePaddingClass(),
getStickyColumnClass(column, colIndex)
]"
>
<slot :name="`cell-${column.key}`" :row="row" :value="row[column.key]" :expanded="actionsExpanded">
{{ column.formatter ? column.formatter(row[column.key], row) : row[column.key] }}
</slot>
</td>
</tr>
</template>
<!-- Data rows: virtual scroll mode -->
<!-- Data rows (virtual scroll) -->
<template v-else>
<tr v-if="virtualPaddingTop > 0" aria-hidden="true">
<td :colspan="columns.length"
@@ -295,9 +270,6 @@ let resizeHandler: (() => void) | null = null
onMounted(() => {
checkScrollable()
checkActionsColumnWidth()
if (!props.virtualScroll) {
startProgressiveReveal(sortedData.value?.length ?? 0)
}
if (tableWrapperRef.value && typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver(() => {
checkScrollable()
@@ -316,7 +288,6 @@ onMounted(() => {
onUnmounted(() => {
resizeObserver?.disconnect()
cancelAnimationFrame(progressiveRAF)
if (resizeHandler) {
window.removeEventListener('resize', resizeHandler)
resizeHandler = null
@@ -347,11 +318,9 @@ interface Props {
* will emit 'sort' events instead of performing client-side sorting.
*/
serverSideSort?: boolean
/** Enable virtual scrolling (opt-in, default false) */
virtualScroll?: boolean
/** Estimated row height in px for the virtualizer (default 56) */
estimateRowHeight?: number
/** Number of rows to render beyond visible area (default 5) */
/** Number of rows to render beyond the visible area (default 5) */
overscan?: number
}
@@ -555,7 +524,7 @@ const sortedData = computed(() => {
// --- Virtual scrolling ---
const rowVirtualizer = useVirtualizer(computed(() => ({
count: props.virtualScroll ? (sortedData.value?.length ?? 0) : 0,
count: sortedData.value?.length ?? 0,
getScrollElement: () => tableWrapperRef.value,
estimateSize: () => props.estimateRowHeight ?? 56,
overscan: props.overscan ?? 5,
@@ -575,46 +544,11 @@ const virtualPaddingBottom = computed(() => {
})
const measureElement = (el: any) => {
if (el && props.virtualScroll) {
if (el) {
rowVirtualizer.value.measureElement(el as Element)
}
}
// --- Progressive rendering: render rows in chunks to avoid blocking the main thread ---
const PROGRESSIVE_CHUNK = 30
const visibleCount = ref(0)
let progressiveRAF = 0
const displayedData = computed(() => {
const all = sortedData.value
if (!all || visibleCount.value >= all.length) return all
return all.slice(0, visibleCount.value)
})
function startProgressiveReveal(total: number) {
cancelAnimationFrame(progressiveRAF)
visibleCount.value = Math.min(PROGRESSIVE_CHUNK, total)
if (total <= PROGRESSIVE_CHUNK) return
const step = () => {
if (visibleCount.value < total) {
visibleCount.value = Math.min(visibleCount.value + PROGRESSIVE_CHUNK, total)
if (visibleCount.value < total) {
progressiveRAF = requestAnimationFrame(step)
}
}
}
progressiveRAF = requestAnimationFrame(step)
}
// Restart progressive rendering when the underlying data array changes (page switch, filter, reload).
// Use shallowRef identity check so sort-only changes (same array reference) don't re-trigger.
const dataIdentity = computed(() => props.data)
watch(dataIdentity, () => {
if (!props.virtualScroll) {
startProgressiveReveal(sortedData.value?.length ?? 0)
}
})
const hasActionsColumn = computed(() => {
return props.columns.some(column => column.key === 'actions')
})
@@ -726,14 +660,10 @@ defineExpose({
--select-col-width: 52px; /* 勾选列宽度:px-6 (24px*2) + checkbox (16px) */
position: relative;
overflow-x: auto;
isolation: isolate;
}
/* Virtual scroll mode: enable vertical scrolling, fill available height */
.table-wrapper.virtual-scroll-container {
overflow-y: auto;
flex: 1;
min-height: 0;
isolation: isolate;
}
/* 表头容器,确保在滚动时覆盖表体内容 */
@@ -142,7 +142,6 @@
default-sort-key="name"
default-sort-order="asc"
:sort-storage-key="ACCOUNT_SORT_STORAGE_KEY"
:virtual-scroll="true"
:estimate-row-height="72"
:overscan="5"
>