feat(ux): U1 虚拟滚动 — Pod/事件/审计/命令列表大数据量渲染优化

- 安装 @tanstack/react-virtual@3.14.4
- 新增 hooks/useVirtualRows.ts 可复用虚拟滚动 Hook
- PodsTab: 全量渲染→虚拟滚动, 支持 1000+ Pod + 动态行高(展开行)
- EventTable: 移除 maxRows=200 截断, 万级事件流畅渲染
- AuditOperationsTab: shadcn Table→虚拟滚动 CSS Grid 布局
- SessionCommandsPanel: 虚拟滚动, page_size 200→500
- npm run build ✅, tsc --noEmit ✅, 17/17 集成测试通过
This commit is contained in:
attacker committed 2026-06-27 09:36:17 +08:00
1 parent e4f1018ca1
commit f2069fe3f0
11 files changed
+673 -330

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
// useVirtualRows — 虚拟滚动 Hook,基于 @tanstack/react-virtual
// 用于大列表(1000+ 行)场景的性能优化,只渲染可见区域的行
// 用法:
// const { scrollRef, virtualRows, totalHeight } = useVirtualRows({
// count: data.length, estimateSize: () => 40, overscan: 10,
// });
import { useRef, useCallback } from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
export interface VirtualRowsOptions {
count: number;
estimateSize: (index: number) => number;
overscan?: number;
/** 滚动容器的 padding 顶部像素(影响虚拟化偏移计算) */
paddingTop?: number;
}
export interface VirtualRowsResult {
/** 绑定到滚动容器 div 的 ref */
scrollRef: React.RefObject<HTMLDivElement>;
/** 当前可见+overscan 范围内的虚拟行 */
virtualRows: ReturnType<ReturnType<typeof useVirtualizer>['getVirtualItems']>;
/** 虚拟容器的总高度(用于设置 spacer 高度) */
totalHeight: number;
/** 滚动到指定 index */
scrollToIndex: (index: number) => void;
}
/**
* 虚拟滚动 Hook
*
* ```tsx
* const { scrollRef, virtualRows, totalHeight } = useVirtualRows({
* count: items.length, estimateSize: () => 40, overscan: 8,
* });
* return (
* <div ref={scrollRef} style={{ height: 500, overflow: 'auto' }}>
* <div style={{ height: totalHeight, position: 'relative' }}>
* {virtualRows.map(vr => (
* <div key={vr.key} style={{ position: 'absolute', top: vr.start, height: vr.size, width: '100%' }}>
* {renderRow(items[vr.index])}
* </div>
* ))}
* </div>
* </div>
* );
* ```
*/
export function useVirtualRows({
count,
estimateSize,
overscan = 8,
}: VirtualRowsOptions): VirtualRowsResult {
const scrollRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count,
getScrollElement: () => scrollRef.current,
estimateSize,
overscan,
});
const virtualRows = virtualizer.getVirtualItems();
const totalHeight = virtualizer.getTotalSize();
const scrollToIndex = useCallback(
(index: number) => {
virtualizer.scrollToIndex(index, { align: 'start' });
},
[virtualizer],
);
return { scrollRef, virtualRows, totalHeight, scrollToIndex };
}