diff --git a/src/main/java/com/yunhai/cms/service/impl/PortalQueryService.java b/src/main/java/com/yunhai/cms/service/impl/PortalQueryService.java index e4a29d9..eea9106 100644 --- a/src/main/java/com/yunhai/cms/service/impl/PortalQueryService.java +++ b/src/main/java/com/yunhai/cms/service/impl/PortalQueryService.java @@ -75,12 +75,24 @@ public class PortalQueryService { return null; } PortalSiteSnapshot snapshot = PortalSiteSnapshot.of(site); - snapshot.setTheme(resolveTheme(snapshot.getTheme(), snapshot.getCode())); + String originalTheme = snapshot.getTheme(); + snapshot.setTheme(resolveTheme(originalTheme, snapshot.getCode())); derivePublicBaseUrl(site, snapshot); // 微调参数注入(方案 §5.3):按已解析主题 schema 过滤,回退主题与参数过滤天然一致 com.yunhai.cms.theme.ThemeInfo themeInfo = themeRegistry.get(snapshot.getTheme()); java.util.Map themeParams = themeInfo.filterValidValues(rawParamsMap(snapshot)); + // docs/20 E(R-46):站点用下架主题 → 强制对应 colorScheme 预设(保持原视觉近似); + // 站点保存归一化后 theme 已是保留主题,改由 config.theme_params.colorScheme 站点级驱动 + com.yunhai.cms.theme.ThemeRegistry.AliasInfo alias = + com.yunhai.cms.theme.ThemeRegistry.aliasOf(originalTheme); + String siteScheme = siteColorScheme(snapshot.getConfig()); + if (alias != null && alias.colorScheme != null) { + themeParams.put("colorScheme", alias.colorScheme); + } else if (siteScheme != null && themeInfo.param("colorScheme") != null + && themeInfo.param("colorScheme").isValidValue(siteScheme)) { + themeParams.put("colorScheme", siteScheme); + } snapshot.setThemeParams(themeParams); snapshot.setThemeVarsCss(com.yunhai.cms.theme.ThemeVarsCss.build(themeInfo, themeParams)); @@ -280,15 +292,41 @@ public class PortalQueryService { } } - /** 皮肤包存在性检查:委托 ThemeRegistry(theme.json + 4 模板齐全校验,方案 §5.1 重构,缺省回退 default) */ + /** + * 皮肤包存在性检查:委托 ThemeRegistry;docs/20 E 后下架主题按别名表映射到保留主题 + * (R-46:门户不断渲染),未知主题回退 default。 + */ private String resolveTheme(String theme, String siteCode) { if (themeRegistry.contains(theme)) { return theme; } + com.yunhai.cms.theme.ThemeRegistry.AliasInfo alias = + com.yunhai.cms.theme.ThemeRegistry.aliasOf(theme); + if (alias != null) { + log.warn("皮肤包 {} 已下架(docs/20 E 清场),映射到 {}(colorScheme={})site={}", + theme, alias.target, alias.colorScheme, siteCode); + return alias.target; + } log.warn("皮肤包 {} 未注册(site={}),回退 default", theme, siteCode); return com.yunhai.cms.theme.ThemeRegistry.DEFAULT_THEME; } + /** 站点级 colorScheme(config_json.theme_params.colorScheme,仅归一化保存后的别名站点使用) */ + private static String siteColorScheme(java.util.Map config) { + if (config == null) { + return null; + } + Object tp = config.get("theme_params"); + if (tp instanceof Map) { + Object v = ((Map) tp).get("colorScheme"); + if (v != null) { + String s = String.valueOf(v); + return s.isEmpty() ? null : s; + } + } + return null; + } + /** * 参数原始值(工作项 T:参数微调上移为皮肤包级):取当前主题的包级 {@code paramValues} * ({@code themes/.meta/{name}.json} 覆盖层)。站点 {@code config_json.theme_params} 已退役、不再读取 diff --git a/src/main/java/com/yunhai/cms/service/impl/SiteService.java b/src/main/java/com/yunhai/cms/service/impl/SiteService.java index 36d3255..abd07a2 100644 --- a/src/main/java/com/yunhai/cms/service/impl/SiteService.java +++ b/src/main/java/com/yunhai/cms/service/impl/SiteService.java @@ -191,13 +191,27 @@ public class SiteService { site.setSchId(req.getSchId()); site.setSchName(req.getSchName()); site.setName(req.getName()); - String resolvedTheme = StringUtils.hasText(req.getTheme()) + String requestedTheme = StringUtils.hasText(req.getTheme()) ? req.getTheme() : com.yunhai.cms.theme.ThemeRegistry.DEFAULT_THEME; + // docs/20 E:下架主题名按别名表归一化为保留主题(保存后 theme 字段收敛到 3 套注册包) + com.yunhai.cms.theme.ThemeRegistry.AliasInfo alias = + com.yunhai.cms.theme.ThemeRegistry.aliasOf(requestedTheme); + String resolvedTheme = requestedTheme; if (!themeRegistry.contains(resolvedTheme)) { - throw new BizException(ErrorCode.PARAM_ERROR, "皮肤包未注册:" + resolvedTheme); + if (alias != null) { + resolvedTheme = alias.target; + } else { + throw new BizException(ErrorCode.PARAM_ERROR, "皮肤包未注册:" + resolvedTheme); + } } site.setTheme(resolvedTheme); - validateConfigJson(req.getConfigJson(), site.getId()); + String configJson = req.getConfigJson(); + if (alias != null && alias.colorScheme != null) { + // 原配色写入站点级 theme_params.colorScheme(渲染端据此套用预设,保留原视觉近似) + configJson = withSiteColorScheme(configJson, alias.colorScheme); + req.setConfigJson(configJson); + } + validateConfigJson(configJson, site.getId()); // 工作项 T:参数微调上移为皮肤包级,站点不再持有参数值——themeParams 入参直接忽略 // (config_json 原样入库;theme_params 仍是 RESERVED 键以容忍存量/老前端提交,不报错) site.setConfigJson(req.getConfigJson()); @@ -209,6 +223,26 @@ public class SiteService { return SiteVo.of(site); } + /** + * docs/20 E:站点级 colorScheme 写入 config_json.theme_params(仅 colorScheme 键, + * 其余 theme_params 内容原样保留;theme_params 为 RESERVED 键不参与常规校验)。 + */ + private String withSiteColorScheme(String configJson, String colorScheme) { + java.util.Map config = ExtJsonUtils.parse(configJson); + Object tp = config.get("theme_params"); + java.util.Map themeParams = new java.util.LinkedHashMap<>(); + if (tp instanceof java.util.Map) { + ((java.util.Map) tp).forEach((k, v) -> themeParams.put(String.valueOf(k), v)); + } + themeParams.put("colorScheme", colorScheme); + config.put("theme_params", themeParams); + try { + return CONFIG_MAPPER.writeValueAsString(config); + } catch (Exception e) { + throw new BizException(ErrorCode.PARAM_ERROR, "站点配置序列化失败"); + } + } + /** * W4 门户 path 计算(唯一入口,前后端各一份纯函数语义:front/src/utils/sitePath.ts 同构): * 顶层(parentSiteId≤0)=code;子站=父站 path + '/' + code。 diff --git a/src/main/java/com/yunhai/cms/theme/ThemeParam.java b/src/main/java/com/yunhai/cms/theme/ThemeParam.java index dfa013e..6af6416 100644 --- a/src/main/java/com/yunhai/cms/theme/ThemeParam.java +++ b/src/main/java/com/yunhai/cms/theme/ThemeParam.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; +import java.util.Map; import java.util.regex.Pattern; /** @@ -47,6 +48,13 @@ public class ThemeParam { private List