# mini-demo 迁移指导规范

## 版本信息
| 版本 | 日期 | 描述 | 作者 |
|------|------|------|------|
| 2.1 | 2025-10-15 | 修正Taro测试位置，统一使用mini/tests/目录 | Winston |
| 2.0 | 2025-10-15 | 整合精确样式迁移内容，优化文档结构 | Winston |
| 1.0 | 2025-10-15 | 初始mini-demo迁移指导规范 | Winston |

## 概述

本文档提供从原生微信小程序（mini-demo）迁移到Taro + React技术栈的完整指导，包含**百分百样式保留**的精确迁移方案，确保迁移过程顺利且保持用户体验一致性。

## 迁移策略

### 整体迁移方法
- **渐进式迁移**: 逐个页面迁移，保持业务连续性
- **技术栈转换**: 原生小程序 → Taro + React + TypeScript
- **样式重构**: WXSS → Tailwind CSS + shadcn/ui（精确样式保留）
- **数据集成**: 模拟数据 → 真实后端API

### 迁移优先级
1. **高优先级**: 核心出行流程页面
2. **中优先级**: 用户管理相关页面
3. **低优先级**: 辅助功能和设置页面

## 页面迁移对照表

### 核心页面迁移

| 原页面 | 目标页面 | 状态 | 复杂度 |
|--------|----------|------|--------|
| `mini-demo/pages/home/home` | `mini/src/pages/home/index.tsx` | ✅ 待迁移 | 中等 |
| `mini-demo/pages/select-activity/select-activity` | `mini/src/pages/activity-select/index.tsx` | ✅ 待迁移 | 中等 |
| `mini-demo/pages/schedule-list/schedule-list` | `mini/src/pages/schedule-list/index.tsx` | ✅ 待迁移 | 中等 |
| `mini-demo/pages/order-detail/order-detail` | `mini/src/pages/order-detail/index.tsx` | 🔄 规划中 | 高 |
| `mini-demo/pages/passenger/passenger` | `mini/src/pages/passenger/index.tsx` | 🔄 规划中 | 中等 |

## 组件映射规范

### 基础组件映射

| 原生小程序组件 | Taro组件 | 说明 |
|----------------|----------|------|
| `<view>` | `<View>` | 视图容器 |
| `<text>` | `<Text>` | 文本组件 |
| `<button>` | `<Button>` | 按钮组件 |
| `<input>` | `<Input>` | 输入框组件 |
| `<image>` | `<Image>` | 图片组件 |
| `<scroll-view>` | `<ScrollView>` | 滚动视图 |
| `<swiper>` | `<Swiper>` | 轮播组件 |

### 自定义组件映射

| 原组件 | 新组件 | 位置 |
|--------|--------|------|
| 轮播图组件 | `BannerCarousel` | `mini/src/components/home/BannerCarousel.tsx` |
| 出行方式选择 | `TravelTypeSelector` | `mini/src/components/home/TravelTypeSelector.tsx` |
| 地点选择器 | `LocationPicker` | `mini/src/components/home/LocationPicker.tsx` |
| 活动筛选器 | `ActivityFilter` | `mini/src/components/home/ActivityFilter.tsx` |
| 路线卡片 | `RouteCard` | `mini/src/components/home/RouteCard.tsx` |

## 样式转换规范

### WXSS → Tailwind CSS 转换

**原WXSS样式：**
```css
/* mini-demo/pages/home/home.wxss */
.container {
  padding: 20rpx;
  background-color: #ffffff;
}

.title {
  font-size: 32rpx;
  font-weight: bold;
  color: #333333;
}

.button {
  background-color: #007AFF;
  color: #ffffff;
  border-radius: 8rpx;
  padding: 20rpx 40rpx;
}
```

**转换后的Tailwind样式：**
```tsx
// mini/src/pages/home/index.tsx
<View className="p-5 bg-white">
  <Text className="text-[32rpx] font-bold text-gray-900">标题</Text>
  <Button className="bg-blue-500 text-white rounded-[8rpx] px-10 py-5">
    按钮
  </Button>
</View>
```

### 常用样式转换表

