实现单行和多行拖拽表格 ( Ant Design, React, react-dnd, react-dnd-html5-backend)

161 阅读2分钟

依赖版本

"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
// 提供 TypeScript 类型定义
"@types/react-dnd": "^3.0.2",
"@types/react-dnd-html5-backend": "^3.0.2",

可拖拽行 — <DraggableRow/>

import React from 'react';
import { useDrag, useDrop } from 'react-dnd';

const type = 'DraggableRow';

const DraggableRow = ({ index, moveRow, className, style, selectedIndices = [], moveRows, ...restProps }) => {
  const ref = React.useRef();
  const [{ isOver, dropClassName }, drop] = useDrop({
    accept: type,
    collect: (monitor) => {
      const { index: dragIndex } = monitor.getItem() || {};
      if (dragIndex === index) {
        return {};
      }
      return {
        isOver: monitor.isOver(),
        dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
      };
    },
    drop: (item) => {
      // item: { index, type, selectedIndices }
      if (selectedIndices.includes(item.index) && typeof moveRows === 'function') {
        const dragIndices = [];
        let addFlag = true, deleteFlag = true, position = 1
        while (addFlag || deleteFlag) {
          if (addFlag && selectedIndices.includes(item.index + position)) {
            dragIndices.push(item.index + position);
          } else {
            addFlag = false;
          }

          if (deleteFlag && selectedIndices.includes(item.index - position)) {
            dragIndices.unshift(item.index - position);
          } else {
            deleteFlag = false
          }

          position++;
        }
        moveRows(selectedIndices, index);
      } else {
        moveRow(item.index, index);
      }
    },
  });
  const [, drag] = useDrag({
    type: type,
    item: { index, type },
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  });
  drop(drag(ref));

  return (
    <tr
      ref={ref}
      className={`${className}${isOver ? dropClassName : ''}`}
      style={{ cursor: 'move', ...style }}
      {...restProps}
    />
  );
};

export default DraggableRow;

拖拽单行

const moveRows = (dragIndices, hoverIndex) => {
  const newData = [...data];
  newData.splice(dragIndices[0], dragIndices.length);
  newData.splice(hoverIndex, 0, ...dragIndices.map(index => data[index]));
  setData(newData);
};

拖拽多行

使用多选框,拖拽时将连续被选择的部分统一拖拽

const moveRows = (dragIndices, hoverIndex) => {
  const newData = [...data];
  const hoverKey = newData[hoverIndex].key;
  newData.splice(dragIndices[0], dragIndices.length);
  const _hoverIndex = newData.findIndex((item) => item.key === hoverKey);
  newData.splice(_hoverIndex, 0, ...dragIndices.map(index => data[index]));
  setData(newData);
};

应用在 Table 中 — <DraggableTable/>

import React, { useState } from 'react';
import { Table } from 'antd';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import DraggableRow from './DraggableRow';

const columns = [
  {
    title: 'Name',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: 'Age',
    dataIndex: 'age',
    key: 'age',
  },
  {
    title: 'Address',
    dataIndex: 'address',
    key: 'address',
  },
];

const DraggableTable = () => {
  const [data, setData] = useState([
    {
      key: '1',
      name: 'John Brown',
      age: 32,
      address: 'New York No. 1 Lake Park',
    },
    {
      key: '2',
      name: 'Jim Green',
      age: 42,
      address: 'London No. 1 Lake Park',
    },
    {
      key: '3',
      name: 'Joe Black',
      age: 32,
      address: 'Sidney No. 1 Lake Park',
    },
    {
      key: '4',
      name: 'Jim Red',
      age: 18,
      address: 'London No. 2 Lake Park',
    },
    {
      key: '5',
      name: 'Jake White',
      age: 18,
      address: 'Dublin No. 2 Lake Park',
    },
  ]);

  const [selectedRowKeys, setSelectedRowKeys] = useState([]);

  const components = {
    body: {
      row: DraggableRow,
    },
  };

  const moveRow = (dragIndex, hoverIndex) => {
    const dragRow = data[dragIndex];
    const newData = [...data];
    newData.splice(dragIndex, 1);
    newData.splice(hoverIndex, 0, dragRow);
    setData(newData);
  };

  const moveRows = (dragIndices, hoverIndex) => {
    const newData = [...data];
    newData.splice(dragIndices[0], dragIndices.length);
    newData.splice(hoverIndex, 0, ...dragIndices.map(index => data[index]));
    setData(newData);
  };

  const rowSelection = {
    onChange: (selectedRowKeys, selectedRows) => {
      console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
      setSelectedRowKeys(selectedRowKeys);
    },
    getCheckboxProps: record => ({
      disabled: record.name === 'Disabled User', // Column configuration not to be checked
      name: record.name,
    }),
    selectedRowKeys
  };

  return (
    <DndProvider backend={HTML5Backend}>
      <Table
        columns={columns}
        dataSource={data}
        components={components}
        rowSelection={Object.assign({ type: "checkbox" }, rowSelection)}
        onRow={(record, index) => ({
          index,
          moveRow,
          moveRows,
          selectedIndices: selectedRowKeys.map((key) => data.findIndex((item) => item.key === key)),
        })}
        rowKey="key"
      />
    </DndProvider>
  );
};

export default DraggableTable;