Tim's Blog

Tim's Blog

Halo【Walker】主题改造记录(一):自定义友链与动态页脚

2025-09-17
Halo【Walker】主题改造记录(一):自定义友链与动态页脚

说起来,Walker 主题算是我在 Halo 市场里第一款一见钟情的主题了,那种简洁与质感真的深得我心。也正因如此,当初没能及时入手,一直让我耿耿于怀。

最近,虽然也在关注 「微浸」 主题的新测试版,但还是打算等它更稳定一些再考虑切换。正好趁着这个空档,在 “海鲜市场” 淘了一个月的专业版授权,也算是圆了自己一个小小的执念。

Walker 主题的设计虽已足够出色,但我还是希望在细节上能更贴合个人的使用习惯。本文档主要记录针对 「友链页面」「网站页脚」 两部分的具体改造过程。

tips:info 20250918 修复移动端造成页面拉伸问题,移动端页脚导航链接改悬浮
tips:info 20251104 通过主题配置中「页脚」注入CSS样式,解决瞬间页面因span标签造成字体颜色异常问题

✨ 友链页面改造:实现信息与规则选项卡

为了更清晰地展示本站信息及友链申请规则,决定对默认的友链页面进行功能增强,设计一个集成了 “站点信息”“友链规则” 的选项卡组件。

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 后台的 主题设置 -> 插件适配 -> 页面底部内容,将下方的 HTMLCSSJavaScript 代码完整粘贴进去。这段代码负责构建选项卡的 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. 效果

doc_walker1.webp

🚀 页脚优化:添加动态导航链接

为了方便快速导航,同时也习惯了 微浸主题 的页脚设计,在 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>

效果

doc_walker2.webp

✍️ 写在结尾

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