主题机制
Nexus 的主题机制把“页面长什么样”与“页面做什么”分开管理。业务页面只使用 primary(主色)、background(背景)、destructive(危险操作)等语义名称,不直接写死颜色;切换主题时,页面结构和业务逻辑不需要修改。
三个容易混淆的概念
| 概念 | 决定什么 | 示例 |
|---|---|---|
| 主题预设 | 品牌配色、字体、圆角、阴影和局部特色效果 | 默认主题、暖橙主题、黑金主题 |
| 明暗模式 | 当前主题使用亮色版还是暗色版 | light、dark、跟随系统 |
| 站点主题配置 | 默认使用哪个主题,以及是否允许访客切换 | 后台“系统配置”中的外观选项 |
主题预设和明暗模式是两个独立维度。例如,“黑金主题 + 亮色模式”和“黑金主题 + 暗色模式”是同一主题的两种显示状态。
整体工作方式
完整过程可以理解为:
- 开发人员把主题包放入
app/themes/。 - Nexus 在开发或构建时自动扫描主题目录,生成 CSS 入口和主题注册表。
- 站点启动时读取后台配置,确定默认主题及是否允许用户切换。
- 浏览器把当前主题写到
<html>的data-theme属性,把明暗状态写到darkclass。 - 页面组件读取当前主题提供的语义变量并完成渲染。
主题目录
每套主题是一个独立目录:
app/themes/
├── nexus/
│ ├── index.css
│ └── theme.json
├── black-gold/
│ ├── index.css
│ └── theme.json
└── warm-orange/
├── index.css
└── theme.json目录名就是主题的唯一标识,必须使用小写 kebab-case,例如 ocean-green。每个主题包含两个文件:
| 文件 | 作用 |
|---|---|
theme.json | 主题名称、版本、作者、说明及是否为基座主题 |
index.css | 亮色、暗色 Token 和该主题专属的局部样式 |
主题描述文件示例:
{
"displayName": { "zh": "海洋绿", "en": "Ocean green" },
"version": "1.0.0",
"author": "Your Team",
"description": "清爽、克制的绿色品牌主题"
}全库必须恰好有一个 base: true 的基座主题。当前基座是 nexus,它还承担两项职责:
- 通过
@theme把 Nexus Token 映射成 Tailwind 可使用的语义类; - 通过
:root提供兜底样式,避免主题值缺失或过期时页面无样式。
普通主题不能声明为基座,也不应提供全局 :root 兜底,否则可能覆盖其他主题。
构建期自动注册
nuxt.config.ts 注册了 server/utils/themes/nexus-theme.module.ts。该 Nuxt 模块扫描 app/themes/,并自动生成:
| 生成文件 | 用途 |
|---|---|
app/assets/css/themes.generated.css | 按稳定顺序导入每套主题的 index.css |
shared/theme-registry.generated.ts | 提供主题标识、双语名称和默认主题 |
app/assets/css/main.css 只导入统一的 themes.generated.css。新增主题后不需要手动修改 main.css、useTheme.ts 或主题下拉菜单。
两个
generated文件都是构建产物,不要手工修改。下次运行开发服务或生产构建时,手工改动会被覆盖。
开发模式会监听 app/themes/。增加、删除主题目录或修改 theme.json 后,注册表会自动刷新;修改 index.css 也会进入 Vite 的热更新流程。
扫描器采用不同的容错策略:
- 基座主题缺失、重复或缺少
index.css:立即停止构建,因为整个站点将失去样式基础。 - 普通主题目录名不合法或缺少
index.css:输出警告并跳过该主题。 - 普通主题的
theme.json缺失或格式错误:输出警告,并以目录名作为显示名称继续加载。
运行期如何切换
app/composables/useTheme.ts 同时管理两个状态:
| 状态 | 写入位置 | 本地存储 key |
|---|---|---|
| 当前主题 | <html data-theme="..."> | nexus-theme-name |
| 明暗模式 | <html class="dark"> | nexus-mode |
app/app.vue 在服务端渲染首屏时就注入默认 data-theme,客户端加载后再响应用户选择。这样可以减少页面先显示默认颜色、随后突然换色的闪烁。
后台站点配置影响切换行为:
| 配置 | 含义 |
|---|---|
defaultTheme | 站点默认主题 |
allowThemeSwitch | 是否允许访客选择其他主题;关闭时强制使用 defaultTheme |
allowThemeMode | 是否显示亮色、暗色和跟随系统的切换入口 |
主题注册表还保留旧主题标识的兼容映射。用户浏览器中如果存有已经删除的主题名,会自动回退到当前基座主题。
管理员配置主题
管理员可以在 /admin/settings 的外观设置中选择站点默认主题,并控制访客是否能切换主题和明暗模式。配置存入 settings 表,保存后即时生效。
Nexus 内置三套主题预设:
| 预设 | 风格 |
|---|---|
| 默认主题 | 暖米纸感中性色、墨黑主色与暖橙点缀 |
| 暖橙主题 | 亮橙主色与冷调中性色 |
| 黑金主题 | 暖米亮色与黑金暗色风格 |
后台配置项:
| 配置 | 含义 |
|---|---|
defaultTheme | 站点默认使用的主题预设 |
allowThemeSwitch | 是否允许访客选择其他主题;关闭时强制使用 defaultTheme |
allowThemeMode | 是否显示亮色、暗色和跟随系统的切换入口 |
管理员验收时,至少确认默认主题在首页、登录页和后台均生效;开启切换后,访客可以在主题菜单中切换预设和明暗模式,刷新页面后选择仍然保留。主题切换不应改变业务数据和页面功能。
Token 如何影响组件
Token 是带业务含义的视觉变量。组件关心“这是主按钮”或“这是危险操作”,不关心它最终是黑色、橙色还是金色。
常用 Token 包括:
| Token | 使用位置 |
|---|---|
--background / --foreground | 页面背景和正文 |
--card / --card-foreground | 卡片和卡片文字 |
--primary / --primary-foreground | 主按钮、链接和选中状态 |
--secondary / --muted | 次级区域和弱化信息 |
--destructive | 删除、停用等危险操作 |
--border / --input / --ring | 边框、输入框和键盘焦点 |
--success / --warning | 成功与警告状态 |
页面应优先使用语义类:
<button class="bg-primary text-primary-foreground">
保存设置
</button>不要在普通业务组件中写死品牌颜色:
<!-- 不推荐:切换主题后仍然固定为蓝色 -->
<button class="bg-blue-600 text-white">
保存设置
</button>确实只属于某套主题的特色效果,可以限定在主题选择器下:
[data-theme="ocean-green"] .home-hero-title {
color: hsl(var(--primary));
}这样不会影响其他主题。
新增主题
新增主题可以使用 theme-extend 技能,也可以手工完成。
1. 创建主题包
创建 app/themes/ocean-green/theme.json 和 app/themes/ocean-green/index.css。普通主题至少需要覆盖完整的亮色与暗色变量:
@layer base {
[data-theme="ocean-green"] {
--background: 160 20% 98%;
--foreground: 170 35% 10%;
--primary: 173 80% 28%;
--primary-foreground: 0 0% 100%;
/* 继续定义 card、muted、border、destructive 等完整 Token */
}
[data-theme="ocean-green"].dark {
--background: 170 25% 6%;
--foreground: 160 15% 96%;
--primary: 168 65% 58%;
--primary-foreground: 170 35% 10%;
/* 继续定义暗色完整 Token */
}
}2. 启动并检查自动注册
运行开发服务后,确认生成的主题注册表中出现 ocean-green,并且页面的主题菜单显示 theme.json 中的双语名称。
3. 完成视觉验收
至少检查以下组合:
| 场景 | 检查重点 |
|---|---|
| 新主题亮色 | 正文、主按钮、输入框、边框和警告信息清晰可读 |
| 新主题暗色 | 深色背景有层次,文字与操作按钮对比充足 |
| 默认主题 | 新主题的局部样式没有影响原有页面 |
| 桌面端和移动端 | 主题菜单、弹窗和表格没有溢出 |
产品经理主要验收品牌感、内容层级和核心流程;开发人员还应运行 npm test,确认主题目录、注册表、SSR 注入和切换机制的结构测试通过。
常见问题
新主题没有出现在菜单中
检查目录名是否为 kebab-case、index.css 是否存在,以及开发终端是否出现 [nexus-theme] 警告。不要直接编辑生成的注册表。
切换后部分组件颜色不变
通常是组件写死了 blue-*、十六进制颜色或内联 style。把它改为 primary、muted、destructive 等语义 Token。
暗色模式文字看不清
检查 [data-theme="<name>"].dark 是否覆盖了完整 Token,尤其是 foreground、muted-foreground、primary-foreground 和 destructive-foreground。
后台选择了主题但访客无法切换
这是两项不同配置:defaultTheme 决定默认外观,allowThemeSwitch 决定访客能否自行选择。关闭切换时,所有访客都会使用管理员指定的默认主题。

