Files
LokiStack/docs/web-ui-guide/06-主题系统与视觉设计.md
T
logan f9c6c1d826 feat: opsx-loki 全面升级 — 前端重构/用户与LDAP/部署向导/日志查询
基于 WEB-UI-Development-Guide 标准的整体升级,并更名 LokiDeploy → opsx-loki。

前端(Gin+React+shadcn/ui):
- 按 02/07 章重构:页面域化拆分(无超 400 行文件)、PageContainer/ListQuery、
  反馈分级(成功静默/失败 toast/危险确认)、401 单飞刷新+队列重放
- 暗色模式(next-themes)+ 语义 Token(--primary 品牌色/success/warning/info)
- 部署向导(状态推导四步:环境→存储→Loki→采集,内嵌表单闭环)
- 日志查询页:LogQL 标签构建器/拖拽直方图/日志上下文/Live Tail/查询历史
- 按用户+标签策略的查询授权(服务端强制合并策略)

后端(Go+Gin+JSON DB):
- 简单用户管理(仅 admin,无 RBAC)+ TokenVersion 会话吊销 + 登录 IP 限流
- LDAP 接入:配置(AES 加密存储/hasPassword 脱敏)/测试连接/批量导入/登录
- 存储配置测试连接(SigV4 零依赖)+ 桶不存在自动创建
- Loki 三模式部署修复:SSH 密钥认证、3.x 配置模板、ruler 对象存储嵌套块、
  凭证解密、孤儿 deploying 状态恢复、Grafana 部署(可选默认关)+面板导入
- DaemonSet 采集纯 YAML(去 helm 依赖)+ DNS 自愈回退 ClusterIP + 集群名标签
- 日志查询代理:cluster-svc 经 kube-apiserver proxy/url 直连 + cursor 翻页
- 统一 log 命名空间;部署就绪探活;Grafana 访问地址自动记录

修复:run.sh 增量编译检测、Sanitized 脱敏补全、LokiDeployment Update 白名单、
健康检查 CreatedAt 零值等 10+ 存量 bug。

数据兼容:DB 沿用旧 lokideploy.json(新名优先);localStorage 一次性迁移;
HKDF 加密前缀保留(已加密凭证不受影响)。
2026-10-24 13:20:12 +08:00

15 KiB
Raw Blame History

06 — 主题系统与视觉设计

参考:vue-vben-admin(HSL Token + Tailwind 桥接 + CSS layer + EP 适配器)、soybean-admin(单一 ThemeSetting 类型 + 三端同步)、vue-pure-admin(EP↔Tailwind v4 变量桥接)、ant-design-pro(antd token + data-theme);补充:gin-vue-admin(EP SCSS 定制三层法)


一、设计 Token:单一事实源

1.1 Token 分层模型

语义层(唯一手写处)
  --background --foreground --primary --card --border --radius ...
     ↓ 派生(工具类桥接)
Tailwind 层    bg-background text-foreground(@theme 映射)
     ↓ 派生(适配器)
组件库层       --el-color-primary / antd token —— 从语义层变量计算得出
     ↓ 派生(算法)
色阶层         primary-50 … primary-950 / light-3 … light-9

