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代码时,强制遵守:
- 先扫后写:Grep项目现有颜色/字体/间距定义,复用已有Token,禁止硬编码新值(硬编码=改主题时散落各处无法统一修改,且与现有设计体系不一致)
- 配色:白色/浅灰为主(#FFFFFF/#FAFAFA/#F3F4F6),文字#1a1a1a/#374151/#9CA3AF,强调色≤1种
- 禁止项:❌emoji图标(不同系统渲染不一致且不专业,用SF Symbols/Lucide) ❌AI蓝紫渐变(用户明确禁止) ❌任何紫色色值(purple/violet/#722ed1/#667eea/#764ba2/#6366f1等含紫色的hex)❌花哨动效(分散注意力降低可用性) ❌过度装饰(增加视觉噪音降低信息密度) ❌非ISO/非中文时间格式(所有时间必须显示为中文)
- 排版:行高1.5~1.7,字号阶梯24/18/14/12,正文max-width 680px,左对齐为主
- 间距:4px倍数(4/8/12/16/24/32/48),留白≥30%,内容不贴边
- 动效:Hover 150ms ease,点击scale(0.98),页面切换250ms,列表stagger 30ms
- 组件:按钮高度36-44px/圆角8px,输入框高度40px/border 1px #E5E7EB,卡片shadow 0 1px 3px rgba(0,0,0,0.1)
- 风格:简约克制,信息层级清晰,美观性=第一优先级
完整审查流程(手动 /ui-design 或专项审查时执行)
Phase 1: 设计系统分析
- 扫描项目中的颜色定义、字体定义、间距常量
- 检查是否有统一的设计Token(颜色/字体/圆角/阴影/间距)
- 识别不一致的样式:同一语义用了不同的颜色/字体大小
- 检查暗色模式:所有颜色是否有Dark Mode适配、动态颜色是否使用系统语义色
Phase 2: 布局与响应式审查
检查布局约束:
- 硬编码宽高→应使用自适应/约束
- 文本支持动态字体大小(Accessibility)
- 多屏幕尺寸/分辨率/多显示器下布局正确
- 竖屏/横屏切换处理(移动端)、窗口缩放处理(桌面端)
检查间距一致性:
- padding/margin使用统一间距系统(4/8/12/16/24)
- 同类元素间距一致、内容与容器边缘间距统一
检查国际化布局:
- 文本长度变化对布局的影响(德文/日文可能比中文长50%+)
- RTL语言支持(阿拉伯文/希伯来文布局镜像)
- 日期/数字/货币格式本地化
Phase 3: 状态完整性审查
- 每个视图/组件检查以下状态是否全部覆盖:
- 空状态:首次使用/无数据时显示什么?是否有引导操作?
- 加载状态:骨架屏/Spinner/进度条?是否有超时处理?
- 成功状态:正常数据展示
- 部分加载:列表只加载了一部分?分页/加载更多?
- 错误状态:网络错误/权限不足/数据异常?是否有重试按钮?
- 禁用状态:条件不满足时按钮/输入框的视觉反馈
Phase 4: 交互与动效审查
交互反馈:
- 按钮hover/press/disabled状态
- 操作成功/失败的视觉反馈
- 长操作的进度反馈(不要让用户猜是否在运行)
动画:
- 过渡动画duration 150-300ms
- 尊重系统「减少动态效果」设置
- 不要用动画掩盖性能问题
Phase 5: 可访问性审查
- 检查:
- 颜色对比度 >= 4.5:1(WCAG AA)
- 点击目标 >= 44pt(iOS)/ 48dp(Android)/ 合理大小(桌面)
- VoiceOver/TalkBack/屏幕阅读器标签
- 键盘导航完整、焦点顺序合理
Phase 6: 性能审查
- 检查UI性能:
- 图片:是否有未压缩的大图、是否有懒加载、是否使用适当分辨率
- 列表:大数据量是否使用虚拟化/复用(LazyVStack/RecyclerView/虚拟滚动)
- 重复渲染:状态变化是否触发不必要的全量重绘
- 主线程:UI更新是否在主线程、是否有主线程阻塞操作
Phase 7: 平台规范审查
- macOS:Menu Bar菜单完整性、右键上下文菜单、拖拽支持、Touch Bar、键盘快捷键
- iOS:导航模式一致(Push/Modal)、Safe Area、手势冲突、系统控件使用
- 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代码前,必须先执行:
- Grep项目中所有颜色定义(搜索
Color(/UIColor/NSColor/#/rgb/--color/主题文件) - Grep项目中所有字体/间距定义
- 识别并复用项目已有的设计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.21.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位小数,状态用中文文字而非代码
导出交互
- 点击导出按钮 → 按钮显示loading+disabled
- 请求后端API → 返回文件blob或下载链接
- 触发浏览器下载 → 按钮恢复
- 失败时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管理
// 计数器模式:多个并发请求只显示一个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
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
// 前端未捕获错误自动上报到后端DB,后台可查
window.addEventListener('unhandledrejection', (event) => {
createSysError({
form: '前端',
info: `${event.reason}\nStack: ${event.reason?.stack || '无'}`,
level: 'error'
})
})
统一错误预览组件(预设错误类型)
// 按HTTP状态码预设错误提示和图标
const presetErrors = {
500: { title: '服务器错误', tips: '常见于后台panic,请查看后台日志' },
404: { title: '资源未找到', tips: '接口未注册或路径不匹配' },
401: { title: '身份认证失败', tips: '令牌过期,请重新登录' },
network: { title: '网络错误', tips: '无法连接服务器,检查网络' }
}
路由守卫+NProgress进度条
// 白名单路由免登录 + 动态路由按需注册 + 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变量)
// 通过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")