antd table 可伸缩列多表头实现

73 阅读3分钟

antd table 可伸缩列多表头实现

项目简介

React Resizable的核心是一个名为<Resizable>的React组件,它通过监听鼠标或触摸事件,使用户能够直接拖动边框以改变元素的尺寸。这个组件不仅提供了一种直观的方式来改变元素大小,还支持回调函数,以便在尺寸变化时更新状态和重新渲染组件。

react-resizable

拖动前.png

image.png

拖动后.png

image.png

自定义 useTableResizable hooks
import React, { useState, useCallback, useMemo } from 'react';
import { ColumnProps } from '~/Interface';

interface Props {
  columns:any[];
}

export const useTableResizable = (props: Props): [ColumnProps[]] => {
  const { columns } = props;
  const [newColumn, setnewColumn] = useState(columns);

  const setNewColumnCellMemoized = (column: any[], indexarray: number[]) => {
    column.map((col, index) => {
      //使用useMemo钩子创建一个记忆化的索引路径数组parent,它包含了从根到当前列的索引。这个数组用于在调整列宽时确定列的位置。
      const parent = useMemo(() => [...indexarray, index], [indexarray, index]);
       col.onHeaderCell = useCallback((column: any) => ({
          width: column.width, //列的宽度
          indexarry: withIndex, //索引数组
          onResize: handleResize(index, withIndex), // handleResize 函数的回调,它接收当前索引和索引数组作为参数
        }),
        [index, withIndex]
      );

      //递归处理子列
      if (col.children && col.children.length) {
        setNewColumnCellMemoized(col.children, withIndex)
      }
    });
  };

  setNewColumnCellMemoized(newColumn, []);

    //useCallback钩子创建的,以确保这个回调函数在组件的生命周期内保持一致性,避免不必要的重新渲染。
  const handleResize = useCallback((index: number, indexarray: number[]) => (e: any, a: { size: any }) => {
    let nextColumns = JSON.parse(JSON.stringify(newColumn));
    const { size } = a;
    const width = size ? Math.max(140, size.width) : 140;
    
    if (indexarray.length == 2) {
      //设置嵌套列中的子列的宽度
      nextColumns[indexarray[0]].children[indexarray[1]].width = width;
      // 调用mergeColumns函数,将调整后的列数组nextColumns与原始列数组columns合并。
      let mergeRes = mergeColumns(nextColumns, columns);
      //使用setnewColumn函数(可能是一个通过useState钩子创建的设置器函数)来更新状态,将合并后的列数组设置为新的列定义。
      setnewColumn(mergeRes); 
    }
  },[newColumn, columns]) //这是useCallback钩子的依赖数组,它确保当newColumn或columns发生变化时,handleResize函数会被重新创建。

  const mergeColumns = (columnsWithoutRender: any[], columnsWithRender: any[]): any[] => {
    // 创建一个映射,用于快速定位具有render函数的子column
    const renderMap = new Map<string, (text: any) => JSX.Element>();
    columnsWithRender.forEach((column) => {
      if (column.children) {
        column.children.forEach((subColumn: any) => {
          if (subColumn.render) {
            // 如果子列具有 render 函数,则将其 dataIndex 和 render 函数存储到 renderMap 中。
            renderMap.set(subColumn.dataIndex, subColumn.render);
          }
        });
      }
    });

    // 合并column,如果子column没有render函数,则从映射中获取
    const mergedColumns = columnsWithoutRender.map((column) => ({
      ...column,
      children: column.children ? column.children.map((subColumn: any) => ({
        ...subColumn,
        // 为子列设置 render 属性。如果 renderMap 中存在对应 dataIndex 的渲染函数,则使用该函数;否则,使用子列自己的 render 函数(如果有的话)。
        render: renderMap.get(subColumn.dataIndex) || subColumn.render,
      })) : [],
    }));

    return mergedColumns;
  }

  return [newColumn]
}
TableReszableTitle.tsx
/**  可伸缩列 多表头 */
import * as React from 'react';
import { Resizable } from "react-resizable"
import './style.less';

interface Props {
  onResize: () => void;
  width: any;
}

export default (props: Props): JSX.Element => {
  const { onResize, width, ...restProps } = props

  if (width == undefined) {
    return <th {...restProps} />
  }

  return (
    <Resizable
      width={width}
      height={0}
      handle={
        <span
          className="react-resizable-handle"
          onClick={(e) => {
            e.preventDefault()
            e.stopPropagation()
          }}
        />
      }
      onResize={onResize}
      draggableOpts={{ nableUserSelectHack: false }}
    >
      <th {...restProps} />
    </Resizable>
  )
}
TableReszableTitle.less
: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;
  }
}
使用方法
import * as React from 'react';
import { Table } from "antd";
import { IOwnColumnProps } from '@yitu/react-datatable/lib/types';
import { useTableResizable } from '~/Hooks';
import { TableReszableTitle } from '~/component/TableReszableTitle';

export default (): JSX.Element => {
  const columns: IOwnColumnProps[] = [
    {
      title: "人口",
      key: "人口",
      children: [
        {
          title: "小计",
          dataIndex: "Population",
          width: 100,
          ellipsis: true
        },
        {
          title: "农业",
          dataIndex: "CityPopulation",
          width: 100,
          ellipsis: true
        },
        {
          title: "非农",
          dataIndex: "VillagePopulation",
          width: 100,
          ellipsis: true
        }
      ]
    }, 
    {
      title: "地区",
      dataIndex:"area"
    }
  ]

  const [ newColumn ]= useTableResizable({ columns });
  
  return (
    <Table
      columns={newColumn}
      id="data-dashboard-list-wrapper"
      className="data-dashboard-list-wrapper"
      components={{
        header: {
          cell: TableReszableTitle
        }
      }}
      />
  )
}