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}
}
{
setShowPhoneVerify(false);
setPendingSubmit(false);
}}
onSuccess={handlePhoneVerified}
/>
);
}