今年8月开发新疆项目时,曾接到个需求,需要我们这边配合第三方的小程序(立克办)半屏打开我们的新疆一码游小程序,并且需要做单点登录,从立克办那边直接无感登录我们小程序。

刚开始觉得没多少事,事后做下来,还是出了一些问题。主要原因还是因为涉及第三方,事先缺少沟通,有些东西想当然了。当时听产品描述需求,以为第三方(立克办)那边半屏打开我们的新疆一码游小程序,所有参数都在wx.navigateToMiniProgram方法的path参数里面(实际他们一部分参数在path中,另一部分在extraData中)。结果做完上线生产(第三方没有提供开发版和体验版,只有生产版本),才发现问题,经过和第三方多番线上语言沟通,才搞清楚问题所在,沟通过程中,第三方因为一些理由拒绝修改传参方式,所以我这边又只好配合修改了接收参数方式。最终才线上测试搞定。
啰嗦了半天,还没讲到正题,今天在这里,我主要是想记录以下几个问题,方便以后查阅,都说好记性不如烂笔头嘛,下面开始记录。
一、关于打开半屏小程序
打开半屏小程序,我们可以从微信官方文档中知道,使用wx.openEmbeddedMiniProgram 即可,下面介绍下它的几个重要参数。
1、envVersion
envVersion表示要打开的小程序版本,包含develop、trial、release,从文档
中我们可以了解到,如果主动打开方的小程序未设置envVersion,或者设置为正式版release,那么被打开方的小程序也必须是正式版。通俗讲就是,我开发,你也开发,我体验,你也体验,我正式,你也正式。
如果双方还在开发调试阶段,最好还是设置为体验版trial比较好,双方都是体验版版,方便开发调试,等开发测试没啥问题了,再改为release或者去掉envVersion参数,以避免在正式版发现错误,盲改后再线上测试。
我们当时就是在正式版,我这里只能盲改开发,易出错,效率低。
2、appId、path、extraData
appId为要打开的小程序 appId,无需多言。path为要打开的页面路径,值为字符串string类型,extraData为需要传递给目标小程序的数据,值为对象object类型。这里的path不仅仅可以设置要打开的小程序页面路径,还能传递参数,path 中 ? 后面的部分会成为 query。
虽然path、extraData两者都可以传递参数,不过大家需要注意两者获取参数的区别。

从文档中我们已经得知,两者传参方式都可以在app生命周期onLaunch,onShow中获取,只有path传递的参数,可以在页面级生命周期onLoad中取得。这里大家开发的时候,一定得注意。
关于如何获取extraData参数,我们可以在小程序App.vue的onLaunch、onShow中按如下方式获取:
js
export default {
//onLaunch(opts) {
// let extraData = opts?.referrerInfo?.extraData
//},
// 建议如无特殊要求只执行一次,获取extraData参数,都在onShow中执行
onShow (opts) {
let extraData = opts?.referrerInfo?.extraData
}
}
从上次配合半屏打开小程序,开发单点登录的经验来看,一般写在onShow中最好,大家都知道,onLaunch只在小程序初始化完成时触发,onShow则是当小程序启动,或每次从后台进入前台显示时都会触发。如果没有需求说只要执行一次,那么使用onShow则是最为稳妥。
我当时就是最先写在onLaunch中,导致他们立克办小程序用户打开我们一码游小程序,之后退出我们小程序后(用户没有删除我们一码游小程序),再次操作,这时,登录失败。原因就是进入我们小程序,不会再执行onLaunch中的代码,获取不到extraData参数,从而导致登录失败。后来我又改到onShow中才解决了问题,如下图:

二、同时在path、extraData中传了参,被半屏打开的小程序这边,如何处理参数?
拿我们上次对接立克办为例,后来了解到,他们是这样传的参数,如下图:

