Skip to content

Theme Development / 主题开发指南

本文说明 Zeno 的外观主题、服务器卡片主题和相关设置如何分层,以及新增或修改主题时必须遵守的代码、兼容性、测试和视觉验收规则。

1. 先区分三类“主题”

Zeno 当前有三层互相独立、可以组合的外观能力:

层级设置字段当前值作用范围
明暗模式themesystemlightdark全站颜色模式和 color-scheme
外观预设appearance_presetdefaultgaussian_blur卡片透明度、模糊、圆角、边框、阴影、背景遮罩和主题色
服务器卡片主题server_card_themeclassiccapsule首页 ServerCard 内部的信息编排和局部样式

不要用服务器卡片主题复制整套明暗配色,也不要用外观预设改变数据语义或卡片 DOM 结构。三层应当正交:任何服务器卡片主题都必须能与浅色、深色、两种外观预设以及有/无背景图组合使用。

当前兼容性基线:

  • classic 是默认值和安全回退值,必须保留现有信息密度、交互和升级前行为。
  • capsule 只改变服务器卡片内部组织,继续复用同一批数据、格式化函数、指标组件、状态颜色和页面布局。
  • 旧数据库没有 server_card_theme、旧 API 响应省略该字段或存量值非法时,Controller 和 Web 都必须回退到 classic
  • Admin API 收到未知值时必须返回校验错误,不能静默保存。

2. 设计边界

2.1 必须复用的能力

主题只负责视觉和布局,不得复制或改写以下业务逻辑:

  • 节点在线、警告、离线和无数据状态语义;
  • CPU、内存、磁盘、流量、网络速率、账单、到期日、延迟和丢包计算;
  • 币种换算、单位格式化、计费周期和流量周期;
  • 卡片点击、键盘访问、预加载、路由和可访问性标签;
  • UsageBarMetricServerFlag、图标和既有格式化函数;
  • 全站颜色、圆角、阴影、模糊和背景相关 CSS Token。

优先通过主题作用域 class、CSS Token 和少量条件布局完成变体。除非两个主题确实有不同的语义结构,否则不要复制整份 ServerCard

2.2 视觉语言

  • 保留 Zeno 已有的轻量、干净、透明背景风格,不把主题做成另一套独立设计系统。
  • 同一层级只能使用一种分组语言。服务器卡片上下区域统一使用分割线体系时,不得把上方资源项做成独立有边框小卡片、下方继续使用分割线。这是新主题和后续主题修订的验收标准;已有样式不应成为继续复制混合语言的理由。
  • 优先复用 --foreground--muted--border--divider--divider-strong--blue、资源状态色和 --radius-*;不要在主题选择器内散落固定色值。
  • 绿色、橙色、红色是状态语义色,不是装饰色。主题不能改变同一状态的含义。
  • 数字使用稳定的单行布局和 tabular-nums;长服务器名、容量、账单和负载值不能挤出卡片。
  • hover 只在 @media (hover: hover) and (pointer: fine) 下启用;同时尊重 prefers-reduced-motion

2.3 响应式与可访问性

每个主题必须保持:

  • < 768px 单列、768px–1023px 两列、>= 1024px 三列的首页卡片布局;
  • 卡片自身在窄容器内无横向滚动;
  • 卡片的 roletabIndex、Enter/Space 行为和 focus-visible 不退化;
  • 状态和资源信息不能只依靠颜色表达;
  • 装饰层使用 pointer-events: none,不能遮挡卡片点击或后台控件。

3. 代码地图

Controller

文件职责
internal/controller/api/admin_settings_types.goSiteSettings、PATCH DTO、默认值、允许值校验
internal/controller/api/settings_store.goserver_card_theme 设置键
internal/controller/api/settings_binding.go从通用 settings 表读取并校验
internal/controller/api/settings_update_binding.go将 PATCH 字段绑定到持久化更新
internal/controller/api/admin_settings_test.go默认值、PATCH/Public 回读、非法存量回退和非法输入测试

设置保存在通用键值表中。只增加一个新的 server_card_theme 枚举值通常不需要数据库 migration;如果主题需要新的持久化字段,则属于设置/API 变更,必须同步 DTO、默认值、读写绑定、校验、测试和 API 文档。

