Antd+React+react-resizable实现表格拖拽功能
1、先看效果
2、环境准备
在package.json 引入相关的依赖
"dependencies": {
"antd": "^5.4.0",
"react-resizable": "^3.0.4",
},
"devDependencies": {
"@types/react": "^18.0.33",
"@types/react-resizable": "^3.0.1",
}
3、功能实现
一、拖拽组件实现
1、导入所需组件:在你的代码文件中导入所需的组件:
import { Resizable, ResizableProps } from 'react-resizable';
2、创建可调整大小的列组件:为了实现表格拖拽功能,你需要为每一列创建一个可调整大小的组件。在每个表格列的头部,使用Resizable
组件来包裹列的内容:
/**
* 公共组件:实现拖拽
*/
import { isNumber } from 'lodash';
import { StyleHTMLAttributes } from 'react';
import { Resizable, ResizableProps } from 'react-resizable';
type ResizableTitleProps = ResizableProps & {
resizable?: boolean;
style: StyleHTMLAttributes<any>;
};
const ResizableTitle = (props: ResizableTitleProps) => {
const { onResize, width, resizable, ...restProps } = props;
if (!width || !resizable) {
return <th {...restProps} />;
}
let resizeWidth: any = width;
if (!isNumber(resizeWidth)) {
resizeWidth = Number(resizeWidth.replace('px', ''));
}
return (
<Resizable
width={resizeWidth}
height={0}
handle={
<span
className="react-resizable-handle"
onClick={(e) => {
e.stopPropagation();
}}
/>
}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: true }}
// maxConstraints={[800, 800]}
>
<th
{...restProps}
style={{
...restProps?.style,
width: `${resizeWidth}px`,
borderRight: '1px solid rgba(2, 9, 23, 70%)',
}}
/>
</Resizable>
);
};
export default ResizableTitle;
在上面的代码中,我们使用Resizable
组件来包裹了列的内容。handle
属性定义了列的调整大小的手柄样式,你可以根据需要自定义。draggableOpts
属性用于配置调整大小的选项。
二、在antd写入tab,并引用该拖拽组件
1、在table的components属性中,引入该拖拽组件
<Table
...
{...(isResizable
? {
components: {
header: {
cell: ResizableTitle, // 动态拖拽设置列宽
},
},
}
: {})}
columns={getColumns(currentColumns)}
{...props}
/>
2、对每一列的onHeaderCell都加上拖拽的属性resizable,控制该列是否可拖拽,配置onResize的回调方法
const getColumns = (columns: any) => {
return (columns || []).map((col: any, idx: number) => {
return {
...col,
onHeaderCell: (column: any) => ({
width: column.width || 100,
resizable: isResizable && !column?.fixed,
onResize: handleResize(idx, col.dataIndex as string),
}),
};
});
};
3、拖拽后更新表格的列宽
// 拖拽后更新表格宽度
const handleResize =
(index: number, colDataIndex?: string) =>
(e: any, { size }: { size: { width: number } }) => {
if (!colDataIndex) {
return;
}
setCurrentColumns((pre) => {
let temp = [...pre];
temp[index] = {
...temp[index],
width: size.width < 50 ? 50 : size.width,
};
computedWidth(temp);
return temp;
});
};
4、具体实现如下:
import { Table } from 'antd';
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import ResizableTitle from './ResizableTitle';
import styles from './index.less';
export interface ListTableProps {
className?: any;
rowClassName?: any;
dimension?: number;
columns?: any;
dataSource?: any;
pagination?: any;
scroll?: object;
virtual?: boolean;
rowKey?: any;
isShowScrollX?: boolean;
vid?: string;
isResizable?: boolean; //是否可退拽
onChange?: (pagination: any, filters: any, sorter: any) => void;
}
// 暂无数据组件
const NoDataComponent = () => {
return (
<div className={clsx(['h-[250px]', 'flex justify-center items-center'])}>
<div
className={clsx([
'w-[76px] h-[94px]',
'bg-[url("/images/no-data.svg")] bg-no-repeat',
])}
/>
</div>
);
};
const ListTable: React.FC<ListTableProps> = ({
className,
rowClassName = () => '',
onChange,
dataSource,
isShowScrollX,
defaultFixedNode,
columns: initCols,
isResizable,
vid = 'resize_table',
...props
}) => {
const [currentColumns, setCurrentColumns] = useState([]);
const [leftRightNodeIsFixed, setLeftRightNodeIsFixe] =
useState(defaultFixedNode); // 左右节点是否固定
useEffect(() => {
setCurrentColumns(initCols);
}, [initCols]);
useEffect(() => {
setCurrentColumns(initCols);
}, [initCols]);
// 计算宽度,当出现底部滚动条时,最左最右节点固定
const computedWidth = (columns: any) => {
const widthAll =
document.getElementsByClassName('ant-table-body')?.[0]?.clientWidth;
const currentTabWidth = (columns || []).reduce((pre: number, cur: any) => {
return Number(pre) + (Number(cur?.width) || 0);
}, 0);
setLeftRightNodeIsFixe(currentTabWidth > widthAll);
};
// 拖拽后更新表格宽度
const handleResize =
(index: number, colDataIndex?: string) =>
(e: any, { size }: { size: { width: number } }) => {
if (!colDataIndex) {
return;
}
setCurrentColumns((pre) => {
let temp = [...pre];
temp[index] = {
...temp[index],
width: size.width < 50 ? 50 : size.width,
};
computedWidth(temp);
return temp;
});
};
const getColumns = (columns: any) => {
return (columns || []).map((col: any, idx: number) => {
return {
...col,
onHeaderCell: (column: any) => ({
width: column.width || 100,
resizable: isResizable && !column?.fixed,
onResize: handleResize(idx, col.dataIndex as string),
}),
};
});
};
return (
<Table
rowClassName={(record, index) => {
return rowClassName(record, index);
}}
locale={{ emptyText: <NoDataComponent /> }}
{...(isResizable
? {
components: {
header: {
cell: ResizableTitle, // 动态拖拽设置列宽
},
},
}
: {})}
columns={getColumns(currentColumns)}
onChange={onChange}
dataSource={dataSource}
{...props}
/>
);
};
export default ListTable;
4、常见问题:
1、拖拽时,鼠标离开,拖拽被还原,80%原因是因为父组件触发了useState更新,column被还原成初始态,
2、拖拽要设置最小宽度和最大宽度,防止拖拽过程中找不到元素