Files
YschcmsService/src/main/resources/templates/themes/agri-forest/fragments/home-blocks.html
T
chenxing 82d6ff4602
build / build-push (push) Successful in 51s
feat(theme): 新增三套高校官网风皮肤 poly-blue/agri-forest/jade-normal
以 default 为基座(文件最全:含事项库 service/guide 两模板),设计语言与配色取自
真实高校官网 CSS(浙理工 #0558A5+橙、信阳农林 #1C6E19+麦穗金、信阳师大 #008D3F+暖橙)。
三包均自带九段 home-blocks、:root 契约变量别名、中文防塌列三件套、真实实拍截图
(1920 无头截图缩样)。

- UniversitySkinContractTest(7 用例):九段 fragment/homeStyle 分发/防塌列/变量别名/
  层序/自有截图/主色色域(蓝或绿通道主导,粉紫禁入)
- ThemeRegistryTest +哨兵:三包注册成功且主色与 default/scholar-blue 及彼此互异
- jade-normal 修复:news_grid 全宽封面变体把正文列压成 0 宽致 390px 溢出 47px,
  增 .jn-feat-a--stack 纵向变体,复测 overflowX=0
- 门禁:ThemeContractIT 14 主题×7 路由全绿;全量 mvn test 393/393 绿
2026-09-20 02:31:53 +08:00

258 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!--
agri-forest(农林绿)首页区块 fragment:站点配了 home_layout 时,区块正文必须由本文件渲染,
不能落到 themes/common 的通用骨架——否则换肤只换了页头页脚,首页正文仍是骨架内置蓝灰卡(线上事故根因)。
九段形态:news_grid(三图焦点+要闻列表)/ media_focus(来源徽标密行)/ compact(46px 日期方块行)/
image_wall(四列图片墙)/ card(默认卡:头条+行式)/ ticker(速递横条)/ gallery(校园映像图廊)/
links(专题网站缩略卡)/ empty(空态)。
契约:block 为渲染端装配的同构 Map(type/title/category.name/homeStyle/items/moreUrl/subtitle/
background/source/linkGroups),与回落模式 categoryBlocks 兼容;区块标题一律取 category.name。
空态守卫必须包在 th:block 外层:th:replace(优先级 100)先于 th:if(300)求值,写在同一标签上等于没写。
-->
<!-- 形态一:三图焦点 + 要闻列表(homeStyle=news_grid)——横排 3 张封面卡 + 双列行式列表 -->
<section class="af-section af-section--news" 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="af-sec-bg" th:if="${!#strings.isEmpty(newsBg)}"
th:style="|background-image:url(${url.upload(newsBg)})|"></div>
<div class="af-block af-container">
<div class="af-block-head">
<div class="af-block-title">
<span class="af-block-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="M4 5h11a1 1 0 0 1 1 1v13H6a2 2 0 0 1-2-2V5z"/><path d="M16 9h3a1 1 0 0 1 1 1v7a2 2 0 0 1-2 2"/><path d="M7 9h6M7 13h6M7 17h4"/></svg>
</span>
<h2 th:text="${block.category.name}">学校要闻</h2>
</div>
<a class="af-more" th:href="${block.moreUrl}">更多 &gt;</a>
</div>
<div class="af-news-body" th:if="${!#lists.isEmpty(block.items)}"
th:with="rest=${#lists.size(block.items) > 3 ? block.items.subList(3, block.items.size()) : null}">
<ul class="af-trio">
<li class="af-trio-item" th:each="item, stat : ${block.items}" th:if="${stat.index &lt; 3}">
<a th:href="${url.article(item.id)}" th:title="${item.title}">
<span class="af-trio-cover" th:if="${item.coverImage != null}">
<img th:src="${url.upload(item.coverImage)}" th:alt="${item.title}" loading="lazy">
</span>
<span class="af-trio-cover af-trio-ph" th:if="${item.coverImage == null}" aria-hidden="true">
<svg viewBox="0 0 24 24" width="30" height="30" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="M5 19C5 9 12 4 20 4c0 9-5 15-12 15-1.5 0-3-.4-3-1z"/><path d="M5 19c3-5 6-8 10-10"/></svg>
</span>
<span class="af-trio-cap">
<span class="af-trio-title" th:text="${item.title}">焦点标题</span>
<span class="af-trio-meta">
<span class="af-top-badge" th:if="${item.topmost == 1}">置顶</span>
<time class="af-item-date" th:if="${item.publishTime != null}"
th:text="${#temporals.format(item.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
</span>
</span>
</a>
</li>
</ul>
<ul class="af-block-list af-block-list--two" th:if="${rest != null}">
<li class="af-line" th:each="item : ${rest}">
<a class="af-block-item-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
<span class="af-top-badge" th:if="${item.topmost == 1}">置顶</span>
<time class="af-item-date" th:if="${item.publishTime != null}"
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
</li>
</ul>
</div>
<th:block th:if="${#lists.isEmpty(block.items)}">
<div th:replace="~{'themes/agri-forest/fragments/home-blocks' :: empty(${block})}"></div>
</th:block>
</div>
</section>
<!-- 形态二:来源徽标密行(homeStyle=media_focus)——学术动态,双列紧凑行 + 来源徽标 -->
<section class="af-section af-container" th:fragment="media_focus(block)" aria-label="栏目区块">
<div class="af-block">
<div class="af-block-head">
<div class="af-block-title">
<span class="af-block-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="M9 3h6M10 3v6L4.6 18A2 2 0 0 0 6.3 21h11.4a2 2 0 0 0 1.7-3L14 9V3"/><path d="M7.5 15h9"/></svg>
</span>
<h2 th:text="${block.category.name}">学术动态</h2>
</div>
<a class="af-more" th:href="${block.moreUrl}">更多 &gt;</a>
</div>
<ul class="af-block-list af-block-list--two" th:if="${!#lists.isEmpty(block.items)}">
<li class="af-line af-line--media" th:each="item : ${block.items}">
<span class="af-src" th:if="${!#strings.isEmpty(item.source)}" th:text="${item.source}">来源</span>
<a class="af-block-item-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
<span class="af-top-badge" th:if="${item.topmost == 1}">置顶</span>
<time class="af-item-date" th:if="${item.publishTime != null}"
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
</li>
</ul>
<th:block th:if="${#lists.isEmpty(block.items)}">
<div th:replace="~{'themes/agri-forest/fragments/home-blocks' :: empty(${block})}"></div>
</th:block>
</div>
</section>
<!-- 形态三:46px 日期方块列表(homeStyle=compact)——通知公告:日大号 + 年月小号,墨绿底白字,hover 金线 -->
<section class="af-section af-container" th:fragment="compact(block)" aria-label="栏目区块">
<div class="af-block">
<div class="af-block-head">
<div class="af-block-title">
<span class="af-block-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="M3 10v4l6 1 9 5V4L9 9l-6 1z"/><path d="M7 15l1.5 5h3L10 16"/></svg>
</span>
<h2 th:text="${block.category.name}">通知公告</h2>
</div>
<a class="af-more" th:href="${block.moreUrl}">更多 &gt;</a>
</div>
<ul class="af-block-list af-notice-list" th:if="${!#lists.isEmpty(block.items)}">
<li class="af-line af-line--date" th:each="item : ${block.items}">
<span class="af-date-chip" th:if="${item.publishTime != null}" th:with="d=${item.publishTime}">
<b th:text="${#temporals.format(d, 'dd')}">01</b>
<i th:text="${#temporals.format(d, 'yyyy-MM')}">2026-01</i>
</span>
<a class="af-block-item-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
<span class="af-top-badge" th:if="${item.topmost == 1}">置顶</span>
</li>
</ul>
<th:block th:if="${#lists.isEmpty(block.items)}">
<div th:replace="~{'themes/agri-forest/fragments/home-blocks' :: empty(${block})}"></div>
</th:block>
</div>
</section>
<!-- 形态四:四列图片墙(homeStyle=image_wall)——仅取有封面的条目,悬浮标题 -->
<section class="af-section af-container" th:fragment="image_wall(block)" aria-label="栏目区块">
<div class="af-block-head">
<div class="af-block-title">
<span class="af-block-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"><rect x="3" y="5" width="18" height="14" rx="1"/><circle cx="8.5" cy="10" r="1.5"/><path d="M21 15.5l-4.5-4.5-9 8"/></svg>
</span>
<h2 th:text="${block.category.name}">图片新闻</h2>
</div>
<a class="af-more" th:href="${block.moreUrl}">更多 &gt;</a>
</div>
<ul class="af-wall" th:if="${!#lists.isEmpty(block.items)}">
<li class="af-wall-item" th:each="item : ${block.items}" th:if="${item.coverImage != null}">
<a th:href="${url.article(item.id)}" th:title="${item.title}">
<img th:src="${url.upload(item.coverImage)}" th:alt="${item.title}" loading="lazy">
<span class="af-wall-cap" th:text="${item.title}">图片标题</span>
</a>
</li>
</ul>
<th:block th:if="${#lists.isEmpty(block.items)}">
<div th:replace="~{'themes/agri-forest/fragments/home-blocks' :: empty(${block})}"></div>
</th:block>
</section>
<!-- 形态五:默认卡(homeStyle 未设/未知)——整宽头条(有封面则图文)+ 行式列表 -->
<section class="af-section af-container" th:fragment="card(block)" aria-label="栏目区块">
<div class="af-block">
<div class="af-block-head">
<div class="af-block-title">
<span class="af-block-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="M6 3h8l4 4v14H6V3z"/><path d="M14 3v4h4M9 12h6M9 16h6"/></svg>
</span>
<h2 th:text="${block.category.name}">栏目名</h2>
</div>
<a class="af-more" th:href="${block.moreUrl}">更多 &gt;</a>
</div>
<div class="af-feat" th:if="${!#lists.isEmpty(block.items)}" th:with="feat=${block.items[0]}">
<a th:href="${url.article(feat.id)}">
<span class="af-feat-cover" th:if="${feat.coverImage != null}">
<img th:src="${url.upload(feat.coverImage)}" th:alt="${feat.title}" loading="lazy">
</span>
<span class="af-feat-body">
<span class="af-feat-title" th:text="${feat.title}">头条标题</span>
<span class="af-feat-summary" th:if="${!#strings.isEmpty(feat.summary)}"
th:text="${feat.summary}">摘要</span>
<span class="af-feat-meta">
<span class="af-top-badge" th:if="${feat.topmost == 1}">置顶</span>
<time class="af-item-date" th:if="${feat.publishTime != null}"
th:text="${#temporals.format(feat.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
</span>
</span>
</a>
</div>
<ul class="af-block-list" th:if="${#lists.size(block.items) > 1}">
<li class="af-line" th:each="item, stat : ${block.items}" th:if="${stat.index > 0}">
<a class="af-block-item-title" th:href="${url.article(item.id)}" th:text="${item.title}">文章标题</a>
<span class="af-top-badge" th:if="${item.topmost == 1}">置顶</span>
<time class="af-item-date" th:if="${item.publishTime != null}"
th:text="${#temporals.format(item.publishTime, 'MM-dd')}">01-01</time>
</li>
</ul>
<th:block th:if="${#lists.isEmpty(block.items)}">
<div th:replace="~{'themes/agri-forest/fragments/home-blocks' :: empty(${block})}"></div>
</th:block>
</div>
</section>
<!-- 布局模式:要闻速递横条(block.items 由渲染端按置顶+最新装配;单行不换行,中文不塌列) -->
<section class="af-ticker" th:fragment="ticker(block)" aria-label="要闻速递"
th:if="${!#lists.isEmpty(block.items)}">
<div class="af-container af-ticker-inner">
<span class="af-ticker-label">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 10v4l6 1 9 5V4L9 9l-6 1z"/><path d="M7 15l1.5 5h3L10 16"/></svg>
<span th:text="${block.title}">要闻速递</span>
</span>
<ul class="af-ticker-list">
<li th:each="item : ${block.items}">
<a class="af-ticker-item" th:href="${url.article(item.id)}" th:text="${item.title}">要闻标题</a>
</li>
</ul>
</div>
</section>
<!-- 布局模式:校园映像图廊(block.items = 轮播位 gallery,四列图片墙 + 悬浮标题) -->
<section class="af-section af-container af-gallery" th:fragment="gallery(block)" aria-label="校园映像"
th:if="${!#lists.isEmpty(block.items)}">
<div class="af-gallery-head">
<div class="af-block-title af-gallery-title">
<span class="af-block-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"><rect x="3" y="5" width="18" height="14" rx="1"/><circle cx="8.5" cy="10" r="1.5"/><path d="M21 15.5l-4.5-4.5-9 8"/></svg>
</span>
<h2 th:text="${block.title}">校园映像</h2>
</div>
<p class="af-gallery-sub" th:if="${!#strings.isEmpty(block.subtitle)}" th:text="${block.subtitle}">
Campus Gallery
</p>
</div>
<ul class="af-wall af-wall--4">
<li class="af-wall-item" th:each="g : ${block.items}">
<a th:href="${g.linkUrl}" th:title="${g.title}">
<img th:src="${g.imageUrl}" th:alt="${g.title}" loading="lazy">
<span class="af-wall-cap" th:if="${!#strings.isEmpty(g.title)}" th:text="${g.title}">作品标题</span>
</a>
</li>
</ul>
</section>
<!-- 布局模式:专题网站缩略卡(block.linkGroups 由渲染端按 group 过滤;4 列卡片含标题条) -->
<section class="af-section af-container" th:fragment="links(block)" aria-label="专题网站">
<div class="af-block-head">
<div class="af-block-title">
<span class="af-block-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
</span>
<h2 th:text="${block.title}">专题网站</h2>
</div>
</div>
<ul class="af-topic-grid">
<th:block th:each="grp : ${block.linkGroups}">
<li class="af-topic" th:each="l : ${grp.links}">
<a th:href="${l.linkUrl}" target="_blank" rel="noopener" th:title="${l.name}">
<span class="af-topic-bar" aria-hidden="true"></span>
<span class="af-topic-glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c3 3.5 3 14 0 18M12 3c-3 3.5-3 14 0 18"/></svg>
</span>
<span class="af-topic-name" th:text="${l.name}">专题名称</span>
</a>
</li>
</th:block>
</ul>
</section>
<!-- 空态(各形态引用时守卫必须包在 th:block 外层:th:replace 优先级 100 先于 th:if 300 求值) -->
<div class="af-empty" th:fragment="empty(block)">
<p>栏目暂无内容</p>
<p class="af-empty-hint">内容发布后将在此展示</p>
</div>
</html>