Ant-Design Table 可伸缩列(保姆级教程)

145 阅读3分钟

实现功能

  1.  列可拖拽拉伸
  2.  实现宽度存储(避免切换 page 导致宽度重置,我这里存到了GlobalStore 里设置关闭 Tab 也不会重置,可根据自己需要的位置存贮)
  3.  最大宽度最小宽度设置
import React, {
    forwardRef,
    useState,
    useEffect,
    useCallback,
    useMemo,
    ForwardedRef
} from "react";
import { Table as AntdTable, TableColumnType, TableProps } from "antd";
import { Resizable } from "react-resizable";
import "react-resizable/css/styles.css";
import "./resizableTable.less";
import { GlobalStore } from "@/stores";


// 添加列宽配置规则
// 1、在 GlobalStore 添加对应的列宽参数及默认值
// 2、在 columnWidthConfig 中添加对应的表格和列映射关系 
// "pageTableKey(ResizableTable 里设置 pageTableKey value)": {
//     columnName: "列宽度对应名称(对应 GlobalStore 中的参数名称)"
// }
// 3、在 minWidthConfig 中添加对应的最小列宽配置
// "pageTableKey": {
//     columnName: number, // 最小宽度值
// },


// 定义可调整列宽的表格属性
interface ResizableColumn<T> extends TableColumnType<T> {
    minWidth?: number;
    maxWidth?: number;
    resizable?: boolean;
}
 
interface ResizableTableProps<T> extends Omit<TableProps<T>, "columns"> {
    columns: ResizableColumn<T>[];
    pageTableKey?: string;
}
 
// 定义 ResizableHeader 的 props 类型
interface ResizableHeaderProps {
    width?: number;
    minWidth?: number;
    maxWidth?: number;
    resizable?: boolean;
    onResize?: (width: number) => void;
    children?: React.ReactNode;
    [key: string]: any;
}
 
