Ant Design表格列Resize行为定制开发咨询
Ant Design表格列Resize行为定制开发咨询
嘿,我完全get到你的需求了:要让Ant Design Table结合react-resizable实现列宽拖拽调整,而且最后一列的调整行为要特殊定制——
- 当表格没有左侧固定列时,拖拽最后一列右边缘,列宽增加的同时,表格右侧边缘要牢牢贴住容器/视口右侧,通过横向滚动来容纳加宽的列
- 当表格有左侧固定列时,拖拽最后一列右边缘,列宽增加时,最后一列的右边缘要始终贴合左侧固定列的右边缘,保持对齐
下面我结合你提供的现有代码,梳理实现思路和具体调整方案:
一、核心逻辑拆解
要实现这个特殊的resize效果,关键要抓住三个要点:
- 能识别当前调整的列是否是最后一列
- 能判断表格是否存在左侧固定列,并计算固定列的总宽度
- 调整最后一列宽度时,同步修改表格的滚动配置,强制右侧边缘贴合目标边界
二、升级ResizableTitle组件
我们需要给现有组件新增几个props,用来传递列的位置和表格的固定列状态,然后在resize回调中处理特殊逻辑:
import React, { useState } from 'react'; import { Resizable, ResizeCallbackData } from 'react-resizable'; import 'react-resizable/css/styles.css'; // 扩展props类型,添加列位置与固定列相关信息 interface ResizableTitleProps extends React.HTMLAttributes<any> { onResize: (e: React.SyntheticEvent<Element>, data: ResizeCallbackData) => void; onResizeStop: (e: React.SyntheticEvent<Element>, data: ResizeCallbackData) => void; width: number; minWidth: number; isResize: boolean; isLastColumn: boolean; // 标记是否为最后一列 hasLeftFixedColumns: boolean; // 标记是否存在左侧固定列 fixedColumnsTotalWidth?: number; // 左侧固定列的总宽度 tableScrollWidth?: number; // 表格滚动容器的宽度 } const ResizableTitle = (props: ResizableTitleProps) => { const { onResize, width, onResizeStop, minWidth, isResize, isLastColumn, hasLeftFixedColumns, fixedColumnsTotalWidth, tableScrollWidth, ...restProps } = props; const [isFieldResizing, setIsFieldResizing] = useState(false); if (!width || !isResize) { return <th {...restProps} />; } // 处理最后一列的特殊resize逻辑 const handleResize = (e: React.SyntheticEvent<Element>, data: ResizeCallbackData) => { if (isLastColumn) { let targetWidth = data.size.width; // 有左侧固定列时,强制最后一列宽度 = 滚动容器宽度 - 固定列总宽度 if (hasLeftFixedColumns && fixedColumnsTotalWidth && tableScrollWidth) { targetWidth = tableScrollWidth - fixedColumnsTotalWidth; } // 传递处理后的宽度给父组件 const customData = { ...data, size: { width: targetWidth, height: data.size.height } }; onResize(e, customData); } else { // 普通列直接调用原回调 onResize(e, data); } }; const handleResizeStart = (e: React.SyntheticEvent<Element>) => { e.stopPropagation(); setIsFieldResizing(true); document.body.classList.add('no-select'); }; const handleResizeStop = (e: React.SyntheticEvent<Element>, data: ResizeCallbackData) => { setIsFieldResizing(false); // 最后一列的停止回调同样处理宽度 if (isLastColumn) { let targetWidth = data.size.width; if (hasLeftFixedColumns && fixedColumnsTotalWidth && tableScrollWidth) { targetWidth = tableScrollWidth - fixedColumnsTotalWidth; } const customData = { ...data, size: { width: targetWidth, height: data.size.height } }; onResizeStop(e, customData); } else { onResizeStop(e, data); } document.body.classList.remove('no-select'); }; return ( <Resizable width={width} height={0} handle={ <span className="react-resizable-handle" onClick={(e) => e.stopPropagation()} /> } onResize={handleResize} draggableOpts={{ enableUserSelectHack: false }} onResizeStart={handleResizeStart} onResizeStop={handleResizeStop} minConstraints={[minWidth, 0]} > <th {...restProps} className={ isFieldResizing ? `${restProps.className || ''} is-resizing` : restProps.className } /> </Resizable> ); }; export default ResizableTitle;
三、父组件(表格使用处)调整
在Table所在的组件中,我们需要维护列宽、滚动配置等状态,同时给每个ResizableTitle传递对应的判断参数:
import { Table, ColumnType } from 'antd'; import ResizableTitle from './components/resizable-title'; import { useState, useEffect } from 'react'; interface IListForms { formsRef?: React.RefObject<HTMLDivElement>; // 其他业务props... } const ListForms = ({ formsRef }: IListForms) => { const [resizableColumns, setResizableColumns] = useState<ColumnType<any>[]>([]); const [tableScroll, setTableScroll] = useState({ x: '1050', y: '400' }); const [hasLeftFixedColumns, setHasLeftFixedColumns] = useState(false); const [fixedColumnsTotalWidth, setFixedColumnsTotalWidth] = useState(0); // 模拟初始列配置(实际可从接口/全局配置获取) const initialColumns = [ { title: '选择', fixed: 'left', width: 50, isResize: false, }, { title: '表单名称', dataIndex: 'name', width: 200, minWidth: 120, isResize: true, }, { title: '创建时间', dataIndex: 'createTime', width: 180, minWidth: 140, isResize: true, }, { title: '操作', width: 150, minWidth: 120, isResize: true, }, ]; // 初始化列配置,判断固定列并计算总宽度 useEffect(() => { const cols = initialColumns.map(col => ({ ...col })); setResizableColumns(cols); // 检查是否有左侧固定列 const hasFixed = cols.some(col => col.fixed === 'left'); setHasLeftFixedColumns(hasFixed); // 计算左侧固定列总宽度 if (hasFixed) { const totalWidth = cols.filter(col => col.fixed === 'left').reduce((sum, col) => sum + (col.width || 0), 0); setFixedColumnsTotalWidth(totalWidth); } }, []); // 处理列宽更新 const handleColumnWidthChange = (index: number, newWidth: number) => { const newColumns = [...resizableColumns]; newColumns[index] = { ...newColumns[index], width: newWidth }; setResizableColumns(newColumns); // 最后一列调整时,同步更新表格滚动宽度 if (index === resizableColumns.length - 1) { if (hasLeftFixedColumns) { // 有固定列时,滚动宽度 = 固定列总宽度 + 最后一列新宽度 setTableScroll(prev => ({ ...prev, x: fixedColumnsTotalWidth + newWidth, })); } else { // 无固定列时,滚动宽度 = 所有列总宽度 const totalWidth = newColumns.reduce((sum, col) => sum + (col.width || 0), 0); setTableScroll(prev => ({ ...prev, x: totalWidth })); } } }; // 生成resize回调 const generateResizeHandler = (index: number) => ( e: React.SyntheticEvent<Element>, data: ResizeCallbackData ) => { handleColumnWidthChange(index, data.size.width); }; // 包装列,传递ResizableTitle所需props const columns = resizableColumns.map((col, index) => ({ ...col, title: ( <ResizableTitle width={col.width || 0} minWidth={col.minWidth || 100} isResize={col.isResize || false} onResize={generateResizeHandler(index)} onResizeStop={generateResizeHandler(index)} isLastColumn={index === resizableColumns.length - 1} hasLeftFixedColumns={hasLeftFixedColumns} fixedColumnsTotalWidth={fixedColumnsTotalWidth} tableScrollWidth={typeof tableScroll.x === 'string' ? parseInt(tableScroll.x) : tableScroll.x} > {col.title} </ResizableTitle> ), })); // 业务状态模拟(实际替换为你的真实状态) const formsList = []; const listFormsIsLoading = false; const pagination = {}; const deleteFormsIds = []; const isAllFormsSelected = false; return ( <div className="table-section table-section-with-search" ref={formsRef}> <Table className="tableCellPadding-9" columns={columns} onChange={() => {}} // 替换为你的表格onChange逻辑 showSorterTooltip={false} loading={listFormsIsLoading} components={{ header: { cell: ResizableTitle }, }} dataSource={formsList} pagination={pagination} locale={{ emptyText: ( <div>No data found</div> // 替换为你的空状态组件 ), }} rowSelection={{ type: 'checkbox', selectedRowKeys: deleteFormsIds.map(item => item), onChange: () => {}, // 替换为你的选择逻辑 onSelectAll: isAllFormsSelected ? () => {} : () => {}, fixed: 'left', columnWidth: '50px', }} rowKey="id" scroll={tableScroll} tableLayout={formsList.length > 0 ? 'fixed' : 'auto'} /> </div> ); }; export default ListForms;
三、保留原有样式(补充滚动容器样式)
你的原有SCSS已经处理了resize手柄的交互效果,这里再补充一点容器样式,确保右侧边缘固定:
.table-section { width: 100%; overflow: hidden; // 限制容器不超出父元素 } .ant-table-thead { > tr > th { .react-resizable-handle { opacity: 0; background: none; transition: all 0.2s ease; right: 0; top: 0; z-index: 9; height: 100%; width: 12px; padding: 0; &:after { content: ""; position: absolute; top: 5px; bottom: 5px; left: 3px; z-index: 1; width: 1px; background-color: #e8e8e8; // 替换为你的项目分割线颜色 } &:hover { opacity: 1; } } &.is-resizing { .react-resizable-handle { opacity: 1; transition: all 0.2s ease; cursor: col-resize; } } &:hover { .react-resizable-handle { opacity: 1; transition: all 0.2s ease; cursor: col-resize; } .table-sort-icon { width: 16px; opacity: 1; transition: all 0.2s ease; } } } } .no-select { user-select: none; }
四、关键细节说明
- 列宽状态维护:必须通过state实时保存每一列的宽度,否则组件重新渲染后会丢失调整结果
- 滚动配置同步:最后一列调整时,必须同步修改表格的
scroll.x值,才能保证右侧边缘贴合目标边界 - 固定列宽度计算:如果有多个左侧固定列,要实时计算它们的总宽度,确保最后一列的对齐精度
- 边界限制:给每列设置
minWidth,避免列宽被调整到过小导致内容溢出
如果实际测试中还有细节偏差,可以根据你的容器布局和业务场景,微调宽度计算的逻辑哦~
内容来源于stack exchange
相关产品推荐
相关产品推荐

