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 { getProductMainImage } from '../lib/product-images'; import { track } from '../lib/analytics'; import PhoneVerifySheet from '../components/PhoneVerifySheet'; import { useUserSession } from '../contexts/UserSessionContext'; import { tryGetClientGpsLocation } from '../lib/client-location'; import { applyWechatLoginResult, ensureWechatAuthForPay, handleWechatAuthCallback, } from '../lib/wechat-auth'; import { isWechatEnv } from '../lib/weixin'; 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; mainImageUrl?: string | null; carouselUrls?: string[] | null; }; 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 [showWechatBindPhone, setShowWechatBindPhone] = useState(false); const [wxSessionKey, setWxSessionKey] = useState(null); 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 [previewLoading, setPreviewLoading] = useState(false); 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) { track('order_confirm_view', { refType: 'PRODUCT', refId: productId, productId, quantity }); } }, [productId, quantity]); useEffect(() => { if (!isWechatEnv()) return; handleWechatAuthCallback() .then((result) => { if (result && applyWechatLoginResult(result)) { void refreshProfile(); } }) .catch(() => {}); }, [refreshProfile]); useEffect(() => { if (!productId) return; let cancelled = false; setPreviewLoading(true); const body: { productId: string; quantity: number; addressId?: string } = { productId, quantity, }; if (addressId) body.addressId = addressId; request('USER_H5', '/trade/orders/preview', { method: 'POST', body: JSON.stringify(body), }) .then((data) => { if (!cancelled) setPreview(data); }) .catch((e) => { if (!cancelled) { setPreview(null); setMsg(e instanceof Error ? e.message : String(e)); } }) .finally(() => { if (!cancelled) setPreviewLoading(false); }); return () => { cancelled = true; }; }, [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 productImage = preview?.product ? getProductMainImage(preview.product) : getProductMainImage(); async function doSubmit() { let clientLocation = null; try { clientLocation = await tryGetClientGpsLocation(); } catch { /* GPS 获取失败不阻塞下单 */ } 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; } const authResult = await ensureWechatAuthForPay(); if (!authResult.ok) { if ('needBindPhone' in authResult) { setWxSessionKey(authResult.wxSessionKey); setShowWechatBindPhone(true); setPendingSubmit(true); return; } return; } setLoading(true); setMsg(''); try { await doSubmit(); } catch (e) { setMsg(e instanceof Error ? e.message : '下单失败'); } finally { setLoading(false); } } async function handleWechatPhoneBound() { await refreshProfile(); if (!pendingSubmit) return; setPendingSubmit(false); 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}
配送方式

{!addressId ? '选择地址后确认' : isCross ? '物流配送' : '小飞侠配送'}

{addressId && !isCross && (

预计24小时内送达

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

{msg}

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