关于节点的拖拽的实现,我们从前端的实现说起。因为拖拽相当于更新操作,因此就是前端将最新的数据计算好直接让后端更新即可。另外我们注意到,拖拽主要改变之一是sort字段,数据库里原先所有菜单的sort都是0,目前我么可以利用拖拽的机会,给拖拽影响到的所有节点重新排个序,这是必要的。继续去思考:比如你进行了一次拖拽,哪些节点需要让后端更新sort字段呢?那就是该节点所在的所有兄弟节点,你都需要传给后端,放到一个list里面,sort赋上新值。但这里就牵涉到一个很严重的问题:我们能不能拿到被拖拽节点在成功拖拽后所在的所有兄弟节点?答案是得分情况,除去一级菜单以外的任何情况我们都是可以拿到里面任意一层菜单的所有节点,因为我们可以通过某一个节点(这里指的就是dropNode)的parent先找到父节点,再根据父节点找到所有子节点。但我们可以观察到一件事情:一级菜单的parent都是null!那着就完犊子了,我暂时想不到什么方式可以直接拿到这21个一级菜单,虽然他们离我那么近,我却不能得到他们,shit!这就是代码if-else。即判断被拖拽节点是否被拖到一级菜单来划分。那这种情况怎么解决呢?自然是求助于后端了,这不难。
另外你还需要考虑的是:如果被拖动节点里面还有层级(当然最多只会有一层,因为我们设定最多有三级菜单),那被拖动节点的层级改变后,里面层级的节点的层级也会发生变化。但里面就没必要赋sort值更新
不管怎么说,这一拖拽,会影响不少事情,就像洛基偷走了宇宙魔方产生了新时间线那样复杂。不过你也看到了,我们需要更新的所有节点要更新的字段可能不太一样,比如有的需要更新层级,有的不需要,有的需要更新parentCid,有的不需要,我们可以将这些节点分各类,放到不同的数组里,返给后端,这样后端处理起来就比较从容。以下是前端代码:
handleDrop(draggingNode, dropNode, dropType, ev) {
console.log("tree drop: ", draggingNode, dropNode, dropType, ev);
let param = { type: "" };
let nodeArray = [];
let subLevelChangeArray = [];
let catLevel = 1;
//我们需要根据该毁掉方法拿到被拖拽节点最新的parentCid、catLevel、sort
//我们发现这样一件事情,那就是最新的这三个字段全都可以从dropNode这个节点中拿到
//1.当前节点最新的父节点id
//before、after、inner
// if (dropType == "inner") {
// parentCid = dropNode.data.catId;
// } else {
// parentCid = dropNode.data.parentCid;
// }
//2.当前拖拽节点的最新层级,注意,拖拽节点并不会影响其父亲节点以及兄弟的层级,因为这里所谓的
//层级指的是自己这个节点本身的深度,而不是它拥有的子节点的深度
//但是会影响这个被拖拽节点的子节点的深度,比如将一个二级节点拖拽到一级节点的位置,那么如果原来这个二级节点
//下面还有一级,这级节点原来层级为3,现在都变成了2。因此我们需要遍历这个被拖拽节点的所有子节点,每一层(最多只会有一层了)
//的所有节点层级都变为被拖拽节点的层级加一
if (dropType == "inner") {
catLevel = dropNode.data.catLevel * 1 + 1;
for (
let index = 0;
index < draggingNode.data.entityChildren.length;
index++
) {
let currentData = draggingNode.data.entityChildren[index];
let object = {};
object["catLevel"] = currentData.catLevel + 1;
object["catId"] = currentData.catId;
subLevelChangeArray.push(object);
}
param["subLevelChangeArray"] = subLevelChangeArray;
} else {
catLevel = dropNode.data.catLevel;
}
//3.当前拖拽节点的最新顺序
//之所以判断 draggingNode.level 就是因为如果拖拽到一级菜单的话,父级为null,无法进行排序
if (dropNode.level == 1 && dropType != "inner") {
this.expandedKeys = [];
//type字段表示是否被拖拽到1层级,如果是这种情况,该层级的排序需要交给服务端处理
param["type"] = "1";
} else {
//type字段表示是否被拖拽到1层级,如果不是这种情况,该层级的排序在前端就能处理
param["type"] = "0";
if (dropType == "inner") {
this.expandedKeys = [];
this.expandedKeys.push(dropNode.data.catId);
for (let index = 0; index < dropNode.childNodes.length; index++) {
let currentNode = dropNode.childNodes[index];
let object = { catId: "", parentCid: "", catLevel: "", sort: "" };
object["catId"] = currentNode.data.catId;
object["sort"] = index;
object["parentCid"] = dropNode.data.catId;
if (currentNode.id == draggingNode.id) {
object["catLevel"] = catLevel;
}
nodeArray.push(object);
}
} else {
this.expandedKeys = [];
this.expandedKeys.push(dropNode.data.parentCid);
for (
let index = 0;
index < dropNode.parent.childNodes.length;
index++
) {
let currentNode = dropNode.parent.childNodes[index];
let object = { catId: "", parentCid: "", catLevel: "", sort: "" };
object["catId"] = currentNode.data.catId;
object["sort"] = index;
object["parentCid"] = dropNode.data.parentCid;
object["catLevel"] = catLevel;
nodeArray.push(object);
}
}
}
param["nodeArray"] = nodeArray;
param["draggingNode"] = draggingNode.data;
param["dropType"] = dropType;
param["dropNode"] = dropNode.data;
console.log("拖拽节点成功后的请求数据:", param);
//以上就拿到了更新数据,发送给后端
this.$http({
url: this.$http.adornUrl("/product/category/list/updateTreeStatus"),
method: "post",
data: this.$http.adornData(param, false),
}).then(({ data }) => {
this.getMenus();
});
},
对于后端来讲,也稍微有一些复杂,那就是一级菜单交给了后端你去查,如何实现从一个顺序状态到另一个顺序状态的变化,有一种情况很容易解决,那就是如果是从其他层级的菜单拖到一级菜单,那仅相当于add操作,你只需要找到add的位置即可,原本一级菜单的顺序没有变化,很容易;但如果仅仅是一级菜单间的拖动如何实现呢?我最开始想的是线性代数里的“交换”的概念,即这种情况利用相邻菜单之间的两两交换肯定可以实现效果,但这种做法过于麻烦,肯定会有不少bug。突发奇想,一个小妙招出现了!!!我tm完全可以将被拖动的这个一级菜单看做其他层级的,这样就和上面的做法一样了,只需要add即可!!!那么如何实现呢?笨蛋,那当然是将被拖动菜单先remove一下啦,这样再add回来,可以实现逻辑(方法)的复用!我真是tm小聪明蛋儿!!!!下面是后端代码:
/** 需要批量更新排序的节点 */
private List<CategoryEntity> nodeArray;
/** type字段表示是否被拖拽到1层级 1:是 0:不是 */
private Integer type;
/** 需要批量更新层级的节点(由于被拖拽节点层级的改变导致的其所有子节点层级改变) */
private List<CategoryEntity> subLevelChangeArray;
/** 被拖拽的节点信息 */
private CategoryEntity draggingNode;
/** 拖拽类型:before、after、inner */
private String dropType;
/** dropNode的节点信息 */
private CategoryEntity dropNode;
}
@Override
public void updateTreeStatus(UpdateTreeStatusVO vo) {
List<CategoryEntity> nodeArray = CollectionUtil.isEmpty(vo.getNodeArray()) ? new ArrayList<>() : vo.getNodeArray();
List<CategoryEntity> subLevelChangeArray = CollectionUtil.isEmpty(vo.getSubLevelChangeArray()) ? new ArrayList<>() : vo.getSubLevelChangeArray();
CategoryEntity draggingNode = vo.getDraggingNode();
String dropType = vo.getDropType();
CategoryEntity dropNode = vo.getDropNode();
if(vo.getType() == 1){
QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
wrapper.eq("parent_cid",0);
List<CategoryEntity> categoryEntityList = baseMapper.selectList(wrapper);
categoryEntityList.sort((a,b) ->a.getSort() - b.getSort());
if(draggingNode.getCatLevel().equals(1)){
Iterator<CategoryEntity> iterator = categoryEntityList.iterator();
while(iterator.hasNext()){
CategoryEntity entity = iterator.next();
if(entity.getCatId().equals(draggingNode.getCatId())){
iterator.remove();
}
}
}
mixCategory(draggingNode, dropType, dropNode, categoryEntityList);
categoryEntityList.addAll(subLevelChangeArray);
categoryEntityList.sort((a,b) ->a.getSort() - b.getSort());
categoryDao.updateTreeNodeDataBatch(categoryEntityList);
}else{
nodeArray.addAll(subLevelChangeArray);
nodeArray.sort((a,b) ->a.getSort() - b.getSort());
categoryDao.updateTreeNodeDataBatch(nodeArray);
}
}
/**
*
* @param draggingNode
* @param dropType
* @param dropNode
* @param categoryEntityList
*/
private void mixCategory(CategoryEntity draggingNode, String dropType, CategoryEntity dropNode, List<CategoryEntity> categoryEntityList) {
Integer increment = 0;
Integer index = 0;
Integer sort = 0;
for (CategoryEntity entity : categoryEntityList) {
if("before".equals(dropType) && entity.getCatId().equals(dropNode.getCatId())){
index = increment;
entity.setSort(increment + 1);
sort = increment;
increment += 2;
}else if("after".equals(dropType) && entity.getCatId().equals(dropNode.getCatId())){
index = increment + 1;
entity.setSort(increment);
sort = increment + 1;
increment += 2;
}else{
entity.setSort(increment);
increment +=1;
}
}
draggingNode.setCatLevel(1);
draggingNode.setParentCid(0L);
draggingNode.setSort(sort);
categoryEntityList.add(index, draggingNode);
}
如何写批量更新的动态sql ?下面是个例子:
update pms_category
<trim prefix="set" suffixOverrides=",">
<trim prefix="parent_cid =case" suffix="end,">
<foreach collection="list" item="i" index="index">
<if test="i.parentCid!=null">
when cat_id=#{i.catId} then #{i.parentCid}
</if>
</foreach>
</trim>
<trim prefix=" cat_level =case" suffix="end,">
<foreach collection="list" item="i" index="index">
<if test="i.catLevel!=null">
when cat_id=#{i.catId} then #{i.catLevel}
</if>
</foreach>
</trim>
<trim prefix="sort =case" suffix="end," >
<foreach collection="list" item="i" index="index">
<if test="i.sort!=null">
when cat_id=#{i.catId} then #{i.sort}
</if>
</foreach>
</trim>
</trim>
where
<foreach collection="list" separator="or" item="i" index="index" open="(" close=")">
cat_id=#{i.catId}
</foreach>