# 查价系统 · 宿主 postMessage 对接说明 **版本**:1.1 **更新日期**:2026-07-24 **适用对象**:在 iframe 中嵌入查价页的宿主系统(如 ShipRo / 元子侧适配方) **嵌入页**:`/embed-demo`(须先按 `嵌入对接文档.md` 完成 API Key 直登) > 原则:**由查价系统定义「要填什么 / 查完回什么」**;宿主按本协议发 `postMessage` 预填、收结果。字段与程序内部询价结构对齐,宿主无需再猜哪些字段可用。 --- ## 0. ShipRo 联调要点(v1.1) ### 0.1 URL 直进 MotherShip 登录后表单(方案 A) ```text https://if.dev.51track.vip/embed-demo?login_type=api_key&api_key=&embed=1&module=MS_LOGGED_IN ``` 等价:`entry=ms_logged_in` 登录成功后**跳过** MotherShip/Flock 卡片与「免账号/登录后」点选,直接进入「创建新货件」。须该客户已绑定 MotherShip 官网账密,否则停在账密面板。 ### 0.2 fill 可晚到 / 可重复 / 先缓存再灌表(方案 B) - 收到 `chajia:fill` **立刻缓存** `payload.form`(不要求当前已在表单页)。 - 切入 `module` 后,表单挂载时写入 UI;用户在表单页再点「填入数据」会**再次**灌表(`seq` 递增)。 - 先回 `chajia:fill-ack`(`ok: true`,缓存确认);表单真正写入后再回一次 ack(`ui_applied: true`)。 - **不依赖**宿主必须先等 `chajia:ready`(ready 仍会发,便于时序;晚到 fill 同样有效)。 ### 0.3 `MS_LOGGED_IN` 地址可只传整段文本 宿主可不传 `place_id` / `mothership_option_id`。查价页把 `formatted_address` 或 `street` 写入提货/送货**搜索框**;用户须点选联想确认后才能「继续」。`cargo_lines` 写入货物行(inch/lb);空数组不改货物。 --- ## 1. 总览 ```text 宿主页面 └─ iframe → .../embed-demo?...&embed=1[&module=MS_LOGGED_IN] ├─ iframe → 宿主:chajia:ready / chajia:fill-ack / chajia:quote-result / chajia:error └─ 宿主 → iframe:chajia:select-module / chajia:fill / chajia:ping ``` | 方向 | 类型 | 作用 | |------|------|------| | iframe→宿主 | `chajia:ready` | 会话就绪(可开始发 fill;非强制前置) | | 宿主→iframe | `chajia:select-module` | 切入四模块之一 | | 宿主→iframe | `chajia:fill` | 按模块写入/缓存表单字段(**必带 module**,可重复) | | 宿主→iframe | `chajia:scroll-quotes` | 请求 iframe 滚到「选择承运商」区(私卡出报价后) | | iframe→宿主 | `chajia:fill-ack` | 已缓存;表单写入后再带 `ui_applied` | | iframe→宿主 | `chajia:quote-result` | 查价结束(成功/失败/过期);**及** MS 侧栏点选承运商时再次推送(带 `selected`) | | iframe→宿主 | `chajia:quote-save` | 用户点「保存本次询价记录」(可选;写快照) | | iframe→宿主 | `chajia:scroll-top` | 进入二级详情等:请求宿主把查价 iframe 区域滚到视口顶部 | | iframe→宿主 | `chajia:error` | 协议层错误(缺 module 等) | | iframe→宿主 | `chajia:module-changed` | 用户或宿主切换了模块 | 信封统一格式: ```json { "source": "chajia", "version": 1, "type": "chajia:fill", "request_id": "宿主生成的 UUID,可选,用于配对", "module": "MS_LOGGED_IN", "payload": {} } ``` - **必须**校验 `source === "chajia"` 且 `version === 1`,忽略其它来源。 - `targetOrigin`:生产建议写成查价域名;联调可用 `*`。 - 仅当页面在 **iframe** 中时,查价页才会向 `parent` 回传。 --- ## 2. 模块 ID(必须区分) 与 OpenAPI 路线一致,**四选一、互斥**: | module | 含义 | 门户表现 | |--------|------|----------| | `MS_GUEST` | MotherShip 免账号 | MotherShip → 免账号 | | `MS_LOGGED_IN` | MotherShip 登录后 | MotherShip → 登录后(须已绑官网账密) | | `FLOCK_GUEST` | Flock 免账号 | Flock → 免账号 | | `FLOCK_LOGGED_IN` | Flock 登录后 | Flock → 登录后(须已绑官网账密) | `chajia:fill` **必须**带 `module`。不同模块的 `payload.form` 字段不同,不可混用。 --- ## 3. 宿主 → iframe:预填(按模块) ### 3.1 通用写法 ```js iframe.contentWindow.postMessage( { source: "chajia", version: 1, type: "chajia:fill", request_id: crypto.randomUUID(), module: "MS_LOGGED_IN", payload: { select_module: true, form: { /* 见 3.3 */ } }, }, "https://if.dev.51track.vip", ); ``` 也可把 `form` 直接作为 `payload`(无 `select_module` 包装)。 ### 3.2 `MS_GUEST` ```json { "pickup": { "street": "1234 Warehouse Blvd", "city": "Los Angeles", "state": "CA" }, "delivery": { "street": "5678 Distribution Dr", "city": "Dallas", "state": "TX" }, "weight": { "value": 500, "unit": "lb" }, "dimensions": { "length": 48, "width": 40, "height": 48, "unit": "in" }, "pallet_count": 2, "cargo_type": "general_freight" } ``` ### 3.3 `MS_LOGGED_IN`(ShipRo 现网:整段文本 + cargo_lines) ```json { "pickup_address": { "street": "整段发货地址原文", "formatted_address": "整段发货地址原文" }, "delivery_address": { "street": "整段目的地址原文", "formatted_address": "整段目的地址原文" }, "cargo_lines": [ { "cargo_type": "pallet", "quantity": 1, "weight_lb": 100, "length_in": 48, "width_in": 40, "height_in": 48 } ] } ``` | 字段 | 说明 | |------|------| | 地址 | `formatted_address` 或 `street` → 搜索框;可不传 place_id(用户点选联想) | | cargo_lines | inch/lb;**空数组不改**货物 | | ready_* / accessorials | 可选;不传用页默认 | ### 3.4 仅切模块 ```json { "source": "chajia", "version": 1, "type": "chajia:select-module", "module": "MS_LOGGED_IN" } ``` --- ## 4. 查价结果 `chajia:quote-result` → `payload.quotes[].carrier` / `final_total`;失败读 `error_message`。 宿主若需明确区分“官网原价”和“客户加价后展示价”,可直接读: - `base_total_before_markup`:未加价原价 - `customer_markup_amount`:该客户加价金额 - `customer_final_total`:该客户最终展示价(等同当前 `final_total`) - `is_customer_markup_applied`:是否已叠加客户加价 - `pricing_mode = "customer_final"`:宿主可直接按客户最终价展示 ### 4.1 侧栏点选承运商即时同步(宿主锁价) MotherShip 登录后侧栏(`MS_LOGGED_IN`): 1. 用户点选任意承运商卡片、默认选中出价、或切换保障导致生效费率变化时 2. iframe 再次发出 **`chajia:quote-result`**(**不依赖**绿色「保存本次询价记录」) 3. `payload.quotes[]` 中当前生效那条 **`selected: true`**,其余 `false` 4. 宿主取 `selected === true` 的档(无则兜底首条)即可锁定承运商与 `final_total` / `customer_final_total` 5. 绿色「保存」仍可选发 `chajia:quote-save`(`action: "save"` + 同样 `selected`),仅用于写快照;私卡询价跳转**不必**等保存 触发时机代码入口:`onSelectQuote` → `notifyHostQuoteSelection` → `reportQuoteResult`。 ### 4.2 出报价后视野定位(需求3) 1. **查价页**:报价列表出现后自动 `scrollIntoView` 到侧栏「选择承运商」(`#ms-choose-carrier`);「已选」角标与承运商名同一行,不遮挡标题 2. **宿主**:收到 `quote-result` 后左侧滚到「当前选中报价」,并可向 iframe 发: ```json { "source": "chajia", "version": 1, "type": "chajia:scroll-quotes" } ``` iframe 收到后再次滚承运商区。需查价页与 cc-client **一起部署** 才完整。 ### 4.3 「继续」进入二级后置顶 用户在一级点「继续」进入提货/送货详情时: 1. **查价页**:iframe 内 `scrollTo(0)` + 根节点 `scrollIntoView` 2. **同时**向宿主发: ```json { "source": "chajia", "version": 1, "type": "chajia:scroll-top", "module": "MS_LOGGED_IN", "payload": { "reason": "logged-in-details" } } ``` 3. **宿主**:收到后将私卡询价弹窗/iframe 容器滚到视口顶部 需查价页与 cc-client 一并更新。 --- ## 5. 联调顺序(ShipRo) 1. iframe:`...&embed=1&module=MS_LOGGED_IN` 2. 进「创建新货件」后点「填入数据」→ `chajia:fill` 3. 收 `fill-ack`(缓存)→ 再收 `ui_applied` ack 4. 用户点选地址联想 → 继续 → `chajia:quote-result`(出票) 5. 用户在侧栏点选承运商 → 再收 `chajia:quote-result`(带 `selected`)→ 宿主锁价后可「确认询价并新建集装箱」 亦可打开后立即 fill(先缓存,表单挂载后灌入)。 | 能力 | 状态 | |------|------| | URL `module` / `entry` 直进 | 已实现 | | fill 缓存 + 可重复 + 晚到灌表 | 已实现 | | MS_LOGGED_IN 搜索框 + cargo_lines | 已实现 | | MS_GUEST 预填 / quote-result | 已实现 | | 侧栏点选承运商 → quote-result + selected | 已实现 | | 出报价后自动滚到「选择承运商」/ scroll-quotes | 已实现 | | 「继续」进二级 → scroll-top 置顶 | 已实现 | --- ## 6. 文档关系 | 文档 | 用途 | |------|------| | `嵌入对接文档.md` | iframe URL、API Key、`module` | | 本文 | postMessage | | `api对接文档.md` | OpenAPI | 代码:`lib/embed/host-bridge.ts`、`parseEmbedEntryModule`(`lib/embed/sso-params.ts`)、`map-quotes-selection.ts`、`mothership-logged-in-quote-sidebar` / `embedded-quote-widget`。