概述 — 这个功能做什么
InfraGuard 控制台内置两套主题:深色(默认) 和 浅色。主题通过文档根元素(<html>)上的单个属性 data-theme 切换,其取值会整体替换 SCSS 中定义的 --infr-* CSS 变量令牌 集合。所有组件都使用 var(--infr-*) 令牌而不是硬编码的十六进制色值,因此只要改动一个单词(dark ↔ light),整个界面就会立即重新着色。
默认是 深色 — 作为 NOC(网络运营中心)控制台,它采用深蓝黑背景搭配青绿色信号强调色,使严重程度颜色(crit / warn / good)在深色画面上清晰可辨。
<html data-theme="dark"> ← 默认,NOC 深色
<html data-theme="light"> ← 浅色(明亮的办公环境)
主题是 纯前端偏好。它绝不会保存在服务器上,与您的账号和数据库都无关 — 它只存在于浏览器的
localStorage中。退出登录后仍然保留;如果有人用同一账号在另一台设备上登录,那台设备遵循它自己的设置。
何时使用
- 夜间运维 / NOC 大屏 → 深色(眩光更少,严重程度颜色对比度更高)。
- 明亮的办公室、投影仪、文档截图 → 浅色。
- 个人对比度 / 色觉偏好 — 按用户切换。
一旦选定就会在该浏览器中保留,因此通常只需设置一次。
页面布局
页眉的切换按钮
控制台外壳(layouts/app.blade.php)和登录页(layouts/simple.blade.php)都在顶栏右侧放置了一个 主题切换按钮。
| 项目 | 值 |
|---|---|
| 标记钩子 | <button data-theme-toggle> |
| 图标 | mdi-theme-light-dark(半月) |
| aria-label | msg.action.theme(已本地化) |
| 动作 | 点击在深色 ↔ 浅色之间循环 |
| 位置 | 顶栏操作区(语言切换器旁边,用户菜单左侧) |
登录前(访客)页面也带有相同的切换按钮,因此无需通过认证即可更改主题。
颜色令牌分组
resources/scss/_variables.scss 在两个代码块 :root[data-theme="dark"] 与 :root[data-theme="light"] 中,用不同的取值定义了同一批令牌名。代表性令牌如下:
| 令牌 | 用途 | dark | light |
|---|---|---|---|
--infr-bg |
页面背景 | #0b1220 |
#f6f8fb |
--infr-surface |
面板 / 卡片表面 | #111b2c |
#ffffff |
--infr-fg |
正文文字 | #e9eff8 |
#111a28 |
--infr-fg-muted |
次要文字 | #8493ac |
#63728b |
--infr-border |
边框 | #1e2c43 |
#dbe2ec |
--infr-accent |
强调色(青绿) | #2dd4bf |
#0d9488 |
--infr-crit |
严重程度 critical | #fb7185 |
#e11d48 |
--infr-warn |
严重程度 warning | #fbbf24 |
#b45309 |
--infr-good |
严重程度 healthy | #34d399 |
#0f9d6a |
--infr-info |
信息 | #38bdf8 |
#0369a1 |
除此之外,每套主题还定义了 --infr-surface-2/hi、--infr-border-2、--infr-accent-soft/ink、--infr-on-accent(强调色之上的按钮文字)、TCP 状态颜色(--infr-state-*)、图表颜色(--infr-chart-2)、阴影 / 辉光(--infr-shadow / --infr-glow)以及顶栏毛玻璃(--infr-topbar-*)。每个代码块还会声明 color-scheme: dark|light,使原生表单控件和滚动条与主题保持一致。
SSH 终端调色板是一个例外。
--infr-term-*令牌 无论data-theme取何值都保持深邃、沉浸的深色。即使在浅色主题下,SSH 控制台 依然保留深色终端。
操作步骤
切换
- 点击顶栏右侧的 半月图标(切换主题)。
- 它会立即在深色 ↔ 浅色之间翻转,选择会自动保存。
- 下次访问或刷新页面时,会恢复您上次的选择。
内部实现(开发者参考)
前端逻辑写在 resources/js/app.js 中。核心代码如下:
const THEME_KEY = 'infr-theme'; // localStorage 键名
const get_theme = () => localStorage.getItem(THEME_KEY) || 'dark'; // 默认深色
const set_theme = (t) => {
document.documentElement.setAttribute('data-theme', t); // 根元素属性
localStorage.setItem(THEME_KEY, t); // 持久化
window.dispatchEvent(new CustomEvent('infr:theme', { detail: t })); // 重新给图表着色
};
// 绑定切换按钮
document.querySelectorAll('[data-theme-toggle]')
.forEach(btn => btn.addEventListener('click',
() => set_theme(get_theme() === 'dark' ? 'light' : 'dark')));
行为小结:
| 步骤 | 内容 |
|---|---|
| 存储键 | localStorage['infr-theme'] = "dark" | "light" |
| 应用到 | document.documentElement(即 <html>)上的 data-theme 属性 |
| 默认值 | dark(当 localStorage 为空或不可读时) |
| 事件 | 每次切换都会在 window 上触发 infr:theme CustomEvent(detail = 当前主题) |
| 跨标签页同步 | 监听 storage 事件 → 同一浏览器的 其他标签页 自动同步 |
防止无样式内容闪烁(FOUC)— 绘制前脚本
<head> 最顶部有一段极小的内联脚本(app.blade.php 与 simple.blade.php 共用,位于 CSS 的 @vite 加载 之前),它会在任何样式表生效之前先设置 data-theme。这样就消除了刷新时的"深色→浅色闪烁"。
<script>
(function () {
try { document.documentElement.setAttribute('data-theme',
localStorage.getItem('infr-theme') || 'dark'); }
catch (e) { document.documentElement.setAttribute('data-theme', 'dark'); }
})();
</script>
即使在
localStorage被阻止的环境中(无痕模式、第三方存储被拦截等),catch也会安全地回退到 深色。
图表与编辑器跟随主题
- ECharts(
<metric-chart>/ echarts-kit): 通过getComputedStyle读取--infr-*令牌来构建图表颜色、坐标轴与提示框,并在infr:theme事件上 重新着色。is_dark()的判定是data-theme !== 'light'(因为深色是默认值)。参见 服务器时间序列图表。 - CodeMirror(
<cm-editor>)::dark属性在oneDark(深色)与浅色主题之间切换。Vue 页面计算is_dark_mode = document.documentElement.dataset.theme === 'dark',以:dark="is_dark_mode"传入,并在infr:theme事件上刷新它。参见 备份脚本生成器。
<cm-editor v-model="script.body" lang="bash" :dark="is_dark_mode"></cm-editor>
数据来源
主题 完全没有数据库或服务器端存储。 它只用到:
| 位置 | 内容 |
|---|---|
localStorage['infr-theme'] |
每个浏览器的用户选择(dark/light) |
<html data-theme> |
运行时已应用的状态 |
resources/scss/_variables.scss |
--infr-* 令牌在各主题下的实际取值(编译进 CSS) |
resources/js/app.js |
切换 / 持久化 / 事件绑定 |
CLAUDE.md 的 附录 C(浅色 / 深色主题令牌) 是调色板设计的权威依据。
访问权限
主题是 与认证或授权无关的界面偏好。
- 所有用户(包括登录前的访客)都可以自由切换 — 没有等级限制。
- 在 demo=1 / staff=5 / admin=10 下行为完全相同。
- 由于服务器端不写入任何内容,它也绝不会出现在审计日志(
infr_audit_log)中。
排障与常见问题
Q. 刷新后主题被重置了。
如果浏览器阻止了 localStorage(无痕模式、严格隐私设置、第三方存储拦截),则无法保存选择,每次都会退回默认的 深色。请为该站点授予存储访问权限。
Q. 我在一个标签页里改了,另一个标签页没有更新。
切换会通过 storage 事件传播到 同一浏览器的其他标签页。如果没有立即生效,可能是该标签页尚未加载监听器绑定(app.js)— 请刷新它。不同 设备/浏览器 的存储彼此独立,不会同步(这是设计如此)。
Q. 我切换到了浅色,但 SSH 终端仍然是深色。
这是预期行为。终端使用专门的 --infr-term-* 调色板,无论应用主题如何都保持深邃的深色(为了可读性 / 沉浸感)。
Q. 某个组件显示不正常(主题对它不生效)。
那个组件硬编码了十六进制色值而不是使用令牌。按照约定,组件只能使用 var(--infr-*) 令牌。请用回归检查的 grep 确认 resources/scss/pages 中没有直接写死的十六进制色值:
rg -n "#[0-9a-fA-F]{3,6}" resources/scss/pages
Q. 我希望它跟随操作系统的深色模式。
当前实现 不会 跟随操作系统偏好(prefers-color-scheme)。初始值始终是 dark,并且只记住用户通过切换按钮显式选择的值。操作系统集成是路线图中的项目。
相关内容
- 导航与布局 — 顶栏操作区(主题 / 语言 / 用户菜单)
- 语言 · 区域设置 — 与主题并列的另一项前端偏好
- 检索本手册 — /manual 的搜索与结构
- 服务器时间序列与 MetricChart — 依据主题令牌重新着色的图表
- 备份脚本生成器 — CodeMirror 的
:dark绑定 - SSH 控制台 — 独立于应用主题、固定为深色的终端