// 可调整列宽的表头单元格组件
const ResizableHeader: React.FC<ResizableHeaderProps> = (props) => {
    const {
        width = 100,
        minWidth = 100,
        maxWidth = 1000,
        resizable = false,
        onResize,
        children,
        ...restProps
    } = props;
 
    const [isResizing, setIsResizing] = useState(false);
 
 
    const handleResize = useCallback((e: any, { size }: any) => {
        e.stopPropagation();
        onResize?.(size.width);
    }, [onResize]);
 
    // 如果列不可调整,直接返回表头
    if (!resizable || !width) {
        return (
            <th {...restProps}>
                <div style={{ width: "100%", height: "100%", display: "flex", alignItems: "center" }}>
                    {children}
                </div>
            </th>
        );
    }
 
    return (
        <Resizable
            width={width}
            height={0}
            minConstraints={[minWidth, 0]}
            maxConstraints={[maxWidth, 0]}
            onResizeStart={() => setIsResizing(true)}
            onResizeStop={() => setIsResizing(false)}
            onResize={handleResize}
            resizeHandles={["e"]}
        >
            <th {...restProps}>
                <div
                    style={{
                        width: "100%",
                        height: "100%",
                        display: "flex",
                        alignItems: "center",
                        // position: "relative"
                    }}
                >
                    {children}
                    {/* 拖拽手柄 */}
                    <div
                        style={{
                            // position: "absolute",
                            top: 0,
                            right: "-5px",
                            width: 5,
                            height: "100%",
                            cursor: "col-resize",
                            backgroundColor: isResizing ? "#1890ff" : "transparent",
                            opacity: isResizing ? 0.5 : 0,
                            transition: "opacity 0.2s"
                        }}
                    />
                </div>
            </th>
        </Resizable>
    );
};
 
 
const columnWidthConfig: Record<string, Record<string, string>> = {
    "you-pageTableKey": {
        columnName: "your-GlobalStore-columnNameWidth",
    },
   
};
// 可调整列宽的表格组件
const ResizableAntdTable = forwardRef(<T extends object>(
    props: ResizableTableProps<T>,
    ref: ForwardedRef<HTMLDivElement>
) => {
    const { columns, className, pageTableKey, ...restProps } = props;
    const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
    const [isInitialized, setIsInitialized] = useState(false);
 
    // 初始化列宽 - 从 GlobalStore 恢复已保存的列宽
    useEffect(() => {
        if (isInitialized) return;
 
        const initialWidths: Record<string, number> = {};
        columns.forEach((column, index) => {
            const columnKey: any = column.key || column.dataIndex?.toString() || `column-${index}`;
            let savedWidth: number | undefined;
            if (pageTableKey) {
                const pageConfig = columnWidthConfig[pageTableKey];
                if (pageConfig) {
                    const paramKey = pageConfig[columnKey];
                    if (paramKey) {
                        savedWidth = GlobalStore[paramKey];
                    }
                }
            }
            initialWidths[columnKey] = savedWidth !== undefined ? savedWidth : (column.width as number || 100);
        });
 
        setColumnWidths(initialWidths);
        setIsInitialized(true);
    }, [columns, isInitialized, pageTableKey]);
 
    // 处理列宽调整
    const handleResize = useCallback((columnKey: string, width: number) => {
        setColumnWidths(prev => {
            const newColumnWidths = { ...prev, [columnKey]: width };
 
            if (pageTableKey) {
                const pageConfig = columnWidthConfig[pageTableKey];
                if (pageConfig) {
                    const paramKey = pageConfig[columnKey];
                    if (paramKey) {
                        GlobalStore.changeParams(paramKey, width);
                    }
                }
            }
            return newColumnWidths;
        });
    }, [pageTableKey]);
 
    // 创建配置映射对象
    const minWidthConfig = useMemo(() => ({
        "you-pageTableKey": {
            columnName: number(最小宽度),
        },
        
    }), []);
 
    // 处理列配置
    const processedColumns = useMemo(() => {
        return columns.map((column, index) => {
            const columnKey: any = column.key || column.dataIndex?.toString() || `column-${index}`;
            const currentWidth = columnWidths[columnKey] !== undefined
                ? columnWidths[columnKey]
                : (column.width as number || 100);
 
            // 计算最小宽度
            let columnMinWidth: number = 100;
            if (pageTableKey && minWidthConfig[pageTableKey as keyof typeof minWidthConfig]) {
                const pageConfig = minWidthConfig[pageTableKey as keyof typeof minWidthConfig];
                if (columnKey in pageConfig) {
                    columnMinWidth = pageConfig[columnKey as keyof typeof pageConfig];
                }
            }
 
            return {
                ...column,
                width: currentWidth,
                onHeaderCell: () => ({
                    width: currentWidth,
                    minWidth: column.minWidth || columnMinWidth,
                    maxWidth: column.maxWidth || 1000,
                    resizable: column.resizable || false,
                    onResize: (width: number) => handleResize(columnKey, width),
                    style: {
                        padding: 0,
                        position: "relative" as const,
                        overflow: "hidden"
                    }
                })
            };
        });
    }, [columns, columnWidths, handleResize, minWidthConfig, pageTableKey]);
 
    // 配置表格组件
    const components = {
        header: {
            cell: ResizableHeader,
        },
    };
 
    return (
        <AntdTable<T>
            ref={ref as React.LegacyRef<any>}
            columns={processedColumns}
            components={components}
            {...restProps}
            className={`resizable-table ${className || ""}`}
        />
    );
}) as <T extends object>(props: ResizableTableProps<T> & { ref?: ForwardedRef<HTMLDivElement> }) => JSX.Element;
 
export default ResizableAntdTable;

使用方法

import { ResizableTable } from "@/components";//注册成公共组件
 
<ResizableTable
      pageTableKey="your-pageTableKey"
 />

开发期间问题&思路

思路1:通过自定义 columns 的 title 渲染方法,在 title 中返回一个 Resizable 组件,并手动处理列宽状态和拖拽事件。

问题:但是后来发现这样会导致如果列有排序的话会导致排序的图标在最右侧,拖拽图标在排序左侧。发现拖拽是加到 Title 的元素里,而排序图标官方默认是 ::after 在之后调试很久发现无法解决这个问题就换下一个思路。(有能解决的大佬可以讨论下)

思路2:利用 Antd Table 的 components 属性,自定义表头单元格(HeaderCell)为可调整宽度的ResizableHeader 组件,并通过 onHeaderCel 属性将必要的参数传递给自定义表头单元格。

问题:注意有些表格有自定义的 component,这里就遇到了坑,这里表格用 components 但是却只写了一个 body,导致自定义被组件原生的覆盖掉了。

解决思路:在组件内导出  ResizableHeader,加上header。

<ResizableTable
      components={{
            header: {
                cell: ResizableHeader
            },
            body: {
                cell: ({ children, ...props }: CellProps) => <td {...props}>{children}</td>
                }
            }}
      pageTableKey="your-pageTableKey"
 />