Halo【Walker】主题改造记录(一):自定义友链与动态页脚
编辑
说起来,
Walker主题算是我在Halo市场里第一款一见钟情的主题了,那种简洁与质感真的深得我心。也正因如此,当初没能及时入手,一直让我耿耿于怀。最近,虽然也在关注 「微浸」 主题的新测试版,但还是打算等它更稳定一些再考虑切换。正好趁着这个空档,在 “海鲜市场” 淘了一个月的专业版授权,也算是圆了自己一个小小的执念。
Walker 主题的设计虽已足够出色,但我还是希望在细节上能更贴合个人的使用习惯。本文档主要记录针对 「友链页面」 和 「网站页脚」 两部分的具体改造过程。
✨ 友链页面改造:实现信息与规则选项卡
为了更清晰地展示本站信息及友链申请规则,决定对默认的友链页面进行功能增强,设计一个集成了 “站点信息” 和 “友链规则” 的选项卡组件。
1. 修改主题模板以暴露站点信息
为了在前端动态获取站点的基础信息(如标题、URL等),需要对主题的模板文件进行微调。由于 Walker 主题已趋于稳定基本很少更新,直接修改源文件是目前较为直接的方式。
定位到主题文件:/halo/data/themes/theme-walker/templates/links.html。
在该文件的 <th:block th:fragment="content"> 标签内部,添加以下脚本,通过 Thymeleaf 的内联功能将后端的 site对象数据传递给前端的 window.siteInfo 变量。
<script th:inline="javascript">
/* 把 Halo 后端的 site 对象序列化成 JSON,并赋值给 window.siteInfo */
window.siteInfo = {
title: /*[[${site.title}]]*/,
description: /*[[${site.seo.description}]]*/,
url: /*[[${site.url}]]*/,
rss: /*[[${site.url+'/rss.xml'}]]*/,
logo: /*[[${site.url+site.logo}]]*/};
</script>
2. 注入页面样式与交互脚本
前往 Halo 后台的 主题设置 -> 插件适配 -> 页面底部内容,将下方的 HTML、CSS 和 JavaScript 代码完整粘贴进去。这段代码负责构建选项卡的 UI、定义样式并实现交互逻辑。
- 动态规则维护:仅需修改 JavaScript 中的
friendshipRules数组,即可更新友链规则,无需改动其他代码。 - 一键复制功能:站点信息列表集成了复制按钮,方便访客操作。
- Toast:由于之前修改友链提交插件时已经添加了全局Toast,这里不再添加。
<style>
/* 容器样式 */
.friendship-container {
border-radius: 8px;
border: 1px solid rgba(128, 128, 128, 0.2);
background: transparent;
overflow: hidden;
}
/* 选项卡 */
.tab-buttons {
display: flex;
padding: 4px;
background: rgba(128, 128, 128, 0.05);
border-bottom: 1px solid rgba(128, 128, 128, 0.2);
}
.tab-button {
flex: 1;
padding: 10px 20px;
border: none;
background: transparent;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
border-radius: 4px;
/*opacity: 0.7;*/
}
.tab-button.active {
font-weight: 600;
background: rgba(128, 128, 128, 0.05);
opacity: 1;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.tab-button:hover:not(.active) {
background: rgba(128, 128, 128, 0.1);
opacity: 0.9;
}
.tab-panel {
display: none;
padding: 24px;
}
.tab-panel.active {
display: block;
animation: friendshipFadeIn 0.3s ease;
}
@keyframes friendshipFadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 信息项 */
.info-item, .rule-item {
display: flex;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid rgba(128, 128, 128, 0.2);
gap: 12px;
}
.info-item:last-child, .rule-item:last-child {
border-bottom: none;
}
.info-left {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.item-dot {
width: 8px;
height: 8px;
background: #4CAF50;
border-radius: 50%;
flex-shrink: 0;
}
.info-label, .rule-number {
font-weight: 600;
font-size: 14px;
min-width: 50px;
flex-shrink: 0;
}
.rule-number {
min-width: 20px;
}
.info-value, .rule-text {
font-size: 14px;
line-height: 1.4;
flex: 1;
/*opacity: 0.7;*/
margin: 0;
}
/* 复制按钮 */
.copy-btn {
background: transparent;
border: none;
padding: 4px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
width: 24px;
height: 24px;
opacity: 0.5;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.copy-btn::before {
content: '📋';
font-size: 14px;
}
.copy-btn:hover {
background: rgba(76, 175, 80, 0.1);
color: #4CAF50;
opacity: 1;
}
.copy-btn:active {
background: rgba(76, 175, 80, 0.2);
transform: scale(0.9);
}
/* 响应式 */
@media (max-width: 768px) {
.tab-panel {
padding: 20px;
}
.info-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.info-left {
width: 100%;
}
.copy-btn {
align-self: flex-end;
}
}
</style>
<h2 class="mb-6 inline-flex items-center gap-2 text-lg text-base-content/90">
<svg style="width:18px;margin:auto 0" class="inline-block" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M333.824 986.112H178.176c-78.848 0-143.36-64.512-143.36-143.36V175.616c0-78.848 64.512-143.36 143.36-143.36h667.136c78.848 0 143.36 64.512 143.36 143.36v155.136c0 22.528-18.432 40.96-40.96 40.96s-40.96-18.432-40.96-40.96V175.616c0-33.792-27.648-61.44-61.44-61.44H178.176c-33.792 0-61.44 27.648-61.44 61.44v667.136c0 33.792 27.648 61.44 61.44 61.44h155.136c22.528 0 40.96 18.432 40.96 40.96s-17.92 40.96-40.448 40.96zM494.08 976.384c-10.24 0-20.992-4.096-29.184-11.776-15.872-15.872-15.872-41.984 0-57.856L916.48 454.656c15.872-15.872 41.984-15.872 57.856 0 15.872 15.872 15.872 41.984 0 57.856l-451.584 451.584c-7.68 8.192-18.432 12.288-28.672 12.288z" fill="currentColor"></path>
<path d="M782.336 340.992H241.664c-22.528 0-40.96-18.432-40.96-40.96s18.432-40.96 40.96-40.96h541.184c22.528 0 40.96 18.432 40.96 40.96s-18.432 40.96-41.472 40.96zM557.056 619.008H241.664c-22.528 0-40.96-18.432-40.96-40.96s18.432-40.96 40.96-40.96h315.904c22.528 0 40.96 18.432 40.96 40.96s-18.432 40.96-41.472 40.96z" fill="currentColor"></path>
</svg>
友链信息
</h2>
<div >
<div class="friendship-container">
<div class="tab-buttons">
<button class="tab-button text-base-content" data-tab="info">站点信息</button>
<button class="tab-button active text-base-content" data-tab="rules">友链规则</button>
</div>
<div class="tab-panel" id="info">
<div class="info-item">
<div class="info-left">
<div class="item-dot"></div>
<div class="info-label text-base-content">站名</div>
<div class="info-value text-base-content/60" data-info="title"></div>
</div>
<button class="copy-btn" onclick="copyToClipboard(window.siteInfo?.title)"></button>
</div>
<div class="info-item">
<div class="info-left">
<div class="item-dot"></div>
<div class="info-label text-base-content">描述</div>
<div class="info-value text-base-content/60" data-info="description"></div>
</div>
<button class="copy-btn" onclick="copyToClipboard(window.siteInfo?.description)"></button>
</div>
<div class="info-item">
<div class="info-left">
<div class="item-dot"></div>
<div class="info-label text-base-content">链接</div>
<div class="info-value text-base-content/60" data-info="url"></div>
</div>
<button class="copy-btn" onclick="copyToClipboard(window.siteInfo?.url)"></button>
</div>
<div class="info-item">
<div class="info-left">
<div class="item-dot"></div>
<div class="info-label text-base-content">订阅</div>
<div class="info-value text-base-content/60" data-info="rss"></div>
</div>
<button class="copy-btn" onclick="copyToClipboard(window.siteInfo?.rss)"></button>
</div>
<div class="info-item">
<div class="info-left">
<div class="item-dot"></div>
<div class="info-label text-base-content">标识</div>
<div class="info-value text-base-content/60" data-info="logo"></div>
</div>
<button class="copy-btn" onclick="copyToClipboard(window.siteInfo?.logo)"></button>
</div>
</div>
<div class="tab-panel active" id="rules">
</div>
</div>
</div>
<script>
// 友链规则数组
const friendshipRules = [
'恕不接受"零互动"的友链申请。如果您认同本站,欢迎在评论区留下足迹。',
'希望贵站已用心经营超一年(10+文章)、内容遵守中国相关法律法规,且半年内有过更新。',
'认同是交换的基础,所以申请前请先将本站链接添加至贵站。',
'若链接长期失效或贵站单方面移除本站,链接将不作通知直接移除。',
'如果我们是旧识,可直接申请。'
];
document.addEventListener('DOMContentLoaded', function() {
generateRules();
// 设置站点信息
window.siteInfo.description='眼睛记得的光影,心记得的温度,它们不会消散,只是藏进岁月的褶皱里。某一天,风一吹,它们又鲜活如初...';
// 更新显示
document.querySelectorAll('[data-info]').forEach(el => {
el.textContent = window.siteInfo[el.getAttribute('data-info')] || '';
});
// 选项卡切换
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', function() {
const targetTab = this.getAttribute('data-tab');
document.querySelectorAll('.tab-button, .tab-panel').forEach(el => el.classList.remove('active'));
this.classList.add('active');
document.getElementById(targetTab).classList.add('active');
});
});
});
// 动态生成友链规则
function generateRules() {
const rulesContainer = document.getElementById('rules');
if(!rulesContainer)return;
rulesContainer.innerHTML = '';
friendshipRules.forEach((rule, index) => {
const ruleItem = document.createElement('div');
ruleItem.className = 'rule-item';
ruleItem.innerHTML = `
<div class="rule-number text-base-content">${index + 1}.</div>
<div class="rule-text text-base-content/60">${rule}</div>
`;
rulesContainer.appendChild(ruleItem);
});
}
// 复制功能
function copyToClipboard(text) {
if (!text) return;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
showToast?.('✅ 已复制到剪贴板', 'success', 2000);
}).catch(() => {
showToast?.('请手动复制', 'info', 2000);
});
} else {
showToast?.('请手动复制', 'info', 2000);
}
}
</script>
3. 效果

