feat(theme): S5 学院蓝主题改造——home-blocks/service/guide页+区块CSS+ThemeRegistry增强
build / build-push (push) Successful in 53s

This commit is contained in:
chenxing
2026-09-22 17:35:38 +08:00
parent 82d6ff4602
commit bc794ccce3
28 changed files with 1818 additions and 86 deletions
@@ -69,13 +69,14 @@ public class ThemeController {
}
/** W1(G4)+ 任务四:编辑元数据——外部包写 theme.json(title/描述/参数 schema),
* 内置包显示名/描述写 .meta 覆盖层(参数 schema 拒改),保存后引用站点立即生效 */
* 内置包显示名/描述写 .meta 覆盖层(参数 schema 拒改),保存后引用站点立即生效。
* 工作项 T:paramValues(包级参数值)两种来源统一写 .meta 覆盖层,null=不改 */
@PutMapping("/{name}/meta")
public com.yunhai.cms.model.vo.ThemeVo updateMeta(BoaUser authUserWithDept,
@PathVariable("name") String name,
@RequestBody com.yunhai.cms.model.dto.ThemeMetaRequest req) {
ThemeInfo info = themePackageService.updateMeta(authUserWithDept, name,
req.getTitle(), req.getDescription(), req.getParams());
req.getTitle(), req.getDescription(), req.getParams(), req.getParamValues());
return themeService.voOf(info);
}
@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* 门户页面装配组件(方案 §5.5,P1 抽取重构):PortalController 与主题预览接口共用,
@@ -288,6 +289,8 @@ public class PortalPageRenderer {
private static final int LAYOUT_SIZE_MAX = 20;
private static final int LAYOUT_LIST_SIZE_DEFAULT = 8;
private static final int LAYOUT_TICKER_SIZE_DEFAULT = 6;
/** 区块内手填链接:http(s) 绝对地址或站内根相对路径(与 SiteService 保存期校验同契约,拒绝伪协议) */
private static final Pattern LAYOUT_LINK_URL = Pattern.compile("^(https?://\\S+|/.+)$");
/**
* home_layout → 模板区块序列。null = 无布局/结构非法(回落现状装配,D2)。
@@ -341,6 +344,10 @@ public class PortalPageRenderer {
return buildGalleryBlock(site, spec, url);
case "links":
return buildLinksBlock(site, spec);
case "quick_links":
return buildQuickLinksBlock(spec);
case "notice_bar":
return buildNoticeBarBlock(spec);
default:
return null; // 白名单外的脏数据跳过,不打挂渲染
}
@@ -463,6 +470,48 @@ public class PortalPageRenderer {
return block;
}
/**
* 快捷入口宫格(v2 手册 §2.8 类型 A):数据是区块内联数组,不读任何外部表。
* 保存期已校验 url 白名单,装配期对脏数据(直改库/历史配置)再容错一次——不合法项静默丢弃,不打挂渲染。
*/
private Map<String, Object> buildQuickLinksBlock(Map<String, Object> spec) {
List<Map<String, Object>> links = new ArrayList<>();
Object raw = spec.get("items");
if (raw instanceof List) {
for (Object o : (List<?>) raw) {
if (!(o instanceof Map)) {
continue;
}
Map<?, ?> item = (Map<?, ?>) o;
String name = str(item.get("name"), "").trim();
String linkUrl = str(item.get("url"), "").trim();
if (name.isEmpty() || !LAYOUT_LINK_URL.matcher(linkUrl).matches()) {
continue;
}
Map<String, Object> link = new HashMap<>();
link.put("name", name);
link.put("url", linkUrl);
links.add(link);
}
}
Map<String, Object> block = baseBlock(spec, "quick_links", "快捷通道");
block.put("quickLinks", links);
return block;
}
/** 公告条(v2 手册 §2.8 类型 B):站点单条手填通告;level 白名单外归一 info,url 不合法则退化为纯文本 */
private Map<String, Object> buildNoticeBarBlock(Map<String, Object> spec) {
String linkUrl = str(spec.get("url"), "").trim();
if (!LAYOUT_LINK_URL.matcher(linkUrl).matches()) {
linkUrl = "";
}
Map<String, Object> block = baseBlock(spec, "notice_bar", "公告");
block.put("noticeText", str(spec.get("text"), "").trim());
block.put("noticeUrl", linkUrl);
block.put("noticeLevel", "warn".equals(str(spec.get("level"), "").trim()) ? "warn" : "info");
return block;
}
// ===== 布局装配工具 =====
private static String str(Object v, String fallback) {