
能力有限,目前主题开发只是在大佬们开发的主题上折腾,加上一些自己想要的功能(Memos,时间轴以及随手拍等)。
自定义模板
一般来说,新增功能页面通过新增自定义模板的方式来进行会比较方便,最好就是有功能相近的页面进行参考。例如我的Memos页面就是搬了瞬间的前端代码,修改了获取渲染数据的逻辑代码,来达成展示Memos数据的目标。
说是模板,但无非也是一个HTML页面,实现非模板的功能页面当然也可以。
目前 Halo 支持为 文章、独立页面和分类归档 设置自定义模板。
新增自定义模板配置
修改主题文件目录下的 theme.yaml文件,定义新模板,方式如下:
在 theme.yaml 的 spec 节点下添加如下配置:
customTemplates:
{type}:
- name: {name}
description: {description}
screenshot: {screenshot}
file: {file}.html字段说明:
type:模板类型,目前支持postpagecategory。name:模板名称description:模板描述screenshot:模板预览图file:模板文件名,需要在/templates/目录下创建
示例:
customTemplates:
page:
- name: 留言板
description: 留言板文章
screenshot:
file: page_leaving.html
- name: Memos
description: Memos
screenshot:
file: memos_display.html配置加载后,就能在Halo后台新建相应类型的内容时,在设置处,选择自定义模板 。此时打开内容则会打开自定义模板配置时的html文件。具体的功能便可以以此 html 文件为起点进行设计开发。

setting.yaml
定义主题配置里的配置项。在spec.forms 节点下添加新配置定义
spec:
forms:
- group: basic
label: 基本设置
formSchema:
- $formkit: select
name:
if:
label:
value: 'user'
help: '' group:配置分组,即配置的导航栏菜单选项label:group对于显示的名称formkit:配置方式select:下拉选择框radio:复选框text:文本输入number:数值输入attachment:文件路径选择group:一组配置,含多个子配置
name:配置变量名if:判断条件,满足条件表单结构才会显示label:配置展示名value:默认值help:配置提示信息options:选择项配置,配置方式为select或radio时需要给出选择项children:子配置,当配置方式为group时需要添加子配置default: 默认值validation:验证规则required:表示字段是必填项,不能为空。minLength:length:要求字段值的长度至少为 lengthmaxLength:length:要求字段值的长度最大为 lengthnumeric:验证字段值是否为数字matches:fieldName:验证当前字段的值是否和另一个name为 fieldName 的字段的值相匹配alpha:只允许字母alpha_num:只允许字母和数url:验证是否为有效的 URL。min:value:数值字段的最小值验证。max:value:数值字段的最大值验证。pattern:regex:使用正则表达式进行验证。
validationMessage:验证提示信息
Tip:theme.yaml 以及 setting.yaml 是持久化存储在数据库中的,不会在修改之后主动更新,需要重新加载主题才能生效
模板变量
以单页为例,在单页模板开发中,可以用到变量singlePage ,singlePage是SinglePageVo类型,类型定义如下:(来源Halo文档)
{
"metadata": {
"name": "string", // 唯一标识
"creationTimestamp": "2022-11-20T14:29:44.601Z", // 创建时间
......
},
"spec": {
"title": "string", // 标题
"cover": "string", // 封面图
......
"stats": {
"visit": 0, // 访问数量
"upvote": 0, // 点赞数量
"comment": 0 // 评论数量
},
"owner": "#ContributorVo", // 创建者
"content": "#ContentVo" // 内容
}在进行单页的模板开发时就可以用到这个变量。我所用到的场景是时间轴/里程碑页面的数据传递。通过编辑页面内容,在页面内容里保存数据,而页面代码通过${singlePage.content.content} 获取到内容字符串,再进行后续的数据处理。相当于把页面的数据存储在了编辑器的内容里。
变量类型具体信息参考Halo官方文档
元数据
元数据,应该是指在每个内容的设置页面里的配置数据。

元素据的定义在主题根目录下的annotation-setting.yaml文件。
代码中使用参考:
<!--是否开启侧边栏-->
<th:block th:if="${#annotations.getOrDefault(singlePage, 'enable_aside', 'true') == 'true'}">
<th:block th:replace="~{modules/common/aside :: aside}" />
</th:block>
<!--end-->htmlType
主题的多数html文件头部都有着类似代码:
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
th:replace="~{modules/layout :: html(title = ${theme.config.journals.journals_title ?: '我的动态'}+'-'+${site.title},htmlType = journals,header = null,leftSidebar = true,content = ~{::content}, head = null, footer = null)}"
>其中指定了htmlType ,不同的htmlType,代码所加载的内容会不同,例如引入的js文件,链接的css文件等。
以tail.html 为例,多数html文件最后都含有这句代码
<th:block th:replace="~{modules/macro/tail :: tail}" />在templates/modules/macro下找到tail.html 文件,此文件用于引入js文件,同时会根据htmlType的不同,引入不同的文件。
Finder API
为了满足在任意位置获取数据的需求,Halo官方提供了 Finder API
例如,需要获取指定单页的数据,可以使用singlePageFinder.getByName(pageName) ,API将返回所指定页面的SinglePageVo singlePage ,就是上面模板变量的例子。
如此就可以在任何地方获取到想要的数据。
其中参数pageName 为单页的唯一标识,其他类型内容的API也是一样,即上面例子中的metadata.name 。较为直接的获取单一内容唯一标识的方法是,点开内容的编辑页面,点开后的URL,接于name= 后的是唯一标识。

RESTful API
Halo 也提供了 RESTful 风格的 API。
Finder API有一定的使用限制,它是Halo自定义的方法,并不能在JS代码中灵活使用,需要使用注入之类的方法进行传递数据。
<script th:inline="javascript"> // 服务器端注入数据
const Config = {
Host: /*[[${theme.config.memos.memos_host}]]*/ '',
Height: /*[[${theme.config.memos.memos_block_height}]]*/ 300,
isShow: /*[[${theme.config.memos.enable_memos_link}]]*/ true
};
</script>以singlePageFinder.getByName(pageName) 为例,如果参数pageName是在JS代码执行过程中动态获得的,想通过getByName获取数据就不是那么容易了,此时就需要Halo提供的RESTful API。
同样的例子,Halo提供了SinglePage的API/apis/api.content.halo.run/v1alpha1/singlepages/{name} 来获取单页的数据。
async function getSinglePageDataByName(name) {
try {
const url = `/apis/api.content.halo.run/v1alpha1/singlepages/${name}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
......
} catch (error) {
console.error('Error fetching post:', error);
return null;
}
}其他API参考 Halo API 文档
默认评论
Halo系统提供的评论