惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Security Blog
Microsoft Security Blog
Google DeepMind News
Google DeepMind News
P
Privacy International News Feed
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Threatpost
GbyAI
GbyAI
V
Visual Studio Blog
H
Help Net Security
Vercel News
Vercel News
P
Palo Alto Networks Blog
Project Zero
Project Zero
AWS News Blog
AWS News Blog
Latest news
Latest news
Cyberwarzone
Cyberwarzone
C
Cybersecurity and Infrastructure Security Agency CISA
The Register - Security
The Register - Security
博客园_首页
WordPress大学
WordPress大学
G
GRAHAM CLULEY
T
Tor Project blog
有赞技术团队
有赞技术团队
Know Your Adversary
Know Your Adversary
AI
AI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
O
OpenAI News
博客园 - 聂微东
月光博客
月光博客
S
Security Affairs
Webroot Blog
Webroot Blog
L
LangChain Blog
Apple Machine Learning Research
Apple Machine Learning Research
NISL@THU
NISL@THU
N
News and Events Feed by Topic
Blog — PlanetScale
Blog — PlanetScale
S
Securelist
V
Vulnerabilities – Threatpost
aimingoo的专栏
aimingoo的专栏
阮一峰的网络日志
阮一峰的网络日志
Stack Overflow Blog
Stack Overflow Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
D
DataBreaches.Net
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Y
Y Combinator Blog
Cisco Talos Blog
Cisco Talos Blog
The Cloudflare Blog
IT之家
IT之家
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
L
Lohrmann on Cybersecurity
T
The Blog of Author Tim Ferriss

Halo - 强大易用的开源建站工具

支持优惠券和折扣码,Halo 2.25 发布 AI Foundation:给 Halo 插件生态一层 AI 能力底座 Halo 应用市场已支持开发者入驻和创建应用 Halo 如何接入 Google Search Console 如何自动备份 Halo 网站 让 AI Agents 管理 Halo 网站内容 支持商城小程序,Halo 2.24 发布 如何在本地快速体验 Halo 使用 Halo CLI + GitHub Actions 持续部署主题 使用页面缓存插件优化 Halo 网站速度 阿里云 ESA 接入 Halo 配置指南 在本地搭建 Halo 插件开发环境 七牛云 DCDN 接入 Halo 配置指南 如何一键更新 Halo 的主题和插件 又拍云 CDN 接入 Halo 配置指南 腾讯云 EdgeOne 接入 Halo 配置指南 Halo 网站加载慢?8 种原因排查与优化方法 Halo 导入 Word 文档教程:批量导入并保留图片 Rybbit 接入 Halo 教程:开源隐私友好的流量分析工具 Umami 接入 Halo 教程:自托管网站流量统计 在 Halo 中使用 Markdown 写作的 6 种方式 如何从社区版切换到 Halo 付费版
主题开发技巧:为 Halo 网站添加一个知识库样式的页面
Ryan Wang · 2026-04-13 · via Halo - 强大易用的开源建站工具

随着 Halo 站点内容越来越丰富,许多站长会面临一个问题:博客列表页适合展示时效性内容,但对于产品文档、使用教程、常见问题等结构化知识内容来说,用普通博客列表来组织显得太扁平,用户很难快速找到自己需要的内容。

本文介绍如何利用 Halo 的自定义分类模板机制,在不依赖任何额外插件的情况下,为主题添加一套完整的知识库页面,包括知识库入口和子分类文章列表。

实现原理

Halo 支持在 theme.yaml 中为分类(category)注册额外的渲染模板,称为 自定义分类模板。用户在后台管理某个分类时,可以从下拉菜单中选择使用哪套模板来渲染该分类页面。

知识库的层级结构正好与分类树对应:

知识库根分类(使用 category_knowledge_base_root 模板)
├── 入门指南(使用 category_knowledge_base 模板)
│   ├── 文章 A
│   └── 文章 B
└── 进阶使用(使用 category_knowledge_base 模板)
    ├── 文章 C
    └── 文章 D

第一步:在 theme.yaml 注册模板

theme.yamlspec.customTemplates 下注册模板:

spec:
  customTemplates:
    post:
      - name: 知识库
        description: 知识库文章页
        file: post_knowledge_base.html
    category:
      - name: 知识库入口
        description: 知识库入口页,展示所有子分类及文章预览
        file: category_knowledge_base_root.html
      - name: 知识库
        description: 知识库子分类,展示该分类下的文章列表
        file: category_knowledge_base.html

修改完 theme.yaml 后,需要在后台 主题 → 重载主题配置 才能生效。之后在分类设置中就能看到这些模板选项。

第二步:知识库入口页

模板名称:category_knowledge_base_root.html

入口页负责展示整个知识库的全貌:标题、描述、搜索入口,以及所有子分类的卡片网格。

核心 API

  • categoryFinder.getByNames(category.spec.children) — 根据父分类的 children 字段获取子分类列表

  • postFinder.listByCategory(1, 5, subCat.metadata.name) — 获取每个子分类的前 5 篇文章

<!-- Hero 区域:展示知识库标题、描述和搜索入口 -->
<section class="hero">
  <h1 th:text="${category.spec.displayName}"></h1>
  <p
    th:text="${category.spec.description ?: '快速查找所需内容,轻松解决每一个问题。'}"
  ></p>

  <!-- 搜索按钮(需安装 PluginSearchWidget) -->
  <button
    th:if="${pluginFinder.available('PluginSearchWidget')}"
    onclick="javascript: SearchWidget.open();"
    type="button"
  >
    搜索文章...
  </button>
