Files
pingxiangcard/docs/superpowers/specs/2026-06-26-ui-ux-standardization-design.md
T
jacobxu666andClaude 6e27b449bb docs: update UI/UX spec per review feedback
- Add $font-size-4xl (64rpx) to font scale
- Fix line-height-tight to match uni.scss (1.2)
- Use existing $uv-* naming for neutral colors, add $uv-placeholder-color
- Clarify $font-family-body change (serif → system fonts for body)
- Split spacing into recommended vs reserved tiers
- Fix $radius-md → $radius-base, add $radius-xl
- Fix shadow values to match uni.scss rgba(15,23,42,...) base
- Add PageResult<T> pagination type
- Add MockStore for interactive mock data
- Add image resource specs (sizes, formats)
- Add accessibility guidelines
- Fix page count (27 → 21)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 09:12:16 +08:00

18 KiB
Raw Blame History

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 字号体系

10 级字号,消灭所有游离值:

变量 值 场景
$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-4xl 64rpx 促销大数字(介于 3xl 和 display 之间)
$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.2 标题
$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 中性色(文字 & 背景)

沿用 uni.scss 现有 $uv-* 命名,补充缺失值,替代现有 55+ 种硬编码灰值:

主变量(uni.scss 已有,保持不变):

变量 值 用途
$uv-main-color #111111 标题、重要正文
$uv-content-color #333333 正文
$uv-tips-color #666666 次要说明
$uv-placeholder-color #999999 占位符(新增,uni.scss 缺失)
$uv-disabled-color #c8c9cc 不可操作文字
$uv-bg-color #f3f4f6 页面背景
$uv-bg-color-hover #f1f1f1 按压态背景
$uv-border-color #eeeeee 分割线、边框
$color-card #ffffff 卡片/模块背景(uni.scss 已有)

语义别名(向后兼容,指向上述主变量):

别名 指向 说明
$color-foreground $uv-main-color uni.scss 已有
$color-muted-foreground $uv-content-color uni.scss 已有
$color-background $uv-bg-color uni.scss 已有
$color-border $uv-border-color uni.scss 已有

迁移时优先使用主变量($uv-*),语义别名仅在已有代码中保留,新代码禁止使用别名。

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 $uv-main-color
#333 $uv-content-color
#666 $uv-tips-color
#999, #888888 $uv-placeholder-color
#cccccc, #BBBBBB, #dadbde $uv-disabled-color / $uv-light-color
#e5e7eb, #E2E8F0 $uv-border-color
#f7f8fa, #f5f5f5, #f7f7f7, #FAFAFA, #F8FAFC $uv-bg-color
#fff, #ffffff, #FFFFFF $color-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 字体声明

// uni.scss 现状(display 和 body 都用了思源宋体):
$font-family-display: 'SourceHanSerifCN', '思源宋体', serif;        // ✅ 保持不变
$font-family-body: 'SourceHanSerifCN', '思源宋体', -apple-system, 'PingFang SC', sans-serif;  // ⚠️ 需修改

// 修改后:
$font-family-display: 'SourceHanSerifCN', '思源宋体', serif;        // 标题装饰字体
$font-family-body: -apple-system, 'PingFang SC', 'Helvetica Neue', sans-serif;  // 正文系统字体

为什么要改 $font-family-body:uni.scss 现状把思源宋体(衬线体)用于正文,导致所有文字都是衬线风格,阅读体验偏重且加载慢。改为系统字体后,正文干净利落,仅标题/品牌名保留思源宋体的文化感。

  • $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 卡片

圆角:$radius-lg (24rpx)
投影:$shadow-base → 0 4rpx 16rpx rgba(15, 23, 42, 0.08)
内边距:$spacing-4 (32rpx)
卡片间距:$spacing-3 (24rpx)
背景:$color-card (#ffffff)

4.3 箭头/Chevron

统一一种实现:

  • 尺寸:12rpx × 12rpx
  • border-width:2rpx
  • 颜色:$uv-placeholder-color
  • 右箭头:transform: rotate(45deg)
  • 下箭头:transform: rotate(135deg)

4.4 间距规则

推荐使用(日常开发只用这 7 个):

变量 值 场景
$spacing-1 8rpx 图标与文字间隙
$spacing-2 16rpx 紧凑元素间距
$spacing-3 24rpx 列表项间距、卡片间距
$spacing-4 32rpx 区域内容边距
$spacing-5 40rpx 区块间距
$spacing-6 48rpx 页面顶部/底部安全区
$spacing-8 64rpx 大区块分隔

保留但少用(uni.scss 已有,仅特殊场景允许):

变量 值 场景
$spacing-0 0 重置间距
$spacing-10 80rpx 超大区块分隔
$spacing-12 96rpx 页面级留白
$spacing-16 128rpx 极少使用
$spacing-20 160rpx 极少使用

禁止硬编码间距值(如 padding: 20rpx)。

4.5 圆角规则

变量 值 场景
$radius-sm 8rpx 标签、小徽标
$radius-base 16rpx 输入框、小卡片
$radius-lg 24rpx 卡片、弹窗
$radius-xl 32rpx 大面板(少用)
$radius-full 9999rpx 胶囊按钮、圆形头像

4.6 投影规则

以 uni.scss 现有值为准(使用 rgba(15, 23, 42, ...) 冷灰色基底):

变量 值 场景
$shadow-sm 0 2rpx 8rpx rgba(15,23,42,0.04) 轻投影
$shadow-base 0 4rpx 16rpx rgba(15,23,42,0.08) 卡片默认
$shadow-lg 0 8rpx 32rpx rgba(15,23,42,0.12) 浮层、弹窗
$shadow-xl 0 16rpx 48rpx rgba(15,23,42,0.16) 大弹窗/浮层

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 业务类型

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
}

