757 lines
42 KiB
Markdown
757 lines
42 KiB
Markdown
# Thing UI 设计系统与开发约束
|
||
|
||
> **适用范围**:`src/` 下全部前端代码(12 个模块、220+ 组件)
|
||
> **约束力**:第 7 章为硬性约束,Code Review 据此判定;第 3–5 章为实现规范
|
||
> **配套**:`UI Design System` 画布(色彩 / 排版 / 组件 / 骨架四屏可视化规范)
|
||
> **唯一事实来源**:token 值以 `src/style.css` 为准,本文档与之同步
|
||
|
||
---
|
||
|
||
## 1. 设计原则
|
||
|
||
以下 8 条为不可协商项,贯穿全部模块。
|
||
|
||
**P1 · 单一强调色**
|
||
全应用只有 `--primary` 一种交互色,用于按钮、链接、选中态、焦点环。不存在"第二个主题色"。状态色(success/warning/danger)仅表达**状态**,不得用于可点击元素。
|
||
|
||
**P2 · 颜色只能来自 token**
|
||
禁止在组件内写 hex、rgb、oklch,禁止引用 Tailwind 原生色阶(`bg-blue-500`、`text-emerald-700`)。组件里出现的每个颜色都必须能追溯到 `style.css` 的一个语义变量。
|
||
|
||
**P3 · 状态必须四态齐全**
|
||
任何数据驱动的界面必须实现 **加载 / 空 / 错误 / 成功** 四态。禁止空白,禁止静默失败。这是最容易漏、也最影响质感的一条。
|
||
|
||
**P4 · 一个主区域,一个主操作**
|
||
每屏有且仅有一个视觉重心、一个主按钮。破坏性操作(停止内核、删除记录)必须视觉降级或二次确认,且不得与主操作同权重。
|
||
|
||
**P5 · 骨架统一,内容区自治**
|
||
所有模块共享同一套外壳(侧边栏 / 标题栏 / Tab 行)。**模块之间允许不同的只有内容区布局**——这是"统一"与"灵活"的分界线。
|
||
|
||
**P6 · 密度一致**
|
||
全应用统一使用**紧凑偏中**密度:13px 正文、34px 控件、16px 卡片内边距。禁止在同一屏内混用疏密两种节奏。
|
||
|
||
**P7 · 卡片统一描边 + 轻阴影,浮层用重阴影**
|
||
层级靠**表面色差 + 1px 描边 + 轻阴影(`shadow-sm` / `--shadow-card`)**表达。浮层(下拉、气泡、对话框)使用更重的 `--shadow-popover` / `--shadow-dialog`。
|
||
|
||
**P8 · 中文界面不做负字距**
|
||
字体模板中的负字距(`-0.28px` 等)是为拉丁字母设计的,会让中文发挤。**拉丁文与数字可保留微调,中文一律 `letter-spacing: 0`。**
|
||
|
||
---
|
||
|
||
## 2. Design Token
|
||
|
||
### 2.1 命名与结构
|
||
|
||
Token 分四组,**在现有 shadcn 变量之上扩展,不另起一套**:
|
||
|
||
```
|
||
shadcn 既有(值调整) → --background / --foreground / --primary / --card / --border / ...
|
||
本项目扩展 → --canvas / --fg-* / --success / --warning / --data-* / --shadow-*
|
||
```
|
||
|
||
**关键调整**:把 shadcn 的 `--primary` 从当前近黑色(`oklch(0.21 0.006 285.885)`)改为强调蓝 `#0066CC`。这样全部 shadcn 组件(Button / Switch / Checkbox / focus ring)自动获得统一的交互色,无需逐组件改样式。
|
||
|
||
### 2.2 色彩
|
||
|
||
#### 浅色模式(`:root`)
|
||
|
||
```css
|
||
:root {
|
||
/* ── 表面 ───────────────────────────── */
|
||
--canvas: #F5F5F7; /* 应用底 / 模块内容区背景 */
|
||
--background: #FFFFFF; /* shadcn 基准面 */
|
||
--card: #FFFFFF; /* 卡片 / 面板 */
|
||
--popover: #FFFFFF; /* 浮层 */
|
||
--subtle: #F0F0F2; /* 次级填充:搜索框底、分段控件槽、代码块 */
|
||
--hover: #EBEBED; /* 幽灵控件 hover */
|
||
--active: #E4E4E7; /* 幽灵控件 active */
|
||
|
||
/* ── 描边 ───────────────────────────── */
|
||
--border: #E4E4E7; /* 卡片外框、输入框 */
|
||
--border-strong: #D1D1D6; /* 需要更强分隔时 */
|
||
--divider: #F0F0F2; /* 卡片内部分隔线(1px) */
|
||
--input: #E4E4E7;
|
||
|
||
/* ── 文字 ───────────────────────────── */
|
||
--foreground: #1D1D1F; /* 主文字:标题、正文、数值 */
|
||
--fg-secondary: #55555C; /* 次文字:键名、标签说明、幽灵控件文字 */
|
||
--muted-foreground: #8A8A90; /* 辅助:占位符、规格说明、次级 meta */
|
||
--fg-disabled: #B4B4BA; /* 禁用 */
|
||
|
||
/* ── 强调 ───────────────────────────── */
|
||
--primary: #0066CC;
|
||
--primary-hover: #0055B3;
|
||
--primary-active: #004A9E;
|
||
--primary-foreground: #FFFFFF;
|
||
--primary-soft: #E8F1FC; /* 选中 Tab 底、选中行底 */
|
||
--primary-soft-text: #004F9E; /* 上述底上的文字 */
|
||
--ring: #0071E3;
|
||
|
||
/* ── 语义状态 ───────────────────────── */
|
||
--success: #0F7B4F; --success-soft: #E7F5EE; --success-text: #0B5C3B;
|
||
--warning: #A96B00; --warning-soft: #FDF4E4; --warning-text: #7D4F00;
|
||
--danger: #C7362F; --danger-soft: #FDECEA; --danger-text: #9A2620;
|
||
--info: #0066CC; --info-soft: #E8F1FC; --info-text: #004F9E;
|
||
--neutral: #8A8A90; --neutral-soft: #F0F0F2; --neutral-text: #55555C;
|
||
--destructive: #C7362F; /* shadcn 兼容别名,指向 danger */
|
||
--destructive-foreground: #FFFFFF;
|
||
|
||
/* ── 数据方向(与状态语义解耦)───────── */
|
||
--data-down: #0F7B4F; /* 下载 / 入站 */
|
||
--data-up: #C7362F; /* 上传 / 出站 */
|
||
|
||
/* ── 尺寸 ───────────────────────────── */
|
||
--radius: 12px;
|
||
--radius-xs: 4px; --radius-sm: 6px; --radius-md: 8px;
|
||
--radius-lg: 12px; --radius-xl: 16px;
|
||
|
||
/* ── 阴影 ───────────────────────────── */
|
||
--shadow-card: 0 1px 2px rgba(0,0,0,0.06);
|
||
--shadow-popover: 0 1px 3px rgba(0,0,0,0.06), 0 4px 16px rgba(0,0,0,0.10);
|
||
--shadow-dialog: 0 4px 12px rgba(0,0,0,0.08), 0 20px 48px rgba(0,0,0,0.16);
|
||
}
|
||
```
|
||
|
||
#### 深色模式(`.dark`)
|
||
|
||
深色下**必须成对重定义**,且强调色改为亮蓝(`#0066CC` 在深底上对比度不足):
|
||
|
||
```css
|
||
.dark {
|
||
--canvas: #16161A;
|
||
--background: #16161A;
|
||
--card: #1E1E22;
|
||
--popover: #1E1E22;
|
||
--subtle: #26262B;
|
||
--hover: #2C2C32;
|
||
--active: #33333A;
|
||
|
||
--border: #2E2E34;
|
||
--border-strong: #3A3A42;
|
||
--divider: #26262B;
|
||
--input: #2E2E34;
|
||
|
||
--foreground: #F5F5F7;
|
||
--fg-secondary: #A8A8B0;
|
||
--muted-foreground: #7A7A82;
|
||
--fg-disabled: #55555C;
|
||
|
||
--primary: #2997FF;
|
||
--primary-hover: #4AA8FF;
|
||
--primary-active: #1A85E8;
|
||
--primary-foreground: #FFFFFF;
|
||
--primary-soft: #14304D;
|
||
--primary-soft-text: #7FC0FF;
|
||
--ring: #2997FF;
|
||
|
||
--success: #3DC77F; --success-soft: #12301F; --success-text: #7FE0AB;
|
||
--warning: #E0A83C; --warning-soft: #33260E; --warning-text: #F0C976;
|
||
--danger: #FF6B60; --danger-soft: #3A1A18; --danger-text: #FF9E96;
|
||
--info: #2997FF; --info-soft: #14304D; --info-text: #7FC0FF;
|
||
--neutral: #7A7A82; --neutral-soft: #26262B; --neutral-text: #A8A8B0;
|
||
--destructive: #FF6B60; --destructive-foreground: #FFFFFF;
|
||
|
||
--data-down: #3DC77F;
|
||
--data-up: #FF6B60;
|
||
}
|
||
```
|
||
|
||
#### 状态色三件套的用法
|
||
|
||
每个语义状态有 **3 个** token,用途严格区分:
|
||
|
||
| Token | 用途 | 示例 |
|
||
|---|---|---|
|
||
| `--success` | 实心:状态点、图标、粗体数值 | ● 运行中 的圆点 |
|
||
| `--success-soft` | 浅底:徽章背景、行高亮 | `<span class="bg-success-soft">` |
|
||
| `--success-text` | 浅底上的文字 | 徽章内的"已安装" |
|
||
|
||
**禁止**把 `--success` 用作文字色 + `--success-soft` 以外的底色组合,也**禁止**用状态色做可点击元素的颜色(见 P1)。
|
||
|
||
#### 强调色的使用边界
|
||
|
||
`--primary` 只表达两件事:**这里可以点**、**这里是当前项**。
|
||
|
||
| 用 | 不用 |
|
||
|---|---|
|
||
| 主按钮底色、开关开启态、焦点环 | 卡片标题前的**装饰性图标** |
|
||
| 当前 Tab / 选中列表行 / 选中项 | 全部 Tab 的图标 |
|
||
| 可点击的文字链接 | 状态徽章(那是状态语义色的职责) |
|
||
| 输入框聚焦态 | 大面积背景、分隔线、装饰元素 |
|
||
|
||
**反例(已修正)**:proxy 概览页的 6 个卡片标题图标全部用了 `text-primary`,加上主按钮、开关、选中态,整屏发蓝 —— 结果"蓝色"不再意味着"可点击",层次感也丢了。已统一改为 `--fg-secondary`。
|
||
|
||
#### 数据方向色(`--data-*`)
|
||
|
||
下载/上传速率沿用现有视觉(绿/红),但独立成 token,**与 `success/danger` 解耦**。
|
||
|
||
> **待决策**:当前 `data-up` 与 `danger` 同色,会让人把"上传速率"误读为"异常"。若希望彻底解耦,建议改为
|
||
> `--data-down: #0066CC`(蓝)/ `--data-up: #A96B00`(琥珀)。这是**唯一改动成本为零**的时机——只需改两个 token。
|
||
|
||
### 2.3 排版
|
||
|
||
字体族保持不变(现有 `--font-sans` 已正确针对中文优化):
|
||
|
||
```css
|
||
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||
'Microsoft YaHei', 'PingFang SC', 'Noto Sans SC', sans-serif;
|
||
--font-mono: 'JetBrains Mono', 'Cascadia Code', Consolas, monospace;
|
||
```
|
||
|
||
生产代码不引用外部字体 CDN(设计稿 / 原型页可保留 Inter);数值对齐通过全局 `font-feature-settings: 'tnum','lnum'` 实现,不依赖具体字体。
|
||
|
||
字号标尺(9 级,全部为 `px`):
|
||
|
||
| Token | 字号 / 字重 | 行高 | 用途 |
|
||
|---|---|---|---|
|
||
| `display` | 28 / 700 | 36 | 页面级大标题(设置页、关于页),**全应用不超过 1 处/屏** |
|
||
| `metric` | 22 / 600 | 28 | 关键数值(速率、容量)· **必须 `font-variant-numeric: tabular-nums`** |
|
||
| `h1` | 16 / 600 | 24 | 模块标题、卡片标题 |
|
||
| `h2` | 14 / 600 | 20 | 区块标题、卡片标题(小卡) |
|
||
| `h3` | 13 / 600 | 20 | 小节标题、列表分组标题 |
|
||
| `body` | 13 / 400 | 20 | **界面默认正文** |
|
||
| `body-strong` | 13 / 600 | 20 | 行内强调、键值行的键 |
|
||
| `caption` | 12 / 400 | 18 | 辅助说明、参数解释 |
|
||
| `label` | 11 / 500 | 16 | 徽章、标签、表头、单位 |
|
||
|
||
**规则**:
|
||
|
||
1. 字号只从以上 9 级中取,**禁止 `text-[13.5px]` 这类任意值**。
|
||
2. 数值(速率、版本号、计数)统一用 `tabular-nums`,避免刷新时数字跳动。
|
||
3. 中文 `letter-spacing: 0`;拉丁小标题可 `-0.1px`,正文一律 `0`。
|
||
4. 行高按表中固定值写,不用 Tailwind 默认相对行高。
|
||
|
||
### 2.4 间距
|
||
|
||
4px 基数,**8 个值,用尽即止**:
|
||
|
||
| Token | 值 | 典型用途 |
|
||
|---|---|---|
|
||
| `space-1` | 4 | 图标与文字间隙、徽章内边距(纵向) |
|
||
| `space-2` | 8 | 控件内元素间隙、徽章内边距(横向) |
|
||
| `space-3` | 12 | 列表行内间隙、卡片标题与内容间距 |
|
||
| `space-4` | 16 | **卡片内边距、卡片间距** |
|
||
| `space-5` | 20 | **模块内容区默认内边距** |
|
||
| `space-6` | 24 | 区块之间、对话框内边距 |
|
||
| `space-8` | 32 | 大区块分隔 |
|
||
| `space-12` | 48 | 页面级分段 |
|
||
|
||
**禁止** `p-[18px]`、`gap-[14px]`、`mt-[6px]`。需要更细的调整时,说明是布局问题而非间距问题。
|
||
|
||
### 2.5 圆角
|
||
|
||
| Token | 值 | 适用 |
|
||
|---|---|---|
|
||
| `radius-xs` | 4 | 内联小元素:标签内块、进度条端点 |
|
||
| `radius-sm` | 6 | 徽章、小按钮、下拉项 |
|
||
| `radius-md` | 8 | **按钮、输入框、下拉框、分段控件、Tab 项** |
|
||
| `radius-lg` | 12 | **卡片、面板、样张容器** |
|
||
| `radius-xl` | 16 | 对话框、大浮层 |
|
||
| `radius-full` | 9999 | 状态点、开关、头像、搜索框(胶囊) |
|
||
|
||
**规则**:同层级控件必须同圆角。按钮 8px 就全应用 8px,不允许某模块用 6px。
|
||
|
||
### 2.6 阴影与层级
|
||
|
||
**层级主要靠表面色差 + 描边 + 轻阴影**(P7):
|
||
|
||
| 层级 | 表现 | 用途 |
|
||
|---|---|---|
|
||
| L0 应用底 | `--canvas`,无描边无阴影 | 窗口、模块内容区 |
|
||
| L1 卡片 | `--card` + 1px `--border` + 轻阴影 `shadow-sm` | 所有卡片、面板 |
|
||
| L1.5 次级容器 | `--subtle`,无描边 | 搜索框底、分段控件槽、代码块 |
|
||
| L2 浮层 | `--card` + `--shadow-popover` | 下拉菜单、气泡、tooltip、右键菜单 |
|
||
| L3 模态 | `--card` + `--shadow-dialog` + 遮罩 | 对话框、AlertDialog |
|
||
|
||
卡片默认使用轻阴影 `--shadow-card`(`shadow-sm`);`--shadow-popover` / `--shadow-dialog` 仅用于浮层与模态。
|
||
|
||
### 2.7 动效
|
||
|
||
```css
|
||
--ease-standard: cubic-bezier(0.22, 0.61, 0.36, 1); /* 沿用现有 tab-animate 曲线 */
|
||
--duration-fast: 120ms; /* hover、颜色变化 */
|
||
--duration-base: 200ms; /* 展开、浮层出现 */
|
||
--duration-slow: 320ms; /* 页面/Tab 切换 */
|
||
```
|
||
|
||
**规则**:
|
||
|
||
1. 只对 `opacity` 和 `transform` 做动画(性能)。
|
||
2. 按钮按下统一 `transform: scale(0.97)`,不用颜色变化模拟按压。
|
||
3. 所有动画必须包在 `@media (prefers-reduced-motion: reduce)` 里降级为 `none`(现有 `.music-eq` 已正确实现,作为模板)。
|
||
4. Tab 切换复用现有全局 `.tab-animate` 类,**不要各模块自建过渡**。
|
||
|
||
### 2.8 图标
|
||
|
||
- 库:`@lucide/vue`,**禁止混用其他图标库或内联 Unicode 符号**(`↓` `✓` `●` 等)。
|
||
- 尺寸只有 3 档:`14`(行内 / 徽章内)、`16`(按钮、Tab、默认)、`20`(侧边栏、卡片标题)。
|
||
- `stroke-width` 统一 `1.75`(lucide 默认 2,偏粗,与 13px 正文不协调)—— 在 `style.css` 里对 `.lucide` 全局设置,不要逐个组件写。
|
||
- 图标**不单独承载语义**:状态点旁必须有文字,工具栏图标必须有 tooltip 或文字标签(P4 可理解性)。
|
||
|
||
### 2.9 主题色自定义(已落地)
|
||
|
||
**需求**:设置页提供主题色选择,统一改变全应用的强调色。
|
||
|
||
**可行性**:可行且成本低 —— 因为全应用的"可交互色"已经收敛到单一来源 `--primary`。落地后组件层改动为 0。
|
||
|
||
**实现**:`src/lib/theme.ts`(派生 + 应用)+ `appStore.setAccent()` + 设置页「主题色」卡片。
|
||
用户**只选一个基色**,深浅两套由代码派生 —— 不需要为浅色/深色各选一次(同色相两个亮度是既有做法:
|
||
浅 `#0066cc` / 深 `#2997ff`),也不会出现"浅色选青、深色选紫"的语义割裂。
|
||
|
||
#### 需要派生的变量
|
||
|
||
只改基色是不够的,必须同时派生 7 个变量,否则 hover / 选中态仍是旧色:
|
||
|
||
| 变量 | 派生规则(OKLCH,色相 H 原样保留) | 用途 |
|
||
|---|---|---|
|
||
| `--primary` | 基色,L 归一到 `0.42–0.58`(浅)/ `0.68–0.80`(深),C 限制在 `0.06–0.20` | 主按钮、开关、焦点环 |
|
||
| `--primary-hover` | 浅:L − 0.08;深:L + 0.06 | 主按钮 hover |
|
||
| `--primary-active` | 浅:L − 0.16;深:L − 0.10 | 主按钮按下 |
|
||
| `--primary-soft` | 浅:`oklch(0.95, C×0.30, H)`;深:`oklch(0.30, C×0.45, H)` | 选中底、幽灵按钮 hover |
|
||
| `--primary-soft-text` | 基色加深/提亮起步,再按对比度推进到 ≥4.5:1 | 上述底上的文字 |
|
||
| `--ring` | 同 `--primary` | 焦点环 |
|
||
| `--primary-foreground` | 白字低于 2.5:1 时才换 `#1D1D1F` | 填充上的文字 |
|
||
|
||
深色模式**单独派生一套**(深底上要提亮),不能复用浅色值。
|
||
|
||
#### 关键技术点
|
||
|
||
1. **用 OKLCH 做派生,不要用 HSL。**
|
||
只换色相 H、锁定 L 与 C,才能保证换任意色相后对比度一致。HSL 的"明度"感知不均匀,换色相会时亮时暗。
|
||
|
||
2. **对比度靠"推进亮度"而不是固定阈值判定。**
|
||
同一 OKLCH L 下不同色相的 WCAG 亮度差很多(L=0.58 的青柠对白底只有 ~2.8:1,蓝色却有 ~5:1),
|
||
所以归一到区间后还要继续推进 L 直到达标:
|
||
- 浅色:`--primary` 对 `#FFFFFF` ≥ 4.5:1(它会被当文字用在白卡上)→ 选亮色自动压暗
|
||
- 深色:`--primary` 对 `#1E1E22` ≥ 4.5:1 → 自动提亮
|
||
- `--primary-soft-text` 对 `--primary-soft` ≥ 4.5:1
|
||
以上断言在 `src/lib/theme.test.ts` 里对 10 个预设 × 2 模式全量校验。
|
||
|
||
3. **`--primary-foreground` 的取舍是有意的。**
|
||
深色模式下强调色必须够亮才能在深底上当文字用(≥4.5:1),同一颜色做填充时白字只能到 2.7–3:1 ——
|
||
现有主题就是这个取舍(`#2997ff` + 白字 ≈ 2.98:1)。因此门槛按 UI 组件的 2.5:1 兜底,
|
||
只在自定义极亮色时才改深墨。
|
||
|
||
4. **只开放"强调色",不开放状态色。**
|
||
`success` / `warning` / `danger` 是语义色,改了会破坏"绿色=成功"的心智模型。`--data-*` 同理。
|
||
`--info` 一族当前取值与强调色相同(蓝),但**不跟随**主题色 —— 它表达"信息"而非"可交互",
|
||
换紫/绿主色后少数徽标(代理"国外"、HTTP 1xx/3xx、快速面板分类)仍是蓝色,属有意的语义区分。
|
||
|
||
5. **预设 + 自定义。**
|
||
10 个预设(默认蓝 / 靛蓝 / 紫罗兰 / 玫红 / 橙 / 琥珀 / 青柠 / 翠绿 / 青绿 / 石墨)+ 原生取色器。
|
||
纯自由取色很容易选出对比度不达标的颜色,所以色板优先。
|
||
色板显示色用**派生后的浅色模式主色**(所见即所得,如青柠显示为压暗后的绿)。
|
||
|
||
6. **实时预览 + 可回滚。**
|
||
点击色块 / 拖动取色器立即生效(写 7 个变量,无防抖);「恢复默认」清除行内变量回到内置蓝。
|
||
|
||
7. **持久化**:随其它设置写入 `thing_app_settings`(`accentId` + `accentHex`)。
|
||
属**纯增量字段**,`SETTINGS_VERSION` 不递增 —— 递增会命中"版本不匹配清空"分支,
|
||
把用户的模块排序/启停/开机自启一并抹掉,为零收益付真实数据损失。
|
||
|
||
8. **行内变量的优先级陷阱。**
|
||
变量写在 `documentElement` 的行内样式上,优先级高于 `:root` 与 `.dark` 两条规则,因此:
|
||
切模式必须**整组 7 个重写**(否则浅色的行内值会残留到深色模式);
|
||
恢复默认必须 `removeProperty`(写空串仍算"已设置")。
|
||
|
||
#### 落地范围
|
||
|
||
- `src/lib/theme.ts` —— 派生计算 + 应用/清除(新增,含单测)
|
||
- `src/stores/appStore.ts` —— `accentId`/`accentHex` + `setAccent()`,在 `applyTheme()` 与
|
||
`handleSystemThemeChange()` 里按当前模式应用
|
||
- `src/modules/settings/GeneralSettings.vue` —— 「主题色」卡片
|
||
- 9 个独立窗口(快速面板 / 托盘菜单 / 下载窗 / 剪贴板弹窗与预览 / 翻译弹窗 / 截图覆盖层·编辑器·贴图)
|
||
各加一行 `applyStoredAccent()`;托盘菜单额外补 `storage` 监听
|
||
- `src/style.css` —— **无需改动**(7 个变量已是 CSS 变量)
|
||
- **首屏闪烁(FOUC)不需要额外处理**:主窗口 `visible: false`,`appStore.init()` 在 `show()` 前完成应用;
|
||
文档原建议的 Rust `initialization_script` 方案与本仓库 localStorage 方案冲突,不实施
|
||
|
||
**影响面:0 处组件改动。** 这正是把交互色收敛到 `--primary` 单点带来的收益 —— 现在不做,将来也不用挨个改组件。
|
||
|
||
---
|
||
|
||
## 3. 页面框架
|
||
|
||
框架只定义三件事:**谁多高、谁滚动、padding 加在哪**。这三件事定死之前,任何模块级的美化都会被布局 bug 吃掉。
|
||
|
||
### 3.1 分区与高度约束链
|
||
|
||
```
|
||
AppShell h-screen flex-col
|
||
├ TitleBar h-14 shrink-0 固定 · 侧栏图标 + 模块 Tab + 搜索
|
||
└ 主体 flex-1 min-h-0 flex-row
|
||
├ Sidebar w-15 shrink-0 60px 固定
|
||
└ 内容滚动区 flex-1 min-h-0 overflow-y-auto ← 全应用唯一滚动容器
|
||
└ 内容 wrapper p-5(占满内容宽,无 max-width)
|
||
```
|
||
|
||
Tab 已并入标题栏(见 3.5),内容区不含独立 Tab 行,纵向空间最大化。
|
||
|
||
**三个必须遵守的点**(都是实际踩过的坑,不是理论):
|
||
|
||
1. **`min-h-0` 不能漏。**
|
||
flex 子项默认 `min-height: auto` —— 内容变高时它**不收缩**,直接把父容器顶破,底部 padding 被推出视口。这就是"卡片贴着应用底部"的根因。
|
||
|
||
2. **padding 加在滚动容器**内部**的 wrapper 上。**
|
||
加在滚动容器自身,底部 padding 会被内容高度覆盖,滚动到底依然贴边。
|
||
|
||
3. **断点按内容宽度选,不按视口宽度。**
|
||
侧边栏固定占 60px —— 视口 1000px 时内容只剩 940px。两列网格必须用 `md`(768) 而不是 `lg`(1024),否则窗口一窄就退化成单列全宽、卡片大片留白。
|
||
|
||
### 3.2 滚动归属
|
||
|
||
**全应用只有一处滚动容器。**
|
||
|
||
- 模块内容区随外层滚动,**不是**每个模块各建一个滚动容器
|
||
- 模块根 / 页面级容器**禁止** `overflow-y-auto` —— 那会绕过外层 `ScrollArea`,出现原生宽滚动条(快速面板踩过);根容器一律 `min-h-full`
|
||
- 局部列表(剪贴板历史、音乐队列等)**可以用 `ScrollArea` 组件**做独立滚动 —— 这是「局部区域」而非「页面级」
|
||
- 需要固定表头的长表格:用 `position: sticky` 固定表头,而不是给表格单独开滚动容器
|
||
- 例外:terminal / translate 的工作台面板内部滚动(workbench 模式特征)
|
||
- 代价:切换 Tab 时共享同一个滚动位置(可以接受,反而更符合"翻页"直觉)
|
||
|
||
**统一组件使用规则**(违规即 UI 不一致,等同违反第 5 章 D 系):
|
||
|
||
| 需求 | 用 | 禁止 |
|
||
|---|---|---|
|
||
| 页面滚动 | 外层 `ScrollArea`(自动,模块无需处理) | 模块根 `overflow-y-auto` |
|
||
| 局部列表滚动 | `ScrollArea` 组件 | 原生 `overflow` |
|
||
| 键值行 / 状态点 / 徽章 / 大数值 | `@/components/common` 对应组件 | 手写 flex 结构 |
|
||
| 卡片 | `Card` / `InfoGroup(card)` / `StatCard`(统一 `shadow-sm`) | 手写描边块 |
|
||
| 多分组设置表单 | `SettingGroup` + `SettingRow` | 手写行 |
|
||
|
||
### 3.3 内容区宽度
|
||
|
||
内容区**不限宽,占满窗口宽度**(已移除 `max-w-6xl` 限制)。窗口拉宽时卡片随内容区延展,不再居中约束。
|
||
|
||
### 3.4 内容区布局模式
|
||
|
||
内容区**只能**从以下五种里选,不得自创:
|
||
|
||
| 模式 | 适用 | 规格 |
|
||
|---|---|---|
|
||
| `grid` | 概览 / 状态总览 | `grid-cols-1 md:grid-cols-2`·gap 16·`items-start` 让卡片各自高度 |
|
||
| `split` | 列表类(clipboard / music / terminal) | 左栏 280–320px·右栏 fill·中间 1px `--border`·无 padding |
|
||
| `workbench` | 输入-输出类(devtools / translate) | 纵向堆叠·gap 12 |
|
||
| `settings` | 设置页 | 纵向堆叠·gap 24·复用 `SettingGroup` |
|
||
| `plain` | 其他 | 纵向堆叠·gap 12 |
|
||
|
||
**`grid` 的用法边界**:只适合「各块内容量接近」的概览页。若各块信息量差异大,两列会让矮的那列留空。
|
||
|
||
**概览页标准结构 · 「状态横幅 + 卡片网格」**(适用于 proxy / 下载 / 硬件监控,四处横幅已统一,见下方「落地状态」):
|
||
|
||
```
|
||
第一行:状态横幅(全宽,独立于网格,高约 76px)
|
||
├ 容器 `Card`(`!py-0 !gap-0`)+ `CardContent`(`flex flex-wrap items-center gap-x-6 gap-y-3 px-4 py-4`)
|
||
│ 圆角/描边/阴影一律由 Card 提供(`rounded-xl + border + shadow-sm`),禁止手写 `rounded-lg border bg-card`
|
||
├ 左组成组(状态 → 核心属性 → 实时指标,依次靠左,不把指标悬在中间)
|
||
│ 状态区 状态点 8px + 状态文字 13px/500 + 进程名 13px 次要色(状态点/文字用语义色;运行态加 status-dot-pulse)
|
||
│ 指标区 label 11px 灰 + 数值 18px/600 tabular-nums;核心属性(如内核版本、PID)也占一个指标位
|
||
└ 操作区 运行中 = 重启(outline)+停止(destructive);停止 = 启动(primary);sm = 32px;`ml-auto` 推到行尾
|
||
下方:**两列瀑布流** `columns-1 md:columns-2`(卡片 `break-inside-avoid`,列间距/卡间距 16px),`InfoGroup variant="card"`
|
||
├ 卡内键值**一律单列**(`InfoGroup columns=1`,一行一条)—— 双列键值会挤压值区,路径类长值被截断
|
||
├ 瀑布流各卡自然高度、列内堆叠,**没有「同行等高」的配对压力** —— 双列网格的配对调整是反复返工的根源,已废弃
|
||
├ 表单类内容(下拉/输入)用「label 在上 + 控件全宽」纵排,不塞进键值行的值区
|
||
└ 阅读顺序为纵向(先左列后右列),分组按重要性排序;列底部不齐是模型特征,不是缺陷
|
||
```
|
||
|
||
**适用判断**:模块概览有「一个核心状态 + 2-4 个实时指标 + 一个主操作」就用横幅;三者不齐的模块直接用卡片网格,不要硬凑横幅。
|
||
|
||
**落地状态**(2026-09-21 统一):proxy 概览 / proxy 连接 / 下载任务 / 硬件监控概览四处横幅均按本节实现 ——
|
||
- 硬件监控:`PID / 传感器 / 重启 / 事件` 从 `text-xs` 内联「标签: 值」提升为指标位,进程名用 `ThingHK`,操作区 `xs` → `sm`,停止按钮改 `destructive`
|
||
- 下载:`下载中 / 等待 / 已暂停 / 已完成 / 已取消` 从 `Badge` 改为指标位,且恒显(0 比隐藏更真实,也避免位置漂移)
|
||
- 状态文字一律「状态 + 进程名」顺序(`运行中 mihomo` / `已连接 ThingHK` / `运行中 下载引擎`)
|
||
|
||
**已否决的两个方案**(避免回头再试):
|
||
- **平铺分组**(InfoGroup 无容器版):无容器所以视觉"散";键值两端对齐导致一行一条时"空"。
|
||
- **等宽两列卡片**:内容量不齐导致列内留白。D2 之所以成立,是横幅吸收了最"高"的内容(状态+指标),剩下的卡片内容量天然接近。
|
||
|
||
**设置行**结构(`SettingGroup` + `SettingRow`):
|
||
```
|
||
分组标题(h2 14/600)+ 可选说明
|
||
└ 设置行:左 label · 右控件,行高 44,行间 1px --divider
|
||
```
|
||
|
||
### 3.5 导航与 Tab
|
||
|
||
**Tab 只有一处:标题栏。** 模块内容区不再渲染 Tab 行。
|
||
|
||
| 层级 | 位置 | 内容 |
|
||
|---|---|---|
|
||
| 一级导航 | 侧边栏 60px | 模块图标(代理 / 音乐 / 剪贴板…) |
|
||
| 二级导航 | **标题栏内** | 当前模块的 Tab(概览 / 连接 / 节点 / 订阅 / 设置) |
|
||
|
||
**为什么**:Tab 行独占 48px 纵向空间,却只承载一个切换器。并入标题栏后,内容区最大化。
|
||
|
||
**实现**:
|
||
```ts
|
||
useModuleTabs('proxy', activeTab, tabs, { alwaysVisible: true })
|
||
```
|
||
- 模块**不渲染** `TabsList`,标题栏切换器常驻
|
||
- 传 `alwaysVisible` 后不再注册 `IntersectionObserver`
|
||
- 未传该选项的模块保持原行为(Tab 行滚出视口时才在标题栏补一个)—— 属过渡状态,应逐步迁移
|
||
|
||
**选中态**:`--primary` 实心胶囊 + 白字。标题栏空间小、Tab 数量少,实心比下划线更易扫读。
|
||
|
||
> 过渡期存在两套 Tab 外观:模块内的 `TabsList`(下划线)与标题栏切换器(实心胶囊)。**全部模块迁移完成后,只保留标题栏这一套**,那时 `TabsTrigger` 的下划线样式可以简化掉。
|
||
|
||
### 3.6 状态覆盖(P3 落地)
|
||
|
||
每种模式都必须实现四态,**抽成通用组件,不在模块内手写**:
|
||
|
||
| 状态 | 组件 | 表现 |
|
||
|---|---|---|
|
||
| 加载 | `<StateBlock variant="loading">` | Spinner 16px + "加载中…",高度撑满容器最小 120px |
|
||
| 空 | `<StateBlock variant="empty">` | 图标 32px(`--muted-foreground`) + 说明 caption + 可选主按钮 |
|
||
| 错误 | `<StateBlock variant="error">` | 图标 32px(`--danger`) + 错误信息 + "重试"次按钮 |
|
||
| 无权限 | `<StateBlock variant="denied">` | 图标 + 说明 + 引导操作 |
|
||
|
||
**验收**:随机打开任一模块的任一 Tab,断网/清空数据/制造错误,都必须看到对应的块,而不是空白。
|
||
|
||
### 3.7 页面构造示例 · proxy(样板,含其余四页规划)
|
||
|
||
概览页已定稿(横幅 + 瀑布流)。其余四页用**同一套模式**组装,空态一律按 M8(结构恒定 + `—` 占位):
|
||
|
||
**统一原则(四页共用)**
|
||
1. 每页从 3.4 的模式里选一种承载,不自创
|
||
2. 统计类信息统一用「指标行」形态(与横幅指标区同构:label 11 + 数值 18/600),容器口径同横幅 —— 用 `Card` 承载,不做裸指标行(M8)
|
||
3. 表格/列表统一用卡片承载,长表头 `sticky`
|
||
4. 行内操作集中在行尾,用 `icon-sm`(28px),禁微缩按钮
|
||
|
||
**连接** —— 指标行 + 表格卡(workbench 变体)
|
||
```
|
||
指标行:活跃连接 + 规则命中 chips(与概览横幅同容器 Card + `px-4 py-4`;chips 紧挨活跃连接,随命中项变化向右延伸)
|
||
下载/上传速率已删(与概览横幅重复);命中规则计数已删(恒等于活跃连接数)
|
||
表格卡(全宽):进程/域名 + 国内国外徽章 + 规则 + 上下行流量 + 断开
|
||
表头 sticky · 行 hover(bg-hover) · 空态「暂无活跃连接」
|
||
```
|
||
|
||
**节点** —— 组导航 + 节点网格
|
||
```
|
||
顶部:代理组切换(SegmentedNav,组多时降级为 Select)+ 测速按钮(行右)
|
||
节点网格:节点小卡(名称 + 延迟徽章 + 选中态 primary-soft),grid-cols-2 lg:grid-cols-3
|
||
未运行时网格空态「暂无节点」,结构不变
|
||
```
|
||
|
||
**订阅** —— 列表卡 + 导入区(plain 模式)
|
||
```
|
||
列表卡:行式(名称 / 更新时间 / 节点数 + 更新、删除),行高 52,行间 1px divider
|
||
导入区:URL 输入 + 导入按钮,置于列表卡上方
|
||
空态「暂无订阅」+ 主按钮引导导入
|
||
```
|
||
|
||
**设置** —— 单卡 + 卡内标题(峰定稿偏好:设置页标题留在卡片内)
|
||
```
|
||
单张设置卡承载全部设置项,卡片占满内容宽(放大居中与概览/连接一致)
|
||
输入类双列(md:grid-cols-2)、开关行「左说明右开关」、提示条 primary-soft 底;保存按钮在标题栏
|
||
(SettingGroup / SettingRow 保留,用于全局设置页等多分组场景)
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 组件规范
|
||
|
||
### 4.1 业务组件清单(统一从 `@/components/common` 导入)
|
||
|
||
以下 14 个组件已建立完成。**模块内不得再手写同类结构**——这是"UI 不统一"反复复发的根本原因:只要没有现成的替代品,开发者就只能手写。
|
||
|
||
| # | 组件 | 解决的问题 | 关键 props |
|
||
|---|---|---|---|
|
||
| 1 | `ModuleShell` | **页面框架落地件**:高度约束链 + 滚动归属 + 内容区 padding/限宽 | `layout`, `padded`, `maxWidth`, `tabs` |
|
||
| 2 | `ModuleToolbar` | 模块内容区顶部操作条 | `slot: left/right` |
|
||
| 3 | `SectionHeader` | 区块标题 + 说明 + 右侧操作 | `title`, `description`, `slot: action` |
|
||
| 4 | `StatCard` | 键值卡片(需要视觉分组的独立块) | `title`, `description`, `slot: action` |
|
||
| 4a | `InfoGroup` | **平铺分组**(概览页键值快照,密度优先) | `title`, `columns`, `slot: action` / `slot: footer` |
|
||
| 5 | `MetricValue` | 大数值 + 标签 + 单位 + 方向色 | `value`, `unit`, `label`, `trend` |
|
||
| 6 | `KeyValueRow` | 键值行(状态/版本/路径) | `label`, `value`, `slot: value` |
|
||
| 7 | `StatusDot` | 状态点 + 文字 | `state: success/warning/danger/neutral`, `label` |
|
||
| 8 | `StatusBadge` | 徽章 | `variant`, `size: sm/md` |
|
||
| 9 | `SettingRow` | 设置行 | `label`, `description`, `slot: control` |
|
||
| 10 | `SettingGroup` | 设置分组 | `title`, `description` |
|
||
| 11 | `DataListRow` | 列表行(主从分栏左栏) | `title`, `subtitle`, `active`, `slot: trailing` |
|
||
| 12 | `StateBlock` | 加载/空/错误/无权限 | `variant`, `title`, `description`, `slot: action` |
|
||
| 13 | `CopyField` | 可复制文本(路径、版本号) | `value`, `mono` |
|
||
| 14 | `ConfirmDialog` | 破坏性操作确认 | `title`, `description`, `variant: danger` |
|
||
|
||
### 4.2 关键组件规格
|
||
|
||
**按钮**(基于 shadcn `Button`,只调 token 与尺寸)
|
||
|
||
| 变体 | 底色 | 文字 | 描边 | 用途 |
|
||
|---|---|---|---|---|
|
||
| `default`(主) | `--primary` | `--primary-foreground` | 无 | 每屏仅 1 个 |
|
||
| `outline`(次) | `--card` | `--foreground` | `--border-strong` | 常规操作 |
|
||
| `ghost`(幽灵) | 无底 / hover `--primary-soft` | `--fg-secondary` / hover `--primary-soft-text` | 无 | 工具栏、行内 |
|
||
| `destructive`(危险) | `--danger` | 白 | 无 | 停止/删除,需确认 |
|
||
|
||
尺寸:`sm` 高 28 / `default` 高 34 / `lg` 高 40。内边距 `px-3.5`(default),圆角 `radius-md`。按下统一 `scale(0.97)`。
|
||
**禁止** `h-4 px-1.5 text-[10px]` 这类微缩按钮(`ProxyModule.vue:1768` 有此写法)——需要更小的按钮时用 `StatusBadge`。
|
||
|
||
**交互反馈(hover / active / focus)**
|
||
|
||
| 场景 | hover | active |
|
||
|---|---|---|
|
||
| 主按钮 | `--primary-hover`(变深) | `--primary-active` |
|
||
| 次按钮 / 卡片 | `--hover`(中性加深) | `--active` |
|
||
| 幽灵按钮 / 图标按钮 | `--primary-soft` + `--primary-soft-text` | `--primary-soft` |
|
||
| 列表行 / 菜单项 | `--hover` | — |
|
||
| 选中行 / 选中 Tab | — | `--primary-soft` |
|
||
|
||
**焦点环**全应用统一:`ring-2 ring-ring/40`。hover ≠ selected:hover 表达"可以点",selected 表达"已经选中",后者才用强调色。
|
||
|
||
**状态点**
|
||
|
||
```
|
||
8px 圆 · border-radius: full · 底色 = 对应 --<state>
|
||
与文字间距 6px · 文字用 body 或 label
|
||
```
|
||
可选呼吸动效(运行中):`opacity` 1↔0.55,1.8s 循环,`prefers-reduced-motion` 下禁用。
|
||
|
||
**徽章**
|
||
|
||
```
|
||
高 20 · 内边距 3px 8px · 圆角 radius-sm
|
||
底 --<state>-soft · 文字 --<state>-text · 字号 label(11/500)
|
||
```
|
||
|
||
**卡片**
|
||
|
||
```
|
||
底 --card · 1px --border · 圆角 radius-lg(12) · 内边距 space-4(16)
|
||
标题 h2(14/600) · 标题与内容间距 space-3(12)
|
||
统一轻阴影 shadow-sm(--shadow-card)—— shadcn Card / InfoGroup card / StatCard 一致
|
||
```
|
||
|
||
**输入 / 下拉 / 分段控件**
|
||
|
||
```
|
||
高度 34 · 圆角 radius-md(8) · 1px --border
|
||
输入与下拉态:底 --card
|
||
分段控件:外槽底 --subtle,内边距 3,选中项底 --card + 文字 --foreground,未选中项文字 --fg-secondary
|
||
聚焦:border 不变,box-shadow: 0 0 0 2px --ring(40% alpha)
|
||
```
|
||
|
||
### 4.3 组件用法要点
|
||
|
||
- 业务组件统一从 `@/components/common` 导入(见 4.1 清单),模块内禁止手写同类结构。
|
||
- 页面用 `ModuleShell` 包裹,通过 `layout`(grid / split / workbench / settings / plain)选内容区模式。
|
||
- 内容区先按四态组织:先 `loading`,再 `empty`/`error`,最后才是数据内容(见 3.6 / M8)。
|
||
- 颜色只用 token 类名(`bg-card border border-border`),禁止 hex 与原生色阶(见第 5 章 D1–D2)。
|
||
- 数值统一 `tabular-nums`;状态点 / 徽章用 `StatusDot` / `StatusBadge`,键值用 `KeyValueRow` / `InfoGroup`。
|
||
|
||
---
|
||
|
||
## 5. 开发约束(硬性)
|
||
|
||
### 5.1 禁止清单
|
||
|
||
| # | 禁止 | 替代方案 |
|
||
|---|---|---|
|
||
| D1 | 组件内出现 hex / rgb / oklch 字面量 | 用语义 token 类名 |
|
||
| D2 | Tailwind 原生色阶 `bg-blue-500` `text-emerald-700` `border-slate-200` | 用 `bg-primary` `text-success-text` `border-border` |
|
||
| D3 | 间距/圆角/尺寸的任意值 `p-[18px]` `rounded-[10px]` | 归位 4px 栅格:`p-4` `rounded-md`(非 4 倍数就近归档)。**字号例外**:正文标准档 `text-[11px]`~`text-[14px]` 是项目字号体系(Tailwind 无 13px 预设),允许保留;非标准档(15/13.5/16px 等)归位最近标准档 |
|
||
| D4 | 用状态色表达可点击性(绿色按钮 = 主操作) | 主操作一律 `--primary` |
|
||
| D5 | 卡片使用重阴影(shadow-popover / shadow-dialog 或 hover 抬起阴影) | 卡片统一轻阴影 `shadow-sm` |
|
||
| D6 | 模块内手写状态点 / 徽章 / 卡片标题 / 键值行 | 用第 4.1 节组件 |
|
||
| D7 | 混用图标库,或用 Unicode 符号(`↓ ✓ ● ▸`)当图标 | lucide,尺寸 14/16/20 |
|
||
| D8 | 中文加负字距 | `letter-spacing: 0` |
|
||
| D9 | 生产代码 `@import` 外部字体 CDN | 系统字体栈 |
|
||
| D10 | 数据驱动界面缺四态之一 | `StateBlock` |
|
||
| D11 | 一屏多个主按钮 / 破坏性操作与主操作同权重 | 视觉降级 + `ConfirmDialog` |
|
||
| D12 | 在 `.dark` 下靠改组件类名做适配 | 只改 token,组件代码不感知主题 |
|
||
|
||
### 5.2 必须清单
|
||
|
||
- **M1** 新增模块必须使用 `ModuleShell`,内容区三选一(第 3.2 节)
|
||
- **M2** 所有颜色/间距/圆角/字号可追溯到 `style.css` 的 token
|
||
- **M3** 深色模式与浅色模式同时验证,不得只测一套
|
||
- **M4** 数值使用 `tabular-nums`
|
||
- **M5** 动效尊重 `prefers-reduced-motion`
|
||
- **M6** 控件最小点击区 28×28(桌面标准,非触摸的 44)
|
||
- **M7** 文字与背景对比度 ≥ 4.5:1;`--muted-foreground` 不得用于正文,只用于 ≥12px 的辅助信息
|
||
- **M8** 数据驱动的界面**结构恒定**:无数据时保持完整结构、值用 `—` 占位(横幅、键值行、表格单元同规则);**不得**用「xx 未运行」之类文案整块替换结构。运行状态由横幅状态区或状态点表达,不在卡片内复述。控件在不可用时禁用,而不是隐藏整块。
|
||
`—` 与 `0` 的区分:`—` = 数据缺失/不适用(如未运行时的 PID);`0` = 语义上就是零(如未运行时的连接数、速率)—— 可计数指标显示 0 比 `—` 更真实。指标行/横幅必须有容器(卡片),裸指标行浮在画布上与页面不协调。
|
||
- **M9** 动态数值的指标位**按数据类型固定最小宽度**:速率/流量/计数高频刷新,宽度变化会左右推移兄弟元素。做法同 OSD(`tabular-nums` + `min-width`)。**宽度按内容定,不要一刀切**——速率 `min-w-24`(容纳 `999 KB/s`)、版本/累计 `min-w-20`、纯计数 `min-w-12`;统一给大值会留白并把同卡操作按钮挤换行
|
||
- **M8** 键盘可聚焦元素必须有可见焦点环(`ring-2 ring-ring/40`)
|
||
|
||
### 5.3 Code Review 检查点
|
||
|
||
提交前自查(建议做成 MR 模板 checklist):
|
||
|
||
1. `grep -rE "(#[0-9a-fA-F]{3,8}|(bg|text|border)-(slate|gray|zinc|blue|emerald|amber|red|rose|orange|sky|purple|violet)-[0-9]{2,3})" src/` **无新增命中**
|
||
2. `grep -rE "\w-\[[^\]]+\]" src/` 新增行数可控且能说明理由
|
||
3. 深浅两套主题截图留存
|
||
4. 加载/空/错误三态至少手动触发过一次
|
||
5. 破坏性操作有确认
|
||
|
||
**建议自动化**:加入 ESLint 规则,直接拦截 D1–D3。
|
||
|
||
```js
|
||
// eslint 思路示例:禁止特定 class 模式
|
||
{
|
||
files: ['**/*.vue'],
|
||
rules: {
|
||
'no-restricted-syntax': [
|
||
'error',
|
||
{
|
||
selector: 'Literal[value=/(bg|text|border)-(slate|gray|zinc|blue|emerald|amber|red|rose|orange|sky|purple|violet)-\\d{3}/]',
|
||
message: '禁止使用 Tailwind 原生色阶,请使用语义 token(见 UI_DESIGN_SYSTEM.md 第 5.1 节 D2)'
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 工具与状态
|
||
|
||
### 6.1 组件预览页(改 token 后的回归工具)
|
||
|
||
路径:`preview/` —— **不在生产构建入口内**,只在 `bun dev` 时可访问。
|
||
|
||
```bash
|
||
bun dev
|
||
# 浏览器打开 http://localhost:14210/preview/index.html
|
||
```
|
||
|
||
用途:改完 `src/style.css` 的 token 后,把全部基础组件与业务组件过一遍,确认浅色 / 深色两套都正确 —— 替代"改完 token 靠记忆推断影响面"。本页不依赖任何 Tauri API,纯浏览器即可运行。
|
||
|
||
查询参数:
|
||
|
||
| 参数 | 作用 | 示例 |
|
||
|---|---|---|
|
||
| `theme=dark` | 以深色模式启动 | `?theme=dark` |
|
||
| `only=<id>` | 只渲染指定分区,便于放大核对 | `?only=data` |
|
||
|
||
已内置 `id` 的分区:`forms`、`data`、`shell`。需要放大看其他分区时,按同样方式给 `<section>` 加 `id` 即可。
|
||
|
||
> **实现注意**:`only` 的隔离 CSS 只能用**显式标记**(`[data-block]` / `[data-page-header]`),**不能用 `section` / `header` 这类标签选择器**——`StatCard` 内部也用 `<section>` 和 `<header>`,会被一起隐藏,产生"组件没渲染"的假象。
|
||
|
||
### 6.2 当前状态
|
||
|
||
- 业务组件层(第 4.1 节 14 个组件)与 Token 层已落地,`preview/` 预览页已完成浅色/深色视觉回归。
|
||
- 色阶治理:Tailwind 原生色阶从约 155 处降至 3 处(proxy / terminal / monitor / translate / music 的色阶与 Tabs 已完成)。
|
||
- proxy 概览页定稿为「状态横幅 + 两列瀑布流」,InfoGroup 新增 `variant="card"`。
|
||
- 状态横幅统一(2026-09-21):proxy 概览 / proxy 连接 / 下载任务 / 硬件监控概览四处横幅统一为 §3.4 的三段式(状态区 → 指标区 → 操作区)+ `Card` 容器;监控与下载的旧样式(`text-xs` 内联键值、Badge 计数、`xs` 按钮)已废弃。
|
||
- 主题色自定义落地(2026-09-21,见 §2.9):`src/lib/theme.ts` + `appStore.setAccent()` + 设置页「主题色」卡片(10 预设 + 自定义),深浅两套自动派生、对比度自动兜底;组件层 0 改动。
|
||
- 终端模块非终端页对齐(2026-09-21,见 TERMINAL_MODULE_PLAN.md §5):根 padding 归类、列表 Card 化、表单走 `@/components/ui` 原语、四态/徽章走 `@/components/common`、设置页用 `SettingGroup`/`SettingRow`(此后二者与 `StateBlock`/`StatusBadge` 有真实消费者);终端画布、状态栏、`h-11` 契约、xterm 链保持不动。
|
||
- 剩余项:
|
||
- `OsdWindow` 免改(功能性配置,非 UI token 问题)
|
||
- `DownloadWindow` / `ClipboardPopup` 仍有少量 hex,待统一
|
||
- ESLint 约束规则(第 5.3 节)尚未搭建
|
||
|
||
### 6.3 待决策
|
||
|
||
| # | 问题 | 状态 | 结论 / 影响 |
|
||
|---|---|---|---|
|
||
| 1 | 数据方向色是否与状态色解耦 | 已定(暂保留现有取值) | 保留"下载=绿、上传=红",已独立成 `--data-*` token,要换色只改这 2 个值 |
|
||
| 2 | 强调色用 `#0066CC` | ✅ 已采用 | 落地;同时解决 shadcn primary 近黑导致按钮发灰 |
|
||
| 3 | 浮窗(OSD/截图/下载)是否纳入统一体系 | 部分已定 | `OsdWindow` 免改(功能性配置);`DownloadWindow`/`ClipboardPopup` 少量 hex 待统一;不强行并入主界面 token |
|
||
| 4 | `--font-mono` 是否用网络字体 | ✅ 已定 | 系统 `Cascadia Code` / `Consolas`,不引外部字体 |
|
||
| 5 | 侧边栏宽度 60px | ✅ 已定 | 沿用现有实现 |
|
||
| 6 | 内容区限宽 | ✅ 已定 | 改为**不限宽占满**窗口宽度(原 `max-w-6xl` 限制已移除) |
|
||
| 7 | 卡片阴影 | ✅ 已定 | 卡片统一轻阴影 `shadow-sm`(shadcn Card / InfoGroup card / StatCard 一致),浮层仍用 `shadow-popover`/`shadow-dialog` |
|
||
|
||
---
|
||
|
||
**维护约定**:Token 值以 `src/style.css` 为唯一事实来源,本文档与之同步;两者冲突时以本文档为准并修正代码。新增 token 或组件须同步更新第 2、4 章。
|