铁律:语义 Token 是唯一手写源,其余全部派生。 任何地方再出现硬编码色值(#409eff、rgb(64,158,255))都是违规。

1.2 vben 的 Token 定义(shadcn 范式)

/* vben: packages/@core/base/design/src/design-tokens/default.css */
:root {
  --background: 0 0% 100%;      /* HSL 三元组,不带 hsl() 包装 */
  --primary: 211 91% 39%;
  --border: 214 32% 91%;
  --radius: 0.5rem;
}
.dark {                          /* 整组覆盖,暗色即换一套值 */
  --background: 224 71% 4%;
  --primary: 210 98% 56%;
}

两个细节值千金:

  1. 裸 HSL 值不带 hsl() 包装——这样 hsl(var(--primary) / 0.5) 可直接拼透明度(soybean 同款:RGB 拆三段 r g b 供 rgb(var(--x) / 0.5))
  2. 暗色 = .dark class 整组覆盖,而非媒体查询——用户偏好优先于系统

1.3 Tailwind v4 桥接(vben)

/* internal/tailwind-config/src/theme.css */
@custom-variant dark (&:is(.dark *));            /* class 策略暗色 */
@theme inline {
  --color-background: hsl(var(--background));    /* 语义 Token → 工具类 */
  --color-primary: hsl(var(--primary));
}
@layer properties, theme, base, components, utilities;  /* 级联层声明 */

CSS layer 声明是组件库共存的关键:把组件库样式压在低层,工具类天然可覆盖,避免 !important 军备竞赛。

1.4 pure 的 EP↔Tailwind 桥接与 soybean 的三端同步(★★★★☆)

EP 工程引入原子类的最短路径(pure):

/* vue-pure-admin: src/style/tailwind.css —— Tailwind v4 CSS-first 写法 */
@custom-variant dark (&:is(.dark *));        /* class 策略暗色 */
@theme {
  --color-bg_color: var(--el-bg-color);      /* EP 变量 → 原子色 token */
  --color-primary: var(--el-color-primary);  /* 于是 bg-bg_color / text-primary 与组件库同色 */
}

soybean 则是「单一强类型配置对象」路线:themeSettings(App.Theme.ThemeSetting 一份 schema 即文档)驱动 CSS 变量 + NaiveUI GlobalThemeOverrides + UnoCSS theme 三端同步,且用 BUILD_TIME 作覆盖标志——发版自动淘汰用户本地旧主题缓存(防「新版本旧皮肤」)。两案共同点:换肤 = 改上游变量,组件库与原子类自动跟随。


二、暗色模式

2.1 实现要点(三标杆殊途同归)

环节 做法 反面
切换机制 html 挂 class(vben/pure .dark)或 data-theme="dark"(adp) prefers-color-scheme 媒体查询直连(用户无法覆盖系统)
跟随系统 watch prefers-color-scheme 作为 auto 模式初值,用户手动选择后以用户为准 —
持久化 localStorage 单 key;读取要在 app 挂载前(防白闪,必要时内联脚本) 挂载后再初始化(首屏闪白)
组件库 EP:引 dark/css-vars.css 一行;antd:token 切换 自写暗色样式覆盖组件库

三态标准:light | dark | auto(auto 跟随系统)。附加无障碍模式(可选):灰度 / 色弱滤镜(html-grey / html-weakness,一行 filter + class 切换;值班大屏场景有真实需求)。

暗色的分层策略与浅色不同:阴影在深底上几乎不可见——暗色模式靠边框 + 表面亮度差分层(卡片 = 页面底色提亮一档),不要只调阴影。

2.2 双端 key 一致(迁移期,必须)

同产品双栈并存的工程,暗色 key 必须两端同名(推荐 <app>.theme.mode),切换机制统一(class + data-theme 双挂成本最低)。key 不一致 = 同浏览器两端口偏好不互通 + 迁移时暗色选择器要全量改。


三、组件库主题定制(Element Plus)

标杆做法(vben):语义 Token 派生组件库变量——useElementPlusDesignTokens 监听主题变化,把语义 Token 实时映射为 --el-color-primary 全系(暗色下自动反转色阶方向)。换肤不碰组件库,只改上游 Token。

补充范式(gva 的 EP 专属三层法):

第 1 层 编译期:SCSS 定制色板
  @forward 'element-plus/theme-chalk/src/common/var.scss'
    with ($colors: ('primary': ('base': #409EFF), ...));

第 2 层 运行时:JS 生成 --el-color-primary 及 light-1..10 / dark-1..2 / -bg
  色阶写入 documentElement —— 换主色不重新编译
  (gva setBodyPrimaryColor:light/dark 两套明度算法)

第 3 层 暗色变量:element-plus/theme-chalk/dark/css-vars.css
  + html.dark { --el-bg-color: ... } 局部覆盖

pure 的 setEpThemeColor()(useDataThemeChange.ts)是第 2 层的更简实现:documentElement.style.setProperty + 循环生成 light-19/dark-12,且暗色下色阶方向反转。品牌主色运行时换肤(站点名/Logo/主色/favicon 联动)是底座「品牌可配」能力的核心——主色、圆角(--el-border-radius-base)、基础字号纳入同一变量体系。


四、视觉执行规范(写出好看代码的数值层)

本章前三节解决「主题怎么搭」,本节解决「页面怎么写才好看」。所有数值必须落为 CSS 变量后使用——AI/开发者写页面时只允许引用变量,不允许现场发明数值。

4.1 品牌可配项

配置项 生效点 存储
站点名称 侧栏 logo 文案 / document.title 后缀 后端配置
Logo 侧栏 + favicon 后端配置
主色 --el-color-primary 系列运行时混色 后端配置或用户偏好
暗色 用户偏好 localStorage(统一命名空间)

pure 的补充范式:运行时平台配置(public/platform-config.json,Title/Layout/Theme/DarkMode 等 30+ 项,启动拉取注入)——改配置不发版(详见 08 章 env 分层)。

4.2 基础 Token 全表(新工程第一天就建)

:root {
  /* —— 背景层次(浅色值示例;暗色整组覆盖,靠亮度差分层) —— */
  --bg-page:  #f5f6f8;             /* 页面底 */
  --bg-card:  #ffffff;             /* 卡片 / 弹层 */
  --bg-hover: rgba(0, 0, 0, 0.04); /* 行/项 hover */
  --bg-muted: #f5f7fa;             /* 次级区块 / 代码块 */

  /* —— 文字层次(四档灰,禁用两档灰打天下) —— */
  --text-primary:     rgba(0, 0, 0, 0.88);  /* 正文 */
  --text-secondary:   rgba(0, 0, 0, 0.65);  /* 次要说明、表头 */
  --text-placeholder: rgba(0, 0, 0, 0.38);  /* 占位、禁用标签 */
  --text-disabled:    rgba(0, 0, 0, 0.25);
  --text-inverse:     #ffffff;              /* 深底上的文字 */

  /* —— 边框(三级) —— */
  --border-strong: rgba(0, 0, 0, 0.26);  /* 输入框 */
  --border-base:   rgba(0, 0, 0, 0.15);  /* 卡片描边 */
  --border-light:  rgba(0, 0, 0, 0.08);  /* 分割线 */

  /* —— 语义色(对齐组件库 success/warning/danger/info) —— */

  /* —— 间距(4px 基数,全站只用这六档) —— */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px;
  --space-4: 16px; --space-6: 24px; --space-8: 32px;

  /* —— 圆角(三档) —— */
  --radius-sm: 4px;   /* 标签、小按钮 */
  --radius:    8px;   /* 卡片、按钮、输入框(主值) */
  --radius-lg: 12px;  /* 弹窗、大图 */

  /* —— 阴影(卡片最多 2 级;弹层用组件库默认) —— */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.06);
  --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.08);

  /* —— 层级(防 z-index 军备竞赛) —— */
  --z-dropdown: 100;  --z-drawer: 200;
  --z-modal: 300;     --z-message: 400;
}

项目可从组件库变量派生(EP:--bg-card: var(--el-bg-color)),重点是层次齐全、命名统一。卡片风格全站二选一:描边(border-base)或阴影(shadow-1),不许混用。

4.3 字体与排版

用途 字号 / 行高 字重
页面标题 18-20 / 28 600
卡片标题 16 / 24 600
正文 / 表格 14 / 22 400
次要说明 13 / 20 400
辅助 / 标签 12 / 20 400

规则:正文最小 12px(再小不可读);同一页面标题层级 ≤2 级;数字用 font-variant-numeric: tabular-nums;中英文与数字混排加空格(「共 12 条」);长文本省略必须带 title 提示;字体栈取系统栈 + 组件库默认,禁止引入额外 webfont 除非品牌需要。

4.4 图标规范

  • 一套图标集(线性或面性统一,禁止混搭两套风格;推荐 Iconify 单集或组件库图标)
  • 尺寸四档:12(辅助)/ 16(按钮、菜单、表格行内 主值)/ 20(页头)/ 24(空态插图位)
  • 图标与文字并排间距 6px;按钮内图标与文字同色同级
  • 纯图标按钮必须带 tooltip 与 aria-label

4.5 过渡与微交互

场景 时长 / 缓动
颜色 / hover 变化 150ms ease
展开 / 抽屉 / 弹层 200-300ms cubic-bezier(0.4, 0, 0.2, 1)
页面切换 fade 150ms

规则:同类交互全站同时长同缓动;禁止超过 400ms 的阻塞动画;按钮提交必须有 loading 态(防重复触发);自定义交互组件四态齐全——hover / active / focus-visible / disabled 缺一不可(focus 可见性是可访问性底线)。

4.6 对齐与栅格

  • 表格:数字列右对齐 + tabular-nums,文本列左对齐,居中对齐只用于状态标签/操作列(全列居中是典型反模式)
  • 表单:标签右对齐或上对齐全站统一一档;输入宽度按内容语义定(手机号 11 位就别 100% 宽)
  • 卡片间距统一 --space-4(16px);页面内边距 --space-6(24px)

五、好看反模式清单(出现即改)

  1. 硬编码色值(#333、rgba(0,0,0,.5) 散落模板)——不走 Token
  2. 一页混用多种圆角 / 多种阴影 / 非 4px 基数间距(8 和 10 混用)
  3. 全列居中对齐的表格(数字该右对齐、文本该左对齐)
  4. 图标混用两套风格(线性 + 面性、单色 + 彩色)
  5. 一页多个 primary 按钮(主操作只能有一个,其余 default/text)
  6. 只有两种灰的文字层次(该用四档文字 Token)
  7. 弹窗套弹窗再套抽屉(层级超过两层改抽屉分流)
  8. 满屏成功 toast(见 07 章反馈分级:成功静默)
  9. 骨架屏与真实内容尺寸不符(loading 结束页面跳动)
  10. 表格列被挤压换行(缺 min-width / 未开横向滚动)
  11. 危险操作无确认且无 danger 语义色
  12. 空页面白板(empty 必须带引导动作)
  13. 阴影当装饰乱叠(卡片最多 shadow-2,层级感来自留白而非阴影堆叠)
  14. 暗色模式直接套浅色阴影(暗色靠边框 + 亮度差分层)

六、死 CSS 治理

存量工程常见病灶:styles/ 下积累多代无入口引用的样式文件(曾有工程沉淀约 2000 行死 CSS,且「设计系统」存在 v1/v2 两代并存——换了两茬样式系统、旧的没删的考古层)。

6.1 治理方案(四步)

第 1 步 验死:对每个文件跑全仓引用检查(import/@import/url()),
        确认零引用后直接删除(git 里有历史,不建备份目录)
第 2 步 收敛:保留三件套 = variables(语义 Token)+ 栅格 + 基础元素;
        组件样式回归 scoped / 组件库
第 3 步 立规:CI 检查 styles/ 下每个 .css 必须被入口链引用,
        新文件无引用直接 fail
第 4 步 单源:设计规范类文档只留一份(docs),代码里不留第二套实现

规则:CSS 文件必须有入口引用链,如同 JS 文件必须有 import——「无引用样式」=「无主代码」,删除是唯一正确处理。


七、样式写法优先级(adp 明文范式,直接抄用)

1. 组件库自带能力(EP 的 type/size/token props)      ← 优先
2. 语义工具类 / Tailwind 原子类(布局、间距)          ← 其次
3. scoped 样式 + CSS 变量(主题相关)                  ← 业务组件
4. 深度选择器 :deep()(覆盖组件库,须注释原因)         ← 最后手段
5. 全局覆盖(唯一收口文件,如 element 覆盖表)          ← 禁散落

gva 的补充范式:语义化页面类(gva-search-box / gva-table-box / gva-btn-list)。与「页面容器 + 列表组件」的组件路线二选一——推荐组件路线(类型提示与 props 约束更强),不要并行引入两套体系。


八、最佳实践清单

✅ 要做

  1. 新颜色/间距/圆角一律引用 Token 变量,组件里禁止现场发明数值
  2. 透明度拼接用 hsl(var(--x) / 0.5) 裸值模式
  3. 暗色切换在 app 挂载前完成(必要时 index.html 内联首帧脚本)
  4. 图表颜色从主题变量读取(初始化注入当前主题色板,暗色切换重建实例)
  5. 新全局覆盖样式集中一个文件并注释原因

❌ 不要

  1. 不要保留 v1/v2 并存的设计系统文件
  2. 不要用 !important 解决覆盖问题(用 CSS layer 或提高选择器语义)
  3. 不要在组件里写 @media (prefers-color-scheme) 直连暗色(绕过用户选择)
  4. 不要硬编码暗色背景色值(读组件库 bg 变量)
  5. 不要双端各存一套主题 key(迁移期统一命名)

九、AI 检查清单(对比目标工程逐项核对)

P0(结构)

  1. styles/ 下是否有零入口引用的样式文件?列出并标记删除(统计行数)
  2. 是否存在硬编码色值 / 非 Token 间距圆角出现在模板与 scoped 样式?抽样列出
  3. 是否有多套并行样式系统 / 两代设计系统文件并存?

P1(视觉执行)

  1. 基础 Token 全表(背景四档/文字四档/边框三级/间距六档/圆角三档)是否建立且被引用?
  2. 排版抽检:正文 <12px?数字列未右对齐/未 tabular-nums?长文本省略无 title?
  3. 图标抽检:是否混用两套风格?纯图标按钮是否缺 tooltip/aria-label?
  4. 过渡抽检:同类交互时长是否统一?提交按钮是否都有 loading?四态是否齐全?
  5. 对照「好看反模式清单」14 条逐条扫描,命中项列出
  6. 暗色抽检:分层是否靠边框/亮度差?图表色板是否随主题重建?

P2(体验升级)

  1. 对比度达标:正文 ≥ 4.5:1、大字 ≥ 3:1(WCAG AA)——用工具抽查文字层次 Token
  2. Tailwind 是否与组件库变量打通(pure @theme 桥接)?
  3. 是否需要 BUILD_TIME 发版覆盖用户旧主题缓存(soybean 方案)?
  4. 灰度/色弱无障碍模式是否值得启用(值班场景)?

下一篇:07-组件设计与交互反馈 —— 组件是设计的词汇表。