Files
pingxiangcard/docs/superpowers/plans/2026-06-26-ui-ux-standardization.md
jacobxu666andClaude e03ac06e02 docs: add UI/UX standardization implementation plan
17 tasks across 3 phases:
- Phase 1 (Tasks 1-8): Infrastructure (tokens, types, mock, cleanup)
- Phase 2 (Tasks 9-12): Core pages (index, card list/detail, buy)
- Phase 3 (Tasks 13-17): Remaining pages and components

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

36 KiB
Raw Permalink Blame History

UI/UX 标准化实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 统一所有页面的设计 token、类型定义和 mock 数据管理,为后端 API 对接做好准备。

Architecture: 分三步执行。第1步建立基础设施(token 统一 + 类型 + mock 层),不改任何页面视觉。第2步迁移核心链路(首页→年卡→购买→订单)。第3步迁移剩余页面和组件。

Tech Stack: uni-app 3.0 + Vue 3 + TypeScript + Pinia + SCSS

Global Constraints

  • 颜色体系统一使用 uni.scss 的 $uv-* 变量($uv-primary: #3c9cff)
  • 字体族:标题 $font-family-display(思源宋体),正文 $font-family-body(系统字体)
  • 禁止在页面 <style> 中定义局部 $font、$primary、$text-main 等变量
  • 禁止 font-weight: bold 关键字,必须用 $font-weight-bold
  • 禁止硬编码颜色值(如 #333),必须用全局变量
  • 禁止硬编码间距值(如 padding: 20rpx),必须用 $spacing-*
  • 禁止在页面中硬编码业务数据,必须从 mock/ 或 api/ import
  • 验证方式:pnpm run dev:mp-weixin 构建通过 + pnpm run type-check 通过

File Structure

新建文件

src/mock/index.ts            — USE_MOCK 开关
src/mock/images.ts           — 占位图片 URL 常量
src/mock/scenic.ts           — 景区 mock(42 条)
src/mock/scenic-detail.ts    — 景区详情 mock(8 条)
src/mock/order.ts            — 订单 mock(6 条)
src/mock/reserve.ts          — 预约记录 mock(6 条)
src/mock/notice.ts           — 公告 mock(5 条)
src/mock/store.ts            — Mock 内存状态管理
src/api/scenic.ts            — 景区 API 函数
src/api/order.ts             — 订单 API 函数
src/api/reserve.ts           — 预约 API 函数

修改文件

src/uni.scss                 — 新增缺失变量
src/styles/_tokens.scss      — 对齐 uni.scss 值
src/api/types.ts             — 补全业务类型
src/App.vue                  — 全局样式
.env.local / .env.dev / .env.uat — 新增 VITE_USE_MOCK
9 个页面                      — 删除局部 SCSS 变量
~25 个页面/组件               — 迁移硬编码值

Phase 1: 基础设施(不改页面视觉)


Task 1: 更新 uni.scss — 补齐缺失变量

Files:

  • Modify: src/uni.scss:71-83(字号区域)
  • Modify: src/uni.scss:51($uv-tips-color 后插入 $uv-placeholder-color)
  • Modify: src/uni.scss:65-67(渐变后插入语义色)

Interfaces:

  • Produces: $font-size-xxs, $font-size-4xl, $uv-placeholder-color, $color-price, $color-price-highlight, $color-status-pending, $color-status-success, $color-status-cancel

  • Step 1: 新增 $font-size-xxs 和 $font-size-4xl

在 src/uni.scss 的字号区域(第 75 行 $font-size-xs 之前),插入:

$font-size-xxs: 18rpx;

在 $font-size-3xl: 56rpx;(第 82 行)之后、$font-size-display: 72rpx; 之前,插入:

$font-size-4xl: 64rpx;
  • Step 2: 新增 $uv-placeholder-color

在 $uv-tips-color: #666666;(第 51 行)之后插入:

$uv-placeholder-color: #999999;
  • Step 3: 新增语义色变量

在 $gradient-accent 定义(第 67 行)之后插入:

// Semantic colors (价格/状态)
$color-price: $uv-error;
$color-price-highlight: #FFD600;
$color-status-pending: #ff9500;
$color-status-success: #00b42a;
$color-status-cancel: #86909c;
  • Step 4: 修改 $font-family-body

将第 72 行:

$font-family-body: 'SourceHanSerifCN', '思源宋体', -apple-system, 'PingFang SC', sans-serif;

改为:

$font-family-body: -apple-system, 'PingFang SC', 'Helvetica Neue', sans-serif;
  • Step 5: 验证编译

Run: pnpm run dev:mp-weixin Expected: 构建成功,无 SCSS 编译错误

  • Step 6: Commit
git add src/uni.scss
git commit -m "feat(tokens): add missing variables to uni.scss

- \$font-size-xxs (18rpx), \$font-size-4xl (64rpx)
- \$uv-placeholder-color (#999999)
- Semantic colors: \$color-price, \$color-status-*
- Change \$font-family-body to system fonts"

Task 2: 对齐 _tokens.scss 与 uni.scss

Files:

  • Modify: src/styles/_tokens.scss(全文替换变量值)

Interfaces:

  • Consumes: Task 1 产生的所有 uni.scss 变量

  • Produces: 与 uni.scss 值一致的 _tokens.scss

  • Step 1: 替换 _tokens.scss 全文

将 src/styles/_tokens.scss 全文替换为以下内容(所有颜色值对齐 uni.scss,字体对齐思源宋体系):

// ============================================
// Design Tokens — Aligned with uni.scss
// ============================================
// ⚠️ 所有值必须与 src/uni.scss 保持一致
// uni.scss 是 source of truth,此文件仅为 main.scss 的兼容层

// === COLORS ===

// Brand accent
$color-accent: #3c9cff;
$color-accent-secondary: #398ade;
$color-accent-foreground: #FFFFFF;

// Semantic
$color-success: #5ac725;
$color-warning: #f9ae3d;
$color-error: #e53935;

// Neutrals
$color-background: #f3f4f6;
$color-foreground: #111111;
$color-muted: #f4f4f5;
$color-muted-foreground: #333333;
$color-border: #eeeeee;
$color-card: #FFFFFF;
$color-ring: #3c9cff;

// Semantic (价格/状态)
$color-price: #e53935;
$color-price-highlight: #FFD600;
$color-status-pending: #ff9500;
$color-status-success: #00b42a;
$color-status-cancel: #86909c;

// Gradients
$gradient-accent: linear-gradient(135deg, #{$color-accent}, #{$color-accent-secondary});

// === TYPOGRAPHY ===

$font-family-display: 'SourceHanSerifCN', '思源宋体', serif;
$font-family-body: -apple-system, 'PingFang SC', 'Helvetica Neue', sans-serif;
$font-family-mono: 'Menlo', 'Monaco', 'Courier New', monospace;

$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-title: 38rpx;
$font-size-2xl: 44rpx;
$font-size-3xl: 56rpx;
$font-size-4xl: 64rpx;
$font-size-display: 72rpx;

$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-bold: 700;

$line-height-tight: 1.2;
$line-height-normal: 1.5;
$line-height-relaxed: 1.75;

// === SPACING (rpx) ===

$spacing-0: 0;
$spacing-1: 8rpx;
$spacing-2: 16rpx;
$spacing-3: 24rpx;
$spacing-4: 32rpx;
$spacing-5: 40rpx;
$spacing-6: 48rpx;
$spacing-8: 64rpx;
$spacing-10: 80rpx;
$spacing-12: 96rpx;
$spacing-16: 128rpx;
$spacing-20: 160rpx;

// === BORDER RADIUS ===

$radius-sm: 8rpx;
$radius-base: 16rpx;
$radius-lg: 24rpx;
$radius-xl: 32rpx;
$radius-2xl: 48rpx;
$radius-full: 9999rpx;

// === SHADOWS ===

$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);
$shadow-accent: 0 8rpx 24rpx rgba(60, 156, 255, 0.25);

// === TRANSITIONS ===

$transition-fast: 150ms ease;
$transition-base: 250ms ease;
$transition-slow: 400ms ease;

// === Z-INDEX ===

$z-base: 0;
$z-dropdown: 100;
$z-sticky: 200;
$z-modal: 300;
$z-toast: 400;

// === BORDER ===

$border-width: 1rpx;

// ============================================
// Legacy $uni-* compatibility (for uni-ui)
// ============================================

$uni-color-primary: $color-accent;
$uni-color-success: $color-success;
$uni-color-warning: $color-warning;
$uni-color-error: $color-error;
$uni-text-color: $color-foreground;
$uni-text-color-inverse: #FFFFFF;
$uni-text-color-grey: $color-muted-foreground;
$uni-text-color-placeholder: #999999;
$uni-text-color-disable: #c8c9cc;
$uni-bg-color: $color-background;
$uni-bg-color-grey: $color-muted;
$uni-bg-color-hover: #f1f1f1;
$uni-bg-color-mask: rgba(0, 0, 0, 0.4);
$uni-border-color: $color-border;
$uni-font-size-sm: $font-size-sm;
$uni-font-size-base: $font-size-base;
$uni-font-size-lg: $font-size-lg;
$uni-font-size-title: 40rpx;
$uni-font-size-subtitle: $font-size-xl;
$uni-font-size-paragraph: $font-size-base;
$uni-img-size-sm: 40rpx;
$uni-img-size-base: 52rpx;
$uni-img-size-lg: 80rpx;
$uni-border-radius-sm: $radius-sm;
$uni-border-radius-base: $radius-base;
$uni-border-radius-lg: $radius-lg;
$uni-border-radius-circle: 50%;
$uni-spacing-row-sm: 10rpx;
$uni-spacing-row-base: 20rpx;
$uni-spacing-row-lg: 30rpx;
$uni-spacing-col-sm: 8rpx;
$uni-spacing-col-base: 16rpx;
$uni-spacing-col-lg: 24rpx;
$uni-opacity-disabled: 0.3;
$uni-color-title: $color-foreground;
$uni-color-subtitle: $color-muted-foreground;
$uni-color-paragraph: $color-muted-foreground;
  • Step 2: 验证编译

Run: pnpm run dev:mp-weixin Expected: 构建成功,main.scss 中的 $color-background、$color-foreground、$font-family-body 使用新值

  • Step 3: Commit
git add src/styles/_tokens.scss
git commit -m "refactor(tokens): align _tokens.scss with uni.scss values

All color, typography, and spacing values now match uni.scss.
Added semantic colors and \$font-size-xxs/\$font-size-4xl."

Task 3: 扩展 types.ts 业务类型

Files:

  • Modify: src/api/types.ts

Interfaces:

  • Produces: ScenicSession, ReserveRecord, Notice, QuickAction, Announcement, PageResult<T>, PageQuery, OrderDetail, FaqItem

  • Step 1: 在 types.ts 末尾追加新类型

在 src/api/types.ts 文件末尾(MockPayResult 接口之后)追加:

/** 景区场次 */
export interface ScenicSession {
  id: number
  scenicId: number
  date: string
  startTime: string
  endTime: string
  stock: number
  remainStock: number
}

/** 预约记录 */
export interface ReserveRecord {
  id: number
  scenicId: number
  scenicName: string
  date: string
  session: string
  status: 'pending' | 'confirmed' | 'cancelled' | 'used'
  userName?: string
  maskedId?: string
  cardType?: string
  cardNo?: string
  applyTime?: string
  cancelTime?: string
  verifyTime?: string
  verifyPhoto?: string
}

/** 公告 */
export interface Notice {
  id: number
  title: string
  content: string
  body?: string
  publishTime: string
  isTop: boolean
}

/** 首页快捷入口 */
export interface QuickAction {
  id: number
  icon: string
  label: string
  path: string
}

/** 首页弹窗公告 */
export interface Announcement {
  id: number
  type?: string
  typeLabel?: string
  title: string
  date?: string
  content: string
  body?: string
}

/** 订单详情 */
export interface OrderDetail extends OrderInfo {
  orderNo?: string
  payTime?: string
  payChannel?: string
  redeemCode?: string
  activatedTime?: string
  refundedTime?: string
  originalPrice?: number
  cardType?: string
}

/** FAQ 条目 */
export interface FaqItem {
  q: string
  a: Array<{ text: string; highlight?: boolean; copy?: string }>
}

/** 分页结果 */
export interface PageResult<T> {
  list: T[]
  total: number
  page: number
  pageSize: number
}

/** 分页请求参数 */
export interface PageQuery {
  page: number
  pageSize: number
}
  • Step 2: 验证类型检查

Run: pnpm run type-check Expected: 无类型错误

  • Step 3: Commit
git add src/api/types.ts
git commit -m "feat(types): add ScenicSession, ReserveRecord, Notice, FaqItem, PageResult types"

Task 4: 创建 Mock 基础设施

Files:

  • Create: src/mock/index.ts
  • Create: src/mock/images.ts
  • Create: src/mock/store.ts
  • Modify: .env.local, .env.dev, .env.uat

Interfaces:

  • Produces: USE_MOCK 常量、占位图片常量、orderStore/reserveStore 响应式数据

  • Step 1: 添加环境变量

在 .env.local、.env.dev、.env.uat 末尾各追加一行:

VITE_USE_MOCK=true
  • Step 2: 创建 src/mock/index.ts
/** Mock 数据开关 — 由 .env 文件的 VITE_USE_MOCK 控制 */
export const USE_MOCK = import.meta.env.VITE_USE_MOCK === 'true'
  • Step 3: 创建 src/mock/images.ts

从现有页面中收集所有 Unsplash URL,集中管理。实现时从 src/pages/index/index.vue、src/pages/scenic-list/ 等文件提取所有图片 URL,按用途命名:

// 景区封面占位图
export const IMG_SCENIC_1 = 'https://images.unsplash.com/...'  // 从 index.vue hotSpots 提取
export const IMG_SCENIC_2 = 'https://images.unsplash.com/...'
// ... 每个现有 Unsplash URL 对应一个常量

// 头像占位图
export const IMG_AVATAR_DEFAULT = 'https://...'

// 年卡 Banner
export const IMG_CARD_BANNER = 'https://...'

// 通用占位
export const IMG_PLACEHOLDER = 'https://...'

实现时从各页面 <script> 中提取实际的 Unsplash URL,不要发明新 URL。

  • Step 4: 创建 src/mock/store.ts
import { ref } from 'vue'
import type { OrderInfo, ReserveRecord } from '@/api/types'
import { mockOrders } from './order'
import { mockReserves } from './reserve'

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

export function updateOrderStatus(orderId: string, status: OrderInfo['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), 0) + 1
  reserveStore.value.push({ ...record, id })
}
  • Step 5: 验证

Run: pnpm run type-check Expected: mock 文件无类型错误

  • Step 6: Commit
git add src/mock/ .env.local .env.dev .env.uat
git commit -m "feat(mock): create mock infrastructure with USE_MOCK switch"

Task 5: 迁移 Mock 数据到 mock/ 目录

Files:

  • Create: src/mock/scenic.ts — 从 scenic-list.vue 提取 42 条景区数据
  • Create: src/mock/scenic-detail.ts — 从 scenic-detail.vue 提取 8 条详情
  • Create: src/mock/order.ts — 从 order-list.vue 提取 6 条订单
  • Create: src/mock/reserve.ts — 从 reserve-list.vue 提取 6 条预约
  • Create: src/mock/notice.ts — 从 notice-list.vue 提取 5 条公告

Interfaces:

  • Consumes: Task 3 的 Scenic, OrderInfo, ReserveRecord, Notice 类型

  • Produces: mockScenicList, mockScenicDetails, mockOrders, mockReserves, mockNotices

  • Step 1: 创建 src/mock/scenic.ts

从 src/pages/scenic-list/scenic-list.vue 第 247-337 行提取 scenicList 数组和筛选选项(regionOptions、levelOptions、cardTypeOptions),转换为符合 ScenicSpot 类型。

类型对齐注意:现有 ScenicSpot 的 id 是 string 类型。如果 scenic-list.vue 中使用数字 id,在 mock 中转为 String(id)。保持 ScenicSpot 接口不变,后续对接 API 时再统一调整。

import type { ScenicSpot } from '@/api/types'
import { IMG_SCENIC_1 /* ... */ } from './images'

export const mockScenicList: ScenicSpot[] = [
  // 从 scenic-list.vue 提取的 42 条数据
  // 保持原始字段值,image 字段使用 images.ts 常量
]

export const regionOptions = [
  // 从 scenic-list.vue 提取
]

export const levelOptions = [
  // 从 scenic-list.vue 提取
]

实现时逐条复制 scenic-list.vue 中的数据,确保 42 条完整。

  • Step 2: 创建 src/mock/scenic-detail.ts

从 src/pages/scenic-detail/scenic-detail.vue 第 172-181 行提取 scenicData 数组(8 条详情)。

  • Step 3: 创建 src/mock/order.ts

从 src/pages/order/order-list.vue 第 151-158 行提取 orders 数组(6 条订单)。

import type { OrderDetail } from '@/api/types'

export const mockOrders: OrderDetail[] = [
  // 从 order-list.vue 提取的 6 条数据
]
  • Step 4: 创建 src/mock/reserve.ts

从 src/pages/reserve/reserve-list.vue 第 228-235 行提取 records 数组(6 条预约记录)。

  • Step 5: 创建 src/mock/notice.ts

从 src/pages/notice-list/notice-list.vue 第 47-53 行提取 notices 数组(5 条公告)。

  • Step 6: 验证类型检查

Run: pnpm run type-check Expected: 所有 mock 文件类型正确

  • Step 7: Commit
git add src/mock/
git commit -m "feat(mock): extract all hardcoded data into mock/ modules

- scenic: 42 items from scenic-list.vue
- scenic-detail: 8 items from scenic-detail.vue
- order: 6 items from order-list.vue
- reserve: 6 items from reserve-list.vue
- notice: 5 items from notice-list.vue"

Task 6: 创建 API 函数层

Files:

  • Create: src/api/scenic.ts
  • Create: src/api/order.ts
  • Create: src/api/reserve.ts

Interfaces:

  • Consumes: Task 4 的 USE_MOCK、Task 5 的 mock 数据、Task 3 的类型

  • Produces: getScenicList(), getScenicDetail(), getOrderList(), getOrderDetail(), getReserveList() 等函数

  • Step 1: 创建 src/api/scenic.ts

import { USE_MOCK } from '@/mock'
import { mockScenicList } from '@/mock/scenic'
import { mockScenicDetails } from '@/mock/scenic-detail'
import type { ScenicSpot } from './types'
import { get } from './request'

/** 获取景区列表 */
export function getScenicList(): Promise<ScenicSpot[]> {
  if (USE_MOCK) return Promise.resolve(mockScenicList)
  return get<ScenicSpot[]>('/mp-api/scenic/list')
}

/** 获取景区详情 */
export function getScenicDetail(id: string): Promise<ScenicSpot | undefined> {
  if (USE_MOCK) {
    return Promise.resolve(mockScenicDetails.find(s => s.id === id))
  }
  return get<ScenicSpot>(`/mp-api/scenic/detail?id=${id}`)
}
  • Step 2: 创建 src/api/order.ts
import { USE_MOCK } from '@/mock'
import { mockOrders } from '@/mock/order'
import { orderStore } from '@/mock/store'
import type { OrderDetail, OrderInfo, CreateOrderParams, MockPayResult } from './types'
import { get, post } from './request'

/** 获取订单列表 */
export function getOrderList(): Promise<OrderInfo[]> {
  if (USE_MOCK) return Promise.resolve(orderStore.value)
  return get<OrderInfo[]>('/mp-api/order/list')
}

/** 获取订单详情 */
export function getOrderDetail(id: string): Promise<OrderDetail | undefined> {
  if (USE_MOCK) {
    return Promise.resolve(mockOrders.find(o => o.id === id))
  }
  return get<OrderDetail>(`/mp-api/order/detail?id=${id}`)
}

/** 创建订单 */
export function createOrder(params: CreateOrderParams): Promise<{ orderId: string }> {
  if (USE_MOCK) {
    return Promise.resolve({ orderId: `mock_${Date.now()}` })
  }
  return post<{ orderId: string }>('/mp-api/order/create', params)
}
  • Step 3: 创建 src/api/reserve.ts
import { USE_MOCK } from '@/mock'
import { reserveStore } from '@/mock/store'
import type { ReserveRecord } from './types'
import { get, post } from './request'

/** 获取预约记录列表 */
export function getReserveList(): Promise<ReserveRecord[]> {
  if (USE_MOCK) return Promise.resolve(reserveStore.value)
  return get<ReserveRecord[]>('/mp-api/reserve/list')
}

/** 创建预约 */
export function createReserve(params: Omit<ReserveRecord, 'id' | 'status'>): Promise<{ id: number }> {
  if (USE_MOCK) {
    const id = Math.max(...reserveStore.value.map(r => r.id), 0) + 1
    reserveStore.value.push({ ...params, id, status: 'pending' })
    return Promise.resolve({ id })
  }
  return post<{ id: number }>('/mp-api/reserve/create', params)
}
  • Step 4: 验证类型检查

Run: pnpm run type-check Expected: 无类型错误

  • Step 5: Commit
git add src/api/scenic.ts src/api/order.ts src/api/reserve.ts
git commit -m "feat(api): add scenic, order, reserve API functions with mock fallback"

Task 7: 更新 App.vue 全局样式

Files:

  • Modify: src/App.vue(<style> 部分)

  • Step 1: 更新 App.vue 的 page 全局样式

main.scss 中已有 page { ... } 定义,且 _tokens.scss 已在 Task 2 中对齐。验证 main.scss 的 page 样式是否生效:

// main.scss 已有(无需修改):
page {
  background-color: $color-background;  // → #f3f4f6
  color: $color-foreground;              // → #111111
  font-family: $font-family-body;        // → -apple-system, 'PingFang SC'...
  font-size: $font-size-base;            // → 28rpx
  line-height: $line-height-normal;      // → 1.5
}

确认 App.vue 的 <style> 中 import 了 main.scss(当前已有 @import './styles/main.scss')。

如果 App.vue 中有重复的 page { min-height: 100vh } 样式,保留它(与 main.scss 不冲突)。

  • Step 2: 验证

Run: pnpm run dev:mp-weixin Expected: 构建成功,微信开发者工具中页面背景和字体符合规范

  • Step 3: Commit
git add src/App.vue
git commit -m "chore: verify App.vue global styles aligned with token system"

Task 8: 清理 9 个页面的局部 SCSS 变量

Files:

  • Modify: src/pages/faq/faq.vue(删除 lines 243-249 的局部变量)
  • Modify: src/pages/order/order-list.vue(删除 lines 232-240)
  • Modify: src/pages/order/order-detail.vue(删除 lines 225-234)
  • Modify: src/pages/qrcode/index.vue(删除 lines 249-250)
  • Modify: src/pages/card-buy/card-buy.vue(删除 lines 85-90)
  • Modify: src/pages/reserve/reserve-success.vue(删除 lines 122-130)
  • Modify: src/pages/card-bind/card-bind.vue(删除 lines 270-279)
  • Modify: src/pages/reserve/reserve-index.vue(删除 lines 229-238)
  • Modify: src/pages/reserve/reserve-list.vue(删除 lines 305-313)

Interfaces:

  • Consumes: Task 1 的 uni.scss 全局变量

每个文件的变量映射关系:

局部变量 全局替换为 值
$font $font-family-body 系统字体
$primary $uv-primary #3c9cff
$text-main $uv-main-color #111111
$text-label $uv-main-color #222222→#111111(合并)
$text-body $uv-content-color #333333
$text-hint $uv-tips-color #666666
$text-weak $uv-placeholder-color #999999
$divider $uv-border-color #eeeeee
$card-radius $radius-lg 24rpx
$accent $uv-primary #3c9cff

注意:$bar-height: 120rpx 是组件特定值,保留为局部变量。

  • Step 1: 逐文件删除局部变量定义块

对每个文件:

  1. 删除 <style> 开头的局部变量定义行($font: ...; 到 $divider: ...; 等)
  2. 在 <style> 内全文搜索替换:
    • $font → $font-family-body(注意不要替换 $font-size 等前缀匹配)
    • $primary → $uv-primary(注意不要替换 $primary-dark 先处理)
    • $text-main → $uv-main-color
    • $text-label → $uv-main-color(合并到主色)
    • $text-body → $uv-content-color
    • $text-hint → $uv-tips-color
    • $text-weak → $uv-placeholder-color
    • $divider → $uv-border-color
    • $card-radius → $radius-lg
    • $accent → $uv-primary
  3. 特殊处理 card-bind.vue:$primary-dark: #e89a2a 是 bug(值是金色 #e89a2a,不是蓝色)。检查该变量在样式中的使用场景:
    • 如果用于金色/警告相关样式 → 替换为 $uv-warning-dark(#f1a532)
    • 如果用于主色深蓝相关样式 → 替换为 $uv-primary-dark(#398ade)
  • Step 2: 验证编译

Run: pnpm run dev:mp-weixin Expected: 构建成功,所有 9 个页面视觉无回退

  • Step 3: Commit
git add src/pages/
git commit -m "refactor: remove local SCSS variables from 9 pages

Replace with global uni.scss tokens:
\$font → \$font-family-body
\$primary → \$uv-primary
\$text-* → \$uv-main-color/\$uv-content-color/\$uv-tips-color/\$uv-placeholder-color
\$divider → \$uv-border-color
\$card-radius → \$radius-lg"

Phase 2: 核心链路迁移(4 个页面 + 相关组件)

迁移检查清单(每个页面必须执行全部 6 项):

  1. 删除局部 SCSS 变量(已在 Task 8 处理)
  2. 硬编码颜色值 → 全局变量(见 spec 2.5 迁移对照表)
  3. 硬编码字号 → 对应 $font-size-* 变量(见 spec 1.2 游离值迁移表)
  4. 硬编码间距 → 对应 $spacing-* 变量
  5. 模板中硬编码数据 → import mock 数据
  6. font-weight: bold → $font-weight-bold,硬编码过渡 → $transition-*

Task 9: 迁移首页 (index.vue)

Files:

  • Modify: src/pages/index/index.vue

当前状态:已使用 SCSS 变量,但有部分硬编码值需修正。

  • Step 1: 颜色迁移

搜索替换(在 <style> 中):

  • #e53935 → $color-price(价格场景)或 $uv-error(错误场景)

  • #FFD600 → $color-price-highlight(促销价,已有)

  • rgba(255, 255, 255, ...) → 按 spec 2.4 规范保留(暗底白字透明度已是标准值)

  • rgba(60, 156, 255, ...) → 保留(品牌蓝透明度变体)

  • Step 2: 字号迁移

  • font-size: 22rpx → $font-size-xs(20rpx)

  • font-size: 144rpx → 保留(首页大卡促销数字,特殊值)

  • Step 3: 模板数据迁移

将 <script> 中的硬编码数据替换为 import:

  • hotSpots 数组 → import { mockScenicList } from '@/mock/scenic'(取前 6 条)

  • announcements 数组 → import { mockNotices } from '@/mock/notice'

  • quickActions 数组 → 保留(UI 配置,非业务数据)

  • Step 4: 验证

Run: pnpm run dev:mp-weixin Expected: 首页视觉无回退,数据正常显示

  • Step 5: Commit
git add src/pages/index/
git commit -m "refactor(index): migrate hardcoded values to design tokens and mock data"

Task 10: 迁移年卡列表 (card/list.vue)

Files:

  • Modify: src/packages/user/pages/card/list.vue

当前状态:已使用 SCSS 变量,少量硬编码。

  • Step 1: 颜色迁移

  • #fff → $color-card

  • rgba(48, 49, 51, ...) → 保留(暗底渐变,特殊视觉效果)

  • rgba(255, 255, 255, 0.8) → 保留(暗底白字标准透明度)

  • Step 2: 间距迁移

无关键间距值需要迁移(已有 token 化),保留 icon/decoration 的硬编码像素值。

  • Step 3: 验证并提交
git add src/packages/user/pages/card/list.vue
git commit -m "refactor(card/list): migrate remaining hardcoded colors to tokens"

Task 11: 迁移年卡详情 (card/detail.vue)

Files:

  • Modify: src/packages/user/pages/card/detail.vue

当前状态:大量硬编码值(13 种颜色、22 种字号/间距、4 个 font-weight: bold)。

  • Step 1: 颜色迁移
硬编码值 替换为
#ddd $uv-border-color
#fff $color-card
#333 $uv-content-color
#e53935 $color-price
#ccc $uv-disabled-color
#666 $uv-tips-color
#eee $uv-border-color
#999 $uv-placeholder-color
  • Step 2: 字号迁移
硬编码值 替换为
36rpx $font-size-xl
44rpx $font-size-2xl
26rpx $font-size-base(28rpx,向上取整)
30rpx $font-size-lg(32rpx)
28rpx $font-size-base
24rpx $font-size-sm
22rpx $font-size-xs(20rpx)
  • Step 3: 间距迁移
硬编码值 替换为
24rpx $spacing-3
30rpx $spacing-4(32rpx)
16rpx $spacing-2
12rpx $spacing-2(16rpx,向上取整)
8rpx $spacing-1
20rpx $spacing-3(24rpx)
  • Step 4: font-weight 迁移

全部 font-weight: bold → font-weight: $font-weight-bold

  • Step 5: 验证并提交
git add src/packages/user/pages/card/detail.vue
git commit -m "refactor(card/detail): migrate all hardcoded values to design tokens"

Task 12: 迁移购买流程 (card/buy.vue + card-buy.vue)

Files:

  • Modify: src/packages/user/pages/card/buy.vue

  • Modify: src/pages/card-buy/card-buy.vue

  • Step 1: 迁移 packages/card/buy.vue

颜色迁移(同 Task 11 对照表):

  • #f5f5f5 → $uv-bg-color
  • #fff → $color-card
  • #333 → $uv-content-color
  • #e53935 → $color-price
  • #eeeeee → $uv-border-color
  • #666 → $uv-tips-color
  • #3c9cff → $uv-primary
  • #999 → $uv-placeholder-color
  • #eee → $uv-border-color
  • #e5e7eb → $uv-border-color

字号迁移:

  • 30rpx → $font-size-lg
  • 28rpx → $font-size-base
  • 36rpx → $font-size-xl
  • 32rpx → $font-size-lg
  • 22rpx → $font-size-xs
  • 40rpx → $font-size-xl(36rpx,向下取整)

间距迁移:

  • 20rpx → $spacing-3(24rpx)
  • 24rpx → $spacing-3
  • 30rpx → $spacing-4(32rpx)
  • 16rpx → $spacing-2

font-weight: bold → $font-weight-bold(4 处)

  • Step 2: 迁移 pages/card-buy/card-buy.vue

删除局部变量(已在 Task 8 处理)。

颜色迁移:

  • #ffffff → $color-card
  • #e53935 → $color-price
  • rgba(0, 0, 0, 0.06) → 保留(投影变体)
  • rgba(60, 156, 255, 0.3) → 保留(品牌蓝投影)

字号迁移:

  • 26rpx → $font-size-base(28rpx)
  • 28rpx → $font-size-base
  • 38rpx → $font-size-xl(36rpx)
  • 22rpx → $font-size-xs(20rpx)

font-weight: 700 → $font-weight-bold(3 处)

过渡迁移:

  • all 0.2s ease → all $transition-fast

  • Step 3: 验证并提交

git add src/packages/user/pages/card/buy.vue src/pages/card-buy/
git commit -m "refactor(card/buy): migrate purchase flow to design tokens"

Phase 3: 剩余页面迁移


Task 13: 迁移订单页面

Files:

  • Modify: src/pages/order/order-list.vue

  • Modify: src/pages/order/order-detail.vue

  • Step 1: 迁移 order-list.vue

局部变量已删除(Task 8)。

颜色迁移:

  • 搜索所有 #111/#222 → $uv-main-color
  • #333 → $uv-content-color
  • #666 → $uv-tips-color
  • #999 → $uv-placeholder-color
  • #eeeeee → $uv-border-color
  • #ff9500 → $color-status-pending
  • #00b42a → $color-status-success
  • #86909c → $color-status-cancel
  • #f53f3f → $uv-error

字号迁移(26rpx → $font-size-base,其他按对照表)。

模板数据迁移:

  • orders ref 数组 → import { getOrderList } from '@/api/order',onLoad 中调用

  • Step 2: 迁移 order-detail.vue

同 order-list.vue 的颜色/字号迁移。

模板数据迁移:

  • mockOrders 数组 → import { getOrderDetail } from '@/api/order'

  • Step 3: 验证并提交

git add src/pages/order/
git commit -m "refactor(order): migrate order pages to design tokens and API layer"

Task 14: 迁移预约页面

Files:

  • Modify: src/pages/reserve/reserve-index.vue

  • Modify: src/pages/reserve/reserve-list.vue

  • Modify: src/pages/reserve/reserve-success.vue

  • Step 1: 迁移 reserve-list.vue

局部变量已删除(Task 8)。 颜色/字号迁移同订单页面模式。 状态颜色:pending → $color-status-pending,used/auto_used → $color-status-success,cancelled → $color-status-cancel。 模板数据迁移:records → import { getReserveList } from '@/api/reserve'。

  • Step 2: 迁移 reserve-index.vue

局部变量已删除(Task 8)。 颜色/字号迁移同上。 模板数据:scenic、userInfo、mockStocks、sessionMap → 后续对接 API 时替换,当前保留。

  • Step 3: 迁移 reserve-success.vue

局部变量已删除(Task 8)。 颜色/字号迁移同上。

  • Step 4: 验证并提交
git add src/pages/reserve/
git commit -m "refactor(reserve): migrate reserve pages to design tokens"

Task 15: 迁移剩余页面

Files:

  • src/pages/login/login.vue — 已有 SCSS 变量,少量硬编码

  • src/pages/login/agreement.vue — 已有 SCSS 变量

  • src/pages/login/privacy.vue — 已有 SCSS 变量

  • src/pages/profile/index.vue — 已有 SCSS 变量

  • src/pages/qrcode/index.vue — 局部变量已删除(Task 8)

  • src/pages/faq/faq.vue — 局部变量已删除(Task 8)

  • src/pages/card-bind/card-bind.vue — 局部变量已删除(Task 8)

  • src/pages/notice-list/notice-list.vue — 混合状态

  • src/pages/scenic-detail/scenic-detail.vue — 已有 SCSS 变量

  • src/pages/scenic-list/scenic-list.vue — 已有 SCSS 变量

  • src/pages/union-buy/union-buy.vue — 已有 SCSS 变量

  • src/pages/usage-guide/usage-guide.vue — 纯图片页面

  • src/packages/user/pages/card/activate.vue — 硬编码字体

  • src/packages/user/pages/card/bind.vue — 硬编码字体

  • src/packages/user/pages/card/qrcode.vue — 硬编码字体

  • Step 1: 逐文件迁移

对每个文件执行 6 项检查清单:

  1. 删除局部变量(已在 Task 8 处理)
  2. 硬编码颜色 → 全局变量
  3. 硬编码字号 → $font-size-*
  4. 硬编码间距 → $spacing-*
  5. 模板数据 → mock import(notice-list、qrcode、faq)
  6. font-weight: bold → $font-weight-bold

分包页面特殊处理(activate/bind/qrcode):

  • 这些文件使用 font-family: 'SourceHanSerifCN'... 硬编码字符串
  • 替换为 font-family: $font-family-display(标题)或 $font-family-body(正文)

notice-list 数据迁移:

  • notices 数组 → import { mockNotices } from '@/mock/notice'

qrcode 数据迁移:

  • userCards 数组 → 保留(需用户登录后从 API 获取)

  • Step 2: 验证并提交

git add src/pages/ src/packages/
git commit -m "refactor: migrate remaining pages to design tokens"

Task 16: 迁移公共组件

Files:

  • Modify: src/components/card-item.vue — 从纯 CSS 改为 SCSS

  • Modify: src/components/empty-state.vue — 从纯 CSS 改为 SCSS

  • Modify: src/components/McGlass.vue — 从纯 CSS 改为 SCSS

  • Step 1: 迁移 card-item.vue

将 <style scoped> 改为 <style lang="scss" scoped>。

颜色迁移:

硬编码值 替换为
#fff $color-card
#eee $uv-border-color
#333 $uv-content-color
#999 $uv-placeholder-color
#E74C3C $color-price
#ccc $uv-disabled-color
#4A90D9 $uv-primary
#E8F0FE $uv-primary-light

字号迁移:

硬编码值 替换为
30rpx $font-size-lg
24rpx $font-size-sm
32rpx $font-size-lg
22rpx $font-size-xs

间距迁移:

硬编码值 替换为
16rpx $spacing-2
20rpx $spacing-3(24rpx)
8rpx $spacing-1
4rpx 保留(极小装饰值)
12rpx $spacing-2(16rpx)

圆角迁移:

  • 16rpx → $radius-base
  • 20rpx → $radius-base(16rpx,向下取整)

font-weight: bold → $font-weight-bold(2 处)

  • Step 2: 迁移 empty-state.vue

改为 <style lang="scss" scoped>。

颜色迁移:

  • #999 → $uv-placeholder-color
  • #4A90D9 → $uv-primary
  • #E8F0FE → $uv-primary-light

字号/间距迁移:

  • 80rpx(icon) → 保留(装饰尺寸)
  • 28rpx → $font-size-base
  • 40rpx → $spacing-5
  • 20rpx → $spacing-3(24rpx)
  • 30rpx → $spacing-4(32rpx)
  • 12rpx → $spacing-2(16rpx)

圆角:30rpx → $radius-full

  • Step 3: 迁移 McGlass.vue

改为 <style lang="scss" scoped>。

  • rgba 值保留(毛玻璃效果的特殊视觉值)

  • 24rpx 圆角 → $radius-lg

  • backdrop-filter 值保留

  • Step 4: 验证并提交

git add src/components/
git commit -m "refactor(components): migrate card-item, empty-state, McGlass to SCSS tokens"

Task 17: 最终验证

  • Step 1: TypeScript 类型检查

Run: pnpm run type-check Expected: 无错误

  • Step 2: 全量构建

Run: pnpm run dev:mp-weixin Expected: 构建成功

  • Step 3: 视觉回归检查

在微信开发者工具中逐页检查:

  • 首页:轮播、景区卡片、超级玩家卡价格(¥98 亮黄色)

  • 年卡列表/详情:布局、颜色、字号

  • 购买流程:价格显示、按钮样式

  • 订单列表/详情:状态标签颜色

  • 预约页面:表单、列表

  • 个人中心、登录页

  • Step 4: 清理遗留

  • 删除 src/data/scenic-spots.ts(已被 mock/scenic.ts 替代)

  • 确认无残留的局部 $font/$primary 定义:grep -r '\$font:' src/pages/ src/packages/

  • 确认无残留的 font-weight: bold:grep -r 'font-weight: bold' src/

  • Step 5: 最终提交

git add -A
git commit -m "chore: final verification and cleanup after UI/UX standardization"