From a666e13f5681db0a37b5878a6608e19270402511 Mon Sep 17 00:00:00 2001 From: chenxing Date: Tue, 8 Sep 2026 20:25:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(theme):=20=E7=9A=AE=E8=82=A4=E5=8C=85?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=94=B6=E6=95=9B=E4=B8=8E=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E6=89=B9=E6=AC=A1-=E5=85=9C=E5=BA=95catch=E7=BB=8FwrapFailure?= =?UTF-8?q?=E5=85=88log.error=E8=AE=B0=E5=8E=9F=E5=A7=8B=E5=A0=86=E6=A0=88?= =?UTF-8?q?=E5=86=8D=E5=8C=85=E8=A3=85(upload/derive/updateMeta=E4=B8=89?= =?UTF-8?q?=E5=A4=84,extractZip/deleteRecursively=E9=9D=99=E9=BB=98?= =?UTF-8?q?=E5=90=9E=E6=94=B9warn/debug);=E5=86=85=E7=BD=AE=E5=8C=85?= =?UTF-8?q?=E6=94=B9=E6=98=BE=E7=A4=BA=E5=90=8D=E8=B5=B0.meta=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E5=B1=82(ThemeRegistry=E5=8A=A0=E8=BD=BD=E5=90=8E?= =?UTF-8?q?=E8=A6=86=E5=86=99title/description,updateMeta=E6=94=BE?= =?UTF-8?q?=E5=BC=80=E5=86=85=E7=BD=AE:=E5=85=A8=E7=A9=BA=3D=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E9=BB=98=E8=AE=A4,params=E6=8B=92=E6=94=B9);derive/up?= =?UTF-8?q?load=E6=94=AF=E6=8C=81=E8=A1=A8=E5=8D=95=E5=91=BD=E5=90=8D(them?= =?UTF-8?q?e.json=E6=94=B9=E5=86=99regex=E6=8D=A2ObjectMapper=E9=98=B2?= =?UTF-8?q?=E8=AF=AF=E6=9B=BF=E6=8D=A2,upload=E8=90=BD=E4=BD=8D=E5=89=8D?= =?UTF-8?q?=E6=94=B9staging=E5=A4=B1=E8=B4=A5=E4=B8=8D=E8=90=BD=E4=BD=8D);?= =?UTF-8?q?resolveTheme=E5=9B=9E=E9=80=80warn=E8=A1=A5=E7=AB=99=E7=82=B9?= =?UTF-8?q?=E7=BC=96=E7=A0=81;ThemeVo.editable=E8=AF=AD=E4=B9=89=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=8F=AF=E7=BC=96=E8=BE=91=E5=85=83=E6=95=B0=E6=8D=AE?= =?UTF-8?q?;=E9=99=84=E5=86=85=E7=BD=AE=E5=8C=85mint-breeze/fresh-mint2?= =?UTF-8?q?=E4=B8=8ER1=E5=AD=A6=E6=A0=A1=E4=B8=BB=E7=AB=99=E7=BC=96?= =?UTF-8?q?=E7=A0=81=E8=BF=81=E7=A7=BBSQL;308/308=E5=85=A8=E7=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/upgrade_r1_school_code.sql | 35 + .../cms/controller/api/ThemeController.java | 21 +- .../cms/model/dto/ThemeDeriveRequest.java | 8 +- .../cms/model/dto/ThemeMetaRequest.java | 7 +- .../cms/service/impl/PortalQueryService.java | 6 +- .../yunhai/cms/service/impl/ThemeService.java | 3 +- .../yunhai/cms/theme/ThemePackageService.java | 160 ++++- .../com/yunhai/cms/theme/ThemeRegistry.java | 33 +- .../templates/themes/fresh-mint2/README.md | 36 + .../themes/fresh-mint2/assets/css/main.css | 624 ++++++++++++++++++ .../themes/fresh-mint2/assets/favicon.png | Bin 0 -> 2617 bytes .../themes/fresh-mint2/assets/favicon.svg | 8 + .../themes/fresh-mint2/assets/js/main.js | 40 ++ .../themes/fresh-mint2/assets/screenshot.png | Bin 0 -> 25678 bytes .../templates/themes/fresh-mint2/detail.html | 42 ++ .../themes/fresh-mint2/fragments/footer.html | 23 + .../themes/fresh-mint2/fragments/head.html | 25 + .../themes/fresh-mint2/fragments/header.html | 61 ++ .../templates/themes/fresh-mint2/home.html | 57 ++ .../templates/themes/fresh-mint2/list.html | 58 ++ .../templates/themes/fresh-mint2/single.html | 23 + .../templates/themes/fresh-mint2/theme.json | 21 + .../templates/themes/mint-breeze/README.md | 36 + .../themes/mint-breeze/assets/css/main.css | 590 +++++++++++++++++ .../themes/mint-breeze/assets/favicon.png | Bin 0 -> 2617 bytes .../themes/mint-breeze/assets/favicon.svg | 8 + .../themes/mint-breeze/assets/js/main.js | 40 ++ .../themes/mint-breeze/assets/screenshot.png | Bin 0 -> 86616 bytes .../templates/themes/mint-breeze/detail.html | 42 ++ .../themes/mint-breeze/fragments/footer.html | 21 + .../themes/mint-breeze/fragments/head.html | 24 + .../themes/mint-breeze/fragments/header.html | 55 ++ .../templates/themes/mint-breeze/home.html | 57 ++ .../templates/themes/mint-breeze/list.html | 51 ++ .../templates/themes/mint-breeze/single.html | 23 + .../templates/themes/mint-breeze/theme.json | 22 + .../com/yunhai/cms/portal/ThemeRenderIT.java | 4 +- .../java/com/yunhai/cms/theme/ThemeApiIT.java | 43 +- .../cms/theme/ThemePackageServiceTest.java | 108 ++- 39 files changed, 2343 insertions(+), 72 deletions(-) create mode 100644 sql/upgrade_r1_school_code.sql create mode 100644 src/main/resources/templates/themes/fresh-mint2/README.md create mode 100644 src/main/resources/templates/themes/fresh-mint2/assets/css/main.css create mode 100644 src/main/resources/templates/themes/fresh-mint2/assets/favicon.png create mode 100644 src/main/resources/templates/themes/fresh-mint2/assets/favicon.svg create mode 100644 src/main/resources/templates/themes/fresh-mint2/assets/js/main.js create mode 100644 src/main/resources/templates/themes/fresh-mint2/assets/screenshot.png create mode 100644 src/main/resources/templates/themes/fresh-mint2/detail.html create mode 100644 src/main/resources/templates/themes/fresh-mint2/fragments/footer.html create mode 100644 src/main/resources/templates/themes/fresh-mint2/fragments/head.html create mode 100644 src/main/resources/templates/themes/fresh-mint2/fragments/header.html create mode 100644 src/main/resources/templates/themes/fresh-mint2/home.html create mode 100644 src/main/resources/templates/themes/fresh-mint2/list.html create mode 100644 src/main/resources/templates/themes/fresh-mint2/single.html create mode 100644 src/main/resources/templates/themes/fresh-mint2/theme.json create mode 100644 src/main/resources/templates/themes/mint-breeze/README.md create mode 100644 src/main/resources/templates/themes/mint-breeze/assets/css/main.css create mode 100644 src/main/resources/templates/themes/mint-breeze/assets/favicon.png create mode 100644 src/main/resources/templates/themes/mint-breeze/assets/favicon.svg create mode 100644 src/main/resources/templates/themes/mint-breeze/assets/js/main.js create mode 100644 src/main/resources/templates/themes/mint-breeze/assets/screenshot.png create mode 100644 src/main/resources/templates/themes/mint-breeze/detail.html create mode 100644 src/main/resources/templates/themes/mint-breeze/fragments/footer.html create mode 100644 src/main/resources/templates/themes/mint-breeze/fragments/head.html create mode 100644 src/main/resources/templates/themes/mint-breeze/fragments/header.html create mode 100644 src/main/resources/templates/themes/mint-breeze/home.html create mode 100644 src/main/resources/templates/themes/mint-breeze/list.html create mode 100644 src/main/resources/templates/themes/mint-breeze/single.html create mode 100644 src/main/resources/templates/themes/mint-breeze/theme.json diff --git a/sql/upgrade_r1_school_code.sql b/sql/upgrade_r1_school_code.sql new file mode 100644 index 0000000..d4ad8de --- /dev/null +++ b/sql/upgrade_r1_school_code.sql @@ -0,0 +1,35 @@ +-- ============================================================ +-- R1 迁移脚本:学校主站编码改 5 位数字码(1 + 学校ID左补零至4位) +-- 例:sch_id=13 → code/path = 10013;幂等(只处理编码≠目标值的行) +-- 前置:W4 嵌套 path 机制已落地(cms_site.path 列已回填,uk_site_path 已建) +-- 旧链接提示:执行后 /portal/sch-13/ 不再解析(301 兼容仅覆盖子站按 code +-- 回查,主站不在其列);已对外发链需在 Caddy 层加旧前缀 301 或接受断链。 +-- ============================================================ + +-- 0) 前置校验:先人工看一眼存量主站 +-- ① 同一 sch_id 挂多个顶层站 → 改码后触发 uk_site_path 冲突,需先合并/停用; +-- ② sch_id 为空的顶层站(如演示站)会被本脚本跳过、保持原编码。 +SELECT id, code, sch_id, path, status FROM cms_site WHERE parent_site_id = 0 OR parent_site_id IS NULL; + +-- 1) 顶层主站:code 与 path 一起改为目标 5 位码(sch_id 全局唯一 ⇒ 天然满足 +-- uk_site_path / uk_site_parent_code;sch_id 为空的顶层站跳过) +UPDATE cms_site + SET code = CONCAT('1', LPAD(sch_id, 4, '0')), + path = CONCAT('1', LPAD(sch_id, 4, '0')) + WHERE (parent_site_id = 0 OR parent_site_id IS NULL) + AND sch_id IS NOT NULL + AND code <> CONCAT('1', LPAD(sch_id, 4, '0')); + +-- 2) 子站 path 级联重算(code 不动,仅换父前缀;幂等) +UPDATE cms_site s JOIN cms_site p ON s.parent_site_id = p.id + SET s.path = CONCAT(p.path, '/', s.code) + WHERE s.parent_site_id > 0 + AND s.path <> CONCAT(p.path, '/', s.code); + +-- 3) 校验(两条都应返回 0 行) +SELECT id, code, path FROM cms_site WHERE path = '' OR path IS NULL; +SELECT id, code, path FROM cms_site WHERE parent_site_id = 0 OR parent_site_id IS NULL; + +-- 4) 收尾:缓存为进程内 Caffeine(站点快照 TTL 30min、旧 key 自然过期); +-- 想立即生效可在站点管理保存一次任一站点(触发 SiteContentChangedEvent 清缓存), +-- 或重启 cms 容器。 diff --git a/src/main/java/com/yunhai/cms/controller/api/ThemeController.java b/src/main/java/com/yunhai/cms/controller/api/ThemeController.java index 0acd80f..7be2901 100644 --- a/src/main/java/com/yunhai/cms/controller/api/ThemeController.java +++ b/src/main/java/com/yunhai/cms/controller/api/ThemeController.java @@ -48,27 +48,34 @@ public class ThemeController { return themeService.list(); } - /** W1(G2):上传合规 zip——立即注册(免重启);不合规返回逐条校验报告,不落盘不注册 */ + /** W1(G2):上传合规 zip——立即注册(免重启);不合规返回逐条校验报告,不落盘不注册。 + * 任务四:title/description 选填,落位前改写包内 theme.json(留空沿用包内声明) */ @PostMapping("/upload") - public com.yunhai.cms.model.vo.ThemeVo upload(BoaUser authUserWithDept, @RequestParam("file") MultipartFile file) { - ThemeInfo info = themePackageService.upload(authUserWithDept, file); + public com.yunhai.cms.model.vo.ThemeVo upload(BoaUser authUserWithDept, + @RequestParam("file") MultipartFile file, + @RequestParam(value = "title", required = false) String title, + @RequestParam(value = "description", required = false) String description) { + ThemeInfo info = themePackageService.upload(authUserWithDept, file, title, description); return themeService.voOf(info); } - /** W1(G3):从现有包派生新皮肤(复制包体+改名,外部包可编辑可删除) */ + /** W1(G3):从现有包派生新皮肤(复制包体+改名,外部包可编辑可删除);任务四支持同步命名 */ @PostMapping("/derive") public com.yunhai.cms.model.vo.ThemeVo derive(BoaUser authUserWithDept, @RequestBody com.yunhai.cms.model.dto.ThemeDeriveRequest req) { - ThemeInfo info = themePackageService.derive(authUserWithDept, req.getSource(), req.getName()); + ThemeInfo info = themePackageService.derive(authUserWithDept, req.getSource(), req.getName(), + req.getTitle(), req.getDescription()); return themeService.voOf(info); } - /** W1(G4):编辑外部包 theme.json 的 title 与参数 schema(保存后引用站点立即生效) */ + /** W1(G4)+ 任务四:编辑元数据——外部包写 theme.json(title/描述/参数 schema), + * 内置包显示名/描述写 .meta 覆盖层(参数 schema 拒改),保存后引用站点立即生效 */ @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.getParams()); + ThemeInfo info = themePackageService.updateMeta(authUserWithDept, name, + req.getTitle(), req.getDescription(), req.getParams()); return themeService.voOf(info); } diff --git a/src/main/java/com/yunhai/cms/model/dto/ThemeDeriveRequest.java b/src/main/java/com/yunhai/cms/model/dto/ThemeDeriveRequest.java index 5e35a6f..bbe21b6 100644 --- a/src/main/java/com/yunhai/cms/model/dto/ThemeDeriveRequest.java +++ b/src/main/java/com/yunhai/cms/model/dto/ThemeDeriveRequest.java @@ -5,7 +5,7 @@ import lombok.Data; import java.util.List; -/** W1 皮肤包派生请求(POST /api/theme/derive) */ +/** W1 皮肤包派生请求(POST /api/theme/derive);任务四增选填 title/description(写入新包 theme.json) */ @Data public class ThemeDeriveRequest { @@ -14,4 +14,10 @@ public class ThemeDeriveRequest { /** 新包名(外部包目录名,theme.json name 同步改写) */ private String name; + + /** 显示名称(选填;留空沿用源包 title) */ + private String title; + + /** 描述(选填;null=沿用源包,""=清空) */ + private String description; } diff --git a/src/main/java/com/yunhai/cms/model/dto/ThemeMetaRequest.java b/src/main/java/com/yunhai/cms/model/dto/ThemeMetaRequest.java index 57a510a..40bff9f 100644 --- a/src/main/java/com/yunhai/cms/model/dto/ThemeMetaRequest.java +++ b/src/main/java/com/yunhai/cms/model/dto/ThemeMetaRequest.java @@ -5,12 +5,15 @@ import lombok.Data; import java.util.List; -/** W1 皮肤包元数据编辑请求(PUT /api/theme/{name}/meta):title/参数 schema,保留其余 theme.json 字段 */ +/** W1 皮肤包元数据编辑请求(PUT /api/theme/{name}/meta);任务四增 description 并放开内置包改名(走 .meta 覆盖层) */ @Data public class ThemeMetaRequest { private String title; - /** 参数 schema 整体替换(key/label/type/默认值/色值合法性服务端强校验);null=不改 */ + /** 描述(null=不改;""=清空) */ + private String description; + + /** 参数 schema 整体替换(key/label/type/默认值/色值合法性服务端强校验);null=不改;内置包拒改 */ private List params; } 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 b0ec6d2..1356688 100644 --- a/src/main/java/com/yunhai/cms/service/impl/PortalQueryService.java +++ b/src/main/java/com/yunhai/cms/service/impl/PortalQueryService.java @@ -75,7 +75,7 @@ public class PortalQueryService { return null; } PortalSiteSnapshot snapshot = PortalSiteSnapshot.of(site); - snapshot.setTheme(resolveTheme(snapshot.getTheme())); + snapshot.setTheme(resolveTheme(snapshot.getTheme(), snapshot.getCode())); derivePublicBaseUrl(site, snapshot); // 微调参数注入(方案 §5.3):按已解析主题 schema 过滤,回退主题与参数过滤天然一致 com.yunhai.cms.theme.ThemeInfo themeInfo = themeRegistry.get(snapshot.getTheme()); @@ -279,11 +279,11 @@ public class PortalQueryService { } /** 皮肤包存在性检查:委托 ThemeRegistry(theme.json + 4 模板齐全校验,方案 §5.1 重构,缺省回退 default) */ - private String resolveTheme(String theme) { + private String resolveTheme(String theme, String siteCode) { if (themeRegistry.contains(theme)) { return theme; } - log.warn("皮肤包 {} 未注册,回退 default", theme); + log.warn("皮肤包 {} 未注册(site={}),回退 default", theme, siteCode); return com.yunhai.cms.theme.ThemeRegistry.DEFAULT_THEME; } diff --git a/src/main/java/com/yunhai/cms/service/impl/ThemeService.java b/src/main/java/com/yunhai/cms/service/impl/ThemeService.java index 7cf2179..491dce9 100644 --- a/src/main/java/com/yunhai/cms/service/impl/ThemeService.java +++ b/src/main/java/com/yunhai/cms/service/impl/ThemeService.java @@ -43,7 +43,8 @@ public class ThemeService { usedNames.add(s.getName()); } vo.setUsedBySiteNames(usedNames); - vo.setEditable(ThemeRegistry.SOURCE_EXTERNAL.equals(source)); + // 任务四:语义改为「可编辑元数据」——内置包亦可改显示名/描述(.meta 覆盖层),内容仍只读 + vo.setEditable(true); return vo; } diff --git a/src/main/java/com/yunhai/cms/theme/ThemePackageService.java b/src/main/java/com/yunhai/cms/theme/ThemePackageService.java index e426b17..c980557 100644 --- a/src/main/java/com/yunhai/cms/theme/ThemePackageService.java +++ b/src/main/java/com/yunhai/cms/theme/ThemePackageService.java @@ -74,7 +74,7 @@ public class ThemePackageService { // ===== 上传(T-W1-05..12)===== - public ThemeInfo upload(BoaUser user, MultipartFile file) { + public ThemeInfo upload(BoaUser user, MultipartFile file, String title, String description) { requireSuperAdmin(user); if (file == null || file.isEmpty()) { throw new BizException(ErrorCode.PARAM_ERROR, "上传文件为空"); @@ -108,6 +108,8 @@ public class ThemePackageService { if (!report.isEmpty()) { throw new BizException(ErrorCode.PARAM_ERROR, "皮肤包校验未通过:" + String.join(";", report)); } + // 上传表单命名(任务四):落位前改写 staging 内 theme.json,失败即整体失败不落位 + applyStagedNaming(rootDir == null ? staging : staging.resolve(rootDir), title, description); // 原子替换/落位(同名外部包=覆盖更新,T-W1-12) Path target = Paths.get(themeDir, pkgName); if (Files.exists(target)) { @@ -122,7 +124,7 @@ public class ThemePackageService { } catch (BizException e) { throw e; } catch (Exception e) { - throw new BizException(ErrorCode.PARAM_ERROR, "皮肤包处理失败:" + e.getMessage()); + throw wrapFailure("上传", "皮肤包处理失败:", e); } finally { if (staging != null) { deleteRecursively(staging); @@ -142,9 +144,26 @@ public class ThemePackageService { return null; } + /** 上传表单命名(任务四):落位前改写 staging 内 theme.json 的 title/description(空值跳过对应字段) */ + private void applyStagedNaming(Path pkgDir, String title, String description) throws IOException { + if (!StringUtils.hasText(title) && description == null) { + return; + } + Path json = pkgDir.resolve("theme.json"); + com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); + com.fasterxml.jackson.databind.node.ObjectNode root = + (com.fasterxml.jackson.databind.node.ObjectNode) mapper.readTree(json.toFile()); + if (StringUtils.hasText(title)) { + root.put("title", title.trim()); + } + if (description != null) { + root.put("description", description); + } + Files.write(json, mapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(root)); + } + /** 解包到 staging;返回 theme.json 声明的 name(防御:zip-slip/条目数/体积/扩展名白名单) */ - private String extractZip(MultipartFile file, Path staging, List report) throws IOException { - String declaredName = null; + private String extractZip(MultipartFile file, Path staging, List report) throws IOException { String declaredName = null; int entries = 0; long total = 0; try (ZipInputStream zip = new ZipInputStream(file.getInputStream())) { @@ -198,6 +217,7 @@ public class ThemePackageService { declaredName = new com.fasterxml.jackson.databind.ObjectMapper() .readTree(json).path("name").asText(null); } catch (Exception e) { + log.warn("zip 内 theme.json 解析失败,忽略声明包名:{}", e.getMessage()); declaredName = null; } } @@ -253,7 +273,7 @@ public class ThemePackageService { // ===== 派生(T-W1-17)===== - public ThemeInfo derive(BoaUser user, String sourceName, String newName) { + public ThemeInfo derive(BoaUser user, String sourceName, String newName, String title, String description) { requireSuperAdmin(user); if (!themeRegistry.contains(sourceName)) { throw new BizException(ErrorCode.PARAM_ERROR, "皮肤包未注册:" + sourceName); @@ -272,12 +292,20 @@ public class ThemePackageService { } else { copyClasspathPackage(sourceName, target); } - // 改写 theme.json:name=newName(title 追加副名,其余保留) + // 改写 theme.json(任务四「添加皮肤包」表单):name=newName;显示名/描述按表单覆盖。 + // ObjectNode 写回取代旧 regex replaceAll——源包名出现在其他字段值(如描述)时不再误替换 Path json = target.resolve("theme.json"); - String content = new String(Files.readAllBytes(json), java.nio.charset.StandardCharsets.UTF_8); - content = content.replaceAll("(\"name\"\\s*:\\s*\")" + sourceName + "(\")", - "$1" + newName + "$2"); - Files.write(json, content.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); + com.fasterxml.jackson.databind.node.ObjectNode root = + (com.fasterxml.jackson.databind.node.ObjectNode) mapper.readTree(json.toFile()); + root.put("name", newName); + if (StringUtils.hasText(title)) { + root.put("title", title.trim()); + } + if (description != null) { + root.put("description", description); + } + Files.write(json, mapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(root)); themeRegistry.reload(); publishThemeChanged(); log.info("皮肤包 {} 派生为 {}(外部包)", sourceName, newName); @@ -286,7 +314,7 @@ public class ThemePackageService { throw e; } catch (Exception e) { deleteRecursively(target); - throw new BizException(ErrorCode.PARAM_ERROR, "派生皮肤包失败:" + e.getMessage()); + throw wrapFailure("派生", "派生皮肤包失败:", e); } } @@ -321,46 +349,94 @@ public class ThemePackageService { } } - // ===== 编辑(T-W1-17:title/参数 schema;保留 specVersion/模板清单字段)===== + // ===== 编辑(T-W1-17 + 任务四:title/描述/参数 schema;保留 specVersion/模板清单字段)===== - public ThemeInfo updateMeta(BoaUser user, String name, String title, List params) { + /** + * 元数据编辑(任务四放开内置包):外部包直写 theme.json;内置包 theme.json 在 jar 内只读, + * 显示名/描述写 {@link ThemeRegistry} 的 .meta 覆盖层。内容(模板/资产)两种来源都不可经此修改。 + */ + public ThemeInfo updateMeta(BoaUser user, String name, String title, String description, + List params) { requireSuperAdmin(user); if (!themeRegistry.contains(name)) { throw new BizException(ErrorCode.PARAM_ERROR, "皮肤包未注册:" + name); } - if (!ThemeRegistry.SOURCE_EXTERNAL.equals(themeRegistry.sourceOf(name))) { - throw new BizException(ErrorCode.FORBIDDEN, "内置包不可编辑(D1:内置只读)"); + boolean external = ThemeRegistry.SOURCE_EXTERNAL.equals(themeRegistry.sourceOf(name)); + if (!external && params != null) { + throw new BizException(ErrorCode.PARAM_ERROR, "内置包参数 schema 不可修改(仅显示名称/描述可改)"); } - Path json = Paths.get(themeDir, name, "theme.json"); try { - com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); - com.fasterxml.jackson.databind.node.ObjectNode root = - (com.fasterxml.jackson.databind.node.ObjectNode) mapper.readTree(json.toFile()); - if (StringUtils.hasText(title)) { - root.put("title", title); - } - if (params != null) { - ThemeInfo probe = new ThemeInfo(); - probe.setParams(params); - try { - ThemePackageValidator.validateParams(probe); - } catch (IllegalArgumentException e) { - throw new BizException(ErrorCode.PARAM_ERROR, "参数 schema 非法:" + e.getMessage()); + if (external) { + Path json = Paths.get(themeDir, name, "theme.json"); + com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); + com.fasterxml.jackson.databind.node.ObjectNode root = + (com.fasterxml.jackson.databind.node.ObjectNode) mapper.readTree(json.toFile()); + if (StringUtils.hasText(title)) { + root.put("title", title); } - root.set("params", mapper.valueToTree(params)); + if (description != null) { + root.put("description", description); + } + if (params != null) { + ThemeInfo probe = new ThemeInfo(); + probe.setParams(params); + try { + ThemePackageValidator.validateParams(probe); + } catch (IllegalArgumentException e) { + throw new BizException(ErrorCode.PARAM_ERROR, "参数 schema 非法:" + e.getMessage()); + } + root.set("params", mapper.valueToTree(params)); + } + Files.write(json, mapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(root)); + } else { + writeBuiltinMetaOverride(name, title, description); } - Files.write(json, mapper.writerWithDefaultPrettyPrinter() - .writeValueAsBytes(root)); themeRegistry.reload(); publishThemeChanged(); return themeRegistry.get(name); } catch (BizException e) { throw e; } catch (IOException e) { - throw new BizException(ErrorCode.PARAM_ERROR, "theme.json 写入失败:" + e.getMessage()); + throw wrapFailure("元数据写入", "theme.json 写入失败:", e); } } + /** + * 内置包元数据覆盖层写入(任务四):title/description 全空 = 删除覆盖层(恢复默认), + * 否则合并写入 {themeDir}/.meta/{name}.json(title 空且 description 非空时保留已有 title)。 + */ + private void writeBuiltinMetaOverride(String name, String title, String description) throws IOException { + Path overlay = Paths.get(themeDir, ".meta", name + ".json"); + if (!StringUtils.hasText(title) && (description == null || description.trim().isEmpty())) { + try { + Files.deleteIfExists(overlay); + } catch (IOException e) { + log.warn("元数据覆盖层 {} 删除失败:{}", overlay, e.getMessage()); + } + return; + } + com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); + com.fasterxml.jackson.databind.node.ObjectNode root; + if (Files.exists(overlay)) { + try { + root = (com.fasterxml.jackson.databind.node.ObjectNode) mapper.readTree(overlay.toFile()); + } catch (Exception e) { + log.warn("元数据覆盖层 {} 损坏,重建:{}", overlay, e.getMessage()); + root = mapper.createObjectNode(); + } + } else { + root = mapper.createObjectNode(); + } + if (StringUtils.hasText(title)) { + root.put("title", title.trim()); + } + if (description != null) { + root.put("description", description); + } + Files.createDirectories(overlay.getParent()); + Files.write(overlay, mapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(root)); + } + // ===== 删除(T-W1-15/16)===== public void remove(BoaUser user, String name) { @@ -381,6 +457,8 @@ public class ThemePackageService { "皮肤包正被站点使用,无法删除:" + String.join("、", names)); } deleteRecursively(Paths.get(themeDir, name)); + // 元数据覆盖层一并清理(任务四;外部包通常没有,best-effort) + deleteRecursively(Paths.get(themeDir, ".meta", name + ".json")); themeRegistry.reload(); publishThemeChanged(); log.info("皮肤包 {} 已删除并热注销", name); @@ -395,6 +473,16 @@ public class ThemePackageService { // ===== 内部 ===== + /** + * 兜底异常包装(C 级修复):先以 error 级记录原始堆栈再转业务异常—— + * 线上(/app 容器)只能看到接口文案,没有这一步则 FileSystemException 等 + * 只剩裸路径无从排查。userMessage 保持既有文案格式(含冒号后缀)。 + */ + static BizException wrapFailure(String logAction, String userMessage, Exception e) { + log.error("皮肤包{}失败", logAction, e); + return new BizException(ErrorCode.PARAM_ERROR, userMessage + e.getMessage()); + } + private void requireSuperAdmin(BoaUser user) { if (user == null || user.getType() == null || user.getType() != 1) { throw new BizException(ErrorCode.FORBIDDEN, "仅平台超管可执行该操作"); @@ -452,10 +540,12 @@ public class ThemePackageService { walk.sorted(Comparator.reverseOrder()).forEach(p -> { try { Files.delete(p); - } catch (IOException ignored) { + } catch (IOException e) { + log.debug("清理失败(best-effort):{}", p, e); } }); - } catch (IOException ignored) { + } catch (IOException e) { + log.debug("清理失败(best-effort):{}", path, e); } } } diff --git a/src/main/java/com/yunhai/cms/theme/ThemeRegistry.java b/src/main/java/com/yunhai/cms/theme/ThemeRegistry.java index 9760d7f..e0e9ef5 100644 --- a/src/main/java/com/yunhai/cms/theme/ThemeRegistry.java +++ b/src/main/java/com/yunhai/cms/theme/ThemeRegistry.java @@ -50,6 +50,9 @@ public class ThemeRegistry { private static final String SCAN_PATTERN = "classpath*:templates/themes/*/theme.json"; + /** 元数据覆盖层目录(任务四):{cms.theme.dir}/.meta/{name}.json;外部包扫描按「含 theme.json 的目录」过滤,天然跳过 */ + private static final String META_OVERRIDE_DIR = ".meta"; + /** 未知字段忽略、已知字段兼容(方案 §4.2 specVersion 演进策略) */ private static final ObjectMapper MAPPER = new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -112,6 +115,7 @@ public class ThemeRegistry { ThemeInfo info = MAPPER.readValue(resource.getInputStream(), ThemeInfo.class); ThemePackageValidator.validate(info, dirName, rel -> new ClassPathResource("templates/themes/" + dirName + "/" + rel).exists()); + applyMetaOverride(info); loaded.put(info.getName(), info); loadedSources.put(info.getName(), SOURCE_BUILTIN); } catch (Exception e) { @@ -131,6 +135,7 @@ public class ThemeRegistry { log.warn("外部主题包 {} 与内置包同名,忽略外部包(内置只读、优先,D1)", dirName); continue; } + applyMetaOverride(info); loaded.put(info.getName(), info); loadedSources.put(info.getName(), SOURCE_EXTERNAL); } catch (Exception e) { @@ -229,9 +234,33 @@ public class ThemeRegistry { } } + /** + * 元数据覆盖层(任务四):包加载后读 {themeDir}/.meta/{name}.json 覆写 title/description—— + * 内置包 theme.json 在 jar 内只读,显示名/描述改名走此通道;损坏的覆盖层忽略(按原始元数据注册)。 + */ + private void applyMetaOverride(ThemeInfo info) { + if (themeDir == null || themeDir.isEmpty() || info.getName() == null) { + return; + } + Path overrideFile = Paths.get(themeDir, META_OVERRIDE_DIR, info.getName() + ".json"); + if (!Files.isRegularFile(overrideFile)) { + return; + } + try { + ThemeInfo meta = MAPPER.readValue(overrideFile.toFile(), ThemeInfo.class); + if (meta.getTitle() != null && !meta.getTitle().trim().isEmpty()) { + info.setTitle(meta.getTitle().trim()); + } + if (meta.getDescription() != null) { + info.setDescription(meta.getDescription()); + } + } catch (Exception e) { + log.warn("元数据覆盖层 {} 读取失败,按原始元数据注册:{}", overrideFile, e.getMessage()); + } + } + /** W1:外部目录包扫描(themeDir 下各包的 theme.json;目录不存在视为空) */ - protected List scanExternalPackages() { - List packages = new ArrayList<>(); + protected List scanExternalPackages() { List packages = new ArrayList<>(); if (themeDir == null || themeDir.isEmpty()) { return packages; } diff --git a/src/main/resources/templates/themes/fresh-mint2/README.md b/src/main/resources/templates/themes/fresh-mint2/README.md new file mode 100644 index 0000000..aee78a9 --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/README.md @@ -0,0 +1,36 @@ +# fresh-mint2 皮肤包 + +由 blue-white 派生的清新薄荷风皮肤(**卡片封面版**,docs/10 契约 §5 工作流)。**升级随版本发布,不在站点间复制目录**。 + +## 风格定位 + +清爽、亲和,适合职业院校门户:主色薄荷青、圆角 12px、卡片纯白+柔和阴影、细线吸顶顶栏+居中 logo、列表卡片网格(一行 3 张)支持封面图、几何圆点装饰与大留白;无纹理、无通栏大图、无复杂动效。 + +与 `fresh-mint`(同色系基础版)的差异:列表为带封面的卡片网格(`listLayout=grid`,`decoration=dots`),header 为 3 列网格(左:专业院系 / 中:品牌 / 右:搜索);`fresh-mint` 采用 top-bar + brand-row 顶栏与纯文字列表卡片。 + +## 数据契约(模板可取用的 model 属性全集,契约 §3) + +| 页面 | 属性 | +| --- | --- | +| 全部 | `site`(快照:config/分类树/轮播/友链组/院系子站)、`url`(PortalUrlHelper:home/category/article/upload/asset/search/siteHome)、`themeBase`(=`themes/fresh-mint2`)、`navCategories`、`pageTitle`、`seoDescription`、`canonical`、`themeParams`(微调参数,仅 theme.json 声明过的 key)、`themeVarsCss`(服务端预构建 `:root{}` 覆盖串,无参数为空) | +| home | `banners`(时间窗内 home 位)、`topArticles`(置顶+最新 6)、`categoryBlocks`(LIST 型根栏目块,各 top 8) | +| list / search | `listData`(分页 + `items[].coverImage`)、`category`(list)、`q` + `searchMode`(search) | +| detail | `article`、`prevArticle`、`nextArticle`、`currentCategoryId` | +| single | `category`(含 `content`)、`currentCategoryId` | + +原则:皮肤不得假设"首页一定有轮播"、"列表一定有封面",对空集合优雅降级。 + +## 轮播图尺寸约定(bannerSize) + +1920x560(管理端上传端按此引导裁剪)。 + +## 可调参数 + +见 `theme.json` 的 `params`: + +- **color**(进 CSS 变量):`primary` 主色 / `primaryDark` 主色加深(用于品牌文字、悬停加深)/ `primarySoft` 主色浅调(装饰、点缀背景)/ `bg` 页面底色 / `border` 边框色。 +- **select**:`listLayout` = `grid`(默认,卡片网格一行 3 张,支持封面)或 `list`(简约列表,封面靠左缩略);`decoration` = `dots`(默认,背景几何圆点)或 `plain`(纯净无装饰)。 + +换色建议:主色系三值同步调整(primary / primaryDark / primarySoft),bg 与 border、primarySoft 保持同族浅色调,避免底色与卡片对比失衡。 + +优先级:`custom_css` > `themeVarsCss` > `main.css`。 diff --git a/src/main/resources/templates/themes/fresh-mint2/assets/css/main.css b/src/main/resources/templates/themes/fresh-mint2/assets/css/main.css new file mode 100644 index 0000000..dead9df --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/assets/css/main.css @@ -0,0 +1,624 @@ +/* ============================================================ + * fresh-mint2 皮肤包样式(blue-white 派生,docs/10 皮肤包契约 §5) + * 清新薄荷风(卡片封面版):主色 #17A2A2、浅薄荷底、白卡 12px 圆角 + 柔和阴影; + * 细线吸顶顶栏 + 居中 logo;列表为带封面的卡片网格(一行 3 张);几何圆点装饰。 + * 无纹理、无通栏大图、无复杂动效。 + * 轮播图设计尺寸约定:1920×560(管理端上传端按此引导裁剪)。 + * ============================================================ */ +:root { + --primary: #17A2A2; + --primary-dark: #0F7C7C; + --primary-soft: #E6F6F6; + --bg: #F5FBFA; + --card-bg: #FFFFFF; + --border: #DCEDEB; + --text: #1F2D2D; + --text-light: #6B8382; + + --radius: 12px; + --radius-sm: 8px; + --max-width: 1200px; + + --shadow-card: 0 6px 18px rgba(15, 124, 124, .06); + --shadow-hover: 0 14px 30px rgba(15, 124, 124, .13); + --shadow-pop: 0 10px 26px rgba(15, 124, 124, .12); +} + +* { box-sizing: border-box; } +body { + margin: 0; + font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; + font-size: 15px; + line-height: 1.7; + color: var(--text); + background-color: var(--bg); + background-image: radial-gradient(rgba(23, 162, 162, .10) 1.6px, transparent 1.6px); + background-size: 28px 28px; + background-attachment: fixed; +} +body.decor-plain { background-image: none; } +h1, h2, h3 { font-weight: 600; letter-spacing: .01em; } +a { color: inherit; text-decoration: none; } +a:hover { color: var(--primary-dark); } +img { max-width: 100%; } +time, .item-meta { color: var(--text-light); font-size: 13px; } +.empty { + color: var(--text-light); + text-align: center; + padding: 36px 0; + background: var(--card-bg); + border: 1px dashed var(--border); + border-radius: var(--radius); +} + +/* ===== 头部:细线顶栏 + 居中 logo + 吸顶 ===== */ +.site-header { + position: sticky; + top: 0; + z-index: 200; + background: rgba(255, 255, 255, .94); + border-bottom: 1px solid var(--border); + box-shadow: 0 2px 12px rgba(15, 124, 124, .05); +} +.header-inner { + max-width: var(--max-width); + margin: 0 auto; + padding: 12px 16px; + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 16px; +} +.header-aside { display: flex; align-items: center; gap: 10px; min-width: 0; } +.header-aside-left { justify-self: start; } +.header-aside-right { justify-self: end; } + +.site-brand { display: flex; align-items: center; gap: 12px; justify-self: center; } +.site-logo { height: 40px; width: auto; } +.site-name { + font-size: 22px; + font-weight: 600; + color: var(--primary-dark); + letter-spacing: .02em; + white-space: nowrap; +} + +.site-search { display: flex; gap: 8px; } +.site-search input { + border: 1px solid var(--border); + background: var(--primary-soft); + border-radius: 999px; + padding: 8px 16px; + width: 190px; + font-size: 14px; + color: var(--text); + outline: none; +} +.site-search input::placeholder { color: #9bb6b5; } +.site-search input:focus { border-color: var(--primary); background: #fff; } +.site-search button { + border: none; + border-radius: 999px; + background: var(--primary); + color: #fff; + font-size: 14px; + padding: 8px 20px; + cursor: pointer; +} +.site-search button:hover { background: var(--primary-dark); color: #fff; } + +/* ===== 专业院系下拉(院系子站切换,《院系子站方案》) ===== */ +.site-colleges { position: relative; } +.site-colleges .colleges-toggle { + display: flex; + align-items: center; + gap: 4px; + border: 1px solid var(--border); + border-radius: 999px; + background: #fff; + padding: 7px 16px; + cursor: pointer; + color: var(--primary-dark); + font-size: 14px; + font-weight: 500; + white-space: nowrap; +} +.site-colleges .colleges-toggle:hover { border-color: var(--primary); background: var(--primary-soft); } +.site-colleges .colleges-arrow { font-style: normal; font-size: 12px; color: var(--text-light); } +.site-colleges .colleges-menu { + display: none; + position: absolute; + top: calc(100% + 8px); + right: 0; + min-width: 200px; + max-height: 420px; + overflow-y: auto; + margin: 0; + padding: 6px; + list-style: none; + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-pop); + z-index: 210; +} +.site-colleges:hover .colleges-menu { display: block; } +.site-colleges .colleges-menu li a { + display: block; + padding: 9px 14px; + border-radius: var(--radius-sm); + color: var(--text); + white-space: nowrap; +} +.site-colleges .colleges-menu li a:hover { background: var(--primary-soft); color: var(--primary-dark); } +.site-colleges .colleges-menu li.active a { color: var(--primary-dark); font-weight: 600; background: var(--primary-soft); } + +/* ===== 主导航:细线文字导航 + 薄荷下划线激活态 ===== */ +.site-nav { background: transparent; border-top: 1px solid var(--border); } +.site-nav ul { + max-width: var(--max-width); + margin: 0 auto; + padding: 0 16px; + list-style: none; + display: flex; + flex-wrap: wrap; + justify-content: center; +} +.site-nav > ul > li { position: relative; } +.site-nav li > a { + display: block; + padding: 13px 22px; + color: var(--text); + font-weight: 500; + position: relative; +} +.site-nav > ul > li > a::before { + content: ""; + position: absolute; + left: 22px; + right: 22px; + bottom: 8px; + height: 2px; + border-radius: 2px; + background: var(--primary); + transform: scaleX(0); + transition: transform .18s ease; +} +.site-nav > ul > li:hover > a::before, +.site-nav > ul > li.active > a::before { transform: scaleX(1); } +.site-nav > ul > li:hover > a, +.site-nav > ul > li.active > a { color: var(--primary-dark); } +.site-nav li.has-sub > a::after { content: " ▾"; font-size: 12px; opacity: .7; } + +/* ===== 二级下拉导航 ===== */ +.site-nav ul.sub-nav { + display: none; + position: absolute; + top: 100%; + left: 0; + min-width: 178px; + max-width: none; + margin: 0; + padding: 6px; + list-style: none; + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-pop); + z-index: 205; +} +.site-nav li:hover > .sub-nav { display: block; } +.site-nav .sub-nav li a { + padding: 9px 16px; + border-radius: var(--radius-sm); + color: var(--text); + white-space: nowrap; + background: none; +} +.site-nav .sub-nav li a::before { display: none; } +.site-nav .sub-nav li a:hover { background: var(--primary-soft); color: var(--primary-dark); } +.site-nav .sub-nav li.active a { color: var(--primary-dark); font-weight: 600; } + +/* ===== 首页轮播(收窄 + 圆角,不做通栏大图) ===== */ +.banner { + position: relative; + max-width: var(--max-width); + margin: 24px auto 4px; + padding: 0 16px; +} +.banner-slides { + position: relative; + height: 380px; + border-radius: var(--radius); + overflow: hidden; + box-shadow: var(--shadow-card); +} +.banner-slide { display: none; position: absolute; inset: 0; } +.banner-slide.active { display: block; } +.banner-slide img { width: 100%; height: 100%; object-fit: cover; } +.banner-title { + position: absolute; + left: 16px; + right: auto; + bottom: 16px; + max-width: 68%; + padding: 8px 18px; + border-radius: 999px; + background: rgba(255, 255, 255, .92); + color: var(--text); + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.banner-dots { + position: absolute; + bottom: 24px; + right: 32px; + display: flex; + gap: 8px; +} +.banner-dots button { + width: 9px; + height: 9px; + border-radius: 50%; + border: none; + background: rgba(255, 255, 255, .75); + cursor: pointer; +} +.banner-dots button.active { background: var(--primary); transform: scale(1.25); } + +/* ===== 通用区块:白卡 + 柔和阴影 ===== */ +.top-strip, .category-block { + max-width: var(--max-width); + margin: 20px auto; + padding: 22px 24px 24px; + background: var(--card-bg); + border: none; + border-radius: var(--radius); + box-shadow: var(--shadow-card); +} +.section-title, .block-head h2 { + margin: 0 0 16px; + font-size: 19px; + font-weight: 600; + display: flex; + align-items: center; +} +.section-title::before, .block-head h2::before { + content: ""; + flex: none; + width: 7px; + height: 7px; + margin-right: 26px; + border-radius: 50%; + background: var(--primary); + box-shadow: 12px 0 0 rgba(23, 162, 162, .32), 24px 0 0 rgba(23, 162, 162, .14); +} +.block-head { display: flex; align-items: baseline; justify-content: space-between; } +.more { + font-size: 13px; + color: var(--primary-dark); + background: var(--primary-soft); + border-radius: 999px; + padding: 4px 14px; + flex: none; +} + +/* 头条推荐:卡片网格(listLayout=list 时回落为行列表) */ +.top-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; } +.top-grid li { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 16px 18px; + background: var(--primary-soft); + border-radius: var(--radius-sm); +} +.top-grid li a { + flex: 1; + font-weight: 500; + color: var(--text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.top-grid li time { flex: none; } +.top-grid.is-list { display: block; } +.top-grid.is-list li { background: transparent; padding: 11px 0; border-bottom: 1px solid var(--border); } +.top-grid.is-list li:last-child { border-bottom: none; } + +/* 栏目区块:行列表 + 圆点标记 */ +.block-list { list-style: none; margin: 0; padding: 0; } +.block-list li { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 11px 0; + border-bottom: 1px solid var(--border); +} +.block-list li:last-child { border-bottom: none; } +.block-list li a { + flex: 1; + display: flex; + align-items: center; + gap: 10px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.block-list li a::before { + content: ""; + flex: none; + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--primary); + opacity: .5; +} +.top-badge { + flex: none; + color: #fff; + background: var(--primary); + font-size: 12px; + font-weight: 500; + padding: 2px 10px; + border-radius: 999px; +} + +/* ===== 列表页:卡片网格(一行三张,支持封面) ===== */ +.list-main { + max-width: var(--max-width); + margin: 0 auto; + padding: 24px 16px 48px; + background: transparent; + border: none; +} +.home-main { padding-bottom: 8px; } +.breadcrumb { color: var(--text-light); font-size: 13px; margin: 0 0 14px; } +.breadcrumb a { margin-right: 8px; } +.breadcrumb span { margin-left: 8px; color: var(--primary-dark); } +.page-title { + font-size: 24px; + font-weight: 600; + margin: 0 0 22px; + padding-bottom: 14px; + border-bottom: 1px solid var(--border); +} + +.article-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; +} +.article-list li { + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-card); + overflow: hidden; + transition: transform .2s ease, box-shadow .2s ease; +} +.article-list li:hover { transform: translateY(-4px); box-shadow: var(--shadow-hover); } +.item-card { display: flex; flex-direction: column; height: 100%; } +.item-cover { + display: block; + aspect-ratio: 16 / 9; + overflow: hidden; + background: var(--primary-soft); +} +.item-cover img { display: block; width: 100%; height: 100%; object-fit: cover; } +.item-body { display: flex; flex-direction: column; gap: 8px; padding: 16px 18px 18px; flex: 1; } +.item-title { + font-size: 16px; + font-weight: 600; + line-height: 1.5; + color: var(--text); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} +.item-summary { + color: var(--text-light); + font-size: 14px; + line-height: 1.7; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} +.item-meta { display: flex; gap: 10px; align-items: center; margin-top: auto; padding-top: 4px; } + +.article-list.is-list { display: block; } +.article-list.is-list li { margin-bottom: 14px; } +.article-list.is-list .item-card { flex-direction: row; align-items: center; gap: 18px; padding: 16px 20px; } +.article-list.is-list .item-cover { width: 190px; flex: none; aspect-ratio: 16 / 10; border-radius: var(--radius-sm); } +.article-list.is-list .item-body { padding: 0; } +.article-list.is-list .item-meta { margin-top: 4px; } + +/* ===== 分页 ===== */ +.pagination { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; padding: 36px 0 8px; } +.pagination a { + min-width: 36px; + text-align: center; + padding: 7px 14px; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--card-bg); + color: var(--text-light); +} +.pagination a:hover { border-color: var(--primary); color: var(--primary-dark); } +.pagination a.active { background: var(--primary); color: #fff; border-color: var(--primary); } + +/* ===== 详情 / 单页 ===== */ +.detail-main { + max-width: 1000px; + margin: 24px auto 48px; + padding: 36px 40px 40px; + background: var(--card-bg); + border: none; + border-radius: var(--radius); + box-shadow: var(--shadow-card); +} +.article-title { margin: 8px 0 14px; font-size: 27px; font-weight: 600; text-align: center; line-height: 1.45; } +.article-meta { + display: flex; + gap: 0; + justify-content: center; + flex-wrap: wrap; + color: var(--text-light); + font-size: 13px; + padding-bottom: 20px; + border-bottom: 1px solid var(--border); +} +.article-meta > * { padding: 0 12px; position: relative; } +.article-meta > * + *::before { + content: ""; + position: absolute; + left: 0; + top: 50%; + width: 4px; + height: 4px; + margin-top: -2px; + border-radius: 50%; + background: var(--primary); + opacity: .5; +} +.article-content { line-height: 1.95; font-size: 16px; padding-top: 24px; word-break: break-word; color: var(--text); } +.article-content p { margin: 0 0 16px; } +.article-content h1, .article-content h2, .article-content h3 { margin: 26px 0 12px; } +.article-content img { height: auto; border-radius: var(--radius-sm); } +.article-content a { color: var(--primary-dark); text-decoration: underline; text-underline-offset: 3px; } +.article-content blockquote { + margin: 18px 0; + padding: 12px 18px; + background: var(--primary-soft); + border-left: 3px solid var(--primary); + border-radius: 0 var(--radius-sm) var(--radius-sm) 0; +} +.article-content table { border-collapse: collapse; width: 100%; } +.article-content table td, .article-content table th { border: 1px solid var(--border); padding: 8px 12px; } +.article-sibling { display: flex; flex-direction: column; gap: 10px; margin-top: 32px; font-size: 14px; } +.article-sibling a { + padding: 12px 18px; + border-radius: var(--radius-sm); + background: var(--card-bg); + border: 1px solid var(--border); + color: var(--text-light); +} +.article-sibling a:hover { border-color: var(--primary); color: var(--primary-dark); } + +/* ===== 页脚(浅薄荷底) ===== */ +.site-footer { + margin-top: 40px; + background: var(--primary-soft); + color: var(--text-light); + border-top: 1px solid var(--border); +} +.footer-decor { + height: 26px; + display: flex; + justify-content: center; + align-items: center; +} +.footer-decor::before { + content: ""; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--primary); + box-shadow: 14px 0 0 rgba(23, 162, 162, .32), -14px 0 0 rgba(23, 162, 162, .32); +} +.footer-links { + max-width: var(--max-width); + margin: 0 auto; + padding: 8px 16px 4px; + display: flex; + flex-direction: column; + gap: 10px; +} +.links-group { display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: center; font-size: 13px; } +.group-name { + font-weight: 600; + color: var(--primary-dark); + margin-right: 6px; + padding-right: 16px; + position: relative; +} +.group-name::after { + content: ""; + position: absolute; + right: 0; + top: 50%; + width: 4px; + height: 4px; + margin-top: -2px; + border-radius: 50%; + background: var(--primary); + opacity: .5; +} +.links-group a { color: var(--text-light); } +.links-group a:hover { color: var(--primary-dark); } +.footer-info { + max-width: var(--max-width); + margin: 0 auto; + padding: 14px 16px 26px; + text-align: center; + font-size: 13px; +} +.footer-info p { margin: 4px 0; } +.links-group a .link-logo, +.sp-links-row a .link-logo { + height: 20px; + width: auto; + max-width: 64px; + object-fit: contain; + vertical-align: middle; + margin-right: 4px; + border-radius: 4px; +} + +/* ===== 移动端适配 ===== */ +@media (max-width: 768px) { + .header-inner { grid-template-columns: 1fr; justify-items: center; gap: 12px; padding: 12px 14px; } + .site-brand { order: 1; justify-self: center; } + .header-aside-right { order: 2; justify-self: stretch; } + .header-aside-left { order: 3; justify-self: center; } + .site-search { flex: 1; } + .site-search input { flex: 1; width: auto; min-width: 0; } + .site-colleges .colleges-menu { left: 50%; right: auto; transform: translateX(-50%); min-width: 200px; } + .site-nav ul { flex-wrap: nowrap; overflow-x: auto; justify-content: flex-start; } + .site-nav > ul > li > a { padding: 12px 16px; white-space: nowrap; } + .site-nav > ul > li > a::before { left: 16px; right: 16px; } + + .banner { margin-top: 16px; } + .banner-slides { height: 190px; } + .banner-title { font-size: 13px; padding: 6px 14px; } + .banner-dots { bottom: 14px; right: 20px; } + + .top-grid { grid-template-columns: 1fr; } + .article-list { grid-template-columns: 1fr; } + .article-list.is-list .item-card { flex-direction: column; align-items: stretch; } + .article-list.is-list .item-cover { width: 100%; aspect-ratio: 16 / 9; } + .top-strip, .category-block { padding: 18px 16px 20px; margin: 16px auto; } + .detail-main { margin: 16px auto 32px; padding: 22px 18px 26px; } + .article-title { font-size: 22px; } + .article-content { font-size: 15px; } + + .site-nav ul.sub-nav { + display: block; + position: static; + border: none; + border-radius: var(--radius-sm); + box-shadow: none; + background: var(--primary-soft); + min-width: 0; + padding: 6px; + } + .site-nav .sub-nav li a { color: var(--text); padding: 8px 14px; } +} diff --git a/src/main/resources/templates/themes/fresh-mint2/assets/favicon.png b/src/main/resources/templates/themes/fresh-mint2/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..497cae1565a13392c053aa7f18c3e2245d0bdc0f GIT binary patch literal 2617 zcmV-93dZ$`P)6M;}t9w9JENH&o7bFb&#bMM`=*$swd?{^N9 z^UcneZ-2SFd%yGTckg+x=jk6^Ruo0obw>l}08vD8_qYn=ukqZ_)MmBhZgUl=6*jaP z6Wf*?g{u%96XQ5aBuC*Yje`!lV#Y+LnPSIBa<{pW0Wd2vU`0%F1g^4qp#x(}j>6UW zM@WsOX$I&tM|37%B*)>g84#W$R+78IJ-XdGuv%tTI}MERNbWvY6(=UNhNOonksOCx zwIG-yUnF;%tER#J&#@)P;Hqvl5v-Unw&WPx!vsT*(UQB))p*BAji)`b6!>bH0Wt$* z27qZgRN9!f><#6e0Za3HCyuSkvfyhQ_W<26nBF4WmK=f4&H%QbNOAo zTXNTVbS+4M*dA2LUFCKLSaY?)5nFN;9vKga(UN0uqbtI+$OxHYj+QCZ7L#ZA)Vh`a z%3@zZR!UwVDL*TT!0ymw_rL_v7q5@J-!U{aHZAP=$8u{tz#1=}twrI1bgj85xM5A^ ziu_cFk@CV+@^GGZ+!+1W#lb(F>J1G}3+|VpAOHRtFvn_GO~>p-;8}j}GoQ$RvOYK2 ztH8a;#8mi=Q+>Lrrwu?R|*{i}r#SFu(gU7q?j7$sYBN3w( zWVOxoNM2YrKd!0pmB$J`w=uYw(kDV*{`rsRwQMf%dK3}-isf2)(=IzG0CBS%o%?;- z-mk50stG`XWOY&6+H(It&JU1;xXb5q;i?l8U}6PKtdO~?z>7O8s>{+Lp+es9g_c#C zstD(=8Im~lQ(pYJr%J0!6I}XKYF7B4Z7y;9{O^^=j0r38UWvuGG~|*8xF*K?*XQE< zbr&wsh*-ey{pM#&z-9Tywvs@)KqKMS`T$@kW25qxPcKheT%Bh=Lh41^KUpAxzYv=N zW<4oR?;Qfq3wS?M@8ZRWl`RcH(#YWJZ=^SrjgP67m{?)(md(YQdT(vr1q(@n9h;Z) z^*3rf0HX3D><~;|RgwmXQdgeI*I$RLB3yrpa+8aLaeCR@lj2}XNidcB^#=qQ57=0n z1?WOs+Fbl)wQ45^8!Kx*I~uPl^#i(;7pC#@m*qmN4Xc5P*TfNfCS(qBk_eBLFn&+KX=g z>F633!FM|Y*l`1N`($e$pvybvhjRCw?ir`Y$qqz*t!JdOXABUfvuBh%c=^j}HD=7r zZY$&Rf1d9LL?K^)zWxfgKH$*XT@zC?fFk6B4j${|>+c-v1Biw=J*Wu$X8-7+w{HN7 z96H{0dw7bkzYb^C3DZ>rx?^MWSKqucJT?t3%gEUDZ~k&s1b?ZmDol1nx6`os$oS0u zgYDq5?0fCPh#6+?{(_wWbj~z;udw>B@%dY4dVc%YYv7uYH$Qf^R~&yKcE>D6^Wi@~ za_NIh1K@(3I@d?uTr_`$+bty=)_?FyYj-HY&4QkR@gM%8P1gnTlxnSb{SFXJ#wTZf z|M%<5(!EusnUDxM`tFVIzWnbAUY*`|6+!n4nwk#pf4zO*@6OLy8}2Vq$jkS>dX7A% zXLK<=6)^)GY4037cBVVYtFA2c-|Mt3QsRGp@8%1?YCU}+1OkiFuB zN%tAkDTYULHrAF+4U!Mj46j>=|k62>quc)Ds%NU;I+m?>u7u1v4;|0qw95gm0+MX{^m# zUzL-eHQ$ANWIX5e`CIR{^`33-6V@-vm32E0(_PGH?~pil&C0;mjU|n>L0_^57Ahm- z(Ig1i*?06@K=%O{Ke< zD^nM7!I_VYPE5aYxc!eOJOA(b;MiJz-FEKZg}m&P7k90DYE!A#{0F~i*NS3B zEM4ZB0z0=>KL6!fuSdhb1txj4N7ogos`CDaeK`Kc<;Eq*t`$*1vNO`Wd%soxczuy` zU8LuSYAZ@zMRsLrCaJ2LljAv*gOLHzUk|pa;1&;NrM~=pQ$=xN=3OmPih}8z*5;r5 zpqn%rkjnwM{G#^q+_VGVYb?w~-5}`hLQdwup2or)A0Uq%cP(mRFSp4(w^l z4fvul4Eeyd}_vo zUY+dtNRGqJ3;-rOBDUlh++#kE%+-=MvK{*?O43HP&05gxl{C`Ev$gE1W{|>91l=)f z>1Zu`jm!X^A~S%025i&qXs~0I(#Ek>b9|CiNh2|#w&XZGYE0NvwB4*rj>Bi~7o<4- z*OH@fIU>e=L`;?fSFQzCZv{hSt%Q#)#P10&Wll0+3rOK7{{sL3|NkbcTV((M00v1! bK~w_(7E61T^Kr)d00000NkvXXu0mjfW)3bH literal 0 HcmV?d00001 diff --git a/src/main/resources/templates/themes/fresh-mint2/assets/favicon.svg b/src/main/resources/templates/themes/fresh-mint2/assets/favicon.svg new file mode 100644 index 0000000..cbfc6c6 --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/assets/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/themes/fresh-mint2/assets/js/main.js b/src/main/resources/templates/themes/fresh-mint2/assets/js/main.js new file mode 100644 index 0000000..d1918c6 --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/assets/js/main.js @@ -0,0 +1,40 @@ +/* fresh-mint2 皮肤包脚本:仅首页轮播自动播放与圆点切换,无任何外部依赖 */ +(function () { + var slides = document.querySelectorAll('.banner-slide'); + var dots = document.querySelectorAll('.banner-dots button'); + if (!slides.length) { + return; + } + var current = 0; + var timer = null; + + function show(index) { + slides[current].classList.remove('active'); + if (dots[current]) { + dots[current].classList.remove('active'); + } + current = (index + slides.length) % slides.length; + slides[current].classList.add('active'); + if (dots[current]) { + dots[current].classList.add('active'); + } + } + + function start() { + if (slides.length > 1) { + timer = window.setInterval(function () { + show(current + 1); + }, 5000); + } + } + + dots.forEach(function (dot, index) { + dot.addEventListener('click', function () { + window.clearInterval(timer); + show(index); + start(); + }); + }); + + start(); +})(); diff --git a/src/main/resources/templates/themes/fresh-mint2/assets/screenshot.png b/src/main/resources/templates/themes/fresh-mint2/assets/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..534893bd8ee1107ac9b0299d30eb999b112b9395 GIT binary patch literal 25678 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 + + + +
+ +
+ + +
+

文章标题

+ + +
正文
+
+ + +
+ +
+ + diff --git a/src/main/resources/templates/themes/fresh-mint2/fragments/footer.html b/src/main/resources/templates/themes/fresh-mint2/fragments/footer.html new file mode 100644 index 0000000..b0e7a5e --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/fragments/footer.html @@ -0,0 +1,23 @@ + + + +
+ + + + +
+ diff --git a/src/main/resources/templates/themes/fresh-mint2/fragments/head.html b/src/main/resources/templates/themes/fresh-mint2/fragments/head.html new file mode 100644 index 0000000..c552917 --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/fragments/head.html @@ -0,0 +1,25 @@ + + + + + + + + 页面标题 + + + + + + + + + + + + + + + diff --git a/src/main/resources/templates/themes/fresh-mint2/fragments/header.html b/src/main/resources/templates/themes/fresh-mint2/fragments/header.html new file mode 100644 index 0000000..e727949 --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/fragments/header.html @@ -0,0 +1,61 @@ + + + + + diff --git a/src/main/resources/templates/themes/fresh-mint2/home.html b/src/main/resources/templates/themes/fresh-mint2/home.html new file mode 100644 index 0000000..4da07a1 --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/home.html @@ -0,0 +1,57 @@ + + + + +
+ +
+ + + + +
+

头条推荐

+ +
+ + +
+
+

栏目名

+ 更多 > +
+ +

暂无内容

+
+
+ +
+ + + diff --git a/src/main/resources/templates/themes/fresh-mint2/list.html b/src/main/resources/templates/themes/fresh-mint2/list.html new file mode 100644 index 0000000..2caac35 --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/list.html @@ -0,0 +1,58 @@ + + + + +
+ +
+ + + +

栏目名

+

搜索词

+ + +

暂无内容

+ + + +
+ +
+ + + diff --git a/src/main/resources/templates/themes/fresh-mint2/single.html b/src/main/resources/templates/themes/fresh-mint2/single.html new file mode 100644 index 0000000..da14b58 --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/single.html @@ -0,0 +1,23 @@ + + + + +
+ +
+ + +
+

栏目名

+ +
单页内容
+

暂无内容

+
+
+ +
+ + diff --git a/src/main/resources/templates/themes/fresh-mint2/theme.json b/src/main/resources/templates/themes/fresh-mint2/theme.json new file mode 100644 index 0000000..3d9a1be --- /dev/null +++ b/src/main/resources/templates/themes/fresh-mint2/theme.json @@ -0,0 +1,21 @@ +{ + "specVersion": 1, + "name": "fresh-mint2", + "title": "清新薄荷·II", + "version": "1.0.0", + "author": "yunhai", + "description": "清新薄荷风(卡片封面版):主色 #17A2A2、浅薄荷底、白卡 12px 圆角;细线吸顶顶栏 + 居中 logo;列表为带封面的卡片网格(一行 3 张);几何圆点装饰与大留白;适合职业院校。", + "screenshot": "assets/screenshot.png", + "bannerSize": "1920x560", + "params": [ + { "key": "primary", "cssVar": "--primary", "label": "主色(薄荷绿)", "type": "color", "default": "#17A2A2" }, + { "key": "primaryDark", "cssVar": "--primary-dark", "label": "主色加深", "type": "color", "default": "#0F7C7C" }, + { "key": "primarySoft", "cssVar": "--primary-soft", "label": "主色浅调(装饰)", "type": "color", "default": "#E6F6F6" }, + { "key": "bg", "cssVar": "--bg", "label": "页面底色", "type": "color", "default": "#F5FBFA" }, + { "key": "border", "cssVar": "--border", "label": "边框色", "type": "color", "default": "#DCEDEB" }, + { "key": "listLayout", "label": "列表形态", "type": "select", "default": "grid", + "options": [ { "value": "grid", "label": "卡片网格(一行三张)" }, { "value": "list", "label": "简约列表" } ] }, + { "key": "decoration", "label": "背景装饰", "type": "select", "default": "dots", + "options": [ { "value": "dots", "label": "几何圆点" }, { "value": "plain", "label": "纯净无装饰" } ] } + ] +} diff --git a/src/main/resources/templates/themes/mint-breeze/README.md b/src/main/resources/templates/themes/mint-breeze/README.md new file mode 100644 index 0000000..b7a5e6b --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/README.md @@ -0,0 +1,36 @@ +# mint-breeze 皮肤包 + +由 blue-white 派生的清新薄荷风皮肤(docs/10 契约 §5 工作流)。**升级随版本发布,不在站点间复制目录**。 + +> 注:本包与 fresh-mint 为同源并存的两个车道产物(并发会话分道,2026-09-08),互不依赖;站点二选一即可。 + +## 风格定位 + +清爽、亲和,适合职业院校门户:薄荷青品牌色、圆角 12px、卡片纯白+柔和阴影、细线顶栏+居中 logo(桌面吸顶,移动端取消)、列表卡片网格(一行 3 张)、几何圆点装饰与大留白;无纹理、无通栏大图、无复杂动效。 + +## 数据契约(模板可取用的 model 属性全集,契约 §3) + +| 页面 | 属性 | +| --- | --- | +| 全部 | `site`(快照:config/分类树/轮播/友链组/院系子站)、`url`(PortalUrlHelper:home/category/article/asset/search/siteHome)、`themeBase`(=`themes/mint-breeze`)、`navCategories`、`pageTitle`、`seoDescription`、`canonical`、`themeParams`(微调参数,仅 theme.json 声明过的 key)、`themeVarsCss`(服务端预构建 `:root{}` 覆盖串,无参数为空) | +| home | `banners`(时间窗内 home 位)、`topArticles`(置顶+最新 6)、`categoryBlocks`(LIST 型根栏目块,各 top 8) | +| list / search | `listData`(分页)、`category`(list)、`q` + `searchMode`(search) | +| detail | `article`、`prevArticle`、`nextArticle`、`currentCategoryId` | +| single | `category`(含 `content`)、`currentCategoryId` | + +原则:皮肤不得假设"首页一定有轮播",对空集合优雅降级。 + +## 轮播图尺寸约定(bannerSize) + +1920x560(管理端上传端按此引导裁剪,D2 决策点)。 + +## 可调参数 + +见 `theme.json` 的 `params`: + +- **color**(进 CSS 变量):`primary` 主色 / `primaryDark` 主色加深(即强调色,用于品牌文字与悬停加深)/ `primaryLight` 主色提亮 / `bg` 页面底色 / `cardBg` 卡片底色 / `border` 边框色 / `text` 正文文字 / `textLight` 次要文字。 +- **select**:`listLayout` = `grid`(默认,卡片网格一行 3 张)或 `row`(传统行列表)。 + +换色建议:主色系三值同步调整(primary / primaryDark / primaryLight),bg 与 border、primaryLight 保持同族浅色调,避免底色与卡片对比失衡。 + +优先级:`custom_css` > `themeVarsCss` > `main.css`。 diff --git a/src/main/resources/templates/themes/mint-breeze/assets/css/main.css b/src/main/resources/templates/themes/mint-breeze/assets/css/main.css new file mode 100644 index 0000000..33466ef --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/assets/css/main.css @@ -0,0 +1,590 @@ +/* ============================================================ + * mint-breeze 皮肤包样式(blue-white 派生,docs/10 皮肤包契约 §5) + * 清新薄荷风:薄荷青品牌色 + 圆角 12px + 柔和阴影 + 卡片网格列表 + * + 几何圆点装饰与大留白;无纹理、无通栏大图、无复杂动效。 + * 轮播图设计尺寸约定:1920×560(管理端上传端按此引导裁剪)。 + * ============================================================ */ +:root { + --primary: #17A2A2; + --primary-dark: #0F7C7C; + --primary-light: #E6F6F6; + --text: #2F3E3C; + --text-light: #7D8F8C; + --bg: #F5FBFA; + --card-bg: #FFFFFF; + --border: #DDF0EE; + --radius: 12px; + --radius-sm: 8px; + --radius-pill: 999px; + --shadow-soft: 0 6px 18px rgba(15, 124, 124, .08); + --shadow-hover: 0 12px 28px rgba(15, 124, 124, .14); + --max-width: 1200px; +} + +* { box-sizing: border-box; } +body { + margin: 0; + font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; + color: var(--text); + background: var(--bg); +} +a { color: inherit; text-decoration: none; transition: color .15s ease; } +a:hover { color: var(--primary); } +img { max-width: 100%; } +time, .item-meta { color: var(--text-light); font-size: 13px; } +.empty { color: var(--text-light); text-align: center; padding: 32px 0; } + +/* ===== 头部:细线顶栏 + 居中品牌行 + 白底导航(桌面吸顶) ===== */ +.site-header { + position: sticky; + top: 0; + z-index: 90; + background: var(--card-bg); + box-shadow: var(--shadow-soft); +} +/* 细线顶栏:院系切换在左、站内搜索在右;院系未配置时搜索仍靠右 */ +.top-bar { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 16px; + padding: 7px max(16px, calc((100% - var(--max-width)) / 2 + 16px)); + border-bottom: 1px solid var(--border); + font-size: 13px; +} +.top-bar .site-colleges { margin-right: auto; } +/* 居中品牌行:两侧几何圆点装饰(纯 CSS,无纹理) */ +.brand-row { + position: relative; + display: flex; + justify-content: center; + padding: 18px 16px 14px; +} +.brand-row::before, .brand-row::after { + content: ""; + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 46px; + height: 10px; + opacity: .45; + background-image: + radial-gradient(circle, var(--primary) 2.2px, transparent 2.8px), + radial-gradient(circle, var(--primary) 1.6px, transparent 2.2px), + radial-gradient(circle, var(--primary) 1.1px, transparent 1.7px); + background-repeat: no-repeat; + background-position: left center, center center, right center; +} +.brand-row::before { left: 6%; } +.brand-row::after { right: 6%; } +.site-brand { display: flex; align-items: center; gap: 14px; } +.site-logo { height: 52px; } +.site-name { + font-size: 28px; + font-weight: 600; + letter-spacing: .02em; + color: var(--primary-dark); +} +.site-name:hover { color: var(--primary); } +.site-search { display: flex; align-items: center; } +.site-search input { + border: 1px solid var(--border); + border-right: none; + border-radius: var(--radius-pill) 0 0 var(--radius-pill); + padding: 7px 16px; + width: 220px; + outline: none; + background: var(--card-bg); + color: var(--text); + transition: border-color .15s ease, box-shadow .15s ease; +} +.site-search input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px var(--primary-light); +} +.site-search button { + border: none; + background: var(--primary); + color: #fff; + padding: 7px 20px; + border-radius: 0 var(--radius-pill) var(--radius-pill) 0; + cursor: pointer; + transition: background .15s ease; +} +.site-search button:hover { background: var(--primary-dark); } + +/* ===== 专业院系下拉(院系子站切换):胶囊开关 + 圆角白卡面板 ===== */ +.site-colleges { position: relative; } +.site-colleges .colleges-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + padding: 5px 14px; + cursor: pointer; + color: var(--primary-dark); + font-weight: 600; + white-space: nowrap; + transition: border-color .15s ease, background .15s ease; +} +.site-colleges:hover .colleges-toggle { + border-color: var(--primary); + background: var(--primary-light); +} +.site-colleges .colleges-arrow { font-style: normal; font-size: 12px; color: var(--primary); } +.site-colleges .colleges-menu { + display: none; + position: absolute; + top: 100%; + left: 0; + min-width: 200px; + max-height: 420px; + overflow-y: auto; + margin: 0; + padding: 8px 0; + list-style: none; + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-hover); + z-index: 110; +} +.site-colleges:hover .colleges-menu { display: block; } +.site-colleges .colleges-menu li a { + display: block; + margin: 0 8px; + padding: 9px 12px; + border-radius: var(--radius-sm); + color: var(--text); + white-space: nowrap; +} +.site-colleges .colleges-menu li a:hover { background: var(--primary-light); color: var(--primary-dark); } +.site-colleges .colleges-menu li.active a { color: var(--primary); font-weight: 600; } + +/* ===== 主导航:白底 + 柔和文字层级,当前项圆点标记 ===== */ +.site-nav { background: var(--card-bg); border-top: 1px solid var(--border); } +.site-nav ul { + max-width: var(--max-width); + margin: 0 auto; + padding: 0 16px; + list-style: none; + display: flex; + justify-content: center; + flex-wrap: wrap; +} +.site-nav li a { + position: relative; + display: block; + padding: 13px 20px; + color: var(--text); + font-weight: 500; +} +.site-nav li a:hover { color: var(--primary); } +.site-nav li.active a { color: var(--primary); font-weight: 600; } +/* 几何圆点:当前栏目下方的小圆点 */ +.site-nav li.active > a::before { + content: ""; + position: absolute; + left: 50%; + bottom: 5px; + width: 5px; + height: 5px; + transform: translateX(-50%); + border-radius: 50%; + background: var(--primary); +} + +/* ===== 二级下拉导航:圆角白卡面板 ===== */ +.site-nav > ul > li { position: relative; } +.site-nav li.has-sub > a::after { content: " ▾"; font-size: 12px; opacity: .7; } +.site-nav ul.sub-nav { + display: none; + position: absolute; + top: 100%; + left: 0; + min-width: 168px; + max-width: none; + margin: 0; + padding: 8px 0; + list-style: none; + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-hover); + z-index: 100; +} +.site-nav li:hover > .sub-nav { display: block; } +.site-nav .sub-nav li a { + margin: 0 8px; + padding: 9px 12px; + border-radius: var(--radius-sm); + color: var(--text); + font-weight: 400; + white-space: nowrap; + background: none; +} +.site-nav .sub-nav li a:hover { background: var(--primary-light); color: var(--primary-dark); } +.site-nav .sub-nav li.active a { color: var(--primary); font-weight: 600; } + +/* ===== 首页轮播:容器内圆角卡片(非通栏),几何圆点指示器 ===== */ +.banner { + position: relative; + width: min(var(--max-width), calc(100% - 32px)); + margin: 24px auto 0; + border-radius: var(--radius); + overflow: hidden; + box-shadow: var(--shadow-soft); +} +.banner-slides { position: relative; height: 400px; } +.banner-slide { display: none; position: absolute; inset: 0; } +.banner-slide.active { display: block; } +.banner-slide img { width: 100%; height: 100%; object-fit: cover; } +.banner-title { + position: absolute; + left: 0; right: 0; bottom: 0; + padding: 14px 20px; + color: #fff; + font-weight: 500; + background: linear-gradient(transparent, rgba(20, 63, 61, .62)); +} +.banner-dots { + position: absolute; + bottom: 14px; right: 20px; + display: flex; gap: 8px; +} +.banner-dots button { + width: 9px; height: 9px; + border-radius: 50%; + border: none; + background: rgba(255, 255, 255, .55); + cursor: pointer; + transition: background .15s ease, transform .15s ease; +} +.banner-dots button.active { background: #fff; transform: scale(1.25); } + +/* ===== 首页区块:卡片网格(头条通栏 + 栏目块一行 3 张) ===== */ +.home-main { + max-width: var(--max-width); + margin: 0 auto; + padding: 28px 16px 56px; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 24px; +} +.top-strip, .category-block { + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 24px; +} +.top-strip { grid-column: 1 / -1; } +/* 栏目标题:几何圆点标记(环形点),标题中等粗细、层级柔和 */ +.section-title, .block-head h2 { + margin: 0 0 14px; + font-size: 19px; + font-weight: 600; + letter-spacing: .02em; + display: flex; + align-items: center; + gap: 10px; +} +.section-title::before, .block-head h2::before { + content: ""; + flex: none; + width: 9px; + height: 9px; + border-radius: 50%; + background: var(--primary); + box-shadow: 0 0 0 3.5px var(--primary-light); +} +.block-head { display: flex; align-items: center; justify-content: space-between; } +.more { + flex: none; + font-size: 13px; + color: var(--text-light); + padding: 4px 12px; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + transition: color .15s ease, border-color .15s ease, background .15s ease; +} +.more:hover { + color: var(--primary-dark); + border-color: var(--primary); + background: var(--primary-light); +} +.top-strip ul, .block-list { list-style: none; margin: 0; padding: 0; } +.top-strip li, .block-list li { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 0; + border-bottom: 1px dashed var(--border); +} +.top-strip li:last-child, .block-list li:last-child { border-bottom: none; } +.top-strip li a, .block-list li a { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.top-badge { + flex: none; + color: var(--primary-dark); + background: var(--primary-light); + font-size: 12px; + font-weight: 500; + padding: 2px 10px; + border-radius: var(--radius-pill); +} + +/* ===== 列表页:卡片网格(默认一行 3 张;listLayout=row 切换行列表) ===== */ +.list-main { + max-width: var(--max-width); + margin: 0 auto; + padding: 28px 16px 56px; +} +.breadcrumb { color: var(--text-light); font-size: 13px; margin-bottom: 14px; } +.breadcrumb a { margin-right: 8px; } +.breadcrumb span { margin-left: 8px; } +.page-title { + font-size: 24px; + font-weight: 600; + letter-spacing: .02em; + margin: 0 0 22px; + display: flex; + align-items: center; + gap: 10px; +} +.page-title::before { + content: ""; + flex: none; + width: 9px; + height: 9px; + border-radius: 50%; + background: var(--primary); + box-shadow: 0 0 0 3.5px var(--primary-light); +} +.article-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; +} +.article-list li { + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 22px 20px; + display: flex; + flex-direction: column; + gap: 10px; + transition: transform .18s ease, box-shadow .18s ease; +} +.article-list li:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); } +.item-title { + font-size: 16px; + font-weight: 600; + line-height: 1.55; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} +.article-list li:hover .item-title { color: var(--primary); } +.item-summary { + color: var(--text-light); + font-size: 13.5px; + line-height: 1.7; + margin: 0; + flex: 1; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} +.item-meta { + display: flex; + gap: 10px; + align-items: center; + margin-top: auto; + padding-top: 10px; + border-top: 1px dashed var(--border); +} +/* 布局变体:传统行列表(themeParams listLayout=row,见 list.html classappend) */ +.list-rows .article-list { display: block; } +.list-rows .article-list li { display: block; margin-bottom: 16px; } +.list-rows .item-summary { -webkit-line-clamp: 2; flex: none; } +.list-rows .item-meta { border-top: none; padding-top: 0; margin-top: 6px; } + +/* ===== 分页:胶囊页码 ===== */ +.pagination { + display: flex; + gap: 8px; + justify-content: center; + flex-wrap: wrap; + padding: 28px 0 4px; +} +.pagination a { + min-width: 36px; + text-align: center; + padding: 6px 12px; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + background: var(--card-bg); + font-size: 14px; + transition: color .15s ease, border-color .15s ease, background .15s ease; +} +.pagination a:hover { color: var(--primary-dark); border-color: var(--primary); } +.pagination a.active { background: var(--primary); color: #fff; border-color: var(--primary); } + +/* ===== 详情/单页:整卡承载,正文舒适行距 ===== */ +.detail-main { + max-width: var(--max-width); + margin: 0 auto; + padding: 28px 16px 56px; +} +.detail-main .article { + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 36px 40px; +} +.article-title { + margin: 4px 0 14px; + font-size: 26px; + font-weight: 600; + letter-spacing: .02em; + text-align: center; +} +.article-meta { + display: flex; + gap: 18px; + justify-content: center; + flex-wrap: wrap; + color: var(--text-light); + font-size: 13px; + padding-bottom: 18px; + border-bottom: 1px solid var(--border); +} +.article-content { line-height: 1.9; font-size: 16px; padding-top: 22px; word-break: break-word; } +.article-content img { height: auto; border-radius: var(--radius-sm); } +.article-content table { border-collapse: collapse; width: 100%; } +.article-content table td, .article-content table th { border: 1px solid var(--border); padding: 6px 10px; } +.article-sibling { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 20px; font-size: 14px; } +.article-sibling a { + flex: 1 1 320px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + background: var(--card-bg); + border-radius: var(--radius); + box-shadow: var(--shadow-soft); + padding: 14px 18px; + transition: transform .18s ease, box-shadow .18s ease, color .15s ease; +} +.article-sibling a:hover { + color: var(--primary-dark); + transform: translateY(-2px); + box-shadow: var(--shadow-hover); +} + +/* ===== 页脚:浅薄荷底 + 圆点分隔条(三态渲染逻辑不动) ===== */ +.site-footer { + margin-top: 40px; + background: var(--primary-light); + color: var(--text); + border-top: 1px solid var(--border); +} +.site-footer::before { + content: ""; + display: block; + height: 8px; + width: min(var(--max-width), calc(100% - 32px)); + margin: 18px auto 0; + background-image: radial-gradient(circle, var(--primary) 1.6px, transparent 2.2px); + background-size: 16px 8px; + background-repeat: repeat-x; + opacity: .4; +} +.footer-links { + max-width: var(--max-width); + margin: 0 auto; + padding: 18px 16px 0; + display: flex; + flex-direction: column; + gap: 10px; +} +.links-group { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; font-size: 13px; } +.group-name { font-weight: 600; margin-right: 4px; color: var(--primary-dark); } +.links-group a { + padding: 3px 10px; + border-radius: var(--radius-pill); + transition: background .15s ease, color .15s ease; +} +.links-group a:hover { background: rgba(255, 255, 255, .75); color: var(--primary-dark); } +.footer-info { + max-width: var(--max-width); + margin: 0 auto; + padding: 14px 16px 24px; + text-align: center; + font-size: 13px; + color: var(--text-light); +} +.footer-info p { margin: 4px 0; } + +/* B4 友链 Logo:与名称并排小图(高度 ~20px,垂直居中;过宽截断不撑破页脚) */ +.links-group a .link-logo { + height: 20px; + width: auto; + max-width: 64px; + object-fit: contain; + vertical-align: middle; + margin-right: 4px; + border-radius: 4px; +} + +/* ===== 平板(≤992px):网格降为两列,隐藏品牌行圆点装饰 ===== */ +@media (max-width: 992px) { + .home-main { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .article-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .brand-row::before, .brand-row::after { display: none; } +} + +/* ===== 移动端(≤768px):取消吸顶,网格单列,导航横向滑动 ===== */ +@media (max-width: 768px) { + .site-header { position: static; } + .top-bar { padding: 8px 14px; justify-content: flex-start; flex-wrap: wrap; } + .top-bar .site-colleges { margin-right: 0; } + .site-search { flex: 1; } + .site-search input { flex: 1; width: auto; min-width: 0; } + .brand-row { padding: 14px 16px 10px; } + .site-logo { height: 40px; } + .site-name { font-size: 22px; } + .site-nav ul { + justify-content: flex-start; + flex-wrap: nowrap; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .banner-slides { height: 200px; } + .home-main { grid-template-columns: 1fr; padding: 18px 14px 40px; } + .article-list { grid-template-columns: 1fr; } + .detail-main .article { padding: 22px 16px; } + .article-title { font-size: 21px; } + /* 触屏无 hover:子菜单静态平铺(缩进列表),不做浮层 */ + .site-nav ul.sub-nav { + display: block; + position: static; + border: none; + box-shadow: none; + background: transparent; + min-width: 0; + padding: 0 0 6px; + } + .site-nav .sub-nav li a { margin: 0; padding: 8px 16px 8px 34px; border-radius: 0; color: var(--text-light); } + .site-nav .sub-nav li a:hover { background: var(--primary-light); color: var(--primary-dark); } +} diff --git a/src/main/resources/templates/themes/mint-breeze/assets/favicon.png b/src/main/resources/templates/themes/mint-breeze/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..497cae1565a13392c053aa7f18c3e2245d0bdc0f GIT binary patch literal 2617 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 bK~w_(7E61T^Kr)d00000NkvXXu0mjfW)3bH literal 0 HcmV?d00001 diff --git a/src/main/resources/templates/themes/mint-breeze/assets/favicon.svg b/src/main/resources/templates/themes/mint-breeze/assets/favicon.svg new file mode 100644 index 0000000..cbfc6c6 --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/assets/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/themes/mint-breeze/assets/js/main.js b/src/main/resources/templates/themes/mint-breeze/assets/js/main.js new file mode 100644 index 0000000..87fbb17 --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/assets/js/main.js @@ -0,0 +1,40 @@ +/* mint-breeze 皮肤包脚本:仅首页轮播自动播放与圆点切换,无任何外部依赖 */ +(function () { + var slides = document.querySelectorAll('.banner-slide'); + var dots = document.querySelectorAll('.banner-dots button'); + if (!slides.length) { + return; + } + var current = 0; + var timer = null; + + function show(index) { + slides[current].classList.remove('active'); + if (dots[current]) { + dots[current].classList.remove('active'); + } + current = (index + slides.length) % slides.length; + slides[current].classList.add('active'); + if (dots[current]) { + dots[current].classList.add('active'); + } + } + + function start() { + if (slides.length > 1) { + timer = window.setInterval(function () { + show(current + 1); + }, 5000); + } + } + + dots.forEach(function (dot, index) { + dot.addEventListener('click', function () { + window.clearInterval(timer); + show(index); + start(); + }); + }); + + start(); +})(); diff --git a/src/main/resources/templates/themes/mint-breeze/assets/screenshot.png b/src/main/resources/templates/themes/mint-breeze/assets/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a7d0c9760a3b391d4ab3f65954efaae84febde GIT binary patch literal 86616 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 lRPJDMriQD7xz?ET>rb?$J%uGFc + + + +

+ +
+ + +
+

文章标题

+ + +
正文
+
+ + +
+ +
+ + diff --git a/src/main/resources/templates/themes/mint-breeze/fragments/footer.html b/src/main/resources/templates/themes/mint-breeze/fragments/footer.html new file mode 100644 index 0000000..1f3f83d --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/fragments/footer.html @@ -0,0 +1,21 @@ + + + +
+ + +
+ diff --git a/src/main/resources/templates/themes/mint-breeze/fragments/head.html b/src/main/resources/templates/themes/mint-breeze/fragments/head.html new file mode 100644 index 0000000..1809482 --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/fragments/head.html @@ -0,0 +1,24 @@ + + + + + + + 页面标题 + + + + + + + + + + + + + + + diff --git a/src/main/resources/templates/themes/mint-breeze/fragments/header.html b/src/main/resources/templates/themes/mint-breeze/fragments/header.html new file mode 100644 index 0000000..216bc4b --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/fragments/header.html @@ -0,0 +1,55 @@ + + + + + diff --git a/src/main/resources/templates/themes/mint-breeze/home.html b/src/main/resources/templates/themes/mint-breeze/home.html new file mode 100644 index 0000000..21bfb84 --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/home.html @@ -0,0 +1,57 @@ + + + + +
+ +
+ + + + +
+

头条推荐

+ +
+ + +
+
+

栏目名

+ 更多 > +
+ +

暂无内容

+
+
+ +
+ + + diff --git a/src/main/resources/templates/themes/mint-breeze/list.html b/src/main/resources/templates/themes/mint-breeze/list.html new file mode 100644 index 0000000..882fc1b --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/list.html @@ -0,0 +1,51 @@ + + + + +
+ + +
+ + + +

栏目名

+

搜索词

+ + +

暂无内容

+ + + +
+ +
+ + + diff --git a/src/main/resources/templates/themes/mint-breeze/single.html b/src/main/resources/templates/themes/mint-breeze/single.html new file mode 100644 index 0000000..48e504c --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/single.html @@ -0,0 +1,23 @@ + + + + +
+ +
+ + +
+

栏目名

+ +
单页内容
+

暂无内容

+
+
+ +
+ + diff --git a/src/main/resources/templates/themes/mint-breeze/theme.json b/src/main/resources/templates/themes/mint-breeze/theme.json new file mode 100644 index 0000000..90ad6e5 --- /dev/null +++ b/src/main/resources/templates/themes/mint-breeze/theme.json @@ -0,0 +1,22 @@ +{ + "specVersion": 1, + "name": "mint-breeze", + "title": "薄荷沁风", + "version": "1.0.0", + "author": "yunhai", + "description": "清新薄荷风门户:细线顶栏+居中 logo 吸顶、薄荷青品牌色、圆角 12px 卡片网格列表、几何圆点装饰与大留白,清爽亲和,适合职业院校。", + "screenshot": "assets/screenshot.png", + "bannerSize": "1920x560", + "params": [ + { "key": "primary", "cssVar": "--primary", "label": "主色", "type": "color", "default": "#17A2A2" }, + { "key": "primaryDark", "cssVar": "--primary-dark", "label": "主色加深(强调色)", "type": "color", "default": "#0F7C7C" }, + { "key": "primaryLight", "cssVar": "--primary-light", "label": "主色提亮", "type": "color", "default": "#E6F6F6" }, + { "key": "bg", "cssVar": "--bg", "label": "页面底色", "type": "color", "default": "#F5FBFA" }, + { "key": "cardBg", "cssVar": "--card-bg", "label": "卡片底色", "type": "color", "default": "#FFFFFF" }, + { "key": "border", "cssVar": "--border", "label": "边框色", "type": "color", "default": "#DDF0EE" }, + { "key": "text", "cssVar": "--text", "label": "正文文字色", "type": "color", "default": "#2F3E3C" }, + { "key": "textLight", "cssVar": "--text-light", "label": "次要文字色", "type": "color", "default": "#7D8F8C" }, + { "key": "listLayout", "label": "列表布局", "type": "select", "default": "grid", + "options": [ { "value": "grid", "label": "卡片网格(一行 3 张)" }, { "value": "row", "label": "传统行列表" } ] } + ] +} diff --git a/src/test/java/com/yunhai/cms/portal/ThemeRenderIT.java b/src/test/java/com/yunhai/cms/portal/ThemeRenderIT.java index 6de37a9..b357d30 100644 --- a/src/test/java/com/yunhai/cms/portal/ThemeRenderIT.java +++ b/src/test/java/com/yunhai/cms/portal/ThemeRenderIT.java @@ -92,7 +92,7 @@ class ThemeRenderIT extends CmsIntegrationTestBase { System.out.println("SOURCE-OF=" + themeRegistry.sourceOf("t-ext-ssr") + " DIR=" + themeRegistry.getThemeDir()); themePackageService.upload(TestUsers.superAdmin(), com.yunhai.cms.support.ThemePackageTestSupport.buildZip( - "t-ext-ssr", "t-ext-ssr", true, true, true, true, true, "EXT-SSR-OK")); + "t-ext-ssr", "t-ext-ssr", true, true, true, true, true, "EXT-SSR-OK"), null, null); persistSiteWithTheme("extportal", "t-ext-ssr", "{}"); MvcResult result = mockMvc.perform(get("/portal/extportal/")).andExpect(status().isOk()).andReturn(); @@ -106,7 +106,7 @@ class ThemeRenderIT extends CmsIntegrationTestBase { void externalPackageAssetsServed() throws Exception { themePackageService.upload(TestUsers.superAdmin(), com.yunhai.cms.support.ThemePackageTestSupport.buildZip( - "t-ext-asset", "t-ext-asset", true, true, true, true, true, null)); + "t-ext-asset", "t-ext-asset", true, true, true, true, true, null), null, null); persistSiteWithTheme("extassets", "t-ext-asset", "{}"); // 门户资产路由(匿名) diff --git a/src/test/java/com/yunhai/cms/theme/ThemeApiIT.java b/src/test/java/com/yunhai/cms/theme/ThemeApiIT.java index af659b3..4ba4c09 100644 --- a/src/test/java/com/yunhai/cms/theme/ThemeApiIT.java +++ b/src/test/java/com/yunhai/cms/theme/ThemeApiIT.java @@ -143,22 +143,22 @@ class ThemeApiIT extends CmsIntegrationTestBase { org.springframework.mock.web.MockMultipartFile zip = com.yunhai.cms.support.ThemePackageTestSupport.buildZip( "t-api-fb", "t-api-fb", true, true, true, true, true, null); - assertThatThrownBy(() -> themePackageService.upload(TestUsers.schoolUser(1), zip)) + assertThatThrownBy(() -> themePackageService.upload(TestUsers.schoolUser(1), zip, null, null)) .isInstanceOf(BizException.class) .hasMessageContaining("仅平台超管"); } @Test - @DisplayName("T-W1-14: list 增补字段 source/inUseCount/editable;内置包 editable=false") + @DisplayName("T-W1-14: list 增补字段 source/inUseCount/editable;任务四起内置包 editable=true(元数据可编辑)") void listCarriesNewFields() throws Exception { themePackageService.upload(TestUsers.superAdmin(), com.yunhai.cms.support.ThemePackageTestSupport.buildZip( - "t-api-ext", "t-api-ext", true, true, true, true, true, null)); + "t-api-ext", "t-api-ext", true, true, true, true, true, null), null, null); List list = themeService.list(); com.yunhai.cms.model.vo.ThemeVo def = list.stream() .filter(v -> "default".equals(v.getName())).findFirst().orElseThrow(IllegalStateException::new); assertThat(def.getSource()).isEqualTo("builtin"); - assertThat(def.isEditable()).isFalse(); + assertThat(def.isEditable()).isTrue(); assertThat(def.getInUseCount()).isGreaterThanOrEqualTo(0); com.yunhai.cms.model.vo.ThemeVo ext = list.stream() @@ -173,7 +173,7 @@ class ThemeApiIT extends CmsIntegrationTestBase { void deleteInUsePackageRejected() throws Exception { themePackageService.upload(TestUsers.superAdmin(), com.yunhai.cms.support.ThemePackageTestSupport.buildZip( - "t-api-del", "t-api-del", true, true, true, true, true, null)); + "t-api-del", "t-api-del", true, true, true, true, true, null), null, null); com.yunhai.cms.model.dto.SiteSaveRequest req = new com.yunhai.cms.model.dto.SiteSaveRequest(); req.setCode("delsite"); req.setParentSiteId(0); @@ -194,7 +194,7 @@ class ThemeApiIT extends CmsIntegrationTestBase { void deleteUnusedPackageSucceeds() throws Exception { themePackageService.upload(TestUsers.superAdmin(), com.yunhai.cms.support.ThemePackageTestSupport.buildZip( - "t-api-ext2", "t-api-ext2", true, true, true, true, true, null)); + "t-api-ext2", "t-api-ext2", true, true, true, true, true, null), null, null); com.yunhai.cms.model.dto.SiteSaveRequest req = new com.yunhai.cms.model.dto.SiteSaveRequest(); req.setCode("extsite"); req.setParentSiteId(0); @@ -231,7 +231,7 @@ class ThemeApiIT extends CmsIntegrationTestBase { @Test @DisplayName("T-W1-17: 派生+编辑链路——derive(default→my-skin) → PUT meta → list 与资产反映新值") void deriveAndUpdateMetaChain() throws Exception { - themePackageService.derive(TestUsers.superAdmin(), "default", "my-skin"); + themePackageService.derive(TestUsers.superAdmin(), "default", "my-skin", null, null); assertThat(themeRegistry.contains("my-skin")).isTrue(); assertThat(themeRegistry.sourceOf("my-skin")).isEqualTo("external"); @@ -245,7 +245,7 @@ class ThemeApiIT extends CmsIntegrationTestBase { edited.setType(primary.getType()); edited.setCssVar(primary.getCssVar()); edited.setDefaultValue("#123456"); - themePackageService.updateMeta(TestUsers.superAdmin(), "my-skin", "我的皮肤", + themePackageService.updateMeta(TestUsers.superAdmin(), "my-skin", "我的皮肤", null, java.util.Collections.singletonList(edited)); com.yunhai.cms.model.vo.ThemeVo vo = themeService.list().stream() @@ -269,4 +269,31 @@ class ThemeApiIT extends CmsIntegrationTestBase { .hasMessageContaining("仅平台超管"); themePackageService.reload(TestUsers.superAdmin()); } + + // ===== 任务四(2026-09-08 批次):命名表单 + 内置包改名 ===== + + @Test + @DisplayName("任务四: 内置包元数据编辑放开——.meta 覆盖层生效,list 反映新显示名") + void builtinMetaUpdateAllowed() { + themePackageService.updateMeta(TestUsers.superAdmin(), "default", "默认薄荷", "平台内置改名", null); + assertThat(themeRegistry.get("default").getTitle()).isEqualTo("默认薄荷"); + assertThat(themeService.list().stream() + .filter(v -> "default".equals(v.getName())).findFirst() + .orElseThrow(IllegalStateException::new).getTitle()).isEqualTo("默认薄荷"); + assertThat(java.nio.file.Files.exists(themeDir.resolve(".meta").resolve("default.json"))).isTrue(); + } + + @Test + @DisplayName("任务四: upload 带 title/description 表单字段——落位 theme.json 生效且 list 反映") + void uploadWithNamingFields() throws Exception { + org.springframework.mock.web.MockMultipartFile zip = + com.yunhai.cms.support.ThemePackageTestSupport.buildZip( + "t-api-name", "t-api-name", true, true, true, true, true, null); + themePackageService.upload(TestUsers.superAdmin(), zip, "表单命名", "表单描述"); + com.yunhai.cms.model.vo.ThemeVo vo = themeService.list().stream() + .filter(v -> "t-api-name".equals(v.getName())) + .findFirst().orElseThrow(IllegalStateException::new); + assertThat(vo.getTitle()).isEqualTo("表单命名"); + assertThat(vo.getDescription()).isEqualTo("表单描述"); + } } diff --git a/src/test/java/com/yunhai/cms/theme/ThemePackageServiceTest.java b/src/test/java/com/yunhai/cms/theme/ThemePackageServiceTest.java index e8aa45a..e1a6fe1 100644 --- a/src/test/java/com/yunhai/cms/theme/ThemePackageServiceTest.java +++ b/src/test/java/com/yunhai/cms/theme/ThemePackageServiceTest.java @@ -99,7 +99,7 @@ class ThemePackageServiceTest { @Test @DisplayName("T-W1-05: 合规 zip 上传成功——落盘结构完整、返回 ThemeInfo、registry 含之") void uploadValidZipSucceeds() throws Exception { - ThemeInfo info = service.upload(TestUsers.superAdmin(), zipOf("t-zip-ok", "t-zip-ok", true, true, true, true)); + ThemeInfo info = service.upload(TestUsers.superAdmin(), zipOf("t-zip-ok", "t-zip-ok", true, true, true, true), null, null); assertThat(info.getName()).isEqualTo("t-zip-ok"); assertThat(registry.contains("t-zip-ok")).isTrue(); assertThat(registry.sourceOf("t-zip-ok")).isEqualTo("external"); @@ -120,11 +120,11 @@ class ThemePackageServiceTest { } MockMultipartFile evil = new MockMultipartFile("file", "evil.zip", "application/zip", bos.toByteArray()); - assertThatThrownBy(() -> service.upload(TestUsers.superAdmin(), evil)) + assertThatThrownBy(() -> service.upload(TestUsers.superAdmin(), evil, null, null)) .isInstanceOf(RuntimeException.class) .hasMessageContaining("非法路径"); // 合法包同批校验防误伤 - service.upload(TestUsers.superAdmin(), file); + service.upload(TestUsers.superAdmin(), file, null, null); assertThat(Files.exists(themeDir.resolve("t-slip"))).isTrue(); assertThat(Files.exists(themeDir.getParent().resolve("evil.html"))).isFalse(); } @@ -135,7 +135,7 @@ class ThemePackageServiceTest { byte[] big = new byte[21 * 1024 * 1024]; java.util.Arrays.fill(big, (byte) 'x'); MockMultipartFile file = new MockMultipartFile("file", "big.zip", "application/zip", big); - assertThatThrownBy(() -> service.upload(TestUsers.superAdmin(), file)) + assertThatThrownBy(() -> service.upload(TestUsers.superAdmin(), file, null, null)) .hasMessageContaining("20MB"); } @@ -143,7 +143,7 @@ class ThemePackageServiceTest { @DisplayName("T-W1-08: 缺 theme.json 拒绝(报告含'缺 theme.json')") void missingThemeJsonRejected() throws Exception { assertThatThrownBy(() -> service.upload(TestUsers.superAdmin(), - zipOf("t-nojson", "t-nojson", false, true, true, true))) + zipOf("t-nojson", "t-nojson", false, true, true, true), null, null)) .hasMessageContaining("缺 theme.json"); } @@ -152,7 +152,7 @@ class ThemePackageServiceTest { void nameDirMismatchRejected() throws Exception { // zip 根目录 t-other 与 theme.json name=t-nojson 不一致 assertThatThrownBy(() -> service.upload(TestUsers.superAdmin(), - zipOf("t-nojson", "t-other", true, true, true, true))) + zipOf("t-nojson", "t-other", true, true, true, true), null, null)) .hasMessageContaining("name 与目录名不一致"); } @@ -160,7 +160,7 @@ class ThemePackageServiceTest { @DisplayName("T-W1-10: 缺必备模板/片段/截图拒绝(报告逐条列出)") void missingTemplatesRejected() throws Exception { assertThatThrownBy(() -> service.upload(TestUsers.superAdmin(), - zipOf("t-nolist", "t-nolist", true, false, false, false))) + zipOf("t-nolist", "t-nolist", true, false, false, false), null, null)) .hasMessageContaining("list.html") .hasMessageContaining("fragments/head.html") .hasMessageContaining("screenshot"); @@ -170,7 +170,7 @@ class ThemePackageServiceTest { @DisplayName("T-W1-11: 与内置同名拒绝(name=default),内置包不受影响") void builtinNameRejected() throws Exception { assertThatThrownBy(() -> service.upload(TestUsers.superAdmin(), - zipOf("default", "default", true, true, true, true))) + zipOf("default", "default", true, true, true, true), null, null)) .hasMessageContaining("内置"); assertThat(registry.sourceOf("default")).isEqualTo("builtin"); assertThat(registry.get("default").getTitle()).isNotBlank(); @@ -179,22 +179,108 @@ class ThemePackageServiceTest { @Test @DisplayName("T-W1-12: 外部同名=覆盖更新(目录替换、registry schema 更新)") void externalSameNameOverwrites() throws Exception { - service.upload(TestUsers.superAdmin(), zipOf("t-zip-ov", "t-zip-ov", true, true, true, true)); + service.upload(TestUsers.superAdmin(), zipOf("t-zip-ov", "t-zip-ov", true, true, true, true), null, null); ThemeInfo first = registry.get("t-zip-ov"); String oldTitle = first.getTitle(); MockMultipartFile v2 = zipOf("t-zip-ov", "t-zip-ov", true, true, true, true); // 修改 title 便于断言覆盖生效 - service.updateMeta(TestUsers.superAdmin(), "t-zip-ov", oldTitle + "-v2", null); + service.updateMeta(TestUsers.superAdmin(), "t-zip-ov", oldTitle + "-v2", null, null); ThemeInfo updated = registry.get("t-zip-ov"); assertThat(updated.getTitle()).isEqualTo(oldTitle + "-v2"); // 重新上传同包 → 目录被替换、注册仍有效(schema 更新语义) - service.upload(TestUsers.superAdmin(), v2); + service.upload(TestUsers.superAdmin(), v2, null, null); assertThat(registry.contains("t-zip-ov")).isTrue(); assertThat(Files.exists(themeDir.resolve("t-zip-ov").resolve("theme.json"))).isTrue(); } + // ===== C 级修复 + 任务四(2026-09-08 批次)===== + + @Test + @DisplayName("C修复: 兜底 wrapFailure 先记原始堆栈(ERROR+Throwable)再包装,文案保持既有格式") + void wrapFailureLogsOriginalStack() { + ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) + org.slf4j.LoggerFactory.getLogger(ThemePackageService.class); + ch.qos.logback.core.read.ListAppender appender = + new ch.qos.logback.core.read.ListAppender<>(); + appender.start(); + logger.addAppender(appender); + try { + RuntimeException original = new RuntimeException("boom-io"); + com.yunhai.platform.apistd.BizException wrapped = + ThemePackageService.wrapFailure("上传", "皮肤包处理失败:", original); + assertThat(wrapped.getMessage()).isEqualTo("皮肤包处理失败:boom-io"); + assertThat(appender.list).anySatisfy(event -> { + assertThat(event.getLevel()).isEqualTo(ch.qos.logback.classic.Level.ERROR); + assertThat(event.getThrowableProxy().getClassName()) + .isEqualTo(RuntimeException.class.getName()); + assertThat(event.getThrowableProxy().getMessage()).isEqualTo("boom-io"); + }); + } finally { + logger.detachAppender(appender); + } + } + + @Test + @DisplayName("任务四: 上传表单命名——落位前改写 theme.json,title/description 生效") + void uploadWithNaming() throws Exception { + ThemeInfo info = service.upload(TestUsers.superAdmin(), + zipOf("t-zip-name", "t-zip-name", true, true, true, true), "薄荷绿上传", "测试描述"); + assertThat(info.getTitle()).isEqualTo("薄荷绿上传"); + assertThat(info.getDescription()).isEqualTo("测试描述"); + String json = new String(Files.readAllBytes( + themeDir.resolve("t-zip-name").resolve("theme.json")), StandardCharsets.UTF_8); + assertThat(json).contains("薄荷绿上传"); + } + + @Test + @DisplayName("任务四: 添加皮肤包(派生+命名)——name/title/description 一次写入,不再 regex 误替换") + void deriveWithNaming() { + ThemeInfo info = service.derive(TestUsers.superAdmin(), "default", + "t-derive-name", "我的薄荷", "派生描述"); + assertThat(info.getName()).isEqualTo("t-derive-name"); + assertThat(info.getTitle()).isEqualTo("我的薄荷"); + assertThat(info.getDescription()).isEqualTo("派生描述"); + assertThat(registry.sourceOf("t-derive-name")).isEqualTo("external"); + } + + @Test + @DisplayName("任务四: 内置包改显示名——.meta 覆盖层生效、name 不变;带 params 拒绝;空值恢复默认") + void builtinMetaRenameAndReset() throws Exception { + String originalTitle = registry.get("default").getTitle(); + + // 改名:覆盖层落盘、registry 生效、来源仍 builtin + ThemeInfo renamed = service.updateMeta(TestUsers.superAdmin(), "default", "默认皮肤·薄荷", "平台内置", null); + assertThat(renamed.getTitle()).isEqualTo("默认皮肤·薄荷"); + assertThat(renamed.getDescription()).isEqualTo("平台内置"); + assertThat(Files.exists(themeDir.resolve(".meta").resolve("default.json"))).isTrue(); + assertThat(registry.sourceOf("default")).isEqualTo("builtin"); + + // 内置包带 params schema → 明确拒绝 + assertThatThrownBy(() -> service.updateMeta(TestUsers.superAdmin(), "default", null, null, + new ArrayList<>())) + .hasMessageContaining("参数 schema 不可修改"); + + // 恢复默认:title/description 全空 → 覆盖层删除、标题还原 + service.updateMeta(TestUsers.superAdmin(), "default", "", "", null); + assertThat(Files.exists(themeDir.resolve(".meta").resolve("default.json"))).isFalse(); + assertThat(registry.get("default").getTitle()).isEqualTo(originalTitle); + } + + @Test + @DisplayName("任务四: .meta 目录不干扰外部包扫描,reload 后 registry 完整") + void metaDirDoesNotDisturbScan() throws Exception { + int before = registry.list().size(); + Path metaDir = themeDir.resolve(".meta"); + Files.createDirectories(metaDir); + Files.write(metaDir.resolve("default.json"), + "{\"title\":\"覆盖名\"}".getBytes(StandardCharsets.UTF_8)); + registry.reload(); + assertThat(registry.list().size()).isEqualTo(before); + assertThat(registry.get("default").getTitle()).isEqualTo("覆盖名"); + } + /** JDK8 递归删除(测试临时目录专用) */ private static void deleteRecursively(Path path) { if (!Files.exists(path)) {