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 [skuId, setSkuId] = 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 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]); const selectedProduct = options?.products.find((p) => p.id === productId); const skuOptions = (selectedProduct?.skus ?? []).filter((s) => s.status === 'ON_SALE'); const selectedSku = skuOptions.find((s) => s.id === skuId) || skuOptions.find((s) => s.id === selectedProduct?.defaultSkuId) || skuOptions[0]; const allowOnline = selectedSku ? selectedSku.allowOnlinePurchase !== false : selectedProduct ? selectedProduct.allowOnlinePurchase !== false : true; const allowOnSite = selectedSku ? !!selectedSku.allowOnSitePickup : !!selectedProduct?.allowOnSitePickup; const allowCrossCity = selectedSku ? selectedSku.allowCrossCityDelivery !== false : selectedProduct ? selectedProduct.allowCrossCityDelivery !== false : true; useEffect(() => { if (!open || !selectedProduct) return; const def = skuOptions.find((s) => s.id === selectedProduct.defaultSkuId) || skuOptions.find((s) => s.isDefault) || skuOptions[0]; if (def && skuId !== def.id && (!skuId || !skuOptions.some((s) => s.id === skuId))) { setSkuId(def.id); setQuantity(def.saleUnit === 'BOX' ? 1 : Math.max(quantity, 2)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, productId, selectedProduct?.id]); useEffect(() => { if (!open || !selectedProduct) return; if (!allowOnline && allowOnSite && deliveryMode !== 'ON_SITE_PICKUP') { setDeliveryMode('ON_SITE_PICKUP'); return; } if (allowOnline && !allowOnSite && deliveryMode !== 'ADDRESS') { setDeliveryMode('ADDRESS'); } }, [open, selectedProduct, allowOnline, allowOnSite, deliveryMode]); 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, skuId: skuId || undefined, 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, skuId, quantity, deliveryMode, region?.city, region?.district, step]); useEffect(() => () => stopPoll(), []); function resetForm() { stopPoll(); setPhone(''); setReceiverName(''); setRegionCodes([]); setAddressDetail(''); setQuantity(2); setProductId(undefined); setSkuId(undefined); setPromoCodeId(undefined); setDeliveryMode('ADDRESS'); setAutoReceive(false); setPreview(null); setProductId(options?.products[0]?.id); setStep('form'); setCreated(null); setCodeUrl(null); setPayLoading(false); } function handleClose() { resetForm(); onClose(); } function validateForm(): string | null { if (!/^1\d{10}$/.test(phone.trim())) return '请输入有效手机号'; if (!productId) return '请选择商品'; if (deliveryMode === 'ADDRESS') { if (!allowOnline) return '该商品不支持线上购买'; if (!region?.province || !region?.city || !region?.district) return '请选择省市区'; if (!addressDetail.trim()) return '请填写详细地址'; if (!autoReceive) return '配送到址须勾选同意自动收货'; } else if (!allowOnSite) { 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, skuId: skuId || 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' }), }); if (pay.mode === 'mock') { message.success(`支付成功:${order.orderNo}`); resetForm(); onSuccess({ id: order.id, orderNo: order.orderNo }); return; } 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); } } 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))} /> { setSkuId(id); const sku = skuOptions.find((s) => s.id === id); if (sku?.saleUnit === 'BOX') setQuantity(1); }} options={skuOptions.map((s) => ({ value: s.id, label: `${s.specText || '默认'} · ¥${fmtMoney(s.price)} · ${s.saleUnit === 'BOX' ? `${s.bottlesPerUnit}瓶/箱` : '瓶'}`, }))} /> ) : null} setQuantity(Math.max(1, Number(v) || 1))} style={{ width: '100%' }} /> {(options?.promoCodes.length ?? 0) > 0 ? ( 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)}
) : (
请完善商品与数量
)}
)}
); }