前言
在GIS系统中图层目录是方便用户对图层进行分类,点击加载的一个很好的展示方式,很多大数据平台都会用到,想做一个图层目录,需要用到树结构,在关系型数据库中树结构通常使用标注parentId的方式来存储。
处理流程
数据库表
构建一个树结构表,带有排序字段(sort),父节点Id(parentId),自身id(id),其余按需添加
前端展示
通过后台接口查询数据,前端使用elementui的树结构来展示,需要先将查询的数据处理成其格式。其中onlyDirectory是为了只展示文件夹的参数,每一级的树节点都会根据sort字段排序,所以如何排序中如何准确处理sort字段很重要!
<div class="layerTree">
<div class="custom-tree-container">
<div class="block" style="width:400px;height: 100%">
<div style="display: flex ; padding: 10px 0px">
<div class="titleContainer">图层目录</div>
</div>
<el-tree v-loading="layerTreeLoading"
:data="layer_tree"
ref="treeRef"
node-key="id"
default-expand-all
:expand-on-click-node="false"
@node-click="nodeclick"
@node-drag-end="handleDragEnd"
@node-drop="handleDrop"
draggable
:allow-drop="allowDrop"
:allow-drag="allowDrag">
<span class="custom-tree-node"
slot-scope="{ node, data }">
<span >
<span :class="data.type == 'layer' ? 'el-icon-picture-outline' :'el-icon-folder'" style="margin-right: 10px"></span>
<template v-if="data.isEdit==1">
<el-input ref="input"
@blur="() => submitEdit(node,data)"
v-model="newlabel"
style="height:20px;line-height:10px"></el-input>
</template>
<span v-else v-text="data.name"></span>
</span>
<span style="margin-left: 30px">
<el-button
type="text"
size="mini"
@click="() => append(node,data)">
添加目录
</el-button>
<el-button
type="text"
size="mini"
@click="() => edit(node,data)">
修改
</el-button>
<el-button
v-if="!data.undeletable"
type="text"
size="mini"
@click="() => remove(node, data)">
删除
</el-button>
</span>
</span>
</el-tree>
</div>
</div>
</div>
handleTree(data,onlyDirectory ,id, parentId, children, rootId, type) {
id = id || 'id'
parentId = parentId || 'parentId'
children = children || 'children'
type = type || 'type'
rootId = rootId || Math.min.apply(Math, data.map(item => {
return item[parentId]
})) || 0
//对源数据深度克隆
const cloneData = JSON.parse(JSON.stringify(data))
//循环所有项
const treeData = cloneData.filter(father => {
let branchArr = cloneData.filter(child => {
//返回每一项的子级数组
return father[id] === child[parentId] && (onlyDirectory? child[type]!= 'layer' : true)
});
branchArr.length > 0 ? father.children = branchArr.sort(function (a,b) {return a.sort-b.sort-1}) : '';
//返回第一层
return father[parentId] === rootId;
});
//第一层排序
let resTreeData = treeData !== '' ? treeData.sort(function (a,b) {return a.sort-b.sort-1}) : data
return resTreeData;
},
排序实现
利用elementui的拖拽实现排序,也可以说是节点的移动,关键在于elementui自带的@node-drag-end监听拖拽结束的时间,主要有三个参数,一个拖拽的节点,一个目标节点,一个目标节点状态。
同一个父节点
情况一
4移动到0后,此时拖拽的节点为4,目标节点为0,在目标节点后面所以状态为after,
拖拽节点的sort = 目标节点的sort + 1
1、2、3节点加1
情况二
1移动到3后,此时拖拽的节点为1,目标节点为3,在目标节点后面所以状态为after,
拖拽节点的sort = 目标节点的sort
2和3节点减1
情况三
4移动到1前,此时拖拽的节点为4,目标节点为1,在目标节点前面所以状态为before,
拖拽节点的sort = 目标节点的sort
1、2、3节点加1
情况四
1移动到4前,此时拖拽的节点为1,目标节点为4,在目标节点前面所以状态为before,
拖拽节点的sort = 目标节点的sort - 1
2和3节点减1
不同父节点
情况一
目录1的3节点移动到目录2的1节点前,此时拖拽的节点为目录1的节点3,目标节点为目录2的节点1,在目标节点前面所以状态为before,
拖拽节点的parentId = 目标节点的parentId
拖拽节点的sort = 目标节点的sort
目录1的4节点减1
目录2的1234节点加1
情况二
目录1的3节点移动到目录2的1节点前,此时拖拽的节点为目录1的节点3,目标节点为目录2的节点0,在目标节点后面所以状态为after,
拖拽节点的parentId = 目标节点的parentId
拖拽节点的sort = 目标节点的sort + 1
目录1的4节点减1
目录2的1234节点加1
情况三
elementui拖拽到目录节点上而不是之间时,其状态状态会为inner,将目录1的节点3拖拽到目录2,将默认后方插入,此时
目录1的4节点减1
拖拽节点的sort = 目录2长度
实现代码
handleDragEnd(draggingNode, dropNode, dropType, ev) {
let update_data = draggingNode.data
update_data["oldParentId"] = update_data.parentId
update_data["oldSort"] = update_data.sort
update_data["dropType"] = dropType
if(dropType == 'after'){
let newParentId = dropNode.parent.level == 0 ? "0" :dropNode.parent.data.id
if(update_data.parentId == newParentId)
if(dropNode.data.sort > update_data.sort ) {
update_data.sort = dropNode.data.sort
}
else{
update_data.sort = dropNode.data.sort + 1
}
else{
update_data.sort = dropNode.data.sort + 1
}
update_data.parentId = newParentId
}
else if(dropType == 'before'){
let newParentId = dropNode.parent.level == 0 ? "0" :dropNode.parent.data.id
if(update_data.parentId == newParentId) {
if (dropNode.data.sort < update_data.sort) {
update_data.sort = dropNode.data.sort
} else {
update_data.sort = dropNode.data.sort - 1
}
}
else{
update_data.sort = dropNode.data.sort
}
update_data.parentId = newParentId
}
else if(dropType == 'inner'){
debugger
update_data.parentId = dropNode.data.id
}
else{
return
}
this.layerTreeLoading = true
updateLayertreeSort(update_data).then(res=>{
this.$modal.msgSuccess('修改成功')
this.initLayerTree()
})
},
/*service*/
public void updateLayertreeSort(LayertreeUpdateSortReqVO updateReqVO){
// 校验存在
validateLayertreeExists(updateReqVO.getId());
//检验更新方法
if(updateReqVO.getOldParentId().longValue() == updateReqVO.getParentId().longValue()) {
if(updateReqVO.getOldSort() > updateReqVO.getSort()){
layertreeMapper.updateSortBefore(updateReqVO.getOldParentId(),updateReqVO.getOldSort(),updateReqVO.getSort());
}
else{
layertreeMapper.updateSortAfter(updateReqVO.getOldParentId(),updateReqVO.getOldSort(),updateReqVO.getSort());
}
}
else{
if (updateReqVO.getDropType().equals("before") || updateReqVO.getDropType().equals("after")){
layertreeMapper.updateSortInOldParent(updateReqVO.getOldParentId(),updateReqVO.getOldSort());
layertreeMapper.updateSortInNewParent(updateReqVO.getParentId(),updateReqVO.getSort());
}
else if (updateReqVO.getDropType().equals("inner")) {
layertreeMapper.updateSortInOldParent(updateReqVO.getOldParentId(),updateReqVO.getOldSort());
Set<Long> set = new HashSet<>();
set.add(updateReqVO.getParentId());
int length = layertreeMapper.selectList("parent_id",set).size();
updateReqVO.setSort(length);
} else {
return;
}
}
// 更新
LayertreeDO updateObj = LayertreeConvert.INSTANCE.convert(updateReqVO);
layertreeMapper.updateById(updateObj);
};
/*mybatis mapper*/
@Update("update sbsj_layertree set sort = sort + 1 where parent_id = #{parentId} and sort >= #{newSort} and sort < #{oldSort}")
void updateSortBefore(@Param("parentId") Long parentId, @Param("oldSort") Integer oldSort,@Param("newSort") Integer newSort);
@Update("update sbsj_layertree set sort = sort - 1 where parent_id = #{parentId} and sort > #{oldSort} and sort <= #{newSort}")
void updateSortAfter(@Param("parentId") Long parentId, @Param("oldSort") Integer oldSort,@Param("newSort") Integer newSort);
@Update("update sbsj_layertree set sort = sort - 1 where parent_id = #{parentId} and sort > #{oldSort}")
void updateSortInOldParent(@Param("parentId") Long parentId,@Param("oldSort") Integer oldSort);
@Update("update sbsj_layertree set sort = sort + 1 where parent_id = #{parentId} and sort >= #{newSort}")
void updateSortInNewParent(@Param("parentId") Long parentId,@Param("newSort") Integer newSort);
结尾
要确保排序的正确还需要在其添加节点、删除节点、修改节点的时候都要做数据库校验与控制,这里就不一一阐述。
参考
芋道源码: https://doc.iocoder.cn/
elementui树形控件: https://element.eleme.io/#/zh-CN/component/tree