feat(theme): 新增三套高校官网风皮肤 poly-blue/agri-forest/jade-normal
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:
chenxing
2026-09-20 02:31:53 +08:00
parent d2df12b91e
commit 82d6ff4602
50 changed files with 9289 additions and 0 deletions
@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
<head th:replace="~{${themeBase} + '/fragments/head' :: head}"></head>
<body>
<header th:replace="~{${themeBase} + '/fragments/header' :: header}"></header>
<!-- 服务中心(事项库 §6.1 契约):serviceList / serviceFilters / activeFilters / q;
筛选与分页 URL 全部后端预生成(facets[].url / pageUrls[].url),模板禁止手拼 query -->
<main class="af-page af-container" id="main-content">
<div class="af-page-card af-svc-page">
<nav class="af-crumb" aria-label="面包屑">
<a th:href="${url.home()}">首页</a>
<span class="af-crumb-sep" aria-hidden="true">/</span>
<span class="af-crumb-current">服务中心</span>
</nav>
<h1 class="af-page-title">服务中心</h1>
<!-- P1-4 停机空态:svc-hall 不可达时降级渲染(200),不出 500 错误页 -->
<div class="af-empty af-svc-unavailable" th:if="${svcUnavailable}">
<p>事项服务暂时不可用,请稍后再试</p>
<p class="af-empty-hint">事项数据服务暂不可达,稍后刷新即可恢复</p>
</div>
<div class="af-svc-filters"
th:if="${!svcUnavailable and (!#lists.isEmpty(serviceFilters.objects) or !#lists.isEmpty(serviceFilters.categories) or !#lists.isEmpty(serviceFilters.depts))}">
<div class="af-svc-filter-group" th:if="${!#lists.isEmpty(serviceFilters.objects)}">
<span class="af-svc-filter-label">服务对象</span>
<div class="af-svc-filter-options">
<a class="af-svc-chip" th:each="opt : ${serviceFilters.objects}"
th:classappend="${opt.active} ? 'active'" th:href="${opt.url}">
<span th:text="${opt.name}">对象</span><em th:text="${opt.count}">0</em>
</a>
</div>
</div>
<div class="af-svc-filter-group" th:if="${!#lists.isEmpty(serviceFilters.categories)}">
<span class="af-svc-filter-label">事项类别</span>
<div class="af-svc-filter-options">
<a class="af-svc-chip" th:each="opt : ${serviceFilters.categories}"
th:classappend="${opt.active} ? 'active'" th:href="${opt.url}">
<span th:text="${opt.name}">类别</span><em th:text="${opt.count}">0</em>
</a>
</div>
</div>
<div class="af-svc-filter-group" th:if="${!#lists.isEmpty(serviceFilters.depts)}">
<span class="af-svc-filter-label">服务部门</span>
<div class="af-svc-filter-options">
<a class="af-svc-chip" th:each="opt : ${serviceFilters.depts}"
th:classappend="${opt.active} ? 'active'" th:href="${opt.url}">
<span th:text="${opt.name}">部门</span><em th:text="${opt.count}">0</em>
</a>
</div>
</div>
</div>
<div class="af-svc-toolbar" th:if="${!svcUnavailable}">
<form class="af-svc-search" th:action="${url.serviceList()}" method="get" role="search">
<input type="search" name="q" th:value="${q}" placeholder="搜索事项名称 / 简介" maxlength="64">
<button type="submit">搜索</button>
</form>
<a class="af-svc-clear" th:if="${activeFilters.hasAny}" th:href="${activeFilters.clearUrl}">清除筛选</a>
</div>
<ul class="af-svc-list" th:if="${!svcUnavailable}">
<li th:each="it : ${serviceList.items}">
<a class="af-svc-item" th:href="${url.serviceItem(it.id)}">
<span class="af-svc-item-cover" th:if="${it.coverImage != null}">
<img th:src="${url.upload(it.coverImage)}" th:alt="${it.title}">
</span>
<span class="af-svc-item-body">
<span class="af-svc-item-title">
<span class="af-svc-item-name" th:text="${it.title}">事项名称</span>
<span class="af-svc-tag online" th:if="${it.onlineFlag == 1}">可在线办理</span>
<span class="af-svc-tag"
th:text="${it.handleMode == 1 ? '线上办理' : (it.handleMode == 2 ? '线下办理' : '线上线下')}">办理方式</span>
</span>
<span class="af-svc-item-summary" th:if="${!#strings.isEmpty(it.summary)}"
th:text="${it.summary}">简介</span>
<span class="af-svc-item-meta">
<span th:if="${!#strings.isEmpty(it.deptName)}" th:text="${it.deptName}">部门</span>
<span th:if="${!#strings.isEmpty(it.categoryName)}" th:text="${it.categoryName}">类别</span>
<time th:if="${it.publishTime != null}"
th:text="${#temporals.format(it.publishTime, 'yyyy-MM-dd')}">2026-01-01</time>
</span>
</span>
</a>
</li>
</ul>
<div class="af-empty" th:if="${!svcUnavailable and #lists.isEmpty(serviceList.items)}">
<p th:if="${activeFilters.hasAny}">未找到符合条件的服务事项</p>
<p th:if="${!activeFilters.hasAny}">服务中心暂未上架事项</p>
<p class="af-empty-hint" th:if="${activeFilters.hasAny}">换个筛选条件或关键词试试</p>
<p class="af-empty-hint" th:if="${!activeFilters.hasAny}">事项发布后将在此展示</p>
</div>
<!-- 分页:URL 由后端预生成(携带当前筛选,pageUrls[].active 标记当前页);停机态 totalPages=0 自然隐藏 -->
<nav class="af-pagination" th:if="${!svcUnavailable and serviceList.totalPages > 1}" aria-label="分页">
<a th:each="p : ${serviceList.pageUrls}"
th:classappend="${p.active} ? 'active'"
th:href="${p.url}"
th:aria-label="${'第' + p.page + '页'}"
th:aria-current="${p.active} ? 'page'"
th:text="${p.page}">1</a>
</nav>
</div>
</main>
<footer th:replace="~{${themeBase} + '/fragments/footer' :: footer}"></footer>
<script th:src="${url.asset('js/main.js')}"></script>
</body>
</html>