Explorar el Código

feat(mall): 完善分类管理功能和PC端设计接口

- 添加syncCategoryId字段支持远程分类同步
- 实现分类主题色配置功能
- 新增PcDkhDesignController提供PC端设计相关接口
- 优化分类树构建逻辑,支持增量更新
- 扩展数据权限拦截器支持dkh_表前缀
- 添加推荐主题配置查询功能
- 完善头部菜单和分类设置的数据填充逻辑
hurx hace 11 horas
padre
commit
626cf3e1ef

+ 3 - 1
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlatformDataScopeInterceptor.java

@@ -128,7 +128,8 @@ public class PlatformDataScopeInterceptor implements Interceptor {
         "maintenance_server_item",
         "contract_supply",
         "team_member",
-        "ep_"
+        "ep_",
+        "dkh_"
         // 注意:前缀匹配需特殊处理(如 qrtz_),见 isIgnoreTable 方法
     ));
 
@@ -252,6 +253,7 @@ public class PlatformDataScopeInterceptor implements Interceptor {
             || tableName.startsWith("protocol_")
             || tableName.startsWith("ns_diy_")
             || tableName.startsWith("ep_")
+            || tableName.startsWith("dkh_")
             ;
     }
 

+ 112 - 0
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/controller/pc/PcDkhDesignController.java

