antd表格拖拽

艾茂学
2023-12-01

组件DragSortableTable

支持合并单元格拖拽

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]);
            }}
        />
    );
};

 类似资料: