前言
一开始遇到给表格增加列宽拖动的需求,所以引用了网上现有的‘react-resizable’插件,但是在实际的使用过程中,发现还没有element table自带的列宽拖动好用,‘react-resizable’是实时拖动的变化的,所以也没法快速拖动,搜索了一下网上现有的,目前也只有一个‘react-resizable’,索性自己封装了一个组件,还稍显粗糙,发出来沟流沟流,
废话不多说,先上代码:
组件部分代码:
/**
* @description:1:需要每一列默认有宽度,2:目前不支持多表头列的拖拽
*
*/
import React, { useEffect, useRef, useState } from 'react';
import './index.less';
export const ResizableHeaderCell = (props: any) => {
const { onResize, width, children, ...restProps } = props;
const [dragging, setDragging] = useState(false);
const dragLineRef = useRef<HTMLDivElement | null>(null);
const draggingRef = useRef(false); // 新增
const startX = useRef(0);
const startWidth = useRef(0);
const cellRef = useRef<HTMLDivElement | null>(null);
// 鼠标按下时触发
const handleMouseDown = (e: React.MouseEvent) => {
e.preventDefault();
startX.current = e.clientX;
setDragging(true);
draggingRef.current = true; // 同时更新 useRef
startWidth.current = width;
const lineHeight = (document.querySelector('.ant-table-body') as HTMLElement)?.offsetHeight;
const dragLine = document.createElement('div');
dragLine.className = 'drag-line';
dragLine.style.height = `${80}px`;
dragLine.style.width = '2px';
dragLine.style.position = 'absolute';
dragLine.style.left = `${e.clientX}px`;
document.body.appendChild(dragLine);
dragLineRef.current = dragLine;
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
//鼠标放开时触发
const handleMouseUp = (e: MouseEvent) => {
setDragging(false);
draggingRef.current = false; // 同时更新 useRef
const newWidth = startWidth.current + (e.clientX - startX.current);
// console.log(width, 'startWidth.current = width--原本的宽度');
// console.log(e.clientX - startX.current, '移动的宽度');
// console.log(newWidth, 'newWidth---移动后的宽度');
if (onResize) {
onResize(newWidth);
}
if (dragLineRef.current) {
dragLineRef.current.parentElement?.removeChild(dragLineRef.current);
dragLineRef.current = null;
}
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
//鼠标移动时触发
const handleMouseMove = (e: MouseEvent) => {
if (draggingRef.current && dragLineRef.current) {
dragLineRef.current.style.left = `${e.clientX}px`;
}
};
useEffect(() => {
// 组件卸载时移除事件监听器
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, []);
return (
<th ref={cellRef} className="resize-th" style={{ width }} {...restProps}>
<div className="resize-container">{children}</div>
<div className="resize-handle" onMouseDown={handleMouseDown}></div>
</th>
);
};
less
.resize-th::before {
display: flex;
cursor: pointer !important;
}
.resize-container {
position: relative;
height: 100%;
}
.resize-handle {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 5px !important;
height: 100%;
cursor: col-resize;
z-index: 1;
}
.drag-line {
top: 220px;
z-index:1000;
background: #f0a76f;
}
首先需要说一下,光看组件,使用起来还是有点问题,因为还需要在tsx文件中,对数据进行一些处理,下面是使用举例:
import { ResizableHeaderCell } from '@/components/MultilistHeadResizable';
const initColumns: ProColumns<StoreInfoTableListItem>[] = [...]
const [columns, setColumns] = useState<ProColumns<StoreInfoTableListItem>[]>(initColumns);
const handleResize =
(index: number) =>
(newWidth: any) => {
setColumns((prevColumns) => {
const nextColumns = [...prevColumns];
nextColumns[index] = {
...nextColumns[index],
width:newWidth,
};
localStorage.setItem('Store-Info-tableColumns', JSON.stringify(nextColumns));
return nextColumns;
});
};
const mergedColumns = columns.map((col, index) => ({
...col,
onHeaderCell: (column: ProColumns<any>) => ({
width: column.width,
onResize: handleResize(index),
}),
}));
return (
<>
<ProTable<StoreInfoTableListItem>
className="proTable_style noTitle_Select_Setting"
rowKey="dimBInfoId"
actionRef={actionRef}
columns={mergedColumns as ProColumns<StoreInfoTableListItem>[]}
components={{
header: {
cell: ResizableHeaderCell,
},
}}
request={getListData}
rowSelection={{
alwaysShowAlert: true,
selectedRowKeys,
onChange: onSelectChange,
}}
columnsState={{
persistenceKey: 'StoreInfoShipNewSetting',
persistenceType: 'localStorage',
}}
tableAlertRender={false}/>
实现思路
主要是根据ProTable的components自定义了一个表头,另外这里还是有坑存在的,比如你columns使用了render,renderFormItem等等,在缓存的时候是要出问题的,需要自己处理一下,如果不缓存就没问题,另外mergedColumns方法,handleResize方法是可以根据实际情况使用做改变的。我举个双表头的例子:
const handleResize = (key: string) => {
return (newWidth: number) => {
setColumns((prevColumns) => {
const updateColumns = (
cols: ProColumns<any>[],
): ProColumns<any>[] => {
return cols.map((col) => {
if (col.key === key) {
return {
...col,
width: newWidth,
};
}
if (col.children) {
return {
...col,
children: updateColumns(col.children),
};
}
return col;
});
};
const nextColumns = updateColumns([...prevColumns]);
localStorage.setItem('tableColumns', JSON.stringify(nextColumns));
return nextColumns;
});
};
};
// 递归遍历 columns 并生成 mergedColumns
const getMergedColumns = (
cols: ProColumns<any>[],
): ProColumns<any>[] => {
return cols.map((col: any) => {
if (col.children) {
return {
...col,
children: getMergedColumns(col.children),
onHeaderCell: (column: ProColumns<any>) => ({
width: column.width,
onResize: handleResize(col.key as string) as any,
}),
};
}
return {
...col,
onHeaderCell: (column: ProColumns<any>) => ({
width: column.width,
onResize: handleResize(col.key as string) as any,
}),
};
});
};
const mergedColumns: ProColumns<any>[] = getMergedColumns(columns);
总结
虽然实现了需求,但是代码上看起来挺臃肿的,如果有更好,更简洁的处理方法,欢迎交流~好了散会
补充: 上一篇介绍了自定义的列宽拖动方法,后面嫌弃每个页面都写,挺麻烦的,所以抽离了部分公共代码,单独封装成了hooks,看此方法之前,请先阅读我前一篇文章。
好久没弄了,代码有点乱,我先上图片,代码可以直接复制下面代码:
`import { useState, useEffect } from 'react';
const useResizableColumns = <T extends any[]>( initialColumns: T, storageKey: string, renderFunctions: Record<string, Function>, ) => { // 状态:列配置 const [columns, setColumns] = useState(initialColumns);
// 初始化:从 localStorage 中读取列配置,并重新绑定渲染逻辑 useEffect(() => { const storedColumns = localStorage.getItem(storageKey); if (storedColumns) { try { const parsedColumns = JSON.parse(storedColumns); // 重新绑定渲染逻辑 const columnsWithMethods = injectRenderFunctions(parsedColumns, renderFunctions); setColumns(columnsWithMethods as T); // 确保类型一致 } catch (error) { console.error('Failed to parse stored columns:', error); } } }, [storageKey, renderFunctions]);
const injectRenderFunctions = (cols: any[], renderFunctions: Record<string, Function>): any[] => { return cols.map((col) => { // 如果当前列有 children,递归处理子列 if (col.children) { return { ...col, children: injectRenderFunctions(col.children, renderFunctions), }; } // 如果当前列有对应的渲染逻辑,动态注入 if (renderFunctions[col.dataIndex || col.key]) { return { ...col, ...renderFunctionscol.dataIndex || col.key, }; } return col; }); };
const handleResize = (key: string) => (newWidth: number) => { setColumns((prevColumns) => { const updateColumns = (cols: any[]): any[] => { return cols.map((col) => { // 如果当前列的 key 匹配,更新宽度 if (col.key === key) { return { ...col, width: newWidth, }; } // 如果当前列有 children,递归处理子列 if (col.children) { return { ...col, children: updateColumns(col.children), }; } return col; }); }; const nextColumns = updateColumns([...prevColumns]); // 将更新后的列配置保存到 localStorage localStorage.setItem(storageKey, JSON.stringify(nextColumns)); return nextColumns as T; // 确保类型一致 }); };
const getMergedColumns = (cols: any[]): any[] => { return cols.map((col) => { // 如果当前列有 children,递归处理子列 if (col.children) { return { ...col, children: getMergedColumns(col.children), onHeaderCell: (column: any) => ({ width: column.width, onResize: handleResize(col.key) as any, }), }; } return { ...col, onHeaderCell: (column: any) => ({ width: column.width, onResize: handleResize(col.key) as any, }), }; }); };
// 返回处理后的列配置 return getMergedColumns(columns); };
export default useResizableColumns;`