Web

文件职责
web/src/types.tsServerCardThemeAppearancePresetAdminSettings 类型
web/src/api/apiTypes.tsPublic/Admin API snake_case DTO
web/src/api/publicNormalizers.tsAPI 响应归一化和 classic 安全回退
web/src/api/adminNormalizers.tsAdmin PATCH camelCase → snake_case
web/src/lib/appearance.ts外观预设、默认设置、CSS Token 和明暗模式应用
web/src/components/admin/AdminSettingsSection.tsx后台主题选择器和保存草稿
web/src/App.tsx把生效设置传给首页卡片并应用全站外观
web/src/components/ServerCard.tsx卡片结构、主题作用域和共享指标
web/src/styles.css全局 Token、基础卡片样式和主题作用域规则
web/src/components/ServerCard.test.tsx主题 DOM、信息完整性、状态和格式测试
web/src/components/admin/AdminDashboard.test.tsx选择器、输入校验和保存流程
web/src/api/publicClient.test.tsPublic 设置归一化和非法值回退
web/src/api/adminAuthSettingsClient.test.tsAdmin 设置读取、PATCH 映射和回读

4. 新增服务器卡片主题

以下示例把新主题 ID 写作 compact;实际名称应使用稳定、全小写的 ASCII snake_case 或单词,不要使用展示文案作为持久化值。

第一步:扩展后端契约

  1. validServerCardTheme 中允许新值。
  2. 保持默认值为 classic,不要因为新增主题改变旧实例的展示。
  3. 为 Admin PATCH 成功、非法值拒绝、Public 回读和非法存量回退补测试。
  4. 确认 Public/Admin Settings 都返回 server_card_theme,但不暴露任何凭据。

第二步:扩展前端类型和回退

  1. ServerCardTheme 联合类型中加入新值。
  2. 更新 Public normalizer 的允许值集合;未知值仍回退到 classic
  3. 更新 Admin PATCH DTO 和测试 fixture。
  4. 在后台选择器中加入中文展示名,持久化值保持稳定。
  5. 保存仍必须带 expected_revision;主题开发不能绕过设置的乐观并发控制。

建议把允许值集中成一个只读列表并复用于类型守卫、normalizer 和选择器,避免每增加一个主题就漏改某一处分支。

第三步:实现卡片变体

当前第二种主题通过 is-capsule class 和局部条件渲染实现。加入第三种及更多主题前,应先把单个 capsule 布尔分支整理为可扩展的主题作用域,例如:

tsx
<article className="kulin-node-card" data-card-theme={serverCardTheme}>
  ...
</article>

CSS 仅在主题作用域内覆盖需要改变的部分:

css
.kulin-node-card[data-card-theme='compact'] .node-usage-grid {
  /* 只写 compact 与基础卡片不同的规则 */
}

实现时遵守:

  • 基础 .kulin-node-card 仍是所有主题的共同来源;
  • DOM 差异尽量限制在卡片内部,页面网格、头部、底部健康区和路由不复制;
  • 不用 nth-child 猜测业务含义,优先使用稳定的语义 class;
  • 每项资源和指标保持原有顺序、标签、单位和数据来源;
  • 如果隐藏一个旧区域,必须证明同一信息已在新区域完整出现,而不是直接丢失;
  • CSS 选择器必须同时在浅色、深色和高斯模糊预设下成立。

第四步:补齐测试

至少覆盖:

  • classic 的 DOM 和文案保持不变;
  • 新主题获得唯一、可断言的作用域;
  • 四个资源项以及底部网络、账单、到期、延迟和丢包信息仍存在且顺序稳定;
  • online、warning、offline、no_data 的 class 和文字语义正确;
  • 缺失/非法主题在 Controller 与 Web 两层都回退到 classic
  • Admin 选择、PATCH payload、成功回读和 revision conflict 正确;
  • 长名称、长容量、长账单周期和空数据不产生溢出或伪造值。

不要只断言压缩后的 CSS 字符串。优先断言语义 DOM、class/data attribute、计算样式和真实渲染结果。