</section>

<!-- 子分类宫格 -->
<section class="kb-grid-section">
  <th:block
    th:with="subCategories = ${categoryFinder.getByNames(category.spec.children)}"
  >
    <div class="kb-grid">
      <th:block th:each="subCat : ${subCategories}">
        <div
          th:with="subPosts = ${postFinder.listByCategory(1, 5, subCat.metadata.name)}"
          class="kb-card"
        >
          <!-- 卡片头部:图标 + 名称 + 文章数 -->
          <a th:href="@{${subCat.status.permalink}}" class="kb-card-header">
            <img
              th:unless="${#strings.isEmpty(subCat.spec.cover)}"
              th:src="${subCat.spec.cover}"
            />
            <h2 th:text="${subCat.spec.displayName}"></h2>
            <span th:text="|${subCat.postCount} 篇文章|"></span>
          </a>
          <!-- 文章预览列表 -->
          <ul class="kb-article-list">
            <li th:each="post : ${subPosts.items}">
              <a
                th:href="@{${post.status.permalink}}"
                th:text="${post.spec.title}"
              ></a>
            </li>
            <li th:if="${subPosts.total > 5}">
              <a
                th:href="@{${subCat.status.permalink}}"
                th:text="|查看全部 ${subPosts.total} 篇 →|"
              ></a>
            </li>
          </ul>
        </div>
      </th:block>
    </div>
  </th:block>
</section>

有两处值得注意:搜索按钮通过 pluginFinder.available('PluginSearchWidget') 判断是否安装了搜索插件,安装了才渲染,点击后调用 SearchWidget.open() 打开全站搜索弹窗;子分类的图标直接使用了 category.spec.cover,可以在后台为每个子分类上传一张图标图片。

第三步:子分类文章列表页

模板名称:category_knowledge_base.html

该模板使用分类模板的标准变量 category(当前分类信息)和 posts(分页文章列表),展示当前子分类下的所有文章。

面包屑导航

利用 categoryFinder.getBreadcrumbs(category.metadata.name) 自动生成完整路径,无需手动维护层级关系:

<nav
  th:with="breadcrumbs = ${categoryFinder.getBreadcrumbs(category.metadata.name)}"
>
  <ol>
    <li><a href="/">首页</a></li>
    <li th:each="bc, stats : ${breadcrumbs}">
      <a
        th:unless="${stats.last}"
        th:href="@{${bc.status.permalink}}"
        th:text="${bc.spec.displayName}"
      ></a>
      <span
        th:if="${stats.last}"
        th:text="${bc.spec.displayName}"
        aria-current="page"
      ></span>
    </li>
  </ol>
</nav>

文章列表

文章列表不显示封面,只展示标题和发布日期,保持知识库的专注感:

<ul class="post-list">
  <li th:each="post : ${posts.items}">
    <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
    <time th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"></time>
  </li>
</ul>

<nav th:if="${posts.hasPrevious() || posts.hasNext()}">
  <a
    th:href="@{${posts.prevUrl}}"
    th:classappend="${!posts.hasPrevious() ? 'disabled' : ''}"
    rel="prev"
    >
    上一页
  </a>
  <span th:text="|${posts.page} / ${posts.totalPages}|"></span>
  <a
    th:href="@{${posts.nextUrl}}"
    th:classappend="${!posts.hasNext() ? 'disabled' : ''}"
    rel="next"
    >
  下一页
  </a>
</nav>

第四步:知识库文章页

模板名称:post_knowledge_base.html

这一步并非必须。如果你希望知识库的文章和其他文章样式不同(比如去掉封面图、简化作者信息、侧边栏只保留目录),可以单独新建一个文章模板 post_knowledge_base.html

文章模板同样可以使用 categoryFinder.getBreadcrumbs 来生成面包屑,只需从文章的第一个分类开始反查路径:

<th:block
  th:unless="${#lists.isEmpty(post.categories)}"
  th:with="breadcrumbs = ${categoryFinder.getBreadcrumbs(post.categories[0].metadata.name)}"
>
  <!-- 面包屑结构同上 -->
</th:block>

在 Halo 后台配置

完成模板开发后,在 Halo 后台按以下步骤配置:

新建知识库根分类:自定义模板选择「知识库入口」,如果你为知识库的文章单独新建了文章模板,还可以在此处设置自定义文章模板为「知识库」。另外建议勾选在列表中隐藏,勾选之后该分类下的文章将不会出现在博客归档、首页文章列表等公共列表中,相当于把知识库内容从博客流中独立出来。

Halo 编辑文章分类,选择自定义模板

新建子分类:在根分类下新建若干子分类(如「入门指南」「常见问题」),自定义模板选择「知识库」。日后发布文章时只需勾选对应子分类即可自动归入知识库体系。

Halo 编辑文章分类,选择自定义模板

总结

整套知识库功能完全基于 Halo 现有机制实现,无需额外插件:

功能 机制
多套页面样式 theme.yaml 自定义分类模板
子分类列表 categoryFinder.getByNames(category.spec.children)
文章预览列表 postFinder.listByCategory(page,size,name)
面包屑导航 categoryFinder.getBreadcrumbs(name)
搜索入口 pluginFinder.available() + SearchWidget.open()
内容隔离 分类「在列表中隐藏」选项

这种「模板即功能」的思路在 Halo 主题开发中非常实用,同样可以用来做产品发布页、案例展示页等差异化内容区域。