主题漫游
主题漫游是一个面向 Halo 2.25+ 的多主题访客切换插件。站长可以把已安装主题与对应菜单绑定后开放给访客;每位访客只改变自己的页面渲染,不会启用或停用 Halo 的全局主题,也不会影响其他访客。
告别一个站只能穿一套衣服的烦恼。今天宠这个,明天宠那个,让安装过的主题都能雨露均沾。
主要能力
- 自动读取 Halo 中已经安装的主题和菜单,无需手工新增主题数据。
- 每套主题都能绑定自己的菜单,并决定是否开放给访客。
- 支持为主题自动识别内置模板和自定义页面模板,并按一级、二级导航进行编排。
- 访客可以自由切换站长准备的多套主题,不再只能看到一种风格,也让安装的每套主题都有机会派上用场。
- 每位访客的选择独立保存,不会改变 Halo 当前启用的全局主题,也不会影响其他访客。
- 切换后保留当前真实页面路径,并支持多种平滑切换动画。
效果演示

群聊讨论:

安装与使用
- 安装并启动插件。
- 在 Console 左侧进入“主题漫游”。
- 根据需要为主题选择 Halo 菜单,或编排主题自己的导航结构。
- 打开需要向访客展示的主题。
- 在“漫游配置”中设置记忆时间、开放范围、入口位置和切换动画。
如果没有任何主题处于“前台开放”状态,插件不会向站点页面注入切换入口。
主题接入
插件不要求主题进行适配,默认会自动注入完整的主题切换入口。主题需要指定入口位置时,可以提供一个稳定锚点:
<div data-theme-roaming-anchor></div>
如果希望完全使用主题自己的按钮,只需添加 data-theme-roaming-open。插件检测到自定义按钮后,不会重复显示默认入口:
<button type="button" data-theme-roaming-open>切换主题</button>
自定义默认按钮
主题可以通过 CSS 变量调整默认入口的尺寸、圆角、颜色、背景、边框和阴影:
[data-theme-roaming-anchor] {
--tr-trigger-size: 36px;
--tr-trigger-embedded-size: 36px;
--tr-trigger-radius: 12px;
--tr-trigger-color: #be185d;
--tr-trigger-background: #fdf2f8;
--tr-trigger-border: #fbcfe8;
--tr-trigger-shadow: none;
--tr-trigger-hover-color: #db2777;
--tr-trigger-hover-background: #fce7f3;
--tr-trigger-hover-border: #f472b6;
--tr-trigger-hover-shadow: none;
--tr-trigger-icon-size: 17px;
}
需要进一步控制时,可以通过 Shadow Parts 修改插件内部元素:
#theme-roaming-root::part(trigger) {
transition: transform 160ms ease;
}
#theme-roaming-root::part(trigger):hover {
transform: translateY(-1px);
}
JavaScript 接口
前台接口为 window.ThemeRoaming,提供以下方法:
open():打开主题面板。close():关闭主题面板。toggle():切换面板状态。refresh():重新读取开放主题。select(themeName):切换到指定主题。getState():读取当前主题漫游状态。mount(anchor):把入口挂载到指定元素。setAppearance(options):动态设置默认入口样式。
调用前应判断接口是否存在:
window.ThemeRoaming?.open();
动态设置入口样式:
window.ThemeRoaming?.setAppearance({
size: "36px",
radius: "12px",
color: "#be185d",
background: "#fdf2f8",
border: "#fbcfe8",
shadow: "none"
});
插件会派发 theme-roaming:catalog、theme-roaming:statechange、theme-roaming:before-switch 和 theme-roaming:error 事件,主题可以按需监听。
数据与菜单接口
开放主题目录:
GET /apis/roaming.serenity/v1alpha1/catalog
Thymeleaf 可以通过 themeRoamingFinder.getCatalog() 获取当前主题和开放主题列表。
菜单绑定和自定义导航建议使用 themeRoamingMenuFinder。未安装插件时必须保留 Halo 原生 menuFinder 回退:
${themeRoamingMenuFinder != null
? themeRoamingMenuFinder.getForTheme(theme.metadata.name).menuItems
: menuFinder.getPrimary().menuItems}
开发与构建
插件版本仅在 gradle.properties 的 pluginVersion 中维护,插件元数据和 JAR 文件名会在构建时自动同步。
.\gradlew clean build
构建包含 Console 前端、Java 编译和单元测试,产物位于 build/libs/。
兼容性
- Halo:
>= 2.25.0 - Java:21
Halo 升级后建议重新执行完整构建,并验证首页、列表页、文章页、主题静态资源、菜单绑定和自定义导航。
源码与反馈
- 源码仓库:https://github.com/atangccc/Roaming
- 问题反馈:https://github.com/atangccc/Roaming/issues
- 协议:GPL-3.0