/** * 宿主 iframe ↔ 查价页 postMessage 协议(v1) * 文档:api调用文档/嵌入-postMessage对接文档.md */ export const CHAJIA_PM_SOURCE = "chajia" as const; export const CHAJIA_PM_VERSION = 1 as const; /** 四条互斥查价模块(与 OpenAPI 路线 ID 一致) */ export type ChajiaModuleId = | "MS_GUEST" | "MS_LOGGED_IN" | "FLOCK_GUEST" | "FLOCK_LOGGED_IN"; export type ChajiaHostToIframeType = | "chajia:select-module" | "chajia:fill" | "chajia:ping" /** 宿主请求 iframe 内滚到「选择承运商」报价区 */ | "chajia:scroll-quotes"; export type ChajiaIframeToHostType = | "chajia:ready" | "chajia:pong" | "chajia:module-changed" | "chajia:fill-ack" | "chajia:quote-result" | "chajia:quote-save" /** 进入二级详情等场景:请求宿主把查价区滚到视口顶部 */ | "chajia:scroll-top" | "chajia:error"; export type ChajiaPmType = ChajiaHostToIframeType | ChajiaIframeToHostType; export interface ChajiaPmEnvelope { source: typeof CHAJIA_PM_SOURCE; version: typeof CHAJIA_PM_VERSION; type: T; /** 宿主生成,用于配对 fill / 结果 */ request_id?: string; module?: ChajiaModuleId; payload?: unknown; } /** MotherShip 免账号:表单草稿(与嵌入 QuoteForm 一致) */ export interface MsGuestFillPayload { business_customer_id?: string; /** ccnew LR_Base_User.F_Account */ business_user_account?: string; pickup?: { street?: string; city?: string; state?: string; zip?: string }; delivery?: { street?: string; city?: string; state?: string; zip?: string }; weight?: { value: number; unit?: "lb" | "kg" | "t" }; dimensions?: { length: number; width: number; height: number; unit?: "in" | "cm" | "m"; }; pallet_count?: number; cargo_type?: string; service_level?: "standard" | "guaranteed"; display_unit?: "imperial" | "metric"; } /** * MotherShip 登录后地址预填。 * 宿主可只传整段文本(street / formatted_address),写入搜索框; * place_id / mothership_option_id 可选——无则用户须点选联想确认。 */ export interface MsLoggedInAddressFill { street?: string; city?: string; state?: string; zip?: string; place_id?: string; formatted_address?: string; mothership_option_id?: string; mothership_display_label?: string; } /** MotherShip 登录后:地址文本 + 附加服务 + 货物行 */ export interface MsLoggedInFillPayload { business_customer_id?: string; /** ccnew LR_Base_User.F_Account;无 BC 时反查加价客户 */ business_user_account?: string; pickup_address?: MsLoggedInAddressFill; delivery_address?: MsLoggedInAddressFill; ready_date?: string; ready_time?: string; pickup_accessorials?: string[]; delivery_accessorials?: string[]; cargo_lines?: Array<{ cargo_type?: string; quantity?: number; weight_lb?: number; length_in?: number; width_in?: number; height_in?: number; }>; } /** 从地址 fill 抽出搜索框文案(整段文本优先) */ export function msLoggedInAddressQueryText( addr: MsLoggedInAddressFill | null | undefined, ): string { if (!addr) return ""; const formatted = addr.formatted_address?.trim() ?? ""; if (formatted) return formatted; const street = addr.street?.trim() ?? ""; if (street) return street; const parts = [addr.city, addr.state, addr.zip] .map((x) => (x ?? "").trim()) .filter(Boolean); return parts.join(", "); } /** Flock 免账号 */ export interface FlockGuestFillPayload { pickup_date: string; pickup_zip: string; delivery_zip: string; pallet_count: number; total_weight: { value: number; unit?: "lb" | "kg" | "t" }; dimensions: { length: number; width: number; height: number; unit?: "in" | "cm" | "m"; }; } /** Flock 登录后 Quick(核心字段) */ export interface FlockLoggedInFillPayload { pickup_date: string; pickup_zip: string; delivery_zip: string; pickup_type?: string; delivery_type?: string; items: Array<{ quantity: number; packaging_type?: string; length_in: number; width_in: number; height_in: number; total_weight_lb: number; freight_class?: string; description?: string; }>; pickup_liftgate?: boolean; delivery_liftgate?: boolean; additional_services?: string[]; vehicle_types?: string[]; } export type ChajiaFillPayloadByModule = { MS_GUEST: MsGuestFillPayload; MS_LOGGED_IN: MsLoggedInFillPayload; FLOCK_GUEST: FlockGuestFillPayload; FLOCK_LOGGED_IN: FlockLoggedInFillPayload; }; export interface ChajiaFillMessagePayload< M extends ChajiaModuleId = ChajiaModuleId, > { /** 是否切到对应模块(默认 true) */ select_module?: boolean; /** 模块表单数据 */ form: ChajiaFillPayloadByModule[M]; } /** 查价成功后回传(与程序 QuoteItem / QuoteDetail 对齐) */ export interface ChajiaQuoteResultPayload { quote_id: string; request_id: string; status: "done" | "failed" | "expired"; module: ChajiaModuleId; currency: string; source_type?: string | null; is_realtime?: boolean; quotes: Array<{ carrier: string; service_level: string; rate_option: string; transit_days: string; transit_description: string; raw_freight: number; surcharges: number; raw_total: number; markup_percent: number; markup_amount: number; final_total: number; /** 给宿主的显式别名:原价(未加价) */ base_total_before_markup?: number; /** 给宿主的显式别名:本客户加价金额 */ customer_markup_amount?: number; /** 给宿主的显式别名:本客户最终展示价 */ customer_final_total?: number; /** 宿主无需再猜:true 表示 final_total 已是客户价 */ is_customer_markup_applied?: boolean; /** 宿主可直接据此取展示价 */ pricing_mode?: "customer_final"; breakdown: Array<{ item: string; amount: number }>; /** 宿主保存快照时标记选中行 */ selected?: boolean; guaranteed?: boolean; guarantee_amount?: number | null; estimated_pickup?: string | null; estimated_delivery?: string | null; }>; error_code?: string | null; error_message?: string | null; } /** 用户点击「保存本次询价记录」回传宿主(含选中报价) */ export interface ChajiaQuoteSavePayload extends ChajiaQuoteResultPayload { action: "save"; selected_carrier?: string | null; coverage?: "basic" | "freight_protect" | null; cargo_value_usd?: number | null; ready_date?: string | null; estimated_pickup?: string | null; pickup_address?: MsLoggedInAddressFill; delivery_address?: MsLoggedInAddressFill; cargo_lines?: Array<{ cargo_type?: string; quantity?: number; weight_lb?: number; length_in?: number; width_in?: number; height_in?: number; }>; } export interface ChajiaErrorPayload { code: string; message: string; module?: ChajiaModuleId; } export interface ChajiaFillAckPayload { ok: boolean; module: ChajiaModuleId; applied_keys: string[]; message?: string; } const MODULE_SET = new Set([ "MS_GUEST", "MS_LOGGED_IN", "FLOCK_GUEST", "FLOCK_LOGGED_IN", ]); export function isChajiaModuleId(v: unknown): v is ChajiaModuleId { return typeof v === "string" && MODULE_SET.has(v); } export function moduleToHubRoute( module: ChajiaModuleId, ): { provider: "mothership" | "flock"; mode: "anon" | "login" } { switch (module) { case "MS_GUEST": return { provider: "mothership", mode: "anon" }; case "MS_LOGGED_IN": return { provider: "mothership", mode: "login" }; case "FLOCK_GUEST": return { provider: "flock", mode: "anon" }; case "FLOCK_LOGGED_IN": return { provider: "flock", mode: "login" }; } } export function parseChajiaPostMessage( data: unknown, ): ChajiaPmEnvelope | null { if (!data || typeof data !== "object") return null; const o = data as Record; if (o.source !== CHAJIA_PM_SOURCE) return null; if (o.version !== CHAJIA_PM_VERSION) return null; if (typeof o.type !== "string" || !o.type.startsWith("chajia:")) return null; return { source: CHAJIA_PM_SOURCE, version: CHAJIA_PM_VERSION, type: o.type as ChajiaPmType, request_id: typeof o.request_id === "string" ? o.request_id : undefined, module: isChajiaModuleId(o.module) ? o.module : undefined, payload: o.payload, }; } /** iframe → 宿主(仅当嵌入在 iframe 中时发送) */ export function postToHost( type: ChajiaIframeToHostType, opts?: { request_id?: string; module?: ChajiaModuleId; payload?: unknown; targetOrigin?: string; }, ): void { if (typeof window === "undefined") return; if (window.parent === window) return; const msg: ChajiaPmEnvelope = { source: CHAJIA_PM_SOURCE, version: CHAJIA_PM_VERSION, type, request_id: opts?.request_id, module: opts?.module, payload: opts?.payload, }; window.parent.postMessage(msg, opts?.targetOrigin ?? "*"); } export function listTopLevelKeys(obj: unknown): string[] { if (!obj || typeof obj !== "object" || Array.isArray(obj)) return []; return Object.keys(obj as Record); }