Antd Table 拖动控制列宽度
介绍如何实现 antd table 组件头部可通过拖拽来改变列的宽度
背景
最近一个项目有一个需求, 使用表格组件时需要让表格头部列的边框可以横向拖拽, 并且拖拽后需要改变该列的宽度, antd 的 table 组件中并没有实现这个功能, 所以需要自己二次封装
需求
- 表格列头部右部边框可横向拖拽
- 拖拽中显示一条贯穿表格的纵向虚线展示当前拖到的位置
- 拖拽完成后更新该列的宽度到鼠标松开后的位置
封装 table 组件
由于 antd table 并没有提供类似功能, 所以需要将 table 组件二次封装, 以下是封装后的 Table 组件代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| import { Table as AntdTable, TableProps } from "antd"; import { useState } from "react";
import { DraggableHeaderBorder } from "./ResizableHeader";
interface ITableRowProps { children: { key: string; props: { children: string; column: { dataIndex: string; title: string; key: string; width?: number; }; }; }[]; }
export const Table = <T,>({ columns, ...props }: TableProps<T>) => { const [mergedColumns, setCol] = useState(columns);
return ( <AntdTable<T> {...props} columns={mergedColumns} components={{ header: { row: (props: ITableRowProps) => { return ( <tr> // 便遍历所有 column, 并重新生成自定义的 th {props.children.map((item) => { return ( <DraggableHeaderBorder key={item.key} title={item.props.children} onChange={(width) => { const key = item.key; setCol( mergedColumns?.map((col) => col.key === key ? { ...col, width } : col ) ); }} width={item.props.column.width} /> ); })} </tr> ); }, }, }} /> ); };
|
其中, DraggableHeaderBorder 是可拖拽组件, 该组件的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
| import { FC, memo, useEffect, useRef } from "react";
interface IProps { title: string; onChange: (width: number) => void; width?: number; }
const defaultClasses = "w-1 border-r";
const beforeDraggingClasses = "absolute top-1/2 -translate-y-1/2 right-0 h-4 border-secondary-neutral";
const draggingClasses = "border-dashed fixed border-quaternary-content z-[9999]";
export const DraggableHeaderBorder: FC<IProps> = memo( ({ title, width, onChange }) => { const thRef = useRef<HTMLTableCellElement>(null); const borderRef = useRef<HTMLSpanElement>(null); const initLeft = useRef<number>(0); const endLeft = useRef<number>(0);
function handleDragStart(e: MouseEvent) { e.preventDefault(); e.stopPropagation(); initLeft.current = Math.floor(e.clientX);
const height = (e.target! as HTMLElement).closest(".ant-table-container") ?.clientHeight ?? 0; const top = e.clientY; borderRef.current!.setAttribute( "class", defaultClasses + " " + draggingClasses );
borderRef.current!.setAttribute( "style", `left:${initLeft.current}px;top:${top}px;height:${height}px` );
window.addEventListener("mousemove", handleDragOver); window.addEventListener("mouseup", handleDragEnd); }
function handleDragOver(e: MouseEvent) { e.preventDefault(); e.stopPropagation(); const x = Math.floor(e.clientX); borderRef.current!.style.left = `${x}px`; }
function handleDragEnd(e: MouseEvent) { e.preventDefault(); e.stopPropagation(); if (borderRef.current) { endLeft.current = borderRef.current.offsetLeft; borderRef.current.setAttribute( "class", defaultClasses + " cursor-col-resize " + beforeDraggingClasses ); borderRef.current.setAttribute("style", "");
const diff = endLeft.current - initLeft.current; const width = thRef.current!.offsetWidth + diff; onChange(width); } window.removeEventListener("mousemove", handleDragOver); }
useEffect(() => { if (borderRef.current && width) { borderRef.current.addEventListener("mousedown", handleDragStart); }
return () => { if (borderRef.current && width) { borderRef.current.removeEventListener("mousedown", handleDragStart); window.removeEventListener("mouseup", handleDragEnd); } }; }, []);
return ( <th className="relative h-[32px] !p-3 before:!content-none" ref={thRef} style={width ? { width } : {}} > <span className="inline-block w-full">{title}</span> <span ref={borderRef} className={ defaultClasses + " " + beforeDraggingClasses + `${width ? " cursor-col-resize" : ""}` } ></span> </th> ); } );
|
该组件生成一个带可拖拽元素的 th 元素, 将其导入到 antd table 的 components api 中的 header.row 字段作为替换的元素即可
随后引入封装后的组件, 使用和 antd table 一样的 api, 即可渲染出能通过拖拽改变列宽度的表格