feat(theme): 新增三套高校官网风皮肤 poly-blue/agri-forest/jade-normal
build / build-push (push) Successful in 51s
build / build-push (push) Successful in 51s
以 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 绿
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<!-- 公共页脚(农林绿·墨绿多列):品牌区(校徽+站名+社媒图标) + 地址/联系/版权/备案 + 二维码位
|
||||
+ 友链分组(浅色带)+ 更深底条(校名)。
|
||||
数据契约与 default 一致(linkGroups/contact/copyright/beian/logo/schName),
|
||||
增强键(docs/12):footerAddressLines / qr_wechat / qr_weibo / social_*(有值才渲染,空站点优雅降级)。 -->
|
||||
<footer class="af-footer" th:fragment="footer">
|
||||
<!-- 主区:墨绿多列 -->
|
||||
<div class="af-footer-main">
|
||||
<div class="af-container af-footer-cols">
|
||||
<!-- 品牌列 -->
|
||||
<div class="af-footer-brand">
|
||||
<div class="af-footer-logo-row">
|
||||
<img th:if="${site.config['logo'] != null}" class="af-footer-logo"
|
||||
th:src="${url.upload(site.config['logo'])}" th:alt="${site.name}">
|
||||
<span class="af-footer-site" th:text="${site.name}">站点名称</span>
|
||||
</div>
|
||||
<!-- 社媒主页(url 校验,有值才渲染对应图标;图标内联 SVG,契约 §4.6 不引外链) -->
|
||||
<div class="af-social" th:if="${site.config['social_wechat'] != null
|
||||
or site.config['social_weibo'] != null
|
||||
or site.config['social_douyin'] != null
|
||||
or site.config['social_bilibili'] != null}">
|
||||
<a th:if="${site.config['social_wechat'] != null}" class="af-social-icon"
|
||||
th:href="${site.config['social_wechat']}" target="_blank" rel="noopener" aria-label="微信公众号" th:title="微信公众号">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" focusable="false" aria-hidden="true"><path fill="currentColor" d="M9.3 4C5.8 4 3 6.4 3 9.4c0 1.7.9 3.2 2.4 4.2l-.6 2 2.2-1.1c.6.2 1.3.3 2 .3h.4a5 5 0 0 1-.2-1.4c0-2.9 2.7-5.2 6-5.2h.3C14.9 5.9 12.3 4 9.3 4zM7 7.5a.9.9 0 1 1 0 1.8.9.9 0 0 1 0-1.8zm4.7 0a.9.9 0 1 1 0 1.8.9.9 0 0 1 0-1.8zM15 9.6c-3 0-5.4 2-5.4 4.5S12 18.5 15 18.5c.6 0 1.2-.1 1.7-.3l1.9 1-.5-1.8c1.3-.9 2.2-2.2 2.2-3.7 0-2.5-2.4-4.1-5.3-4.1zm-1.9 2.5a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6zm3.8 0a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6z"/></svg>
|
||||
</a>
|
||||
<a th:if="${site.config['social_weibo'] != null}" class="af-social-icon"
|
||||
th:href="${site.config['social_weibo']}" target="_blank" rel="noopener" aria-label="微博" th:title="微博">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" focusable="false" aria-hidden="true"><path fill="currentColor" d="M10.1 19.6c-3.5.3-6.6-1.3-6.9-3.6-.3-2.2 2.3-4.4 5.8-4.8 3.5-.3 6.6 1.3 6.9 3.6.3 2.3-2.3 4.5-5.8 4.8zm3.2-4.9c-.4-1.3-2-2-3.8-1.8-1.9.3-3.2 1.6-2.9 3 .3 1.3 2 2.1 3.9 1.8 1.8-.3 3.1-1.7 2.8-3zm-3.1 1.9a.7.7 0 0 1-.9.3.6.6 0 0 1-.2-.9c.2-.3.6-.4.9-.2.3.1.4.5.2.8zm.8-1a.3.3 0 0 1-.4.1c-.2 0-.3-.2-.2-.4.1-.1.3-.2.4-.1.2 0 .3.2.2.4zm3.6-7.3a3.9 3.9 0 0 0-3.6-1.1.5.5 0 0 0-.4.6c.1.3.4.5.6.4a2.9 2.9 0 0 1 3.3 3.7c-.1.3.1.5.4.6.2 0 .5-.1.6-.4.4-1.4.1-2.8-.9-3.8zm2.6-1a6.8 6.8 0 0 0-6.3-2 .5.5 0 0 0-.4.6c0 .3.3.5.6.4a5.8 5.8 0 0 1 6.7 7.4c-.1.3.1.5.3.6.3.1.6 0 .7-.3.8-2.3.3-4.9-1.6-6.7z"/></svg>
|
||||
</a>
|
||||
<a th:if="${site.config['social_douyin'] != null}" class="af-social-icon"
|
||||
th:href="${site.config['social_douyin']}" target="_blank" rel="noopener" aria-label="抖音" th:title="抖音">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" focusable="false" aria-hidden="true"><path fill="currentColor" d="M16.6 3c.4 1.8 1.6 3.2 3.4 3.5v2.8c-1.3 0-2.5-.4-3.5-1.1v5.6a5.6 5.6 0 1 1-5.6-5.6c.3 0 .6 0 .9.1v2.9a2.7 2.7 0 1 0 1.9 2.6V3h2.9z"/></svg>
|
||||
</a>
|
||||
<a th:if="${site.config['social_bilibili'] != null}" class="af-social-icon"
|
||||
th:href="${site.config['social_bilibili']}" target="_blank" rel="noopener" aria-label="哔哩哔哩" th:title="哔哩哔哩">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" focusable="false" aria-hidden="true"><path fill="currentColor" d="M7.2 4.3 9 6.4h6l1.8-2.1c.3-.4.9-.4 1.2-.1.3.3.4.8.1 1.2l-1 1h1.4A3.5 3.5 0 0 1 22 9.9v6.6a3.5 3.5 0 0 1-3.5 3.5h-13A3.5 3.5 0 0 1 2 16.5V9.9a3.5 3.5 0 0 1 3.5-3.5h1.4l-1-1A.9.9 0 0 1 6 4.2c.3-.3.9-.3 1.2.1zM5.5 8.2A1.7 1.7 0 0 0 3.8 9.9v6.6c0 .9.8 1.7 1.7 1.7h13c.9 0 1.7-.8 1.7-1.7V9.9c0-.9-.8-1.7-1.7-1.7h-13zm3 3.2c.5 0 .9.4.9.9v.9a.9.9 0 1 1-1.8 0v-.9c0-.5.4-.9.9-.9zm7 0c.5 0 .9.4.9.9v.9a.9.9 0 1 1-1.8 0v-.9c0-.5.4-.9.9-.9z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 地址/联系列(多校区地址按行直出;contact/copyright/beian 可空,空块收敛) -->
|
||||
<div class="af-footer-info">
|
||||
<ul class="af-footer-addr" th:if="${!#lists.isEmpty(site.footerAddressLines)}">
|
||||
<li th:each="line : ${site.footerAddressLines}" th:text="${line}">校区地址</li>
|
||||
</ul>
|
||||
<p class="af-footer-line" th:if="${site.config['contact'] != null}" th:text="${site.config['contact']}">联系方式</p>
|
||||
<p class="af-footer-line" th:if="${site.config['copyright'] != null}" th:text="${site.config['copyright']}">版权信息</p>
|
||||
<p class="af-footer-line" th:if="${site.config['beian'] != null}" th:text="${site.config['beian']}">备案号</p>
|
||||
</div>
|
||||
|
||||
<!-- 二维码位(asset 校验,有值才渲染) -->
|
||||
<div class="af-footer-qr" th:if="${site.config['qr_wechat'] != null or site.config['qr_weibo'] != null}">
|
||||
<figure class="af-qr" th:if="${site.config['qr_wechat'] != null}">
|
||||
<img th:src="${url.upload(site.config['qr_wechat'])}" alt="微信二维码">
|
||||
<figcaption>官方微信</figcaption>
|
||||
</figure>
|
||||
<figure class="af-qr" th:if="${site.config['qr_weibo'] != null}">
|
||||
<img th:src="${url.upload(site.config['qr_weibo'])}" alt="微博二维码">
|
||||
<figcaption>官方微博</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 友链区(浅绿带;无分组的站整段不渲染;分组无链接时防御性跳过——数据上分组由链接行物化,不可达) -->
|
||||
<div class="af-footer-links" th:if="${!#lists.isEmpty(site.linkGroups)}">
|
||||
<div class="af-container af-footer-links-inner">
|
||||
<div class="af-links-group" th:each="group : ${site.linkGroups}"
|
||||
th:if="${!#lists.isEmpty(group.links)}">
|
||||
<span class="af-group-name" th:text="${group.groupName}">分组</span>
|
||||
<p class="af-links-row">
|
||||
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener">
|
||||
<!-- B4 友链 Logo:有 logo 图片+名称并排(渲染期补基准),无 logo 纯文字 -->
|
||||
<img th:if="${link.logoUrl != null and !#strings.isEmpty(link.logoUrl)}" class="af-link-logo"
|
||||
th:src="${url.upload(link.logoUrl)}" th:alt="${link.name}" loading="lazy">
|
||||
<span th:text="${link.name}">链接</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底条(更深墨绿):全站无任何配置信息时仍保留结构完整(空块由 CSS 收敛) -->
|
||||
<div class="af-footer-bottom">
|
||||
<div class="af-container af-footer-bottom-inner">
|
||||
<p th:if="${site.schName != null and !#strings.isEmpty(site.schName)}"
|
||||
th:text="${site.schName}">学校名称</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
|
||||
<!-- 公共 head 片段:SEO(title/description/canonical)+ 主题资产(方案 §8.3/8.6),与 default 数据契约一致 -->
|
||||
<head th:fragment="head">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title th:text="${pageTitle}">页面标题</title>
|
||||
<meta name="description" th:if="${seoDescription != null and !#strings.isEmpty(seoDescription)}"
|
||||
th:content="${seoDescription}">
|
||||
<link rel="canonical" th:if="${canonical != null}" th:href="${canonical}">
|
||||
<!-- og 标签基本三件(C-7,方案 §8.6 canonical/og 同基准) -->
|
||||
<meta property="og:title" th:if="${pageTitle != null}" th:content="${pageTitle}">
|
||||
<meta property="og:description" th:if="${seoDescription != null and !#strings.isEmpty(seoDescription)}" th:content="${seoDescription}">
|
||||
<meta property="og:image" th:if="${ogImage != null}" th:content="${ogImage}">
|
||||
<link rel="stylesheet" th:href="${url.asset('css/main.css')}">
|
||||
<!-- 微调参数 CSS 变量覆盖(方案 §5.3):位于皮肤 CSS 之后层叠覆盖;值由服务端预构建并校验 -->
|
||||
<style th:if="${not #strings.isEmpty(themeVarsCss)}" th:utext="${themeVarsCss}"></style>
|
||||
<!-- 站点级自定义样式(B3):位于皮肤 CSS 与微调变量之后层叠覆盖;内容经服务端黑名单强校验后直出 -->
|
||||
<style th:if="${not #strings.isEmpty(customCss)}" th:utext="${customCss}"></style>
|
||||
<!-- favicon:管理端配置了 favicon 键用上传图,未配置输出主题内置兜底 icon(assets/favicon.png) -->
|
||||
<link rel="icon" type="image/png"
|
||||
th:href="${site.config['favicon'] != null ? url.upload(site.config['favicon']) : url.asset('favicon.png')}">
|
||||
</head>
|
||||
</html>
|
||||
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<!-- 公共头部(农林绿):白色页头(左校徽+校名 / 右工具行:日期+校园直通车下拉+搜索)
|
||||
+ 墨绿通栏主导航(白字,当前栏目 primaryLight 底),导航下方与 banner 无缝衔接。
|
||||
数据契约与 default 完全一致:topbar 日期由 main.js 填充、专业院系下拉(site.colleges 空则不渲染)。 -->
|
||||
<header class="af-header" th:fragment="header">
|
||||
<a class="af-skip-link" href="#main-content">跳到主要内容</a>
|
||||
|
||||
<!-- 白色页头 -->
|
||||
<div class="af-head">
|
||||
<div class="af-head-inner">
|
||||
<!-- 左:校徽 + 校名 -->
|
||||
<div class="af-brand">
|
||||
<img th:if="${site.config['logo'] != null}" class="af-logo"
|
||||
th:src="${url.upload(site.config['logo'])}" th:alt="${site.name}">
|
||||
<a class="af-sitename" th:href="${url.home()}" th:text="${site.name}">站点名称</a>
|
||||
</div>
|
||||
<!-- 右:工具行(今日日期 + 校园直通车 + 站内搜索);无子站站点下拉整块不渲染 -->
|
||||
<div class="af-head-side">
|
||||
<span class="af-date" id="siteDate" th:aria-label="'今日日期'">加载日期…</span>
|
||||
<div class="af-colleges" th:if="${!site.colleges.isEmpty()}"
|
||||
th:with="collegesLabel=${site.config['colleges_label'] ?: '校园直通车'}">
|
||||
<span class="af-colleges-toggle">
|
||||
<a href="javascript:void(0)" th:text="${collegesLabel}">校园直通车</a>
|
||||
<i class="af-colleges-arrow" aria-hidden="true">▾</i>
|
||||
</span>
|
||||
<ul class="af-colleges-menu">
|
||||
<li th:each="col : ${site.colleges}" th:classappend="${col.path == site.path} ? 'active'">
|
||||
<a th:href="${url.siteHome(col.path, col.publicBaseUrl)}"
|
||||
th:title="${col.name}" th:text="${col.name}">学院名称</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<form class="af-search" th:action="${url.search('', null)}" method="get" role="search">
|
||||
<input type="text" name="q" placeholder="站内搜索" aria-label="站内搜索">
|
||||
<button type="submit">搜索</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 墨绿通栏主导航:外链栏目直接外跳;其余进栏目页(方案 §8.1) -->
|
||||
<nav class="af-nav" aria-label="栏目导航">
|
||||
<ul>
|
||||
<li th:classappend="${currentCategoryId == null and navServiceActive == null} ? 'active'">
|
||||
<a th:href="${url.home()}">首页</a>
|
||||
</li>
|
||||
<li th:each="cat : ${navCategories}"
|
||||
th:classappend="${((currentCategoryId == cat.id or navTopId == cat.id) ? 'active' : '') + (!cat.children.isEmpty() ? ' has-sub' : '')}">
|
||||
<a th:if="${cat.type == 3}" th:href="${cat.linkUrl}" target="_blank" rel="noopener"
|
||||
th:text="${cat.name}">栏目</a>
|
||||
<a th:unless="${cat.type == 3}" th:href="${url.category(cat.id)}"><span th:text="${cat.name}">栏目</span><i th:if="${!cat.children.isEmpty()}" class="af-caret" aria-hidden="true">▾</i></a>
|
||||
<!-- 二级下拉:白底面板(.af-sub-nav 样式见 assets/css/main.css);当前项高亮(docs/11) -->
|
||||
<ul class="af-sub-nav" th:if="${!cat.children.isEmpty()}">
|
||||
<li th:each="sub : ${cat.children}"
|
||||
th:classappend="${sub.id == currentCategoryId} ? 'active'">
|
||||
<a th:if="${sub.type == 3}" th:href="${sub.linkUrl}" target="_blank" rel="noopener"
|
||||
th:text="${sub.name}">子栏目</a>
|
||||
<a th:unless="${sub.type == 3}" th:href="${url.category(sub.id)}" th:text="${sub.name}">子栏目</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
</html>
|
||||
@@ -0,0 +1,257 @@
|
||||
<!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}">更多 ></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 < 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}">更多 ></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}">更多 ></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}">更多 ></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}">更多 ></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>
|
||||
Reference in New Issue
Block a user