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

317 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 范式)
```css
/* 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)
```css
/* 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):**
```css
/* 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-1~9/dark-1~2,且暗色下**色阶方向反转**。品牌主色运行时换肤(站点名/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 全表(新工程第一天就建)
```css
: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(视觉执行)**
4. 基础 Token 全表(背景四档/文字四档/边框三级/间距六档/圆角三档)是否建立且被引用?
5. 排版抽检:正文 <12px?数字列未右对齐/未 tabular-nums?长文本省略无 title?
6. 图标抽检:是否混用两套风格?纯图标按钮是否缺 tooltip/aria-label?
7. 过渡抽检:同类交互时长是否统一?提交按钮是否都有 loading?四态是否齐全?
8. 对照「好看反模式清单」14 条逐条扫描,命中项列出
9. 暗色抽检:分层是否靠边框/亮度差?图表色板是否随主题重建?
**P2(体验升级)**
10. 对比度达标:正文 ≥ 4.5:1、大字 ≥ 3:1(WCAG AA)——用工具抽查文字层次 Token
11. Tailwind 是否与组件库变量打通(pure `@theme` 桥接)?
12. 是否需要 BUILD_TIME 发版覆盖用户旧主题缓存(soybean 方案)?
13. 灰度/色弱无障碍模式是否值得启用(值班场景)?
---
> 下一篇:[07-组件设计与交互反馈](07-组件设计与交互反馈.md) —— 组件是设计的词汇表。