// 分页通用
interface PageResult<T> {
  list: T[]
  total: number
  page: number
  pageSize: number
}

// 分页请求参数
interface PageQuery {
  page: number
  pageSize: number
}

5.3 Mock 切换机制

// 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 状态管理

对于需要交互的 mock 数据(如订单状态变更、预约操作),提供简单的内存状态管理:

// mock/store.ts
import { ref } from 'vue'
import type { Order, ReserveRecord } from '@/api/types'
import { mockOrders } from './order'
import { mockReserves } from './reserve'

export const orderStore = ref<Order[]>([...mockOrders])
export const reserveStore = ref<ReserveRecord[]>([...mockReserves])

export function updateOrderStatus(orderId: number, status: Order['status']) {
  const order = orderStore.value.find(o => o.id === orderId)
  if (order) order.status = status
}

export function addReserve(record: Omit<ReserveRecord, 'id'>) {
  const id = Math.max(...reserveStore.value.map(r => r.id)) + 1
  reserveStore.value.push({ ...record, id })
}

仅 mock 模式使用。对接 API 后这些操作改为 API 调用,页面逻辑不变。

5.5 占位图片规范

统一收到 mock/images.ts:

export const IMG_SCENIC_COVER = 'https://images.unsplash.com/...'
export const IMG_AVATAR = 'https://...'
export const IMG_CARD_BANNER = 'https://...'
// ...

对接 API 后这些常量直接废弃,不影响页面代码。


6. 图片资源规范

类型 尺寸 格式 说明
TabBar 图标 81px × 81px PNG 无透明通道,选中/未选中各一套
页面分享图 500px × 400px JPEG < 128KB
年卡封面 750px × 420px JPEG < 200KB
景区封面 750px × 420px JPEG < 200KB,统一裁切比例 16:9
背景图 优先 CSS 渐变 — 避免大图,减小包体积
  • 网络图片必须在微信公众平台配置 downloadFile 合法域名
  • 本地图片放 src/static/ 目录,构建时自动打包
  • 占位图统一使用 mock/images.ts 中的常量

7. 无障碍规范

  • 所有可交互元素(<view @click>、<button>)必须设置 role 或 aria-role 属性
  • <image> 标签必须提供有意义的描述(小程序用 aria-label 或父容器 aria-role)
  • 颜色对比度至少 4.5:1(WCAG AA 标准),正文文字不得仅靠颜色传达信息
  • 错误状态必须配合图标 + 文字,不能只用红色标识
  • 表单输入框必须关联 <label> 或使用 aria-label

8. 执行计划

第1步:基础设施(不改任何页面)

序号 任务 验证方式
1.1 清理 uni.scss:补齐 $font-size-xxs、$font-size-4xl、$uv-placeholder-color、语义色、修改 $font-family-body 为系统字体 编译通过
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步:剩余页面迁移(~21 个页面)

  • 同样 6 项清单逐页执行
  • 分包页面(packages/user/pages/card/)6 个文件同步处理
  • 纯 CSS 组件(card-item.vue、empty-state.vue、McGlass.vue)改为 SCSS + 全局变量

9. 迁移规则(禁止项)

  • ❌ 禁止在 <style> 中定义 $font、$primary、$text-main 等局部变量
  • ❌ 禁止使用 font-weight: bold 关键字,必须用 $font-weight-bold
  • ❌ 禁止硬编码颜色值(如 #333、#666),必须用全局变量
  • ❌ 禁止硬编码间距值(如 padding: 20rpx),必须用 $spacing-*
  • ❌ 禁止在页面中硬编码业务数据,必须从 mock/ 或 api/ import
  • ❌ 禁止使用 0.15s ease 等硬编码过渡值,必须用 $transition-*