快速实现一个antd的table组件的拖拽排序功能

4,170 阅读2分钟

最近产品要求开发一款支持拖拽排序的table表格,看了一下antd官方给的示例里引入了一堆三方库,写法更是花里胡哨,感到极其的繁琐,这了介绍一下自己实现的方法。(经过反馈官方已在2023.1月更新了新的demo,现在优雅了很多,不喜欢三方库的同学可以看看以下纯原生写法😊)

dragTable.gif

话不多说,直接上码:

方法封装

const onRow = (state:[], setState:any) => ({
    draggable: true,
    style: { cursor: 'move' },
    onDragStart: (ev:any) => {
      ev.dataTransfer.effectAllowed = "move";
      ev.dataTransfer.setData('text', ev.target.getAttribute('data-row-key'));
    },
    onDragEnter: (ev:any) => {
      const nodes = ev.target.parentNode.childNodes;
      nodes.forEach((item:any) => item.style.borderTop = '2px dashed #1890ff');
    },
    onDragLeave: (ev:any) => {
      const nodes = ev.target.parentNode.childNodes;
      nodes.forEach((item:any) => item.style.borderTop = '');
    },
    onDrop: (ev:any) => {
      ev.preventDefault();
      ev.stopPropagation();
      const dragId = Number(ev.dataTransfer.getData('text'));
      const dropCol = ev.target.tagName !== 'TR' ? ev.target.parentNode : ev.target;
      // dropCol.parentNode.insertBefore(dragCol, dropCol); // DOM操作
      const dropId = Number(dropCol.getAttribute('data-row-key'));
      const dragIndex = state.findIndex((item:any) => item.id === dragId); // 注意这里的id
      const dropIndex = state.findIndex((item:any) => item.id === dropId);
      const data = [...state];
      const item = data.splice(dragIndex, 1); // 移除
      data.splice(dropIndex, 0, item[0]); // 插入
      setState(data);
      dropCol.childNodes.forEach((item:any) => item.style.borderTop = '');
    },
    onDragOver: (ev:any) => ev.preventDefault(),
});

使用

<Table
  rowKey="id"
  dataSource={query}
  pagination={false}
  onRow={() => onRow(query, setQuery)}
>
  <Table.Column
    title="查询条件"
    dataIndex="label"
  />
</Table>

其它代码

const [query, setQuery] = useState<any>([]);

简单讲两句

antd的table组件有个onRow字段,该字段就是表格对应的tr标签。

首先开启draggable属性让元素可以拖拽,在onDragStart里使用事件对象的dataTransfer.setData方法记录拖拽元素的data-row-key,这个key即在table组件设置的rowKey,要保证其唯一性;在onDrop里使用getData读取拖拽元素的key,通过移除和插入更新数组的顺序重新render;在onDragOver阻止浏览器的默认事件。三个拖拽事件相互配合即可轻松快速的完成一款纯天然的拖拽效果,无需引入其它三方库。

onDragEnter和onDragLeave用来添加一些交互展示效果,也可以不用。

另外需要注意:

  1. 在onDrop方法中获取dragIndex和dropIndex时,比较时的字段需要和Table的rowKey一致。比如你的rowKey是uid,那么就要写成const dragIndex = state.findIndex((item:any) => item.uid === dragId)
  2. 如果你的单元格里嵌套了多个元素,那么获取dropCol就要使用递归写法,一直找到tagName为TR为止。const dropCol = ev.target.tagName !== 'TR' ? ev.target.parentNode : ev.target
  3. 在实际生产中还需要更严谨一点,比如判断是否放在拖动元素上,以及向下拖拽插入时,需要记录clientY并和放置的元素的clientY进行比较,来决定样式加borderTop还是bordeBottom等。

参考资料:HTML Drag and Drop API