要通过 Sequelize 的 Schema 和 Ant Design 的 DraggableTable 组件实现拖拽排序功能,需要在后端设计一个用于存储排序顺序的字段,并在前端实现拖拽排序的逻辑。本文介绍一个实现此功能的步骤指南以及对应的代码示例:
1. 后端 (Sequelize 模型设计)
首先,在 Sequelize 模型中添加一个字段,比如 sortOrder
,用于存储每个记录的排序顺序:
const Item = sequelize.define('item', {
// ...其他字段
sortOrder: {
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: 0 // 默认排序值
}
// ...其他配置
});
2. 后端 (处理排序更新请求)
接着,创建一个接口来处理排序更新的请求:
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 组件来使行可拖拽,并在拖拽结束时发送更新请求到后端:
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. 前端 (发送排序更新请求)
当用户完成拖拽操作时,触发一个函数来发送新的排序顺序给后端:
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 }),
});
// ...处理响应
};
上述代码实现了前端的拖拽排序功能,并在每次拖拽结束时更新后端数据库中的排序字段。这样,无论何时从数据库中检索数据,它们都将按照用户定义的顺序进行排序。
如果前端提交的数据包括分页信息(current
和 pageSize
),并且开发者希望在分页的环境中实现拖拽排序,这个情况会更复杂。因为排序操作可能会影响多个页面上的数据。为了处理这种情况,可以采用下述策略:
1. 后端处理
当后端接收到排序更新请求时,它需要考虑当前页面和页面大小,以便正确计算全局排序顺序。具体实现可能会根据业务逻辑而有所不同,但基本思路是:
- 计算当前页面第一项在全局数据中的排序位置。
- 更新当前页面项的全局排序位置。
- 考虑到排序变更可能会影响到其他页面的数据,可能需要重新计算并更新其他页面数据的排序位置。
2. Sequelize 更新逻辑
假设前端发送的数据包括排序项的 ID、当前页数 current
和每页大小 pageSize
:
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. 前端逻辑
前端在发送排序请求时,应包括当前页面和页面大小信息:
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 }),
});
// ...处理响应
};
注意点
- 需要考虑到用户可能将项目从一个页面拖到另一个页面的情况。这种情况下可能需要重新加载并重新排序相关页面上的数据。
- 也可能需要在数据库查询中采用一些优化措施,特别是在处理大量数据时,以保持系统性能和响应速度。
这种分页情况下的拖拽排序实现相对复杂,需要仔细考虑不同情况下的数据处理和用户体验。