Files
pingxiangcard/docs/superpowers/specs/2026-06-26-ui-ux-standardization-design.md
T
2026-06-26 09:05:59 +08:00

455 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UI/UX 标准化设计规范
> 湖南12301文旅专区微信小程序(C端)
> 2026-06-26
## 背景
项目现有 35 个 .vue 文件,存在以下问题:
- 双 token 系统冲突(`uni.scss` vs `_tokens.scss`)
- 14 个页面自定义局部 SCSS 变量,绕过全局体系
- 3 套字体族混用(SourceHanSerifCN / Noto Serif SC / 系统字体)
- 55+ 种硬编码颜色值(4 种品牌蓝、4 种错误红、5 种背景灰)
- 19 种字号仅 8 种有对应变量
- 9 种按钮高度
- mock 数据分散在各页面,格式不统一
目标:统一所有页面的字体、字号、颜色、间距、组件规范,以及 mock 数据管理方式,为后续后端 API 对接做好准备。
## 已确认的决策
| 决策项 | 选择 | 理由 |
|--------|------|------|
| 颜色体系 | uni.scss 体系 | 偏暖偏亮,`#3c9cff` 主色 |
| 字体族 | 思源宋体系 | 文化/旅游感强 |
| 模拟数据 | 集中管理 + 类型先行 | 对接 API 时页面零改动 |
| 执行节奏 | 基础设施先行 → 核心链路 → 剩余页面 | 风险最小 |
---
## 1. Token 体系统一
### 1.1 Source of Truth
- 保留 `src/uni.scss` 作为唯一 token 文件(uni-app 自动注入到所有 `<style lang="scss">`)
- `_tokens.scss` 中与 `uni.scss` 冲突的变量值改为直接引用 `uni.scss` 变量,或删除
- 禁止任何页面在 `<style>` 中重新定义 `$font`、`$primary`、`$text-main` 等局部变量
### 1.2 字号体系
9 级字号,消灭所有游离值:
| 变量 | 值 | 场景 |
|------|-----|------|
| `$font-size-xxs` | 18rpx | 辅助标注、库存提示、角标 |
| `$font-size-xs` | 20rpx | 标签、小徽标 |
| `$font-size-sm` | 24rpx | 次要说明、辅助文字 |
| `$font-size-base` | 28rpx | 正文、按钮文字 |
| `$font-size-lg` | 32rpx | 卡片标题、区域小标题 |
| `$font-size-xl` | 36rpx | 区域标题 |
| `$font-size-2xl` | 44rpx | 页面大标题 |
| `$font-size-3xl` | 56rpx | 大号展示数字 |
| `$font-size-display` | 72rpx | 超大展示(极少使用) |
**游离值迁移对照**:
| 原值 | 迁移到 | 理由 |
|------|--------|------|
| 18rpx | 新增 `$font-size-xxs` | 使用频率高,值得单独层级 |
| 22rpx | `$font-size-xs` (20rpx) | 视觉差异极小 |
| 26rpx | `$font-size-base` (28rpx) | 正文统一 28rpx |
| 30rpx | `$font-size-lg` (32rpx) | 卡片标题统一 32rpx |
| 34rpx | `$font-size-xl` (36rpx) | 差异极小 |
### 1.3 字重
| 变量 | 值 | 场景 |
|------|-----|------|
| `$font-weight-normal` | 400 | 正文、说明 |
| `$font-weight-medium` | 500 | 次要标题、按钮 |
| `$font-weight-bold` | 700 | 主标题、价格数字、强调 |
禁止使用 CSS 关键字 `bold`,统一用数字变量。
### 1.4 行高
| 变量 | 值 | 场景 |
|------|-----|------|
| `$line-height-tight` | 1.25 | 标题 |
| `$line-height-normal` | 1.5 | 正文 |
| `$line-height-relaxed` | 1.75 | 长段落、说明文 |
---
## 2. 颜色体系
### 2.1 品牌 & 功能色
以 `uni.scss` 现有值为准,不做修改:
| 变量 | 值 | 用途 |
|------|-----|------|
| `$uv-primary` | `#3c9cff` | 主色/品牌蓝 |
| `$uv-error` | `#e53935` | 错误/危险红 |
| `$uv-warning` | `#f9ae3d` | 警告/橙 |
| `$uv-success` | `#5ac725` | 成功/绿 |
### 2.2 中性色(文字 & 背景)
统一灰阶,替代现有 55+ 种硬编码灰值:
| 变量 | 值 | 用途 |
|------|-----|------|
| `$color-text-primary` | `#111111` | 标题、重要正文 |
| `$color-text-regular` | `#333333` | 正文 |
| `$color-text-secondary` | `#666666` | 次要说明 |
| `$color-text-placeholder` | `#999999` | 占位符、禁用文字 |
| `$color-text-disabled` | `#cccccc` | 不可操作文字 |
| `$color-bg-page` | `#f3f4f6` | 页面背景 |
| `$color-bg-card` | `#ffffff` | 卡片/模块背景 |
| `$color-bg-hover` | `#f5f5f5` | 按压态背景 |
| `$color-border` | `#eeeeee` | 分割线、边框 |
### 2.3 语义色
订单状态、标签等场景专用:
| 变量 | 值 | 用途 |
|------|-----|------|
| `$color-price` | `#e53935` | 价格红 |
| `$color-price-highlight` | `#FFD600` | 促销亮黄(首页大卡专用) |
| `$color-status-pending` | `#ff9500` | 待处理/待支付 |
| `$color-status-success` | `#00b42a` | 已完成/已支付 |
| `$color-status-cancel` | `#86909c` | 已取消/已关闭 |
### 2.4 透明度规范
暗底白字(5 档):
```
rgba(255, 255, 255, 0.35) — 最弱(信任标签等)
rgba(255, 255, 255, 0.55) — 弱(次要说明)
rgba(255, 255, 255, 0.7) — 中(描述文字)
rgba(255, 255, 255, 0.85) — 强(正文)
rgba(255, 255, 255, 0.95) — 最强(标题)
```
白底投影(3 档):
```
rgba(0, 0, 0, 0.04) — 轻投影(卡片)
rgba(0, 0, 0, 0.08) — 中投影(浮层)
rgba(0, 0, 0, 0.12) — 重投影(弹窗)
```
### 2.5 硬编码颜色迁移对照
| 现有硬编码值 | 迁移到 |
|-------------|--------|
| `#111`, `#1A1A1A`, `#222` | `$color-text-primary` |
| `#333` | `$color-text-regular` |
| `#666` | `$color-text-secondary` |
| `#999`, `#888888` | `$color-text-placeholder` |
| `#cccccc`, `#BBBBBB`, `#dadbde` | `$color-text-disabled` |
| `#e5e7eb`, `#E2E8F0` | `$color-border` |
| `#f7f8fa`, `#f5f5f5`, `#f7f7f7`, `#FAFAFA`, `#F8FAFC` | `$color-bg-page` |
| `#fff`, `#ffffff`, `#FFFFFF` | `$color-bg-card` |
| `#e53935`, `#EF4444`, `#f53f3f`, `#E74C3C` | `$uv-error` 或 `$color-price` |
| `#3c9cff`, `#0052FF`, `#4A90D9`, `#4D7CFF` | `$uv-primary` |
| `#5ac725`, `#10B981`, `#00b42a` | `$uv-success` 或 `$color-status-success` |
| `#f9ae3d`, `#F59E0B`, `#ff9500`, `#ff7d00` | `$uv-warning` 或 `$color-status-pending` |
---
## 3. 字体族 & 排版
### 3.1 字体声明
```scss
$font-family-display: 'SourceHanSerifCN', '思源宋体', 'STSong', serif;
$font-family-body: -apple-system, 'PingFang SC', 'Helvetica Neue', sans-serif;
```
- `$font-family-display`:标题、品牌名、卡名等装饰性文字
- `$font-family-body`:正文、按钮、表单、说明文字(默认字体,加载快)
全局在 `App.vue` 中声明 `font-family: $font-family-body`,仅需要装饰性的元素单独指定 `$font-family-display`。
### 3.2 字号场景对照
| 场景 | 变量 | 值 |
|------|------|-----|
| 页面大标题 | `$font-size-2xl` | 44rpx |
| 区域标题 | `$font-size-xl` | 36rpx |
| 卡片标题 | `$font-size-lg` | 32rpx |
| 正文/按钮 | `$font-size-base` | 28rpx |
| 次要说明 | `$font-size-sm` | 24rpx |
| 辅助标注/标签 | `$font-size-xs` | 20rpx |
| 极小文字/角标 | `$font-size-xxs` | 18rpx |
---
## 4. 组件规范
### 4.1 按钮
从 9 种高度收敛到 3 种:
| 尺寸 | 高度 | 字号 | 场景 |
|------|------|------|------|
| sm | 64rpx | `$font-size-sm` (24rpx) | 列表内操作、小按钮 |
| md | 80rpx | `$font-size-base` (28rpx) | 页面内主要操作 |
| lg | 96rpx | `$font-size-lg` (32rpx) | 底部固定栏、支付按钮 |
- 统一圆角 `$radius-full`(胶囊形)
- 主操作:`$uv-primary` 填充色 + 白色文字
- 次要操作:描边样式(border `$uv-primary` + `$uv-primary` 文字)
- 按压态:`transform: scale(0.95)` + `transition: transform $transition-fast`
### 4.2 卡片
```scss
圆角:$radius-lg (24rpx)
投影:$shadow-base → 0 4rpx 16rpx rgba(0, 0, 0, 0.04)
内边距:$spacing-4 (32rpx)
卡片间距:$spacing-3 (24rpx)
背景:$color-bg-card (#ffffff)
```
### 4.3 箭头/Chevron
统一一种实现:
- 尺寸:12rpx × 12rpx
- border-width:2rpx
- 颜色:`$color-text-placeholder`
- 右箭头:`transform: rotate(45deg)`
- 下箭头:`transform: rotate(135deg)`
### 4.4 间距规则
只允许使用以下变量,禁止硬编码间距值:
| 变量 | 值 | 场景 |
|------|-----|------|
| `$spacing-1` | 8rpx | 图标与文字间隙 |
| `$spacing-2` | 16rpx | 紧凑元素间距 |
| `$spacing-3` | 24rpx | 列表项间距、卡片间距 |
| `$spacing-4` | 32rpx | 区域内容边距 |
| `$spacing-5` | 40rpx | 区块间距 |
| `$spacing-6` | 48rpx | 页面顶部/底部安全区 |
| `$spacing-8` | 64rpx | 大区块分隔 |
### 4.5 圆角规则
| 变量 | 值 | 场景 |
|------|-----|------|
| `$radius-sm` | 8rpx | 标签、小徽标 |
| `$radius-md` | 16rpx | 输入框、小卡片 |
| `$radius-lg` | 24rpx | 卡片、弹窗 |
| `$radius-full` | 9999rpx | 胶囊按钮、圆形头像 |
### 4.6 投影规则
| 变量 | 值 | 场景 |
|------|-----|------|
| `$shadow-sm` | `0 2rpx 8rpx rgba(0,0,0,0.04)` | 轻投影 |
| `$shadow-base` | `0 4rpx 16rpx rgba(0,0,0,0.04)` | 卡片默认 |
| `$shadow-lg` | `0 8rpx 32rpx rgba(0,0,0,0.08)` | 浮层、弹窗 |
### 4.7 过渡动画
| 变量 | 值 | 场景 |
|------|-----|------|
| `$transition-fast` | `150ms ease` | 按压、hover |
| `$transition-base` | `250ms ease` | 展开/收起、淡入淡出 |
禁止使用 `0.15s ease`、`0.2s ease` 等硬编码值。
---
## 5. 类型定义 & Mock 数据
### 5.1 文件结构
```
src/
├── api/
│ ├── types.ts # 所有业务类型(唯一来源)
│ ├── request.ts # HTTP 封装(已有,不动)
│ ├── scenic.ts # 景区接口
│ ├── card.ts # 年卡接口
│ ├── order.ts # 订单接口
│ ├── user.ts # 用户接口(已有,补充类型)
│ └── reserve.ts # 预约接口
├── mock/
│ ├── index.ts # 统一导出 + USE_MOCK 开关
│ ├── scenic.ts # 景区 mock
│ ├── card.ts # 年卡 mock
│ ├── order.ts # 订单 mock
│ ├── reserve.ts # 预约记录 mock
│ ├── notice.ts # 公告 mock
│ └── images.ts # 占位图片 URL 常量
```
### 5.2 types.ts 业务类型
```typescript
interface Scenic {
id: number
name: string
cover: string
address: string
lat: number
lng: number
openTime: string
description: string
ticketPrice: number
freeTimes: number
}
interface ScenicSession {
id: number
scenicId: number
date: string
startTime: string
endTime: string
stock: number
remainStock: number
}
interface Card {
id: number
name: string
price: number
originalPrice: number
cover: string
benefits: string
scenicIds: number[]
}
interface Order {
id: number
cardId: number
cardName: string
amount: number
status: 'pending' | 'paid' | 'cancelled' | 'closed'
payTime: string | null
createTime: string
}
interface OrderDetail extends Order {
orderNo: string
userId: number
userName: string
phone: string
payMethod: string
}
interface ReserveRecord {
id: number
scenicId: number
scenicName: string
date: string
session: string
status: 'pending' | 'confirmed' | 'cancelled' | 'used'
}
interface Notice {
id: number
title: string
content: string
publishTime: string
isTop: boolean
}
interface QuickAction {
id: number
icon: string
label: string
path: string
}
interface Announcement {
id: number
title: string
content: string
}
```
### 5.3 Mock 切换机制
```typescript
// mock/index.ts
export const USE_MOCK = import.meta.env.VITE_USE_MOCK === 'true'
// api/scenic.ts
import { USE_MOCK } from '@/mock'
import { mockScenicList } from '@/mock/scenic'
import type { Scenic[] } from './types'
export function getScenicList(): Promise<Scenic[]> {
if (USE_MOCK) return Promise.resolve(mockScenicList)
return request.get('/mp-api/scenic/list')
}
```
- `.env` 文件中 `VITE_USE_MOCK=true` 控制开关
- 对接 API 时改为 `false`,页面代码零改动
### 5.4 占位图片规范
统一收到 `mock/images.ts`:
```typescript
export const IMG_SCENIC_COVER = 'https://images.unsplash.com/...'
export const IMG_AVATAR = 'https://...'
export const IMG_CARD_BANNER = 'https://...'
// ...
```
对接 API 后这些常量直接废弃,不影响页面代码。
---
## 6. 执行计划
### 第1步:基础设施(不改任何页面)
| 序号 | 任务 | 验证方式 |
|------|------|---------|
| 1.1 | 清理 `uni.scss`:补齐 `$font-size-xxs`、语义色、行高、字体族变量 | 编译通过 |
| 1.2 | 处理 `_tokens.scss`:冲突值改为引用 `uni.scss` 或删除 | 编译通过,无变量冲突 |
| 1.3 | `api/types.ts` 补全所有业务 interface | `pnpm run type-check` 通过 |
| 1.4 | 创建 `src/mock/` 目录,迁移所有硬编码数据,实现 `USE_MOCK` 开关 | mock 数据可正常 import |
| 1.5 | `App.vue` 全局字体声明统一为 `$font-family-body` | 构建预览正常 |
| 1.6 | 删除 14 个页面中的局部 `$font`、`$primary`、`$text-main` 等变量声明 | 编译通过 |
### 第2步:核心链路迁移(4 个页面)
迁移页面:`首页 → 年卡列表 → 年卡详情 → 购买/订单`
每页迁移清单:
1. 删除局部 SCSS 变量,改用全局 token
2. 硬编码颜色值 → 全局变量
3. 硬编码字号 → 对应 `$font-size-*` 变量
4. 硬编码间距 → 对应 `$spacing-*` 变量
5. 模板中硬编码数据 → import mock 数据
6. 组件样式对齐规范(按钮高度、卡片圆角、箭头样式)
验证:`pnpm run dev:mp-weixin` 构建 + 微信开发者工具预览,确认视觉无回退。
### 第3步:剩余页面迁移(~27 个页面)
- 同样 6 项清单逐页执行
- 分包页面(`packages/user/pages/card/`)6 个文件同步处理
- 纯 CSS 组件(`card-item.vue`、`empty-state.vue`、`McGlass.vue`)改为 SCSS + 全局变量
---
## 7. 迁移规则(禁止项)
- ❌ 禁止在 `<style>` 中定义 `$font`、`$primary`、`$text-main` 等局部变量
- ❌ 禁止使用 `font-weight: bold` 关键字,必须用 `$font-weight-bold`
- ❌ 禁止硬编码颜色值(如 `#333`、`#666`),必须用全局变量
- ❌ 禁止硬编码间距值(如 `padding: 20rpx`),必须用 `$spacing-*`
- ❌ 禁止在页面中硬编码业务数据,必须从 `mock/` 或 `api/` import
- ❌ 禁止使用 `0.15s ease` 等硬编码过渡值,必须用 `$transition-*`