前后端合作实现基于Antd的列表拖拽功能

要通过 Sequelize 的 Schema 和 Ant Design 的 DraggableTable 组件实现拖拽排序功能,需要在后端设计一个用于存储排序顺序的字段,并在前端实现拖拽排序的逻辑。本文介绍一个实现此功能的步骤指南以及对应的代码示例:

1. 后端 (Sequelize 模型设计)

首先,在 Sequelize 模型中添加一个字段,比如 sortOrder,用于存储每个记录的排序顺序:

javascript 复制代码
const Item = sequelize.define('item', {
  // ...其他字段
  sortOrder: {
    type: Sequelize.INTEGER,
    allowNull: false,
    defaultValue: 0 // 默认排序值
  }
  // ...其他配置
});

2. 后端 (处理排序更新请求)

接着,创建一个接口来处理排序更新的请求:

javascript 复制代码
app.post('/update-sort-order', async (req, res) => {
  const { sortedItems } = req.body; // 前端发送的已排序的数组

  try {
    await sequelize.transaction(async (transaction) => {
      for (const [index, item] of sortedItems.entries()) {
        await Item.update({ sortOrder: index }, { where: { id: item.id }, transaction });
      }
    });

    return res.status(200).json({ message: 'Sort order updated successfully' });
  } catch (error) {
    return res.status(500).json({ message: error.message });
  }
});

3. 前端 (Ant Design DraggableTable)

在前端需要配置 Ant Design 的 DraggableTable 组件来使行可拖拽,并在拖拽结束时发送更新请求到后端:

jsx 复制代码
import React, { useState, useCallback } from 'react';
import { Table } from 'antd';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper';

const type = 'DraggableRow';

const DraggableRow = ({ index, moveRow, ...restProps }) => {
  const ref = React.useRef();
  const [, drop] = useDrop({
    accept: type,
    hover(item, monitor) {
      if (!ref.current) {
        return;
      }
      const dragIndex = item.index;
      const hoverIndex = index;
      // Don't replace items with themselves
      if (dragIndex === hoverIndex) {
        return;
      }
      // Determine rectangle on screen
      const hoverBoundingRect = ref.current.getBoundingClientRect();
      // Get vertical middle
      const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
      // Determine mouse position
      const clientOffset = monitor.getClientOffset();
      // Get pixels to the top
      const hoverClientY = clientOffset.y - hoverBoundingRect.top;
      // Only perform the move when the mouse has crossed half of the items height
      if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
        return;
      }
      if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
        return;
      }
      // Time to actually perform the action
      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.
      item.index = hoverIndex;
    },
  });
  const [{ isDragging }, drag] = useDrag({
    type,
    item: { type, index },
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  });
  drag(drop(ref));

  return (
    <tr
      ref={ref}
      style={{ cursor: 'move', opacity: isDragging ? 0 : 1 }}
      {...restProps}
    />
  );
};

const DraggableTable = ({ columns, dataSource, onSortEnd }) => {
  const [data, setData] = useState(dataSource);
  const moveRow = useCallback(
    (dragIndex, hoverIndex) => {
      const dragRow = data[dragIndex];
      setData(update(data, {
        $splice: [
          [dragIndex, 1],
          [hoverIndex, 0, dragRow],
        ],
      }));
      onSortEnd(data);
    },
    [data, onSortEnd],
  );

  return (
    <DndProvider backend={HTML5Backend}>
      <Table
        columns={columns}
        dataSource={data}
        components={{
          body: {
            row: DraggableRow,
          },
        }}
        onRow={(record, index) => ({
          index,
          moveRow,
        })}
      />
    </DndProvider>
  );
};

export default DraggableTable;

在拖拽结束后,onSortEnd 回调会被触发,这时可以将新的排序发送到后端进行更新。

4. 前端 (发送排序更新请求)

当用户完成拖拽操作时,触发一个函数来发送新的排序顺序给后端:

