主题开发指南
Clara 主题体系完全开放:按契约清单自由设计全部页面,前台外观不受任何固定排版限制。
Clara BBS 主题开发指南
面向读者:想为 Clara BBS 制作主题模板的开发者与设计师。只需具备基础 PHP 模板与 CSS 知识即可上手。
本指南随系统发行并持续维护;
content/themes/下的三套官方主题(Prism 多色门户 / Ember 深色暖焰 / Starter 极简骨架)均为可对照的完整参考实现。
一、主题是什么
主题 = 一套前台视图层的覆盖包:模板文件 + 样式表 + 静态资产。它决定站点「长什么样」——页面布局、排版风格、配色与组件形态。
主题不改变站点的任何行为:发帖、权限、支付、钩子扩展等都由系统与插件负责。主题与插件职责分离——主题只管视图,插件只管行为,互不越界。
渐进覆盖是这套机制的核心:主题不需要复制全部模板。你只覆盖想改的页面,其余页面自动回退到系统内置模板。最小可行主题可以只有一个样式表文件。
二、五分钟上手
- 复制官方示例主题目录
content/themes/starter/,重命名为你的主题目录名(仅限字母、数字、中横线、下划线); - 编辑
theme.php头部元信息(名称 / 版本 / 作者 / 描述); - 修改
assets/style.css,换成你的配色与排版; - 进入后台「系统 → 主题外观」,点「启用」——前台立即生效。
不满意随时「恢复默认」,站点即刻回到内置模板,主题文件原样保留。
三、目录结构
your-theme/
├── theme.php # 必须:元信息 + 访问守卫,文件体一般留空
├── screenshot.png # 建议:后台主题卡片预览图(1200×900)
├── README.md # 建议:主题说明
├── views/ # 可选:模板覆盖(只放想改的页面)
│ ├── layout.php # 全站布局
│ ├── home.php # 首页
│ ├── thread.php # 帖子详情页
│ ├── forum.php # 版块页
│ └── partials/ # 共享件覆盖(如 thread-row.php 列表行)
└── assets/ # 可选:样式 / 脚本 / 图片
└── style.css # 约定文件名:存在即被系统自动加载
theme.php 写法(元信息在头部块注释中,系统后台据此展示):
<?php
/**
* Theme Name: 我的主题
* Version: 1.0.0
* Author: 你的名字
* Description: 一句话介绍(后台卡片展示)
* Theme URI: https://example.com(可选)
*/
if (!defined('BASE_PATH')) {
exit;
}
views/ 下每个模板文件同样必须带访问守卫(放在文件顶部):
<?php
if (!defined('BASE_PATH')) {
exit;
}
模板只应被系统渲染器 include,不应能被 URL 直接执行。服务器侧已有主题目录 PHP 拦截规则兜底,代码侧守卫是约定的第二道防线,两道都要有。
四、模板查找与回退规则
前台渲染每个页面时按此顺序查找模板:
content/themes/{激活主题}/views/{模板名}.php—— 你的覆盖- 系统内置对应模板 —— 自动回退
要点:
- 共享件(帖子列表行、编辑器等)经
partial_path('名称')引用,同样走这条查找链——覆盖partials/thread-row.php即可重设计全站列表行; - 后台管理界面不在主题范围内,任何主题都不影响后台;
- 未启用主题时全部走内置模板;主题目录损坏时自动回退,站点不会白屏。
五、契约:重写模板必须保留的内容
主题可以完全重写页面结构,但有些东西是站点各能力的基础设施,重写时必须原样保留(官方示例主题全部保留,可对照参考)。
布局(views/layout.php)
核心布局承载的功能远比直觉多,逐项对齐官方示例主题(Ember/starter)是最稳的做法:
| 必须保留 | 为什么 |
|---|---|
<title> / meta description / keywords / canonical / robots | 搜索引擎收录基础 |
| content-language + geo.* + hreflang + 站点验证 meta + sameas preconnect | GEO 与平台归属验证 |
| rel=prev/next、llms.txt、RSS、OG/Twitter 卡片 | 翻页 SEO / AI 索引 / 分享卡片 |
$GLOBALS['__jsonld'] 输出循环 + $GLOBALS['__geo_citation'] 输出 | 各视图只写入 JSON-LD,唯一输出点在布局——缺循环 = 全站结构化数据静默丢失 |
Hook::filter('page_title', ...) + title_clamp() | 插件改标题管线 + SERP 长度保护 |
layout_head / layout_body_start / layout_body_end 钩子 | 插件全站注入点(主题主样式表也靠 layout_head 注入),丢失 = 插件样式脚本全部消失 |
nav_links 钩子(顶栏「更多」下拉) | 全部插件的前台导航入口,缺失 = 插件入口静默消失 |
nav_logo_after / nav_tools_before / nav_search_after / main_area_before / main_area_after / user_menu_links / footer_links | 各页面的插件注入缝 |
window.CLARA = {...} 配置 + CLARA_I18N + 核心 app.css / editor.css / app.js / editor.js 四件 | 前端交互(通知/表情/上传/确认弹窗/编辑器)全部依赖 |
| 横幅公告 / 弹窗公告 / flash 提示 | 站长运营能力 |
| 通知面板(#ntfWrap)与用户菜单(#userMenu) | 登录态核心交互 |
| 移动端底部导航 + 版块弹层 | 手机端可用性 |
#toastWrap、#confirmModal、#backTop | Toast 提示 / 统一确认弹窗 / 回顶 |
| data-locked 锁定版块拦截脚本 | 权限体系配套(缺了 = 无权版块可被点击进 403) |
| PWA 元数据 + manifest(+ pwa_style=float 时的安装浮窗) | PWA 能力 |
| 统计代码 + 性能徽标 | 后台配置的统计与性能观测 |
与内置皮肤(亮暗切换)的关系
系统内置 clara(亮色)/trae(深色)两套 CSS 皮肤(assets/css/theme-trae.css,按 cookie / 时段自动切换)。主题一旦自带样式层,就必须把内置皮肤排除,否则两套变量互相覆盖、随 cookie 变化忽明忽暗:
- 布局内不输出
<link>引入theme-trae.css; window.CLARA里themeSwitch: false(关闭内置切换下拉,JS 不再改写theme-color);- 主题变量层(重定义核心 CSS 变量)作为唯一换肤来源——回退核心模板的页面配色自动继承。
官方两主题均已按此处理,直接对照即可。
首页(views/home.php)
| 必须保留 | 为什么 |
|---|---|
CollectionPage 与 Organization 结构化数据(jsonld() 调用) | AI 引擎与搜索引擎的实体识别(输出由布局负责,本页负责写入) |
| 首页实体描述段(站点名 + 定位 + 数据的完整句子) | AI 引用站点的关键语料 |
home_content_before / home_hero_after / home_content_after | 插件首页注入点 |
列表容器的 data-sync="home" data-page data-sig 属性 | 前端「有新内容」轮询提示依赖 |
id="forums" 版块区锚点 | 顶栏「版块」与移动端底部导航的统一落点(核心路由没有独立的版块列表页,勿指向不存在的路由) |
列表行(views/partials/thread-row.php)
列表行被全站 7 个列表位共用(首页/版块/搜索/标签/专题/用户主页/收藏),核心会传入丰富的可选变量,主题版应逐项对齐:
| 必须保留 | 为什么 |
|---|---|
<?= Hook::filter('thread_row_extra', '', $t) ?> | 插件行内附加信息(徽章/标记)注入点 |
thread_type_badges($t, $ttMap) 调用 | 悬赏/投票/网盘/附件类型徽章(搜索页/用户主页会传 $ttMap) |
$titleHL 闭包处理 | 搜索页传入时按闭包输出,缺失 = 搜索关键词高亮丢失 |
$catNames 分类徽章、$t['tags'] 标签、gifts/likes/最后回复 | 信息完整性(均可 isset 兜底) |
thread_style_class() 标题样式类 | 后台自定义标题颜色 |
标准帖子链接形态 url('thread-' . $t['id'] . '.html') | 站内链接与收录 URL 一致性 |
其他页面
规则很简单:**核心模板里的每个 Hook::filter(...) 输出点都是插件内容的落点,覆盖任何模板时逐一保留同名调用**。建议每次覆盖前先 diff 核心模板,把钩子位清单抄下来。
六、模板变量与函数速查
布局可用:$pageTitle(页面标题)、$metaDesc(描述)、$canonical(规范链接)、$noindex(是否禁止收录)、$content(页面主体 HTML)、$bodyClass(body 附加类);user() 取当前登录用户(未登录为 null)。
列表行可用:$t(主题行数组:id / title / user_id / username / created_at / replies / views / forum_id / forum_name / tags 等);可选 $showForum(是否显示版块名)。
常用函数:
| 函数 | 用途 |
|---|---|
e($s) | HTML 转义输出——所有动态内容必须经过它 |
t($s) | 多语言翻译 |
url($path) | 站内链接 |
setting($key, $default) | 读站点设置(站点名、描述等) |
user() | 当前登录用户 |
avatar_html($user, $size) | 用户头像 |
time_ago($time) | 相对时间("3 小时前") |
theme_asset($path) | 主题资产链接(自动带版本号防缓存) |
partial_path($name) | 共享件路径 |
icon($name, $size) | 内置 SVG 图标 |
七、三个进阶路线
① 纯 CSS 换肤(零模板):只写 assets/style.css,利用样式优先级覆盖内置样式。适合改配色、字体、圆角、间距。
② 布局重排(覆盖 layout.php):复制示例主题的布局做起点——它已保留全部契约项,你在其间自由重排导航、页脚与主内容结构。
③ 列表重设计(覆盖 partials/thread-row.php):重设计帖子列表行(卡片式 / 双栏 / 杂志式),全站所有列表位(首页、版块、搜索、用户主页)一次生效。
八、调试与常见问题
- 启用了但前台没变:确认后台主题卡片显示「使用中」;确认模板文件名与要覆盖的页面一致;浏览器强刷(主题资产带版本号,改动即失效缓存)。
- 样式变了布局没变:正常——你只提供了样式表,没覆盖模板。想改结构就加对应 views 模板。
- 主题模板生效、核心 CSS 生效、唯独主题配色消失:F12 → Network 查
style.css请求——列表里没有该请求 =content/themes/{主题}/assets/目录漏传(文件不存在时系统连标签都不输出)或注入位失效;请求存在但 404 / Content-Type 为 HTML = 服务器静态放行段未包含content/themes。 - 某个插件的内容不见了:覆盖模板时漏了某个
Hook::filter输出点,对照核心模板补回。插件入口全没了 = 布局漏了nav_links「更多」下拉。 - 改了 CSS 不生效:确认文件在主题的
assets/style.css(约定文件名自动加载);自定义命名文件需在 theme.php 里用Hook::on('layout_head', ...)配合theme_asset()手动引入。 - 页面在亮色/深色之间跳变:布局里加载了内置皮肤
theme-trae.css或themeSwitch未置 false,与主题变量层互相打架——按第五节「与内置皮肤的关系」处理。
九、发布约定
- 元信息四项写全(Name / Version / Author / Description),Version 建议语义化(1.0.0 起);
screenshot.png用主题实际效果截图,比例 4:3;- 主题内所有动态输出经过
e()转义;不写任何固定写死的站点名/链接(用setting()读取,保证主题可移植到任何站点); - 更新主题时递增 Version,后台卡片即展示新版本号。
本文档随系统发布维护;content/themes/ 下三套官方主题(Prism / Ember / Starter)均为可对照的完整参考实现。如发现与当前版本不符,欢迎到社区反馈。







