前端实现树的拖拽排序,刷新后顺序保持不变

718 阅读3分钟

背景

需求是实现树的拖拽排序,刷新后顺序保持不变,节点还能被删除,跨层级拖动。

Jul-03-2022 13-02-59.gif

方案

初步结论是后端的数据结构要提供一个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" 
    }
]

神奇的事情发生了,坐标重复了,34坐标一样,但是拖拽的时候,在时间维度是有先后顺序的,正常来说,应该按照最后一次为主【坐标冲突】。

接着解决,自然想到,那加入时间维度,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的时候,在请求后端一次列表接口,对比有没有变化,如果有变化,就让后者刷新后再更新。

结语

这次需求如果放在后端其实也是一样的,当时想着前端也应该可以解决,尝试了一下,发现难度系数有点大,算法方面,前后端都一样的实现。从纯前端的角度考虑,也等于打开了视野,最后希望有同学如果有更好的方案,欢迎一起探讨。