| WXSS属性 | Tailwind类名 | 示例 |
|----------|--------------|------|
| `width: 100%` | `w-full` | `w-full` |
| `height: 100%` | `h-full` | `h-full` |
| `margin: 20rpx` | `m-5` | `m-5` |
| `padding: 20rpx` | `p-5` | `p-5` |
| `background-color: #fff` | `bg-white` | `bg-white` |
| `color: #333` | `text-gray-900` | `text-gray-900` |
| `font-size: 32rpx` | `text-[32rpx]` | `text-[32rpx]` |
| `border-radius: 8rpx` | `rounded-[8rpx]` | `rounded-[8rpx]` |

## 数据层迁移

### 数据模型转换

**原数据格式：**
```javascript
// mini-demo 模拟数据
const routeData = {
  id: 1,
  startPoint: "北京",
  endPoint: "上海",
  departureTime: "2025-10-15 08:00:00",
  price: 200,
  vehicleType: "商务车"
}
```

**新数据格式：**
```typescript
// TypeScript接口定义
interface Route {
  id: number;
  startPoint: string;
  endPoint: string;
  departureTime: Date;
  price: number;
  vehicleType: VehicleType;
}

// API调用
const { data: routes } = useQuery({
  queryKey: ['routes', queryParams],
  queryFn: () => api.routes.list(queryParams)
})
```

### API集成

**数据获取模式：**
```typescript
// 使用React Query管理服务端状态
import { useQuery } from '@tanstack/react-query'
import { api } from '@/utils/api'

export function useRoutes(queryParams: RouteQueryParams) {
  return useQuery({
    queryKey: ['routes', queryParams],
    queryFn: () => api.routes.list(queryParams),
    staleTime: 5 * 60 * 1000, // 5分钟缓存
  })
}
```

## 页面迁移详细指导

### 首页迁移 (home/home)

**原页面结构分析：**
```xml
<!-- mini-demo/pages/home/home.wxml -->
<view class="container">
  <!-- 轮播海报 -->
  <swiper class="banner-swiper" indicator-dots="true" autoplay="true">
    <swiper-item wx:for="{{bannerList}}" wx:key="id">
      <image src="{{item.imageUrl}}" mode="aspectFill"></image>
    </swiper-item>
  </swiper>

  <!-- 出行方式选择 -->
  <view class="travel-type-selector">
    <view class="type-item {{activeType === 'carpool' ? 'active' : ''}}"
          bindtap="onTypeSelect" data-type="carpool">
      大巴拼车
    </view>
    <!-- 其他出行方式 -->
  </view>

  <!-- 地点选择 -->
  <view class="location-picker">
    <view class="start-location" bindtap="onStartLocationSelect">
      {{startLocation || '选择出发地'}}
    </view>
    <view class="end-location" bindtap="onEndLocationSelect">
      {{endLocation || '选择目的地'}}
    </view>
  </view>

  <!-- 日期选择 -->
  <view class="date-picker" bindtap="onDateSelect">
    {{selectedDate || '选择日期'}}
  </view>

  <!-- 查询按钮 -->
  <button class="search-btn" bindtap="onSearch">查询路线</button>
</view>
```

**迁移后的React组件：**
```tsx
// mini/src/pages/home/index.tsx
import { View, Text, Button } from '@tarojs/components'
import { useLoad } from '@tarojs/taro'
import { BannerCarousel } from '@/components/home/BannerCarousel'
import { TravelTypeSelector } from '@/components/home/TravelTypeSelector'
import { LocationPicker } from '@/components/home/LocationPicker'
import { DatePicker } from '@/components/home/DatePicker'

export default function HomePage() {
  const [travelType, setTravelType] = useState<TravelType>('carpool')
  const [startLocation, setStartLocation] = useState('')
  const [endLocation, setEndLocation] = useState('')
  const [selectedDate, setSelectedDate] = useState<Date>(new Date())

  useLoad(() => {
    console.log('HomePage loaded')
  })

  const handleSearch = () => {
    // 导航到活动选择页面
    navigateTo({
      url: `/pages/activity-select?type=${travelType}&start=${startLocation}&end=${endLocation}&date=${selectedDate.toISOString()}`
    })
  }

  return (
    <View className="min-h-screen bg-gray-50">
      {/* 轮播海报 - MVP限制：使用固定静态图片 */}
      <BannerCarousel banners={[{ id: 1, imageUrl: '/static/home-banner.jpg' }]} />

      {/* 出行方式选择 */}
      <TravelTypeSelector
        value={travelType}
        onChange={setTravelType}
      />

      {/* 地点选择器 */}
      <LocationPicker
        startLocation={startLocation}
        endLocation={endLocation}
        onStartChange={setStartLocation}
        onEndChange={setEndLocation}
      />

      {/* 日期选择器 */}
      <DatePicker
        value={selectedDate}
        onChange={setSelectedDate}
      />

      {/* 查询按钮 */}
      <Button
        className="w-full bg-blue-500 text-white py-3 rounded-lg mt-6"
        onClick={handleSearch}
        disabled={!startLocation || !endLocation}
      >
        查询路线
      </Button>
    </View>
  )
}
```

