拖拽表格行时数据和视图不同步怎么办?
我用原生 JS 实现了表格行的拖拽排序,但拖完之后页面上看起来顺序对了,实际数据数组没变,点保存还是老顺序,这咋整?
试过在 drop 事件里手动 splice 调整数组,但索引老是算错,特别是跨行拖动的时候。有没有靠谱的处理方式?
function handleDrop(e) {
const draggedIndex = parseInt(e.dataTransfer.getData('text/plain'));
const targetIndex = [...table.rows].indexOf(e.target.closest('tr'));
// 这里交换数组元素经常出错
const item = dataList[draggedIndex];
dataList.splice(draggedIndex, 1);
dataList.splice(targetIndex, 0, item);
}
先说说你代码里的几个坑:
第一个问题是 table.rows 和 dataList 的索引不对应。如果你表格有表头,那 table.rows[0] 其实是表头那行,但 dataList[0] 是第一条数据,索引直接差1。
第二个问题是你没考虑拖拽方向。当拖拽行向下移动时,目标索引的计算会有微妙差异。
第三个问题也是最关键的,当 draggedIndex 和 targetIndex 相等时,你的 splice 逻辑会出问题。
我给你一个修复后的版本:
几个关键点解释一下:
用 table.tBodies[0].rows 而不是 table.rows,这样自动跳过表头,索引就对上 dataList 了。
关于索引调整的原理:比如你把第0行拖到第2行,splice(0,1) 删除后,原来的第1、2行变成了新的第0、1行,这时候你要插入到第2个位置,但数组长度已经变了,所以要插入到 targetIndex - 1 = 1 的位置。
最后调用 renderTable() 重新渲染,这样视图和数据彻底同步,不会出现各种奇怪的同步问题。
你现在试一下,应该就解决了。
你想想,当你把第 0 行拖到第 3 行的位置,先执行
splice(0, 1)删除了元素,数组长度减 1,原来第 3 行的元素现在实际在第 2 行的位置。这时候你再用splice(3, 0, item)插入,位置就错了。正确的做法是根据拖拽方向来调整插入位置。往下拖的时候,目标索引要减 1;往上拖的时候直接用目标索引就行。
给你改了一版:
还有几个坑得提醒你。一个是
e.target可能是 td 或者 span 之类的子元素,必须用closest('tr')找到行元素,这块你倒是处理了。另一个是如果表格有 thead,table.rows会把表头也算进去,索引就对不上了,建议用querySelectorAll('tbody tr')只取数据行。最后别忘了在
dragover事件里e.preventDefault(),不然 drop 事件压根不会触发,这个低级错误我以前也踩过。