第五步:同步文档

新增主题时至少更新:

  • 本文的当前值和兼容性基线;
  • docs/HOME_CARD_SPEC.md 的主题说明和结构规格;
  • docs/API.md 的 Settings 示例、允许值和回退规则;
  • docs/TECHNICAL_DESIGN.md 的公开设置字段;
  • 如为用户可见能力,中文/英文 README 或 Release notes。

5. 新增外观预设

外观预设只是一组参数默认值,不应复制服务器卡片结构。新增预设时:

  1. 扩展 AppearancePreset 和 Controller 的 validAppearancePreset
  2. appearancePresets 中定义完整参数组,不继承未说明的偶然值。
  3. appearancePresetOptions 和 Admin 设置中提供名称。
  4. 继续通过 shellStyleForSettings 生成 CSS Token,不在组件中拼接大段内联样式。
  5. 更新数值边界、默认值、API fixture、前后端校验和测试。
  6. 验证预设能与所有 server_card_theme、明暗模式和背景图组合。

6. 本地验证

主题代码修改后,从仓库根目录执行:

bash
go test -race ./internal/controller/api

go vet ./...

npm --prefix web ci
npm --prefix web test -- --run
npm --prefix web run build
npm --prefix web run check:performance

bash scripts/check-sensitive-files.sh
git diff --check

迭代时可以先运行主题相关测试:

bash
npm --prefix web test -- --run \
  src/components/ServerCard.test.tsx \
  src/components/admin/AdminDashboard.test.tsx \
  src/api/publicClient.test.ts \
  src/api/adminAuthSettingsClient.test.ts

go test ./internal/controller/api -run 'Settings|ServerCardTheme'

最终仍需执行完整前端测试和生产构建。源码测试通过不代表生产静态资源正确;需要检查构建后的 HTML/CSS/JS 确实包含新主题作用域和选择器。

7. 真实视觉验收

使用真实浏览器检查构建产物或候选实例,最低矩阵如下:

维度必测值
卡片主题classic、每个新增主题
明暗模式light、dark
外观预设default、gaussian_blur
背景无背景、有背景
视口1440×900、1024×768、390×844
节点状态online、warning、offline、no_data
数据普通值、长名称/长账单/大容量、空值

每个组合至少确认:

  • 卡片上下分区的分割线和视觉语言一致,没有混用子卡边框;
  • 文字、图标、进度条、状态、间距和对齐清楚;
  • document.documentElement.scrollWidth <= document.documentElement.clientWidth
  • 卡片可点击、可用键盘聚焦和打开;
  • 后台切换后 PATCH 成功,重新读取 Public Settings 仍是所选主题;
  • 刷新页面后主题保持,旧/非法值安全回退;
  • 控制台没有错误,页面没有被装饰层遮挡。

截图必须至少包含桌面和手机、浅色和深色。只看源码、单张桌面截图或 CSS fixture 都不能算完成。

8. 发布与回滚

主题设置和代码发布是两件事:

  • 设置层回滚:在后台切回 classic,保存后回读 Public Settings 并刷新首页确认。
  • 代码层回滚:按正常 Zeno 发布流程保留旧镜像 digest、数据库和部署配置,不能把可变 tag 当作回滚点。
  • 候选版本失败时,先恢复固定旧镜像和配置,再确认 /ready、Public Settings、静态资源和首页渲染。
  • 部署授权不等于 commit、push、PR、tag、Release 或镜像发布授权。

9. 完成定义

一个主题只有同时满足以下条件才算完成:

  1. 前后端允许值、默认值、持久化、回退和 revision 行为一致;
  2. 经典主题没有回归,新主题不复制业务逻辑或丢字段;
  3. 主题 CSS 使用现有 Token,并保持统一的分割线与分组语言;
  4. 相关单测、完整前端测试、Go 测试、生产构建和敏感文件检查通过;
  5. 桌面/手机、浅色/深色、主要状态和长内容完成真实浏览器验收;
  6. API、卡片规格和本开发指南同步更新;
  7. 如已部署,运行镜像、readiness、Public Settings 和线上静态资源均已回读确认。

基于 MIT License 发布