前后端合作实现基于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 }),
  });
  // ...处理响应
};

注意点

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

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

相关推荐
齐鲁大虾35 分钟前
SQL Server 和 MySQL的区别
数据库·mysql
东方巴黎~Sunsiny2 小时前
mysql大表空间整理注意点
数据库·mysql
AllData公司负责人3 小时前
AllData数据中台-数据同步平台集成开源项目Seatunnel-Web,完成Mysql到Doris同步流程
数据库·mysql·开源
Hilaku3 小时前
我是如何用一行 JS 代码,让你的浏览器内存瞬间崩溃的?
前端·javascript·node.js
五仁火烧3 小时前
npm run build命令详解
前端·vue.js·npm·node.js
前端付豪3 小时前
NodeJs 做了什么 Fundamentals Internals
前端·开源·node.js
萧曵 丶4 小时前
MySQL InnoDB 实现 MVCC 原理
数据库·mysql·mvcc
万粉变现经纪人4 小时前
如何解决 pip install mysqlclient 报错 ‘mysql_config’ not found 问题
数据库·python·mysql·pycharm·bug·pandas·pip
lkbhua莱克瓦245 小时前
进阶-SQL优化
java·数据库·sql·mysql·oracle
alonewolf_995 小时前
MySQL 8.0 主从复制原理深度剖析与实战全解(异步、半同步、GTID、MGR)
数据库·mysql·adb