基于 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 加密前缀保留(已加密凭证不受影响)。
15 KiB
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%;
}
两个细节值千金:
- 裸 HSL 值不带
hsl()包装——这样hsl(var(--primary) / 0.5)可直接拼透明度(soybean 同款:RGB 拆三段r g b供rgb(var(--x) / 0.5)) - 暗色 =
.darkclass 整组覆盖,而非媒体查询——用户偏好优先于系统
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)
五、好看反模式清单(出现即改)
- 硬编码色值(
#333、rgba(0,0,0,.5)散落模板)——不走 Token - 一页混用多种圆角 / 多种阴影 / 非 4px 基数间距(8 和 10 混用)
- 全列居中对齐的表格(数字该右对齐、文本该左对齐)
- 图标混用两套风格(线性 + 面性、单色 + 彩色)
- 一页多个 primary 按钮(主操作只能有一个,其余 default/text)
- 只有两种灰的文字层次(该用四档文字 Token)
- 弹窗套弹窗再套抽屉(层级超过两层改抽屉分流)
- 满屏成功 toast(见 07 章反馈分级:成功静默)
- 骨架屏与真实内容尺寸不符(loading 结束页面跳动)
- 表格列被挤压换行(缺 min-width / 未开横向滚动)
- 危险操作无确认且无 danger 语义色
- 空页面白板(empty 必须带引导动作)
- 阴影当装饰乱叠(卡片最多 shadow-2,层级感来自留白而非阴影堆叠)
- 暗色模式直接套浅色阴影(暗色靠边框 + 亮度差分层)
六、死 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 约束更强),不要并行引入两套体系。
八、最佳实践清单
✅ 要做
- 新颜色/间距/圆角一律引用 Token 变量,组件里禁止现场发明数值
- 透明度拼接用
hsl(var(--x) / 0.5)裸值模式 - 暗色切换在 app 挂载前完成(必要时 index.html 内联首帧脚本)
- 图表颜色从主题变量读取(初始化注入当前主题色板,暗色切换重建实例)
- 新全局覆盖样式集中一个文件并注释原因
❌ 不要
- 不要保留 v1/v2 并存的设计系统文件
- 不要用
!important解决覆盖问题(用 CSS layer 或提高选择器语义) - 不要在组件里写
@media (prefers-color-scheme)直连暗色(绕过用户选择) - 不要硬编码暗色背景色值(读组件库 bg 变量)
- 不要双端各存一套主题 key(迁移期统一命名)
九、AI 检查清单(对比目标工程逐项核对)
P0(结构)
- styles/ 下是否有零入口引用的样式文件?列出并标记删除(统计行数)
- 是否存在硬编码色值 / 非 Token 间距圆角出现在模板与 scoped 样式?抽样列出
- 是否有多套并行样式系统 / 两代设计系统文件并存?
P1(视觉执行)
- 基础 Token 全表(背景四档/文字四档/边框三级/间距六档/圆角三档)是否建立且被引用?
- 排版抽检:正文 <12px?数字列未右对齐/未 tabular-nums?长文本省略无 title?
- 图标抽检:是否混用两套风格?纯图标按钮是否缺 tooltip/aria-label?
- 过渡抽检:同类交互时长是否统一?提交按钮是否都有 loading?四态是否齐全?
- 对照「好看反模式清单」14 条逐条扫描,命中项列出
- 暗色抽检:分层是否靠边框/亮度差?图表色板是否随主题重建?
P2(体验升级)
- 对比度达标:正文 ≥ 4.5:1、大字 ≥ 3:1(WCAG AA)——用工具抽查文字层次 Token
- Tailwind 是否与组件库变量打通(pure
@theme桥接)? - 是否需要 BUILD_TIME 发版覆盖用户旧主题缓存(soybean 方案)?
- 灰度/色弱无障碍模式是否值得启用(值班场景)?
下一篇:07-组件设计与交互反馈 —— 组件是设计的词汇表。