| 版本 | 日期 | 描述 | 作者 |
|---|---|---|---|
| 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技术栈的完整指导,包含百分百样式保留的精确迁移方案,确保迁移过程顺利且保持用户体验一致性。
| 原页面 | 目标页面 | 状态 | 复杂度 |
|---|---|---|---|
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> |
轮播组件 |
// 正确引入方式
import { Button } from '@/components/ui/button'
// 使用示例
<Button variant="default" size="default">
按钮文本
</Button>
// 可用变体
<Button variant="default">默认</Button>
<Button variant="destructive">危险</Button>
<Button variant="outline">轮廓</Button>
<Button variant="secondary">次要</Button>
<Button variant="ghost">幽灵</Button>
<Button variant="link">链接</Button>
// 正确引入方式
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter
} from '@/components/ui/dialog'
// 使用示例
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>对话框标题</DialogTitle>
</DialogHeader>
<Text>对话框内容</Text>
<DialogFooter>
<Button variant="outline" onClick={() => setIsOpen(false)}>
取消
</Button>
<Button onClick={handleConfirm}>
确认
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
// ❌ 错误 - 使用Taro原生Button
import { Button } from '@tarojs/components'
// ❌ 错误 - 使用自定义模态框
import Modal from '@/components/modal'
// ✅ 正确 - 使用shadcn/ui组件
import { Button } from '@/components/ui/button'
import { Dialog } from '@/components/ui/dialog'
| 原组件 | 新组件 | 位置 |
|---|---|---|
| 轮播图组件 | 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样式:
/* 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样式:
// 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-primary 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] |
原数据格式:
// mini-demo 模拟数据
const routeData = {
id: 1,
startPoint: "北京",
endPoint: "上海",
departureTime: "2025-10-15 08:00:00",
price: 200,
vehicleType: "商务车",
travelMode: "包车"
}
新数据格式:
// TypeScript接口定义
interface Route {
id: number;
startPoint: string;
endPoint: string;
departureTime: Date;
price: number;
vehicleType: VehicleType;
travelMode: TravelMode; // 新增:出行方式(拼车/包车)
}
// API调用
const { data: routes } = useQuery({
queryKey: ['routes', queryParams],
queryFn: () => api.routes.list(queryParams)
})
数据获取模式:
// 使用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分钟缓存
})
}
原页面结构分析:
<!-- 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组件:
// 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-primary text-white py-3 rounded-lg mt-6"
onClick={handleSearch}
disabled={!startLocation || !endLocation}
>
查询路线
</Button>
</View>
)
}
迁移要点:
// 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>
)
}
迁移要点:
// 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 + 阻止冒泡 |
在事件处理函数中处理 |
原小程序方式:
<view bindtap="onItemTap" data-id="{{item.id}}" data-name="{{item.name}}">
点击我
</view>
// 页面JS
onItemTap: function(e) {
const id = e.currentTarget.dataset.id
const name = e.currentTarget.dataset.name
// 处理逻辑
}
React方式:
<View onClick={() => onItemTap(item.id, item.name)}>
点击我
</View>
// 组件函数
const onItemTap = (id: number, name: string) => {
// 处理逻辑
}
原小程序状态:
// 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状态管理:
// 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内容
)
}
原测试结构:
// mini-demo测试(通常较少)
新测试结构:
// 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()
})
})
Playwright测试:
// 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 插件进行适配
// tailwind.config.js
module.exports = {
corePlugins: {
preflight: false, // 禁用preflight
},
// ...其他配置
}
问题: 某些API在不同平台行为不一致 解决方案: 使用平台检测和条件渲染
import { isWeapp, isH5 } from '@/utils/platform'
export function PlatformSpecificComponent() {
return (
<View>
{isWeapp && <Text>微信小程序特有功能</Text>}
{isH5 && <Text>H5特有功能</Text>}
</View>
)
}
问题: 页面加载性能问题 解决方案:
/* 主色调 */
--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
--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)
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 |
包车主题文字 |
// 原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>
// 原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>
// 原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>
// 原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>
// 原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>
// 原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>
// 原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>
// 原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>
// 原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>
// 原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>
// 原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>
// 原WXSS
@media screen and (max-height: 667px) {
.banner-container { height: 22vh; }
.booking-section { padding: 16rpx; }
}
// Tailwind迁移
<View className="h-[25vh] sm:h-[22vh]">
{/* 响应式内容 */}
</View>
// 原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); }
}
/* 导航栏总高度计算 */
--navbar-height: 44px
--status-bar-height: 系统状态栏高度
--total-height: status-bar-height + navbar-height
/* 背景色 */
--navbar-bg-default: #ffffff
--navbar-bg-dark: #1a1a1a
--navbar-bg-primary: #4A90C2
--navbar-bg-transparent: transparent
/* 文字色 */
--navbar-text-default: #333333
--navbar-text-light: #ffffff
--navbar-text-primary: #ffffff
// 样式配置
{
backgroundColor: 'bg-white',
textColor: 'text-gray-900',
border: true,
}
// Tailwind 类名
className="bg-white text-gray-900 border-b border-gray-200"
// 样式配置
{
backgroundColor: 'bg-gray-900',
textColor: 'text-white',
border: true,
}
// Tailwind 类名
className="bg-gray-900 text-white border-b border-gray-700"
// 样式配置
{
backgroundColor: 'bg-transparent',
textColor: 'text-white',
border: false,
}
// Tailwind 类名
className="bg-transparent text-white"
// 样式配置
{
backgroundColor: 'bg-primary',
textColor: 'text-white',
border: false,
}
// Tailwind 类名
className="bg-primary text-white"
// 高度计算
const NAVBAR_HEIGHT = 44
const STATUS_BAR_HEIGHT = systemInfo.statusBarHeight || 0
const TOTAL_HEIGHT = STATUS_BAR_HEIGHT + NAVBAR_HEIGHT
// 胶囊按钮避让
const menuButtonInfo = Taro.getMenuButtonBoundingClientRect()
const rightPosition = systemInfo.screenWidth - menuButtonInfo.left + 10
// 标题宽度限制
const titleMaxWidth = `calc(100% - ${rightPosition}px - 60px - 60px)`
// 标准布局
left: '12px' // left-3
right: '12px' // right-3
<Navbar
title="页面标题"
leftIcon="i-heroicons-chevron-left-20-solid"
rightText="完成"
onClickLeft={() => Taro.navigateBack()}
onClickRight={handleComplete}
/>
<Navbar
title="个人中心"
backgroundColor="bg-gradient-to-r from-blue-500 to-purple-600"
textColor="text-white"
border={false}
rightIcon="i-heroicons-cog-6-tooth-20-solid"
onClickRight={handleSettings}
/>
// 遮罩样式
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
// 对话框基础样式
className="relative bg-white rounded-lg shadow-lg max-w-md w-full mx-4"
// 阴影系统
--dialog-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)
// 基础内边距
className="p-6"
// 自定义样式
className={cn("p-6", className)}
// 底部外边距
className="mb-4"
// 自定义样式
className={cn("mb-4", className)}
// 标题样式
className="text-lg font-semibold text-gray-900"
// 自定义样式
className={cn("text-lg font-semibold text-gray-900", className)}
// 按钮布局
className="flex justify-end space-x-2"
// 自定义样式
className={cn("flex justify-end space-x-2", className)}
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>确认操作</DialogTitle>
</DialogHeader>
<Text className="text-gray-600 mb-6">
确定要执行此操作吗?
</Text>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
取消
</Button>
<Button onClick={handleConfirm}>
确认
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>添加乘客</DialogTitle>
</DialogHeader>
<View className="space-y-4">
<Input placeholder="乘客姓名" />
<Input placeholder="手机号码" />
<Select>
<SelectTrigger>
<SelectValue placeholder="证件类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="idCard">身份证</SelectItem>
<SelectItem value="passport">护照</SelectItem>
</SelectContent>
</Select>
</View>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
取消
</Button>
<Button onClick={handleSubmit}>
保存
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<View className="flex items-center justify-center w-12 h-12 bg-red-100 rounded-full mb-4">
<Text className="i-heroicons-exclamation-triangle-20-solid w-6 h-6 text-red-600" />
</View>
<DialogTitle className="text-center">警告</DialogTitle>
</DialogHeader>
<Text className="text-gray-600 text-center mb-6">
此操作不可撤销,请谨慎操作。
</Text>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
取消
</Button>
<Button variant="destructive" onClick={handleDelete}>
删除
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
// 原小程序
<navigation-bar title="页面标题" background="#ffffff" />
// 迁移后
<Navbar title="页面标题" backgroundColor="bg-white" />
// 原小程序
<modal visible={visible}>
<view class="modal-content">内容</view>
</modal>
// 迁移后
<Dialog open={visible} onOpenChange={setVisible}>
<DialogContent>内容</DialogContent>
</Dialog>
文档状态: 正式版 下次评审: 2025-11-15