之前领导要求就打通webView、小程序和iframs三者之间的通讯做一份技术文档说明,功能是做出来了,结果后面没有接到那个项目,也就没有继续开发下去,但也值得记录一下。
项目目标
实现webView和小程序通讯,webView和iframe通讯,而后以webView作为中转站,实现webView中的iframe和小程序通讯。
需求确定
在商城小程序中以webView的形式引入客服聊天页面IM,在IM中以iframe的形式嵌入第三方页面,内容和样式第三方自定义。iframe的操作需求整理如下:
- 跳转到文章详情
- 链接到另一个客服
- 跳转到商品详情
- 跳转到商品购物车页
- 跳转到订单详情
- 发送消息
可以整理为三大类:1.小程序页面跳转2.向某一个客服发起会话3.在当前会话发送消息
实现
- webView和小程序通讯
官方文档对于web-view组件的描述是:承载网页的容器。会自动铺满整个小程序页面,个人类型的小程序暂不支持使用。所以在小程序中webView是不能以弹窗的形式或者占据页面一部分和其它内容共存,这个特点微信小程序和支付宝小程序都具备。
webView组件和小程序只能通过bindmessage事件绑定进行通讯,对于这个事件官方的描述是这样的:网页向小程序 postMessage 时,会在特定时机(小程序后退、组件销毁、分享)触发并收到消息。也就是说无论webView通过postMessage向小程序发送多少次消息,只要不是在特定的时机(小程序后退、组件销毁、分享),小程序就不会收到webView的消息。
通过以上两点,我们可以确定小程序的当前页面只有一个webView是有效内容,那小程序和webView之间的通讯最常用的也就是小程序进行页面的跳转了。
// 在webView页面中引入微信JS文件
<script src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>
// 进行页面跳转
wx.miniProgram.navigateTo({
url: '/pages/home2/index', // 小程序页面路径
})
2. webView和内嵌的iframe页面通讯
两者通过postMessage进行通讯,主要代码如下
window.addEventListener('message', (event) => {
if (window.parent !== event.source) { return }
console.log(event, 'event'); //接收到的信息
top.postMessage("发送给父级页面的信息,可以为对象", '父级页面url或者*');
}, false);
完整主要代码
- 小程序
<WebView src="xxxx"></WebView>
- webView客服聊天页面IM
// 在webView页面中引入微信JS文件
<script src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>
let ua = navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == "micromessenger") {
//ios的ua中无miniProgram,但都有MicroMessenger(表示是微信浏览器)
wx.miniProgram.getEnv((res) => {
if (res.miniprogram) {
console.log("在小程序里");
// 接收到消息
window.addEventListener('message', this.fnTest, false);
} else {
console.log("不在小程序里");
}
})
} else {
console.log('不在微信里');
}
fnTest = (event) => {
let data = event.data;
if (data.operation === 'msg') {
// 在当前会话发送消息
let r = Math.ceil(Math.random() * 100000)
let uuid = this.state.memberData.memberId + Date.now() + r;
let sendData = '';
// 0视频,1图片,2文件,4文本,8富文本,103iframe
switch (data.data.type) {
// 消息类型处理
case 103:
sendData = data.data.iframeUrl;
break;
}
// this.sendMessage IM中发送消息函数
this.sendMessage(sendData, data.data.type, uuid);
} else if (data.operation === 'action') {
// 小程序页面跳转
wx.miniProgram.navigateTo({
url: data.data.url //'/pages/home2/index'
})
} else if (data.operation === 'chat') {
let code = StringUtils.getQueryString('code')
let url = `index.html?code=${code}&otherId=7&sessionScope=1&userCode=${data.data.userCode}`
// 向某一个客服发起会话
window.app.router.push(url)
}
}
3. 第三方iframe页面
<!-- 简单的模拟html页面 -->
<span class="btn" style="margin-left: 0">发送消息</span>
<span class="action" style="background: lightpink">跳转页面</span>
<span class="chat" style="background: lightseagreen">跟他聊聊</span>
window.addEventListener('message', (event) => {
if (window.parent !== event.source) { return }
console.log(event, 'event'); //接收到的信息
top.postMessage("发送给父级页面的信息,可以为对象", '父级页面url或者*');
}, false);
// operation 操作类型string msg:发送消息 ,action:跳转页面,chat:发起聊天
// type 消息类型string operation为msg时,0视频,1图片,2文件,4文本,8富文本,103iframe
// 其他具体参数另定
$('.btn').on('click', function () {
top.postMessage({
operation: 'msg',
data: {
data: '消息内容',
type: 103, //Number类型
size: 110, //Number类型
name: '', //文件名称
iframeUrl: 'https://dev.iservice.dtyunxi.cn/i-service/dev/iservice-customer-web-pc/test.html'
},
}, '*')
});
$('.action').on('click', function () {
top.postMessage({
operation: 'action',
data: {
url: '/pages/home2/index?xxx=xx', // 需要跳转页面
}
}, '*')
});
$('.chat').on('click', function () {
top.postMessage({
operation: 'chat',
data: {
userCode: '007', // 客服工号
}
}, '*')
});
定义参数
参数名 | 类型 | 是否必填 | 描述 |
operation | string | 是 | 枚举值:1. msg:发送消息 2.action:跳转页面 3.chat:发起聊天 |
data | object | 是 | |
data参数
参数名 | 类型 | 是否必填 | 描述 |
type | number | operation为msg时必填 | 0视频,1图片,2文件,4文本,8富文本,103iframe |
size | number | type为2时必填 | 文件大小,默认为0 |
name | string | type为2时必填 | 文件名称,默认为空 |
iframeUrl | string | type为103时必填 | iframe路径 |
data | string | operation为msg且type不为103时必填 | type为0,1,2时,为视频、图片、文件的路径,type为4,8时,为发送的内容 |
url | string | operation为action时必填 | 小程序跳转路径 |
userCode | string | operation为chat时必填 | 客服工号 |
支付宝小程序
小程序代码
// .axml
<view class="page">
<web-view src="xxxx" onMessage="onmessage"></web-view>
</view>
// .js
Page({
onmessage(e){
// 拿到webView数据,进行一系列事件处理
console.log(e,'e')
}
});
webView代码
// 引入js
<script type="text/javascript" src="https://appx/web-view.min.js"></script>
<!-- 如该 H5 页面需要同时在非支付宝客户端内使用,为避免该请求404,可参考以下写法 -->
<!-- 请尽量在 html 头部执行以下脚本 -->
<script>
if (navigator.userAgent.indexOf('AlipayClient') > -1) {
document.writeln('<script src="https://appx/web-view.min.js"' + '>' + '<' + '/' + 'script>');
}
</script>
componentWillUnmount() {
// 离开页面,清除监听
window.removeEventListener('message', this.fnTest, false);
}
fnTest = (event) => {
// alert(JSON.stringify(event), 'event')
let data = event.data;
if (data.operation === 'msg') {
// 在当前会话发送消息
let r = Math.ceil(Math.random() * 100000)
let uuid = this.state.memberData.memberId + Date.now() + r;
let sendData = '';
// 0视频,1图片,2文件,4文本,8富文本,103iframe
switch (data.data.type) {
// 消息类型处理
case 103:
sendData = data.data.iframeUrl;
break;
}
// this.sendMessage IM中发送消息函数
this.sendMessage(sendData, data.data.type, uuid);
} else if (data.operation === 'action') {
// 小程序页面跳转
// my.navigateTo({ url: `../../${data.data.url}` });
my.navigateTo({ url: `../test/test` });
} else if (data.operation === 'chat') {
let code = StringUtils.getQueryString('code')
let url = `index.html?code=${code}&otherId=7&sessionScope=1&userCode=${data.data.userCode}`
// 向某一个客服发起会话
window.app.router.push(url)
}
}
componentDidMount() {
window.addEventListener('message', this.fnTest, false);
}
特别说明:webView向支付宝小程序发送消息时,并没有微信小程序接收webView消息的限制【需要在特定时机(小程序后退、组件销毁、分享)触发才能收到消息】,只要webView通过 my.postMessage(obj)
向小程序发送消息,小程序就能接收到消息,支付宝小程序和webView的通讯更方便灵活。
流程图