@@ -0,0 +1,112 @@
+package org.dromara.mall.controller.pc;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.mall.domain.DkhRecommendThemeConfig;
+import org.dromara.mall.domain.bo.*;
+import org.dromara.mall.domain.vo.*;
+import org.dromara.mall.service.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * pc大客户控制器
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/pcDkhDesign")
+public class PcDkhDesignController {
+
+    private final IDkhMallConfigService dkhMallConfigService;
+
+    private final IDkhCarouselConfigService dkhCarouselConfigService;
+
+    private final IDkhCategoryMainService dkhCategoryMainService;
+
+    private final IDkhHeaderMenuService dkhHeaderMenuService;
+
+    private final IDkhAdConfigService dkhAdConfigService;
+
+    private final IDkhScenarioConfigService dkhScenarioConfigService;
+
+    private final IDkhRecommendThemeConfigService dkhRecommendThemeConfigService ;
+
+
+    /**
+     * 获取当前搜索页面设置
+     */
+    @GetMapping("/currentSearchConfig")
+    public R<DkhMallConfigVo> getCurrentSearchConfig() {
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        return R.ok(dkhMallConfigService.getCurrentMallConfig(customerId));
+    }
+
+    /**
+     * 轮播图设置
+     */
+    @GetMapping("/carouselList")
+    public TableDataInfo<DkhCarouselConfigVo> carouselList(DkhCarouselConfigBo bo, PageQuery pageQuery) {
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        bo.setCustomerId(customerId);
+        bo.setStatus(1L);//启用
+        return dkhCarouselConfigService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 分类设置
+     */
+    @GetMapping("/categoryMainList")
+    public TableDataInfo<DkhCategoryMainVo> categoryMainList(DkhCategoryMainBo bo, PageQuery pageQuery) {
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        bo.setCustomerId(customerId);
+        bo.setStatus(1L);//启用
+        return dkhCategoryMainService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 头部菜单
+     */
+    @GetMapping("/headerCategoryList")
+    public TableDataInfo<DkhHeaderMenuVo> headerCategoryList(DkhHeaderMenuBo bo, PageQuery pageQuery) {
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        bo.setCustomerId(customerId);
+        bo.setStatus(1L);//启用
+        return dkhHeaderMenuService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 广告模块
+     */
+    @GetMapping("/adConfigList")
+    public TableDataInfo<DkhAdConfigVo> adModuleConfigList(DkhAdConfigBo bo, PageQuery pageQuery) {
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        bo.setCustomerId(customerId);
+        return dkhAdConfigService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 场景方案设置
+     */
+    @GetMapping("/currentScenarioConfig")
+    public TableDataInfo<DkhScenarioConfigVo> getCurrentScenarioGlobalSetting(DkhScenarioConfigBo bo, PageQuery pageQuery) {
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        bo.setCustomerId(customerId);
+        bo.setStatus(1L);//启用
+        return dkhScenarioConfigService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 推荐设置
+     */
+    @GetMapping("/recommendThemeConfig")
+    public R<DkhRecommendThemeConfigVo> getCurrentRecommendThemeConfig() {
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        return R.ok(dkhRecommendThemeConfigService.getCurrentRecommendThemeConfig(customerId));
+    }
+}

+ 5 - 0
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/domain/DkhCategorySub.java

@@ -62,6 +62,11 @@ public class DkhCategorySub extends TenantEntity {
      */
     private Long status;
 
+    /**
+     * 远程商品分类表中同步过来的分类ID
+     */
+    private Long syncCategoryId;
+
     /**
      * 删除标志(0代表存在,2代表删除)
      */

+ 5 - 0
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/domain/bo/DkhCategorySubBo.java

@@ -60,6 +60,11 @@ public class DkhCategorySubBo extends BaseEntity {
      */
     private Long status;
 
+    /**
+     * 远程商品分类表中同步过来的分类ID
+     */
+    private Long syncCategoryId;
+
     /**
      * 备注
      */

+ 1 - 0
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/domain/vo/DkhCategoryMainVo.java

@@ -91,5 +91,6 @@ public class DkhCategoryMainVo implements Serializable {
     @ExcelProperty(value = "备注")
     private String remark;
 
+    private String categoryThemeColor;
 
 }

+ 5 - 0
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/domain/vo/DkhCategorySubVo.java

@@ -80,6 +80,11 @@ public class DkhCategorySubVo implements Serializable {
     @ExcelProperty(value = "启用状态:1-开启,0-关闭")
     private Long status;
 
+    /**
+     * 远程商品分类表中同步过来的分类ID
+     */
+    private Long syncCategoryId;
+
     /**
      * 备注
      */

+ 2 - 1
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/domain/vo/DkhHeaderMenuVo.java

@@ -72,5 +72,6 @@ public class DkhHeaderMenuVo implements Serializable {
     @ExcelProperty(value = "备注")
     private String remark;
 
-
+    /*头部主题色*/
+    private String headerThemeColor;
 }

+ 2 - 2
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/domain/vo/DkhRecommendThemeConfigVo.java

@@ -11,7 +11,7 @@ import lombok.Data;
 import java.io.Serial;
 import java.io.Serializable;
 import java.util.Date;
-
+import java.util.List;
 
 
 /**
@@ -58,5 +58,5 @@ public class DkhRecommendThemeConfigVo implements Serializable {
     @ExcelProperty(value = "备注")
     private String remark;
 
-
+    List<DkhRecommendCategoryConfigVo> categoryConfigList;
 }

+ 6 - 4
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/service/IDkhRecommendThemeConfigService.java

@@ -1,11 +1,11 @@
 package org.dromara.mall.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.mall.domain.DkhRecommendThemeConfig;
-import org.dromara.mall.domain.vo.DkhRecommendThemeConfigVo;
 import org.dromara.mall.domain.bo.DkhRecommendThemeConfigBo;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
-import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.mall.domain.vo.DkhRecommendThemeConfigVo;
 
 import java.util.Collection;
 import java.util.List;
@@ -16,7 +16,7 @@ import java.util.List;
  * @author LionLi
  * @date 2026-06-02
  */
-public interface IDkhRecommendThemeConfigService extends IService<DkhRecommendThemeConfig>{
+public interface IDkhRecommendThemeConfigService extends IService<DkhRecommendThemeConfig> {
 
     /**
      * 查询推荐全局主题配置
@@ -67,4 +67,6 @@ public interface IDkhRecommendThemeConfigService extends IService<DkhRecommendTh
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    DkhRecommendThemeConfigVo getCurrentRecommendThemeConfig(Long customerId);
 }

+ 163 - 21
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/service/impl/DkhCategoryMainServiceImpl.java

@@ -12,6 +12,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.ObjectUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -19,15 +20,19 @@ import org.dromara.mall.domain.DkhCategoryMain;
 import org.dromara.mall.domain.DkhCategorySub;
 import org.dromara.mall.domain.bo.DkhCategoryMainBo;
 import org.dromara.mall.domain.vo.DkhCategoryMainVo;
+import org.dromara.mall.domain.vo.DkhMallConfigVo;
+import org.dromara.mall.domain.vo.EpSearchConfigVo;
 import org.dromara.mall.mapper.DkhCategoryMainMapper;
 import org.dromara.mall.mapper.DkhCategorySubMapper;
 import org.dromara.mall.service.IDkhCategoryMainService;
+import org.dromara.mall.service.IDkhMallConfigService;
 import org.dromara.product.api.RemoteCategoryService;
 import org.dromara.product.api.domain.CategoryDto;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -46,6 +51,8 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
 
     private final DkhCategorySubMapper categorySubMapper;
 
+    private final IDkhMallConfigService dkhMallConfigService;
+
     @DubboReference
     private RemoteCategoryService remoteCategoryService;
 
@@ -77,8 +84,16 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
     public TableDataInfo<DkhCategoryMainVo> queryPageList(DkhCategoryMainBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<DkhCategoryMain> lqw = buildQueryWrapper(bo);
         Page<DkhCategoryMainVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        DkhMallConfigVo currentMallConfig =null;
+        if (ObjectUtils.isNotEmpty(bo.getCustomerId())){
+            currentMallConfig= dkhMallConfigService.getCurrentMallConfig(bo.getCustomerId());
+        }
+
         for (DkhCategoryMainVo vo : result.getRecords()) {
             fillParsedFields(vo);
+            if (ObjectUtils.isNotEmpty(currentMallConfig)){
+                vo.setCategoryThemeColor(currentMallConfig.getCategoryThemeColor());
+            }
         }
         return TableDataInfo.build(result);
     }
@@ -184,6 +199,7 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
                                 level2Sub.setLevel(2L);
                                 level2Sub.setSortOrder(level2Node.has("sortOrder") ? level2Node.get("sortOrder").asLong() : 0L);
                                 level2Sub.setStatus(level2Node.has("status") ? level2Node.get("status").asLong() : 1L);
+                                level2Sub.setSyncCategoryId(level2Id);
                                 categorySubMapper.insert(level2Sub);
 
                                 // 处理三级分类
@@ -205,7 +221,7 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
 
                                         List<CategoryDto> level3List = new ArrayList<>();
                                         level3List.add(level3Dto);
-                                        remoteCategoryService.insertCategory(level3List);
+                                        Long level3Id = remoteCategoryService.insertCategory(level3List);
 
                                         // 本地:三级分类 → DkhCategorySub
                                         DkhCategorySub level3Sub = new DkhCategorySub();
@@ -216,6 +232,7 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
                                         level3Sub.setLevel(3L);
                                         level3Sub.setSortOrder(level3Node.has("sortOrder") ? level3Node.get("sortOrder").asLong() : 0L);
                                         level3Sub.setStatus(level3Node.has("status") ? level3Node.get("status").asLong() : 1L);
+                                        level3Sub.setSyncCategoryId(level3Id);
                                         categorySubMapper.insert(level3Sub);
                                     }
                                 }
@@ -247,30 +264,58 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
         // 先更新主表数据
         boolean flag = baseMapper.updateById(update) > 0;
         if (flag) {
-            // 处理分类树的增量更新(远程商品分类表)
-            if (bo.getSyncCategoryId() != null && StringUtils.isNotBlank(bo.getPanelConfigJson())) {
+            // 从数据库获取 syncCategoryId,避免前端不传导致条件不满足
+            DkhCategoryMain dbRecord = baseMapper.selectById(update.getId());
+            Long syncCategoryId = dbRecord != null ? dbRecord.getSyncCategoryId() : bo.getSyncCategoryId();
+
+            // 查询旧子菜单,构建 name→syncCategoryId 映射,用于远程增量更新时匹配已有分类
+            List<DkhCategorySub> oldSubs = categorySubMapper.selectList(
+                Wrappers.<DkhCategorySub>lambdaQuery()
+                    .eq(DkhCategorySub::getMainCategoryId, update.getId()));
+            Map<String, Long> oldNameToSyncId = new HashMap<>();
+            for (DkhCategorySub sub : oldSubs) {
+                if (sub.getSyncCategoryId() != null && StringUtils.isNotBlank(sub.getName())) {
+                    oldNameToSyncId.put(sub.getLevel() + ":" + sub.getName(), sub.getSyncCategoryId());
+                }
+            }
+
+            if (syncCategoryId != null && StringUtils.isNotBlank(bo.getPanelConfigJson())) {
                 try {
                     JsonNode rootNode = objectMapper.readTree(bo.getPanelConfigJson());
                     JsonNode subMenus = rootNode.get("subMenus");
 
                     if (subMenus != null && subMenus.isArray()) {
+                        // 用旧记录的 syncCategoryId 填充 CategoryDto 的 ID,实现远程增量更新
                         List<CategoryDto> newCategoryTree = buildCategoryTreeFromSubMenus(
                             bo.getName(),
                             subMenus,
-                            bo.getSyncCategoryId()
+                            syncCategoryId,
+                            oldNameToSyncId
                         );
-                        remoteCategoryService.updateCategoryTree(bo.getSyncCategoryId(), newCategoryTree);
+                        remoteCategoryService.updateCategoryTree(syncCategoryId, newCategoryTree);
                     }
+
+                    // 从远程重新获取完整分类树,获取所有分类的最新ID(含新增分类)
+                    List<CategoryDto> remoteTree = remoteCategoryService.getCategoryTreeById(syncCategoryId);
+                    Map<String, Long> remoteNameToId = buildRemoteNameToIdMap(remoteTree);
+
+                    // 删除旧子菜单,重新保存(带远程 syncCategoryId)
+                    categorySubMapper.delete(Wrappers.<DkhCategorySub>lambdaQuery()
+                        .eq(DkhCategorySub::getMainCategoryId, update.getId()));
+                    saveCategorySubs(update.getId(), bo, remoteNameToId);
                 } catch (Exception e) {
                     log.error("更新分类树失败", e);
-                    // 分类树更新失败不影响主表更新,只记录日志
+                    // 分类树更新失败时,仍然更新本地 sub 表(不带远程ID)
+                    categorySubMapper.delete(Wrappers.<DkhCategorySub>lambdaQuery()
+                        .eq(DkhCategorySub::getMainCategoryId, update.getId()));
+                    saveCategorySubs(update.getId(), bo, null);
                 }
+            } else {
+                // 无远程同步时,只更新本地 sub 表
+                categorySubMapper.delete(Wrappers.<DkhCategorySub>lambdaQuery()
+                    .eq(DkhCategorySub::getMainCategoryId, update.getId()));
+                saveCategorySubs(update.getId(), bo, null);
             }
-
-            // 本地 sub 表:删除旧的子菜单,重新保存
-            categorySubMapper.delete(Wrappers.<DkhCategorySub>lambdaQuery()
-                .eq(DkhCategorySub::getMainCategoryId, update.getId()));
-            saveCategorySubs(update.getId(), bo);
         }
         return flag;
     }
@@ -278,7 +323,7 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
     /**
      * 从 subMenus 构建分类树 DTO 列表(树形结构)
      */
-    private List<CategoryDto> buildCategoryTreeFromSubMenus(String categoryName, JsonNode subMenus, Long rootCategoryId) {
+    private List<CategoryDto> buildCategoryTreeFromSubMenus(String categoryName, JsonNode subMenus, Long rootCategoryId, Map<String, Long> nameToSyncId) {
         List<CategoryDto> result = new ArrayList<>();
 
         CategoryDto rootDto = new CategoryDto();
@@ -296,12 +341,12 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
                     JsonNode nestedChildren = level2Node.get("children");
                     if (nestedChildren != null && nestedChildren.isArray()) {
                         for (JsonNode nestedChild : nestedChildren) {
-                            level2Children.add(buildCategoryDtoFromNode(nestedChild, rootCategoryId, 2L));
+                            level2Children.add(buildCategoryDtoFromNode(nestedChild, rootCategoryId, 2L, nameToSyncId));
                         }
                     }
                     continue;
                 }
-                level2Children.add(buildCategoryDtoFromNode(level2Node, rootCategoryId, 2L));
+                level2Children.add(buildCategoryDtoFromNode(level2Node, rootCategoryId, 2L, nameToSyncId));
             }
         }
 
@@ -311,13 +356,22 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
     }
 
     /**
-     * 从 JSON 节点构建 CategoryDto(递归)
+     * 从 JSON 节点构建 CategoryDto(递归),优先从 nameToSyncId 映射获取 ID
      */
-    private CategoryDto buildCategoryDtoFromNode(JsonNode node, Long parentId, Long classLevel) {
+    private CategoryDto buildCategoryDtoFromNode(JsonNode node, Long parentId, Long classLevel, Map<String, Long> nameToSyncId) {
         CategoryDto dto = new CategoryDto();
 
+        // 优先使用 JSON 中的 id,其次从旧记录的 syncCategoryId 映射中查找
         if (node.has("id") && node.get("id").asLong() > 0) {
             dto.setId(node.get("id").asLong());
+        } else if (nameToSyncId != null) {
+            String name = node.has("name") ? node.get("name").asText() : null;
+            if (StringUtils.isNotBlank(name)) {
+                Long syncId = nameToSyncId.get(classLevel + ":" + name);
+                if (syncId != null) {
+                    dto.setId(syncId);
+                }
+            }
         }
 
         dto.setCategoryName(node.has("name") ? node.get("name").asText() : null);
@@ -333,7 +387,7 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
             List<CategoryDto> children = new ArrayList<>();
             for (JsonNode childNode : childrenNode) {
                 children.add(buildCategoryDtoFromNode(childNode,
-                    dto.getId() != null ? dto.getId() : parentId, classLevel + 1));
+                    dto.getId() != null ? dto.getId() : parentId, classLevel + 1, nameToSyncId));
             }
             dto.setChildren(children);
         }
@@ -343,8 +397,12 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
 
     /**
      * 保存子菜单数据到本地 dkh_category_sub 表
+     *
+     * @param mainCategoryId 主分类ID
+     * @param bo             分类设置主
+     * @param nameToSyncId   远程分类名称→ID映射(用于回填 syncCategoryId),可为null
      */
-    private void saveCategorySubs(Long mainCategoryId, DkhCategoryMainBo bo) {
+    private void saveCategorySubs(Long mainCategoryId, DkhCategoryMainBo bo, Map<String, Long> nameToSyncId) {
         try {
             if (StringUtils.isNotBlank(bo.getPanelConfigJson())) {
                 JsonNode rootNode = objectMapper.readTree(bo.getPanelConfigJson());
@@ -352,27 +410,37 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
 
                 if (subMenus != null && subMenus.isArray()) {
                     for (JsonNode level2Node : subMenus) {
+                        String level2Name = level2Node.has("name") ? level2Node.get("name").asText() : null;
                         DkhCategorySub level2Sub = new DkhCategorySub();
                         level2Sub.setCustomerId(bo.getCustomerId());
                         level2Sub.setMainCategoryId(mainCategoryId);
                         level2Sub.setParentId(0L);
-                        level2Sub.setName(level2Node.has("name") ? level2Node.get("name").asText() : null);
+                        level2Sub.setName(level2Name);
                         level2Sub.setLevel(2L);
                         level2Sub.setSortOrder(level2Node.has("sortOrder") ? level2Node.get("sortOrder").asLong() : 0L);
                         level2Sub.setStatus(level2Node.has("status") ? level2Node.get("status").asLong() : 1L);
+                        // 回填远程同步ID
+                        if (nameToSyncId != null && StringUtils.isNotBlank(level2Name)) {
+                            level2Sub.setSyncCategoryId(nameToSyncId.get("2:" + level2Name));
+                        }
                         categorySubMapper.insert(level2Sub);
 
                         JsonNode level3Nodes = level2Node.get("children");
                         if (level3Nodes != null && level3Nodes.isArray() && !level3Nodes.isEmpty()) {
                             for (JsonNode level3Node : level3Nodes) {
+                                String level3Name = level3Node.has("name") ? level3Node.get("name").asText() : null;
                                 DkhCategorySub level3Sub = new DkhCategorySub();
                                 level3Sub.setCustomerId(bo.getCustomerId());
                                 level3Sub.setMainCategoryId(mainCategoryId);
                                 level3Sub.setParentId(level2Sub.getId());
-                                level3Sub.setName(level3Node.has("name") ? level3Node.get("name").asText() : null);
+                                level3Sub.setName(level3Name);
                                 level3Sub.setLevel(3L);
                                 level3Sub.setSortOrder(level3Node.has("sortOrder") ? level3Node.get("sortOrder").asLong() : 0L);
                                 level3Sub.setStatus(level3Node.has("status") ? level3Node.get("status").asLong() : 1L);
+                                // 回填远程同步ID
+                                if (nameToSyncId != null && StringUtils.isNotBlank(level3Name)) {
+                                    level3Sub.setSyncCategoryId(nameToSyncId.get("3:" + level3Name));
+                                }
                                 categorySubMapper.insert(level3Sub);
                             }
                         }
@@ -384,6 +452,23 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
         }
     }
 
+    /**
+     * 从远程分类树构建名称→ID映射
+     * key: "classLevel:categoryName" → value: remoteId
+     */
+    private Map<String, Long> buildRemoteNameToIdMap(List<CategoryDto> remoteTree) {
+        Map<String, Long> map = new HashMap<>();
+        if (remoteTree == null) {
+            return map;
+        }
+        for (CategoryDto dto : remoteTree) {
+            if (dto.getId() != null && StringUtils.isNotBlank(dto.getCategoryName())) {
+                map.put(dto.getClassLevel() + ":" + dto.getCategoryName(), dto.getId());
+            }
+        }
+        return map;
+    }
+
     /**
      * 保存前的数据校验
      */
@@ -415,7 +500,10 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
 
                 // 提取 subMenus
                 if (config.containsKey("subMenus")) {
-                    vo.setSubMenus((List<Map<String, Object>>) config.get("subMenus"));
+                    List<Map<String, Object>> subMenus = (List<Map<String, Object>>) config.get("subMenus");
+                    // 注入 syncCategoryId
+                    injectSyncCategoryId(vo.getId(), subMenus);
+                    vo.setSubMenus(subMenus);
                 }
 
                 // 构建 panelData(排除 subMenus,保留 mainTitle/subTitle/notes/groups)
@@ -439,6 +527,60 @@ public class DkhCategoryMainServiceImpl extends ServiceImpl<DkhCategoryMainMappe
         }
     }
 
+    /**
+     * 从 dkh_category_sub 表查询 syncCategoryId,注入到 subMenus 树中各节点
+     */
+    @SuppressWarnings("unchecked")
+    private void injectSyncCategoryId(Long mainCategoryId, List<Map<String, Object>> subMenus) {
+        if (mainCategoryId == null || subMenus == null || subMenus.isEmpty()) {
+            return;
+        }
+        // 查询本地子分类记录
+        List<DkhCategorySub> subs = categorySubMapper.selectList(
+            Wrappers.<DkhCategorySub>lambdaQuery()
+                .eq(DkhCategorySub::getMainCategoryId, mainCategoryId));
+        if (subs.isEmpty()) {
+            return;
+        }
+        // 构建 level:name → syncCategoryId 映射
+        Map<String, Long> nameToSyncId = new HashMap<>();
+        for (DkhCategorySub sub : subs) {
+            if (sub.getSyncCategoryId() != null && StringUtils.isNotBlank(sub.getName())) {
+                nameToSyncId.put(sub.getLevel() + ":" + sub.getName(), sub.getSyncCategoryId());
+            }
+        }
+        // 递归注入到 subMenus 树
+        injectSyncCategoryIdRecursive(subMenus, nameToSyncId, 2L);
+    }
+
+    /**
+     * 递归遍历 subMenus 树,注入 syncCategoryId
+     */
+    @SuppressWarnings("unchecked")
+    private void injectSyncCategoryIdRecursive(List<Map<String, Object>> nodes, Map<String, Long> nameToSyncId, Long currentLevel) {
+        if (nodes == null) {
+            return;
+        }
+        for (Map<String, Object> node : nodes) {
+            // 从 nameToSyncId 映射中查找并注入 syncCategoryId
+            String name = (String) node.get("name");
+            if (StringUtils.isNotBlank(name)) {
+                Long syncId = nameToSyncId.get(currentLevel + ":" + name);
+                if (syncId != null) {
+                    node.put("syncCategoryId", syncId);
+                }
+            }
+            // 递归处理子节点
+            Object children = node.get("children");
+            if (children instanceof List) {
+                List<Map<String, Object>> childNodes = (List<Map<String, Object>>) children;
+                if (!childNodes.isEmpty()) {
+                    injectSyncCategoryIdRecursive(childNodes, nameToSyncId, currentLevel + 1);
+                }
+            }
+        }
+    }
+
     /**
      * 校验并批量删除分类设置主信息
      *

+ 15 - 0
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/service/impl/DkhHeaderMenuServiceImpl.java

@@ -7,14 +7,17 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.ObjectUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.mall.domain.DkhHeaderMenu;
 import org.dromara.mall.domain.bo.DkhHeaderMenuBo;
 import org.dromara.mall.domain.vo.DkhHeaderMenuVo;
+import org.dromara.mall.domain.vo.DkhMallConfigVo;
 import org.dromara.mall.mapper.DkhHeaderMenuMapper;
 import org.dromara.mall.service.IDkhHeaderMenuService;
+import org.dromara.mall.service.IDkhMallConfigService;
 import org.springframework.stereotype.Service;
 
 import java.util.Collection;
@@ -34,6 +37,8 @@ public class DkhHeaderMenuServiceImpl extends ServiceImpl<DkhHeaderMenuMapper, D
 
     private final DkhHeaderMenuMapper baseMapper;
 
+    private final IDkhMallConfigService dkhMallConfigService;
+
     /**
      * 查询顶部导航菜单配置
      *
@@ -56,6 +61,16 @@ public class DkhHeaderMenuServiceImpl extends ServiceImpl<DkhHeaderMenuMapper, D
     public TableDataInfo<DkhHeaderMenuVo> queryPageList(DkhHeaderMenuBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<DkhHeaderMenu> lqw = buildQueryWrapper(bo);
         Page<DkhHeaderMenuVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        List<DkhHeaderMenuVo> records = result.getRecords();
+        DkhMallConfigVo currentMallConfig =null;
+        if (ObjectUtils.isNotEmpty(bo.getCustomerId())){
+            currentMallConfig= dkhMallConfigService.getCurrentMallConfig(bo.getCustomerId());
+        }
+        for (DkhHeaderMenuVo vo : records) {
+            if (ObjectUtils.isNotEmpty(currentMallConfig)){
+                vo.setHeaderThemeColor(currentMallConfig.getHeaderThemeColor());
+            }
+        }
         return TableDataInfo.build(result);
     }
 

+ 24 - 0
ruoyi-modules/ruoyi-mall/src/main/java/org/dromara/mall/service/impl/DkhRecommendThemeConfigServiceImpl.java

@@ -10,10 +10,18 @@ import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.mall.domain.DkhRecommendCategoryConfig;
 import org.dromara.mall.domain.DkhRecommendThemeConfig;
+import org.dromara.mall.domain.EpRecommendCategoryConfig;
+import org.dromara.mall.domain.EpRecommendThemeConfig;
+import org.dromara.mall.domain.bo.DkhCategoryMainBoToDkhCategoryMainMapper;
 import org.dromara.mall.domain.bo.DkhRecommendThemeConfigBo;
+import org.dromara.mall.domain.vo.DkhRecommendCategoryConfigVo;
 import org.dromara.mall.domain.vo.DkhRecommendThemeConfigVo;
+import org.dromara.mall.domain.vo.EpRecommendThemeConfigVo;
+import org.dromara.mall.mapper.DkhRecommendCategoryConfigMapper;
 import org.dromara.mall.mapper.DkhRecommendThemeConfigMapper;
+import org.dromara.mall.service.IDkhRecommendCategoryConfigService;
 import org.dromara.mall.service.IDkhRecommendThemeConfigService;
 import org.springframework.stereotype.Service;
 
@@ -34,6 +42,7 @@ public class DkhRecommendThemeConfigServiceImpl extends ServiceImpl<DkhRecommend
 
     private final DkhRecommendThemeConfigMapper baseMapper;
 
+private final DkhRecommendCategoryConfigMapper recommendCategoryConfigMapper;
     /**
      * 查询推荐全局主题配置
      *
@@ -133,4 +142,19 @@ public class DkhRecommendThemeConfigServiceImpl extends ServiceImpl<DkhRecommend
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    @Override
+    public DkhRecommendThemeConfigVo getCurrentRecommendThemeConfig(Long customerId) {
+        // 查询是否有配置
+        LambdaQueryWrapper<DkhRecommendThemeConfig> lqw = Wrappers.lambdaQuery();
+        lqw.eq(DkhRecommendThemeConfig::getCustomerId,customerId);
+        lqw.last("LIMIT 1");
+        DkhRecommendThemeConfig config = baseMapper.selectOne(lqw);
+        DkhRecommendThemeConfigVo vo = null;
+        if (null != config) {
+            vo = baseMapper.selectVoById(config.getId());
+            vo.setCategoryConfigList(recommendCategoryConfigMapper.selectVoList(Wrappers.lambdaQuery(DkhRecommendCategoryConfig.class).eq(DkhRecommendCategoryConfig::getCustomerId, customerId).eq(DkhRecommendCategoryConfig::getStatus, 1).orderByAsc(DkhRecommendCategoryConfig::getSortOrder)));
+        }
+        return vo;
+    }
 }