问题描述
在项目开发中,遇到这样一个需求:需要对表格里面的数据进行拖拽排序。
效果图如下所示:
思路
安装两个插件:
- react-sortable-hoc (或者 react-beautiful-dnd)
- array-move
npm install --save react-sortable-hoc
npm install --save array-move
解析
1. react-sortable-hoc
react-sortable-hoc
是一组 react
高阶组件(参数或返回值为函数),用于实现拖动排序功能,可以将任何列表转换为动画,可访问和触摸友好的可排序列表。可以和现有组件集成,支持拖动手柄、自动滚动、锁定轴和操作事件等功能,有着流程的动画效果。可水平、垂直拖动。
react-sortable-hoc 的使用:
react-sortable-hoc
提供了两个特别重要的API
- SortableContainer :是所有可拖拽排序元素的容器
- SortableElement :是每个要拖拽排序元素的容器
- SortableHandle :是定义拖拽手柄的容器
import { SortableHandle } from 'react-sortable-hoc';
import { MenuOutlined } from '@ant-design/icons';
const DragHandle = SortableHandle(() => <MenuOutlined style={{ cursor: 'grab', color: '#999' }} />)
{
title: '拖动排序',
dataIndex: 'sort',
width: 120,
align: 'center',
className: 'drag-visible',
editable: false,
render: () =>{
if (editable) return <DragHandle />;
return <span>禁止拖动</span>
},
},
SortableHandle 就是指下面的箭头部分
SortableElement
提供了一个 index
属性来进行对每个要拖拽元素的排序
SortableContainer
提供一个方法 onSortEnd
,这个方法可以解构两个形参:{ oldIndex , newIndex }
,一个是拖拽元素的标记,一个是将要放的地方的标记。
最后在使用 arrayMoveImmutable
交换数组的位置。
axis 表示拖拽的方向,x 是水平拖拽,y 是垂直拖拽,默认是垂直拖拽
2. array-move
array-move
其实就是一个 API,它的主要作用是用来交换数组中元素的位置。
看下面的实例:
// 在tsx文件中
import React, { useEffect } from 'react';
import { arrayMoveImmutable } from 'array-move';
const Index = () => {
useEffect(() => {
let arr = ['a', 'b', 'c']
let result = arrayMoveImmutable(arr, 1 , 2)
console.log(result)
// 结果输入为: [ 'a', 'c', 'b' ]
})
}
export default Index
使用
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import { arrayMoveImmutable } from 'array-move';
// 定义拖拽的table 容器
const DragTableContainer = SortableContainer((props) => <tbody {...props}>)
// 定义 拖拽的 行
const DragTableItem = SortableElement((props) => <tr {...props}>)
// 定义拖拽手柄
const DragHandle = SortableHandle(() => (
<MenuOutlined title='拖拽排序' />
))
// 表格排序方法
const onSortEnd = ({oldIndex, newIndex}: {oldIndex: number; newIndex: number }) => {
if (oldIndex !== newIndex) {
const newData: any[] = arrayMoveImmutable(([] as any[]).concat(dataSource), oldIndex, newIndex).filter((el: any) => !!el);
handleAllSave(newData) // 父组件传过来的方法,用于更新表格第一列的序号
}
}
// 所有可拖拽排序元素的容器
// DragTableContainer 是上面通过 SortableContainer 定义的拖拽的table 容器
// useDragHandle 参数,意思是: 使用行把手拖拽行排序
// disableAutoscroll 参数,禁止自动滚动
// helperClass 参数,可修改拖拽样式
// onSortEnd `SortableContainer` 提供的一个方法,这个方法可以解构两个形参:`{ oldIndex , newIndex }`,一个是拖拽元素的标记,一个是将要放的地方的标记,用于表格拖拽排序
const DraggableContainer = (props: any) => <DragTableContainer useDragHandle disableAutoscroll helperClass="row-dragging" onSortEnd={onSortEnd} {...props}/>
// 定义 拖拽的 行
// DraggableBodyRow 返回的是由 SortableItem 包裹的每一行元素
const DraggableBodyRow = ({ className, style, ...restProps}: any) => {
const index = dataSource.findIndex((x: any) => x.orderNum === restProps['data-row-key']);
return (<SortableItem index={index} {...restProps} />)
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// 封装的子组件
const EditableTable = (props: any) => {
let { title = '', subtitle = '', columns, rowClassName = () => 'editable-row', dataSource, handleSave, handleAllSave, rowKey, placeholder, clickRow, loading = false, scroll } = props;
const styles = {
tabletitle: { fontWeight: 800, color: '#0095ff', fontSize: '16px' },
subtitle: { color: '#000000', fontSize: '12px' },
};
columns = columns.map((col: any) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: any) => ({
record,
isRowDisable: col.isRowDisable,
isNumber: col.isNumber,
editable: col.editable,
editdisable: col.editdisable,
dataIndex: col.dataIndex,
title: col.title,
handleSave: handleSave,
formRules: col.rules,
placeholder: col?.placeholder,
precision: col?.precision,
min: col?.min,
step: col?.step,
max: col?.max,
formatter: col?.formatter,
parser: col?.parser,
}),
};
});
/**
* 表格行属性
* @param record 表格每行的数据
* @returns
*/
const onRow = (record: any) => {
return {
onClick: clickRow ? () => clickRow(record) : undefined,
}
}
const onSortEnd = ({oldIndex, newIndex}: {oldIndex: number; newIndex: number }) => {
if (oldIndex !== newIndex) {
const newData: any[] = arrayMoveImmutable(([] as any[]).concat(dataSource), oldIndex, newIndex).filter((el: any) => !!el);
handleAllSave(newData)
}
}
const DraggableContainer = (props: any) => <SortableBody useDragHandle disableAutoscroll helperClass="row-dragging" onSortEnd={onSortEnd} {...props}/>
const DraggableBodyRow = ({ className, style, ...restProps}: any) => {
const index = dataSource.findIndex((x: any) => x.orderNum === restProps['data-row-key']);
return (<SortableItem index={index} {...restProps} />)
}
return (
<Fragment>
<div style={{ display: 'flex', marginBottom: '6px' }}>
<Table
className="wrap"
style={{ width: '100%' }}
locale={{ emptyText: '暂无数据' }}
components={{
body: {
wrapper: DraggableContainer,
row: DraggableBodyRow,
// cell: EditableCell
}
}}
rowClassName={rowClassName}
bordered
dataSource={dataSource}
columns={columns}
pagination={false}
rowKey='orderNum'
scroll={scroll || { y: 500 }}
onRow={onRow}
loading={loading}
/>
</div>
</Fragment>
);
};
export default memo(EditableTable);