import { useEffect, useMemo, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import SubPageHeader from '../components/SubPageHeader'; import AppImage from '@dukang/shared-ui/AppImage'; import { request } from '../lib/api'; import { buildProductDetailUrl } from '../lib/navigation'; import { STITCH_ORDER_PRODUCT_IMAGE } from '../lib/order-images'; import { tryGetClientGpsLocation } from '../lib/client-location'; import { getProductMainImage } from '../lib/product-images'; import PhoneVerifySheet from '../components/PhoneVerifySheet'; import { useUserSession } from '../contexts/UserSessionContext'; type Address = { id: string; receiverName: string; phone: string; province: string; city: string; district: string; detail: string; isDefault?: number; }; type PreviewProduct = { id: string; name: string; spec: string; subtitle?: string; price: number; }; type OrderPreview = { product: PreviewProduct; quantity: number; deliveryType: 'LOCAL' | 'CROSS_CITY'; productAmount: number; freightPayType: 'COD' | null; payAmount: number; benefitAmount: number; city?: { localMinQty: number; crossMinQty: number }; }; function maskPhone(phone: string) { return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); } function formatAddress(a: Address) { return `${a.province}${a.city}${a.district}${a.detail}`; } export default function OrderConfirmPage() { const [params] = useSearchParams(); const navigate = useNavigate(); const { phoneVerified, refreshProfile } = useUserSession(); const [showPhoneVerify, setShowPhoneVerify] = useState(false); const [pendingSubmit, setPendingSubmit] = useState(false); const productId = params.get('productId') || ''; const forceCross = params.get('cross') === '1'; const [quantity, setQuantity] = useState(Number(params.get('qty') || 2)); const [addresses, setAddresses] = useState([]); const [addressId, setAddressId] = useState(params.get('addressId') || ''); const [preview, setPreview] = useState(null); const [loading, setLoading] = useState(false); const [msg, setMsg] = useState(''); useEffect(() => { request('USER_H5', '/user/addresses').then((list) => { setAddresses(list); const fromUrl = params.get('addressId'); if (fromUrl && list.some((a) => String(a.id) === fromUrl)) { setAddressId(fromUrl); return; } const def = list.find((a) => a.isDefault === 1) || list[0]; if (def) setAddressId(String(def.id)); }); }, [params]); useEffect(() => { if (!productId || !addressId) return; request('USER_H5', '/trade/orders/preview', { method: 'POST', body: JSON.stringify({ productId, quantity, addressId }), }) .then(setPreview) .catch((e) => setMsg(e instanceof Error ? e.message : String(e))); }, [productId, quantity, addressId]); function updateQuantity(next: number) { const delivery = forceCross || preview?.deliveryType === 'CROSS_CITY' ? 'CROSS_CITY' : 'LOCAL'; const localMin = preview?.city?.localMinQty ?? 2; const crossMin = preview?.city?.crossMinQty ?? 6; const min = delivery === 'LOCAL' ? localMin : crossMin; if (next < min) { setMsg( delivery === 'LOCAL' ? `同城配送至少购买 ${min} 瓶` : `跨城配送至少购买 ${min} 瓶(1箱)`, ); return; } setMsg(''); setQuantity(next); const qs = new URLSearchParams(params); qs.set('qty', String(next)); navigate({ search: qs.toString() }, { replace: true }); } const selectedAddress = useMemo( () => addresses.find((a) => String(a.id) === addressId), [addresses, addressId], ); const isCross = forceCross || preview?.deliveryType === 'CROSS_CITY'; const minQty = isCross ? (preview?.city?.crossMinQty ?? 6) : (preview?.city?.localMinQty ?? 2); const productIndex = productId ? Math.max(0, Number(productId) - 1) : 0; const productImage = productIndex === 0 ? STITCH_ORDER_PRODUCT_IMAGE : getProductMainImage(productIndex); async function doSubmit() { const clientLocation = await tryGetClientGpsLocation(); const order = await request<{ id: string }>('USER_H5', '/trade/orders', { method: 'POST', body: JSON.stringify({ productId, quantity, addressId, ...(clientLocation ? { clientLocation } : {}), }), }); const qs = new URLSearchParams(); qs.set('orderId', order.id); qs.set('productId', productId); qs.set('qty', String(quantity)); qs.set('addressId', addressId); if (forceCross) qs.set('cross', '1'); navigate(`/pay?${qs.toString()}`); } async function submit() { if (!addressId) { setMsg('请选择收货地址'); return; } if (!phoneVerified) { setPendingSubmit(true); setShowPhoneVerify(true); return; } setLoading(true); setMsg(''); try { await doSubmit(); } catch (e) { setMsg(e instanceof Error ? e.message : '下单失败'); } finally { setLoading(false); } } async function handlePhoneVerified() { await refreshProfile(); if (!pendingSubmit) return; setPendingSubmit(false); setLoading(true); setMsg(''); try { await doSubmit(); } catch (e) { setMsg(e instanceof Error ? e.message : '下单失败'); } finally { setLoading(false); } } return (
navigate(buildProductDetailUrl(productId))} />
{isCross && (
warning

提示:该地址超出同城配送范围,将由总部通过物流快递发货。物流费用需由您承担(到付),请确认是否继续。

)} {preview && ( <>

{preview.product.name}

{preview.product.spec || preview.product.subtitle}

¥{preview.product.price}
{quantity}
好客权益 本单可享好客权益 ¥{preview.benefitAmount}
配送方式

{isCross ? '物流配送' : '小飞侠配送'}

{!isCross && (

预计24小时内送达

)}
商品总额 ¥{preview.productAmount}
运费 {isCross ? '到付' : '免运费'}
合计 ¥{preview.payAmount}
)} {!preview && productId && addressId && (
加载订单信息...
)} {msg &&

{msg}

}
实付: ¥{preview?.payAmount ?? '—'}
{ setShowPhoneVerify(false); setPendingSubmit(false); }} onSuccess={handlePhoneVerified} />
); }