javascript 复制代码
const handleSortEnd = async (sortedData) => {
  const sortedItems = sortedData.map((item, index) => ({ id: item.id, sortOrder: index }));
  // 发送请求到后端更新排序
  await fetch('/update-sort-order', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ sortedItems }),
  });
  // ...处理响应
};

上述代码实现了前端的拖拽排序功能,并在每次拖拽结束时更新后端数据库中的排序字段。这样,无论何时从数据库中检索数据,它们都将按照用户定义的顺序进行排序。


如果前端提交的数据包括分页信息(currentpageSize),并且开发者希望在分页的环境中实现拖拽排序,这个情况会更复杂。因为排序操作可能会影响多个页面上的数据。为了处理这种情况,可以采用下述策略:

1. 后端处理

当后端接收到排序更新请求时,它需要考虑当前页面和页面大小,以便正确计算全局排序顺序。具体实现可能会根据业务逻辑而有所不同,但基本思路是:

  • 计算当前页面第一项在全局数据中的排序位置。
  • 更新当前页面项的全局排序位置。
  • 考虑到排序变更可能会影响到其他页面的数据,可能需要重新计算并更新其他页面数据的排序位置。

2. Sequelize 更新逻辑

假设前端发送的数据包括排序项的 ID、当前页数 current 和每页大小 pageSize

javascript 复制代码
app.post('/update-sort-order', async (req, res) => {
  const { sortedItems, current, pageSize } = req.body;

  try {
    await sequelize.transaction(async (transaction) => {
      const globalStartIndex = (current - 1) * pageSize;

      for (const [index, item] of sortedItems.entries()) {
        await Item.update(
          { sortOrder: globalStartIndex + index }, 
          { where: { id: item.id }, transaction }
        );
      }

      // 如果需要,更新其他页面数据的 sortOrder

    });

    return res.status(200).json({ message: 'Sort order updated successfully' });
  } catch (error) {
    return res.status(500).json({ message: error.message });
  }
});

3. 前端逻辑

前端在发送排序请求时,应包括当前页面和页面大小信息:

javascript 复制代码
const handleSortEnd = async (sortedData, current, pageSize) => {
  const sortedItems = sortedData.map((item, index) => ({ id: item.id, sortOrder: index }));
  await fetch('/update-sort-order', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ sortedItems, current, pageSize }),
  });
  // ...处理响应
};

注意点

  • 需要考虑到用户可能将项目从一个页面拖到另一个页面的情况。这种情况下可能需要重新加载并重新排序相关页面上的数据。
  • 也可能需要在数据库查询中采用一些优化措施,特别是在处理大量数据时,以保持系统性能和响应速度。

这种分页情况下的拖拽排序实现相对复杂,需要仔细考虑不同情况下的数据处理和用户体验。

相关推荐
程楠楠&M1 小时前
node.js第三方Express 框架
前端·javascript·node.js·express
盛夏绽放1 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
小技与小术2 小时前
数据库表设计范式
数据库·mysql
安迁岚2 小时前
【SQL Server】华中农业大学空间数据库实验报告 实验三 数据操作
运维·服务器·数据库·sql·mysql
安迁岚2 小时前
【SQL Server】华中农业大学空间数据库实验报告 实验九 触发器
数据库·sql·mysql·oracle·实验报告
无敌岩雀2 小时前
MySQL中的索引
数据库·mysql
东阳马生架构3 小时前
MySQL原理简介—1.SQL的执行流程
mysql
Roc.Chang3 小时前
macos 使用 nvm 管理 node 并自定义安装目录
macos·node.js·nvm
安迁岚3 小时前
【SQL Server】华中农业大学空间数据库实验报告 实验六 视图
数据库·sql·mysql·oracle·实验报告
xoxo-Rachel3 小时前
(超级详细!!!)解决“com.mysql.jdbc.Driver is deprecated”警告:详解与优化
java·数据库·mysql