需求背景:需要实现Antd Table 组件的列伸缩,宽度可以拖拽
在Antd 3.x 的版本中是保留的列伸缩的Demo例子的:
借助 react-resizable
可以实现伸缩列。
# npm 安装
npm install react-resizable --save
# yarn 安装
yarn add react-resizable
参考官方的Demo,封装一个ResizableTable组件:
import { Table } from 'antd';
import type { ColumnsType } from 'antd/lib/table';
import { useEffect,useState } from 'react';
import { Resizable } from 'react-resizable';
import styles from './resizableTable.less';
/**
* 处理松开鼠标还会拖动的问题
* 参考思路:在点击拖动时,使用浏览器API Selection.removeAllRanges 清空原本误选的文本。
*/
const clearSelection = () => {
if (window.getSelection) {
const selection = window.getSelection();
if (selection) {
if (selection.empty) {
// Chrome
selection.empty();
} else if (selection.removeAllRanges) {
// Firefox
selection.removeAllRanges();
}
}
}
else if (document.selection && document.selection.empty) {
// IE
document.selection.empty();
}
};
export const ResizableTitle = (props: any) => {
const { onResize, width, minWidth, maxWidth, ...restProps } = props;
// 没有原始宽度的列,不支持伸缩;会出现从自适应宽度一下子跳到拖动位置;也可以自行增加参数,如 disableResize
if (!width) {
return <th {...restProps} />;
}
const minConstraints: [number, number] | undefined = minWidth
? [minWidth, -Infinity]
: undefined;
const maxConstraints: [number, number] | undefined = maxWidth
? [maxWidth, +Infinity]
: undefined;
return (
<Resizable
width={width}
height={0} // 不需要调整高度,设为 0
minConstraints={minConstraints}
maxConstraints={maxConstraints}
handle={
<span
className="react-resizable-handle"
onClick={(e) => {
// 阻止冒泡
e.stopPropagation();
}}
/>
}
onResize={onResize}
draggableOpts={{
enableUserSelectHack: false,
onMouseDown: () => {
// 处理在 Windows Chrome 和 Edge 松开鼠标依然能拖动
clearSelection();
},
}}
>
<th {...restProps} />
</Resizable>
);
};
interface DataType {
name: {
first: string;
last: string;
};
gender: string;
email: string;
login: {
uuid: string;
};
}
const columnsData: ColumnsType<DataType> = [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
render: (name) => `${name.first} ${name.last}`,
width: '20%',
},
{
title: 'Gender',
dataIndex: 'gender',
filters: [
{ text: 'Male', value: 'male' },
{ text: 'Female', value: 'female' },
],
width: '20%',
},
{
title: 'Email',
dataIndex: 'email',
},
];
const ResizableTable = () => {
const curColumns: ColumnsType<DataType> = columnsData; // 可以是通过props 传进来的,这里用常量做例子
const [column, setColumns] = useState<ColumnsType<any>>([]);
// 拖动时更新表格列
const handleResize = (index: number) => {
return (_e: any, { size }: any) => {
const newCols = [...column];
newCols[index] = {
...newCols[index],
width: size.width || '100%',
};
setColumns(newCols);
};
};
const mergeColumns = column.map((col, index) => ({
...col,
onHeaderCell: (column: any) => ({
width: column.width ?? 100,
// 每一列增加 minWidth, maxWidth 作为 ResizableTitle 的 props
minWidth: 50,
// maxWidth: 1000,
onResize: handleResize(index),
}),
}));
useEffect(() => {
console.log('变化', curColumns);
if (curColumns) {
setColumns(curColumns);
}
}, [curColumns]);
return (
<div className={styles.resizeTable}>
<Table
size="small"
components={{
header: {
cell: ResizableTitle,
},
}}
columns={mergeColumns}
dataSource={[]}
/>
</div>
);
};
export default ResizableTable;
必须引入样式 resizableTable.less
:
.resizeTable {
:global {
.react-resizable {
position: relative;
background-clip: padding-box;
}
.react-resizable-handle {
position: absolute;
width: 10px;
height: 100%;
bottom: 0;
right: -5px;
cursor: col-resize;
background-image: none;
z-index: 1;
}
.ant-table-filter-column,
.ant-table-column-sorters {
display: flex;
/* co1umn 从上到下 */
align-items: center;
/* center代表水平方向 */
justify-content: space-around;
min-width: 70px;
}
.ant-table-thead>tr>th .ant-table-column-sorter {
// margin-top: -21px;
display: table-cell;
vertical-align: middle;
}
}
}
必须保持一列宽度不设置,自适应。否则效果不对。
但我用这个插件后还是不太 OK,总有一些bug,比如如果拖动了不设置宽的列,整个伸缩就会变形;而且如果列数很多的情况下,自适应列效果不理想。
所有这个方案能用但不是很好用。
可以参考:https://juejin.cn/post/7182423243553734717
后续解决方案:
在查阅资料时,看到有个大佬封装好了一个伸缩hook use-antd-resizable-header
,使用起来方便简单。遂引入项目。
https://github.com/hemengke1997/use-antd-resizable-header
pnpm add @minko-fe/use-antd-resizable-header
引入封装组件示例:
import { Table } from 'antd';
import { useAntdResizableHeader } from '@minko-fe/use-antd-resizable-header';
import '@minko-fe/use-antd-resizable-header/dist/style.css';
/** 自定义函数 */
import { isLocaleEn } from '@/utils/commont_rely';
/** type 类申明 */
import type { IProps } from '..'; // 自己封装的表格propsType, 仅作参考
/** 自定义样式 */
import './style.less';
/** ===================================
* @name: 可伸缩列的表格组件
* 注意:至少一列不能拖动(width 不设置即可),请保持至少一列的宽自适应
*======================================*/
interface ResizableTableProps extends IProps {
// 特殊配置
defaultWidth?: number; // 设置不能拖动列的最小宽度 默认 120
minConstraints?: number; // 拖动最小宽度 默认 60
maxConstraints?: number; // 拖动最大宽度 默认800 可设置无穷
}
export default function ResizableTable(props: ResizableTableProps) {
const { title, defaultWidth, minConstraints, maxConstraints } = props;
const columns = props?.columns || []; // 组件传过来的colums
const { components, resizableColumns, tableWidth } = useAntdResizableHeader({
columns,
defaultWidth: defaultWidth || 120,
minConstraints: minConstraints || 60,
maxConstraints: maxConstraints || 800,
});
return (
<div className="resizableTable">
<Table
title={title}
size="small"
dataSource={data} // 组件传过来的data
columns={resizableColumns}
components={components}
scroll={{ x: tableWidth }}
/>
</div>
);
}
使用方便,效果理想,推荐使用这个插件。