### 活动选择页面迁移 (select-activity/select-activity)

**迁移要点：**
```tsx
// mini/src/pages/activity-select/index.tsx
export default function ActivitySelectPage() {
  const router = useRouter()
  const { type, start, end, date } = router.params

  // 根据查询参数获取活动列表
  const { data: activities, isLoading } = useActivities({
    type: type as ActivityType,
    startPoint: start,
    endPoint: end,
    date: date ? new Date(date) : new Date()
  })

  return (
    <View className="min-h-screen bg-white">
      {/* 活动类型筛选 */}
      <ActivityFilter
        value={type as ActivityType}
        onChange={(newType) => {
          // 更新URL参数并重新查询
        }}
      />

      {/* 活动列表 */}
      {isLoading ? (
        <LoadingSpinner />
      ) : (
        <View className="space-y-4 p-4">
          {activities?.map(activity => (
            <ActivityCard
              key={activity.id}
              activity={activity}
              onSelect={() => {
                // 导航到班次列表页面
                navigateTo({
                  url: `/pages/schedule-list?activityId=${activity.id}`
                })
              }}
            />
          ))}
        </View>
      )}
    </View>
  )
}
```

### 班次列表页面迁移 (schedule-list/schedule-list)

**迁移要点：**
```tsx
// mini/src/pages/schedule-list/index.tsx
export default function ScheduleListPage() {
  const router = useRouter()
  const { activityId } = router.params

  const [sortBy, setSortBy] = useState<'price' | 'departureTime'>('departureTime')
  const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc')

  // 获取路线列表
  const { data: routes, isLoading } = useRoutes({
    activityId: Number(activityId),
    sortBy,
    sortOrder
  })

  return (
    <View className="min-h-screen bg-gray-50">
      {/* 排序和筛选工具栏 */}
      <Toolbar
        sortBy={sortBy}
        sortOrder={sortOrder}
        onSortChange={setSortBy}
        onOrderChange={setSortOrder}
      />

      {/* 路线列表 */}
      <View className="space-y-3 p-4">
        {routes?.map(route => (
          <RouteCard
            key={route.id}
            route={route}
            onSelect={() => {
              // 导航到订单确认页面
              navigateTo({
                url: `/pages/order-confirm?routeId=${route.id}`
              })
            }}
          />
        ))}
      </View>
    </View>
  )
}
```

## 事件处理迁移

### 事件绑定转换

| 原事件 | React事件 | 示例 |
|--------|-----------|------|
| `bindtap` | `onClick` | `<Button onClick={handleClick}>` |
| `bindinput` | `onInput` | `<Input onInput={handleInput}>` |
| `bindchange` | `onChange` | `<Picker onChange={handleChange}>` |
| `catchtap` | `onClick` + 阻止冒泡 | 在事件处理函数中处理 |

### 数据传递转换

**原小程序方式：**
```xml
<view bindtap="onItemTap" data-id="{{item.id}}" data-name="{{item.name}}">
  点击我
</view>
```

```javascript
// 页面JS
onItemTap: function(e) {
  const id = e.currentTarget.dataset.id
  const name = e.currentTarget.dataset.name
  // 处理逻辑
}
```

**React方式：**
```tsx
<View onClick={() => onItemTap(item.id, item.name)}>
  点击我
</View>
```

```typescript
// 组件函数
const onItemTap = (id: number, name: string) => {
  // 处理逻辑
}
```

## 状态管理迁移

### 页面状态转换

**原小程序状态：**
```javascript
// mini-demo页面JS
Page({
  data: {
    bannerList: [],
    activeType: 'carpool',
    startLocation: '',
    endLocation: '',
    selectedDate: ''
  },

  onLoad: function() {
    this.setData({
      bannerList: mockBannerData
    })
  },

  onTypeSelect: function(e) {
    this.setData({
      activeType: e.currentTarget.dataset.type
    })
  }
})
```

