|
|
@@ -0,0 +1,927 @@
|
|
|
+# mini-demo 迁移指导规范
|
|
|
+
|
|
|
+## 版本信息
|
|
|
+| 版本 | 日期 | 描述 | 作者 |
|
|
|
+|------|------|------|------|
|
|
|
+| 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
|
|
|
+// tests/unit/mini/pages/home/index.test.tsx
|
|
|
+import { render } from '@testing-library/react'
|
|
|
+import HomePage from '@/pages/home/index'
|
|
|
+
|
|
|
+describe('HomePage', () => {
|
|
|
+ it('renders travel type selector', () => {
|
|
|
+ const { getByText } = render(<HomePage />)
|
|
|
+ expect(getByText('大巴拼车')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('handles location selection', () => {
|
|
|
+ // 测试地点选择功能
|
|
|
+ })
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+### 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
|