在一个程序中,或多或少的会有非主线功能的嵌入。例如电商软件的活动页(砸金蛋,抢红包等),其与软件的主线关联较少,而且须要具备快速迭代、灵活的特性,当考虑到这部分功能的开发时,大多数人会考虑到将这类项目分离出来,例如考虑iframe。而在小程序端,腾讯所提供的webview组件是很友好的,目前为止对于展现类的webview的开发所提供的api基本上能知足咱们的需求。可是,一贯老沉的腾讯固然不会在小程序到webview的通信上给咱们很好的支持,让咱们看看文档:前端
诚然,webview组件提供了bindmessage的方法让网页向小程序传递消息,但是却会在特定时机(小程序后退/组件销毁/分享)触发并收到消息,这种异步的消息抵达机制显然不符合咱们的需求。那么,既然网页=>小程序这条路已经不可行了,不妨想一想另外一条路:小程序=>网页是否有可能。而从小程序向网页传递信息也是必须依赖于腾讯的api的,经过阅读文档,咱们发现vue
web-view网页中可以使用JSSDK 1.3.2提供的接口返回小程序页面支持的接口有:wx.miniProgram.navigateToweb
在往常的小程序开发中,页面之间的通讯就是经过wx.navigateTo实现的。若如法炮制,或许能找到突破口。小程序
在前端调起微信支付的方法十分简单,只需向server发送订单参数获得包括appId,nonceStr,package,paySign,signType,timeStamp这六个参数,而后在小程序环境经过 wx.requestPayment调起便可,基于上述的条件,咱们想到的方案就是当用户点击支付按钮时,在webview中经过wx.miniProgram.navigateTo将从server获取的6个支付参数传递到另外一个小程序页面,并在callback中返回当前webview所在的页面:后端
handlePay(data){
let _this = this;
new Promise((resolve, reject) => {
wx.requestPayment({
...data,
success(res) {
resolve("成功!")
},
fail(err) {
resolve("失败!")
}
})
}).then(outcome => {
wx.navigateBack({
delta: 1,
success() {
wx.showToast({
title: '支付' + outcome,
icon: "none"
})
}
})
})
}
复制代码
若是咱们在小程序的onLoad钩子中调用handlePay方法,便可让用户在此页面只进行支付的操做,并在支付完成(成功或失败)后当即跳回webview页面。 在传统的vue项目中,路由的跳转会触发组件的生命周期钩子,咱们将数据请求放在这些钩子中以触发页面数据的更新。然而上述的回跳本质上是在小程序中的路由变化,在webview页面的onShow钩子会被调用,而后对于嵌入其中的h5,只是被存入了内存栈而已。由于,让h5页面在支付完成后当即刷新结果(或者触发任何一个事件)是一个比较棘手的问题。通过讨论,咱们想到了两种方案:api
(1)跨域
this.$wx.miniProgram.navigateTo({
url: "/pages/training/counter/counter?task=pay¶m="+param
});
/*在h5中经过wx的sdk跳到小程序支付界面,将支付参数传递*/
setTimeout(() => {
this.$refs.dlg.showDialog();
}, 1000);
/*通过1s后,弹出一个点击才能关闭的dialog,让用户经过点击触发页面的刷新*/
复制代码
(2)浏览器
this.$wx.miniProgram.navigateTo({
url: "/pages/training/counter/counter?task=pay¶m="+param
});
/*在h5中经过wx的sdk跳到小程序支付界面,将支付参数传递*/
document.addEventListener("visibilitychange", function() {
/*加入页面刷新方法*/
});
复制代码
第(2)种方法在小程序开发工具中没法使用,在真机调试中奏效(没有进行各类机型的测试);考虑到此方法涉及到操纵document对象,兼容性有待考量,而且在用户回退的过程当中可能会引发误操做的可能。最终,咱们采用了(1)中的弹窗方法。bash
其实这个问题应该在支付以前就解决了的。既然是一个功能性的webview应用,那用户的行为被记录下来是必然的需求(这也是支付的前提)。本次门店培训师项目的后端与小程序商家端本质上是独立的,而用户登陆的逻辑是在小程序商家端的服务器上进行的。所以,本次后端才用了转发的(包一层)的方法,全部的请求会先到达小程序商家端的服务器上,在鉴权完成后,请求会被转发到门店培训师的服务器。 在前端这边最重要的是将表明用户信息的userKey传递给h5(实际上这也是本项目惟一一个小程序向至h5方向通讯的例子):服务器
this.setData({
url: `${this.data.url}?storeId=${storeId}&userKey=${userKey}`
//webview组件的src
})
复制代码
值得注意的是,此种通讯的方式虽然达到了从小程序向h5通讯的需求,可是webview组件的src一旦变动,以前所存在的页面栈会被销毁,全部咱们只能在h5初始化的时候将表明用户信息的userKey传递过去,在h5页面被销毁以前,任何对webview组件src的操做都会致使上述问题。
其实这个看起来像一个伪命题:既然这么想用小程序的api,为何还要写h5。不过,先看一个例子:
onLoad: function (options) {
let _this = this;
let { url, type } = options;
wx.navigateBack({//用神不知鬼不觉的速度跳回去
delta: 1,
success() {
/*
由于保存图片,视频的操做是须要用户在小程序中受权后才能进行的,全部优先解决这个问题。
canSave()将在下载操做前返回用户的受权状况
*
_this.canSave().then(res =>  {
if (res == "ok") {
_this.wxDownload(url, type);
wx.showToast({
title: '开始下载',
icon: "none"
})
}
}).catch(err => {
console.log(err)
})
},
fail(err) {
throw (err)
}
})
},
canSave() {
return new Promise((reslove,reject) => {
wx.getSetting({
success: (res) => {
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({//受权弹窗
scope: 'scope.writePhotosAlbum',
success: () => {
reslove("ok")
},
fail: () => {
//第一次拒绝受权后,因为腾讯的奇怪设定,第二次会直接进fail回调,咱们在此劫持fail函数
wx.showModal({
title: '提示',
content: '请前往设置完成相册受权操做',
success(res) {
if (res.confirm) {
wx.openSetting({//打开小程序设置权限界面
success(res) {
if (res.authSetting['scope.writePhotosAlbum']) {
//用户在设置界面赞成了相册受权
reslove("ok")
}
},
fail(err) {
reject(err)
}
})
} else if (res.cancel) {
reject('用户点击取消')
}
}
})
}
})
} else {
reslove("ok")
}
}
})
})
},
wxDownload(url, type) {
wx.downloadFile({
url: url, //下载资源的地址网络
success: function (res) {
if (res.statusCode === 200) {
wx.playVoice({
filePath: res.tempFilePath
})
}
// 保存视频到本地
switch (type) {
case "img":
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success:
function (data) {
wx.showToast({
title: '图片下载成功',
})
},
});
break;
case "video":
wx.saveVideoToPhotosAlbum({
filePath: res.tempFilePath,
success:
function (data) {
wx.showToast({
title: '视频下载成功'
})
},
});
}
},
fail: function (err) {
console.log(err)
}
})
}
复制代码
复习一下:在小程序中开发功能性webview页面,关键点在于跨域通讯(我以为更像是'跨浏览器通讯'),而通讯的实现是在h5中利用微信sdkwx.miniProgram.navigateTo
。 以往在作小程序项目中,咱们这么写
wx.navigateTo({
url: `/pages/b?id=996`
});
复制代码
那么,在跳转支付的操做中咱们也这么写
wx.navigateTo({
url: `/pages/pay?appId=${appId}&nonceStr=${nonceStr}&package=${package}&paySign=${paySign}&timeStamp=${timeStamp}`
});
复制代码
可是在这样操做一下,发现传递到小程序页面的参数老是不对的,最后发现:package参数中,含有"=",而它在路由中会被转义。由于,咱们须要让它变得优雅一些:
let param = encodeURIComponent(JSON.stringify(res));//对服务端返回的res支付参数加密
this.$wx.miniProgram.navigateTo({
url: "/pages/training/counter/counter?task=pay¶m=" + param
});
//我想,咱们能够本身封装一个小程序的路由api,使它能像vue router那样传参
复制代码
而后在小程序的页面中解密
/*onLoad钩子*/
let {task,param} = options;
let data = JSON.parse(decodeURIComponent(param));
...
复制代码
若咱们想在h5中使用小程序所提供的友好api,方法只有一个:跳到一个小程序页面,再跳回来。若是每使用一个功能,咱们就须要创建一个小程序的页面,这么作,显然是不nice的。能不能让咱们创造一个小程序页面(能够理解为一个封装好的组件),让它只作一件事:输出可能在h5应用中被用到的全部小程序api。那么,咱们应该在组件里这么写
onLoad: function(options) {
//利用task参数传递须要调用的api
let {task,param} = options;
let data = JSON.parse(decodeURIComponent(param));
switch(task){
case "pay":
this.handlePay(data);
break;
case "download":
this.handleDownload(data);
break;
case "copy":
this.handleCopy(data);
break;
case "preview":
this.handlePreview(data);
break;
...
}
},
复制代码
其实业务才是最本质的问题,前面咱们所说起的都是关于怎么去作一件事情,可是为何去作却没有说起。从公司目前的业务考虑,好比咱们的用户端小程序中会时不时增长一些与主线业务无关的应用:服务经理报名,门店线上报名...不断增长的功能会持续性地增长小程序的体积,对于一个承载用户量十分巨大的程序,新功能开发频繁的更新迭代所致使的发版也是十分麻烦的。目前不少场景下,都是经过再增长一个小程序,并在原来的主小程序中增长一个入口达到的,因此咱们不断地注册小程序... 然而,这些不断增长的"小小程序"虽然达到了粒度的要求,但却只能局限小程序的生态中。若是咱们想在公司官网,亦或是公众号也增长一个服务经理报名的入口,就只能经过贴小程序二维码这种方式达到了。在跨场景的问题上,h5应用仍是具备很大的优点的。可是移植到其余环境时,在须要利用小程序api的地方,咱们则须要针对不一样的环境对实现方法进行改写适配(因此很须要面向对象的写法以及代码的粒度)。虽然本文讲述的是如何进行功能性小程序webview页面开发,但目前我仍是以为最好不要在webview界面嵌入一个功能性过多的h5应用,若是有这个需求,能够看看此文。