**React状态管理：**
```tsx
// React组件
import { useState, useEffect } from 'react'

export default function HomePage() {
  const [bannerList, setBannerList] = useState<Banner[]>([])
  const [activeType, setActiveType] = useState<TravelType>('carpool')
  const [startLocation, setStartLocation] = useState('')
  const [endLocation, setEndLocation] = useState('')
  const [selectedDate, setSelectedDate] = useState<Date>(new Date())

  useEffect(() => {
    // 初始化数据
    setBannerList([{ id: 1, imageUrl: '/static/home-banner.jpg' }])
  }, [])

  const handleTypeSelect = (type: TravelType) => {
    setActiveType(type)
  }

  return (
    // JSX内容
  )
}
```

## 测试迁移指导

### 单元测试迁移

**原测试结构：**
```javascript
// mini-demo测试（通常较少）
```

**新测试结构：**
```typescript
// mini/tests/pages/home/index.test.tsx
import TestUtils from '@tarojs/test-utils-react'
import HomePage from '@/pages/home/index'

const testUtils = new TestUtils()

describe('HomePage', () => {
  it('renders travel type selector', async () => {
    await testUtils.mount(HomePage)
    const travelTypeSelector = await testUtils.queries.waitForQuerySelector('.travel-type-selector')
    expect(travelTypeSelector).toBeInTheDocument()
  })

  it('handles location selection', async () => {
    // 测试地点选择功能
    await testUtils.mount(HomePage)
    const locationPicker = await testUtils.queries.waitForQuerySelector('.location-picker')
    expect(locationPicker).toBeInTheDocument()
  })
})
```

### E2E测试迁移

**Playwright测试：**
```typescript
// tests/e2e/travel-flow/home-page.spec.ts
test('首页查询流程', async ({ page }) => {
  await page.goto('/pages/home/index')

  // 选择出行方式
  await page.click('[data-testid="travel-type-carpool"]')

  // 选择出发地和目的地
  await page.fill('[data-testid="start-location"]', '北京')
  await page.fill('[data-testid="end-location"]', '上海')

  // 点击查询
  await page.click('[data-testid="search-button"]')

  // 验证跳转到活动选择页面
  await expect(page).toHaveURL(/.*pages\/activity-select.*/)
})
```

## 常见问题解决

### 样式兼容性问题

**问题**: 某些Tailwind类名在小程序中不生效
**解决方案**: 使用 `weapp-tailwindcss` 插件进行适配

```javascript
// tailwind.config.js
module.exports = {
  corePlugins: {
    preflight: false, // 禁用preflight
  },
  // ...其他配置
}
```

### 平台差异处理

**问题**: 某些API在不同平台行为不一致
**解决方案**: 使用平台检测和条件渲染

```tsx
import { isWeapp, isH5 } from '@/utils/platform'

export function PlatformSpecificComponent() {
  return (
    <View>
      {isWeapp && <Text>微信小程序特有功能</Text>}
      {isH5 && <Text>H5特有功能</Text>}
    </View>
  )
}
```

### 性能优化

**问题**: 页面加载性能问题
**解决方案**:
- 使用React.memo优化组件重渲染
- 合理使用React Query缓存
- 图片懒加载和压缩

## 迁移检查清单

### 页面迁移检查
- [ ] 组件结构正确迁移
- [ ] 样式正确转换
- [ ] 事件处理正确绑定
- [ ] 状态管理正确实现
- [ ] 数据获取正确集成
- [ ] 路由跳转正常工作
- [ ] 平台兼容性测试通过

### 功能验证检查
- [ ] 用户交互流程正常
- [ ] 数据展示正确
- [ ] 错误处理完善
- [ ] 加载状态处理
- [ ] 空状态处理

### 质量保证检查
- [ ] 单元测试覆盖
- [ ] 集成测试通过
- [ ] E2E测试通过
- [ ] 代码审查完成
- [ ] 性能测试通过

## 精确样式迁移指南

## 设计系统核心规范

### 颜色系统
```css
/* 主色调 */
--primary-color: #4A90C2
--primary-gradient: linear-gradient(135deg, #4A90C2 0%, #357ABD 100%)
--secondary-color: #5B9BD5

/* 包车主题 */
--charter-color: #d4af37
--charter-gradient: linear-gradient(135deg, #d4af37 0%, #f4d03f 100%)
--charter-bg: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)

/* 功能色 */
--success-color: #52c41a
--warning-color: #fa8c16
--error-color: #ff4d4f
--info-color: #1890ff

/* 中性色 */
--text-color: #333
--text-secondary: #666
--text-placeholder: #8E8E93
--border-color: #E5E5EA
--bg-color: #F8F9FA
--card-bg: #FFFFFF
```

