| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- import { View, ScrollView, Text } from '@tarojs/components'
- import { useQuery, useMutation } from '@tanstack/react-query'
- import { useState, useEffect } from 'react'
- import Taro from '@tarojs/taro'
- import { deliveryAddressClient, orderClient } from '@/api'
- import { InferResponseType, InferRequestType } from 'hono'
- import { Navbar } from '@/components/ui/navbar'
- import { Card } from '@/components/ui/card'
- import { Button } from '@/components/ui/button'
- import { useAuth } from '@/utils/auth'
- import { useCart } from '@/utils/cart'
- import { Image } from '@/components/ui/image'
- type AddressResponse = InferResponseType<typeof deliveryAddressClient.$get, 200>
- type Address = AddressResponse['data'][0]
- type CreateOrderRequest = InferRequestType<typeof orderClient.$post>['json']
- interface CheckoutItem {
- id: number
- name: string
- price: number
- image: string
- quantity: number
- }
- export default function OrderSubmitPage() {
- const { user } = useAuth()
- const { clearCart } = useCart()
- const [selectedAddress, setSelectedAddress] = useState<Address | null>(null)
- const [orderItems, setOrderItems] = useState<CheckoutItem[]>([])
- const [totalAmount, setTotalAmount] = useState(0)
- // 获取地址列表
- const { data: addresses } = useQuery({
- queryKey: ['delivery-addresses', user?.id],
- queryFn: async () => {
- const response = await deliveryAddressClient.$get({
- query: {
- page: 1,
- pageSize: 100,
- filters: JSON.stringify({ userId: user?.id })
- }
- })
- if (response.status !== 200) {
- throw new Error('获取地址失败')
- }
- return response.json()
- },
- enabled: !!user?.id,
- })
- // 创建订单
- const createOrderMutation = useMutation({
- mutationFn: async () => {
- if (!selectedAddress || orderItems.length === 0) {
- throw new Error('请完善订单信息')
- }
- const goodsDetail = JSON.stringify(
- orderItems.map(item => ({
- goodsId: item.id,
- name: item.name,
- price: item.price,
- num: item.quantity
- }))
- )
- const orderData: CreateOrderRequest = {
- orderNo: `ORD${Date.now()}`,
- userId: user!.id,
- amount: totalAmount,
- payAmount: totalAmount,
- goodsDetail,
- addressId: selectedAddress.id,
- recevierName: selectedAddress.name,
- receiverMobile: selectedAddress.phone,
- address: `${selectedAddress.province?.name || ''}${selectedAddress.city?.name || ''}${selectedAddress.district?.name || ''}${selectedAddress.town?.name || ''}${selectedAddress.address}`,
- orderType: 1,
- payType: 0,
- payState: 0,
- state: 0
- }
- const response = await orderClient.$post({ json: orderData })
- if (response.status !== 201) {
- throw new Error('创建订单失败')
- }
- return response.json()
- },
- onSuccess: (data) => {
- // 清空购物车
- clearCart()
-
- Taro.showToast({
- title: '订单创建成功',
- icon: 'success'
- })
-
- // 跳转到订单详情页
- Taro.redirectTo({
- url: `/pages/order-detail/index?id=${data.id}`
- })
- },
- onError: (error) => {
- Taro.showToast({
- title: error.message || '创建订单失败',
- icon: 'none'
- })
- }
- })
- // 页面加载时获取订单数据
- useEffect(() => {
- // 从购物车获取数据
- const checkoutData = Taro.getStorageSync('checkoutItems')
- const cartData = Taro.getStorageSync('mini_cart')
-
- if (checkoutData && checkoutData.items) {
- setOrderItems(checkoutData.items)
- setTotalAmount(checkoutData.totalAmount)
- } else if (cartData && cartData.items) {
- // 使用购物车数据
- const items = cartData.items
- const total = items.reduce((sum: number, item: CheckoutItem) =>
- sum + (item.price * item.quantity), 0)
- setOrderItems(items)
- setTotalAmount(total)
- }
- // 设置默认地址
- if (addresses?.data) {
- const defaultAddress = addresses.data.find(addr => addr.isDefault === 1)
- setSelectedAddress(defaultAddress || addresses.data[0] || null)
- }
- }, [addresses])
- // 选择地址
- const handleSelectAddress = () => {
- Taro.navigateTo({
- url: '/pages/address-manage/index'
- })
- }
- // 提交订单
- const handleSubmitOrder = () => {
- if (!selectedAddress) {
- Taro.showToast({
- title: '请选择收货地址',
- icon: 'none'
- })
- return
- }
- createOrderMutation.mutate()
- }
- return (
- <View className="min-h-screen bg-gray-50">
- <Navbar
- title="确认订单"
- leftIcon="i-heroicons-chevron-left-20-solid"
- onClickLeft={() => Taro.navigateBack()}
- />
-
- <ScrollView className="h-screen pt-12 pb-20">
- <View className="px-4 space-y-4">
- {/* 收货地址 */}
- <Card>
- <View className="p-4">
- <View className="flex items-center justify-between mb-3">
- <Text className="text-lg font-bold">收货地址</Text>
- <Button
- size="sm"
- variant="ghost"
- onClick={handleSelectAddress}
- >
- 选择地址
- </Button>
- </View>
-
- {selectedAddress ? (
- <View>
- <View className="flex items-center mb-2">
- <Text className="font-medium mr-3">{selectedAddress.name}</Text>
- <Text className="text-gray-600">{selectedAddress.phone}</Text>
- {selectedAddress.isDefault === 1 && (
- <Text className="ml-2 px-2 py-1 bg-red-100 text-red-600 text-xs rounded">
- 默认
- </Text>
- )}
- </View>
- <Text className="text-sm text-gray-700">
- {selectedAddress.province?.name || ''}
- {selectedAddress.city?.name || ''}
- {selectedAddress.district?.name || ''}
- {selectedAddress.town?.name || ''}
- {selectedAddress.address}
- </Text>
- </View>
- ) : (
- <Button
- className="w-full"
- onClick={handleSelectAddress}
- >
- 请选择收货地址
- </Button>
- )}
- </View>
- </Card>
- {/* 商品列表 */}
- <Card>
- <View className="p-4">
- <Text className="text-lg font-bold mb-4">商品信息</Text>
-
- {orderItems.map((item) => (
- <View key={item.id} className="flex items-center py-3 border-b border-gray-100 last:border-b-0">
- <Image
- src={item.image}
- className="w-16 h-16 rounded-lg mr-3"
- mode="aspectFill"
- />
-
- <View className="flex-1">
- <Text className="text-sm font-medium mb-1">{item.name}</Text>
- <Text className="text-sm text-gray-600">
- ¥{item.price.toFixed(2)} × {item.quantity}
- </Text>
- </View>
-
- <Text className="text-red-500 font-bold">
- ¥{(item.price * item.quantity).toFixed(2)}
- </Text>
- </View>
- ))}
- </View>
- </Card>
- {/* 订单金额 */}
- <Card>
- <View className="p-4">
- <Text className="text-lg font-bold mb-4">订单金额</Text>
-
- <View className="space-y-2">
- <View className="flex justify-between">
- <Text className="text-gray-600">商品金额</Text>
- <Text>¥{totalAmount.toFixed(2)}</Text>
- </View>
-
- <View className="flex justify-between">
- <Text className="text-gray-600">运费</Text>
- <Text>¥0.00</Text>
- </View>
-
- <View className="flex justify-between text-lg font-bold border-t pt-2">
- <Text>实付款</Text>
- <Text className="text-red-500">¥{totalAmount.toFixed(2)}</Text>
- </View>
- </View>
- </View>
- </Card>
- {/* 支付方式 */}
- <Card>
- <View className="p-4">
- <Text className="text-lg font-bold mb-4">支付方式</Text>
- <View className="flex items-center p-3 border rounded-lg">
- <View className="i-heroicons-credit-card-20-solid w-5 h-5 mr-3 text-blue-500" />
- <Text>微信支付</Text>
- </View>
- </View>
- </Card>
- </View>
- </ScrollView>
- {/* 底部提交栏 */}
- <View className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-3">
- <View className="flex items-center justify-between">
- <View>
- <Text className="text-sm text-gray-600">合计: </Text>
- <Text className="text-lg font-bold text-red-500">
- ¥{totalAmount.toFixed(2)}
- </Text>
- </View>
-
- <Button
- onClick={handleSubmitOrder}
- disabled={!selectedAddress || orderItems.length === 0 || createOrderMutation.isPending}
- loading={createOrderMutation.isPending}
- >
- 提交订单
- </Button>
- </View>
- </View>
- </View>
- )
- }
|