最近做了一个需求(效果部分参照腾讯云 https://cloud.tencent.com/),Web端生成二维码,跳转微信小程序并确认授权,实现小程序和Web的同时登录
一、预期效果(腾讯云)
首先扫web端的这个二维码
然后跳转对应的小程序,点击小程序中的确认登录后,调起授权成功后实现小程序和Web同时登录
二、梳理思路
-
因为跳转的是小程序页面,所以web端的码对应的是小程序某页面的二维码 -
若要实现小程序和Web的同时登录,web扫码后, ① 一定需要有一个“唯一信件”,从Web带到小程序(拼接在二维码中),在小程序中解析出来以后,登录时把它带给后端; ② web端要轮询去调后端的接口,并且是带着这个“唯一信件”,来询问这个“唯一信件”有没有在小程序中获得登录 这个过程就好像,小孩拿着身份证去学校报道,父母在家里持续打电话询问学校:身份证号为XX的小孩现在是否完成报道 -
在上一步的基础上,这个“唯一信件” 还存在一个过期时间,比如超过10分钟后就自动失效 -
从代码的角度来讲,这个“唯一信件” 应由后端生成,这样也方便后端去维护过期状态
至此,应该只剩下一个问题:web端的这个二维码,由前端还是后端生成?怎样生成?
三、关于web端的二维码
-
如果由前端生成,可以采用 扫普通链接二维码 打开小程序,根据官方给出的示例去配置,前端安装qrcodejs2插件生成二维码。 只需要注意一点:开发时只能采用对应的 测试链接 进行模拟(动态参数无法跳转),规则正式发布以后,才可以拿到动态参数 -
上面的这种方式,二维码的内容对应的是链接地址,所以是先跳转链接,然后再唤起小程序,从体验上来说,不是那么友好 -
并且上述过程中需要后端往服务器放校验文件、前端需要下载qrcode库,整体并不快捷 -
况且 后端调微信接口直接生成小程序码 的方式有很多种,功能场景也很完善(我这里后端采用的是wxacode.getUnlimited 这种)
对比下来,后端来生成,开发过程更清晰、开发时间成本也更低、后续也更好维护
四、前端代码实现
前端代码实现——web篇
<div class="main">
<div id="login_container_wechat" ref="qrCodeUrl">
<img :src="qrCode" alt="" v-show="qrCode">
<div class="timeout-cover" v-show="timeOut">
<div>您的二维码已失效</div>
<div>请点击下方刷新按钮</div>
</div>
</div>
<div style="text-align: center; margin: 30px auto">
请使用微信扫一扫登录
<a href="javascript:;" @click="refreshCode"><a-icon type="redo" />刷新 </a>
</div>
</div>
.main {
width: 384px;
height: 300px;
background: #fff;
margin-top: 100px;
}
#login_container_wechat {
width: 200px;
height: 200px;
display: inline-block;
display: flex;
justify-content: center;
margin: 30px auto;
position: relative;
}
.timeout-cover {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.9);
color: rgb(245, 59, 59);
}
#login_container_wechat img {
width: 200px;
height: 200px;
background: #f5f5f5;
}
data () {
return {
wechatTimer: null,
wechatTimerCount: 0,
qrCode: undefined,
qToken: undefined,
timeOut: false
}
},
mounted () {
this.getLoginWxQrCode()
},
getLoginWxQrCode () {
getLoginWxQrCodeApi().then(res => {
const { code, data, msg } = res
if (code === 0) {
this.timeOut = false
this.qrCode = data.QCode
this.qToken = data.QToken
this.wechatTimer = setInterval(() => {
this.getWechatLoginResult()
this.wechatTimerCount = this.wechatTimerCount + 1
if (this.wechatTimerCount > 180) {
this.clearWechatTimer()
}
}, 1000)
} else {
this.$message.error(msg)
}
})
},
getWechatLoginResult () {
getQrCodeStatusApi({
QToken: this.qToken
}).then(res => {
const { code, data } = res
if (code === 0) {
const status = data.status
if (status === 0) {
console.log('二维码状态正常')
} else if (status === 1) {
console.log('二维码已被扫描')
} else if (status === 2) {
console.log('二维码操作了')
this.clearWechatTimer()
if (data.token) {
}
} else if (status === -1) {
console.log('二维码过期了')
this.timeOut = true
this.clearWechatTimer()
}
}
})
},
refreshCode () {
this.clearWechatTimer()
this.getLoginWxQrCode()
},
clearWechatTimer () {
clearInterval(this.wechatTimer)
this.wechatTimerCount = 0
},
web端效果:
前端代码实现——小程序
扫码进入小程序,在对应页面的 onLoad 中拿到链接里拼接的参数
先看下后端使用 wxacode.getUnlimited 的二维码内容:
- page,是小程序的落地页,需要在前面生成web端二维码时给到后端
- scene 是二维码中的参数载体,我们在小程序中,取的就是这个参数
- 重要的是,在真机中,参数scene有可能被编码,需要兼容这个现象
- 跳转到这个页面后,我们需要重新走一遍小程序的授权登录(不管当前是否已经登录)
知道了上面这些前提,就可以码起来了
<view class="common-login-main">
<view class='mytop-box'>
<view class="mytop-avatar">
<open-data type="userAvatarUrl"></open-data>
</view>
<view class='mytop-username'>
<open-data type="userNickName"></open-data>
</view>
<view class="desc">将使用微信登录XX平台</view>
</view>
<button class="btn" bindtap="getUserProfile">确认登录</button>
<view class="cancle" bindtap="cancle">取消</view>
</view>
.common-login-main {
padding-top: 200rpx;
}
.mytop-box {
display: flex;
width: 100%;
height: 100%;
align-items: center;
flex-direction: column;
justify-content: center;
}
.mytop-avatar {
overflow: hidden;
width: 130rpx;
height: 130rpx;
left: 50%;
border-radius: 50%;
box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.2);
}
.mytop-username {
font-size: 35rpx;
color: #3E3E40;
margin-top: 40rpx;
}
.desc {
font-size: 28rpx;
color: var(--descColor);
margin-top: 40rpx;
}
.btn {
margin-top: 80rpx;
width: 520rpx;
height: 72rpx;
line-height: 72rpx;
background-color: #FA7627;
color: #fff;
font-size: 28rpx;
border-radius: 36rpx;
margin-bottom: 40rpx;
}
.cancle {
text-align: center;
color: #FA7627;
font-size: 28rpx;
}
onLoad(options) {
if (options.scene) {
var scene = {}
if (options.scene.includes('%3D')) {
scene = JSON.parse(
'{"' +
decodeURI(
decodeURIComponent(options.scene)
.replace(/&/g, '","')
.replace(/=/g, '":"')
) +
'"}'
)
} else {
let sceneArr = options.scene.split('&')
sceneArr.forEach(item => {
scene[item.split('=')[0]] = (item.split('=')[1])
})
}
}
updateWxQTokenInfoAPI({ scene.QToken })
this.setData({
query: scene
})
},
getUserProfile () {
wx.showLoading()
var p1 = new Promise(function (resolve, reject) {
wx.login({
success: res => {
resolve(res)
}
})
})
var p2 = new Promise(function (resolve, reject) {
wx.getUserProfile({
desc: '用于完善会员资料',
success: res => {
resolve(res)
},
fail: err => {
wx.hideLoading()
}
})
});
Promise.all([p1, p2]).then((results) => {
this.handleUserInfo({
...results[0],
...results[1]
})
},
handleUserInfo (data) {
const { code, encryptedData, userInfo, iv, rawData, signature, cloudID } = data
const params = {
QToken: this.data.query.QToken
}
}
实际效果: 实现下来会发现代码并不难,主要是前面的构思阶段要考虑
欢迎评论,一起探索更多~
|