import { useEffect, useMemo, useRef, useState } from 'react'; import { Alert, Button, Checkbox, Form, Input, InputNumber, Modal, QRCode, Radio, Select, Space, Typography, message, } from 'antd'; import type { HqProxyOrderCreateRequest, PartnerProxyDeliveryMode, PartnerProxyOrderOptions, PartnerProxyOrderPreviewResult, ProxyOrderCreateResponse, ProxyOrderPayResponse, } from '@dukang/shared-types'; import ChinaRegionCascader from './ChinaRegionCascader'; import { parseRegionCodes } from '../lib/china-region'; import { request } from '../lib/api'; type ProxyOrderModalProps = { open: boolean; onClose: () => void; onSuccess: (order: { id: string; orderNo: string }) => void; }; type PayStatus = { id: string; orderNo: string; status: string; payStatus: string; }; function fmtMoney(n: number) { return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } export default function ProxyOrderModal({ open, onClose, onSuccess }: ProxyOrderModalProps) { const [options, setOptions] = useState(null); const [loadingOptions, setLoadingOptions] = useState(false); const [phone, setPhone] = useState(''); const [receiverName, setReceiverName] = useState(''); const [regionCodes, setRegionCodes] = useState([]); const [addressDetail, setAddressDetail] = useState(''); const [productId, setProductId] = useState(); const [quantity, setQuantity] = useState(2); const [promoCodeId, setPromoCodeId] = useState(); const [deliveryMode, setDeliveryMode] = useState('ADDRESS'); const [autoReceive, setAutoReceive] = useState(false); const [preview, setPreview] = useState(null); const [previewLoading, setPreviewLoading] = useState(false); const [submitting, setSubmitting] = useState(false); const [step, setStep] = useState<'form' | 'pay'>('form'); const [created, setCreated] = useState(null); const [codeUrl, setCodeUrl] = useState(null); const [payLoading, setPayLoading] = useState(false); const [mockConfirming, setMockConfirming] = useState(false); const pollRef = useRef(null); const region = useMemo(() => parseRegionCodes(regionCodes), [regionCodes]); function stopPoll() { if (pollRef.current != null) { window.clearInterval(pollRef.current); pollRef.current = null; } } useEffect(() => { if (!open) return; setLoadingOptions(true); request('/admin/proxy-orders/options') .then((data) => { setOptions(data); if (data.products[0]) setProductId(data.products[0].id); }) .catch((e) => message.error(e instanceof Error ? e.message : '加载失败')) .finally(() => setLoadingOptions(false)); }, [open]); useEffect(() => { if (!open || !productId || quantity < 1 || step !== 'form') { if (step === 'form') setPreview(null); return; } const timer = window.setTimeout(() => { setPreviewLoading(true); request('/admin/proxy-orders/preview', { method: 'POST', body: JSON.stringify({ productId, quantity, deliveryMode, receiverCity: deliveryMode === 'ADDRESS' ? region?.city || undefined : undefined, receiverDistrict: deliveryMode === 'ADDRESS' ? region?.district || undefined : undefined, }), }) .then(setPreview) .catch(() => setPreview(null)) .finally(() => setPreviewLoading(false)); }, 300); return () => window.clearTimeout(timer); }, [open, productId, quantity, deliveryMode, region?.city, region?.district, step]); useEffect(() => () => stopPoll(), []); function resetForm() { stopPoll(); setPhone(''); setReceiverName(''); setRegionCodes([]); setAddressDetail(''); setQuantity(2); setPromoCodeId(undefined); setDeliveryMode('ADDRESS'); setAutoReceive(false); setPreview(null); setProductId(options?.products[0]?.id); setStep('form'); setCreated(null); setCodeUrl(null); setPayLoading(false); setMockConfirming(false); } function handleClose() { resetForm(); onClose(); } function validateForm(): string | null { if (!/^1\d{10}$/.test(phone.trim())) return '请输入有效手机号'; if (!productId) return '请选择商品'; if (deliveryMode === 'ADDRESS') { if (!region?.province || !region?.city || !region?.district) return '请选择省市区'; if (!addressDetail.trim()) return '请填写详细地址'; if (!autoReceive) return '配送到址须勾选同意自动收货'; } else { const product = options?.products.find((p) => p.id === productId); if (product && product.allowOnSitePickup === false) return '该商品不支持现场提货'; } if (!preview) return '请等待费用计算完成'; return null; } function startPoll(orderId: string) { stopPoll(); pollRef.current = window.setInterval(() => { void request(`/admin/proxy-orders/${orderId}/pay-status`) .then((st) => { if (st.payStatus === 'PAID') { stopPoll(); message.success(`支付成功:${st.orderNo}`); const payload = { id: st.id, orderNo: st.orderNo }; resetForm(); onSuccess(payload); } }) .catch(() => undefined); }, 2000); } async function submit() { const err = validateForm(); if (err) { message.warning(err); return; } const payload: HqProxyOrderCreateRequest = { phone: phone.trim(), deliveryMode, autoReceive: deliveryMode === 'ADDRESS' ? true : undefined, receiverName: receiverName.trim() || undefined, province: deliveryMode === 'ADDRESS' ? region?.province : undefined, city: deliveryMode === 'ADDRESS' ? region?.city : undefined, district: deliveryMode === 'ADDRESS' ? region?.district : undefined, addressDetail: deliveryMode === 'ADDRESS' ? addressDetail.trim() : undefined, productId: productId!, quantity, promoCodeId: promoCodeId || undefined, }; setSubmitting(true); try { const order = await request('/admin/proxy-orders', { method: 'POST', body: JSON.stringify(payload), }); setCreated(order); setStep('pay'); setPayLoading(true); const pay = await request(`/admin/proxy-orders/${order.id}/pay`, { method: 'POST', body: JSON.stringify({ payMethod: 'NATIVE' }), }); setCodeUrl(pay.codeUrl ?? null); startPoll(order.id); } catch (e) { message.error(e instanceof Error ? e.message : '下单失败'); setStep('form'); setCreated(null); } finally { setSubmitting(false); setPayLoading(false); } } async function handleMockConfirm() { if (!created) return; setMockConfirming(true); try { const st = await request(`/admin/proxy-orders/${created.id}/pay/mock-confirm`, { method: 'POST', body: '{}', }); stopPoll(); message.success(`支付成功:${st.orderNo}`); const payload = { id: st.id, orderNo: st.orderNo }; resetForm(); onSuccess(payload); } catch (e) { message.error(e instanceof Error ? e.message : '模拟支付失败'); } finally { setMockConfirming(false); } } const deliveryLabel = preview?.deliveryType === 'ON_SITE_PICKUP' ? '现场提货' : preview?.deliveryType === 'CROSS_CITY' ? '跨城配送' : '同城配送'; return ( ) : ( ) } > {step === 'pay' && created ? (
{payLoading && !codeUrl ? ( 正在生成收款码… ) : codeUrl ? ( 请使用微信扫一扫完成支付(本地可用下方模拟支付) {codeUrl} ) : ( 收款码生成失败,请关闭后重试 )}
) : ( <>
setPhone(e.target.value.replace(/\D/g, '').slice(0, 11))} /> ({ value: p.id, label: `${p.code} · ${p.name}`, }))} /> ) : null} { setDeliveryMode(e.target.value); if (e.target.value !== 'ADDRESS') setAutoReceive(false); }} options={[ { value: 'ADDRESS', label: '配送到址' }, { value: 'ON_SITE_PICKUP', label: '现场提货' }, ]} /> {deliveryMode === 'ADDRESS' ? ( <> setReceiverName(e.target.value)} /> setAddressDetail(e.target.value)} /> setAutoReceive(e.target.checked)}> 同意自动收货(配送到址必选) ) : null}
费用预览 {previewLoading ? (
计算中…
) : preview ? (
履约:{deliveryLabel}
商品金额:¥{fmtMoney(preview.productAmount)}
实付:¥{fmtMoney(preview.payAmount)}
好客权益:¥{fmtMoney(preview.benefitAmount)}
) : (
请完善商品与数量
)}
)}
); }