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、导入所需组件:在你的代码文件中导入所需的组件:

TypeScript 复制代码
import { Resizable, ResizableProps } from 'react-resizable';

2、创建可调整大小的列组件:为了实现表格拖拽功能,你需要为每一列创建一个可调整大小的组件。在每个表格列的头部,使用Resizable组件来包裹列的内容:

TypeScript 复制代码
/**
 * 公共组件:实现拖拽
 */
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属性中,引入该拖拽组件

javascript 复制代码
 <Table
   ...
      {...(isResizable
        ? {
            components: {
              header: {
                cell: ResizableTitle, // 动态拖拽设置列宽
              },
            },
          }
        : {})}
      columns={getColumns(currentColumns)}
      {...props}
    />

2、对每一列的onHeaderCell都加上拖拽的属性resizable,控制该列是否可拖拽,配置onResize的回调方法

javascript 复制代码
  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、拖拽后更新表格的列宽

javascript 复制代码
// 拖拽后更新表格宽度
  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、具体实现如下:

TypeScript 复制代码
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、拖拽要设置最小宽度和最大宽度,防止拖拽过程中找不到元素

相关推荐
赵啸林2 分钟前
npm发布插件超级简单版
前端·npm·node.js
我码玄黄34 分钟前
THREE.js:网页上的3D世界构建者
开发语言·javascript·3d
罔闻_spider35 分钟前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔37 分钟前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab
爱喝水的小鼠1 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
小晗同学1 小时前
Vue 实现高级穿梭框 Transfer 封装
javascript·vue.js·elementui
WeiShuai1 小时前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript
Wandra1 小时前
很全但是超级易懂的border-radius讲解,让你快速回忆和上手
前端
forwardMyLife1 小时前
element-plus的面包屑组件el-breadcrumb
javascript·vue.js·ecmascript
ice___Cpu1 小时前
Linux 基本使用和 web 程序部署 ( 8000 字 Linux 入门 )
linux·运维·前端