商品卡片跨 App 跳转对接指南
1. 功能说明
goodsCard 商品卡片新增 crossApp 参数,用于控制点击卡片后的处理方式。
crossApp 值 |
点击行为 |
|---|---|
未传、false |
H5 使用 window.open(url, "_blank") 打开商品页面 |
true |
H5 将完整的商品卡片 content 传递给外层,由外层完成跨 App 跳转 |
注意:商品卡片必须提供非空 url 才能点击。即使 crossApp 为 true,也需要提供 url。
当 crossApp 为 true,但当前渠道没有可用的外层桥接能力,或桥接调用发生异常时,将回退到 H5 新窗口打开 url。
2. 消息数据格式
{
"contentType": "goodsCard",
"content": {
"img": "https://example.com/goods.png",
"title": "商品名称",
"desc": "商品描述",
"remark": "商品备注",
"url": "https://example.com/goods/10001",
"crossApp": true
}
}
content 字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
img |
string |
否 | 商品图片地址 |
title |
string |
否 | 商品标题 |
desc |
string |
否 | 商品描述 |
remark |
string |
否 | 商品备注 |
url |
string |
是 | 商品页面地址,同时用于桥接失败时的 H5 回退 |
crossApp |
boolean |
否 | 是否由外层执行跨 App 跳转,默认 false |
外层收到的数据是完整的 content 对象,不仅是 url。
3. 通用协议
跨 App 跳转的统一事件名为:
goodsCardClick
统一业务参数为:
{
"img": "https://example.com/goods.png",
"title": "商品名称",
"desc": "商品描述",
"remark": "商品备注",
"url": "https://example.com/goods/10001",
"crossApp": true
}
不同渠道只在通信调用方式和外层包装结构上存在差异。
4. 各渠道对接方式
4.1 uni-app WebView
建议在客服页面 URL 中增加:
embedded=uniapp
H5 调用方式:
uni.postMessage({
data: {
action: "goodsCardClick",
params: content
}
});
外层收到的数据结构:
{
"action": "goodsCardClick",
"params": {
"img": "https://example.com/goods.png",
"title": "商品名称",
"desc": "商品描述",
"remark": "商品备注",
"url": "https://example.com/goods/10001",
"crossApp": true
}
}
uni-app 页面接收示例:
<template>
<web-view :src="chatUrl" @message="handleWebViewMessage" />
</template>
<script setup>
const handleWebViewMessage = (event) => {
const messages = event.detail?.data || [];
const message = messages[messages.length - 1];
if (message?.action !== "goodsCardClick") return;
const content = message.params;
// 根据 content.url 或其他业务字段执行跨 App 跳转
};
</script>
4.2 Flutter InAppWebView
建议在客服页面 URL 中增加:
embedded=flutter
H5 调用方式:
window.flutter_inappwebview.callHandler("goodsCardClick", content);
Flutter 接收示例:
InAppWebView(
initialUrlRequest: URLRequest(url: WebUri(chatUrl)),
onWebViewCreated: (controller) {
controller.addJavaScriptHandler(
handlerName: 'goodsCardClick',
callback: (arguments) {
if (arguments.isEmpty) return null;
final content = Map<String, dynamic>.from(arguments.first);
final url = content['url'] as String?;
// 根据 content 执行跨 App 跳转
return true;
},
);
},
)
4.3 Android WebView JSInterface
H5 调用方式:
window.Android.goodsCardClick(JSON.stringify({
params: content,
callback: null
}));
Android 需要向 WebView 注册名称为 Android 的 JavaScript Interface,并提供 goodsCardClick 方法。
Kotlin 接收示例:
class ChatJavascriptInterface {
@JavascriptInterface
fun goodsCardClick(message: String) {
val root = JSONObject(message)
val content = root.getJSONObject("params")
val url = content.optString("url")
// 切回主线程后,根据 content 执行跨 App 跳转
}
}
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(ChatJavascriptInterface(), "Android")
Android 收到的 JSON 字符串结构:
{
"params": {
"img": "https://example.com/goods.png",
"title": "商品名称",
"desc": "商品描述",
"remark": "商品备注",
"url": "https://example.com/goods/10001",
"crossApp": true
},
"callback": null
}
4.4 iOS WKWebView
H5 调用方式:
window.webkit.messageHandlers.goodsCardClick.postMessage({
params: content,
callback: null
});
iOS 需要注册名称为 goodsCardClick 的 Script Message Handler。
Swift 接收示例:
final class ChatMessageHandler: NSObject, WKScriptMessageHandler {
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard message.name == "goodsCardClick",
let body = message.body as? [String: Any],
let content = body["params"] as? [String: Any]
else {
return
}
let url = content["url"] as? String
// 根据 content 执行跨 App 跳转
}
}
let contentController = WKUserContentController()
contentController.add(ChatMessageHandler(), name: "goodsCardClick")
let configuration = WKWebViewConfiguration()
configuration.userContentController = contentController
iOS 收到的对象结构:
{
"params": {
"img": "https://example.com/goods.png",
"title": "商品名称",
"desc": "商品描述",
"remark": "商品备注",
"url": "https://example.com/goods/10001",
"crossApp": true
},
"callback": null
}
4.5 JS iframe 嵌入渠道
适用于通过 ueChatCore.js 将客服页面嵌入业务网页的场景。
iframe 向父页面发送的原始 postMessage:
window.parent.postMessage({
type: "goodsCardClick",
source: "iframe",
data: content
}, "*");
使用 ueChatCore.js 时,父页面可以选择以下任一方式接收。
方式一:注册全局回调。
window.ueGoodsCardClick = function (content) {
console.log("商品卡片参数:", content);
// 根据 content 执行跨 App 跳转
};
方式二:监听自定义事件。
window.addEventListener("ueChat:goodsCardClick", function (event) {
const content = event.detail;
console.log("商品卡片参数:", content);
// 根据 content 执行跨 App 跳转
});
未使用 ueChatCore.js、自行嵌入 iframe 时,可以直接监听 message 事件:
window.addEventListener("message", function (event) {
const message = event.data;
if (
message?.source !== "iframe" ||
message?.type !== "goodsCardClick"
) {
return;
}
const content = message.data;
// 根据 content 执行跨 App 跳转
});
生产环境中,自行监听 message 时应校验 event.origin,不要直接信任任意来源的数据。
4.6 普通 H5
普通浏览器页面不需要对接外层桥接。
当 crossApp 未传或为 false 时,H5 直接执行:
window.open(content.url, "_blank");
当误传 crossApp: true,但页面中不存在任何可用桥接时,也会回退到上述 H5 打开方式。
5. 渠道识别与调用优先级
H5 按以下顺序尝试将商品卡片交给外层:
- URL 参数
embedded=uniapp对应的 uni-app 桥接 - URL 参数
embedded=flutter对应的 Flutter 桥接 - Android 的
window.Android.goodsCardClick - iOS 的
window.webkit.messageHandlers.goodsCardClick - iframe 父页面
postMessage - 页面中实际存在的 Flutter 或 uni-app 桥接对象
- 所有桥接均不可用时,回退 H5 打开
url
一个页面应只接入一种主要容器桥接,避免同时注入多个原生桥对象造成渠道识别歧义。
6. 对接检查清单
- 消息的
contentType为goodsCard content.url是非空字符串- 需要跨 App 跳转时,
content.crossApp是布尔值true,不是字符串"true" - uni-app 页面 URL 已传
embedded=uniapp - Flutter 页面 URL 已传
embedded=flutter - Android 已注册
Android.goodsCardClick(String) - iOS 已注册
goodsCardClickScript Message Handler - JS iframe 渠道已注册
ueGoodsCardClick或监听ueChat:goodsCardClick - 外层能处理完整
content,并自行校验目标地址及业务参数 - 已验证外层桥接不可用时可以正常回退 H5 打开商品页面
7. 安全建议
- 外层跳转前应校验
content.url的协议、域名和业务白名单。 - iframe 自行监听
postMessage时应校验event.origin和event.source。 - 不要直接将未校验的商品字段拼接为原生路由或系统 Scheme。
- Android 开启 JavaScript Interface 时,仅暴露必要的方法。
- iOS 应在 WebView 销毁时移除 Script Message Handler,避免生命周期问题。