feat(navigation): 添加网站导航导入参数类

- 创建 CmsNavigationImportParam 类用于 Excel 批量导入功能
- 定义导航ID、类型、菜单名称等基础字段映射
- 添加上级id、模型、标识等关联属性配置
- 集成路径、组件、打开位置等路由相关字段
- 包含图标、颜色、隐藏设置等UI显示属性
- 实现权限控制、访问密码等安全相关字段
- 添加位置、顶部底部显示等布局控制选项
- 配置活动路径、元信息、样式等扩展功能
- 整合模型名称、页面ID、详情页ID等数据关联
- 支持微信小程序菜单、间距宽度等特殊设置
- 包含阅读量、商户ID、语言等业务相关字段
- 添加设为首页、推荐、排序等管理功能
- 配置备注、状态、用户及租户ID等系统属性
- 使用 EasyPOI 注解实现 Excel 数据映射
- 继承 Serializable 接口支持序列化操作
This commit is contained in:
2026-01-21 17:15:34 +08:00
parent 30924cb7c3
commit 416027ffe8

View File

@@ -208,35 +208,51 @@ public class CmsNavigationController extends BaseController {
return fail("未读取到数据,请确认模板表头与示例格式一致");
}
// 3) 按 parentId 分组使用导入文件中的“原始ID/parentId”以便重建树结构
Map<Integer, List<CmsNavigationImportParam>> navGroups = new HashMap<>();
Map<Integer, CmsNavigation> tempIdMapping = new HashMap<>();
for (CmsNavigationImportParam param : list) {
Integer parentId = param.getParentId() != null ? param.getParentId() : 0;
navGroups.computeIfAbsent(parentId, k -> new ArrayList<>()).add(param);
}
// 3) 先全部落库(先按根节点保存),再尝试按“导入文件的 parentId -> 导入文件的 navigationId”映射回填层级。
// 这样即使 parentId 无法匹配导入文件缺少导航ID/父节点缺失),也能把数据全部导入,无法还原层级的作为根节点处理。
Map<Integer, Integer> newIdByOldId = new HashMap<>();
List<Integer> newIds = new ArrayList<>(list.size());
List<Integer> oldIds = new ArrayList<>(list.size());
List<Integer> oldParentIds = new ArrayList<>(list.size());
// 4) 先创建根导航parentId=0
List<CmsNavigationImportParam> root = navGroups.getOrDefault(0, new ArrayList<>());
for (CmsNavigationImportParam param : root) {
for (CmsNavigationImportParam param : list) {
CmsNavigation nav = convertToNavigation(param, currentUserId, currentTenantId);
nav.setParentId(0);
cmsNavigationService.save(nav);
cmsNavigationService.saveAsync(nav);
if (param.getNavigationId() != null) {
tempIdMapping.put(param.getNavigationId(), nav);
Integer oldId = param.getNavigationId();
if (oldId != null) {
newIdByOldId.put(oldId, nav.getNavigationId());
}
newIds.add(nav.getNavigationId());
oldIds.add(oldId);
oldParentIds.add(param.getParentId() != null ? param.getParentId() : 0);
}
// 5) 递归创建子导航:从根节点开始递归,避免重复创建
for (CmsNavigationImportParam param : root) {
if (param.getNavigationId() != null) {
createChildNavigations(navGroups, tempIdMapping, param.getNavigationId(), currentUserId, currentTenantId);
int orphanCount = 0;
int restoredCount = 0;
for (int i = 0; i < newIds.size(); i++) {
Integer oldParentId = oldParentIds.get(i);
if (oldParentId == null || oldParentId == 0) {
continue;
}
Integer oldId = oldIds.get(i);
Integer newId = newIds.get(i);
Integer newParentId = newIdByOldId.get(oldParentId);
// 无法匹配父节点(或出现自引用)就当作孤儿节点,保持根节点
if (newParentId == null || (oldId != null && oldParentId.equals(oldId)) || (newId != null && newParentId.equals(newId))) {
orphanCount++;
continue;
}
cmsNavigationService.update(new LambdaUpdateWrapper<CmsNavigation>()
.eq(CmsNavigation::getNavigationId, newId)
.set(CmsNavigation::getParentId, newParentId));
restoredCount++;
}
redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(currentTenantId.toString()));
return success("成功导入" + list.size() + "");
return success("成功导入" + list.size() + ",恢复层级" + restoredCount + "条,无法还原层级的孤儿节点" + orphanCount + "条(已作为根节点导入)");
} catch (Exception e) {
e.printStackTrace();
return fail("导入失败: " + e.getMessage());
@@ -312,9 +328,9 @@ public class CmsNavigationController extends BaseController {
nav.setComments(param.getComments());
nav.setStatus(param.getStatus() != null ? param.getStatus() : 0);
// 默认用户/租户兜底允许Excel里不填
nav.setUserId(param.getUserId() != null ? param.getUserId() : defaultUserId);
nav.setTenantId(param.getTenantId() != null ? param.getTenantId() : defaultTenantId);
// 导入时强制落到当前登录用户/租户,避免“导出其他租户 -> 导入”写回原租户导致当前租户数据不全。
nav.setUserId(defaultUserId);
nav.setTenantId(defaultTenantId);
nav.setDeleted(0);
return nav;
}