feat(navigation): 同步更新顶级栏目子级显示位置

- 在更新顶级栏目时,若top或bottom显示位置发生变更,递归同步所有子级(含孙级)
- 新增syncChildrenPosition方法,递归收集并批量更新所有后代栏目的top和bottom字段
- 避免触发saveAsync,防止重复生成path和component
- 在控制器层调用该同步方法保持栏目层级位置一致性
This commit is contained in:
2026-07-30 23:16:32 +08:00
parent db680d610d
commit 3e8d3d56b8
3 changed files with 65 additions and 0 deletions

View File

@@ -121,6 +121,24 @@ public class CmsNavigationController extends BaseController {
public ApiResult<?> update(@RequestBody CmsNavigation cmsNavigation) {
// 单页模型绑定唯一性校验:同一租户下同一个单页只能绑定一个栏目
checkPageBindingUnique(cmsNavigation);
// 顶级栏目parentId=0的显示位置top/bottom发生变更时
// 递归同步所有子级(含孙级)的 top/bottom保持层级一致。
if (cmsNavigation.getParentId() != null && cmsNavigation.getParentId() == 0
&& cmsNavigation.getNavigationId() != null
&& (cmsNavigation.getTop() != null || cmsNavigation.getBottom() != null)) {
CmsNavigation oldNav = cmsNavigationService.getById(cmsNavigation.getNavigationId());
if (oldNav != null) {
boolean topChanged = cmsNavigation.getTop() != null && !cmsNavigation.getTop().equals(oldNav.getTop());
boolean bottomChanged = cmsNavigation.getBottom() != null && !cmsNavigation.getBottom().equals(oldNav.getBottom());
if (topChanged || bottomChanged) {
cmsNavigationService.syncChildrenPosition(
cmsNavigation.getNavigationId(),
cmsNavigation.getTop() != null ? cmsNavigation.getTop() : oldNav.getTop(),
cmsNavigation.getBottom() != null ? cmsNavigation.getBottom() : oldNav.getBottom(),
getTenantId());
}
}
}
if (cmsNavigationService.updateById(cmsNavigation)) {
// 修改成功事务处理
cmsNavigationService.saveAsync(cmsNavigation);

View File

@@ -42,4 +42,15 @@ public interface CmsNavigationService extends IService<CmsNavigation> {
void saveAsync(CmsNavigation cmsNavigation);
CmsNavigation getByIdRelByCodeRel(String code);
/**
* 递归同步顶级栏目所有子级含孙级的显示位置top/bottom
* 仅当顶级栏目位置发生变更时调用;只更新 top/bottom 字段,不触发 saveAsync。
*
* @param navigationId 顶级栏目ID
* @param top 顶部显示标记1/0
* @param bottom 底部显示标记1/0
* @param tenantId 租户ID
*/
void syncChildrenPosition(Integer navigationId, Integer top, Integer bottom, Integer tenantId);
}

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.cms.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.cms.entity.CmsDesign;
import com.gxwebsoft.cms.entity.CmsModel;
@@ -23,6 +24,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
/**
@@ -178,6 +180,40 @@ public class CmsNavigationServiceImpl extends ServiceImpl<CmsNavigationMapper, C
// }
}
/**
* 递归同步顶级栏目所有子级含孙级的显示位置top/bottom
* 只更新 top/bottom 字段,不触发 saveAsync避免重复生成 path/component。
*/
@Override
public void syncChildrenPosition(Integer navigationId, Integer top, Integer bottom, Integer tenantId) {
List<Integer> descendantIds = new ArrayList<>();
collectDescendantIds(navigationId, tenantId, descendantIds);
if (descendantIds.isEmpty()) {
return;
}
LambdaUpdateWrapper<CmsNavigation> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.in(CmsNavigation::getNavigationId, descendantIds)
.set(CmsNavigation::getTop, top)
.set(CmsNavigation::getBottom, bottom);
update(updateWrapper);
}
/** 递归收集某个栏目下所有后代栏目ID含直接子级与更深层级 */
private void collectDescendantIds(Integer parentId, Integer tenantId, List<Integer> result) {
List<CmsNavigation> children = list(new LambdaQueryWrapper<CmsNavigation>()
.eq(CmsNavigation::getParentId, parentId)
.eq(CmsNavigation::getTenantId, tenantId)
.eq(CmsNavigation::getDeleted, 0)
.select(CmsNavigation::getNavigationId));
if (children.isEmpty()) {
return;
}
for (CmsNavigation child : children) {
result.add(child.getNavigationId());
collectDescendantIds(child.getNavigationId(), tenantId, result);
}
}
@Override
public CmsNavigation getByIdRelByCodeRel(String code) {
CmsNavigationParam param = new CmsNavigationParam();