树形结构的数据常用于部门管理等有层次结构的场景。目前对于树形结构的UI操作已经有相当多的库了,如ztree、jsTree;一些出名的组件库也都内置的相应的组件,如element-ui。但就目前关于树拖拽操作的后台逻辑实现却少有人提及,特别是在像mysql这种关系型数据下的实现。正好公司前些日子需要这个功能,形成了一个实现方案,整理后放出。
不了解kotlin基本语法可能看着会有点恼火,毕竟它的糖实在是太多了。查询使用的语句比较简单,不了解ktorm,应该影响不大。
?
案例代码:gitee
部分新用户一次性导入大量的部门信息,需要调整部门间层级关系。原有的编辑框操作繁琐也不直观,所以想能不能通过拖拽实现层级关系调整。
?
经过调查,用户导入的部门数量最大在四位数的量级,部门层级最多在5级之内。由于有权限的设定,所以也有查询某部门下所有子部门的需求。
?
总的来说,拖拽功能是较低频的操作,而查询某部门下所有子部门是高频操作。因此功能设计的总体目标也就出来:拖拽可以慢点,查询需要快。
?
在关系型数据存储树形结构通常考虑四种方法:
此次采用前两方式混合。即
CREATE TABLE `tree_node`
(
`id` int unsigned NOT NULL AUTO_INCREMENT,
`create_time` datetime DEFAULT NULL,
`name` varchar(60) NOT NULL,
`node_path` varchar(500) DEFAULT NULL,
`p_id` int unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
p_id
记录此节点的上级节点的id值。如果没有上级节点则为空。
node_path
记录所有上级节点id值,用 ,
分隔,如果没有上级节点则为空。
?
在写此案例时,为了简便,将根节点设定为虚拟的。即在代码设定一个id为null
为根节点,数据库中所有p_id
为空的节点都此虚拟节点的子节点。
如果不希望出虚拟节点则考虑添加一个标识符,做某些运算注意避开就行。
?
此次功能重点在拖拽和查询,其他新增、编辑之类操作就略去。
?
加入node_path
字段就是为了提高查询效率的。根据我们的设定,查询任意节点下所有子节点仅需要一次查询,其伪代码如下:
`node_path` like CONCAT(此节点的`node_path`,‘,‘,此节点的id,‘%‘)
当然大部分情况下还需要包含当前指定节点信息,所以还需要再根据id查询一次。对于我们设定有虚拟节点的情况,还需要再加一个if分支,具体可见TreeNodeService#findSubTree
方法。
仅仅查询扁平化的数据是不够的,我们还需要将数据组装成前端可用的数据。
对于我们这个规模的数据组装倒不难。此处用空间换时间,用两个循环和一个map来实现。大体思路为:
childList
集合中具体可见NodePathUtil#assembleTreeData
方法。
?
这里是最麻烦。由于拖拽是在页面操作完成,所以需要先了解常用拖拽组件的实现。考察过ztree、iview等组件的拖拽功能后,可以总结出这些组件在拖拽时提供信息,将归纳总结如下:
inner
、prev
、next
表示到目标节点的内部,前面,后面。不同组件下用的单词不一样。但需要注意这仅表示相对方向,即从第一层级拖到第十层级也只会有这三个方向。所以前端组件没办法很好表示跨层级移动。经过上面的分析,我们得出拖拽动作需要最少数据如下:
data class TreeNodeMoveVO(
val sourceId: Int,
val sourcePid: Int?, // 被移动后新的pid
val targetId: Int?,
val direction: MoveDirection, // 移动方向枚举
)
正如前面的分析,我们对于节点移动具体信息是不清楚的,没办法知道此节点是从哪一级移到哪一级的(事实上就算知道了对实现也没太大帮助)。大体思路:
具体代码见TreeNodeService#moveNode
方法。
?
说明:
node_path是以字符串的形式保存的,当中间层级节点被移动时,它的所有子节点都需要进行node_path以确保子树查询的正常性。正如之前分析,无法很好地获取节点层级移动信息,所以对node_path更新无论是采用是字符串切分或是正则都会出错。所以才使用此方法。如果有兴趣可以采用其他方法试试。
正如上问题的原因一样,这是为了更新sourceNode它的子节点node_path。虽然可以通过查询直接获取它的子节点,这里的处理就看个人需求了。
?
测试包内TreeNodeDataInit#initData
可快速生成一千条三个层次的数据。
原文:https://www.cnblogs.com/llh4cnblogs/p/15111647.html