"use client"; import { useCallback, useRef, useState } from "react"; import type { AddressInput, MothershipAddressCandidate, QuoteDetail, QuotePageStatus, QuoteRequestBody, } from "@/lib/frontend/types"; import { SUBMIT_LOCK_MS } from "@/lib/frontend/constants"; import { hostCreateQuote, hostFetchMothershipCandidates, hostGetQuote, hostPreheatQuoteSession, hostReleaseQuoteSession, } from "@/lib/frontend/api-client"; import { applyMothershipCandidate } from "@/lib/frontend/mothership-address"; import { pollQuoteUntilDone } from "@/hooks/use-quote-polling"; import { formatQuoteErrorMessage } from "@/modules/quote/quote-error-messages"; import { QuoteForm } from "@/components/quote/quote-form"; import { QuoteResultPanel } from "@/components/quote/quote-result-panel"; import { AddressDisambiguationModal } from "@/components/quote/address-disambiguation-modal"; import { PrimaryButton, SecondaryButton } from "@/components/ui/primary-button"; import { ErrorBanner } from "@/components/ui/error-banner"; import { quoteCtaCls } from "@/components/quote/quote-form-chrome"; import { QuoteProviderSwitch, type QuoteProviderId, } from "@/components/embed/quote-provider-switch"; import { Priority1QuoteWidget } from "@/components/priority1/priority1-quote-widget"; import { FlockQuoteWidget } from "@/components/flock/flock-quote-widget"; import { MothershipLoggedInShipmentForm, type MothershipLoggedInShipmentPayload, } from "@/components/mothership/mothership-logged-in-shipment-form"; import { MothershipLoggedInDetailsForm } from "@/components/mothership/mothership-logged-in-details-form"; import { MothershipLoggedInQuoteSidebar } from "@/components/mothership/mothership-logged-in-quote-sidebar"; import { buildQuoteRequestBodyFromLoggedIn } from "@/lib/frontend/mothership-logged-in-quote-body"; export interface EmbeddedQuoteWidgetProps { customerId: string; apiBaseUrl?: string; /** 未传时使用 Cookie 演示会话鉴权(/embed-demo) */ serviceToken?: string; /** 客户已绑定 MotherShip 账密 → 显示登录后一级查价界面 */ mothershipLoggedInUi?: boolean; /** 客户已绑定 Flock 账密 → 显示登录后 Quick 查价界面 */ flockLoggedInUi?: boolean; /** 门户驱动:强制单一数据源(配合 hideProviderSwitch) */ forcedProvider?: QuoteProviderId; /** 门户驱动:隐藏内置数据源切换(4 路线门户已在外层选路) */ hideProviderSwitch?: boolean; } const FORM_ID = "embedded-quote-form"; export function EmbeddedQuoteWidget({ serviceToken, customerId, apiBaseUrl = "", mothershipLoggedInUi = false, flockLoggedInUi = false, forcedProvider, hideProviderSwitch = false, }: EmbeddedQuoteWidgetProps) { const [provider, setProvider] = useState( forcedProvider ?? "mothership", ); const [status, setStatus] = useState("idle"); const [quote, setQuote] = useState(null); const [error, setError] = useState(null); const [submitLocked, setSubmitLocked] = useState(false); const [loggedInDraft, setLoggedInDraft] = useState(null); const [loggedInStep, setLoggedInStep] = useState<"create" | "details">( "create", ); const [disambiguationOpen, setDisambiguationOpen] = useState(false); const [confirmedPickup, setConfirmedPickup] = useState( null, ); const [confirmedDelivery, setConfirmedDelivery] = useState(null); const [pickupCandidates, setPickupCandidates] = useState< MothershipAddressCandidate[] >([]); const [deliveryCandidates, setDeliveryCandidates] = useState< MothershipAddressCandidate[] >([]); const pendingBodyRef = useRef(null); const pendingSessionRef = useRef(undefined); const reset = useCallback(() => { setStatus("idle"); setQuote(null); setError(null); setConfirmedPickup(null); setConfirmedDelivery(null); setLoggedInDraft(null); setLoggedInStep("create"); }, []); const finishQuote = useCallback((detail: QuoteDetail) => { setQuote(detail); if (detail.status === "failed") { setStatus("error"); setError( detail.error_message ?? formatQuoteErrorMessage(detail.error_code), ); return; } if (detail.status === "expired") { setStatus("expired"); return; } setStatus(detail.is_realtime === false ? "fallback" : "success"); setError(null); }, []); const submitQuote = useCallback( async (body: QuoteRequestBody) => { setStatus("validating"); setError(null); const created = await hostCreateQuote(apiBaseUrl, serviceToken, body); if (created.code !== 0) { setStatus("error"); setError(created.message); return; } const { quote_id, status: createStatus } = created.data; setStatus("processing"); if (createStatus === "done") { const detail = await hostGetQuote( apiBaseUrl, serviceToken, customerId, quote_id, ); if (detail.code !== 0) { setStatus("error"); setError(detail.message); return; } finishQuote(detail.data); return; } const pollResult = await pollQuoteUntilDone(async () => { try { const res = await hostGetQuote( apiBaseUrl, serviceToken, customerId, quote_id, ); if (res.code !== 0) { return { ok: false, errorMessage: res.message }; } return { ok: true, data: res.data }; } catch { return { ok: false }; } }); if (pollResult.type === "done") { finishQuote(pollResult.quote); return; } setStatus("error"); setError( pollResult.type === "timeout" ? formatQuoteErrorMessage("QUOTE_TIMEOUT") : pollResult.message, ); }, [apiBaseUrl, serviceToken, customerId, finishQuote], ); const handleSubmit = useCallback( async (body: QuoteRequestBody) => { if (submitLocked) return; setSubmitLocked(true); setTimeout(() => setSubmitLocked(false), SUBMIT_LOCK_MS); setStatus("resolving_address"); setError(null); let candidatesRes; try { candidatesRes = await hostFetchMothershipCandidates( apiBaseUrl, serviceToken, customerId, body.pickup_address, body.delivery_address, ); } catch { setStatus("error"); setError("地址联想请求失败,请稍后重试"); return; } if (candidatesRes.code !== 0) { setStatus("error"); setError(candidatesRes.message); return; } const { pickup_candidates, delivery_candidates, quote_session_id, } = candidatesRes.data; pendingSessionRef.current = quote_session_id; pendingBodyRef.current = body; setPickupCandidates(pickup_candidates); setDeliveryCandidates(delivery_candidates); setDisambiguationOpen(true); if (quote_session_id) { void hostPreheatQuoteSession( apiBaseUrl, serviceToken, customerId, quote_session_id, ); } setStatus("idle"); }, [apiBaseUrl, serviceToken, customerId, submitLocked], ); const handleDisambiguationConfirm = useCallback( async ( pickup: MothershipAddressCandidate, delivery: MothershipAddressCandidate, ) => { const base = pendingBodyRef.current; if (!base) { return; } setDisambiguationOpen(false); pendingBodyRef.current = null; const confirmedBody: QuoteRequestBody = { ...base, quote_session_id: pendingSessionRef.current, pickup_address: applyMothershipCandidate(base.pickup_address, pickup), delivery_address: applyMothershipCandidate( base.delivery_address, delivery, ), }; setConfirmedPickup(confirmedBody.pickup_address); setConfirmedDelivery(confirmedBody.delivery_address); pendingSessionRef.current = undefined; await submitQuote(confirmedBody); }, [submitQuote], ); const handleDisambiguationCancel = useCallback(() => { const sessionId = pendingSessionRef.current; setDisambiguationOpen(false); pendingBodyRef.current = null; pendingSessionRef.current = undefined; setStatus("idle"); if (sessionId) { void hostReleaseQuoteSession( apiBaseUrl, serviceToken, customerId, sessionId, ); } }, [apiBaseUrl, serviceToken, customerId]); const formDisabled = status === "resolving_address" || status === "validating" || status === "processing" || submitLocked || disambiguationOpen; const switchDisabled = status === "validating" || status === "processing" || status === "resolving_address"; return (

卡派查价

选择数据源填写货物信息,获取实时报价

{!hideProviderSwitch && ( { setProvider(next); reset(); }} disabled={switchDisabled} /> )} {provider === "priority1" ? ( ) : provider === "flock" ? ( ) : ( <> {error && status === "idle" && (
{error}
)}
{mothershipLoggedInUi ? ( loggedInStep === "details" && loggedInDraft ? ( <>
setLoggedInStep("create")} > 返回一级表单
) : ( <> { setLoggedInDraft(payload); setLoggedInStep("details"); setQuote(null); setError(null); try { // 一级信息足够询价;二级仅选报价/后续填详情,不阻塞出价 const body = buildQuoteRequestBodyFromLoggedIn( payload, customerId, ); void submitQuote(body); } catch (err) { setStatus("error"); setError( err instanceof Error ? err.message : "无法构建询价请求", ); } }} />
) ) : ( <> { setConfirmedPickup(null); setConfirmedDelivery(null); }} onCargoChange={() => { setQuote(null); setError(null); if (status === "success" || status === "fallback") { setStatus("idle"); } }} onValidSubmit={(body) => void handleSubmit(body)} />
获取报价
)}
{mothershipLoggedInUi ? ( loggedInStep === "details" && loggedInDraft ? ( ) : (

下一步

点「继续」后进入详情页并开始询价,右侧展示承运商报价。

) ) : ( setStatus("expired")} onExpiredConfirm={reset} /> )}
{!mothershipLoggedInUi && ( void handleDisambiguationConfirm(pickup, delivery) } onCancel={handleDisambiguationCancel} /> )} )}
); }