🚀 页脚优化:添加动态导航链接
为了方便快速导航,同时也习惯了 微浸主题 的页脚设计,在 Walker 主题的页脚处也实现一个简易的导航链接模块。该功能通过主题设置中的 “页脚内容” 注入实现,维护 links 对象即可动态生成导航。
设置
在 主题设置 -> 页脚 -> 版权信息 中追加以下代码:
Powered by
<a href="https://www.halo.run" class="hover:text-base-content" target="_blank">Halo</a> Theme
<a href="https://www.halo.run/store/apps/app-GHgAR" target="_blank" class="hover:text-base-content">Walker</a>
// [!code ++:6]
<!-- 瞬间页面字体颜色问题 -->
<style>
.markdown-body.moment-content p span[style] {
color: unset !important;
}
</style>
<!-- 页脚导航模块 - 符合Walker主题风格 -->
<style>
.footer-nav {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
font-size: 14px;
line-height: 1.5;
color: inherit;
opacity: 0.7;
margin: 8px 0;
}
.footer-nav .nav-separator {
color: inherit;
opacity: 0.5;
margin: 0 8px;
font-weight: 300;
font-size: 12px;
}
.footer-nav a {
color: inherit;
text-decoration: none;
transition: all 0.2s ease;
opacity: 0.8;
position: relative;
}
.footer-nav a:hover {
opacity: 1;
}
.footer-nav a::after {
content: '';
position: absolute;
width: 0;
height: 1px;
bottom: 0;
left: 50%;
background: currentColor;
transition: all 0.2s ease;
transform: translateX(-50%);
}
.footer-nav a:hover::after {
width: 100%;
}
/* 响应式设计 */
@media (max-width: 768px) {
.footer-nav {
/* 固定在底部 */
position: fixed;
bottom: 0;
left: 0;
right: 0;
box-shadow: 0 -1px 3px var(--fallback-bc, oklch(var(--bc) / .2));
z-index: 1000; /* 确保在其他内容之上 */
opacity:1;
margin:0;
padding:10px 0;
display: flex;
flex-wrap: wrap;
}
.footer-nav a {
margin:0px 6px;
}
/* 返回顶部按钮位置调整 */
#btn-scroll-to-top{
bottom:2.8rem;
}
}
</style>
<div class="footer-nav bg-base-100">
<!-- 动态导航链接 -->
<span class="nav-links"></span>
<!-- 分隔符会由JavaScript动态生成 -->
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 导航链接配置
const links = {
'星图': 'https://timxs.com',
'锚点': '/tags',
'星痕': '/moments?tag=网站',
'回音': '/comments',
'航令': '/about'
};
// 动态生成导航链接
function generateNavLinks() {
const navLinksContainer = document.querySelector('.nav-links');
if (!navLinksContainer) return;
const linkElements = Object.entries(links).map(([text, url], index) => {
const link = document.createElement('a');
link.href = url;
link.textContent = text;
link.target = url.startsWith('http') ? '_blank' : '_self';
// 如果不是最后一个链接,添加分隔符
if (index < Object.entries(links).length - 1) {
const separator = document.createElement('span');
separator.className = 'nav-separator';
separator.textContent = '·'; // 使用更优雅的中点符号
return [link, separator];
}
return [link];
}).flat();
// 清空容器并添加新链接
navLinksContainer.innerHTML = '';
linkElements.forEach(element => {
navLinksContainer.appendChild(element);
});
}
// 初始化导航链接
generateNavLinks();
});
</script>
效果

✍️ 写在结尾
至此,两项主要的界面改造已完成。通过这些定制,Walker 主题在保留其核心设计的同时,也更贴合了个人的功能需求和审美偏好。
- 0
- 1
-
分享