172 lines
4.9 KiB
JavaScript
172 lines
4.9 KiB
JavaScript
import { useRef } from 'react';
|
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
|
|
const VirtualizedTable = ({
|
|
dataSource = [],
|
|
columns = [],
|
|
rowHeight = 54,
|
|
height = '50vh',
|
|
overscan = 10,
|
|
rowKey = 'id',
|
|
}) => {
|
|
const parentRef = useRef();
|
|
const headerRef = useRef();
|
|
|
|
const rowVirtualizer = useVirtualizer({
|
|
count: dataSource.length,
|
|
getScrollElement: () => parentRef.current,
|
|
estimateSize: () => rowHeight,
|
|
overscan,
|
|
});
|
|
|
|
const handleBodyScroll = (e) => {
|
|
if (headerRef.current) {
|
|
headerRef.current.scrollLeft = e.target.scrollLeft;
|
|
}
|
|
};
|
|
|
|
const virtualItems = rowVirtualizer.getVirtualItems();
|
|
const totalSize = rowVirtualizer.getTotalSize();
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
border: '1px solid #d1d1d1',
|
|
borderRadius: '4px',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
}}
|
|
>
|
|
{/* Header */}
|
|
<div ref={headerRef} style={{ overflowX: 'hidden', overflowY: 'hidden' }}>
|
|
<table
|
|
style={{
|
|
borderCollapse: 'collapse',
|
|
width: '100%',
|
|
tableLayout: 'auto',
|
|
}}
|
|
>
|
|
<thead style={{ background: '#2c88ee', color: '#fff' }}>
|
|
<tr className="proReceiptTD">
|
|
{columns.map((col) => (
|
|
<th
|
|
key={col.key || col.dataIndex}
|
|
style={{
|
|
padding: '10px 6px',
|
|
textAlign: col.align || 'left',
|
|
fontWeight: 600,
|
|
fontSize: '13px',
|
|
borderRight: '1px solid rgba(255,255,255,0.3)',
|
|
boxSizing: 'border-box',
|
|
width: col.width || 'auto',
|
|
minWidth: col.width || 80,
|
|
whiteSpace: 'nowrap',
|
|
}}
|
|
>
|
|
{col.title}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
</table>
|
|
</div>
|
|
|
|
{/* Body */}
|
|
<div
|
|
ref={parentRef}
|
|
onScroll={handleBodyScroll}
|
|
style={{
|
|
height,
|
|
overflowY: 'auto',
|
|
overflowX: 'auto',
|
|
position: 'relative',
|
|
scrollbarWidth: 'thin',
|
|
}}
|
|
>
|
|
<div style={{ height: `${totalSize}px`, position: 'relative' }}>
|
|
{virtualItems.map((virtualRow) => {
|
|
const record = dataSource[virtualRow.index];
|
|
const key = record?.[rowKey] || virtualRow.index;
|
|
|
|
return (
|
|
<div
|
|
className="UppertablePR"
|
|
key={key}
|
|
style={{
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
width: '100%',
|
|
height: `${rowHeight}px`,
|
|
transform: `translateY(${virtualRow.start}px)`,
|
|
}}
|
|
>
|
|
<table
|
|
style={{
|
|
borderCollapse: 'collapse',
|
|
width: '100%',
|
|
tableLayout: 'auto',
|
|
height: '100%',
|
|
}}
|
|
>
|
|
<tbody>
|
|
<tr
|
|
className="proReceiptTD"
|
|
style={{
|
|
backgroundColor:
|
|
virtualRow.index % 2 === 0 ? '#e2e2e2' : '#fff',
|
|
borderBottom: '1px solid #f0f0f0',
|
|
}}
|
|
>
|
|
{columns.map((col) => (
|
|
<td
|
|
className="tdProductrecipt"
|
|
key={col.key || col.dataIndex}
|
|
style={{
|
|
padding: '6px',
|
|
borderRight: '1px solid #f0f0f0',
|
|
fontSize: '12px',
|
|
fontWeight: 500,
|
|
boxSizing: 'border-box',
|
|
width: col.width || 'auto',
|
|
minWidth: col.width || 80,
|
|
textAlign: col.align || 'left',
|
|
verticalAlign: 'middle',
|
|
}}
|
|
>
|
|
{col.render
|
|
? col.render(
|
|
record?.[col.dataIndex],
|
|
record,
|
|
virtualRow.index
|
|
)
|
|
: record?.[col.dataIndex]}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{dataSource.length === 0 && (
|
|
<div
|
|
style={{
|
|
textAlign: 'center',
|
|
padding: '20px',
|
|
color: '#999',
|
|
fontSize: '14px',
|
|
}}
|
|
>
|
|
No data
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default VirtualizedTable;
|