refactor: add simple virtual list

This commit is contained in:
moonrailgun 2024-07-02 22:53:58 +08:00
parent 66ec94fd08
commit b7670da7db
2 changed files with 113 additions and 47 deletions

View File

@ -0,0 +1,69 @@
import { useWatch } from '@/hooks/useWatch';
import { VirtualItem, useVirtualizer } from '@tanstack/react-virtual';
import { Empty } from 'antd';
import { last } from 'lodash-es';
import React, { useRef } from 'react';
interface VirtualListProps<T = any> {
allData: T[];
hasNextPage: boolean | undefined;
isFetchingNextPage: boolean;
onFetchNextPage: () => void;
estimateSize: (index: number) => number;
renderItem: (item: VirtualItem) => React.ReactElement;
}
export const SimpleVirtualList: React.FC<VirtualListProps> = React.memo(
(props) => {
const {
allData,
hasNextPage,
isFetchingNextPage,
onFetchNextPage,
estimateSize,
renderItem,
} = props;
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: hasNextPage ? allData.length + 1 : allData.length,
getScrollElement: () => parentRef.current,
estimateSize,
overscan: 5,
});
const virtualItems = rowVirtualizer.getVirtualItems();
useWatch([virtualItems], () => {
const lastItem = last(virtualItems);
if (!lastItem) {
return;
}
if (
lastItem.index >= allData.length - 1 &&
hasNextPage &&
!isFetchingNextPage
) {
onFetchNextPage();
}
});
return (
<div ref={parentRef} className="h-full w-full overflow-auto">
{virtualItems.length === 0 && <Empty />}
<div
className="relative w-full"
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
}}
>
{virtualItems.map((virtualItem) => renderItem(virtualItem))}
</div>
</div>
);
}
);
SimpleVirtualList.displayName = 'SimpleVirtualList';

View File

@ -13,6 +13,7 @@ import { useVirtualizer } from '@tanstack/react-virtual';
import { useWatch } from '@/hooks/useWatch'; import { useWatch } from '@/hooks/useWatch';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { ColorTag } from '@/components/ColorTag'; import { ColorTag } from '@/components/ColorTag';
import { SimpleVirtualList } from '@/components/SimpleVirtualList';
export const Route = createFileRoute('/settings/auditLog')({ export const Route = createFileRoute('/settings/auditLog')({
beforeLoad: routeAuthBeforeLoad, beforeLoad: routeAuthBeforeLoad,
@ -66,57 +67,53 @@ function PageComponent() {
<CommonWrapper header={<CommonHeader title={t('Audit Log')} />}> <CommonWrapper header={<CommonHeader title={t('Audit Log')} />}>
<ScrollArea className="h-full overflow-hidden p-4"> <ScrollArea className="h-full overflow-hidden p-4">
<List> <List>
<div ref={parentRef} className="h-full w-full overflow-auto"> <SimpleVirtualList
{virtualItems.length === 0 && <Empty />} allData={allData}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
onFetchNextPage={fetchNextPage}
estimateSize={() => 48}
renderItem={(item) => {
const isLoaderRow = item.index > allData.length - 1;
const data = allData[item.index];
<div return (
className="relative w-full" <List.Item
style={{ key={item.index}
height: `${rowVirtualizer.getTotalSize()}px`, className="absolute left-0 top-0 w-full"
}} style={{
> height: `${item.size}px`,
{virtualItems.map((virtualRow) => { transform: `translateY(${item.start}px)`,
const isLoaderRow = virtualRow.index > allData.length - 1; }}
const item = allData[virtualRow.index]; >
{isLoaderRow ? (
return ( hasNextPage ? (
<List.Item t('Loading more...')
key={virtualRow.index}
className="absolute left-0 top-0 w-full"
style={{
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{isLoaderRow ? (
hasNextPage ? (
t('Loading more...')
) : (
t('Nothing more to load')
)
) : ( ) : (
<div className="flex h-7 items-center overflow-hidden"> t('Nothing more to load')
{item.relatedType && ( )
<ColorTag label={item.relatedType} /> ) : (
<div className="flex h-7 items-center overflow-hidden">
{data.relatedType && (
<ColorTag label={data.relatedType} />
)}
<div
className="mr-2 w-9 text-xs opacity-60"
title={dayjs(data.createdAt).format(
'YYYY-MM-DD HH:mm:ss'
)} )}
<div >
className="mr-2 w-9 text-xs opacity-60" {dayjs(data.createdAt).format('MM-DD HH:mm')}
title={dayjs(item.createdAt).format(
'YYYY-MM-DD HH:mm:ss'
)}
>
{dayjs(item.createdAt).format('MM-DD HH:mm')}
</div>
<div className="h-full flex-1 overflow-auto">
{item.content}
</div>
</div> </div>
)} <div className="h-full flex-1 overflow-auto">
</List.Item> {data.content}
); </div>
})} </div>
</div> )}
</div> </List.Item>
);
}}
/>
</List> </List>
</ScrollArea> </ScrollArea>
</CommonWrapper> </CommonWrapper>