1. 功能介绍、建议、官网链接
- 建议:走一遍官网的虚拟列表(最好走通)
- 官网虚拟列表功能都有;但是!官网虚拟列表里面跟之前表格
.ant-table-tbody 相关的功能都无了,因为这块官网是整体替换了 .ant-table-tbody 这一级的所有内容 - 添加功能:
(1) 全选与单选功能 (2)滚动加载功能(虚拟列表级滚动加载) (3)定位某一行 (4)表格字体闪烁功能 (5)可对表头进行隐藏或显示配置,表格内部内容进行宽度适配 (6)高度设置(这块可能得靠你自己再去深度研究一下这块了) - react-window官网网站需要翻墙才能去访问哦
- ant design 官网表格
2. 效果图
3. 二次封装虚拟列表组件 LhVirtualTable 代码(virtualTable/components/LhVirtualTable/index.tsx)
import { Table, Checkbox } from 'antd';
import styles from './index.less'
import ResizeObserver from 'rc-resize-observer';
import React, { useEffect, useRef, useState } from 'react';
import { VariableSizeGrid as Grid } from 'react-window';
import _ from 'lodash';
const findNeedNode: any = (node: any, key: string = 'ant-table-body') => {
if (node?.className === key) return node;
if (node?.children) {
let needNode: any = [];
node.children.forEach((child: any) => {
needNode.push(findNeedNode(child, key))
})
return needNode;
}
}
const getByteLen = (val:string) => {
var len = 0;
for (var i = 0; i < val.length; i++) {
var a = val.charAt(i);
if (a.match(/[^\x00-\xff]/ig) != null) {
len += 2;
} else {
len += 1;
}
}
return len;
}
const LhVirtualTable = (props: any) => {
const { dataSource, columns, scroll } = props;
const [tableWidth, setTableWidth] = useState(0);
const { lh__pagination, lh__onScrollBottom, lh__scrollIndex } = props;
const [oldDataSourceLen, setOldDataSourceLen] = useState<any>(0)
const tableRef = useRef<any>(null)
const _onScroll: any = (res: { scrollLeft: number, scrollTop: number }) => {
const { scrollLeft, scrollTop } = res;
const dom = findNeedNode(tableRef.current, 'virtual-grid')?.flat(Infinity)[0];
const clientHeight = dom?.children[0]?.clientHeight
if (!clientHeight) return;
const scrollBarHeight = 16;
if ((clientHeight - scroll.y) + scrollBarHeight > scrollTop + 1 + lh__pagination.pageNum) return;
const { pageSize, pageNum, total } = lh__pagination;
const all = pageSize * pageNum;
if (all > total) return;
if (dataSource.length < 1) return;
if (oldDataSourceLen === dataSource.length) return;
setOldDataSourceLen(dataSource.length)
lh__onScrollBottom({ ...lh__onScrollBottom, pageNum: pageNum + 1 });
}
useEffect(() => {
scrollPosition(lh__scrollIndex)
}, [lh__scrollIndex])
const scrollPosition = (index: number) => {
console.log(index, 'i')
const dom = findNeedNode(tableRef.current, 'virtual-grid')?.flat(Infinity)[0];
gridRef.current.scrollToItem({ rowIndex: index });
const scrollDom = dom.children[0]
setTimeout(() => {
scrollDom.children.forEach((node: any, i: number) => {
node.style.background = 'transparent';
if (node?.id?.includes(`lh-${index}`)) {
node.style.background = 'rgba(10, 177, 205, .6)';
}
})
}, 0);
}
const [gridKey, setGridKey] = useState(0)
const [mergedColumns, setMergedColumns] = useState([]);
useEffect(() => {
const widthColumnCount = columns!.filter(({ width }: any) => !width).length;
const wCFilterHidden = columns!.filter(({ hidden, width }: any) => hidden && !width).length;
const usedWidth = columns!.reduce((pre: number, next: { width: number, hidden: Boolean }) => {
if (next.hidden) return pre
return next.width ? pre + +next.width : pre;
}, 0)
setMergedColumns(columns.filter((v: { hidden: boolean }) => !v.hidden)!.map((column: any) => {
if (column.width) {
const widthP = +column.width;
if (widthColumnCount === 0) return { ...column, width: (widthP / usedWidth) * tableWidth }
return wCFilterHidden < 1 ? { ...column, width: widthP } : { ...column, width: (widthP / usedWidth) * tableWidth };
}
return {
...column,
width: Math.floor((tableWidth - usedWidth) / widthColumnCount),
};
}))
setGridKey(gridKey + 1)
}, [columns, tableWidth])
const gridRef = useRef<any>();
const [connectObject] = useState<any>(() => {
const obj = {};
Object.defineProperty(obj, 'scrollLeft', {
get: () => {
if (gridRef.current) {
return gridRef.current?.state?.scrollLeft;
}
return null;
},
set: (scrollLeft: number) => {
if (gridRef.current) {
gridRef.current.scrollTo({ scrollLeft });
}
},
});
return obj;
});
const resetVirtualGrid = () => {
gridRef.current?.resetAfterIndices({
columnIndex: 0,
shouldForceUpdate: true,
});
};
useEffect(() => resetVirtualGrid, [tableWidth]);
const renderVirtualList = (rawData: object[], { scrollbarSize, ref, onScroll }: any) => {
ref.current = connectObject;
const totalHeight = rawData.length * 54;
return (
<Grid
ref={gridRef}
className="virtual-grid"
key={gridKey}
columnCount={mergedColumns.length}
columnWidth={(index: number) => {
const { width } = mergedColumns[index];
return totalHeight > scroll!.y! && index === mergedColumns.length - 1
? (width as number) - scrollbarSize - 1
: (width as number);
}}
height={scroll!.y as number}
rowCount={rawData.length}
rowHeight={(index: number) => {
const width = tableRef.current?.clientWidth;
console.log(width, 'uyyy')
const baseNumber = 24 * ((+(width > (1920 / 2)) +1))
const row = rawData[index];
if (!row) return;
const max = Object.values(row).reduce((pre, next) => {
let len = getByteLen(`${next}`)
if (pre > len) return pre
else return len
}, 0)
console.log(baseNumber, 'uyyy')
return + ((Math.ceil(max / baseNumber) * 24).toFixed(2))
}}
width={tableWidth}
onScroll={(res: { scrollLeft: number }) => {
const { scrollLeft } = res;
onScroll({ scrollLeft });
_onScroll(res)
}}
>
{({
columnIndex,
rowIndex,
style,
}: {
columnIndex: number;
rowIndex: number;
style: React.CSSProperties;
}) => {
const index = rowIndex;
const column = (mergedColumns as any)[columnIndex];
const record = (rawData[index] as any)
const text = record[column.dataIndex]
return (
<div
id={`lh-${rowIndex}-${columnIndex}`}
className={['virtual-table-cell', columnIndex === mergedColumns.length - 1 ? 'virtual-table-cell-last' : ''].join(' ')}
style={{ ...style, display: 'flex', alignItems: 'center' }}
onContextMenu={(event) => {
event.stopPropagation();
event.preventDefault();
console.log(event, record, 'am')
}}
>
<div style={{ width: '100%', textAlign: column.align || 'left', lineHeight: '18px' }}>
{
column.render ? column.render(text, record, index) : `${rowIndex}-${columnIndex}`
}
</div>
</div>
)
}}
</Grid>
);
};
return <>
<ResizeObserver
onResize={({ width }) => {
setTableWidth(width);
}}
>
<Table
{...props}
className={styles['virtual-table']}
columns={mergedColumns.filter((v: any) => !v.hidden)}
ref={tableRef}
pagination={false}
components={{
body: renderVirtualList,
}}
/>
</ResizeObserver>
</>
}
export default LhVirtualTable
4. 使用 virtualTable/index.tsx
import { Checkbox, Button, Popover } from 'antd';
import { useRef, useState, useEffect } from 'react';
import LhVirtualTable from './components/LhVirtualTable'
import styles from './index.less'
import _ from 'lodash';
const dataName = ['涨', '车', '轴', '走', '周', '凤', '胡', '晶', '京', '梅', '韦', '小', '字', '陈', '程', '测', '就', '当', '费', '飞', '矿', '况', '李', '刘', '成', '龙', '于', '巷', '港', '翔']
const serviceArr: any = []
const service = (param: any) => {
const { pageNow, pageSize } = param
const createData = (arr: any) => {
const random = (number: any) => Math.floor(Math.random() * number).toFixed(0)
const nameFn = () =>
Array.from(
new Array(+random(3) < 2 ? 2 : 3),
() => dataName[+random(dataName.length)]
).join('')
const data = Array.from(new Array(pageSize), (x, i) => nameFn()).map(
(name, i) => ({ name, checked: false, key: i, len: new Array(+random(20) + 1).fill('').map(v=> name).join(',') })
)
arr.push(...data)
}
createData(serviceArr)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: serviceArr.slice(pageSize * (pageNow - 1), pageSize * pageNow),
total: 300000
})
}, 500)
})
}
const colorChange = (t: any) => {
const colorList = ['#f00', '#0f0', '#00f', '#0ff', '#f60', '#f0f'];
const random = +(Math.random() * +colorList.length - 1).toFixed(0)
return <span
className={styles['animation_twinkle']}
style={{
color: colorList[random]
}}
>
{t}
</span>
}
const virtualTable = (props: any) => {
const [columns, setColumns] = useState<any>([
{
title: <></>,
width: 30,
align: 'center',
render: (text: any, record: any, index: any) => <Checkbox
style={{ marginLeft: '6px' }}
checked={selectedRowKeys.includes(record.key)}
indeterminate={selectedRowKeys.includes(record.key)}
onChange={(e) => {
if (e.target.checked) {
rowSelection.selectedRowKeys.push(record.key);
} else {
const i = rowSelection.selectedRowKeys.findIndex((v: any) => v === record.key);
rowSelection.selectedRowKeys.splice(i, 1)
}
rowSelection.onChange(rowSelection.selectedRowKeys)
}}
></Checkbox>
},
{ title: 'A', dataIndex: 'key', width: 150, render: (t: any) => colorChange(t) },
{ title: 'B', dataIndex: 'key', render: (t: any) => colorChange(t) },
{ title: 'C', dataIndex: 'key', render: (t: any) => colorChange(t) },
{ title: 'D', dataIndex: 'key', render: (t: any) => colorChange(t) },
{ title: 'E', dataIndex: 'key', width: 200, render: (t: any) => colorChange(t) },
{ title: 'F', dataIndex: 'key', width: 100, render: (t: any) => colorChange(t) },
{ title: 'len', dataIndex: 'len', width: 400, render: (t: any) => colorChange(t) },
]);
const [data, setData] = useState<any>([])
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const rowSelection = {
selectedRowKeys,
hideSelectAll: false,
onChange: (newSelectedRowKeys: React.Key[]) => {
console.log(newSelectedRowKeys, 'newSelectedRowKeys')
setSelectedRowKeys(_.cloneDeep(newSelectedRowKeys))
},
}
const [loading, setLoading] = useState(false)
const [lh__pagination, setLh__pagination] = useState({
pageSize: 1000,
pageNum: 1,
total: 0
})
const param = {
pageNow: lh__pagination.pageNum,
pageSize: lh__pagination.pageSize
}
const getList = (lh__pagination: any) => {
setLoading(true)
service(param)
.then(({ data: ds, total }: any) => {
data.push(...ds)
lh__pagination.total = total;
setLh__pagination(_.cloneDeep(lh__pagination))
setData(_.cloneDeep(data))
})
.finally(() => {
setLoading(false)
})
}
useEffect(() => {
getList(lh__pagination)
}, [])
const [lh__scrollIndex, setLh__scrollIndex] = useState(0)
const scrollPosition = () => {
setLh__scrollIndex(+(Math.random() * data.length).toFixed(0))
}
const PopoverContent = () => {
const hiddenTitle = columns.filter((v: any) => _.isString(v.title)).map((item: any) => (!item.hidden && item.title) || '');
const plainOptions = columns.filter((v: any) => _.isString(v.title)).map((item: any) => item.title);
const onChange = (e: Array<any>) => {
columns.forEach((item: any) => {
if (!e.includes(item.title)) item.hidden = true;
else item.hidden = false;
})
setColumns(_.cloneDeep([...columns]))
}
return <>
<Checkbox.Group
className='flex-column'
options={plainOptions}
defaultValue={hiddenTitle}
style={{ width: '100px' }}
onChange={onChange} />
</>
}
const marginRight10 = {
marginRight: '10px'
}
return <>
<Button style={{ ...marginRight10 }} type='primary' onClick={() => scrollPosition()}>进行scroll随机定位</Button>
<Popover placement="bottom" content={PopoverContent} trigger="click">
<Button type='primary'>对表头进行选择</Button>
</Popover>
<LhVirtualTable
columns={columns}
dataSource={data}
rowSelection={rowSelection}
loading={loading}
scroll={{ y: 300, x: '100vw' }}
lh__scrollIndex={lh__scrollIndex}
lh__pagination={lh__pagination}
lh__onScrollBottom={(pa: any) => {
const pag = _.cloneDeep({ ...lh__pagination, ...pa })
setLh__pagination(pag)
getList(pag)
}}
/>
</>
}
export default virtualTable
5. 布灵布灵效果样式文件 virtualTable/index.less
@keyframes twinkle {
0% {}
50% {
color: #000;
}
}
.animation_twinkle {
animation: twinkle 1s infinite;
}
|