feat(B4): 友链页脚 Logo 渲染-5主题 img+名称并排,渲染期补基准

- PortalLink 模型已有 logoUrl,渲染缺口在模板:5 套 footer 增 th:if logo 图(url.upload 补基准+alt 名称+lazy)
- 5 套 main.css 增 .link-logo(高20px/contain/垂直居中/过宽截断)
- PortalSsrIT+2 例(有 logo 渲染图/无 logo 纯文字无 link-logo);mvn -o test 260/260 全绿
This commit is contained in:
chenxing
2026-09-07 20:22:44 +08:00
parent 19e414ee81
commit a3bee60b61
11 changed files with 117 additions and 10 deletions
@@ -330,3 +330,15 @@ time, .item-meta { color: var(--text-light); font-size: 13px; }
.home-main, .list-main, .detail-main { padding-top: 36px; padding-bottom: 56px; }
.article-list .article-title, .block-head, .page-title { letter-spacing: .02em; }
.article-list li, .banner, .banner-title { border-radius: 4px; }
/* B4 友链 Logo:与名称并排小图(高度 ~20px,垂直居中;过宽截断不撑破页脚) */
.links-group a .link-logo,
.sp-links-row a .link-logo {
height: 20px;
width: auto;
max-width: 64px;
object-fit: contain;
vertical-align: middle;
margin-right: 4px;
border-radius: 2px;
}
@@ -5,8 +5,11 @@
<div class="footer-links" th:if="${!#lists.isEmpty(site.linkGroups)}">
<div class="links-group" th:each="group : ${site.linkGroups}">
<span class="group-name" th:text="${group.groupName}">分组</span>
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener"
th:text="${link.name}">链接</a>
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener">
<img th:if="${link.logoUrl != null and !#strings.isEmpty(link.logoUrl)}" class="link-logo"
th:src="${url.upload(link.logoUrl)}" th:alt="${link.name}" loading="lazy">
<span th:text="${link.name}">链接</span>
</a>
</div>
</div>
<div class="footer-info">
@@ -332,3 +332,15 @@ time, .item-meta { color: var(--text-light); font-size: 13px; }
/* 头部样式参数(theme.json headerStyle=plain → 简洁顶栏) */
.site-header.header-plain { background: #fff; border-bottom: 2px solid var(--primary); }
.site-header.header-plain .site-name { color: var(--primary-dark); }
/* B4 友链 Logo:与名称并排小图(高度 ~20px,垂直居中;过宽截断不撑破页脚) */
.links-group a .link-logo,
.sp-links-row a .link-logo {
height: 20px;
width: auto;
max-width: 64px;
object-fit: contain;
vertical-align: middle;
margin-right: 4px;
border-radius: 2px;
}
@@ -5,8 +5,11 @@
<div class="footer-links" th:if="${!#lists.isEmpty(site.linkGroups)}">
<div class="links-group" th:each="group : ${site.linkGroups}">
<span class="group-name" th:text="${group.groupName}">分组</span>
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener"
th:text="${link.name}">链接</a>
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener">
<img th:if="${link.logoUrl != null and !#strings.isEmpty(link.logoUrl)}" class="link-logo"
th:src="${url.upload(link.logoUrl)}" th:alt="${link.name}" loading="lazy">
<span th:text="${link.name}">链接</span>
</a>
</div>
</div>
<div class="footer-info">
@@ -934,3 +934,15 @@ time, .item-meta { color: var(--text-muted); font-size: var(--fs-sm); }
.article-content table th { background: var(--surface-variant) !important; }
a { color: inherit; }
}
/* B4 友链 Logo:与名称并排小图(高度 ~20px,垂直居中;过宽截断不撑破页脚) */
.links-group a .link-logo,
.sp-links-row a .link-logo {
height: 20px;
width: auto;
max-width: 64px;
object-fit: contain;
vertical-align: middle;
margin-right: 4px;
border-radius: 2px;
}
@@ -8,8 +8,12 @@
<div class="links-group" th:each="group : ${site.linkGroups}"
th:if="${!#lists.isEmpty(group.links)}">
<span class="group-name" th:text="${group.groupName}">分组</span>
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener"
th:text="${link.name}">链接</a>
<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="link-logo"
th:src="${url.upload(link.logoUrl)}" th:alt="${link.name}" loading="lazy">
<span th:text="${link.name}">链接</span>
</a>
</div>
</div>
</div>
@@ -1293,3 +1293,15 @@ input, button, select, textarea { font-size: inherit; color: inherit; }
transition-duration: .01ms !important;
}
}
/* B4 友链 Logo:与名称并排小图(高度 ~20px,垂直居中;过宽截断不撑破页脚) */
.links-group a .link-logo,
.sp-links-row a .link-logo {
height: 20px;
width: auto;
max-width: 64px;
object-fit: contain;
vertical-align: middle;
margin-right: 4px;
border-radius: 2px;
}
@@ -41,8 +41,11 @@
th:if="${!#lists.isEmpty(group.links)}">
<span class="sp-group-name" th:text="${group.groupName}">分组</span>
<p class="sp-links-row">
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener"
th:text="${link.name}">链接</a>
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener">
<img th:if="${link.logoUrl != null and !#strings.isEmpty(link.logoUrl)}" class="link-logo"
th:src="${url.upload(link.logoUrl)}" th:alt="${link.name}" loading="lazy">
<span th:text="${link.name}">链接</span>
</a>
</p>
</div>
</div>
@@ -330,3 +330,15 @@ time, .item-meta { color: var(--text-light); font-size: 13px; }
.banner, .banner-title { border-radius: 20px; overflow: hidden; }
.site-search input, .site-search button { border-radius: 999px; }
.footer-info p { color: var(--primary-dark); }
/* B4 友链 Logo:与名称并排小图(高度 ~20px,垂直居中;过宽截断不撑破页脚) */
.links-group a .link-logo,
.sp-links-row a .link-logo {
height: 20px;
width: auto;
max-width: 64px;
object-fit: contain;
vertical-align: middle;
margin-right: 4px;
border-radius: 2px;
}
@@ -5,8 +5,11 @@
<div class="footer-links" th:if="${!#lists.isEmpty(site.linkGroups)}">
<div class="links-group" th:each="group : ${site.linkGroups}">
<span class="group-name" th:text="${group.groupName}">分组</span>
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener"
th:text="${link.name}">链接</a>
<a th:each="link : ${group.links}" th:href="${link.linkUrl}" target="_blank" rel="noopener">
<img th:if="${link.logoUrl != null and !#strings.isEmpty(link.logoUrl)}" class="link-logo"
th:src="${url.upload(link.logoUrl)}" th:alt="${link.name}" loading="lazy">
<span th:text="${link.name}">链接</span>
</a>
</div>
</div>
<div class="footer-info">
@@ -417,4 +417,35 @@ class PortalSsrIT extends CmsIntegrationTestBase {
assertThat(html).as("保存事件驱逐 cms:site 后输出新样式").contains(".mark-b{color:blue}");
assertThat(html).doesNotContain(".mark-a{color:red}");
}
// ===== B4 友链 Logo 页脚渲染(有 logo 图+名,无 logo 纯文字)=====
@Test
@DisplayName("B4: 带 logo 友链页脚渲染图片(补基准 URL + alt=名称 + lazy)")
void linkWithLogo_rendersImage() throws Exception {
linkRepository.findById(
linkRepository.findBySiteIdOrderByGroupNameAscSortNoAscIdAsc(siteId).get(0).getId())
.ifPresent(l -> {
l.setLogoUrl("/cms/upload/cms/demo1/202609/friend-logo.png");
linkRepository.saveAndFlush(l);
});
MvcResult result = mockMvc.perform(get("/portal/demo1/")).andExpect(status().isOk())
.andReturn();
String html = body(result);
assertThat(html)
.contains("class=\"link-logo\"")
.contains("loading=\"lazy\"")
.contains("src=\"http://localhost/cms/upload/cms/demo1/202609/friend-logo.png\"")
.contains("alt=\"教育部\"");
}
@Test
@DisplayName("B4: 无 logo 友链纯文字渲染(页脚无 link-logo 图片)")
void linkWithoutLogo_rendersTextOnly() throws Exception {
MvcResult result = mockMvc.perform(get("/portal/demo1/")).andExpect(status().isOk())
.andReturn();
String html = body(result);
assertThat(html).contains("<span>教育部</span>");
assertThat(html).doesNotContain("link-logo");
}
}