feat(portal): docs/16 首页布局后端- home_layout零DDL消费与主题fragment化-PortalSiteSnapshot.homeLayout+normalizeHomeLayout脏数据容错;PortalPageRenderer.buildLayoutBlocks按区块序列渲染(article_list多栏目聚合+homeStyle别名+category合成+background回落news_bg_image/ticker top|category双源/gallery标题副题缺省旧键/links分组过滤/visible跳过/未知type跳过/size 1~20夹取);布局模式清空topArticles/focusArticles/categoryBlocks/galleryBanners回落段;SiteService.validateConfigJson带siteId收紧home_layout(blocks<=30/type白名单/size 1~20/文本<=100/categoryIds归属校验:新建拒跨站拒不存在拒);themes/common/fragments/home-blocks.html plb-*兜底骨架(不注册皮肤包,Thymeleaf classpath可解析);campus-glow自有fragment(news_grid/media_focus/card/ticker/gallery)原home.html抽出;default+blue-white+classic-red+fresh-mint+mint-breeze+warm-kinder+scholar-blue+campus-glow八套home.html接入布局分支(th:case父元素+th:replace子元素嵌套,replace优先级100先于case执行);新增PortalHomeLayoutIT 5+SiteServiceHomeLayoutIT 3,全量330绿
build / build-push (push) Successful in 52s
build / build-push (push) Successful in 52s
This commit is contained in:
@@ -21,6 +21,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 首页布局模式(docs/16 §4.4):home_layout 落库后按区块序列渲染(common 兜底骨架,本主题不另设形态) -->
|
||||
<th:block th:if="${layoutBlocks != null}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: style}" />
|
||||
<th:block th:each="block : ${layoutBlocks}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" />
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<!-- 回落分支(docs/16 D2):无 home_layout 时按现状渲染 -->
|
||||
<th:block th:unless="${layoutBlocks != null}">
|
||||
<!-- 置顶/头条区(cms_article topmost 优先) -->
|
||||
<section class="top-strip" th:if="${!#lists.isEmpty(topArticles)}">
|
||||
<h2 class="section-title">头条推荐</h2>
|
||||
@@ -49,6 +59,7 @@
|
||||
</ul>
|
||||
<p class="empty" th:if="${#lists.isEmpty(block.items)}">暂无内容</p>
|
||||
</section>
|
||||
</th:block>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<!--
|
||||
campus-glow 主题首页区块 fragment(docs/16 §4.4 fragment 化):
|
||||
news_grid/media_focus/card 三形态由原 home.html 内联段抽出(标记与样式不变,主题 main.css 继续承载);
|
||||
ticker/gallery 为布局模式专用 fragment(回落模式仍走 home.html 原内联段,数据源 topArticles/galleryBanners)。
|
||||
契约:块字段与回落 categoryBlocks 同构(category.name 取标题);布局块 background 为 Boolean
|
||||
开关(未设时回退旧键 news_bg_image 无条件生效),布局块 category.name=区块标题。
|
||||
-->
|
||||
<!-- 形态一:双栏新闻卡(homeStyle=news_grid)——全幅背景 + 毛玻璃卡 + 双栏日期列表
|
||||
(background 为布局块可选键,回落块无此键——SpEL 对 Map 缺失键点号访问会抛错,用 ['key'] 括号形式) -->
|
||||
<section class="cg-newshot" th:fragment="news_grid(block)" aria-label="栏目区块"
|
||||
th:with="newsBg=${block['background'] != null ? (block['background'] ? site.config['news_bg_image'] : null) : site.config['news_bg_image']}">
|
||||
<div class="cg-newshot-bg" th:if="${!#strings.isEmpty(newsBg)}"
|
||||
th:style="|background-image:url(${url.upload(newsBg)})|"></div>
|
||||
<div class="cg-container">
|
||||
<div class="cg-newshot-card">
|
||||
<div class="cg-sec-head">
|
||||
<h2 th:text="${block.category.name}">校园时讯</h2>
|
||||
<a class="cg-more-circle" th:href="${block.moreUrl}" aria-label="更多">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" focusable="false" aria-hidden="true">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" d="M5 12h14M13 6l6 6-6 6"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="cg-news-grid" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<span class="cg-news-date" th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">09-09</span>
|
||||
<a class="cg-news-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="cg-top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="cg-empty" th:if="${#lists.isEmpty(block.items)}">
|
||||
<p>栏目暂无内容</p>
|
||||
<p class="cg-empty-hint">内容发布后将在此展示</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 形态二:媒体聚焦列表(homeStyle=media_focus)——来源徽标 + 标题双栏 -->
|
||||
<section class="cg-media" th:fragment="media_focus(block)" aria-label="栏目区块">
|
||||
<div class="cg-container">
|
||||
<div class="cg-media-card">
|
||||
<div class="cg-sec-head">
|
||||
<h2 th:text="${block.category.name}">媒体聚焦</h2>
|
||||
<a class="cg-more-circle" th:href="${block.moreUrl}" aria-label="更多">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" focusable="false" aria-hidden="true">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" d="M5 12h14M13 6l6 6-6 6"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="cg-media-grid" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<span class="cg-source-badge" th:if="${!#strings.isEmpty(item.source)}"
|
||||
th:text="${item.source}">媒体来源</span>
|
||||
<a class="cg-media-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="cg-top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="cg-empty" th:if="${#lists.isEmpty(block.items)}">
|
||||
<p>栏目暂无内容</p>
|
||||
<p class="cg-empty-hint">内容发布后将在此展示</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 形态三:默认卡(featured 首条 + 行式列表) -->
|
||||
<section class="cg-block" th:fragment="card(block)" aria-label="栏目区块">
|
||||
<div class="cg-container">
|
||||
<div class="cg-block-card">
|
||||
<div class="cg-sec-head">
|
||||
<h2 th:text="${block.category.name}">栏目名</h2>
|
||||
<a class="cg-more-text" th:href="${block.moreUrl}">更多 ></a>
|
||||
</div>
|
||||
<div class="cg-block-cols" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<a class="cg-block-featured" th:with="feat=${block.items[0]}" th:href="${url.article(feat.id)}">
|
||||
<span class="cg-feat-cover" th:if="${feat.coverImage != null}">
|
||||
<img th:src="${url.upload(feat.coverImage)}" th:alt="${feat.title}">
|
||||
</span>
|
||||
<span class="cg-feat-body">
|
||||
<span class="cg-feat-title" th:text="${feat.title}">头条标题</span>
|
||||
<span class="cg-feat-summary" th:if="${!#strings.isEmpty(feat.summary)}"
|
||||
th:text="${feat.summary}">摘要</span>
|
||||
<span class="cg-feat-meta">
|
||||
<time th:if="${feat.publishTime != null}"
|
||||
th:text="${#temporals.format(feat.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="cg-block-list">
|
||||
<li th:each="item, stat : ${block.items}" th:if="${stat.index > 0}">
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<time th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="cg-empty" th:if="${#lists.isEmpty(block.items)}">
|
||||
<p>栏目暂无内容</p>
|
||||
<p class="cg-empty-hint">内容发布后将在此展示</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 布局模式:滚动速递(数据=渲染端装配进 block.items) -->
|
||||
<section class="cg-ticker" th:fragment="ticker(block)" aria-label="要闻速递" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<div class="cg-container cg-ticker-inner">
|
||||
<span class="cg-ticker-label" th:text="${block.title}">要闻速递</span>
|
||||
<div class="cg-ticker-viewport">
|
||||
<div class="cg-ticker-track">
|
||||
<a class="cg-ticker-item" th:each="item : ${block.items}" th:href="${url.article(item.id)}">
|
||||
<span class="cg-ticker-dot" aria-hidden="true"></span>
|
||||
<span th:text="${item.title}">要闻标题</span>
|
||||
</a>
|
||||
<a class="cg-ticker-item" th:each="item : ${block.items}" th:href="${url.article(item.id)}" aria-hidden="true">
|
||||
<span class="cg-ticker-dot" aria-hidden="true"></span>
|
||||
<span th:text="${item.title}">要闻标题</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 布局模式:线上展厅(数据=block.items 轮播位 gallery;标题/副题=渲染端已合并旧键缺省) -->
|
||||
<section class="cg-gallery" th:fragment="gallery(block)" aria-label="线上展厅" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<div class="cg-container">
|
||||
<div class="cg-gallery-head">
|
||||
<h2 th:text="${block.title}">线上展厅</h2>
|
||||
<p class="cg-gallery-sub" th:text="${block.subtitle}">Online Exhibition Hall</p>
|
||||
</div>
|
||||
<div class="cg-gallery-stage"
|
||||
th:classappend="${'cg-gallery-cols-' + (themeParams['galleryCols'] ?: '4')}"
|
||||
th:attr="data-cols=${themeParams['galleryCols'] ?: '4'}">
|
||||
<div class="cg-gallery-track">
|
||||
<a class="cg-gallery-item" th:each="g : ${block.items}" th:href="${g.linkUrl}"
|
||||
th:title="${g.title}">
|
||||
<img th:src="${g.imageUrl}" th:alt="${g.title}">
|
||||
<span class="cg-gallery-cap" th:if="${!#strings.isEmpty(g.title)}"
|
||||
th:text="${g.title}">作品标题</span>
|
||||
</a>
|
||||
</div>
|
||||
<button class="cg-gallery-arrow prev" type="button" aria-label="上一页" th:if="${#lists.size(block.items) > 1}">‹</button>
|
||||
<button class="cg-gallery-arrow next" type="button" aria-label="下一页" th:if="${#lists.size(block.items) > 1}">›</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -5,7 +5,7 @@
|
||||
<header th:replace="~{${themeBase} + '/fragments/header' :: header}"></header>
|
||||
|
||||
<main class="cg-home" id="main-content">
|
||||
<!-- 首页轮播位(cms_banner position=home;设计尺寸 1920×560,见 theme.json bannerSize) -->
|
||||
<!-- 首页轮播位(cms_banner position=home;设计尺寸 1920×560,见 theme.json bannerSize)——页面级固定槽位,布局模式同样保留 -->
|
||||
<section class="cg-banner" th:if="${!#lists.isEmpty(banners)}" aria-label="焦点图片">
|
||||
<div class="cg-banner-slides">
|
||||
<a class="cg-banner-slide" th:each="b, stat : ${banners}"
|
||||
@@ -23,151 +23,81 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 要闻速递横条:置顶文章横向无缝滚动(hover 暂停) -->
|
||||
<section class="cg-ticker" th:if="${!#lists.isEmpty(topArticles)}" aria-label="要闻速递">
|
||||
<div class="cg-container cg-ticker-inner">
|
||||
<span class="cg-ticker-label">要闻速递</span>
|
||||
<div class="cg-ticker-viewport">
|
||||
<div class="cg-ticker-track">
|
||||
<a class="cg-ticker-item" th:each="item : ${topArticles}" th:href="${url.article(item.id)}">
|
||||
<span class="cg-ticker-dot" aria-hidden="true"></span>
|
||||
<span th:text="${item.title}">要闻标题</span>
|
||||
</a>
|
||||
<a class="cg-ticker-item" th:each="item : ${topArticles}" th:href="${url.article(item.id)}" aria-hidden="true">
|
||||
<span class="cg-ticker-dot" aria-hidden="true"></span>
|
||||
<span th:text="${item.title}">要闻标题</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 首页布局模式(docs/16 §4.4):home_layout 落库后按区块序列渲染。
|
||||
article_list 三种形态走主题自有 fragment(compact/image_wall 与未实现类型落 common 兜底骨架)。
|
||||
注意:th:replace(优先级100)先于 th:case(300)执行——case 必须放父元素、replace 放子元素 -->
|
||||
<th:block th:if="${layoutBlocks != null}" th:each="block : ${layoutBlocks}">
|
||||
<th:block th:switch="${block.type}">
|
||||
<th:block th:case="'article_list'">
|
||||
<th:block th:switch="${block.homeStyle}">
|
||||
<th:block th:case="'news_grid'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: news_grid(${block})}" /></th:block>
|
||||
<th:block th:case="'media_focus'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: media_focus(${block})}" /></th:block>
|
||||
<th:block th:case="'compact'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbCompact(${block})}" /></th:block>
|
||||
<th:block th:case="'image_wall'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbImageWall(${block})}" /></th:block>
|
||||
<th:block th:case="*"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: card(${block})}" /></th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
<th:block th:case="'ticker'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: ticker(${block})}" /></th:block>
|
||||
<th:block th:case="'gallery'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: gallery(${block})}" /></th:block>
|
||||
<th:block th:case="'links'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbLinks(${block})}" /></th:block>
|
||||
<th:block th:case="*"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" /></th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<!-- 一级栏目区块(docs/12):按栏目 homeStyle 三种形态渲染,栏目顺序即首页板块顺序 -->
|
||||
<th:block th:each="block : ${categoryBlocks}">
|
||||
<!-- 形态一:双栏新闻卡(homeStyle=news_grid)——全幅背景 + 毛玻璃卡 + 双栏日期列表 -->
|
||||
<section class="cg-newshot" th:if="${block.homeStyle == 'news_grid'}"
|
||||
th:with="newsBg=${site.config['news_bg_image']}" aria-label="栏目区块">
|
||||
<div class="cg-newshot-bg" th:if="${!#strings.isEmpty(newsBg)}"
|
||||
th:style="|background-image:url(${url.upload(newsBg)})|"></div>
|
||||
<div class="cg-container">
|
||||
<div class="cg-newshot-card">
|
||||
<div class="cg-sec-head">
|
||||
<h2 th:text="${block.category.name}">校园时讯</h2>
|
||||
<a class="cg-more-circle" th:href="${block.moreUrl}" aria-label="更多">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" focusable="false" aria-hidden="true">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" d="M5 12h14M13 6l6 6-6 6"/>
|
||||
</svg>
|
||||
<!-- 回落分支(docs/16 D2):无 home_layout 时按现状渲染(布局模式下渲染端数据源已清空,双保险不漏段) -->
|
||||
<th:block th:unless="${layoutBlocks != null}">
|
||||
<!-- 要闻速递横条:置顶文章横向无缝滚动(hover 暂停) -->
|
||||
<section class="cg-ticker" th:if="${!#lists.isEmpty(topArticles)}" aria-label="要闻速递">
|
||||
<div class="cg-container cg-ticker-inner">
|
||||
<span class="cg-ticker-label">要闻速递</span>
|
||||
<div class="cg-ticker-viewport">
|
||||
<div class="cg-ticker-track">
|
||||
<a class="cg-ticker-item" th:each="item : ${topArticles}" th:href="${url.article(item.id)}">
|
||||
<span class="cg-ticker-dot" aria-hidden="true"></span>
|
||||
<span th:text="${item.title}">要闻标题</span>
|
||||
</a>
|
||||
<a class="cg-ticker-item" th:each="item : ${topArticles}" th:href="${url.article(item.id)}" aria-hidden="true">
|
||||
<span class="cg-ticker-dot" aria-hidden="true"></span>
|
||||
<span th:text="${item.title}">要闻标题</span>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="cg-news-grid" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<span class="cg-news-date" th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">09-09</span>
|
||||
<a class="cg-news-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="cg-top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="cg-empty" th:if="${#lists.isEmpty(block.items)}">
|
||||
<p>栏目暂无内容</p>
|
||||
<p class="cg-empty-hint">内容发布后将在此展示</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 形态二:媒体聚焦列表(homeStyle=media_focus)——来源徽标 + 标题双栏 -->
|
||||
<section class="cg-media" th:if="${block.homeStyle == 'media_focus'}" aria-label="栏目区块">
|
||||
<div class="cg-container">
|
||||
<div class="cg-media-card">
|
||||
<div class="cg-sec-head">
|
||||
<h2 th:text="${block.category.name}">媒体聚焦</h2>
|
||||
<a class="cg-more-circle" th:href="${block.moreUrl}" aria-label="更多">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" focusable="false" aria-hidden="true">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" d="M5 12h14M13 6l6 6-6 6"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="cg-media-grid" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<span class="cg-source-badge" th:if="${!#strings.isEmpty(item.source)}"
|
||||
th:text="${item.source}">媒体来源</span>
|
||||
<a class="cg-media-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="cg-top-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="cg-empty" th:if="${#lists.isEmpty(block.items)}">
|
||||
<p>栏目暂无内容</p>
|
||||
<p class="cg-empty-hint">内容发布后将在此展示</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 一级栏目区块(docs/12):按栏目 homeStyle 三种形态渲染,栏目顺序即首页板块顺序 -->
|
||||
<th:block th:each="block : ${categoryBlocks}">
|
||||
<th:block th:switch="${block.homeStyle}">
|
||||
<th:block th:case="'news_grid'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: news_grid(${block})}" /></th:block>
|
||||
<th:block th:case="'media_focus'"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: media_focus(${block})}" /></th:block>
|
||||
<th:block th:case="*"><th:block th:replace="~{${themeBase} + '/fragments/home-blocks' :: card(${block})}" /></th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<!-- 形态三:默认卡(homeStyle 空)——featured 首条 + 行式列表,旧栏目保持常规呈现 -->
|
||||
<section class="cg-block" th:if="${block.homeStyle == null or block.homeStyle == ''}" aria-label="栏目区块">
|
||||
<!-- 线上展厅(cms_banner position=gallery;设计尺寸 800×520;每行列数由主题参数 galleryCols 控制) -->
|
||||
<section class="cg-gallery" th:if="${!#lists.isEmpty(galleryBanners)}" aria-label="线上展厅">
|
||||
<div class="cg-container">
|
||||
<div class="cg-block-card">
|
||||
<div class="cg-sec-head">
|
||||
<h2 th:text="${block.category.name}">栏目名</h2>
|
||||
<a class="cg-more-text" th:href="${block.moreUrl}">更多 ></a>
|
||||
</div>
|
||||
<div class="cg-block-cols" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<a class="cg-block-featured" th:with="feat=${block.items[0]}" th:href="${url.article(feat.id)}">
|
||||
<span class="cg-feat-cover" th:if="${feat.coverImage != null}">
|
||||
<img th:src="${url.upload(feat.coverImage)}" th:alt="${feat.title}">
|
||||
</span>
|
||||
<span class="cg-feat-body">
|
||||
<span class="cg-feat-title" th:text="${feat.title}">头条标题</span>
|
||||
<span class="cg-feat-summary" th:if="${!#strings.isEmpty(feat.summary)}"
|
||||
th:text="${feat.summary}">摘要</span>
|
||||
<span class="cg-feat-meta">
|
||||
<time th:if="${feat.publishTime != null}"
|
||||
th:text="${#temporals.format(feat.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
</span>
|
||||
</span>
|
||||
<div class="cg-gallery-head">
|
||||
<h2 th:text="${site.config['gallery_title'] ?: '线上展厅'}">线上展厅</h2>
|
||||
<p class="cg-gallery-sub" th:text="${site.config['gallery_subtitle'] ?: 'Online Exhibition Hall'}">Online Exhibition Hall</p>
|
||||
</div>
|
||||
<div class="cg-gallery-stage"
|
||||
th:classappend="${'cg-gallery-cols-' + (themeParams['galleryCols'] ?: '4')}"
|
||||
th:attr="data-cols=${themeParams['galleryCols'] ?: '4'}">
|
||||
<div class="cg-gallery-track">
|
||||
<a class="cg-gallery-item" th:each="g : ${galleryBanners}" th:href="${g.linkUrl}"
|
||||
th:title="${g.title}">
|
||||
<img th:src="${g.imageUrl}" th:alt="${g.title}">
|
||||
<span class="cg-gallery-cap" th:if="${!#strings.isEmpty(g.title)}"
|
||||
th:text="${g.title}">作品标题</span>
|
||||
</a>
|
||||
<ul class="cg-block-list">
|
||||
<li th:each="item, stat : ${block.items}" th:if="${stat.index > 0}">
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<time th:if="${item.publishTime != null}"
|
||||
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="cg-empty" th:if="${#lists.isEmpty(block.items)}">
|
||||
<p>栏目暂无内容</p>
|
||||
<p class="cg-empty-hint">内容发布后将在此展示</p>
|
||||
</div>
|
||||
<button class="cg-gallery-arrow prev" type="button" aria-label="上一页" th:if="${#lists.size(galleryBanners) > 1}">‹</button>
|
||||
<button class="cg-gallery-arrow next" type="button" aria-label="下一页" th:if="${#lists.size(galleryBanners) > 1}">›</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</th:block>
|
||||
|
||||
<!-- 线上展厅(cms_banner position=gallery;设计尺寸 800×520;每行列数由主题参数 galleryCols 控制) -->
|
||||
<section class="cg-gallery" th:if="${!#lists.isEmpty(galleryBanners)}" aria-label="线上展厅">
|
||||
<div class="cg-container">
|
||||
<div class="cg-gallery-head">
|
||||
<h2 th:text="${site.config['gallery_title'] ?: '线上展厅'}">线上展厅</h2>
|
||||
<p class="cg-gallery-sub" th:text="${site.config['gallery_subtitle'] ?: 'Online Exhibition Hall'}">Online Exhibition Hall</p>
|
||||
</div>
|
||||
<div class="cg-gallery-stage"
|
||||
th:classappend="${'cg-gallery-cols-' + (themeParams['galleryCols'] ?: '4')}"
|
||||
th:attr="data-cols=${themeParams['galleryCols'] ?: '4'}">
|
||||
<div class="cg-gallery-track">
|
||||
<a class="cg-gallery-item" th:each="g : ${galleryBanners}" th:href="${g.linkUrl}"
|
||||
th:title="${g.title}">
|
||||
<img th:src="${g.imageUrl}" th:alt="${g.title}">
|
||||
<span class="cg-gallery-cap" th:if="${!#strings.isEmpty(g.title)}"
|
||||
th:text="${g.title}">作品标题</span>
|
||||
</a>
|
||||
</div>
|
||||
<button class="cg-gallery-arrow prev" type="button" aria-label="上一页" th:if="${#lists.size(galleryBanners) > 1}">‹</button>
|
||||
<button class="cg-gallery-arrow next" type="button" aria-label="下一页" th:if="${#lists.size(galleryBanners) > 1}">›</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
|
||||
@@ -21,6 +21,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 首页布局模式(docs/16 §4.4):home_layout 落库后按区块序列渲染(common 兜底骨架,本主题不另设形态) -->
|
||||
<th:block th:if="${layoutBlocks != null}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: style}" />
|
||||
<th:block th:each="block : ${layoutBlocks}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" />
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<!-- 回落分支(docs/16 D2):无 home_layout 时按现状渲染 -->
|
||||
<th:block th:unless="${layoutBlocks != null}">
|
||||
<!-- 置顶/头条区(cms_article topmost 优先) -->
|
||||
<section class="top-strip" th:if="${!#lists.isEmpty(topArticles)}">
|
||||
<h2 class="section-title">头条推荐</h2>
|
||||
@@ -49,6 +59,7 @@
|
||||
</ul>
|
||||
<p class="empty" th:if="${#lists.isEmpty(block.items)}">暂无内容</p>
|
||||
</section>
|
||||
</th:block>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
<!--
|
||||
通用兜底区块骨架(docs/16 §4.4):主题未实现某区块类型时由本文件渲染(结构完整、样式克制)。
|
||||
任何主题 home.html 的布局分支都可引用:~{'themes/common/fragments/home-blocks' :: render(block)}。
|
||||
样式随 fragment 内联携带——themes/common 不注册为皮肤包(无 theme.json),主题资产通道不服务该目录;
|
||||
颜色回落站点主题变量(--primary 等 themeVarsCss 注入),无变量时用内置缺省。
|
||||
契约:block 为渲染端组装的同构 Map——type/title/category.name/homeStyle/items/moreUrl/subtitle/
|
||||
background/source/linkGroups,与回落模式 categoryBlocks 的块字段兼容。
|
||||
-->
|
||||
<th:block th:fragment="style">
|
||||
<style>
|
||||
.plb-section{padding:26px 16px}
|
||||
.plb-container{max-width:1200px;margin:0 auto}
|
||||
.plb-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px;margin:0 0 14px}
|
||||
.plb-head h2{margin:0;font-size:20px;line-height:1.3;padding-left:10px;border-left:4px solid var(--primary,#2d8cf0);color:#1f2d3d}
|
||||
.plb-sub{margin:2px 0 0;color:#8a94a6;font-size:12px;letter-spacing:.12em}
|
||||
.plb-more{font-size:13px;color:var(--primary,#2d8cf0);text-decoration:none;white-space:nowrap}
|
||||
.plb-more:hover{text-decoration:underline}
|
||||
.plb-list{list-style:none;margin:0;padding:0}
|
||||
.plb-list li{display:flex;align-items:center;gap:10px;padding:7px 0;border-bottom:1px dashed rgba(0,0,0,.08);font-size:14px}
|
||||
.plb-list li a{flex:1;min-width:0;color:#333;text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.plb-list li a:hover{color:var(--primary,#2d8cf0)}
|
||||
.plb-list time{color:#999;font-size:12px;flex:none}
|
||||
.plb-badge{flex:none;font-size:11px;color:#fff;background:var(--primary,#2d8cf0);border-radius:3px;padding:1px 5px}
|
||||
.plb-src{flex:none;font-size:11px;color:var(--primary,#2d8cf0);border:1px solid currentColor;border-radius:3px;padding:1px 6px}
|
||||
.plb-empty{color:#98a2b3;font-size:13px;padding:26px 0;text-align:center}
|
||||
.plb-card{background:#fff;border:1px solid rgba(0,0,0,.06);border-radius:10px;padding:20px 24px;box-shadow:0 4px 18px rgba(15,34,66,.05)}
|
||||
.plb-feat{display:flex;gap:16px;margin:0 0 12px;padding:0 0 12px;border-bottom:1px solid rgba(0,0,0,.06);text-decoration:none;color:#333}
|
||||
.plb-feat img{width:200px;height:120px;object-fit:cover;border-radius:6px;flex:none}
|
||||
.plb-feat .t{display:block;font-size:16px;font-weight:600;color:#1f2d3d;margin:0 0 6px}
|
||||
.plb-feat .s{display:block;color:#8a94a6;font-size:13px;line-height:1.6;margin:0 0 6px}
|
||||
.plb-feat .m{color:#999;font-size:12px}
|
||||
.plb-grid2{columns:2;column-gap:40px}
|
||||
.plb-grid2 li{break-inside:avoid}
|
||||
.plb-newshot{position:relative;padding:30px 0;background:#f5f7fb}
|
||||
.plb-newshot-bg{position:absolute;inset:0;background-size:cover;background-position:center}
|
||||
.plb-newshot-card{position:relative;background:rgba(255,255,255,.94);border-radius:10px;padding:22px 26px;box-shadow:0 8px 30px rgba(15,34,66,.08)}
|
||||
.plb-imagewall{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;list-style:none;margin:0;padding:0}
|
||||
.plb-imagewall a{text-decoration:none;color:#333}
|
||||
.plb-imagewall img{width:100%;height:120px;object-fit:cover;border-radius:6px;display:block}
|
||||
.plb-imagewall .t{display:block;font-size:13px;margin-top:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.plb-ticker{background:var(--primary,#2d8cf0);color:#fff;padding:10px 16px;font-size:14px}
|
||||
.plb-ticker-inner{max-width:1200px;margin:0 auto;display:flex;align-items:center;gap:14px}
|
||||
.plb-ticker-label{flex:none;font-weight:600;letter-spacing:.08em}
|
||||
.plb-ticker-list{display:flex;gap:26px;list-style:none;margin:0;padding:0;overflow:hidden}
|
||||
.plb-ticker-list a{color:#fff;text-decoration:none;opacity:.92}
|
||||
.plb-ticker-list a:hover{opacity:1;text-decoration:underline}
|
||||
.plb-gallery-head{text-align:center;margin:0 0 18px}
|
||||
.plb-gallery-head h2{margin:0;font-size:22px;color:#1f2d3d}
|
||||
.plb-gallery-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;list-style:none;margin:0;padding:0}
|
||||
.plb-gallery-grid img{width:100%;height:150px;object-fit:cover;border-radius:8px;display:block}
|
||||
.plb-gallery-grid .t{display:block;font-size:13px;color:#51607a;margin-top:6px;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.plb-gallery-grid a{text-decoration:none}
|
||||
.plb-links{display:flex;flex-wrap:wrap;gap:10px;list-style:none;margin:0;padding:0}
|
||||
.plb-links a{display:inline-block;font-size:13px;color:#51607a;text-decoration:none;border:1px solid rgba(0,0,0,.1);border-radius:16px;padding:4px 14px;background:#fff}
|
||||
.plb-links a:hover{color:var(--primary,#2d8cf0);border-color:var(--primary,#2d8cf0)}
|
||||
@media (max-width:768px){
|
||||
.plb-grid2{grid-template-columns:1fr}
|
||||
.plb-imagewall{grid-template-columns:repeat(2,1fr)}
|
||||
.plb-gallery-grid{grid-template-columns:repeat(2,1fr)}
|
||||
.plb-feat img{width:120px;height:80px}
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
|
||||
<!-- 类型分发:主题 home.html 的布局分支唯一入口;article_list 按 homeStyle 细分,未知类型落卡片兜底。
|
||||
注意:th:replace(优先级100)先于 th:case(300)执行——case 必须放父元素、replace 放子元素,
|
||||
否则未命中的 case 分支也会求值 fragment 表达式(缺键 Map 访问直接抛错)。 -->
|
||||
<th:block th:fragment="render(block)">
|
||||
<th:block th:switch="${block.type}">
|
||||
<th:block th:case="'article_list'">
|
||||
<th:block th:switch="${block.homeStyle}">
|
||||
<th:block th:case="'news_grid'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbNewsGrid(${block})}" /></th:block>
|
||||
<th:block th:case="'media_focus'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbMediaFocus(${block})}" /></th:block>
|
||||
<th:block th:case="'compact'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbCompact(${block})}" /></th:block>
|
||||
<th:block th:case="'image_wall'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbImageWall(${block})}" /></th:block>
|
||||
<th:block th:case="*"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbCard(${block})}" /></th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
<th:block th:case="'ticker'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbTicker(${block})}" /></th:block>
|
||||
<th:block th:case="'gallery'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbGallery(${block})}" /></th:block>
|
||||
<th:block th:case="'links'"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbLinks(${block})}" /></th:block>
|
||||
<th:block th:case="*"><th:block th:replace="~{'themes/common/fragments/home-blocks' :: plbCard(${block})}" /></th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<!-- 区块头(标题/副题 + 更多链接;副题/更多为可选键——SpEL 对 Map 缺失键的属性访问会抛错,一律用 ['key'] 括号形式) -->
|
||||
<div class="plb-head" th:fragment="head(block)">
|
||||
<div>
|
||||
<h2 th:text="${block.title}">区块标题</h2>
|
||||
<p class="plb-sub" th:if="${!#strings.isEmpty(block['subtitle'])}" th:text="${block['subtitle']}">副题</p>
|
||||
</div>
|
||||
<a class="plb-more" th:if="${!#strings.isEmpty(block['moreUrl'])}" th:href="${block['moreUrl']}">更多 ></a>
|
||||
</div>
|
||||
|
||||
<!-- 空态 -->
|
||||
<div class="plb-empty" th:fragment="empty(block)">
|
||||
<p>该区块暂无内容</p>
|
||||
</div>
|
||||
|
||||
<!-- 形态:默认卡(头条 + 行式列表) -->
|
||||
<section class="plb-section plb-card" th:fragment="plbCard(block)" aria-label="栏目区块">
|
||||
<div class="plb-container">
|
||||
<div class="plb-card">
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: head(${block})}"></div>
|
||||
<a class="plb-feat" th:if="${!#lists.isEmpty(block.items)}" th:with="feat=${block.items[0]}"
|
||||
th:href="${url.article(feat.id)}">
|
||||
<img th:if="${feat.coverImage != null}" th:src="${url.upload(feat.coverImage)}" th:alt="${feat.title}">
|
||||
<span>
|
||||
<span class="t" th:text="${feat.title}">头条标题</span>
|
||||
<span class="s" th:if="${!#strings.isEmpty(feat.summary)}" th:text="${feat.summary}">摘要</span>
|
||||
<span class="m">
|
||||
<span class="plb-badge" th:if="${feat.topmost == 1}">置顶</span>
|
||||
<time th:if="${feat.publishTime != null}" th:text="${#temporals.format(feat.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="plb-list" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item, stat : ${block.items}" th:if="${stat.index > 0}">
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="plb-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
<time th:if="${item.publishTime != null}" th:text="${#temporals.format(item.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
|
||||
</li>
|
||||
</ul>
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: empty(${block})}" th:if="${#lists.isEmpty(block.items)}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 形态:纯文字列表(compact) -->
|
||||
<section class="plb-section plb-compact" th:fragment="plbCompact(block)" aria-label="栏目区块">
|
||||
<div class="plb-container">
|
||||
<div class="plb-card">
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: head(${block})}"></div>
|
||||
<ul class="plb-list" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<a class="plb-row-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="plb-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
<time th:if="${item.publishTime != null}" th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
|
||||
</li>
|
||||
</ul>
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: empty(${block})}" th:if="${#lists.isEmpty(block.items)}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 形态:双栏新闻卡(news_grid;background 开关取站点旧键背景图;双栏用 CSS 多列流式分栏) -->
|
||||
<section class="plb-newshot" th:fragment="plbNewsGrid(block)" aria-label="栏目区块"
|
||||
th:with="newsBg=${block.background ? site.config['news_bg_image'] : null}">
|
||||
<div class="plb-newshot-bg" th:if="${!#strings.isEmpty(newsBg)}"
|
||||
th:style="|background-image:url(${url.upload(newsBg)})|"></div>
|
||||
<div class="plb-container">
|
||||
<div class="plb-newshot-card">
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: head(${block})}"></div>
|
||||
<ul class="plb-list plb-grid2" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<time th:if="${item.publishTime != null}" th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="plb-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: empty(${block})}" th:if="${#lists.isEmpty(block.items)}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 形态:媒体聚焦(media_focus;来源徽标) -->
|
||||
<section class="plb-section plb-media-focus" th:fragment="plbMediaFocus(block)" aria-label="栏目区块">
|
||||
<div class="plb-container">
|
||||
<div class="plb-card">
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: head(${block})}"></div>
|
||||
<ul class="plb-list" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}">
|
||||
<span class="plb-src" th:if="${!#strings.isEmpty(item.source)}" th:text="${item.source}">媒体来源</span>
|
||||
<a th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
|
||||
<span class="plb-badge" th:if="${item.topmost == 1}">置顶</span>
|
||||
<time th:if="${item.publishTime != null}" th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
|
||||
</li>
|
||||
</ul>
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: empty(${block})}" th:if="${#lists.isEmpty(block.items)}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 形态:图片墙(image_wall;仅取有封面文章,不足即少几格) -->
|
||||
<section class="plb-section plb-image-wall" th:fragment="plbImageWall(block)" aria-label="栏目区块">
|
||||
<div class="plb-container">
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: head(${block})}"></div>
|
||||
<ul class="plb-imagewall" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<li th:each="item : ${block.items}" th:if="${item.coverImage != null}">
|
||||
<a th:href="${url.article(item.id)}">
|
||||
<img th:src="${url.upload(item.coverImage)}" th:alt="${item.title}">
|
||||
<span class="t" th:text="${item.title}">文章标题</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: empty(${block})}" th:if="${#lists.isEmpty(block.items)}"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 区块:滚动速递(ticker;source=top/category 数据由渲染端装配进 items) -->
|
||||
<section class="plb-ticker" th:fragment="plbTicker(block)" aria-label="滚动速递" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<div class="plb-ticker-inner">
|
||||
<span class="plb-ticker-label" th:text="${block.title}">要闻速递</span>
|
||||
<ul class="plb-ticker-list">
|
||||
<li th:each="item : ${block.items}">
|
||||
<a class="plb-ticker-item" th:href="${url.article(item.id)}" th:text="${item.title}">要闻标题</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 区块:图片画廊(gallery;轮播位 gallery 数据通道不变) -->
|
||||
<section class="plb-section" th:fragment="plbGallery(block)" aria-label="图片画廊" th:if="${!#lists.isEmpty(block.items)}">
|
||||
<div class="plb-container">
|
||||
<div class="plb-gallery-head">
|
||||
<h2 th:text="${block.title}">线上展厅</h2>
|
||||
<p class="plb-sub" th:if="${!#strings.isEmpty(block.subtitle)}" th:text="${block.subtitle}">Online Exhibition Hall</p>
|
||||
</div>
|
||||
<ul class="plb-gallery-grid">
|
||||
<li th:each="g : ${block.items}">
|
||||
<a th:href="${g.linkUrl}" th:title="${g.title}">
|
||||
<img th:src="${g.imageUrl}" th:alt="${g.title}">
|
||||
<span class="t" th:if="${!#strings.isEmpty(g.title)}" th:text="${g.title}">作品标题</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 区块:链接组(links;友情链接分组,group 由渲染端过滤) -->
|
||||
<section class="plb-section" th:fragment="plbLinks(block)" aria-label="链接组">
|
||||
<div class="plb-container">
|
||||
<div class="plb-card">
|
||||
<div th:replace="~{'themes/common/fragments/home-blocks' :: head(${block})}"></div>
|
||||
<th:block th:each="grp : ${block.linkGroups}">
|
||||
<p class="plb-sub" th:if="${!#strings.isEmpty(grp.groupName)}" style="margin:8px 0" th:text="${grp.groupName}">分组名</p>
|
||||
<ul class="plb-links">
|
||||
<li th:each="l : ${grp.links}">
|
||||
<a th:href="${l.linkUrl}" th:text="${l.name}" target="_blank" rel="noopener">链接名</a>
|
||||
</li>
|
||||
</ul>
|
||||
</th:block>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -23,6 +23,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 首页布局模式(docs/16 §4.4):home_layout 落库后按区块序列渲染(common 兜底骨架,本主题不另设形态) -->
|
||||
<th:block th:if="${layoutBlocks != null}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: style}" />
|
||||
<th:block th:each="block : ${layoutBlocks}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" />
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<!-- 回落分支(docs/16 D2):无 home_layout 时按现状渲染 -->
|
||||
<th:block th:unless="${layoutBlocks != null}">
|
||||
<!-- 头条区:头条推荐(置顶优先) + 图片新闻(有封面文章) -->
|
||||
<section class="section container" aria-label="头条与图片新闻">
|
||||
<div class="headline-grid">
|
||||
@@ -110,6 +120,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</th:block>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
|
||||
@@ -21,6 +21,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 首页布局模式(docs/16 §4.4):home_layout 落库后按区块序列渲染(common 兜底骨架,本主题不另设形态) -->
|
||||
<th:block th:if="${layoutBlocks != null}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: style}" />
|
||||
<th:block th:each="block : ${layoutBlocks}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" />
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<!-- 回落分支(docs/16 D2):无 home_layout 时按现状渲染 -->
|
||||
<th:block th:unless="${layoutBlocks != null}">
|
||||
<!-- 置顶/头条区(cms_article topmost 优先) -->
|
||||
<section class="top-strip" th:if="${!#lists.isEmpty(topArticles)}">
|
||||
<h2 class="section-title">头条推荐</h2>
|
||||
@@ -49,6 +59,7 @@
|
||||
</ul>
|
||||
<p class="empty" th:if="${#lists.isEmpty(block.items)}">暂无内容</p>
|
||||
</section>
|
||||
</th:block>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
|
||||
@@ -21,6 +21,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 首页布局模式(docs/16 §4.4):home_layout 落库后按区块序列渲染(common 兜底骨架,本主题不另设形态) -->
|
||||
<th:block th:if="${layoutBlocks != null}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: style}" />
|
||||
<th:block th:each="block : ${layoutBlocks}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" />
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<!-- 回落分支(docs/16 D2):无 home_layout 时按现状渲染 -->
|
||||
<th:block th:unless="${layoutBlocks != null}">
|
||||
<!-- 置顶/头条区(cms_article topmost 优先) -->
|
||||
<section class="top-strip" th:if="${!#lists.isEmpty(topArticles)}">
|
||||
<h2 class="section-title">头条推荐</h2>
|
||||
@@ -49,6 +59,7 @@
|
||||
</ul>
|
||||
<p class="empty" th:if="${#lists.isEmpty(block.items)}">暂无内容</p>
|
||||
</section>
|
||||
</th:block>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
|
||||
@@ -23,7 +23,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 头条区:头条推荐(置顶优先) + 图片新闻(有封面文章) -->
|
||||
<!-- 首页布局模式(docs/16 §4.4):home_layout 落库后按区块序列渲染(common 兜底骨架,本主题不另设形态) -->
|
||||
<th:block th:if="${layoutBlocks != null}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: style}" />
|
||||
<th:block th:each="block : ${layoutBlocks}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" />
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<!-- 回落分支(docs/16 D2):无 home_layout 时按现状渲染 -->
|
||||
<th:block th:unless="${layoutBlocks != null}">
|
||||
<section class="sp-section sp-container" aria-label="头条与图片新闻">
|
||||
<div class="sp-headline">
|
||||
<div class="sp-headline-card" th:if="${!#lists.isEmpty(topArticles)}">
|
||||
@@ -116,6 +125,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</th:block>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
|
||||
@@ -21,6 +21,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 首页布局模式(docs/16 §4.4):home_layout 落库后按区块序列渲染(common 兜底骨架,本主题不另设形态) -->
|
||||
<th:block th:if="${layoutBlocks != null}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: style}" />
|
||||
<th:block th:each="block : ${layoutBlocks}">
|
||||
<th:block th:replace="~{'themes/common/fragments/home-blocks' :: render(${block})}" />
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<!-- 回落分支(docs/16 D2):无 home_layout 时按现状渲染 -->
|
||||
<th:block th:unless="${layoutBlocks != null}">
|
||||
<!-- 置顶/头条区(cms_article topmost 优先) -->
|
||||
<section class="top-strip" th:if="${!#lists.isEmpty(topArticles)}">
|
||||
<h2 class="section-title">头条推荐</h2>
|
||||
@@ -49,6 +59,7 @@
|
||||
</ul>
|
||||
<p class="empty" th:if="${#lists.isEmpty(block.items)}">暂无内容</p>
|
||||
</section>
|
||||
</th:block>
|
||||
</main>
|
||||
|
||||
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
|
||||
|
||||
Reference in New Issue
Block a user