### 阴影系统
```css
--shadow-light: 0 2rpx 12rpx rgba(0, 0, 0, 0.08)
--shadow-medium: 0 4rpx 20rpx rgba(0, 0, 0, 0.12)
--shadow-heavy: 0 8rpx 32rpx rgba(0, 0, 0, 0.16)
```

## Tailwind CSS 精确配置

### tailwind.config.js 扩展配置
```javascript
module.exports = {
  theme: {
    extend: {
      colors: {
        // 精确匹配 mini-demo 颜色
        'primary': '#4A90C2',
        'primary-dark': '#357ABD',
        'secondary': '#5B9BD5',
        'charter': '#d4af37',
        'charter-light': '#f4d03f',
        'charter-dark': '#1a1a1a',
        'charter-bg': '#2d2d2d',
        'success': '#52c41a',
        'warning': '#fa8c16',
        'error': '#ff4d4f',
        'info': '#1890ff',
        'text-primary': '#333',
        'text-secondary': '#666',
        'text-placeholder': '#8E8E93',
        'border': '#E5E5EA',
        'bg': '#F8F9FA',
        'card': '#FFFFFF',
      },
      borderRadius: {
        'card': '20rpx',
        'button': '50rpx',
        'small': '12rpx',
        'medium': '16rpx',
      },
      boxShadow: {
        'light': '0 2rpx 12rpx rgba(0, 0, 0, 0.08)',
        'medium': '0 4rpx 20rpx rgba(0, 0, 0, 0.12)',
        'heavy': '0 8rpx 32rpx rgba(0, 0, 0, 0.16)',
        'primary': '0 8rpx 24rpx rgba(74, 144, 194, 0.4)',
        'charter': '0 8rpx 24rpx rgba(212, 175, 55, 0.4)',
      },
      spacing: {
        'card': '32rpx',
        'section': '24rpx',
        'button': '24rpx',
      }
    }
  }
}
```

## 样式迁移对照表

### 通用样式类
| 原WXSS类名 | Tailwind类名 | 说明 |
|-----------|-------------|------|
| `.card` | `bg-card rounded-card shadow-medium p-card border border-border` | 卡片样式 |
| `.big-btn` | `bg-gradient-to-br from-primary to-primary-dark text-white rounded-button p-button shadow-primary` | 大按钮 |
| `.text-primary` | `text-primary` | 主色调文字 |
| `.text-charter` | `text-charter` | 包车主题文字 |

### 首页样式迁移

#### 顶部轮播图
```tsx
// 原WXSS
.banner-container { height: 25vh; }
.banner-swiper { width: 100%; height: 100%; }

// Tailwind迁移
<View className="h-[25vh] w-full">
  <Swiper className="w-full h-full">
    {/* 轮播内容 */}
  </Swiper>
</View>
```

#### 出行方式选择
```tsx
// 原WXSS
.service-tabs {
  background: rgba(255, 255, 255, 0.95);
  margin: 12rpx 20rpx;
  border-radius: 50rpx;
  padding: 6rpx;
}

// Tailwind迁移
<View className="bg-white/95 m-[12rpx] mx-[20rpx] rounded-button p-[6rpx]">
  {/* tab内容 */}
</View>
```

#### 地点选择器
```tsx
// 原WXSS
.route-item {
  background: #ffffff;
  border-radius: 16rpx;
  padding: 20rpx 16rpx;
  border: 2rpx solid #e1e8ed;
  min-height: 88rpx;
}

// Tailwind迁移
<View className="bg-white rounded-medium p-[20rpx_16rpx] border-2 border-[#e1e8ed] min-h-[88rpx]">
  {/* 地点选择内容 */}
</View>
```

### 活动选择页面样式迁移

#### 头部信息
```tsx
// 原WXSS
.header-info {
  background: linear-gradient(135deg, #4A90C2 0%, #357ABD 100%);
  padding: 40rpx 32rpx;
  color: #fff;
}

// Tailwind迁移
<View className="bg-gradient-to-br from-primary to-primary-dark p-[40rpx_32rpx] text-white">
  {/* 头部内容 */}
</View>
```

