支持合并单元格拖拽
import React, { useState, useEffect } from 'react';
import { Table } from 'antd';
import { DndProvider, DragSource, DropTarget } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import update from 'immutability-helper';
import { isFunction } from '@/utils/types';
import styles from './index.less';
let dragingIndex = -1;
const BodyRow = ({ isOver, connectDragSource, connectDropTarget, moveRow, ...restProps }) => {
const style = { ...restProps.style, cursor: 'move' };
let { className } = restProps;
if (isOver) {
if (restProps.index > dragingIndex) {
className += ` ${styles.dropOverDownward}`;
}
if (restProps.index < dragingIndex) {
className += ` ${styles.dropOverUpward}`;
}
}
return connectDragSource(connectDropTarget(<tr {...restProps} className={className} style={style} />));
};
const rowSource = {
beginDrag(props) {
dragingIndex = props.index;
return {
index: props.index,
};
},
};
const rowTarget = {
drop(props, monitor) {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return;
}
// Time to actually perform the action
props.moveRow(dragIndex, hoverIndex);
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
monitor.getItem().index = hoverIndex;
},
};
const DragableBodyRow = DropTarget('row', rowTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
}))(
DragSource('row', rowSource, connect => ({
connectDragSource: connect.dragSource(),
}))(BodyRow),
);
const DragSortableTable = ({
dataSource,
columns,
onSort,
groupDrag = false, // 是否支持组拖拽
groupDragOrder = 'name', // 合并名称,必传,否则组拖拽会出问题
identifies = 'id', // 唯一标识,必传,否则组拖拽会出问题
...restProps
}) => {
const [dataList, setDataList] = useState([]);
const [columnsList, setColumnsList] = useState([]);
useEffect(() => {
if (Array.isArray(dataSource)) {
setDataList(dataSource);
}
}, [dataSource]);
useEffect(() => {
if (Array.isArray(columns)) {
setColumnsList(columns);
}
}, [columns]);
const components = {
body: {
row: DragableBodyRow,
},
};
const moveRow = (dragIndex, hoverIndex) => {
const dragRow = dataList[dragIndex];
if (groupDrag) {
// 拖拽元素
const rowName_drag = dataList[dragIndex][groupDragOrder];
// 目标元素
const rowName_hover = dataList[hoverIndex][groupDragOrder];
// 拖拽元素项
// const rowName_drag_item = dataList[dragIndex];
// 目标元素项
const rowName_hover_item = dataList[hoverIndex];
// 拖拽元素相同项
const sameList_drag = dataList.filter(el => el[groupDragOrder] === rowName_drag);
// 拖拽元素不同项
const notSameList_drag = dataList.filter(el => el[groupDragOrder] !== rowName_drag);
// 目标元素相同项
const sameList_hover = dataList.filter(el => el[groupDragOrder] === rowName_hover);
// 目标元素不同项
const notSameList_hover = dataList.filter(el => el[groupDragOrder] !== rowName_hover);
// 目标组
if (sameList_hover.length > 1) {
// 拖拽组
if (sameList_drag.length > 1) {
notSameList_drag.splice(
sameList_hover.map((item, index) => ({ ...item, index }))[0].index,
0,
...sameList_drag,
);
} else {
let flag = 0;
sameList_hover.forEach((v, index) => {
if (rowName_hover_item[identifies] === v[identifies]) {
flag = index;
}
});
const changeNotSameList_hover = notSameList_hover.filter(
h => h[groupDragOrder] !== dragRow[groupDragOrder],
);
let dedIndex = 0; // 目标index
// 组start
if (flag === 0) {
sameList_hover.splice(0, 0, dragRow);
notSameList_hover.forEach((v, index) => {
if (sameList_hover[0][identifies] === v[identifies]) {
dedIndex = index;
}
});
changeNotSameList_hover.splice(dedIndex, 0, ...sameList_hover);
} else {
// 组end
sameList_hover.splice(0, 0, dragRow);
notSameList_hover.forEach((v, index) => {
if (sameList_hover[[sameList_hover.length - 1]][identifies] === v[identifies]) {
dedIndex = index;
}
});
changeNotSameList_hover.splice(dedIndex, 0, ...sameList_hover);
}
setDataList(changeNotSameList_hover);
if (isFunction(onSort)) onSort(changeNotSameList_hover);
return;
}
} else {
notSameList_drag.splice(hoverIndex, 0, ...sameList_drag);
}
setDataList(notSameList_drag);
if (isFunction(onSort)) onSort(notSameList_drag);
return;
}
const sortedDataList = update(dataList, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragRow],
],
});
setDataList(sortedDataList);
if (isFunction(onSort)) onSort(sortedDataList);
};
return (
<DndProvider backend={HTML5Backend}>
<Table
className={styles.dragSortableTable}
columns={columnsList}
dataSource={dataList}
components={components}
onRow={(_, index) => ({
index,
moveRow,
})}
{...restProps}
/>
</DndProvider>
);
};
export default DragSortableTable;
.dragSortableTable {
tr.dropOverDownward {
td {
border-bottom: 2px dashed #1890ff;
}
}
tr.dropOverUpward {
td {
border-bottom: 2px dashed #1890ff;
}
}
}
import React, { useState, useMemo } from 'react';
import DragSortableTable from './DragSortableTable';
/*
* 参数text :为当前单元格的值 > string
* 参数data :为当前table 渲染的数据 >数组
* 参数key : 为当前单元格的dataindex> string
* 参数index :为当前单元格的第几行 >Number
* 参数row :为table 当前行的数据对象 >Object
*/
const mergeCells = (text, data, key, index, row) => {
// 如果相等则该单元格被上一列合并,直接返回rowSpan为 0
if (index !== 0 && text === data[index - 1][key]) {
return 0;
}
let rowSpan = 1;
for (let i = index + 1; i < data.length; i++) {
if (text !== data[i][key]) {
break;
} else {
rowSpan++;
}
}
return rowSpan;
};
export default () => {
const list = [
{
id: '1',
name: '组1',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
id: '2',
name: '组1',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
},
{
id: '3',
name: '组3',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
{
id: '4',
name: '组4',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
{
id: '44',
name: '组4',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
{
id: '5',
name: '组5',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
{
id: '005',
name: '组5',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
{
id: '6',
name: '组6',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
{
id: '7',
name: '组7',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
const [dataList, setDataList] = useState([...list]);
// const [columns, setColumns] = useState([...defColumns]);
const columns = useMemo(() => {
const defColumns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (text, row, index) => {
if (dataList.length) {
return {
children: <span>{text}</span>,
props: {
rowSpan: mergeCells(text, dataList, 'name', index, row),
},
};
}
},
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
];
return defColumns;
}, [dataList]);
const tableProps = {};
return (
<DragSortableTable
{...tableProps}
pagination={false}
dataSource={dataList}
columns={columns}
bordered
rowKey="id"
groupDrag
groupDragOrder="name"
identifies="id" // 唯一标识,必传,否则组拖拽会出问题
onSort={data => {
// const ids = data.map(e => e.id);
setDataList([...data]);
}}
/>
);
};