Primitive + Semantic 双层令牌架构,HEX->HSL,明暗双份,@theme inline 暴露为 Tailwind 类。 - 新建 src/app/styles/tokens/ 6 个令牌文件(primitive/semantic-light/semantic-dark/lesson-preparation/tailwind-theme/index) - globals.css 改为 @import 引入,477->258 行 - 清理 91 处 #hex 硬编码颜色 -> hsl(var(--*)) - 清理 10 处硬编码字体 -> var(--font-family-*) - 清理 100 文件 Tailwind 任意值(Tier 1 映射/Tier 3 注释豁免) - 清理 M3 Surface 死代码,升级 --lp-* 令牌(HEX->HSL + 暗色补全) - 新建 ESLint 自定义规则 no-hardcoded-design-tokens(单词边界正则) - eslint.config.mjs 新增 no-restricted-syntax 禁止 #hex + 自定义规则加载(pathToFileURL) - 项目规则新增设计令牌规范强制章节 - 架构图 004/005 同步设计令牌体系节点 - known-issues.md 追加设计令牌问题分类(7 个规则表) 验证: tsc --noEmit 0 errors, npm run lint 0 errors/12 warnings(均为既有问题)
548 lines
25 KiB
Markdown
548 lines
25 KiB
Markdown
# 设计令牌专项重构 · 设计文档
|
|
|
|
> 创建日期:2026-07-04
|
|
> 状态:Draft(待用户审查)
|
|
> 执行方式:大爆炸式(单 PR)
|
|
> 关联规则:`docs/architecture/004_architecture_impact_map.md`、`.trae/rules/project_rules.md`
|
|
|
|
---
|
|
|
|
## 1. 背景与目标
|
|
|
|
### 1.1 现状摘要
|
|
|
|
项目当前令牌散落在 [`src/app/globals.css`](file:///e:/Desktop/CICD/src/app/globals.css)(477 行)与 [`tailwind.config.ts`](file:///e:/Desktop/CICD/tailwind.config.ts)(极简,无 `theme.extend`)。已有 5 类令牌:
|
|
|
|
1. **shadcn 标准令牌**(HSL 格式,完整暗色)
|
|
2. **chart-1~5 数据可视化令牌**(HSL,完整暗色)
|
|
3. **sidebar 令牌**(HSL,完整暗色)
|
|
4. **`--lp-*` 备课编辑器令牌**(HEX 格式,**无暗色**,**未在 `@theme inline` 中暴露**,无法 `bg-lp-paper`)
|
|
5. **Material Design 3 Surface 映射令牌**(在 `@theme inline` 中,**经扫描零使用**,属死代码)
|
|
|
|
### 1.2 关键技术债
|
|
|
|
扫描结果:
|
|
|
|
| 问题类型 | 出现次数 | 文件数 |
|
|
|---|---|---|
|
|
| 硬编码颜色 `#xxx` | 91 处 | 15 文件 |
|
|
| 硬编码字体 `'Inter'/'Fraunces'/'JetBrains Mono'` | 8 处 | 2 文件 |
|
|
| 硬编码 `font-size: Npx` | 15 处 | 3 文件 |
|
|
| Tailwind 任意值 `w-[Npx]/h-[Npx]/p-[Npx]/...` | 98 处 | 40 文件 |
|
|
| 缺失:font/spacing/shadow/text-size 尺度令牌 | — | — |
|
|
| `--lp-*` 无暗色 / 未暴露为 Tailwind 类 | — | — |
|
|
| M3 Surface 令牌零使用(死代码) | — | — |
|
|
|
|
项目规则(`.trae/rules/project_rules.md`)明确禁止"任意值"和"动态拼接类名",但实际任意值已散落到 40 个文件,需要专项治理。
|
|
|
|
### 1.3 重构目标
|
|
|
|
- **核心目标**:全量体系化重建设计令牌,建立完整令牌契约,清理全部硬编码与任意值
|
|
- **命名体系**:双层架构(Primitive + Semantic)+ shadcn 语义扩展
|
|
- **覆盖范围**:font/size/leading/weight/radius/shadow/spacing/duration/ease/z-index 全阶梯
|
|
- **暗色覆盖**:明暗双色全面覆盖(所有令牌都有 `:root` 与 `.dark` 双份)
|
|
- **强制约束**:ESLint 规则 + 注释豁免,防止技术债复发
|
|
|
|
---
|
|
|
|
## 2. 令牌架构
|
|
|
|
### 2.1 双层架构
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────────┐
|
|
│ Layer 1: Primitive (Raw Tokens) — 原始色板/字号/间距/阴影 │
|
|
│ ───────────────────────────────────────────────────────────── │
|
|
│ 命名: --color-zinc-50 ~ 950 │
|
|
│ --color-stone-50 ~ 950 │
|
|
│ --color-indigo-500/600 │
|
|
│ --font-size-1 ~ --font-size-9 │
|
|
│ --space-0 ~ --space-32 │
|
|
│ --shadow-1 ~ --shadow-6 │
|
|
│ --font-family-sans/serif/mono │
|
|
│ 特征: 不直接使用,只被 Semantic 层引用 │
|
|
│ 主题: 无主题差异(色板层不区分明暗) │
|
|
└──────────────────────────────────────────────────────────────────┘
|
|
▲ 引用
|
|
│
|
|
┌──────────────────────────────────────────────────────────────────┐
|
|
│ Layer 2: Semantic (Component/Context Tokens) — 语义层 │
|
|
│ ───────────────────────────────────────────────────────────── │
|
|
│ shadcn 兼容: --background / --foreground / --primary / ... │
|
|
│ 扩展层级: --background-elevated / --text-secondary / ... │
|
|
│ 模块命名空间: --lp-* (lesson-preparation) │
|
|
│ chart/sidebar: --chart-1~5 / --sidebar-* │
|
|
│ 特征: 业务代码唯一引用入口,Tailwind 通过 @theme inline 暴露 │
|
|
│ 主题: :root 与 .dark 双份定义,所有令牌都有暗色 │
|
|
└──────────────────────────────────────────────────────────────────┘
|
|
▲ 引用
|
|
│
|
|
┌──────────────────────────────────────────────────────────────────┐
|
|
│ Layer 3: Tailwind 类 (via @theme inline) │
|
|
│ ───────────────────────────────────────────────────────────── │
|
|
│ bg-background / text-foreground / bg-primary / ... │
|
|
│ bg-lp-paper / text-lp-inline-node-meta / ... │
|
|
│ font-sans / font-serif / font-mono │
|
|
│ text-size-2 / leading-snug / weight-semibold │
|
|
│ radius-md / shadow-2 / space-4 / duration-fast / z-modal │
|
|
└──────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 2.2 核心原则
|
|
|
|
- 业务代码 **只引用 Semantic 层**(直接 `var(--background)` 或 Tailwind 类 `bg-background`),禁止引用 Primitive 层
|
|
- Primitive 层只为 Semantic 层提供"原材料",便于未来多主题切换(只改 Semantic 层映射,不动 Primitive)
|
|
- shadcn 命名严格保留(`--background`/`--foreground`/`--primary` 等),`shared/components/ui/*` 零改动
|
|
- `--lp-*` 保留命名空间(已有 9 文件使用,改名风险大),但补全暗色与 Tailwind 暴露
|
|
|
|
### 2.3 文件分布
|
|
|
|
当前 [`globals.css`](file:///e:/Desktop/CICD/src/app/globals.css) 477 行,补全令牌后估算 1200+ 行,超过项目规则硬性上限 1000 行。拆分为令牌专题目录:
|
|
|
|
```
|
|
src/app/
|
|
├─ globals.css # 仅保留 @import、base layer、组件硬编码样式清理后的样式
|
|
└─ styles/
|
|
└─ tokens/
|
|
├─ primitive.css # Layer 1: 原始色板/字号/间距/阴影阶梯
|
|
├─ semantic-light.css # Layer 2: :root 语义令牌(明色)
|
|
├─ semantic-dark.css # Layer 2: .dark 语义令牌(暗色)
|
|
├─ lesson-preparation.css # --lp-* 令牌(明暗双份)
|
|
├─ tailwind-theme.css # @theme inline 暴露规则
|
|
└─ index.css # 汇总 @import 入口
|
|
```
|
|
|
|
`globals.css` 顶部改为:
|
|
|
|
```css
|
|
@import "tailwindcss";
|
|
@import "./styles/tokens/index.css";
|
|
@plugin "tailwindcss-animate";
|
|
@plugin "@tailwindcss/typography";
|
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
```
|
|
|
|
### 2.4 Tailwind v4 集成
|
|
|
|
[`tailwind.config.ts`](file:///e:/Desktop/CICD/tailwind.config.ts) 保持极简(只 content + plugins),所有令牌通过 `@theme inline` 在 `tailwind-theme.css` 中暴露。
|
|
|
|
---
|
|
|
|
## 3. Primitive 层(原始阶梯)
|
|
|
|
### 3.1 色板(以 Zinc 为基准中性色,与现有 shadcn 一致)
|
|
|
|
```css
|
|
/* primitive.css */
|
|
:root {
|
|
/* Zinc 中性色板(shadcn 默认) */
|
|
--color-zinc-50: 0 0% 99%;
|
|
--color-zinc-100: 240 4.8% 95.9%;
|
|
--color-zinc-200: 240 5.9% 90%;
|
|
--color-zinc-300: 240 4.8% 83.9%;
|
|
--color-zinc-400: 240 5% 64.9%;
|
|
--color-zinc-500: 240 3.8% 46.1%;
|
|
--color-zinc-600: 240 5.2% 33.9%;
|
|
--color-zinc-700: 240 5.3% 26.1%;
|
|
--color-zinc-800: 240 5.9% 10%;
|
|
--color-zinc-900: 240 5.9% 3.9%;
|
|
--color-zinc-950: 240 10% 3.9%;
|
|
|
|
/* Stone 暖灰(用于 lp-* 纸感,与现有 --lp-paper-edge #f8f8f7 对齐) */
|
|
--color-stone-50: 60 4.8% 95.9%;
|
|
--color-stone-100: 60 5.1% 90%;
|
|
--color-stone-200: 20 5.9% 90%;
|
|
--color-stone-300: 24 5.7% 82.9%;
|
|
--color-stone-400: 24 5.4% 63.9%;
|
|
--color-stone-500: 25 5.1% 44.7%;
|
|
--color-stone-600: 33 5% 39.8%;
|
|
--color-stone-700: 30 5.2% 32.7%;
|
|
--color-stone-800: 12 6.5% 31.4%;
|
|
--color-stone-900: 24 10% 10%;
|
|
--color-stone-950: 20 14.3% 4.1%;
|
|
|
|
/* Indigo 强调色(lp-interaction 用) */
|
|
--color-indigo-500: 238.7 83.5% 66.7%;
|
|
--color-indigo-600: 238.6 84.5% 59.8%;
|
|
}
|
|
```
|
|
|
|
**说明**: 仅记录 Primitive 原料,业务代码不直接引用。
|
|
|
|
### 3.2 字号阶梯(替代硬编码 13.5px/14px/12.5px 等)
|
|
|
|
| Token | 值 | 用途 |
|
|
|---|---|---|
|
|
| `--font-size-1` | 12px | 元信息/角色标签 |
|
|
| `--font-size-2` | 13px | inline-node body |
|
|
| `--font-size-3` | 13.5px | inline-node 主文(备课纸感) |
|
|
| `--font-size-4` | 14px | 标题/按钮 |
|
|
| `--font-size-5` | 16px | 正文(Fraunces 16px) |
|
|
| `--font-size-6` | 18px | H2 |
|
|
| `--font-size-7` | 20px | H1 |
|
|
| `--font-size-8` | 24px | 区块标题 |
|
|
| `--font-size-9` | 32px | 页面标题 |
|
|
|
|
### 3.3 间距阶梯(覆盖 98 处任意值)
|
|
|
|
| Token | 值 | 替代示例 |
|
|
|---|---|---|
|
|
| `--space-0` | 0 | |
|
|
| `--space-0_5` | 0.125rem (2px) | `p-[2px]` |
|
|
| `--space-1` | 0.25rem (4px) | `p-[4px]` |
|
|
| `--space-1_5` | 0.375rem (6px) | `gap-[6px]` |
|
|
| `--space-2` | 0.5rem (8px) | |
|
|
| `--space-2_5` | 0.625rem (10px) | `p-[10px]` |
|
|
| `--space-3` | 0.75rem (12px) | |
|
|
| `--space-3_5` | 0.875rem (14px) | `p-[14px]` |
|
|
| `--space-4` | 1rem (16px) | |
|
|
| `--space-5` | 1.25rem (20px) | `m-[20px]` |
|
|
| `--space-6` | 1.5rem (24px) | |
|
|
| `--space-7` | 1.75rem (28px) | `w-[28px]` (lp-tb-btn) |
|
|
| `--space-8` | 2rem (32px) | |
|
|
| `--space-10` | 2.5rem (40px) | |
|
|
| `--space-12` | 3rem (48px) | |
|
|
| `--space-16` | 4rem (64px) | |
|
|
| `--space-18` | 4.5rem (72px) | `px-[72px]` (lp-paper-toolbar) |
|
|
|
|
### 3.4 阴影阶梯(替代硬编码 box-shadow)
|
|
|
|
| Token | 值 | 替代 |
|
|
|---|---|---|
|
|
| `--shadow-1` | `0 1px 2px rgba(15,15,15,0.04)` | shadow-xs |
|
|
| `--shadow-2` | `0 1px 3px rgba(15,15,15,0.06), 0 1px 2px rgba(15,15,15,0.04)` | shadow-sm |
|
|
| `--shadow-3` | `0 4px 6px rgba(15,15,15,0.05), 0 2px 4px rgba(15,15,15,0.04)` | shadow-md |
|
|
| `--shadow-4` | `0 1px 2px rgba(15,15,15,0.04), 0 8px 24px rgba(15,15,15,0.04)` | lp-paper-shadow |
|
|
| `--shadow-5` | `0 1px 2px rgba(15,15,15,0.06), 0 12px 36px rgba(15,15,15,0.08)` | lp-paper-shadow-active |
|
|
| `--shadow-6` | `0 10px 15px rgba(15,15,15,0.1), 0 4px 6px rgba(15,15,15,0.05)` | shadow-xl |
|
|
|
|
### 3.5 字体家族(替代 8 处硬编码)
|
|
|
|
| Token | 值 | 用途 |
|
|
|---|---|---|
|
|
| `--font-family-sans` | `'Inter', system-ui, sans-serif` | UI 元素 |
|
|
| `--font-family-serif` | `'Fraunces', Georgia, serif` | 主文/备课纸感 |
|
|
| `--font-family-mono` | `'JetBrains Mono', monospace` | 角色标签 |
|
|
|
|
---
|
|
|
|
## 4. Semantic 层(shadcn 扩展 + lp-* 升级 + M3 清理)
|
|
|
|
### 4.1 shadcn 标准令牌(零改动,确保 ui/ 兼容)
|
|
|
|
`--background`/`--foreground`/`--card`/`--popover`/`--primary`/`--secondary`/`--muted`/`--accent`/`--destructive`/`--border`/`--input`/`--ring`/`--radius` 全部保留现有 HSL 值,`:root` 与 `.dark` 双份定义不变。
|
|
|
|
### 4.2 语义层扩展(新增层级)
|
|
|
|
| Token | 明色 HSL | 暗色 HSL | 用途 |
|
|
|---|---|---|---|
|
|
| `--background-elevated` | `0 0% 100%` | `240 6% 10%` | 浮层/card-on-card |
|
|
| `--background-sunken` | `240 4.8% 95.9%` | `240 6% 8%` | 凹陷区/inset |
|
|
| `--text-primary` | `= --foreground` | `= --foreground` | 主文 |
|
|
| `--text-secondary` | `240 3.8% 46.1%` | `240 5% 64.9%` | 副文(= muted-foreground) |
|
|
| `--text-tertiary` | `240 4% 65%` | `240 5% 50%` | 占位/元信息 |
|
|
| `--border-strong` | `240 5.9% 70%` | `240 5% 40%` | 强调边框 |
|
|
| `--border-subtle` | `240 5.9% 95%` | `240 5% 18%` | 弱边框 |
|
|
|
|
**说明**: `--text-secondary` 等是 `--muted-foreground` 的语义别名,业务代码优先用语义名。
|
|
|
|
### 4.3 chart-1~5 / sidebar 令牌(零改动)
|
|
|
|
保留现有 HSL 值,`:root` 与 `.dark` 双份定义不变。
|
|
|
|
### 4.4 --lp-* 令牌升级(HEX→HSL + 暗色 + Tailwind 暴露)
|
|
|
|
**明色(`:root`)**:
|
|
|
|
| Token | 旧 HEX | 新 HSL | 说明 |
|
|
|---|---|---|---|
|
|
| `--lp-paper` | `#fefefe` | `0 0% 99.6%` | 纸面 |
|
|
| `--lp-paper-edge` | `#f8f8f7` | `60 9% 97%` | 纸边(对齐 stone-100) |
|
|
| `--lp-paper-shadow` | (box-shadow) | `= --shadow-4` 引用 | 阴影令牌化 |
|
|
| `--lp-paper-shadow-active` | (box-shadow) | `= --shadow-5` 引用 | 阴影令牌化 |
|
|
| `--lp-anchor-range` | `rgba(28,25,23,0.08)` | `20 14.3% 4.1% / 0.08` | 引用 stone-950 |
|
|
| `--lp-anchor-range-active` | `rgba(28,25,23,0.16)` | `20 14.3% 4.1% / 0.16` | |
|
|
| `--lp-anchor-point` | `#1c1917` | `20 14.3% 4.1%` | = stone-950 |
|
|
| `--lp-inline-node-border` | `#d6d3d1` | `30 5.7% 82.9%` | = stone-300 |
|
|
| `--lp-inline-node-text` | `#44403c` | `24 5.4% 26.9%` | ≈ stone-700 微调 |
|
|
| `--lp-inline-node-meta` | `#a8a29e` | `30 5% 64.9%` | = stone-400 |
|
|
| `--lp-interaction` | `#6366f1` | `238.6 84.5% 59.8%` | = indigo-600 |
|
|
|
|
**12 个 --lp-dot-* 节点色点**: 全部映射到 zinc/stone 色板对应值,统一为 HSL。
|
|
|
|
**暗色(`.dark`)**: 全部补齐。例如:
|
|
|
|
- `--lp-paper: 240 6% 10%`(暗背景纸面)
|
|
- `--lp-paper-edge: 240 6% 8%`
|
|
- `--lp-anchor-point: 0 0% 98%`(反相)
|
|
- `--lp-interaction: 238.7 83.5% 66.7%`(= indigo-500,暗色微亮)
|
|
|
|
### 4.5 M3 Surface 映射令牌清理
|
|
|
|
[`globals.css:185-198`](file:///e:/Desktop/CICD/src/app/globals.css#L185-L198) 中的 M3 令牌零使用(已扫描确认),全部删除:
|
|
|
|
- `--color-surface` / `--color-on-surface` / `--color-on-surface-variant`
|
|
- `--color-surface-container-lowest/low/high/highest`
|
|
- `--color-outline-variant` / `--color-outline`
|
|
- `--color-error` / `--color-tertiary` / `--color-tertiary-container`
|
|
|
|
### 4.6 globals.css 硬编码样式令牌化
|
|
|
|
| 硬编码 | 令牌化后 |
|
|
|---|---|
|
|
| `.lp-tb-btn { width: 28px; ... font-family: 'Inter'; font-size: 13px; color: var(--foreground) }` | `width: var(--space-7); font-family: var(--font-family-sans); font-size: var(--font-size-2); color: hsl(var(--foreground))` |
|
|
| `.lp-inline-node { font-family: 'Inter'; font-size: 13.5px; ... border-left: 2px solid var(--lp-inline-node-border); color: var(--lp-inline-node-text) }` | 全部用令牌引用 |
|
|
| `.lp-qa-role.teacher { color: #1c1917 }` | `color: hsl(var(--lp-anchor-point))` |
|
|
| `.lp-qa-role.student { color: #6b7280 }` | `color: hsl(var(--color-zinc-500))` |
|
|
| `.lp-inline-node-title { color: #1a1a1a }` | `color: hsl(var(--lp-anchor-point))` |
|
|
| `.lp-inline-node-body { color: #404040 }` | `color: hsl(var(--lp-inline-node-text))` |
|
|
| `.lp-qa-content { color: #404040 }` | 同上 |
|
|
| `.lp-qa-prompt { color: #525252 }` | `color: hsl(var(--color-zinc-600))` |
|
|
| `.range-anchor { background-color: var(--node-color, #1976d2) }` | fallback 改为 `hsl(var(--lp-interaction))` |
|
|
|
|
---
|
|
|
|
## 5. 尺度令牌补齐清单
|
|
|
|
### 5.1 圆角阶梯
|
|
|
|
| Token | 值 | Tailwind 类 |
|
|
|---|---|---|
|
|
| `--radius-sm` | `calc(var(--radius) - 4px)` | `rounded-sm` |
|
|
| `--radius-md` | `calc(var(--radius) - 2px)` | `rounded-md` |
|
|
| `--radius-lg` | `var(--radius)` = `0.5rem` | `rounded-lg` |
|
|
| `--radius-xl` | `calc(var(--radius) + 4px)` | `rounded-xl` |
|
|
| `--radius-2xl` | `calc(var(--radius) + 8px)` | `rounded-2xl` |
|
|
| `--radius-full` | `9999px` | `rounded-full` |
|
|
|
|
### 5.2 行高与字重
|
|
|
|
| Token | 值 |
|
|
|---|---|
|
|
| `--leading-tight` | 1.2 |
|
|
| `--leading-snug` | 1.35 |
|
|
| `--leading-normal` | 1.5 |
|
|
| `--leading-relaxed` | 1.65 |
|
|
| `--leading-loose` | 1.8 |
|
|
| `--weight-regular` | 400 |
|
|
| `--weight-medium` | 500 |
|
|
| `--weight-semibold` | 600 |
|
|
| `--weight-bold` | 700 |
|
|
|
|
### 5.3 动效与 z-index
|
|
|
|
| Token | 值 | Tailwind 类 |
|
|
|---|---|---|
|
|
| `--duration-fast` | 150ms | `duration-fast` |
|
|
| `--duration-normal` | 200ms | `duration-normal` |
|
|
| `--duration-slow` | 300ms | `duration-slow` |
|
|
| `--ease-in` | `cubic-bezier(0.4, 0, 1, 1)` | `ease-in` |
|
|
| `--ease-out` | `cubic-bezier(0, 0, 0.2, 1)` | `ease-out` |
|
|
| `--ease-in-out` | `cubic-bezier(0.4, 0, 0.2, 1)` | `ease-in-out` |
|
|
| `--z-dropdown` | 1000 | `z-dropdown` |
|
|
| `--z-sticky` | 1100 | `z-sticky` |
|
|
| `--z-modal` | 1300 | `z-modal` |
|
|
| `--z-popover` | 1400 | `z-popover` |
|
|
| `--z-toast` | 1500 | `z-toast` |
|
|
|
|
---
|
|
|
|
## 6. 硬编码清理映射表(分级)
|
|
|
|
### 6.1 91 处 #hex 颜色 — 按文件分类
|
|
|
|
| 文件 | 处数 | 清理策略 |
|
|
|---|---|---|
|
|
| `app/globals.css` | 32 | 全部令牌化(见 4.6 节) |
|
|
| `app/manifest.ts` | 2 | 保留(theme_color 元数据,需字面量),加注释 `// arbitrary-value: PWA manifest requires literal hex` |
|
|
| `app/layout.tsx` | 2 | 检查后令牌化或注释 |
|
|
| `shared/components/ui/chart.tsx` | 5 | 令牌化(用 `--chart-1~5`) |
|
|
| `modules/notifications/channels/email-channel.ts` | 7 | 保留(邮件 HTML 内联样式需字面量),加注释 |
|
|
| `modules/textbooks/components/knowledge-graph.tsx` | 10 | 令牌化(用 `--chart-*` 或新增 `--graph-node-*`) |
|
|
| `modules/textbooks/components/force-graph.tsx` | 17 | 同上 |
|
|
| `modules/lesson-preparation/components/paper-editor/*` | ~6 | 令牌化(用 `--lp-*`) |
|
|
| `modules/attendance/components/attendance-grade-correlation-card.tsx` | 3 | 令牌化(用 `--chart-*`) |
|
|
| `modules/classes/components/my-classes-grid.tsx` | 2 | 令牌化 |
|
|
| `modules/lesson-preparation/components/version-diff-view.tsx` | 1 | 令牌化(用 `--lp-*` 或新增 `--diff-add`/`--diff-remove`) |
|
|
|
|
**新增语义令牌**:
|
|
|
|
- `--diff-add` / `--diff-add-bg`(版本对比新增)
|
|
- `--diff-remove` / `--diff-remove-bg`(版本对比删除)
|
|
- `--graph-node-1` ~ `--graph-node-6`(知识图谱节点色)
|
|
|
|
### 6.2 8 处硬编码 font-family — 全部令牌化
|
|
|
|
| 文件 | 处数 | 令牌化 |
|
|
|---|---|---|
|
|
| `app/globals.css` | 7 | `var(--font-family-sans)` / `var(--font-family-serif)` / `var(--font-family-mono)` |
|
|
| `modules/lesson-preparation/components/paper-editor/textbook-tiptap-editor.tsx` | 1 | 同上 |
|
|
|
|
### 6.3 15 处硬编码 font-size — 全部令牌化
|
|
|
|
| 文件 | 处数 | 令牌化 |
|
|
|---|---|---|
|
|
| `app/globals.css` | 13 | `var(--font-size-N)` |
|
|
| `modules/notifications/channels/email-channel.ts` | 1 | 保留(邮件 HTML),加注释 |
|
|
| `modules/lesson-preparation/components/paper-editor/textbook-tiptap-editor.tsx` | 1 | `var(--font-size-N)` |
|
|
|
|
### 6.4 98 处 Tailwind 任意值 — 分级清理
|
|
|
|
**Tier 1 — 直接映射 Tailwind 默认阶梯**(约 40 处):
|
|
|
|
- `w-[28px]` → `w-7`(28px ≈ 1.75rem,但 w-7 = 1.75rem ✓)
|
|
- `gap-[6px]` → `gap-1.5`(6px = 0.375rem,gap-1.5 ✓)
|
|
- `p-[10px]` → 需新增 `--space-2_5`(10px = 0.625rem)
|
|
- `text-[13px]` → `text-size-2`(13px = `--font-size-2`)
|
|
|
|
**Tier 2 — 新增间距令牌后引用**(约 30 处):
|
|
|
|
- `w-[72px]` → `w-space-18`(`--space-18` = 4.5rem)
|
|
- `h-[16px]` → `h-space-4`(`--space-4` = 1rem)
|
|
- `p-[20px]` → `p-space-5`(`--space-5` = 1.25rem)
|
|
|
|
**Tier 3 — 注释豁免**(约 28 处):
|
|
|
|
- 图表 SVG 尺寸(如 `w-[400px]` 在 chart 容器)→ 加注释 `// arbitrary-value: chart canvas fixed size`
|
|
- 邮件 HTML 内联样式
|
|
- loading.tsx 占位骨架特殊尺寸
|
|
|
|
**ESLint 规则**: `tailwindcss/no-arbitrary-value: error`,允许通过 `// eslint-disable-next-line` + 注释理由豁免。
|
|
|
|
---
|
|
|
|
## 7. 强制约束机制
|
|
|
|
### 7.1 ESLint 规则新增
|
|
|
|
**新增依赖**: `eslint-plugin-tailwindcss`(需验证与 Tailwind v4 兼容性,若不兼容则自定义规则)
|
|
|
|
**`eslint.config.mjs` 新增规则**:
|
|
|
|
```javascript
|
|
{
|
|
// 禁止 Tailwind 任意值(允许 eslint-disable + 注释豁免)
|
|
'tailwindcss/no-arbitrary-value': 'error',
|
|
// 禁止 TSX/CSS 中硬编码颜色字面量(白名单:globals.css primitive 定义、email-channel.ts、manifest.ts)
|
|
'no-restricted-syntax': [
|
|
'error',
|
|
{
|
|
selector: "Literal[value=/#[0-9a-fA-F]{3,8}/]",
|
|
message: '禁止硬编码 hex 颜色,使用设计令牌 var(--*) 或 Tailwind 类 bg-*',
|
|
},
|
|
],
|
|
}
|
|
```
|
|
|
|
**自定义规则文件**: `eslint-rules/no-hardcoded-design-tokens.js`
|
|
|
|
- 检测 TSX/TS 中 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量
|
|
- 检测 `font-family:` CSS 属性中的字面量字体名
|
|
- 检测 `font-size: Npx` 硬编码
|
|
- 白名单:`globals.css`、`styles/tokens/*.css`、`email-channel.ts`、`manifest.ts`
|
|
|
|
### 7.2 项目规则更新
|
|
|
|
[`.trae/rules/project_rules.md`](file:///e:/Desktop/CICD/.trae/rules/project_rules.md) "Tailwind 规范"章节新增:
|
|
|
|
```markdown
|
|
### 设计令牌规范(强制)
|
|
|
|
- **禁止硬编码颜色**: TSX/TS/CSS 中不得出现 `#hex` 颜色字面量,统一使用 `var(--*)` 或 Tailwind 类
|
|
- **禁止硬编码字体**: 不得出现 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量,使用 `var(--font-family-sans/serif/mono)`
|
|
- **禁止硬编码字号**: 不得出现 `font-size: Npx`,使用 `var(--font-size-1~9)`
|
|
- **禁止 Tailwind 任意值**: 不得使用 `w-[Npx]`/`h-[Npx]`/`p-[Npx]` 等,映射到 `--space-*` 或 Tailwind 默认阶梯
|
|
- **豁免场景**(需 `// arbitrary-value: <reason>` 注释):
|
|
- PWA manifest(`app/manifest.ts`)
|
|
- 邮件 HTML 内联样式(`email-channel.ts`)
|
|
- 图表 SVG 固定画布尺寸
|
|
- loading.tsx 占位骨架
|
|
- **令牌文件分布**: `src/app/styles/tokens/`(primitive/semantic-light/semantic-dark/lesson-preparation/tailwind-theme)
|
|
- **改令牌必同步图**: 修改令牌定义后,同步更新 `docs/architecture/004` 与 `005`
|
|
```
|
|
|
|
### 7.3 Lint 命令不变
|
|
|
|
`npm run lint` 与 `npx tsc --noEmit` 保持零错误零警告。
|
|
|
|
---
|
|
|
|
## 8. 验证策略与架构图同步
|
|
|
|
### 8.1 验证清单
|
|
|
|
| 验证项 | 命令/方法 | 通过标准 |
|
|
|---|---|---|
|
|
| TypeScript | `npx tsc --noEmit` | 零错误 |
|
|
| ESLint | `npm run lint` | 零错误零警告 |
|
|
| 视觉回归 | `npm run test:visual` | 快照无差异(或更新后人工确认) |
|
|
| 备课编辑器视觉 | 手动检查 `teacher/lesson-plans/[id]/edit` | 纸感、inline-node、师生对话样式无变化 |
|
|
| 暗色模式 | 切换暗色主题 | 所有页面可读,无对比度问题 |
|
|
| shadcn ui 组件 | 抽查 button/card/dialog/table | 零回归 |
|
|
|
|
### 8.2 架构图同步(项目规则强制)
|
|
|
|
修改完成后,同步更新:
|
|
|
|
| 文档 | 同步内容 |
|
|
|---|---|
|
|
| [`docs/architecture/004_architecture_impact_map.md`](file:///e:/Desktop/CICD/docs/architecture/004_architecture_impact_map.md) | 新增"设计令牌体系"章节(双层架构图、文件分布、令牌分类) |
|
|
| [`docs/architecture/005_architecture_data.json`](file:///e:/Desktop/CICD/docs/architecture/005_architecture_data.json) | `modules.shared.exports` 新增 `design-tokens` 节点;`dependencyMatrix` 新增令牌依赖关系 |
|
|
| [`docs/troubleshooting/known-issues.md`](file:///e:/Desktop/CICD/docs/troubleshooting/known-issues.md) | 新增"设计令牌"问题分类,记录 Tailwind v4 + ESLint 兼容性、HEX→HSL 转换、任意值豁免规则 |
|
|
|
|
### 8.3 大爆炸式执行顺序(单 PR 内)
|
|
|
|
1. 创建 `src/app/styles/tokens/` 6 个文件(primitive/semantic-light/semantic-dark/lesson-preparation/tailwind-theme/index)
|
|
2. `globals.css` 改为 `@import` 引入,删除已迁移内容
|
|
3. `globals.css` 中剩余硬编码样式令牌化(lp-tb-btn/lp-inline-node/lp-qa-*)
|
|
4. 全局扫描替换:91 处 `#hex` → 令牌引用(按 6.1 表)
|
|
5. 全局扫描替换:8 处 font-family + 15 处 font-size → 令牌引用(按 6.2/6.3 表)
|
|
6. 全局扫描替换:98 处任意值 → Tier 1/2/3 分级处理(按 6.4 表)
|
|
7. ESLint 规则新增 + 自定义规则文件
|
|
8. 项目规则文档更新
|
|
9. 架构图同步(004/005)
|
|
10. `docs/troubleshooting/known-issues.md` 更新
|
|
11. `npm run lint` + `npx tsc --noEmit` + `npm run test:visual` 验证
|
|
12. 单次 commit:`refactor(design-tokens): 全量体系化重建设计令牌`
|
|
|
|
---
|
|
|
|
## 9. 风险与缓解
|
|
|
|
| 风险 | 缓解 |
|
|
|---|---|
|
|
| Tailwind v4 + `eslint-plugin-tailwindcss` 兼容性未知 | 实施时先验证,不兼容则自定义 AST 规则 |
|
|
| 大爆炸式改动 50+ 文件,review 困难 | 严格按 8.3 执行顺序,每步独立验证;视觉回归测试保障 |
|
|
| `--lp-*` HEX→HSL 转换可能有色差 | 视觉回归测试 + 手动备课编辑器检查 |
|
|
| 98 处任意值分级清理可能遗漏 | Tier 3 注释豁免需明确理由,ESLint 规则锁定 |
|
|
| 暗色补全可能对比度不足 | 暗色模式手动抽查所有页面 |
|
|
| shadcn ui 组件理论零改动但需验证 | 抽查 button/card/dialog/table 4 个核心组件 |
|
|
|
|
---
|
|
|
|
## 10. 范围与非目标
|
|
|
|
### 10.1 范围内
|
|
|
|
- 令牌底座重建(Primitive + Semantic 双层)
|
|
- 尺度令牌补齐(font/size/leading/weight/radius/shadow/spacing/duration/ease/z-index)
|
|
- `--lp-*` 升级(HEX→HSL + 暗色 + Tailwind 暴露)
|
|
- M3 Surface 死代码清理
|
|
- 91 处硬编码颜色清理
|
|
- 8 处硬编码 font-family 清理
|
|
- 15 处硬编码 font-size 清理
|
|
- 98 处 Tailwind 任意值清理
|
|
- ESLint 强制约束规则
|
|
- 项目规则文档更新
|
|
- 架构图同步(004/005)
|
|
- known-issues.md 更新
|
|
|
|
### 10.2 范围外(非目标)
|
|
|
|
- 第三主题(如 high-contrast、paper 纸感)预留 — YAGNI
|
|
- `shared/components/ui/*` shadcn 组件内部样式改动 — 理论零改动,仅验证
|
|
- `cn()` 工具函数修改 — 不变
|
|
- `tailwind.config.ts` 内容扩展 — 保持极简
|
|
- 设计令牌文档站(Storybook 等)— 不在本期范围
|
|
- 多主题切换架构 — 仅明暗双色
|