#### 行程区域
```tsx
// 原WXSS
.trip-header.departure {
  background: linear-gradient(135deg, #4A90C2 0%, #357ABD 100%);
  color: #fff;
}

// Tailwind迁移
<View className="bg-gradient-to-br from-primary to-primary-dark text-white">
  {/* 去程头部 */}
</View>
```

### 班次列表页面样式迁移

#### 包车卡片
```tsx
// 原WXSS
.charter-card {
  background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
  border: 2rpx solid #d4af37;
  box-shadow: 0 8rpx 32rpx rgba(212, 175, 55, 0.3);
}

// Tailwind迁移
<View className="bg-gradient-to-br from-charter-dark to-charter-bg border-2 border-charter shadow-charter">
  {/* 包车卡片内容 */}
</View>
```

#### 预订按钮
```tsx
// 原WXSS
.book-btn {
  background: linear-gradient(135deg, #4A90C2 0%, #357ABD 100%);
  color: #fff;
  border-radius: 50rpx;
  padding: 24rpx 0;
  box-shadow: 0 8rpx 24rpx rgba(74, 144, 194, 0.4);
}

// Tailwind迁移
<Button className="bg-gradient-to-br from-primary to-primary-dark text-white rounded-button p-[24rpx_0] shadow-primary">
  预订
</Button>
```

### 订单页面样式迁移

#### 包车主题
```tsx
// 原WXSS
.charter-theme {
  background: linear-gradient(180deg, #1a1a1a 0%, #2d2d2d 100%);
}

// Tailwind迁移
<View className="bg-gradient-to-b from-charter-dark to-charter-bg">
  {/* 包车主题内容 */}
</View>
```

#### 支付区域
```tsx
// 原WXSS
.pay-section {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: #fff;
  padding: 24rpx 32rpx;
  box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.1);
}

// Tailwind迁移
<View className="fixed bottom-0 left-0 right-0 bg-white p-[24rpx_32rpx] shadow-[0_-4rpx_20rpx_rgba(0,0,0,0.1)]">
  {/* 支付内容 */}
</View>
```

### 我的页面样式迁移

#### 用户信息区域
```tsx
// 原WXSS
.user-section {
  background: linear-gradient(135deg, #4A90C2 0%, #357ABD 100%);
  padding: 40rpx 32rpx 32rpx 32rpx;
  color: #fff;
}

// Tailwind迁移
<View className="bg-gradient-to-br from-primary to-primary-dark p-[40rpx_32rpx_32rpx_32rpx] text-white">
  {/* 用户信息内容 */}
</View>
```

#### 会员卡片
```tsx
// 原WXSS
.member-card {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 20rpx;
  padding: 24rpx;
  color: #fff;
  box-shadow: 0 6rpx 24rpx rgba(102, 126, 234, 0.3);
}

// Tailwind迁移
<View className="bg-gradient-to-br from-[#667eea] to-[#764ba2] rounded-card p-[24rpx] text-white shadow-[0_6rpx_24rpx_rgba(102,126,234,0.3)]">
  {/* 会员卡片内容 */}
</View>
```

## 响应式设计迁移

### 媒体查询转换
```tsx
// 原WXSS
@media screen and (max-height: 667px) {
  .banner-container { height: 22vh; }
  .booking-section { padding: 16rpx; }
}

// Tailwind迁移
<View className="h-[25vh] sm:h-[22vh]">
  {/* 响应式内容 */}
</View>
```

## 动画效果迁移

### 渐入动画
```tsx
// 原WXSS
.fade-in {
  animation: fadeIn 0.3s ease-in-out;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(20rpx); }
  to { opacity: 1; transform: translateY(0); }
}

// Tailwind迁移
<View className="animate-[fadeIn_0.3s_ease-in-out]">
  {/* 动画内容 */}
</View>

// 在全局CSS中添加
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(20rpx); }
  to { opacity: 1; transform: translateY(0); }
}
```

### 视觉一致性检查清单
- [ ] 颜色系统完全匹配
- [ ] 渐变效果一致
- [ ] 阴影层次相同
- [ ] 圆角设计一致
- [ ] 间距布局相同
- [ ] 字体大小和权重一致
- [ ] 按钮交互效果相同
- [ ] 包车主题效果一致

---

**文档状态**: 正式版
**下次评审**: 2025-11-15