Files
pingxiangcard/docs/superpowers/plans/2026-06-26-ui-ux-standardization.md
T
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

1265 lines
36 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 标准化实施计划
> **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` 之前),插入:
```scss
$font-size-xxs: 18rpx;
```
在 `$font-size-3xl: 56rpx;`(第 82 行)之后、`$font-size-display: 72rpx;` 之前,插入:
```scss
$font-size-4xl: 64rpx;
```
- [ ] **Step 2: 新增 `$uv-placeholder-color`**
在 `$uv-tips-color: #666666;`(第 51 行)之后插入:
```scss
$uv-placeholder-color: #999999;
```
- [ ] **Step 3: 新增语义色变量**
在 `$gradient-accent` 定义(第 67 行)之后插入:
```scss
// 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 行:
```scss
$font-family-body: 'SourceHanSerifCN', '思源宋体', -apple-system, 'PingFang SC', sans-serif;
```
改为:
```scss
$font-family-body: -apple-system, 'PingFang SC', 'Helvetica Neue', sans-serif;
```
- [ ] **Step 5: 验证编译**
Run: `pnpm run dev:mp-weixin`
Expected: 构建成功,无 SCSS 编译错误
- [ ] **Step 6: Commit**
```bash
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,字体对齐思源宋体系):
```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**
```bash
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` 接口之后)追加:
```typescript
/** 景区场次 */
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**
```bash
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`**
```typescript
/** 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,按用途命名:
```typescript
// 景区封面占位图
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`**
```typescript
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**
```bash
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 时再统一调整。
```typescript
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 条订单)。
```typescript
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**
```bash
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`**
```typescript
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`**
```typescript
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`**
```typescript
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**
```bash
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` 样式是否生效:
```scss
// 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**
```bash
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**
```bash
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**
```bash
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: 验证并提交**
```bash
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: 验证并提交**
```bash
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: 验证并提交**
```bash
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: 验证并提交**
```bash
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: 验证并提交**
```bash
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: 验证并提交**
```bash
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: 验证并提交**
```bash
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: 最终提交**
```bash
git add -A
git commit -m "chore: final verification and cleanup after UI/UX standardization"
```