From 98d640fabaf80739aa4a6ae3e5993e5fa2d470a0 Mon Sep 17 00:00:00 2001 From: chenxing Date: Thu, 10 Sep 2026 11:10:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(portal):=20=E9=97=A8=E6=88=B7=E5=B7=A6?= =?UTF-8?q?=E4=BE=A7=E6=A0=8F=E7=9B=AE=E5=AF=BC=E8=88=AA+=E9=A6=96?= =?UTF-8?q?=E9=A1=B5=E8=A7=86=E8=A7=89=E5=8D=87=E7=BA=A7+=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E6=AD=A3=E5=90=8D=E6=89=B9=E6=AC=A1-PortalSideNav?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B+attachCategoryNavigation=E6=B3=A8=E5=85=A5do?= =?UTF-8?q?Category/doArticle=E4=B8=89=E4=BB=B6=E5=A5=97categoryPath/navTo?= =?UTF-8?q?pId/sideNav(=E7=AB=99=E7=82=B9=E5=BF=AB=E7=85=A7=E6=A0=91?= =?UTF-8?q?=E6=8E=A8=E5=AF=BC=E9=9B=B6=E6=96=B0=E5=A2=9E=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?,home/search=E4=B8=8D=E6=B3=A8=E5=85=A5);7=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E4=BE=A7=E6=A0=8F=E4=B8=A4=E6=A0=8Fmarkup+=E7=AA=84=E5=B1=8F?= =?UTF-8?q?=E6=A8=AA=E6=BB=9A+=E9=A1=B6=E5=AF=BC=E8=88=AA=E7=A5=96?= =?UTF-8?q?=E5=85=88=E9=AB=98=E4=BA=AE+=E4=BA=8C=E7=BA=A7=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E5=BD=93=E5=89=8D=E9=A1=B9=E9=AB=98=E4=BA=AE+?= =?UTF-8?q?=E9=9D=A2=E5=8C=85=E5=B1=91=E5=85=A8=E8=B7=AF=E5=BE=84;cms=5Fca?= =?UTF-8?q?tegory.home=5Fstyle=E5=85=A8=E9=93=BE=E8=B7=AF(news=5Fgrid=2016?= =?UTF-8?q?=E6=9D=A18x2=E6=97=A5=E6=9C=9F=E5=89=8D=E7=BC=80/media=5Ffocus?= =?UTF-8?q?=E5=BD=A2=E6=80=81)+PortalListData.Item=E8=A1=A5source=E6=9D=A5?= =?UTF-8?q?=E6=BA=90=E5=BE=BD=E6=A0=87=E5=9B=9B=E5=A4=84=E8=A3=85=E9=85=8D?= =?UTF-8?q?;Banner=E6=9C=BA=E5=88=B6gallery=E5=B1=95=E7=A4=BA=E4=BD=8D?= =?UTF-8?q?=E2=86=92galleryBanners=E5=9B=9B=E5=88=97=E5=A4=A7=E5=9B=BE?= =?UTF-8?q?=E8=BD=AE=E6=92=AD(=E8=87=AA=E7=94=B1=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=E9=9B=B6DDL);=E9=A1=B5=E8=84=9Aconfig=2012=E6=96=B0?= =?UTF-8?q?=E9=94=AE(footer=5Faddresses=E5=A4=9A=E6=A0=A1=E5=8C=BA/qr=5F*/?= =?UTF-8?q?social=5F*=E5=90=ABURL=E6=A0=A1=E9=AA=8C,footerAddressLines?= =?UTF-8?q?=E7=94=B1PortalSiteSnapshot.of=E6=8B=86=E8=A1=8C=E6=B4=BE?= =?UTF-8?q?=E7=94=9F);=E6=96=B0=E5=A2=9E=E5=B1=95=E7=A4=BA=E5=9E=8B?= =?UTF-8?q?=E7=9A=AE=E8=82=A4=E5=8C=85campus-glow;fresh-mint2=E6=AD=A3?= =?UTF-8?q?=E5=90=8Dfresh-mint(theme.json/=E5=A4=B4=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E5=90=8C=E6=AD=A5);AttachmentService=20scene=E7=99=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E5=8A=A0category(=E5=8D=95=E9=A1=B5=E5=AF=8C=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E8=B4=B4=E5=9B=BE=E5=85=8D=E6=B8=85=E5=AD=A4=E5=84=BF?= =?UTF-8?q?);=E6=96=B0=E5=A2=9EPortalSideNavIT=208+PortalHomeBlocksIT=203,?= =?UTF-8?q?CategoryServiceIT/SiteServiceIT/PortalSsrIT/ThemeContractIT?= =?UTF-8?q?=E7=AD=89=E5=8D=95=E7=B1=BB=E7=8B=AC=E8=B7=91=E5=85=A8=E7=BB=BF?= =?UTF-8?q?;=E9=99=84sql/clear=5Fcms=5Fdata.sql=E5=85=A8=E9=87=8F=E6=B8=85?= =?UTF-8?q?=E5=BA=93=E8=84=9A=E6=9C=AC(=E6=B8=85cms=5F*=E5=85=AD=E8=A1=A8?= =?UTF-8?q?=E7=95=99=E5=B9=B3=E5=8F=B0=E8=B4=A6=E5=8F=B7/=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E5=8C=85/=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6,?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=95=B0=E6=8D=AE=E9=87=8D=E8=B5=B0=E7=94=A8?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/clear_cms_data.sql | 41 ++ .../controller/api/AttachmentController.java | 2 +- .../controller/portal/PortalController.java | 5 + .../controller/portal/PortalPageRenderer.java | 77 +++- .../com/yunhai/cms/entity/CmsCategory.java | 7 + .../cms/model/dto/CategorySaveRequest.java | 4 + .../cms/model/portal/PortalCategory.java | 5 + .../cms/model/portal/PortalListData.java | 3 + .../cms/model/portal/PortalSideNav.java | 23 + .../cms/model/portal/PortalSiteSnapshot.java | 23 + .../yunhai/cms/model/vo/CategoryTreeNode.java | 4 + .../cms/service/impl/AttachmentService.java | 8 +- .../cms/service/impl/CategoryService.java | 9 + .../cms/service/impl/PortalQueryService.java | 2 + .../yunhai/cms/service/impl/SiteService.java | 10 +- .../themes/blue-white/assets/css/main.css | 26 ++ .../templates/themes/blue-white/detail.html | 20 +- .../themes/blue-white/fragments/header.html | 5 +- .../templates/themes/blue-white/list.html | 22 +- .../templates/themes/blue-white/single.html | 22 +- .../templates/themes/campus-glow/README.md | 30 ++ .../themes/campus-glow/assets/css/main.css | 402 ++++++++++++++++++ .../themes/campus-glow/assets/favicon.png | Bin 0 -> 936 bytes .../themes/campus-glow/assets/js/main.js | 117 +++++ .../themes/campus-glow/assets/screenshot.png | Bin 0 -> 127326 bytes .../templates/themes/campus-glow/detail.html | 76 ++++ .../themes/campus-glow/fragments/footer.html | 99 +++++ .../themes/campus-glow/fragments/head.html | 20 + .../themes/campus-glow/fragments/header.html | 61 +++ .../templates/themes/campus-glow/home.html | 176 ++++++++ .../templates/themes/campus-glow/list.html | 107 +++++ .../templates/themes/campus-glow/single.html | 50 +++ .../templates/themes/campus-glow/theme.json | 27 ++ .../themes/classic-red/assets/css/main.css | 26 ++ .../templates/themes/classic-red/detail.html | 20 +- .../themes/classic-red/fragments/header.html | 5 +- .../templates/themes/classic-red/list.html | 22 +- .../templates/themes/classic-red/single.html | 22 +- .../themes/default/assets/css/main.css | 48 +++ .../templates/themes/default/detail.html | 34 +- .../themes/default/fragments/header.html | 7 +- .../templates/themes/default/list.html | 133 +++--- .../templates/themes/default/single.html | 51 ++- .../{fresh-mint2 => fresh-mint}/README.md | 4 +- .../assets/css/main.css | 28 +- .../assets/favicon.png | Bin .../assets/favicon.svg | 0 .../assets/js/main.js | 2 +- .../assets/screenshot.png | Bin .../{fresh-mint2 => fresh-mint}/detail.html | 20 +- .../fragments/footer.html | 0 .../fragments/head.html | 0 .../fragments/header.html | 5 +- .../{fresh-mint2 => fresh-mint}/home.html | 0 .../{fresh-mint2 => fresh-mint}/list.html | 22 +- .../templates/themes/fresh-mint/single.html | 41 ++ .../{fresh-mint2 => fresh-mint}/theme.json | 4 +- .../templates/themes/fresh-mint2/single.html | 23 - .../themes/mint-breeze/assets/css/main.css | 26 ++ .../templates/themes/mint-breeze/detail.html | 20 +- .../themes/mint-breeze/fragments/header.html | 5 +- .../templates/themes/mint-breeze/list.html | 22 +- .../templates/themes/mint-breeze/single.html | 22 +- .../themes/scholar-blue/assets/css/main.css | 54 +++ .../templates/themes/scholar-blue/detail.html | 24 +- .../themes/scholar-blue/fragments/header.html | 5 +- .../templates/themes/scholar-blue/list.html | 31 +- .../templates/themes/scholar-blue/single.html | 25 +- .../themes/warm-kinder/assets/css/main.css | 26 ++ .../templates/themes/warm-kinder/detail.html | 20 +- .../themes/warm-kinder/fragments/header.html | 5 +- .../templates/themes/warm-kinder/list.html | 22 +- .../templates/themes/warm-kinder/single.html | 22 +- .../yunhai/cms/portal/PortalHomeBlocksIT.java | 166 ++++++++ .../yunhai/cms/portal/PortalSideNavIT.java | 176 ++++++++ .../cms/service/impl/CategoryServiceIT.java | 34 ++ .../cms/service/impl/SiteServiceIT.java | 34 ++ 77 files changed, 2564 insertions(+), 175 deletions(-) create mode 100644 sql/clear_cms_data.sql create mode 100644 src/main/java/com/yunhai/cms/model/portal/PortalSideNav.java create mode 100644 src/main/resources/templates/themes/campus-glow/README.md create mode 100644 src/main/resources/templates/themes/campus-glow/assets/css/main.css create mode 100644 src/main/resources/templates/themes/campus-glow/assets/favicon.png create mode 100644 src/main/resources/templates/themes/campus-glow/assets/js/main.js create mode 100644 src/main/resources/templates/themes/campus-glow/assets/screenshot.png create mode 100644 src/main/resources/templates/themes/campus-glow/detail.html create mode 100644 src/main/resources/templates/themes/campus-glow/fragments/footer.html create mode 100644 src/main/resources/templates/themes/campus-glow/fragments/head.html create mode 100644 src/main/resources/templates/themes/campus-glow/fragments/header.html create mode 100644 src/main/resources/templates/themes/campus-glow/home.html create mode 100644 src/main/resources/templates/themes/campus-glow/list.html create mode 100644 src/main/resources/templates/themes/campus-glow/single.html create mode 100644 src/main/resources/templates/themes/campus-glow/theme.json rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/README.md (90%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/assets/css/main.css (91%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/assets/favicon.png (100%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/assets/favicon.svg (100%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/assets/js/main.js (89%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/assets/screenshot.png (100%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/detail.html (63%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/fragments/footer.html (100%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/fragments/head.html (100%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/fragments/header.html (91%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/home.html (100%) rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/list.html (72%) create mode 100644 src/main/resources/templates/themes/fresh-mint/single.html rename src/main/resources/templates/themes/{fresh-mint2 => fresh-mint}/theme.json (96%) delete mode 100644 src/main/resources/templates/themes/fresh-mint2/single.html create mode 100644 src/test/java/com/yunhai/cms/portal/PortalHomeBlocksIT.java create mode 100644 src/test/java/com/yunhai/cms/portal/PortalSideNavIT.java diff --git a/sql/clear_cms_data.sql b/sql/clear_cms_data.sql new file mode 100644 index 0000000..f38350f --- /dev/null +++ b/sql/clear_cms_data.sql @@ -0,0 +1,41 @@ +-- ============================================================ +-- bsch_cms 全量清库脚本(清测试数据,全流程重走) +-- 目标库:jdbc:mysql://localhost:3306/bsch_cms(root) +-- 范围:cms_* 六表全空(站点/栏目/文章/轮播/友链/附件),AUTO_INCREMENT 归位 +-- 不动的东西: +-- * 平台侧账号(cmssuper/cmsschool 等在 userbase 平台库,不在本库) +-- * 主题包(${user.dir}/data/themes,含 .meta 覆盖层;要重置主题请手工清该目录) +-- * 上传的物理文件(D:/bsch/cms/upload/,见文件尾可选清理说明) +-- 表结构由 Hibernate ddl-auto=update 维护,本脚本只清数据不删表。 +-- 重灌演示数据:执行同目录 cms_seed_local.sql(其前置即"六表为空",本脚本正好满足)。 +-- ============================================================ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +TRUNCATE TABLE cms_attachment; +TRUNCATE TABLE cms_banner; +TRUNCATE TABLE cms_link; +TRUNCATE TABLE cms_article; +TRUNCATE TABLE cms_category; +TRUNCATE TABLE cms_site; + +SET FOREIGN_KEY_CHECKS = 1; + +-- 验证:六表应全为 0 +SELECT 'cms_site' AS tbl, COUNT(*) AS remain FROM cms_site +UNION ALL SELECT 'cms_category', COUNT(*) FROM cms_category +UNION ALL SELECT 'cms_article', COUNT(*) FROM cms_article +UNION ALL SELECT 'cms_banner', COUNT(*) FROM cms_banner +UNION ALL SELECT 'cms_link', COUNT(*) FROM cms_link +UNION ALL SELECT 'cms_attachment', COUNT(*) FROM cms_attachment; + +-- ------------------------------------------------------------ +-- 可选(不在 SQL 内):清掉测试期上传的物理文件,只留种子静态资产。 +-- 保留:D:/bsch/cms/upload/seed/ (cms_seed_local.sql 的配图依赖它) +-- 删除:seed/ 之外的目录与散文件(测试期真实上传的图片/PDF 均为孤儿) +-- Git Bash 下参考: +-- find /d/bsch/cms/upload -mindepth 1 -maxdepth 1 ! -name seed -exec rm -rf {} + +-- 主题包如需一并重置(连派生皮肤一起清掉,回到内置包): +-- rm -rf <服务运行目录>/data/themes (重启服务后仅剩 classpath 内置包) +-- ------------------------------------------------------------ diff --git a/src/main/java/com/yunhai/cms/controller/api/AttachmentController.java b/src/main/java/com/yunhai/cms/controller/api/AttachmentController.java index faa5a3a..9fd070d 100644 --- a/src/main/java/com/yunhai/cms/controller/api/AttachmentController.java +++ b/src/main/java/com/yunhai/cms/controller/api/AttachmentController.java @@ -26,7 +26,7 @@ public class AttachmentController { private final AttachmentService attachmentService; - /** 上传(multipart:file 必填;articleId 可空;scene 可空 banner/link/site,B2 初步标记) */ + /** 上传(multipart:file 必填;articleId 可空;scene 可空 banner/link/site/category(category=单页栏目贴图),B2 初步标记) */ @PostMapping("/upload") public AttachmentVo upload(BoaUser authUserWithDept, @RequestParam("siteId") Integer siteId, diff --git a/src/main/java/com/yunhai/cms/controller/portal/PortalController.java b/src/main/java/com/yunhai/cms/controller/portal/PortalController.java index bde72a3..fe4e506 100644 --- a/src/main/java/com/yunhai/cms/controller/portal/PortalController.java +++ b/src/main/java/com/yunhai/cms/controller/portal/PortalController.java @@ -133,6 +133,8 @@ public class PortalController { model.addAttribute("navCategories", site.getCategories()); model.addAttribute("currentCategoryId", categoryId); model.addAttribute("category", category); + // 栏目导航三件套(docs/11):面包屑全路径 + 顶导航祖先高亮 + 左侧兄弟侧栏 + pageRenderer.attachCategoryNavigation(site, categoryId, model); model.addAttribute("pageTitle", seoTitle(category, site)); model.addAttribute("seoDescription", category.getSeoDescription()); model.addAttribute("canonical", url.category(categoryId)); @@ -198,6 +200,8 @@ public class PortalController { // 面包屑栏目名(视觉精修 D2-2):按栏目树查名,查不到(栏目已删)退"正文" PortalCategory detailCategory = findCategory(site, detail.getCategoryId()); model.addAttribute("currentCategoryName", detailCategory == null ? "正文" : detailCategory.getName()); + // 栏目导航三件套(docs/11):详情页同栏目页规则;栏目已删时 path 空,模板按缺省形态渲染 + pageRenderer.attachCategoryNavigation(site, detail.getCategoryId(), model); // 正文上传地址渲染期补基准(B-1):详情来自 cms:article 缓存(快照跨两种暴露形态共享), // 拷贝后重写,避免把某一形态的绝对 URL 写回缓存 model.addAttribute("article", detail.withContent( @@ -356,6 +360,7 @@ public class PortalController { item.setTitle(a.getTitle()); item.setSummary(a.getSummary()); item.setCoverImage(a.getCoverImage()); + item.setSource(a.getSource()); item.setPublishTime(a.getPublishTime()); item.setTopmost(a.getTopmost()); return item; diff --git a/src/main/java/com/yunhai/cms/controller/portal/PortalPageRenderer.java b/src/main/java/com/yunhai/cms/controller/portal/PortalPageRenderer.java index febb1f9..2f2bcdf 100644 --- a/src/main/java/com/yunhai/cms/controller/portal/PortalPageRenderer.java +++ b/src/main/java/com/yunhai/cms/controller/portal/PortalPageRenderer.java @@ -5,6 +5,7 @@ import com.yunhai.cms.enums.LinkTypeEnum; import com.yunhai.cms.model.portal.PortalBanner; import com.yunhai.cms.model.portal.PortalCategory; import com.yunhai.cms.model.portal.PortalListData; +import com.yunhai.cms.model.portal.PortalSideNav; import com.yunhai.cms.model.portal.PortalSiteSnapshot; import com.yunhai.cms.repository.CmsArticleRepository; import com.yunhai.cms.utils.PortalUrlHelper; @@ -28,8 +29,9 @@ import java.util.Map; @RequiredArgsConstructor public class PortalPageRenderer { - /** 首页每个一级栏目取 N 条 */ + /** 首页每个一级栏目取 N 条(docs/12:双栏新闻卡形态需 16 条铺满 8 行 × 2 列) */ private static final int HOME_BLOCK_SIZE = 8; + private static final int HOME_BLOCK_GRID_SIZE = 16; /** 首页置顶区取 N 条 */ private static final int HOME_TOP_SIZE = 6; @@ -46,6 +48,7 @@ public class PortalPageRenderer { model.addAttribute("navCategories", site.getCategories()); List> banners = activeBanners(site, "home", url); model.addAttribute("banners", banners); + model.addAttribute("galleryBanners", activeBanners(site, "gallery", url)); model.addAttribute("topArticles", topArticles(site.getId())); model.addAttribute("focusArticles", focusArticles(site.getId())); model.addAttribute("categoryBlocks", categoryBlocks(site.getId(), site.getCategories(), url)); @@ -63,6 +66,71 @@ public class PortalPageRenderer { // ===== 内部装配(自 PortalController 原样迁移)===== + /** + * 栏目导航装配(docs/11):栏目页/详情页共用,三条模型属性—— + * categoryPath(顶级→当前路径,面包屑全路径)、navTopId(顶导航祖先高亮)、 + * sideNav(模块有二级子栏目时的左侧兄弟导航,无则不挂,模板 th:if 兜底)。 + * 全部基于站点快照树推导,零新增查询;搜索/首页等无栏目上下文的路由不调用。 + */ + public void attachCategoryNavigation(PortalSiteSnapshot site, Integer currentCategoryId, Model model) { + if (currentCategoryId == null) { + return; + } + List path = pathFromRoot(site.getCategories(), currentCategoryId); + model.addAttribute("categoryPath", path); + if (path.isEmpty()) { + return; // 栏目已删等兜底(详情页可能挂已删栏目文章),模板按缺省形态渲染 + } + PortalCategory top = path.get(0); + model.addAttribute("navTopId", top.getId()); + if (!top.getChildren().isEmpty()) { + PortalSideNav nav = new PortalSideNav(); + nav.setTop(top); + nav.setItems(top.getChildren()); + nav.setActiveId(resolveActiveId(path, currentCategoryId)); + model.addAttribute("sideNav", nav); + } + } + + /** + * 侧栏高亮位:当前为二级=当前栏目;一级直点=null(侧栏头即模块名,无高亮项); + * 三级及更深=所在二级分支(决策点②:固定列二级层,高亮当前所属分支)。 + */ + private Integer resolveActiveId(List path, Integer currentCategoryId) { + if (path.size() == 2) { + return currentCategoryId; + } + return path.size() > 2 ? path.get(1).getId() : null; + } + + /** 自栏目树求 根→目标 路径(含两端);目标不存在返回空表 */ + private List pathFromRoot(List roots, Integer categoryId) { + for (PortalCategory root : roots) { + List hit = pathFromRootDeep(root, categoryId); + if (hit != null) { + return hit; + } + } + return new ArrayList<>(); + } + + private List pathFromRootDeep(PortalCategory node, Integer categoryId) { + if (node.getId().equals(categoryId)) { + List path = new ArrayList<>(); + path.add(node); + return path; + } + for (PortalCategory child : node.getChildren()) { + List sub = pathFromRootDeep(child, categoryId); + if (sub != null) { + sub.add(0, node); + return sub; + } + } + return null; + } + + /** 启用中且在时间窗内的指定位轮播,并解析为完整跳转 URL */ private List> activeBanners(PortalSiteSnapshot site, String position, PortalUrlHelper url) { @@ -137,6 +205,7 @@ public class PortalPageRenderer { /** * 首页一级栏目区块:每栏目精确取 top N(列表型栏目才出区块)。 * D1-2:区块聚合列表型子孙栏目文章(文章通常挂叶子栏目,父栏目区块必须显示汇总)。 + * docs/12:区块带出栏目 homeStyle,双栏新闻卡(news_grid)取 16 条铺满双栏,其余取 8 条。 */ private List> categoryBlocks(Integer siteId, List categories, PortalUrlHelper url) { @@ -145,12 +214,14 @@ public class PortalPageRenderer { if (category.getType() == null || category.getType() != CategoryTypeEnum.LIST.getCode()) { continue; } + int blockSize = "news_grid".equals(category.getHomeStyle()) + ? HOME_BLOCK_GRID_SIZE : HOME_BLOCK_SIZE; List categoryIds = new ArrayList<>(); collectListDescendantIds(category, categoryIds); List articles = articleRepository .findBySiteIdAndCategoryIdInAndStatus(siteId, categoryIds, com.yunhai.cms.enums.ArticleStatusEnum.PUBLISHED.getCode(), - PageRequest.of(0, HOME_BLOCK_SIZE, + PageRequest.of(0, blockSize, Sort.by(Sort.Order.desc("topmost"), Sort.Order.desc("publishTime")))) .getContent(); List items = new ArrayList<>(); @@ -159,6 +230,7 @@ public class PortalPageRenderer { } Map block = new HashMap<>(); block.put("category", category); + block.put("homeStyle", category.getHomeStyle()); block.put("items", items); block.put("moreUrl", url.category(category.getId())); blocks.add(block); @@ -182,6 +254,7 @@ public class PortalPageRenderer { item.setTitle(a.getTitle()); item.setSummary(a.getSummary()); item.setCoverImage(a.getCoverImage()); + item.setSource(a.getSource()); item.setPublishTime(a.getPublishTime()); item.setTopmost(a.getTopmost()); return item; diff --git a/src/main/java/com/yunhai/cms/entity/CmsCategory.java b/src/main/java/com/yunhai/cms/entity/CmsCategory.java index 3eaa83c..5c43c47 100644 --- a/src/main/java/com/yunhai/cms/entity/CmsCategory.java +++ b/src/main/java/com/yunhai/cms/entity/CmsCategory.java @@ -42,6 +42,13 @@ public class CmsCategory extends BaseEntity { @Column(name = "template", length = 64) private String template; + /** + * 首页展示形态(docs/11,空=主题默认卡):news_grid=双栏新闻卡、media_focus=来源标签列表。 + * 仅一级列表型栏目在首页生效;旧主题不消费该字段(仅条数外溢 16)。 + */ + @Column(name = "home_style", length = 32) + private String homeStyle; + /** * 单页栏目内容(type=2 时门户直接渲染;经 jsoup 白名单清洗入库)。 * 注:方案 §8.1 要求单页直接渲染 content,但 §5.1 DDL 漏列,此处补齐(ddl-auto=update 自动加列)。 diff --git a/src/main/java/com/yunhai/cms/model/dto/CategorySaveRequest.java b/src/main/java/com/yunhai/cms/model/dto/CategorySaveRequest.java index 96c644c..b3d1390 100644 --- a/src/main/java/com/yunhai/cms/model/dto/CategorySaveRequest.java +++ b/src/main/java/com/yunhai/cms/model/dto/CategorySaveRequest.java @@ -38,6 +38,10 @@ public class CategorySaveRequest { @Size(max = 64) private String template; + /** 首页展示形态(docs/11:空=默认卡 / news_grid / media_focus,服务端白名单校验) */ + @Size(max = 32) + private String homeStyle; + /** 单页栏目内容(type=2 有效,服务端 jsoup 清洗后入库) */ private String content; diff --git a/src/main/java/com/yunhai/cms/model/portal/PortalCategory.java b/src/main/java/com/yunhai/cms/model/portal/PortalCategory.java index bc112f0..0f95183 100644 --- a/src/main/java/com/yunhai/cms/model/portal/PortalCategory.java +++ b/src/main/java/com/yunhai/cms/model/portal/PortalCategory.java @@ -25,6 +25,9 @@ public class PortalCategory { private String template; + /** 首页展示形态(docs/11:空=默认卡 / news_grid / media_focus) */ + private String homeStyle; + private String seoTitle; private String seoDescription; @@ -42,6 +45,7 @@ public class PortalCategory { node.setType(c.getType()); node.setLinkUrl(c.getLinkUrl()); node.setTemplate(c.getTemplate()); + node.setHomeStyle(c.getHomeStyle()); node.setSeoTitle(c.getSeoTitle()); node.setSeoDescription(c.getSeoDescription()); node.setContent(c.getContent()); @@ -60,6 +64,7 @@ public class PortalCategory { copy.type = type; copy.linkUrl = linkUrl; copy.template = template; + copy.homeStyle = homeStyle; copy.seoTitle = seoTitle; copy.seoDescription = seoDescription; copy.content = content; diff --git a/src/main/java/com/yunhai/cms/model/portal/PortalListData.java b/src/main/java/com/yunhai/cms/model/portal/PortalListData.java index 298b059..ea22ad5 100644 --- a/src/main/java/com/yunhai/cms/model/portal/PortalListData.java +++ b/src/main/java/com/yunhai/cms/model/portal/PortalListData.java @@ -37,6 +37,9 @@ public class PortalListData { private String coverImage; + /** 来源(转载出处,docs/11:媒体聚焦来源徽标用;可空) */ + private String source; + private LocalDateTime publishTime; private Integer topmost; diff --git a/src/main/java/com/yunhai/cms/model/portal/PortalSideNav.java b/src/main/java/com/yunhai/cms/model/portal/PortalSideNav.java new file mode 100644 index 0000000..23aca63 --- /dev/null +++ b/src/main/java/com/yunhai/cms/model/portal/PortalSideNav.java @@ -0,0 +1,23 @@ +package com.yunhai.cms.model.portal; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 门户左侧栏目导航(docs/11):模块存在二级子栏目时,栏目页/详情页左侧 + * 展示"模块名 + 全部二级子栏目",当前位高亮。纯展示模型,由站点快照树推导。 + */ +@Data +public class PortalSideNav { + + /** 顶级模块(侧栏头) */ + private PortalCategory top; + + /** 全部二级子栏目(快照序,列表/单页/外链型均列出) */ + private List items = new ArrayList<>(); + + /** 当前高亮栏目 id:当前为二级时=当前栏目;三级及更深时=所在二级分支;一级直点=null */ + private Integer activeId; +} diff --git a/src/main/java/com/yunhai/cms/model/portal/PortalSiteSnapshot.java b/src/main/java/com/yunhai/cms/model/portal/PortalSiteSnapshot.java index 08ff4aa..1bdb2e6 100644 --- a/src/main/java/com/yunhai/cms/model/portal/PortalSiteSnapshot.java +++ b/src/main/java/com/yunhai/cms/model/portal/PortalSiteSnapshot.java @@ -63,6 +63,12 @@ public class PortalSiteSnapshot { /** 站点级自定义样式(B3:config_json.custom_css,保存时经服务端黑名单校验,存量直读) */ private String customCss = ""; + /** + * 页脚多行地址(docs/11:config_json.footer_addresses 按 \r?\n 拆分去空行,装配期派生随快照缓存, + * 规避模板内拆分的表达式平台差异)。 + */ + private List footerAddressLines = new ArrayList<>(); + public static PortalSiteSnapshot of(CmsSite site) { PortalSiteSnapshot snapshot = new PortalSiteSnapshot(); snapshot.setId(site.getId()); @@ -78,9 +84,25 @@ public class PortalSiteSnapshot { snapshot.setDomain(ExtJsonUtils.getString(config, "domain")); snapshot.setPublicBaseUrl(ExtJsonUtils.getString(config, "public_base_url")); snapshot.setCustomCss(ExtJsonUtils.getString(config, "custom_css")); + snapshot.setFooterAddressLines(splitAddressLines(ExtJsonUtils.getString(config, "footer_addresses"))); return snapshot; } + /** footer_addresses 多行文本拆行(\r?\n 分隔,去空行;空值返回空列表) */ + private static List splitAddressLines(String raw) { + List lines = new ArrayList<>(); + if (raw == null || raw.trim().isEmpty()) { + return lines; + } + for (String line : raw.split("\r?\n")) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + lines.add(trimmed); + } + } + return lines; + } + /** * 预览用浅拷贝(方案 §5.5):集合字段共享只读引用;覆盖 theme/themeParams/themeVarsCss * 的操作由预览方在拷贝上进行,不影响缓存中的原快照。 @@ -104,6 +126,7 @@ public class PortalSiteSnapshot { copy.setThemeParams(themeParams); copy.setThemeVarsCss(themeVarsCss); copy.setCustomCss(customCss); + copy.setFooterAddressLines(footerAddressLines); return copy; } } diff --git a/src/main/java/com/yunhai/cms/model/vo/CategoryTreeNode.java b/src/main/java/com/yunhai/cms/model/vo/CategoryTreeNode.java index 357d656..14ece75 100644 --- a/src/main/java/com/yunhai/cms/model/vo/CategoryTreeNode.java +++ b/src/main/java/com/yunhai/cms/model/vo/CategoryTreeNode.java @@ -27,6 +27,9 @@ public class CategoryTreeNode { private String template; + /** 首页展示形态(docs/11:空=默认卡 / news_grid / media_focus) */ + private String homeStyle; + /** 单页栏目内容(type=2 时编辑/渲染用) */ private String content; @@ -50,6 +53,7 @@ public class CategoryTreeNode { node.setType(c.getType()); node.setLinkUrl(c.getLinkUrl()); node.setTemplate(c.getTemplate()); + node.setHomeStyle(c.getHomeStyle()); node.setContent(c.getContent()); node.setSortNo(c.getSortNo()); node.setSeoTitle(c.getSeoTitle()); diff --git a/src/main/java/com/yunhai/cms/service/impl/AttachmentService.java b/src/main/java/com/yunhai/cms/service/impl/AttachmentService.java index 3ebbda2..2413f6f 100644 --- a/src/main/java/com/yunhai/cms/service/impl/AttachmentService.java +++ b/src/main/java/com/yunhai/cms/service/impl/AttachmentService.java @@ -56,8 +56,8 @@ public class AttachmentService { private static final List IMAGE_EXTS = Arrays.asList( "jpg", "jpeg", "png", "gif", "webp", "bmp", "ico"); - /** 上传 scene 白名单(B2 附件引用模型):banner=轮播 / link=友链 / site=站点配置 */ - private static final List ALLOWED_SCENES = Arrays.asList("banner", "link", "site"); + /** 上传 scene 白名单(B2 附件引用模型):banner=轮播 / link=友链 / site=站点配置 / category=单页栏目贴图 */ + private static final List ALLOWED_SCENES = Arrays.asList("banner", "link", "site", "category"); private final CmsAttachmentRepository attachmentRepository; private final SiteService siteService; @@ -69,12 +69,12 @@ public class AttachmentService { @Value("${FILE_WEB_PATH:/cms/upload/}") private String fileWebPath; - /** 上传(编辑器图片/附件/轮播图/Logo 复用);articleId 可空;scene 可空(banner/link/site,B2 初步标记) */ + /** 上传(编辑器图片/附件/轮播图/Logo 复用);articleId 可空;scene 可空(banner/link/site/category,B2 初步标记) */ @Transactional public AttachmentVo upload(BoaUser user, Integer siteId, Integer articleId, String scene, MultipartFile file) { CmsSite site = siteService.requireManageable(user, siteId); if (StringUtils.hasText(scene) && !ALLOWED_SCENES.contains(scene)) { - throw new BizException(ErrorCode.PARAM_ERROR, "scene 非法:" + scene + "(仅 banner/link/site)"); + throw new BizException(ErrorCode.PARAM_ERROR, "scene 非法:" + scene + "(仅 banner/link/site/category)"); } if (file == null || file.isEmpty()) { throw new BizException(ErrorCode.PARAM_ERROR, "上传文件不能为空"); diff --git a/src/main/java/com/yunhai/cms/service/impl/CategoryService.java b/src/main/java/com/yunhai/cms/service/impl/CategoryService.java index c91704a..61432bd 100644 --- a/src/main/java/com/yunhai/cms/service/impl/CategoryService.java +++ b/src/main/java/com/yunhai/cms/service/impl/CategoryService.java @@ -33,6 +33,10 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class CategoryService { + /** 首页展示形态白名单(docs/11;空=主题默认卡) */ + private static final java.util.Set HOME_STYLE_VALUES = + new java.util.HashSet<>(java.util.Arrays.asList("news_grid", "media_focus")); + private final CmsCategoryRepository categoryRepository; private final CmsArticleRepository articleRepository; private final com.yunhai.cms.repository.CmsBannerRepository bannerRepository; @@ -70,6 +74,10 @@ public class CategoryService { if (type == CategoryTypeEnum.LINK && !StringUtils.hasText(req.getLinkUrl())) { throw new BizException(ErrorCode.PARAM_ERROR, "外链栏目必须填写外链地址"); } + if (StringUtils.hasText(req.getHomeStyle()) && !HOME_STYLE_VALUES.contains(req.getHomeStyle())) { + throw new BizException(ErrorCode.PARAM_ERROR, + "首页展示形态非法:" + req.getHomeStyle() + "(仅支持 news_grid / media_focus)"); + } CmsCategory category; if (req.getId() == null) { category = new CmsCategory(); @@ -87,6 +95,7 @@ public class CategoryService { category.setType(type.getCode()); category.setLinkUrl(req.getLinkUrl()); category.setTemplate(req.getTemplate()); + category.setHomeStyle(StringUtils.hasText(req.getHomeStyle()) ? req.getHomeStyle() : null); category.setContent(HtmlSanitizer.clean(req.getContent())); category.setSortNo(req.getSortNo() == null ? 0 : req.getSortNo()); category.setSeoTitle(req.getSeoTitle()); 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 1356688..f87a73e 100644 --- a/src/main/java/com/yunhai/cms/service/impl/PortalQueryService.java +++ b/src/main/java/com/yunhai/cms/service/impl/PortalQueryService.java @@ -194,6 +194,7 @@ public class PortalQueryService { item.setTitle(a.getTitle()); item.setSummary(a.getSummary()); item.setCoverImage(a.getCoverImage()); + item.setSource(a.getSource()); item.setPublishTime(a.getPublishTime()); item.setTopmost(a.getTopmost()); data.getItems().add(item); @@ -246,6 +247,7 @@ public class PortalQueryService { item.setTitle(a.getTitle()); item.setSummary(a.getSummary()); item.setCoverImage(a.getCoverImage()); + item.setSource(a.getSource()); item.setPublishTime(a.getPublishTime()); item.setTopmost(a.getTopmost()); return item; diff --git a/src/main/java/com/yunhai/cms/service/impl/SiteService.java b/src/main/java/com/yunhai/cms/service/impl/SiteService.java index ec76874..41bc697 100644 --- a/src/main/java/com/yunhai/cms/service/impl/SiteService.java +++ b/src/main/java/com/yunhai/cms/service/impl/SiteService.java @@ -287,7 +287,8 @@ public class SiteService { /** * config_json 校验(docs/09 §3.1,替代旧「空 Map 且长度>2」判定): * ① 必须是 JSON 对象(修复 A-09:带空白空对象误判);② 键名仅字母/数字/下划线; - * ③ 已知键按类型校验(domain 域名 / public_base_url 完整 URL / logo、favicon 资产路径)。 + * ③ 已知键按类型校验(domain 域名 / public_base_url、social_* 完整 URL / + * logo、favicon、qr_wechat、qr_weibo、news_bg_image 资产路径,docs/12)。 * 系统托管键 theme_params 直通;非字符串值(wechat 等预留对象)不校验原样放行—— * 只拦新脏数据,不做严格 schema 拒绝未知键(D6-2)。 */ @@ -337,6 +338,10 @@ public class SiteService { } break; case "public_base_url": + case "social_wechat": + case "social_weibo": + case "social_douyin": + case "social_bilibili": if (!URL_PATTERN.matcher(text).matches()) { throw new BizException(ErrorCode.PARAM_ERROR, "对外基地址必须是完整的 http(s):// 地址:" + text); @@ -344,6 +349,9 @@ public class SiteService { break; case "logo": case "favicon": + case "qr_wechat": + case "qr_weibo": + case "news_bg_image": if (!ASSET_PATTERN.matcher(text).matches()) { throw new BizException(ErrorCode.PARAM_ERROR, key + " 必须是站点上传路径(/ 开头)或 http(s) 地址"); diff --git a/src/main/resources/templates/themes/blue-white/assets/css/main.css b/src/main/resources/templates/themes/blue-white/assets/css/main.css index 7ddd4bd..577dec3 100644 --- a/src/main/resources/templates/themes/blue-white/assets/css/main.css +++ b/src/main/resources/templates/themes/blue-white/assets/css/main.css @@ -342,3 +342,29 @@ time, .item-meta { color: var(--text-light); font-size: 13px; } margin-right: 4px; border-radius: 2px; } + + +/* ===== 左侧栏目导航(docs/11):有二级节点的模块,栏目/文章页左侧展示兄弟目录 ===== */ +.list-main.has-sidenav, .detail-main.has-sidenav { display: flex; align-items: flex-start; gap: 24px; } +.has-sidenav .list-body { flex: 1; min-width: 0; } +.side-nav { flex: none; width: 220px; background: var(--card-bg, #fff); border: 1px solid var(--border); overflow: hidden; } +.side-nav-head { background: var(--primary); color: #fff; font-size: 20px; font-weight: 700; text-align: center; padding: 18px 12px; letter-spacing: 2px; } +.side-nav-list { list-style: none; margin: 0; padding: 0; } +.side-nav-item + .side-nav-item { border-top: 1px dashed var(--border); } +.side-nav-item a { display: block; padding: 12px 16px; color: var(--text); text-decoration: none; font-size: 15px; transition: background .15s ease, color .15s ease; } +.side-nav-item a:hover { color: var(--primary); background: var(--bg); } +.side-nav-item.active a { background: var(--primary); color: #fff; font-weight: 600; text-decoration: none; } +/* 顶导航二级下拉当前项高亮(docs/11 顺带) */ +.site-nav ul.sub-nav li.active a { color: var(--primary); } +@media (max-width: 768px) { + .list-main.has-sidenav, .detail-main.has-sidenav { flex-direction: column; gap: 12px; } + .side-nav { width: 100%; } + .side-nav-list { display: flex; overflow-x: auto; -webkit-overflow-scrolling: touch; } + .side-nav-item { flex: none; } + .side-nav-item + .side-nav-item { border-top: none; border-left: 1px dashed var(--border); } + .side-nav-item a { white-space: nowrap; } +} +@media print { + .side-nav { display: none; } + .list-main.has-sidenav, .detail-main.has-sidenav { display: block; } +} diff --git a/src/main/resources/templates/themes/blue-white/detail.html b/src/main/resources/templates/themes/blue-white/detail.html index 2e04d6a..8eab71f 100644 --- a/src/main/resources/templates/themes/blue-white/detail.html +++ b/src/main/resources/templates/themes/blue-white/detail.html @@ -4,10 +4,25 @@
-
+
+ + +
@@ -35,6 +50,7 @@ th:href="${url.article(nextArticle.id)}" th:text="'下一篇:' + ${nextArticle.title}">下一篇 +
diff --git a/src/main/resources/templates/themes/blue-white/fragments/header.html b/src/main/resources/templates/themes/blue-white/fragments/header.html index 0f43a5b..5395271 100644 --- a/src/main/resources/templates/themes/blue-white/fragments/header.html +++ b/src/main/resources/templates/themes/blue-white/fragments/header.html @@ -33,14 +33,15 @@ 首页
  • + th:classappend="${((currentCategoryId == cat.id or navTopId == cat.id) ? 'active' : '') + (!cat.children.isEmpty() ? ' has-sub' : '')}"> 栏目 栏目
  • diff --git a/src/main/resources/templates/themes/default/single.html b/src/main/resources/templates/themes/default/single.html index d1e3003..4f92c3c 100644 --- a/src/main/resources/templates/themes/default/single.html +++ b/src/main/resources/templates/themes/default/single.html @@ -5,23 +5,42 @@
    -
    - +
    + + -
    -

    栏目名

    - -
    单页内容
    -
    -

    页面暂无内容

    -

    内容编辑后将在此展示

    -
    -
    +
    + + +
    +

    栏目名

    + +
    单页内容
    +
    +

    页面暂无内容

    +

    内容编辑后将在此展示

    +
    +
    +
    diff --git a/src/main/resources/templates/themes/fresh-mint2/README.md b/src/main/resources/templates/themes/fresh-mint/README.md similarity index 90% rename from src/main/resources/templates/themes/fresh-mint2/README.md rename to src/main/resources/templates/themes/fresh-mint/README.md index aee78a9..cc13715 100644 --- a/src/main/resources/templates/themes/fresh-mint2/README.md +++ b/src/main/resources/templates/themes/fresh-mint/README.md @@ -1,4 +1,4 @@ -# fresh-mint2 皮肤包 +# fresh-mint 皮肤包 由 blue-white 派生的清新薄荷风皮肤(**卡片封面版**,docs/10 契约 §5 工作流)。**升级随版本发布,不在站点间复制目录**。 @@ -12,7 +12,7 @@ | 页面 | 属性 | | --- | --- | -| 全部 | `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{}` 覆盖串,无参数为空) | +| 全部 | `site`(快照:config/分类树/轮播/友链组/院系子站)、`url`(PortalUrlHelper:home/category/article/upload/asset/search/siteHome)、`themeBase`(=`themes/fresh-mint`)、`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` | diff --git a/src/main/resources/templates/themes/fresh-mint2/assets/css/main.css b/src/main/resources/templates/themes/fresh-mint/assets/css/main.css similarity index 91% rename from src/main/resources/templates/themes/fresh-mint2/assets/css/main.css rename to src/main/resources/templates/themes/fresh-mint/assets/css/main.css index dead9df..c1a9f42 100644 --- a/src/main/resources/templates/themes/fresh-mint2/assets/css/main.css +++ b/src/main/resources/templates/themes/fresh-mint/assets/css/main.css @@ -1,5 +1,5 @@ /* ============================================================ - * fresh-mint2 皮肤包样式(blue-white 派生,docs/10 皮肤包契约 §5) + * fresh-mint 皮肤包样式(blue-white 派生,docs/10 皮肤包契约 §5) * 清新薄荷风(卡片封面版):主色 #17A2A2、浅薄荷底、白卡 12px 圆角 + 柔和阴影; * 细线吸顶顶栏 + 居中 logo;列表为带封面的卡片网格(一行 3 张);几何圆点装饰。 * 无纹理、无通栏大图、无复杂动效。 @@ -622,3 +622,29 @@ time, .item-meta { color: var(--text-light); font-size: 13px; } } .site-nav .sub-nav li a { color: var(--text); padding: 8px 14px; } } + + +/* ===== 左侧栏目导航(docs/11):有二级节点的模块,栏目/文章页左侧展示兄弟目录 ===== */ +.list-main.has-sidenav, .detail-main.has-sidenav { display: flex; align-items: flex-start; gap: 24px; } +.has-sidenav .list-body { flex: 1; min-width: 0; } +.side-nav { flex: none; width: 220px; background: var(--card-bg, #fff); border: 1px solid var(--border); overflow: hidden; } +.side-nav-head { background: var(--primary); color: #fff; font-size: 20px; font-weight: 700; text-align: center; padding: 18px 12px; letter-spacing: 2px; } +.side-nav-list { list-style: none; margin: 0; padding: 0; } +.side-nav-item + .side-nav-item { border-top: 1px dashed var(--border); } +.side-nav-item a { display: block; padding: 12px 16px; color: var(--text); text-decoration: none; font-size: 15px; transition: background .15s ease, color .15s ease; } +.side-nav-item a:hover { color: var(--primary); background: var(--bg); } +.side-nav-item.active a { background: var(--primary); color: #fff; font-weight: 600; text-decoration: none; } +/* 顶导航二级下拉当前项高亮(docs/11 顺带) */ +.site-nav ul.sub-nav li.active a { color: var(--primary); } +@media (max-width: 768px) { + .list-main.has-sidenav, .detail-main.has-sidenav { flex-direction: column; gap: 12px; } + .side-nav { width: 100%; } + .side-nav-list { display: flex; overflow-x: auto; -webkit-overflow-scrolling: touch; } + .side-nav-item { flex: none; } + .side-nav-item + .side-nav-item { border-top: none; border-left: 1px dashed var(--border); } + .side-nav-item a { white-space: nowrap; } +} +@media print { + .side-nav { display: none; } + .list-main.has-sidenav, .detail-main.has-sidenav { display: block; } +} diff --git a/src/main/resources/templates/themes/fresh-mint2/assets/favicon.png b/src/main/resources/templates/themes/fresh-mint/assets/favicon.png similarity index 100% rename from src/main/resources/templates/themes/fresh-mint2/assets/favicon.png rename to src/main/resources/templates/themes/fresh-mint/assets/favicon.png diff --git a/src/main/resources/templates/themes/fresh-mint2/assets/favicon.svg b/src/main/resources/templates/themes/fresh-mint/assets/favicon.svg similarity index 100% rename from src/main/resources/templates/themes/fresh-mint2/assets/favicon.svg rename to src/main/resources/templates/themes/fresh-mint/assets/favicon.svg diff --git a/src/main/resources/templates/themes/fresh-mint2/assets/js/main.js b/src/main/resources/templates/themes/fresh-mint/assets/js/main.js similarity index 89% rename from src/main/resources/templates/themes/fresh-mint2/assets/js/main.js rename to src/main/resources/templates/themes/fresh-mint/assets/js/main.js index d1918c6..dc08468 100644 --- a/src/main/resources/templates/themes/fresh-mint2/assets/js/main.js +++ b/src/main/resources/templates/themes/fresh-mint/assets/js/main.js @@ -1,4 +1,4 @@ -/* fresh-mint2 皮肤包脚本:仅首页轮播自动播放与圆点切换,无任何外部依赖 */ +/* fresh-mint 皮肤包脚本:仅首页轮播自动播放与圆点切换,无任何外部依赖 */ (function () { var slides = document.querySelectorAll('.banner-slide'); var dots = document.querySelectorAll('.banner-dots button'); diff --git a/src/main/resources/templates/themes/fresh-mint2/assets/screenshot.png b/src/main/resources/templates/themes/fresh-mint/assets/screenshot.png similarity index 100% rename from src/main/resources/templates/themes/fresh-mint2/assets/screenshot.png rename to src/main/resources/templates/themes/fresh-mint/assets/screenshot.png diff --git a/src/main/resources/templates/themes/fresh-mint2/detail.html b/src/main/resources/templates/themes/fresh-mint/detail.html similarity index 63% rename from src/main/resources/templates/themes/fresh-mint2/detail.html rename to src/main/resources/templates/themes/fresh-mint/detail.html index f59c393..393e496 100644 --- a/src/main/resources/templates/themes/fresh-mint2/detail.html +++ b/src/main/resources/templates/themes/fresh-mint/detail.html @@ -4,10 +4,25 @@
    -
    +
    + + +
    @@ -35,6 +50,7 @@ th:href="${url.article(nextArticle.id)}" th:text="'下一篇:' + ${nextArticle.title}">下一篇 +
    diff --git a/src/main/resources/templates/themes/fresh-mint2/fragments/footer.html b/src/main/resources/templates/themes/fresh-mint/fragments/footer.html similarity index 100% rename from src/main/resources/templates/themes/fresh-mint2/fragments/footer.html rename to src/main/resources/templates/themes/fresh-mint/fragments/footer.html diff --git a/src/main/resources/templates/themes/fresh-mint2/fragments/head.html b/src/main/resources/templates/themes/fresh-mint/fragments/head.html similarity index 100% rename from src/main/resources/templates/themes/fresh-mint2/fragments/head.html rename to src/main/resources/templates/themes/fresh-mint/fragments/head.html diff --git a/src/main/resources/templates/themes/fresh-mint2/fragments/header.html b/src/main/resources/templates/themes/fresh-mint/fragments/header.html similarity index 91% rename from src/main/resources/templates/themes/fresh-mint2/fragments/header.html rename to src/main/resources/templates/themes/fresh-mint/fragments/header.html index e727949..7ca055c 100644 --- a/src/main/resources/templates/themes/fresh-mint2/fragments/header.html +++ b/src/main/resources/templates/themes/fresh-mint/fragments/header.html @@ -41,14 +41,15 @@ 首页
  • + th:classappend="${((currentCategoryId == cat.id or navTopId == cat.id) ? 'active' : '') + (!cat.children.isEmpty() ? ' has-sub' : '')}"> 栏目 栏目