|
@@ -0,0 +1,552 @@
|
|
|
|
|
+import { View, Text, ScrollView } from '@tarojs/components'
|
|
|
|
|
+import Taro, { useRouter, navigateTo } from '@tarojs/taro'
|
|
|
|
|
+import { useState, useEffect } from 'react'
|
|
|
|
|
+import { useQuery, useMutation } from '@tanstack/react-query'
|
|
|
|
|
+import { orderClient, paymentClient, routeClient, passengerClient } from '@/api'
|
|
|
|
|
+import { Navbar, NavbarPresets } from '@/components/ui/navbar'
|
|
|
|
|
+import { Button } from '@/components/ui/button'
|
|
|
|
|
+import { Card, CardContent } from '@/components/ui/card'
|
|
|
|
|
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
|
|
|
|
|
+import type { InferResponseType , InferRequestType} from 'hono/client'
|
|
|
|
|
+import { isWeapp } from '@/utils/platform'
|
|
|
|
|
+
|
|
|
|
|
+// 使用RPC方式提取类型
|
|
|
|
|
+type Passenger = InferResponseType<typeof passengerClient.$get, 200>['data'][0]
|
|
|
|
|
+type OrderCreateRequest = InferRequestType<typeof orderClient.$post>['json']
|
|
|
|
|
+
|
|
|
|
|
+// 模拟数据 - 待替换为真实API
|
|
|
|
|
+export default function OrderPage() {
|
|
|
|
|
+ const router = useRouter()
|
|
|
|
|
+ const { routeId, activityName, type } = router.params
|
|
|
|
|
+
|
|
|
|
|
+ const [passengers, setPassengers] = useState<Passenger[]>([])
|
|
|
|
|
+ const [phoneNumber, setPhoneNumber] = useState('')
|
|
|
|
|
+ const [hasPhoneNumber, setHasPhoneNumber] = useState(false)
|
|
|
|
|
+ const [totalPrice, setTotalPrice] = useState(0)
|
|
|
|
|
+ const [originalPrice, setOriginalPrice] = useState(0)
|
|
|
|
|
+ const [isCharter] = useState(type === 'business-charter')
|
|
|
|
|
+ const [showPassengerSelector, setShowPassengerSelector] = useState(false)
|
|
|
|
|
+
|
|
|
|
|
+ // 使用react-query获取路线数据
|
|
|
|
|
+ const { data: schedule, isLoading: isLoadingRoute } = useQuery({
|
|
|
|
|
+ queryKey: ['route', routeId],
|
|
|
|
|
+ queryFn: async () => {
|
|
|
|
|
+ const response = await routeClient[':id'].$get({
|
|
|
|
|
+ param: {
|
|
|
|
|
+ id: Number(routeId)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error(`获取路线数据失败: ${response.status}`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const route = await response.json()
|
|
|
|
|
+ return route.data
|
|
|
|
|
+ },
|
|
|
|
|
+ enabled: !!routeId
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 使用react-query获取已保存的乘客
|
|
|
|
|
+ const { data: savedPassengers } = useQuery({
|
|
|
|
|
+ queryKey: ['passengers'],
|
|
|
|
|
+ queryFn: async () => {
|
|
|
|
|
+ const response = await passengerClient.$get({
|
|
|
|
|
+ query: {
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ pageSize: 100
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error(`获取乘客数据失败: ${response.status}`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const result = await response.json()
|
|
|
|
|
+ return result.data || []
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 使用react-query创建订单
|
|
|
|
|
+ const createOrderMutation = useMutation({
|
|
|
|
|
+ mutationFn: async (orderData: OrderCreateRequest) => {
|
|
|
|
|
+ const response = await orderClient.$post({
|
|
|
|
|
+ json: orderData
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ const errorData = await response.json()
|
|
|
|
|
+ throw new Error(errorData.message || '订单创建失败')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return await response.json()
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 使用react-query创建支付
|
|
|
|
|
+ const createPaymentMutation = useMutation({
|
|
|
|
|
+ mutationFn: async (paymentData: { orderId: number; totalAmount: number; description: string }) => {
|
|
|
|
|
+ const response = await paymentClient.$post({
|
|
|
|
|
+ json: paymentData
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ const errorData = await response.json()
|
|
|
|
|
+ throw new Error(errorData.message || '支付创建失败')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return await response.json()
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 计算总价格
|
|
|
|
|
+ const calculateTotalPrice = () => {
|
|
|
|
|
+ if (!schedule) return
|
|
|
|
|
+
|
|
|
|
|
+ let original = 0
|
|
|
|
|
+ if (isCharter) {
|
|
|
|
|
+ // 包车按车计费
|
|
|
|
|
+ original = schedule.price
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 拼车按人计费
|
|
|
|
|
+ original = passengers.length * schedule.price
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setOriginalPrice(original)
|
|
|
|
|
+ setTotalPrice(original)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ calculateTotalPrice()
|
|
|
|
|
+ }, [schedule, passengers, isCharter])
|
|
|
|
|
+
|
|
|
|
|
+ // 获取手机号
|
|
|
|
|
+ const handleGetPhoneNumber = (e: any) => {
|
|
|
|
|
+ if (e.detail.errMsg === 'getPhoneNumber:ok') {
|
|
|
|
|
+ // TODO: 实际项目中需要发送code到后端获取手机号
|
|
|
|
|
+ setPhoneNumber('138****8888')
|
|
|
|
|
+ setHasPhoneNumber(true)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.error('获取手机号失败:', e.detail.errMsg)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 添加乘客
|
|
|
|
|
+ const handleAddPassenger = () => {
|
|
|
|
|
+ if (isWeapp() && !hasPhoneNumber) {
|
|
|
|
|
+ // TODO: 显示提示
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (savedPassengers && savedPassengers.length > 0) {
|
|
|
|
|
+ setShowPassengerSelector(true)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ navigateTo({
|
|
|
|
|
+ url: '/pages/passengers/add-passenger'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 选择已保存的乘客
|
|
|
|
|
+ const handleSelectSavedPassenger = (passenger: Passenger) => {
|
|
|
|
|
+ // 检查是否已经添加过这个乘车人
|
|
|
|
|
+ const existingPassenger = passengers.find(p => p.idNumber === passenger.idNumber)
|
|
|
|
|
+ if (existingPassenger) {
|
|
|
|
|
+ // TODO: 显示提示
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setPassengers([...passengers, passenger])
|
|
|
|
|
+ setShowPassengerSelector(false)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 删除乘客
|
|
|
|
|
+ const handleDeletePassenger = (index: number) => {
|
|
|
|
|
+ const newPassengers = [...passengers]
|
|
|
|
|
+ newPassengers.splice(index, 1)
|
|
|
|
|
+ setPassengers(newPassengers)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 添加新乘客
|
|
|
|
|
+ const handleAddNewPassenger = () => {
|
|
|
|
|
+ setShowPassengerSelector(false)
|
|
|
|
|
+ navigateTo({
|
|
|
|
|
+ url: '/pages/passengers/add-passenger'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 管理乘客
|
|
|
|
|
+ const handleManagePassengers = () => {
|
|
|
|
|
+ setShowPassengerSelector(false)
|
|
|
|
|
+ navigateTo({
|
|
|
|
|
+ url: '/pages/passengers/passengers'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建订单并支付
|
|
|
|
|
+ const handlePay = async () => {
|
|
|
|
|
+ if (!hasPhoneNumber) {
|
|
|
|
|
+ // TODO: 显示提示
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (passengers.length === 0) {
|
|
|
|
|
+ // TODO: 显示提示
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!schedule) {
|
|
|
|
|
+ // TODO: 显示提示
|
|
|
|
|
+ console.error('路线数据未加载完成')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!isCharter && passengers.length > (schedule.availableSeats || 0)) {
|
|
|
|
|
+ // TODO: 显示提示
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 创建订单
|
|
|
|
|
+ const orderData: OrderCreateRequest = {
|
|
|
|
|
+ routeId: Number(routeId),
|
|
|
|
|
+ passengerCount: passengers.length,
|
|
|
|
|
+ passengerSnapshots: passengers,
|
|
|
|
|
+ totalAmount: totalPrice,
|
|
|
|
|
+ routeSnapshot: {
|
|
|
|
|
+ id: schedule.id,
|
|
|
|
|
+ name: schedule.name || '',
|
|
|
|
|
+ pickupPoint: schedule.pickupPoint,
|
|
|
|
|
+ dropoffPoint: schedule.dropoffPoint,
|
|
|
|
|
+ departureTime: schedule.departureTime,
|
|
|
|
|
+ price: schedule.price,
|
|
|
|
|
+ vehicleType: schedule.vehicleType,
|
|
|
|
|
+ travelMode: schedule.travelMode
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const order = await createOrderMutation.mutateAsync(orderData)
|
|
|
|
|
+
|
|
|
|
|
+ // 发起支付
|
|
|
|
|
+ await createPaymentMutation.mutateAsync({
|
|
|
|
|
+ orderId: order.id,
|
|
|
|
|
+ totalAmount: totalPrice,
|
|
|
|
|
+ description: `${activityName || '出行'}订单`
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 调用微信支付
|
|
|
|
|
+ // TODO: 实现微信支付调用
|
|
|
|
|
+
|
|
|
|
|
+ // 支付成功后跳转到支付成功页面
|
|
|
|
|
+ navigateTo({
|
|
|
|
|
+ url: `/pages/pay-success?orderId=${order.id}&totalPrice=${totalPrice}&passengerCount=${passengers.length}`
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('支付失败:', error)
|
|
|
|
|
+ // TODO: 显示错误提示
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isLoadingRoute || !schedule) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <View className="flex-1 flex flex-col items-center justify-center bg-gradient-to-b from-gray-50 to-gray-100 min-h-screen">
|
|
|
|
|
+ <View className="flex flex-col items-center">
|
|
|
|
|
+ <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500 mb-4" />
|
|
|
|
|
+ <Text className="text-gray-600">加载中...</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <View className="flex-1 bg-gradient-to-b from-gray-50 to-gray-100 min-h-screen pb-40">
|
|
|
|
|
+ <Navbar
|
|
|
|
|
+ title="订单确认"
|
|
|
|
|
+ leftIcon="i-heroicons-arrow-left-20-solid"
|
|
|
|
|
+ onClickLeft={() => Taro.navigateBack()}
|
|
|
|
|
+ {...NavbarPresets.primary}
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <ScrollView className="flex-1">
|
|
|
|
|
+ {/* 活动信息 */}
|
|
|
|
|
+ <View className="bg-gradient-to-r from-primary to-primary-dark px-4 py-8 text-white shadow-lg">
|
|
|
|
|
+ <Text className="text-2xl font-bold text-center tracking-wide">{activityName || '活动'}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 班次信息 */}
|
|
|
|
|
+ <View className="px-4 mt-4">
|
|
|
|
|
+ <Card className={`${isCharter ? 'bg-gradient-to-br from-charter-dark to-charter-bg border-2 border-charter shadow-charter' : 'bg-white/95 shadow-lg backdrop-blur-md border border-white/40'} rounded-2xl`}>
|
|
|
|
|
+ <CardContent className="p-6">
|
|
|
|
|
+ <View className="flex justify-between items-center mb-6 pb-4 border-b border-gray-200">
|
|
|
|
|
+ <Text className={`text-xl font-bold tracking-wide ${isCharter ? 'text-charter' : 'text-gray-900'}`}>
|
|
|
|
|
+ {isCharter ? '包车服务' : '班次信息'}
|
|
|
|
|
+ </Text>
|
|
|
|
|
+ <View className="bg-primary text-white px-4 py-2 rounded-full text-xs font-semibold tracking-wide">
|
|
|
|
|
+ {schedule.travelMode === 'charter' ? '专车包车' : (schedule.vehicleType === 'business' ? '商务拼车' : '大巴拼车')}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View className="space-y-4 mb-6">
|
|
|
|
|
+ <View className="flex justify-between items-center">
|
|
|
|
|
+ <Text className={`text-sm font-medium ${isCharter ? 'text-gray-300' : 'text-gray-600'}`}>出发时间</Text>
|
|
|
|
|
+ <Text className={`text-sm font-semibold ${isCharter ? 'text-white' : 'text-gray-900'}`}>{schedule.departureTime}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <View className="flex justify-between items-center">
|
|
|
|
|
+ <Text className={`text-sm font-medium ${isCharter ? 'text-gray-300' : 'text-gray-600'}`}>车辆型号</Text>
|
|
|
|
|
+ <Text className={`text-sm font-semibold ${isCharter ? 'text-white' : 'text-gray-900'}`}>{schedule.vehicleType}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <View className="flex justify-between items-center">
|
|
|
|
|
+ <Text className={`text-sm font-medium ${isCharter ? 'text-gray-300' : 'text-gray-600'}`}>上车地点</Text>
|
|
|
|
|
+ <Text className={`text-sm font-semibold ${isCharter ? 'text-white' : 'text-gray-900'} text-right max-w-48`}>{schedule.pickupPoint}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <View className="flex justify-between items-center">
|
|
|
|
|
+ <Text className={`text-sm font-medium ${isCharter ? 'text-gray-300' : 'text-gray-600'}`}>下车地点</Text>
|
|
|
|
|
+ <Text className={`text-sm font-semibold ${isCharter ? 'text-white' : 'text-gray-900'} text-right max-w-48`}>{schedule.dropoffPoint}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <View className="flex justify-between items-center">
|
|
|
|
|
+ <Text className={`text-sm font-medium ${isCharter ? 'text-gray-300' : 'text-gray-600'}`}>
|
|
|
|
|
+ {isCharter ? '包车价格' : '单人票价'}
|
|
|
|
|
+ </Text>
|
|
|
|
|
+ <Text className={`text-base font-bold ${isCharter ? 'text-charter' : 'text-primary'}`}>
|
|
|
|
|
+ ¥{schedule.price}{isCharter ? '/车' : '/人'}
|
|
|
|
|
+ </Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ {!isCharter && (
|
|
|
|
|
+ <View className="flex justify-between items-center">
|
|
|
|
|
+ <Text className="text-sm font-medium text-gray-600">剩余座位</Text>
|
|
|
|
|
+ <Text className="text-sm font-semibold text-gray-900">{schedule.availableSeats}个</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View className="bg-blue-50 p-4 rounded-xl border-l-4 border-primary">
|
|
|
|
|
+ <Text className="text-xs text-blue-700 font-medium">如需退票,请提前联系客服处理</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 手机号获取 */}
|
|
|
|
|
+ <View className="px-4 mt-4">
|
|
|
|
|
+ <Card className={`${isCharter ? 'bg-gradient-to-br from-charter-dark to-charter-bg border-2 border-charter shadow-charter' : 'bg-white/95 shadow-lg backdrop-blur-md border border-white/40'} rounded-2xl`}>
|
|
|
|
|
+ <CardContent className="p-6">
|
|
|
|
|
+ <Text className={`text-xl font-bold tracking-wide mb-6 ${isCharter ? 'text-charter' : 'text-gray-900'}`}>联系方式</Text>
|
|
|
|
|
+
|
|
|
|
|
+ {hasPhoneNumber ? (
|
|
|
|
|
+ <View className="bg-green-50 p-4 rounded-xl border-2 border-green-200">
|
|
|
|
|
+ <View className="flex items-center gap-4">
|
|
|
|
|
+ <View className="i-heroicons-phone-20-solid w-5 h-5 text-green-600" />
|
|
|
|
|
+ <Text className="text-base font-semibold text-gray-900 flex-1">{phoneNumber}</Text>
|
|
|
|
|
+ <View className="bg-green-100 text-green-800 px-3 py-1 rounded-full text-xs font-medium">已验证</View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="default"
|
|
|
|
|
+ size="lg"
|
|
|
|
|
+ openType="getPhoneNumber"
|
|
|
|
|
+ onGetPhoneNumber={handleGetPhoneNumber}
|
|
|
|
|
+ className="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ <View className="flex items-center justify-center">
|
|
|
|
|
+ <View className="i-heroicons-phone-20-solid w-5 h-5 mr-2" />
|
|
|
|
|
+ 微信一键获取手机号
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 乘车人信息 */}
|
|
|
|
|
+ <View className="px-4 mt-4">
|
|
|
|
|
+ <Card className={`${isCharter ? 'bg-gradient-to-br from-charter-dark to-charter-bg border-2 border-charter shadow-charter' : 'bg-white/95 shadow-lg backdrop-blur-md border border-white/40'} rounded-2xl`}>
|
|
|
|
|
+ <CardContent className="p-6">
|
|
|
|
|
+ <View className="flex justify-between items-center mb-6">
|
|
|
|
|
+ <Text className={`text-xl font-bold tracking-wide ${isCharter ? 'text-charter' : 'text-gray-900'}`}>
|
|
|
|
|
+ {isCharter ? '乘车人信息' : '购票人信息'}
|
|
|
|
|
+ </Text>
|
|
|
|
|
+ <View className="bg-primary text-white px-3 py-1 rounded-full text-xs font-semibold">
|
|
|
|
|
+ {passengers.length}{isCharter ? '人' : '张票'}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {passengers.length > 0 && (
|
|
|
|
|
+ <View className="mb-6 space-y-3">
|
|
|
|
|
+ {passengers.map((passenger, index) => (
|
|
|
|
|
+ <Card key={index} className="bg-blue-50/80 border border-blue-100 rounded-xl">
|
|
|
|
|
+ <CardContent className="p-4">
|
|
|
|
|
+ <View className="flex justify-between items-start">
|
|
|
|
|
+ <View className="flex-1">
|
|
|
|
|
+ <Text className="text-base font-semibold text-gray-900 mb-2">{passenger.name}</Text>
|
|
|
|
|
+ <View className="flex items-center gap-3 mb-2">
|
|
|
|
|
+ <View className="bg-primary text-white px-3 py-1 rounded-full text-xs font-medium">
|
|
|
|
|
+ {passenger.idType}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <Text className="text-xs text-gray-600 font-mono">{passenger.idNumber}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <Text className="text-xs text-gray-600 font-mono">{passenger.phone}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="destructive"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={() => handleDeletePassenger(index)}
|
|
|
|
|
+ className="min-w-16"
|
|
|
|
|
+ >
|
|
|
|
|
+ 删除
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ size="lg"
|
|
|
|
|
+ onClick={handleAddPassenger}
|
|
|
|
|
+ className="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ <View className="flex items-center justify-center">
|
|
|
|
|
+ <View className="i-heroicons-plus-20-solid w-5 h-5 mr-2" />
|
|
|
|
|
+ {passengers.length === 0 ? '添加乘车人' : '继续添加'}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </Button>
|
|
|
|
|
+
|
|
|
|
|
+ {!isCharter && passengers.length > 0 && (
|
|
|
|
|
+ <View className="text-center mt-4">
|
|
|
|
|
+ <Text className="text-xs text-gray-500">最多可购买{schedule.availableSeats}张票</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 价格统计 */}
|
|
|
|
|
+ <View className="px-4 mt-4 mb-6">
|
|
|
|
|
+ <Card className={`${isCharter ? 'bg-gradient-to-br from-charter-dark to-charter-bg border-2 border-charter shadow-charter' : 'bg-white/95 shadow-lg backdrop-blur-md border border-white/40'} rounded-2xl`}>
|
|
|
|
|
+ <CardContent className="p-6">
|
|
|
|
|
+ <View className="mb-6 space-y-3">
|
|
|
|
|
+ {isCharter ? (
|
|
|
|
|
+ <View className="flex justify-between items-center">
|
|
|
|
|
+ <Text className={`text-sm font-medium ${isCharter ? 'text-gray-300' : 'text-gray-600'}`}>包车费用</Text>
|
|
|
|
|
+ <Text className={`text-sm font-semibold ${isCharter ? 'text-white' : 'text-gray-900'}`}>¥{schedule.price}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <View className="flex justify-between items-center">
|
|
|
|
|
+ <Text className="text-sm font-medium text-gray-600">票价 × {passengers.length}人</Text>
|
|
|
|
|
+ <Text className="text-sm font-semibold text-gray-900">¥{schedule.price} × {passengers.length}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ <View className="flex justify-between items-center">
|
|
|
|
|
+ <Text className={`text-sm font-medium ${isCharter ? 'text-gray-300' : 'text-gray-600'}`}>原价小计</Text>
|
|
|
|
|
+ <Text className={`text-sm font-semibold ${isCharter ? 'text-white' : 'text-gray-900'}`}>¥{originalPrice}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View className="flex justify-between items-center pt-6 border-t border-gray-200">
|
|
|
|
|
+ <Text className={`text-xl font-bold tracking-wide ${isCharter ? 'text-charter' : 'text-gray-900'}`}>实付金额</Text>
|
|
|
|
|
+ <Text className={`text-3xl font-bold ${isCharter ? 'text-charter' : 'text-primary'}`}>¥{totalPrice}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </ScrollView>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 支付按钮 */}
|
|
|
|
|
+ <View className="fixed bottom-4 left-4 right-4 z-100">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="default"
|
|
|
|
|
+ size="lg"
|
|
|
|
|
+ onClick={handlePay}
|
|
|
|
|
+ disabled={createOrderMutation.isPending || createPaymentMutation.isPending}
|
|
|
|
|
+ className="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ {createOrderMutation.isPending || createPaymentMutation.isPending ? (
|
|
|
|
|
+ <View className="flex items-center justify-center">
|
|
|
|
|
+ <View className="i-heroicons-arrow-path-20-solid animate-spin w-5 h-5 mr-2" />
|
|
|
|
|
+ 处理中...
|
|
|
|
|
+ </View>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <View className="flex items-center justify-center">
|
|
|
|
|
+ <View className="i-heroicons-credit-card-20-solid w-5 h-5 mr-2" />
|
|
|
|
|
+ {isCharter ? '立即包车支付' : '立即购票支付'} ¥{totalPrice}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 乘车人选择器模态框 */}
|
|
|
|
|
+ <Dialog open={showPassengerSelector} onOpenChange={setShowPassengerSelector}>
|
|
|
|
|
+ <DialogContent className="max-w-md max-h-80vh overflow-hidden">
|
|
|
|
|
+ <DialogHeader>
|
|
|
|
|
+ <DialogTitle className="text-lg font-semibold text-gray-900">选择乘车人</DialogTitle>
|
|
|
|
|
+ </DialogHeader>
|
|
|
|
|
+
|
|
|
|
|
+ <ScrollView className="max-h-60vh overflow-y-auto p-1">
|
|
|
|
|
+ {!savedPassengers || savedPassengers.length === 0 ? (
|
|
|
|
|
+ <View className="text-center py-20">
|
|
|
|
|
+ <View className="text-6xl mb-3 opacity-60">👥</View>
|
|
|
|
|
+ <Text className="text-lg text-gray-600 mb-1 font-semibold">暂无已保存的乘车人</Text>
|
|
|
|
|
+ <Text className="text-sm text-gray-500">添加后可快速选择</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <View className="space-y-3">
|
|
|
|
|
+ {savedPassengers.map((passenger) => (
|
|
|
|
|
+ <Card
|
|
|
|
|
+ key={passenger.id}
|
|
|
|
|
+ className="bg-blue-50/80 border border-blue-100 rounded-xl transition-all duration-300 active:translate-y-0.5 active:shadow-md"
|
|
|
|
|
+ onClick={() => handleSelectSavedPassenger(passenger)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <CardContent className="p-4">
|
|
|
|
|
+ <View className="flex items-center justify-between">
|
|
|
|
|
+ <View className="flex-1">
|
|
|
|
|
+ <Text className="text-base font-semibold text-gray-900 mb-2">{passenger.name}</Text>
|
|
|
|
|
+ <View className="flex items-center mb-2">
|
|
|
|
|
+ <View className="bg-primary text-white px-3 py-1 rounded-full text-xs font-medium mr-3">
|
|
|
|
|
+ {passenger.idType}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <Text className="text-xs text-gray-600 font-mono">{passenger.idNumber}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <Text className="text-xs text-gray-600 font-mono">{passenger.phone}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <View className="i-heroicons-plus-20-solid w-5 h-5 text-primary ml-4" />
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </ScrollView>
|
|
|
|
|
+
|
|
|
|
|
+ <DialogFooter className="pt-4 border-t border-gray-100 bg-gray-50/50 flex flex-row gap-4">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ onClick={handleAddNewPassenger}
|
|
|
|
|
+ className="flex-1"
|
|
|
|
|
+ >
|
|
|
|
|
+ <View className="flex items-center justify-center">
|
|
|
|
|
+ <View className="i-heroicons-plus-20-solid w-4 h-4 mr-2" />
|
|
|
|
|
+ 添加新乘车人
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ {savedPassengers && savedPassengers.length > 0 && (
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={handleManagePassengers}
|
|
|
|
|
+ className="flex-1"
|
|
|
|
|
+ >
|
|
|
|
|
+ <View className="flex items-center justify-center">
|
|
|
|
|
+ <View className="i-heroicons-cog-6-tooth-20-solid w-4 h-4 mr-2" />
|
|
|
|
|
+ 管理乘车人
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </DialogFooter>
|
|
|
|
|
+ </DialogContent>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|