主题开发文档
博客系统 v1.0+ 支持完全自定义的主题系统
概述
主题系统允许开发者创建完全自定义的博客外观和布局。每个主题是一个独立的目录,包含配置文件、模板文件和静态资源。主题拥有完全的自主权——自行管理 CSS 样式、布局结构和静态资源,系统不做强制约束。
目录结构
themes/
└── my-theme/
├── manifest.json # 主题配置(必需)
├── layouts/ # 布局模板(必需)
│ └── main.hbs # 主布局(必需,上传主题时强制验证)
├── pages/ # 页面模板(可选,没有则使用默认主题的页面)
│ ├── index.hbs # 首页
│ ├── post.hbs # 文章详情
│ ├── category.hbs # 分类页
│ ├── tag.hbs # 标签页
│ ├── 404.hbs # 404 页面
│ └── error.hbs # 错误页
├── partials/ # 片段模板(可选)
│ └── sidebar.hbs
└── public/ # 静态资源(可选,CSS/JS/图片等)
├── css/
│ └── style.css
├── js/
└── images/
文件要求
| 文件/目录 | 必需性 | 说明 |
|---|---|---|
manifest.json |
必需 | 主题配置文件 |
layouts/main.hbs |
必需 | 主布局,上传主题时强制验证,没有则拒绝安装 |
pages/ |
可选 | 页面模板,没有的页面会自动使用默认主题的同名页面 |
partials/ |
可选 | 片段模板,不支持 fallback |
public/ |
可选 | 静态资源,主题可自由组织目录结构 |
manifest.json
主题的配置文件,定义主题的基本信息和能力:
{
"name": "my-theme",
"version": "1.0.0",
"description": "我的自定义主题",
"author": "作者名",
"hooks": [
"head_css",
"after_header",
"before_nav_end",
"before_content",
"after_content",
"after_post_content",
"sidebar_top",
"sidebar_bottom",
"before_footer",
"body_js"
]
}
字段说明
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
| name | string | 是 | 主题唯一名称(目录名) |
| version | string | 是 | 版本号 |
| description | string | 否 | 主题描述 |
| author | string | 否 | 作者 |
| hooks | string[] | 否 | 声明支持的 Hook 注入点 |
样式管理
主题完全自主管理自己的 CSS 文件,可以放在主题目录的任意位置。在 layouts/main.hbs 中通过 <link> 标签引入即可。
引入方式
<head>
<link rel="stylesheet" href="/themes/my-theme/css/style.css">
<!-- 或任何主题自定义的路径 -->
</head>
推荐的静态资源目录结构
themes/my-theme/public/
├── css/
│ └── style.css
├── js/
│ └── main.js
└── images/
└── logo.png
静态文件通过 /themes/主题名/ 路径访问,例如 /themes/my-theme/css/style.css。
布局模板 (layouts/main.hbs)
主布局文件定义了页面的整体结构,每个主题必须提供自己的 layouts/main.hbs,不支持 fallback。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{title}} - {{../siteName}}</title>
<link rel="stylesheet" href="/themes/{{../themeName}}/css/style.css">
{{#pluginHook "head_css"}}{{/pluginHook}}
</head>
<body>
<header>
<nav>
<a href="/">{{../siteName}}</a>
<ul>
{{#pluginHook "before_nav_end"}}{{/pluginHook}}
</ul>
</nav>
{{#pluginHook "after_header"}}{{/pluginHook}}
</header>
{{#pluginHook "before_content"}}{{/pluginHook}}
<main>
{{{body}}}
</main>
{{#pluginHook "after_content"}}{{/pluginHook}}
{{#pluginHook "before_footer"}}{{/pluginHook}}
<footer>
<p>© {{currentYear}} {{../siteName}}</p>
{{#if ../icpNumber}}
<p>ICP备案: {{../icpNumber}}</p>
{{/if}}
</footer>
{{{pluginScriptTags}}}
{{#pluginHook "body_js"}}{{/pluginHook}}
</body>
</html>
重要变量
在布局中可通过 ../ 访问全局变量:
| 变量 | 说明 |
|---|---|
| siteName | 站点名称 |
| siteDescription | 站点描述 |
| themeName | 当前主题名称 |
| icpNumber | ICP备案号 |
| pluginHookData | 插件注入内容 |
| pluginScriptTags | 插件脚本注入标签 |
| plugins | 激活的插件信息 |
| activePlugins | 激活的插件列表 |
页面模板 (pages/)
页面模板是可选的。如果主题没有提供某个页面,系统会自动使用默认主题的对应页面,并配合当前主题的 layouts/main.hbs 渲染。
可用的页面模板
| 页面文件 | 说明 | Fallback |
|---|---|---|
pages/index.hbs |
首页 | 支持 |
pages/post.hbs |
文章详情 | 支持 |
pages/category.hbs |
分类页 | 支持 |
pages/tag.hbs |
标签页 | 支持 |
pages/404.hbs |
404 页面 | 支持 |
pages/error.hbs |
错误页面 | 支持 |
首页 (index.hbs)
文章列表页面:
<div class="container">
<aside class="sidebar">
{{#pluginHook "sidebar_top"}}{{/pluginHook}}
<div class="categories">
<h3>分类</h3>
<ul>
<li>
<a href="/" class="{{#unless currentCategory}}active{{/unless}}">
最近更新
</a>
</li>
{{#each categories}}
<li>
<a href="/category/{{slug}}"
class="{{#if (eq ../currentCategory.id id)}}active{{/if}}">
{{name}} ({{postCount}})
</a>
</li>
{{/each}}
</ul>
</div>
{{#pluginHook "sidebar_bottom"}}{{/pluginHook}}
</aside>
<main class="posts">
{{#each posts}}
<article class="post-card">
<h2><a href="/post/{{slug}}">{{title}}</a></h2>
<div class="meta">
<span>{{formatDate updatedAt}}</span>
{{#each tags}}
<a href="/tag/{{slug}}" class="tag">{{name}}</a>
{{/each}}
</div>
<p>{{excerpt}}</p>
</article>
{{/each}}
{{#if pagination.hasPrev}}
<a href="?page={{pagination.prev}}" class="pagination">上一页</a>
{{/if}}
{{#if pagination.hasNext}}
<a href="?page={{pagination.next}}" class="pagination">下一页</a>
{{/if}}
</main>
</div>
文章页 (post.hbs)
文章详情页面:
<article class="post-detail">
<h1>{{post.title}}</h1>
<div class="meta">
<span>{{formatDate post.updatedAt}}</span>
{{#each post.tags}}
<a href="/tag/{{slug}}" class="tag">{{name}}</a>
{{/each}}
</div>
<div class="content">
{{{post.htmlContent}}}
</div>
{{#pluginHook "after_post_content"}}{{/pluginHook}}
</article>
可用变量
首页/分类/标签页变量:
| 变量 | 类型 | 说明 |
|---|---|---|
| title | string | 页面标题 |
| posts | array | 文章列表 |
| categories | array | 分类列表(含文章数) |
| currentCategory | object/null | 当前分类 |
| pagination | object | 分页信息 |
| siteName | string | 站点名称 |
文章页变量:
| 变量 | 类型 | 说明 |
|---|---|---|
| title | string | 文章标题 |
| post | object | 文章对象 |
| post.title | string | 文章标题 |
| post.content | string | 原始 Markdown |
| post.htmlContent | string | 渲染后的 HTML |
| post.excerpt | string | 文章摘要 |
| post.slug | string | URL slug |
| post.createdAt | date | 创建时间 |
| post.updatedAt | date | 更新时间 |
| post.author | object | 作者信息 |
| post.tags | array | 标签列表 |
| post.category | object | 分类信息 |
Fallback 机制
系统对不同类型的模板文件采用不同的 fallback 策略:
| 资源类型 | Fallback 行为 |
|---|---|
layouts/main.hbs |
不支持 fallback,主题必须提供,上传时强制验证 |
partials/*.hbs |
不支持 fallback,主题必须自行提供 |
pages/*.hbs |
支持 fallback,主题没有则使用默认主题的页面 |
当页面使用默认主题的 page 模板时,布局仍然使用当前主题的 layouts/main.hbs。
Hook 注入点
主题可以在以下位置声明 Hook,插件内容会自动注入:
| Hook 名称 | 位置 | 说明 |
|---|---|---|
| head_css | <head> 内 |
注入 CSS 链接 |
| after_header | <header> 后 |
紧跟 header 后 |
| before_nav_end | 导航内部末尾 | 导航栏末尾 |
| before_content | <main> 前 |
主内容区之前 |
| after_content | <main> 后 |
主内容区之后 |
| after_post_content | 文章内容后 | 文章正文之后 |
| sidebar_top | 侧边栏顶部 | 侧边栏开始处 |
| sidebar_bottom | 侧边栏底部 | 侧边栏结束处 |
| before_footer | <footer> 前 |
Footer 之前 |
| body_js | <body> 末尾 |
注入 JS 脚本 |
Helper 函数
模板中可用的辅助函数:
| 函数 | 说明 | 示例 |
|---|---|---|
| eq | 相等判断 | {{#if (eq a b)}} |
| includes | 包含判断 | {{#if (includes arr val)}} |
| add | 加法 | {{add page 1}} |
| subtract | 减法 | {{subtract total 1}} |
| formatDate | 日期格式化 | {{formatDate date}} |
| currentYear | 当前年份 | {{currentYear}} |
| json | JSON 序列化 | {{json data}} |
| pluginHook | 插件内容注入 | {{#pluginHook "hookName"}}{{/pluginHook}} |
安装主题
方式一:直接放置
- 将主题目录复制到
themes/目录 - 确保目录名与
manifest.json中的name一致 - 确保主题包含
layouts/main.hbs文件 - 在后台管理页面激活主题
方式二:上传安装
- 进入 后台管理 → 主题管理
- 点击「上传主题」按钮
- 选择主题 ZIP 包(包含完整主题目录)
- 系统自动解压并验证
manifest.json和layouts/main.hbs - 如果缺少必要文件,安装将失败并提示具体原因
- 验证通过后自动安装,然后激活主题
上传验证规则
| 验证项 | 要求 | 验证时机 |
|---|---|---|
manifest.json |
必须存在 | 解压后 |
layouts/main.hbs |
必须存在 | 解压后、保存前 |
开发示例
参考默认主题:themes/default/