path中传递了参数?cityCode=650200000000,extraData中传了state、token。从立克办小程序跳转过来,是需要直接进入我们一码游小程序的指定地区的首页。
由于extraData中也有传参,但处理逻辑只能在被跳转的页面中处理,所以我在app.onShow中获取参数后,将参数存储到了store中,这样在页面中就能获取到传递过来的extraData参数了(不过在小程序退出或者退出登录时,记得销毁,避免其他用户登录误用立克办的登录逻辑)。
App.vue
js
onUnload(){
uni.setStorageSync('lkbToken', "")
this.$store.commit('setExtraData', null)
}
store/index.js
js
logout(state) {
// 其他代码已省略
state.extraData = null
uni.setStorageSync('extraData', null)
uni.setStorageSync('lkbToken', "")
uni.setStorageSync('platformOpenId', "")
}
三、完整的半屏打开小程序,单点登录功能涉及的改动如下:
1、小程序 App.vue
js
export default {
onShow (opt) {
let extraData = opt?.referrerInfo?.extraData
if(extraData){
this.$store.commit('setExtraData',extraData);
}else{
this.$store.commit('setExtraData', null)
}
},
onUnload(){
uni.setStorageSync('lkbToken', "")
this.$store.commit('setExtraData', null)
}
}
2、小程序 pages/index/main.vue
js
export default {
data() {
return {
q: 0, // 是否是扫码/传参切换城市,原来设置的是-1,但是当为-1时永远都不可能会展示推荐景区和城市
query : {},
lkbLoading : false
};
},
onLoad(opt) {
this.query = opt
// 扫描二维码进入小程序,二维码参数获取
if (opt.q) {
const query = decodeURIComponent(opt.q).split("?")[1];
this.query = this.$qs.parse(query)
this.q = this.query.cityCode;
}
else if (opt.cityCode) {
this.q = opt.cityCode;
}
// 立克办半屏打开我们小程序,默认进入克拉玛依首页(这里的opt.token是立克办的token字符串)
else if( this.getLkbBool() ){
opt.cityCode= this.query.cityCode || '650200000000'
this.q = opt.cityCode;
if (!uni.getStorageSync("token")) {
this.likeBanLogin()
};
}
// 其他无关逻辑略...
},
onShow() {
if(this.getLkbBool()){
if (!uni.getStorageSync("token")) {
this.likeBanLogin()
};
return
}
// 其他无关逻辑略...
},
methods: {
// 判断是否立克办传参
getLkbBool(){
const extraData = this.$store.getters.extraData;
return (extraData.token && extraData.state==='likeban') || uni.getStorageSync("lkbToken")
},
// 获取小程序登录用户openid等信息
getOpenId() {
return new Promise((resolve, reject) => {
uni.showLoading();
uni.login({
//获取登录凭证
success: ({ code }) => {
if (code) {
let url = "";
// #ifdef MP-WEIXIN
url = "/auth/getOpenId";
// #endif
// #ifdef MP-ALIPAY
url = "/auth/getAliPayUserId";
// #endif
//获取 openid
this.$http.post(url, {
code: code.split("&")[0],
}).then((res) => {
uni.hideLoading();
resolve(res)
}).catch(() => {
uni.hideLoading();
reject()
});
}
},
fail(err) {
uni.hideLoading();
console.error("uni.login失败", err);
reject(err)
},
});
})
},
// 立克办用户单点登录
likeBanLogin(){
const extraToken = this.$store.getters.extraData?.token;
if(extraToken){
this.$set(this.query,'token',extraToken)
uni.setStorageSync("lkbToken",extraToken);
}
// 这里的query.platformOpenId最终没用到,最先从商城跳转回来使用的 wx.miniProgram.switchTab,
// 实际测试发现无法带参跳转,改为 wx.miniProgram.redirectTo 跳转回小程序中转页 pages/lkb/main.vue 后,
// 已经将 platformOpenId 参数设置在了Storage中(可查看后面的代码)
//let platformOpenId = this.query.platformOpenId || uni.getStorageSync("platformOpenId");
let platformOpenId = uni.getStorageSync("platformOpenId");
if (!platformOpenId) {
// let url = `${process.env.VUE_APP_H5_HOST}/yytour/wechat/mp-auth?source=${process.env.VUE_APP_SOURCE}&lkbToken=${extraToken}`;
let url = `${process.env.VUE_APP_H5_HOST}/yytour/wechat/mp-auth?source=${process.env.VUE_APP_SOURCE}`;
uni.redirectTo({
url: `/pages/webView/main?url=${encodeURIComponent(url)}`,
});
return
}
!uni.getStorageSync("platformOpenId") && uni.setStorageSync("platformOpenId", platformOpenId);
if(this.lkbLoading){
return
}
this.lkbLoading = true;
this.getOpenId().then((res)=>{
const data = res.data
// #ifdef MP-WEIXIN
uni.setStorageSync("openid", data.openId);
// #endif
// #ifdef MP-ALIPAY
uni.setStorageSync("aliPayUserId", data.aliPayUserId);
// #endif
this.$store.commit("updateUserInfo", data);
let storageLkbToken = extraToken || this.query.token || uni.getStorageSync("lkbToken")
this.$http.post("/auth/miniLoginOfLiKeBan", {
openId: data.openId,
platformOpenId: data.platformOpenId || uni.getStorageSync("platformOpenId"),
liKeBanEncryptToken : storageLkbToken
})
.then((res) => {
this.$store.commit("updateUserInfo", res.data);
this.$store.commit("setToken", res.data.token);
this.$store.commit("setShopToken", res.data.merchantToken);
this.lkbLoading = false;
this.$nextTick(()=>{
//this.autoMarketing()
})
}).catch((res)=>{
this.lkbLoading = false;
if (res.errorCode === '00012') {
let timer = setTimeout(()=>{
uni.navigateTo({
url: `/pages/login/main?redirect=true`
})
clearTimeout(timer)
},1500)
}
// $http 中已经做提示,这里就不再重复写了
// else{
// uni.showToast({ title: res.message, icon: 'none' })
// }
});
})
},
}
}
上面likeBanLogin方法中有一段代码,
js
let platformOpenId = this.query.platformOpenId || uni.getStorageSync("platformOpenId");
// console.log('---likeBanLogin---platformOpenId---',platformOpenId)
if (!platformOpenId) {
// console.log('---likeBanLogin---!platformOpenId---')
let url = `${process.env.VUE_APP_H5_HOST}/yytour/wechat/mp-auth?source=${process.env.VUE_APP_SOURCE}&lkbToken=${extraToken}`;
uni.redirectTo({
url: `/pages/webView/main?url=${encodeURIComponent(url)}`,
});
return
}
这是我们从商城获取登录接口的必填参数platformOpenId,商城前端用的是H5开发的,具体为啥要获取这个参数,这是这个项目的一些后台逻辑需要,我们没必要去纠结这个问题。
商城H5页面 pages/mp-auth/index.vue
js
export default {
created() {
if (!code) {
this.$toAuthUrl() // 去授权 获取code
} else {
// 已获取code, 进行登录.
let newMchId = (source == 1 || source == 0 || !source) ? mchId : -1
this.$http.post('merchant/mch-user/userInfoByCode', {
code,
mchId: newMchId
}).then(({ data }) => {
if (!data) {
console.error('登录接口出错, data为空')
return
}
// 立克办登录处理逻辑
if(lkbToken){
// switchTab 无法带参数跳转,使用一个中转页面`/pages/lkb/main`接收参数并处理相应逻辑,然后从中转页面跳转首页`/pages/index/main`
// wx.miniProgram.switchTab({
// url: `/pages/index/main?platformOpenId=${data.platformOpenId}&token=${lkbToken}`
// })
wx.miniProgram.redirectTo({
url: `/pages/lkb/main?platformOpenId=${data.platformOpenId}&token=${lkbToken}`
})
}else{
wx.miniProgram.redirectTo({
url: `/pages/login/main?platformOpenId=${data.platformOpenId}&redirect=${redirect}`
})
}
})
}
}
}
本来登录前从小程序跳转到商城这个页面获取到参数后就可以直接返回小程序首页的,可要返回的小程序页面是tab首页,wx.miniProgram.switchTab的url中无法传递query参数,我只好使用wx.miniProgram.redirectTo,又特意在小程序中建了一个页面文件 pages/lkb/main.vue,用于在小程序中接收从商城H5获取到的登录接口参数platformOpenId。
小程序 pages/lkb/main.vue
js
export default {
onLoad(opts) {
let { platformOpenId, token } = opts;
uni.setStorageSync("platformOpenId", platformOpenId);
//token && uni.setStorageSync("lkbToken", token);
this.$nextTick(() => {
uni.switchTab({
url: "/pages/index/main"
});
});
},
};