"use client"; import { useCallback, useEffect, useState } from "react"; import { Card } from "@/components/ui/card"; import { PrimaryButton, SecondaryButton, } from "@/components/ui/primary-button"; import { InputField } from "@/components/ui/input-field"; import { ErrorBanner } from "@/components/ui/error-banner"; import { Skeleton } from "@/components/ui/skeleton"; import { EmbeddedQuoteWidget } from "@/components/embed/embedded-quote-widget"; import { embedGetProviderCredentials, embedSaveProviderCredential, type EmbedProviderCredential, } from "@/lib/frontend/api-client"; import { moduleToHubRoute, useHostBridgeOptional, } from "@/lib/embed/host-bridge-react"; import type { ChajiaModuleId } from "@/lib/embed/host-bridge"; type QuoteProvider = "mothership" | "flock"; type QuoteMode = "anon" | "login"; interface ModuleDef { provider: QuoteProvider; title: string; desc: string; } /** 门户仅两模块:私卡 / 合单(进入后可选免账号或登录后) */ const MODULES: ModuleDef[] = [ { provider: "mothership", title: "私卡询价", desc: "免账号实时报价,或使用已绑定账号登录后查价", }, { provider: "flock", title: "合单询价", desc: "ZIP Direct 匿名询价,或使用已绑定账号登录后查价", }, ]; function hubModuleId( provider: QuoteProvider, mode: QuoteMode, ): ChajiaModuleId { if (provider === "mothership") { return mode === "login" ? "MS_LOGGED_IN" : "MS_GUEST"; } return mode === "login" ? "FLOCK_LOGGED_IN" : "FLOCK_GUEST"; } interface QuoteRouteHubProps { customerId: string; apiBaseUrl?: string; /** * 隐藏门户外壳:当前模块条、免账号/登录后 Tab、承运商账密表单等。 * 宿主嵌入与正式演示仅展示查价表单本身。 */ hideChrome?: boolean; } export function QuoteRouteHub({ customerId, apiBaseUrl = "", hideChrome = false, }: QuoteRouteHubProps) { const hostBridge = useHostBridgeOptional(); const [loading, setLoading] = useState(true); const [creds, setCreds] = useState< Record >({ mothership: { provider: "mothership", email: null, password: null, has_password: false, updated_at: null, }, flock: { provider: "flock", email: null, password: null, has_password: false, updated_at: null, }, }); const [selected, setSelected] = useState(null); const [mode, setMode] = useState("anon"); const loadCreds = useCallback(async () => { setLoading(true); const res = await embedGetProviderCredentials(apiBaseUrl); if (res.code === 0) { setCreds((prev) => { const next = { ...prev }; for (const item of res.data.list) { if (item.provider === "mothership" || item.provider === "flock") { next[item.provider] = item; } } return next; }); } setLoading(false); }, [apiBaseUrl]); useEffect(() => { void loadCreds(); }, [loadCreds]); // 宿主 postMessage 切模块 useEffect(() => { const m = hostBridge?.activeModule; if (!m) return; const route = moduleToHubRoute(m); const mod = MODULES.find((x) => x.provider === route.provider) ?? null; if (!mod) return; setSelected(mod); setMode(route.mode); }, [hostBridge?.activeModule]); // 精简壳:未点选且未指定 entry 时,自动进入已配置账密的模块 useEffect(() => { if (!hideChrome || loading || selected) return; if (hostBridge?.activeModule) return; const preferred = MODULES.find((m) => creds[m.provider].has_password) ?? MODULES[0]; if (!preferred) return; const nextMode = creds[preferred.provider].has_password ? "login" : "anon"; setSelected(preferred); setMode(nextMode); hostBridge?.setActiveModule(hubModuleId(preferred.provider, nextMode)); }, [hideChrome, loading, selected, creds, hostBridge]); const handleSaved = useCallback((saved: EmbedProviderCredential) => { setCreds((prev) => ({ ...prev, [saved.provider]: saved })); }, []); const openModule = (mod: ModuleDef) => { setSelected(mod); // 已配置账密时默认进登录后;否则免账号 const nextMode = creds[mod.provider].has_password ? "login" : "anon"; setMode(nextMode); hostBridge?.setActiveModule(hubModuleId(mod.provider, nextMode)); }; const onModeChange = (next: QuoteMode) => { setMode(next); if (selected) { hostBridge?.setActiveModule(hubModuleId(selected.provider, next)); } }; if (loading) { return ; } if (!selected) { if (hideChrome) { return ; } return (
{MODULES.map((mod) => { const hasPassword = creds[mod.provider].has_password; return ( openModule(mod)} data-testid={`quote-route-module-${mod.provider}`} >
{mod.title} {hasPassword ? "已配置官网账号" : "可免账号查价"}
{mod.desc}
); })}
); } const providerCred = creds[selected.provider]; const requiresAccount = mode === "login"; const canQuote = !requiresAccount || providerCred.has_password; const quoteArea = canQuote ? ( ) : (

当前客户未配置承运商官网账号,请在管理端「租户管理」中配置后再查价。

); if (hideChrome) { return
{quoteArea}
; } return (

当前查价模块

{selected.title}

setSelected(null)}> 返回模块列表
{( [ { id: "anon" as const, label: "免账号", desc: "无需官网登录" }, { id: "login" as const, label: "登录后", desc: "使用官网账密" }, ] as const ).map((tab) => ( ))}
{requiresAccount && ( )} {quoteArea}
); } interface ProviderCredentialPanelProps { provider: QuoteProvider; apiBaseUrl: string; current: EmbedProviderCredential; onSaved: (saved: EmbedProviderCredential) => void; } function ProviderCredentialPanel({ provider, apiBaseUrl, current, onSaved, }: ProviderCredentialPanelProps) { const [email, setEmail] = useState(current.email ?? ""); const [password, setPassword] = useState(current.password ?? ""); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [savedHint, setSavedHint] = useState(null); useEffect(() => { setEmail(current.email ?? ""); setPassword(current.password ?? ""); setError(null); setSavedHint(null); }, [provider, current.email, current.password]); const handleSave = async () => { if (saving) return; if (!email.trim()) { setError("请填写登录邮箱"); return; } if (!password.trim()) { setError("请填写登录密码(明文展示便于核对,修改后请点保存)"); return; } setSaving(true); setError(null); setSavedHint(null); const res = await embedSaveProviderCredential( apiBaseUrl, provider, email.trim(), password, ); setSaving(false); if (res.code !== 0) { setError(res.message || "保存失败"); return; } setEmail(res.data.email ?? email.trim()); setPassword(res.data.password ?? password); setSavedHint( "账号密码已保存。若官网已改密,请确认此处已同步更新后再查价。", ); onSaved(res.data); }; return (

承运商账号密码

{current.has_password ? "以下为当前已保存的明文账密,请核对是否与官网一致。官网改密后须在此即时保存,否则查价会卡在登录页并报错。" : "首次使用请填写官网账号邮箱与密码,保存后方可查价。"}

{error && (
{error}
)} {savedHint && (

{savedHint}

)}
setEmail(e.target.value)} /> setPassword(e.target.value)} /> void handleSave()} > {current.has_password ? "保存修改" : "保存"}
); }