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

注意点

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

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

相关推荐
qq_3482318519 分钟前
MySQL 与 PostgreSQL PL/pgSQL 的对比详解
数据库·mysql·postgresql
cui_win1 小时前
Prometheus实战教程 - mysql监控
mysql·prometheus·压测
wsx_iot1 小时前
mysql的快照读和当前读
数据库·mysql
梁萌1 小时前
MySQL分区表使用保姆级教程
数据库·mysql·优化·分区表·分区·partitions
Logic1012 小时前
《数据库运维》 郭文明 实验4 数据库备份与恢复实验核心操作与思路解析
运维·数据库·sql·mysql·学习笔记·形考作业·国家开放大学
hssfscv2 小时前
Mysql学习笔记——多表查询
笔记·学习·mysql
MC皮蛋侠客3 小时前
MySQL数据库迁移脚本及使用说明
数据库·mysql
soft20015253 小时前
《Rocky Linux 9.6 部署 MySQL 8.0 生产手册(含错误处理)》
linux·mysql·adb
帝吃藕和3 小时前
MySQL 知识点复习- 6. inner/right/left join
mysql
你真的可爱呀4 小时前
3.MySQL 数据库集成
mysql·node.js·express