背景
需求是实现树的拖拽排序,刷新后顺序保持不变,节点还能被删除,跨层级拖动。
方案
初步结论是后端的数据结构要提供一个pos的字段,来标识这个坐标位置,我们拖拽后会调用update接口来更新pos的值【每次只能更新一个】,没被拖拽前,pos:'',然后排序,先排拖动过的,没拖动过的按原顺序即可。
{
"name":"4",
"pos":"3" // 所在的顺序
}
问题
会有大量的坐标重复,你把4拖拽到3的位置,一轮顺序后,后端返回
[
{
"name":"1",
"pos":""
},
{
"name":"2",
"pos":""
},
{
"name":"3",
"pos":""
},
{
"name":"4",
"pos":"3"
}
]
这个时候,我再把4拖到3的位置,坐标更新如下,后端返回
[
{
"name":"1",
"pos":""
},
{
"name":"2",
"pos":""
},
{
"name":"3",
"pos":"3"
},
{
"name":"4",
"pos":"3"
}
]
神奇的事情发生了,坐标重复了,3和4坐标一样,但是拖拽的时候,在时间维度是有先后顺序的,正常来说,应该按照最后一次为主【坐标冲突】。
接着解决,自然想到,那加入时间维度,pos: '1.1656825912676',排序的时候对比时间,然后对比坐标,理论上可行。
[
{
"name":"1",
"pos":""
},
{
"name":"2",
"pos":""
},
{
"name":"3",
"pos":"3.1656826001429"
},
{
"name":"4",
"pos":"3.1656826013326"
}
]
排序下来,4的位置坐标是有效的,3应该在4的后面,位置相同按时间来,目前来看好像没啥问题。页面效果:
1 -> 2 -> 4 -> 3
再来:1拖到3的位置
[
{
"name":"1",
"pos":"3.1656826414703"
},
{
"name":"2",
"pos":""
},
{
"name":"3",
"pos":"3.1656826001429"
},
{
"name":"4",
"pos":"3.1656826013326"
}
]
这个时候3个相同的坐标了,这个时候,页面效果是:
2 -> 3 -> 1 -> 4
难题是你怎么确定,3会排在1的前面,而4会排在1的后面,因为大家坐标都相同。
这还只是几个case,如果100+个节点,会引起混乱,出了bug,很难解决。
方案二
采用单项链表来做,必须记住上一个节点,理论上问题应该解决了,但是链表就必须发送至少2个更新接口来更新。
[
{
"name":"1",
"pos":""
},
{
"name":"2",
"pos":"1"
},
{
"name":"3",
"pos":"2"
},
{
"name":"4",
"pos":"3"
}
]
当拖动2->3的时候,更新如下
{
"name":"1",
"pos":""
},
{
"name":"2",
"pos":"3" // 1 -> 3
},
{
"name":"3",
"pos":"1" // 2 -> 1
},
{
"name":"4",
"pos":"2" // 3 -> 2
}
必须更新三个update接口满足需求,所有情况均满足要求。
最后
还有一个问题,两个人同时拖动,那怎么更新,引入了锁的机制,只能在onDrop的时候,在请求后端一次列表接口,对比有没有变化,如果有变化,就让后者刷新后再更新。
结语
这次需求如果放在后端其实也是一样的,当时想着前端也应该可以解决,尝试了一下,发现难度系数有点大,算法方面,前后端都一样的实现。从纯前端的角度考虑,也等于打开了视野,最后希望有同学如果有更好的方案,欢迎一起探讨。