⭐ 推荐:社区规则条款 V1.0

主题开发指南

Clara 主题体系完全开放:按契约清单自由设计全部页面,前台外观不受任何固定排版限制。

Clara BBS 主题开发指南

面向读者:想为 Clara BBS 制作主题模板的开发者与设计师。只需具备基础 PHP 模板与 CSS 知识即可上手。

本指南随系统发行并持续维护;content/themes/ 下的三套官方主题(Prism 多色门户 / Ember 深色暖焰 / Starter 极简骨架)均为可对照的完整参考实现。


一、主题是什么

主题 = 一套前台视图层的覆盖包:模板文件 + 样式表 + 静态资产。它决定站点「长什么样」——页面布局、排版风格、配色与组件形态。

主题不改变站点的任何行为:发帖、权限、支付、钩子扩展等都由系统与插件负责。主题与插件职责分离——主题只管视图,插件只管行为,互不越界。

渐进覆盖是这套机制的核心:主题不需要复制全部模板。你只覆盖想改的页面,其余页面自动回退到系统内置模板。最小可行主题可以只有一个样式表文件。


二、五分钟上手

  1. 复制官方示例主题目录 content/themes/starter/,重命名为你的主题目录名(仅限字母、数字、中横线、下划线);
  2. 编辑 theme.php 头部元信息(名称 / 版本 / 作者 / 描述);
  3. 修改 assets/style.css,换成你的配色与排版;
  4. 进入后台「系统 → 主题外观」,点「启用」——前台立即生效。

不满意随时「恢复默认」,站点即刻回到内置模板,主题文件原样保留。


三、目录结构

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 拦截规则兜底,代码侧守卫是约定的第二道防线,两道都要有。


四、模板查找与回退规则

前台渲染每个页面时按此顺序查找模板:

  1. content/themes/{激活主题}/views/{模板名}.php —— 你的覆盖
  2. 系统内置对应模板 —— 自动回退

要点:

  • 共享件(帖子列表行、编辑器等)经 partial_path('名称') 引用,同样走这条查找链——覆盖 partials/thread-row.php 即可重设计全站列表行;
  • 后台管理界面不在主题范围内,任何主题都不影响后台;
  • 未启用主题时全部走内置模板;主题目录损坏时自动回退,站点不会白屏。

五、契约:重写模板必须保留的内容

主题可以完全重写页面结构,但有些东西是站点各能力的基础设施,重写时必须原样保留(官方示例主题全部保留,可对照参考)。

布局(views/layout.php)

核心布局承载的功能远比直觉多,逐项对齐官方示例主题(Ember/starter)是最稳的做法:

必须保留为什么
<title> / meta description / keywords / canonical / robots搜索引擎收录基础
content-language + geo.* + hreflang + 站点验证 meta + sameas preconnectGEO 与平台归属验证
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、#backTopToast 提示 / 统一确认弹窗 / 回顶
data-locked 锁定版块拦截脚本权限体系配套(缺了 = 无权版块可被点击进 403)
PWA 元数据 + manifest(+ pwa_style=float 时的安装浮窗)PWA 能力
统计代码 + 性能徽标后台配置的统计与性能观测

与内置皮肤(亮暗切换)的关系

系统内置 clara(亮色)/trae(深色)两套 CSS 皮肤(assets/css/theme-trae.css,按 cookie / 时段自动切换)。主题一旦自带样式层,就必须把内置皮肤排除,否则两套变量互相覆盖、随 cookie 变化忽明忽暗:

  1. 布局内不输出 <link> 引入 theme-trae.css;
  2. window.CLARA 里 themeSwitch: false(关闭内置切换下拉,JS 不再改写 theme-color);
  3. 主题变量层(重定义核心 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)均为可对照的完整参考实现。如发现与当前版本不符,欢迎到社区反馈。