# UI Design

> UI设计技能 - UI/UX审查、界面优化、设计系统规范。当你涉及修改/新建UI视图、样式、布局、颜色、动效、图标相关代码时必须使用此技能。即使用户只是说"改下样式"或"这个界面不好看"，也应触发。

- Skill: `yuexueyu/ui-design` (Agent Skill)
- Install (CLI): `npx skillmds@latest add yuexueyu/ui-design`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yuexueyu/ui-design/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: yuexueyu (https://skillmd.com/u/yuexueyu)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/yuexueyu/ui-design

---


# UI设计技能 (UI Design Skill)

## 快速规则（日常开发时自动加载，只需读到这里）

> **[UI核心清单]** ① 先Grep项目已有颜色/字体/间距，复用Token禁止硬编码新值 ② 白色/浅灰主基调，强调色≤1种，❌AI蓝紫渐变 ❌任何紫色(purple/violet/#7xx/#6x6xf1等) ③ 留白≥30%，间距4px倍数，信息层级≤3层
> **[图标铁律]** ❌绝对禁止emoji做UI图标——必须用SF Symbols/Lucide/Material Icons，线性为主，大小统一
> **[状态完整]** 每个组件必须覆盖：空状态(有引导)+加载态(骨架屏)+成功态+错误态(有重试)+禁用态
> **[时间格式铁律]** 所有时间必须显示为中文格式：YYYY年M月D日 HH:MM:SS，短格式用M月D日。❌绝对禁止ISO格式(2025-03-11T00:00:00+08:00)、纯数字格式(2025-03-11)、英文格式(Mar 11)

写/改UI代码时，强制遵守：
1. **先扫后写**：Grep项目现有颜色/字体/间距定义，复用已有Token，禁止硬编码新值（硬编码=改主题时散落各处无法统一修改，且与现有设计体系不一致）
2. **配色**：白色/浅灰为主(#FFFFFF/#FAFAFA/#F3F4F6)，文字#1a1a1a/#374151/#9CA3AF，强调色≤1种
3. **禁止项**：❌emoji图标（不同系统渲染不一致且不专业，用SF Symbols/Lucide） ❌AI蓝紫渐变（用户明确禁止） ❌任何紫色色值(purple/violet/#722ed1/#667eea/#764ba2/#6366f1等含紫色的hex)❌花哨动效（分散注意力降低可用性） ❌过度装饰（增加视觉噪音降低信息密度） ❌非ISO/非中文时间格式（所有时间必须显示为中文）
4. **排版**：行高1.5~1.7，字号阶梯24/18/14/12，正文max-width 680px，左对齐为主
5. **间距**：4px倍数(4/8/12/16/24/32/48)，留白≥30%，内容不贴边
6. **动效**：Hover 150ms ease，点击scale(0.98)，页面切换250ms，列表stagger 30ms
7. **组件**：按钮高度36-44px/圆角8px，输入框高度40px/border 1px #E5E7EB，卡片shadow 0 1px 3px rgba(0,0,0,0.1)
8. **风格**：简约克制，信息层级清晰，美观性=第一优先级

---

## 完整审查流程（手动 /ui-design 或专项审查时执行）

### Phase 1: 设计系统分析
1. 扫描项目中的颜色定义、字体定义、间距常量
2. 检查是否有统一的设计Token（颜色/字体/圆角/阴影/间距）
3. 识别不一致的样式：同一语义用了不同的颜色/字体大小
4. 检查暗色模式：所有颜色是否有Dark Mode适配、动态颜色是否使用系统语义色

### Phase 2: 布局与响应式审查
5. 检查布局约束：
   - 硬编码宽高→应使用自适应/约束
   - 文本支持动态字体大小（Accessibility）
   - 多屏幕尺寸/分辨率/多显示器下布局正确
   - 竖屏/横屏切换处理（移动端）、窗口缩放处理（桌面端）

6. 检查间距一致性：
   - padding/margin使用统一间距系统（4/8/12/16/24）
   - 同类元素间距一致、内容与容器边缘间距统一

7. 检查国际化布局：
   - 文本长度变化对布局的影响（德文/日文可能比中文长50%+）
   - RTL语言支持（阿拉伯文/希伯来文布局镜像）
   - 日期/数字/货币格式本地化

### Phase 3: 状态完整性审查
8. 每个视图/组件检查以下状态是否全部覆盖：
   - **空状态**：首次使用/无数据时显示什么？是否有引导操作？
   - **加载状态**：骨架屏/Spinner/进度条？是否有超时处理？
   - **成功状态**：正常数据展示
   - **部分加载**：列表只加载了一部分？分页/加载更多？
   - **错误状态**：网络错误/权限不足/数据异常？是否有重试按钮？
   - **禁用状态**：条件不满足时按钮/输入框的视觉反馈

### Phase 4: 交互与动效审查
9. 交互反馈：
   - 按钮hover/press/disabled状态
   - 操作成功/失败的视觉反馈
   - 长操作的进度反馈（不要让用户猜是否在运行）

10. 动画：
    - 过渡动画duration 150-300ms
    - 尊重系统「减少动态效果」设置
    - 不要用动画掩盖性能问题

### Phase 5: 可访问性审查
11. 检查：
    - 颜色对比度 >= 4.5:1（WCAG AA）
    - 点击目标 >= 44pt（iOS）/ 48dp（Android）/ 合理大小（桌面）
    - VoiceOver/TalkBack/屏幕阅读器标签
    - 键盘导航完整、焦点顺序合理

### Phase 6: 性能审查
12. 检查UI性能：
    - 图片：是否有未压缩的大图、是否有懒加载、是否使用适当分辨率
    - 列表：大数据量是否使用虚拟化/复用（LazyVStack/RecyclerView/虚拟滚动）
    - 重复渲染：状态变化是否触发不必要的全量重绘
    - 主线程：UI更新是否在主线程、是否有主线程阻塞操作

### Phase 7: 平台规范审查
13. macOS：Menu Bar菜单完整性、右键上下文菜单、拖拽支持、Touch Bar、键盘快捷键
14. iOS：导航模式一致(Push/Modal)、Safe Area、手势冲突、系统控件使用
15. Web：浏览器兼容性、SEO基础、PWA支持度

### Phase 8: 输出报告

```
## UI审查报告

### 设计一致性
| 问题 | 位置 | 当前值 | 建议值 | 优先级 |

### 布局/响应式
| 问题 | 位置 | 描述 | 修复方案 | 优先级 |

### 状态覆盖缺失
| 组件/视图 | 缺失状态 | 当前表现 | 建议 | 优先级 |

### 交互/动效
| 问题 | 位置 | 描述 | 建议 | 优先级 |

### 可访问性
| 问题 | 位置 | 描述 | 修复方案 | 优先级 |

### 性能
| 问题 | 位置 | 影响 | 优化方案 | 优先级 |
```

## 设计风格规范（所有UI工作强制遵守）

### 核心风格：简洁清爽
- **主基调**：白色/浅灰为主背景，内容区域干净留白充足
- **配色**：浅色系为主，强调色用饱和度适中的单色（如浅蓝/浅绿/浅灰蓝），避免高饱和
- ❌ **禁止AI风格配色**：蓝紫渐变、紫红霓虹、赛博朋克色系一律不用（用户明确禁止，且这类配色在专业工具软件中显得不严肃）
- ❌ **绝对禁止紫色**：任何含紫色的hex值一律不用，包括但不限于#722ed1/#667eea/#764ba2/#6366f1/#7c3aed/#8b5cf6/#a78bfa/#6d28d9。替代方案：用蓝(#1890ff)、青(#36cfc9)、灰蓝(#4096ff)
- ❌ **禁止非中文时间格式**：所有时间必须显示为中文格式（YYYY年M月D日 HH:MM:SS，短格式M月D日）。绝对禁止ISO 8601格式、纯数字格式(YYYY-MM-DD)、英文格式(Mar 11)。后端用DATE_FORMAT返回纯字符串，前端统一用formatTimeCN工具函数转换
- ❌ **禁止花哨装饰**：无意义的渐变背景/粒子效果/过度阴影/3D效果（增加视觉噪音和渲染开销，降低信息可读性）
- **排版**：字号层级分明（标题/正文/辅助最多3级），行间距舒适，段落间有呼吸感

### 图标规范
- ❌ **绝对禁止使用emoji/表情符号作为UI图标**——emoji在不同系统渲染不一致，不专业
- ✅ 必须使用专业图标库：SF Symbols(Apple) / Lucide / Material Icons / Feather Icons
- 图标风格：线性(outline)为主，与文字同色或浅灰，大小统一(16/20/24px)
- 同一界面内图标风格必须一致（不混用填充和线性）

### 设计Token强制规则
**写任何UI代码前，必须先执行：**
1. Grep项目中所有颜色定义（搜索`Color(`/`UIColor`/`NSColor`/`#`/`rgb`/`--color`/主题文件）
2. Grep项目中所有字体/间距定义
3. 识别并复用项目已有的设计Token体系

**强制约束：**
- ❌ 禁止硬编码新的颜色hex值/RGB值（散落的硬编码颜色无法统一切换主题/暗色模式，维护成本指数级增长）
- ❌ 禁止硬编码新字号（字号不一致让界面显得业余，破坏视觉层次）
- ❌ 禁止新建与现有组件功能重复的组件（重复组件导致行为/样式不一致，维护时改了一个忘了另一个）
- ✅ 新组件必须与现有组件视觉风格一致（圆角/阴影/动效/配色）
- ✅ 无设计系统时，提取现有UI中最常用的颜色/字体/间距作为事实标准

## UI开发指导（写UI代码时的设计师思维）

### 视觉层次（最重要——决定界面是否"高级"）
- **对比度层次**：用颜色深浅/字重/大小区分信息优先级，而非用颜色种类
  - 主标题：深黑(#1a1a1a) + 大字号 + 粗体
  - 正文：深灰(#333) + 正常字号 + 常规字重
  - 辅助信息：浅灰(#999) + 小字号 + 常规字重
- **少即是多**：一个页面的强调色不超过1种，信息层级不超过3层
- **留白比例**：内容区域至少30%是留白。紧凑≠拥挤，宽松=高级感

### 配色方案（经典不过时）
- **主背景**：纯白(#FFFFFF)或极浅灰(#FAFAFA/#F5F5F5)
- **卡片/容器**：白色+极细边框(#E5E5E5, 0.5px)或微弱阴影(0 1px 3px rgba(0,0,0,0.08))
- **强调色**：选一个，全局统一。推荐：
  - 商务：蓝(#2563EB/#1890ff) | 清新：绿(#10B981/#52c41a) | 温暖：橙(#F59E0B/#fa8c16) | 中性：青(#36cfc9)（❌不用灰蓝#6366F1，偏紫）
- **文字色**：不要用纯黑(#000)——用深黑(#1a1a1a)或深灰(#374151)，更柔和
- **分割线**：极浅灰(#F0F0F0)，不要用深色分割线

### 排版规则（让文字有呼吸感）
- **行高**：正文1.5~1.7倍，标题1.2~1.3倍
- **段间距**：段落之间的间距 ≥ 行间距的2倍
- **字号阶梯**（只用这几个，保持统一）：
  - 大标题：24-28px | 小标题：18-20px | 正文：14-16px | 辅助：12-13px
- **对齐**：左对齐为主（最易读），居中只用于标题/空状态

### 交互与微动效（点睛之笔，不是主角）
- **Hover效果**：背景色微变(+5%灰度)或微弱上移(translateY -1px)，duration 150ms
- **点击反馈**：按下时微缩(scale 0.98)或背景色加深，duration 100ms
- **页面切换**：淡入淡出(opacity 0→1, duration 200ms)或滑入(translateX, duration 250ms)
- **列表项出现**：依次淡入(stagger 30ms)，不要同时出现
- **加载状态**：骨架屏(skeleton) > 转圈(spinner) > 纯文字"加载中"
- **成功/完成**：轻量的打勾动画或绿色闪现，不要弹窗打断用户
- ❌ 禁止：弹跳动画/旋转动画/闪烁/过长的动画(>400ms)/遮挡内容的动画（花哨动效分散注意力，过长动画阻碍操作流畅度，闪烁可触发光敏性癫痫）
- ✅ 动效时长参考：即时反馈100ms | 简单过渡200ms | 复杂过渡300ms | 页面切换250ms

### 组件设计标准
- **按钮**：主按钮(填充色+白字)最多1个/区域，次按钮(边框/文字)无限制。圆角4-8px
- **输入框**：浅灰底色或白底+细边框，focus时边框变强调色。高度36-44px
- **卡片**：白底+微阴影或极细边框，内边距16-24px，圆角8-12px
- **弹窗/面板**：居中或从边缘滑入，有半透明遮罩(rgba(0,0,0,0.3))，圆角12-16px
- **列表**：行高48-56px(可点击的)或32-40px(纯展示)，hover变色，间距用分割线或间隙二选一
- **空状态**：居中浅灰图标+说明文字+操作按钮，不要只显示空白

### 响应式思维
- 不硬编码宽高，用min/max+flex/grid约束
- 文字不截断(用省略号...)，但要设max-width防止一行太长(正文max-width: 680px)
- 间距用相对单位或设计Token，不用绝对像素

## 设计原则
- **美观性** = 第一优先级（简约即美，克制即高级）
- **一致性** > 可用性 > 创新
- 遵循平台设计规范（Apple HIG / Material Design / Web标准）
- 信息层级清晰：主操作突出、次要操作弱化
- 留白是设计的一部分，不要填满每一寸空间
- 颜色语义：红=危险/错误，绿=成功，蓝=信息/操作，黄=警告
- 每个状态都需要设计，不只是"正常状态"

## 浅色系三原色配色铁律（后台/管理面板/App强制遵守）

### 核心原则：浅色底+三原色
- **底色**：纯白(#FFFFFF)或极浅灰(#FAFAFA/#F5F5F5)，大面积留白
- **三原色**：整个项目最多使用3种功能色，分别对应：
  - **主色**（1种）：品牌/操作色，用于主按钮/链接/选中态。推荐蓝(#1890ff)或项目已有主色
  - **成功色**（1种）：正向反馈/增长/完成。推荐绿(#52c41a)
  - **警告/危险色**（1种）：错误/删除/下降。推荐红(#ff4d4f)
- **辅助灰阶**（不算在三原色内）：文字(#1a1a1a/#666/#999)、边框(#e5e7eb)、分割线(#f0f0f0)、背景(#fafafa)
- ❌ 禁止超过3种功能色（颜色越多越杂乱，三原色足够表达所有语义）
- ❌ 禁止高饱和/荧光色（刺眼+不专业），功能色饱和度适中即可
- ❌ 禁止大面积使用功能色做背景（功能色只用于小面积强调：按钮/标签/图标/图表线条）

### 后台管理面板配色模板
```
背景色：#FAFAFA（页面）/ #FFFFFF（卡片/表格）
文字色：#1a1a1a（标题）/ #666（正文）/ #999（辅助）
主色：  #1890ff（按钮/链接/选中）
成功色：#52c41a（上涨/通过/激活）
危险色：#ff4d4f（下降/错误/删除/过期）
警告色：#faad14（待处理/注意）← 可选第4色，仅在确实需要时使用
边框：  #e5e7eb
分割线：#f0f0f0
禁用：  #d9d9d9（文字）/ #f5f5f5（背景）
```

## 报表/图表/统计数据展示规范

### 数据准确性铁律
- ❌ 前端禁止自行计算统计数据（求和/平均/百分比）——必须由后端API返回已计算好的数值（前端浮点精度问题+数据不一致风险）
- ❌ 禁止用`toFixed()`做金额显示（浮点精度丢失）——后端返回字符串或整数分
- ✅ 数字显示必须千分位分隔（`12,345`不是`12345`），用`Intl.NumberFormat`或工具函数
- ✅ 百分比保留1位小数（`85.3%`），由后端计算好传给前端
- ✅ 金额显示带单位和符号（`¥12,345.00`），对齐方式右对齐

### 图表规范（ECharts/Chart.js等）
- **颜色**：图表线条/柱子使用三原色体系，多系列时用主色的不同透明度（#1890ff→#1890ff80→#1890ff40）
- **背景**：图表区域纯白，网格线用极浅灰(#f0f0f0)虚线，❌不要深色背景
- **标注**：Y轴有单位标签，X轴日期用中文格式（M月D日），tooltip显示完整数据
- **交互**：hover高亮数据点+tooltip，支持缩放/选区（数据量大时）
- **空状态**：无数据时显示"暂无数据"占位，不显示空白图表框
- **加载态**：图表区域显示骨架屏/loading，不显示错误的零值图表
- **响应式**：图表容器用`ResizeObserver`监听尺寸变化自动resize

### 图表类型选择
| 数据类型 | 推荐图表 | 不推荐 |
|----------|----------|--------|
| 趋势变化（时间序列） | 折线图 | 饼图 |
| 分类对比 | 柱状图（≤10类）| 饼图（>5类难以区分） |
| 占比分布 | 环形图（≤5类）| 饼图（无中心信息展示） |
| 排行/TOP N | 水平柱状图 | 折线图 |
| 数值概览 | 数字卡片（大字号+趋势箭头） | 表格 |

### 仪表盘/Dashboard布局
```
┌─────────────────────────────────────────────┐
│  数字概览卡片（4个一排，大字号+趋势箭头）      │
├─────────────────────┬───────────────────────┤
│  主图表（折线/柱状）  │  辅助图表（环形/排行）   │
│  占2/3宽度           │  占1/3宽度              │
├─────────────────────┴───────────────────────┤
│  数据表格（带分页/筛选/排序）                    │
└─────────────────────────────────────────────┘
```
- 卡片间距16-24px，卡片内边距16-20px
- 数字概览卡片：大字号(28-32px)+趋势箭头(绿↑红↓)+同比/环比小字
- 图表高度300-400px，不要太矮（数据密集时难以辨认）也不要太高（浪费空间）

### 数据表格规范
- **表头**：浅灰背景(#fafafa)，文字加粗，左对齐（数字列右对齐）
- **斑马纹**：奇偶行交替浅灰(#fafafa)，hover行高亮(#e6f7ff)
- **分页**：默认20条/页，支持切换(10/20/50/100)，显示总条数
- **排序**：可排序列显示排序图标，当前排序列高亮
- **筛选**：关键字段支持筛选/搜索，筛选条件显示在表格上方
- **操作列**：固定在右侧，按钮用文字链接而非按钮（节省空间），危险操作(删除)用红色
- **空表格**：显示"暂无数据"+引导操作，不显示空白表格框架

## 数据导出功能规范

### 导出按钮
- 位置：表格右上角，与筛选条件同行
- 样式：次级按钮（边框样式），图标+文字"导出"
- 支持格式：默认Excel(.xlsx)，可选CSV（大数据量时推荐CSV）

### 导出逻辑
- **数据来源**：导出时请求后端API获取全量数据（不是只导出当前页！），后端API需支持`export=true`参数返回全部数据
- **筛选条件**：导出时携带当前筛选/排序条件，导出的是筛选后的结果
- **文件名**：`{模块名}_{导出日期}.xlsx`，如`卡密列表_2025年3月18日.xlsx`
- **大数据量**：>10000条时后端生成文件+返回下载链接，前端显示"导出中..."进度提示
- **表头**：使用中文列名，与页面表格列名一致
- **格式化**：日期用中文格式，金额保留2位小数，状态用中文文字而非代码

### 导出交互
1. 点击导出按钮 → 按钮显示loading+disabled
2. 请求后端API → 返回文件blob或下载链接
3. 触发浏览器下载 → 按钮恢复
4. 失败时toast提示"导出失败，请重试"

## 前端工程化规范（写前端代码时强制遵守）

### Vue 3 规范
- **Composition API优先**：新组件一律用`<script setup>`，禁止Options API混用
- **响应式**：`ref()`用于基本类型，`reactive()`用于对象/数组，`computed()`用于派生状态
- **Props验证**：所有props必须定义类型和默认值，用`defineProps<T>()`泛型写法
- **Emits声明**：所有事件用`defineEmits<T>()`显式声明，禁止未声明的$emit
- **组件命名**：PascalCase（`UserCard.vue`），文件名即组件名，禁止index.vue嵌套
- **模板规范**：`v-for`必须有`:key`（用唯一ID不用index），`v-if`/`v-for`不同时用在同一元素
- **watch清理**：`watchEffect`/`watch`中有定时器/事件监听必须在`onUnmounted`清理

### 状态管理（Pinia）
- **Store粒度**：按功能域拆分（`useUserStore`/`useCartStore`），禁止一个巨型Store
- **异步操作**：actions中处理API调用，组件中不直接写fetch/axios
- **持久化**：需要持久化的状态用`pinia-plugin-persistedstate`，不手写localStorage

### 目录结构
```
src/
  views/          # 页面级组件（路由对应）
  components/     # 可复用组件
  composables/    # 组合式函数（useXxx）
  utils/          # 工具函数（纯函数，无副作用）
  api/            # API请求封装
  stores/         # Pinia stores
  assets/         # 静态资源
  router/         # 路由配置
```

### API请求规范
- **统一封装**：axios/fetch封装为request.js，统一拦截错误/token/loading
- **请求函数**：按模块拆分（`api/user.js`/`api/card.js`），返回Promise
- **错误处理**：请求失败统一toast提示，组件中不重复处理通用错误
- **防重提交**：按钮点击后立即disabled+loading，请求完成后恢复

### Vite / 打包
- **路径别名**：`@/` → `src/`，禁止`../../../`深层相对路径
- **环境变量**：`import.meta.env.VITE_*`，敏感信息不进前端代码
- **代码分割**：路由级懒加载`() => import('./views/Xxx.vue')`
- **依赖优化**：大依赖（echarts/moment等）按需引入，不全量导入

### CSS规范
- **Scoped优先**：`<style scoped>`防止样式泄漏，全局样式放`assets/global.css`
- **BEM或语义化**：类名`.card-header`而非`.a1`/`.box`
- **变量复用**：颜色/间距用CSS变量或项目Token，禁止散落的magic number
- **响应式**：移动优先media query，断点统一（sm:640/md:768/lg:1024/xl:1280）

### 前端性能铁律
- ❌ 禁止`v-for`中嵌套复杂计算（用computed预处理）
- ❌ 禁止在template中写复杂表达式（提取为computed或method）
- ❌ 禁止全量导入UI库（`import ElementPlus from 'element-plus'`→按需导入）
- ✅ 图片必须压缩+懒加载（`loading="lazy"`）
- ✅ 列表>100项必须虚拟滚动
- ✅ 频繁触发的事件（scroll/resize/input）必须防抖/节流

## Vue生产级模式（从gin-vue-admin提取）

### Axios请求Loading管理
```javascript
// 计数器模式：多个并发请求只显示一个loading
let activeAxios = 0
let loadingInstance = null
let forceCloseTimer = null

const showLoading = () => {
  activeAxios++
  if (activeAxios > 0 && !loadingInstance) {
    // 延迟400ms显示（避免快速请求闪烁）
    setTimeout(() => {
      if (activeAxios > 0) {
        loadingInstance = ElLoading.service({})
        // 30秒强制关闭（防loading永远不消失）
        forceCloseTimer = setTimeout(() => {
          loadingInstance?.close()
          loadingInstance = null
          activeAxios = 0
        }, 30000)
      }
    }, 400)
  }
}
const closeLoading = () => {
  activeAxios--
  if (activeAxios <= 0) {
    activeAxios = 0
    clearTimeout(forceCloseTimer)
    loadingInstance?.close()
    loadingInstance = null
  }
}
// 页面卸载时清理（防内存泄漏）
window.addEventListener('beforeunload', resetLoading)
```

### 响应拦截器自动续期Token
```javascript
service.interceptors.response.use((response) => {
  // 后端通过header返回新token，前端自动更新
  if (response.headers['new-token']) {
    userStore.setToken(response.headers['new-token'])
  }
  // code=0为成功，非0显示错误消息
  if (response.data.code === 0) return response.data
  ElMessage.error(response.data.msg)
  return response.data
}, (error) => {
  if (error.response?.status === 401) {
    // 统一401处理：清缓存+跳登录
    userStore.ClearStorage()
    router.push({ name: 'Login', replace: true })
  }
  return Promise.reject(error)
})
```

### 全局前端错误收集→DB
```javascript
// 前端未捕获错误自动上报到后端DB，后台可查
window.addEventListener('unhandledrejection', (event) => {
  createSysError({
    form: '前端',
    info: `${event.reason}\nStack: ${event.reason?.stack || '无'}`,
    level: 'error'
  })
})
```

### 统一错误预览组件（预设错误类型）
```javascript
// 按HTTP状态码预设错误提示和图标
const presetErrors = {
  500: { title: '服务器错误', tips: '常见于后台panic，请查看后台日志' },
  404: { title: '资源未找到', tips: '接口未注册或路径不匹配' },
  401: { title: '身份认证失败', tips: '令牌过期，请重新登录' },
  network: { title: '网络错误', tips: '无法连接服务器，检查网络' }
}
```

### 路由守卫+NProgress进度条
```javascript
// 白名单路由免登录 + 动态路由按需注册 + NProgress进度反馈
const WHITE_LIST = ['Login', 'Init']
router.beforeEach(async (to) => {
  Nprogress.start()
  if (WHITE_LIST.includes(to.name)) return true
  if (!token) return { name: 'Login', query: { redirect: to.fullPath } }
  if (!routerStore.asyncRouterFlag) await setupRouter() // 首次加载动态路由
  return to.matched.length ? true : { path: '/layout/404' }
})
router.afterEach(() => {
  document.querySelector('.main-cont')?.scrollTo(0, 0) // 页面切换回顶部
  Nprogress.done()
})
```

### 主题色动态切换（CSS变量）
```javascript
// 通过CSS变量动态设置Element Plus主色系
export const setBodyPrimaryColor = (color, darkMode) => {
  document.documentElement.style.setProperty('--el-color-primary', color)
  // 自动生成10级浅色/深色衍生色
  for (let i = 1; i <= 10; i++) {
    document.documentElement.style.setProperty(
      `--el-color-primary-light-${i}`,
      lighten(color, i / 10) // 混合白色/深色
    )
  }
}
```

## 约束
- 审查基于代码分析，不做主观美学评价
- 修改建议必须附带具体的代码位置（file:line）
- 不改变功能逻辑，只优化视觉和交互
- 性能建议需附带预估影响（如"减少首屏加载时间~200ms"）

