这一章主要是讲路由权限配置,微信登录授权的设计思路以及测试微信登录授权的小技巧
1.路由权限和微信登录授权操作
1)首先我们得在src目录新建一个setting.js文件
setting.js
module.exports = {
title: '',
// 设置页面的过渡效果
needPageTrans: true,
wxConfig: {
appId: '0000',
appSecret: '0000'
},
// 路由白名单
whiteList: [
'/login',
'/wxlogin',
'/protocol'
]
}
2)然后src/utils/index.js 新增一个方法
src/utils/index.js
/**
* @description 判断设备信息
* @returns {String}
*/
export function judgeDevice() {
let ua = window.navigator.userAgent,
app = window.navigator.appVersion
// 这里可以叫安卓和原生端在userAngent中塞入约定好的名字,我这里是'hydf'
if (ua.toLowerCase().includes('hydf')) {
// app 端
if (ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) {
// ios端
return 'hydf-ios'
} else if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1) {
// android端
return 'hydf-android'
}
} else {
if (ua.toLocaleLowerCase().includes('micromessenger')) {
// 微信浏览器
return 'wx'
} else {
// 其他浏览器
return 'others'
}
}
}
在这里我先插入微信登录的粗略的流程图
我司的主要逻辑是为了获取手机号,所以微信登录授权的过程中,还有与手机号绑定的过程。然后微信登录的大致思路就是,通过路由拦截器进行拦截,将我们开始进入的url存储起来,然后判断cookie中有没有用户id,后面都用userId代替描述。如果没有用户id就跳到微信授权登录页面,最后微信登录授权的逻辑全部放在了微信登录页处理,也就是起到了一个过渡页的作用。虽然多了一个过渡页面,但是可以比较好的处理微信登录授权一开始就是空白的,然后出现微信授权的弹窗,这样处理可能相对于用户来说是比较友好的吧(因为每个用户的视觉体验都是不一样的。所以着重说明是可能。)
3)在src根目录中新建permission.js这一步主要是路由权限控制
src/permission.js
import router from '@/router'
import store from '@/store'
import { Toast } from 'vant'
import { getToken } from '@/utils/auth'
import { LOGOUT, TO_URL, USER_TOKEN } from '@/utils/constant'
import defaultSettings from './settings'
// import VConsole from 'vconsole'
// const vConsole = new VConsole() // 能够在vconsole中使用console.log打印
// 白名单需要公用就抽离出来放在了settings文件中
// const whiteList = ['/login', '/wxlogin', '/invite/accept', '/studentlogin']
// console.log(store.getters.device)
const history = window.sessionStorage
history.clear()
// let historyCount = history.getItem('count') * 1 || 0
history.setItem('/', 0)
// 这里是设置页面的过度效果。不过在微信登录中,最好不要使用,体验感很差
// function setPageDirection(to, from) {
// // 设置过渡方向
// if (to.params.direction) {
// store.commit('page/updateDirection', to.params.direction)
// } else {
// const toIndex = history.getItem(to.path)
// const fromIndex = history.getItem(from.path)
// // 判断记录跳转页面是否访问过,以此判断跳转过渡方式
// if (toIndex) {
// if (
// !fromIndex ||
// parseInt(toIndex, 10) > parseInt(fromIndex, 10) ||
// (toIndex === '0' && fromIndex === '0')
// ) {
// store.commit('page/updateDirection', 'forward')
// } else {
// store.commit('page/updateDirection', 'back')
// }
// } else {
// ++historyCount
// history.setItem('count', historyCount)
// to.path !== '/' && history.setItem(to.path, historyCount)
// store.commit('page/updateDirection', 'forward')
// }
// }
// }
router.beforeEach(async (to, from, next) => {
const hasToken = getToken(USER_TOKEN)
// setPageDirection(to, from)
// 权限验证
if (hasToken) {
// 如果是登录的情况下,移除记录的url
localStorage.removeItem(TO_URL)
// 获取用户信息
const hasGetUserInfo =
store.getters.userInfo && store.getters.userInfo.userId
if (hasGetUserInfo) {
next()
} else {
try {
await store.dispatch('user/getUserInfo')
next()
} catch (err) {
localStorage.setItem(TO_URL, to.fullPath)
await store.commit('user/' + LOGOUT)
Toast(err || 'Has error')
if (store.getters.device === 'wx') {
// 在微信中就直接
next({
path: '/wxlogin',
replace: true,
})
} else {
next({
path: `/login?redirect=${to.path}`,
replace: true,
})
}
}
}
next()
} else {
// 没有token
const findeIndex = defaultSettings.whiteList.findIndex(item => {
return to.path.includes(item)
})
if (findeIndex !== -1) {
// 在白名单中就不需要登录
next()
} else {
// 不在白名单的路由就要记录当前的url并且跳转到微信登录
localStorage.setItem(TO_URL, to.fullPath)
if (store.getters.device === 'wx') {
// 在微信中就直接
next({
path: '/wxlogin',
replace: true,
})
} else {
next({
path: `/login?redirect=${to.path}`,
replace: true,
})
}
}
}
})
4)在main.js中引入permmison.js如下
main.js
.....
.....
// token 验证
import './permission'
......
......
Vue.config.productionTip = false
Vue.config.devtools = true
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')
2.微信登录授权操作以及登录逻辑
我们在上面路由权限中让没有userId或者token(用户的唯一标识)跳转到微信登录过渡页,这个过渡页是我们自己写的。接下来是在这个页面写主要微信登录逻辑以及授权操作。也就是说我们需要新建两个页面,一个是登录过渡页wxLogin.vue,另外一个是手机号login.vue页面,这个后面再说。
在这里我们需要新建一个专门的js放登录逻辑
src/utils/wechatUtil.js
import store from '@/store'
import router from '@/router'
import wx from 'weixin-js-sdk'
import {
getSignature,
getWechatUserByCode,
loginByWechatOpenId,
} from '@/api/weChat'
import {
SET_OPENID,
OPENID,
SET_USERINFO,
SET_TOKEN,
TO_URL,
USER_TOKEN,
} from '@/utils/constant'
import { getToken } from '@/utils/auth'
import { Toast } from 'vant'
export default {
/* wechat jssdk配置接入 'wx3b5e6070b4241088' */
appid: process.env.VUE_APP_WECHAT_APPID,
// appid: 'wx3b5e6070b4241088',
getCode() {
const code = location.href.split('?')[1]
if (!code) return {}
const obj = {}
code.split('&').forEach(item => {
const arr = item.split('=')
obj[arr[0]] = arr[1]
})
return obj
},
// 用opengID登录
loginWithOpenId(openId) {
const path = localStorage.getItem(TO_URL)
loginByWechatOpenId(openId)
.then(data => {
// console.log('通过openid获取token成功')
// console.log('token---', data)
store.commit(`user/${SET_TOKEN}`, data.token)
// 跳转到存储的原来的url
// if (path.includes('/invite/accept')) {
// // console.log('执行了接受邀请的操作了')
// this.acceptInvitation()
// } else {
router.replace({
path,
})
// }
})
.catch(err => {
// console.log('通过openid获取token失败')
if (err.code === 2001) {
// 用户不存在 微信没有和手机号进行绑定
if (path.includes('/invite/accept')) {
// 如果是接受邀请页就跳转到学生登录页面
router.replace({
path: '/studentlogin',
})
} else {
router.replace({
path: '/login',
})
}
}
})
},
// created函数中获取code并登录
createdGetWechatUserByCode() {
const device = store.getters.device
const code = this.getCode().code
const token = getToken(USER_TOKEN)
const openId = getToken(OPENID)
// console.log('createdGetWechatUserByCode-enter')
if (device === 'wx') {
if (!token) {
if (!openId) {
if (code) {
// code 存在就换取openId
return getWechatUserByCode(code).then(data => {
// 存储openId
// console.log('getWechatUserByCode')
const { headimgurl, nickname, openId } = data
store.commit(`user/${SET_OPENID}`, openId)
store.commit(`user/${SET_USERINFO}`, {
headimgurl,
nickname,
})
this.loginWithOpenId(openId)
})
} else {
this.wxLogin()
}
} else {
router.replace({
path: '/login',
})
}
} else {
// 存在就提示已经登录了
Toast('您已登录!')
}
}
},
wxLogin() {
const openId = getToken(OPENID)
if (openId) {
// openId 存在就执行用openId登录
this.loginWithOpenId(openId)
} else {
// 否则就跳转微信的获取code过程
const redirect_uri = encodeURIComponent(location.href)
const link = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${this.appid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`
window.location.href = link
}
},
/* 初始化wxjsdk各种接口 */
init(apiList = []) {
const device = store.getters.device
if (device === 'wx') {
// 需要使用的api列表
return new Promise((resolve, reject) => {
getSignature(store.state.page.initLink).then(res => {
if (res.appId) {
wx.config({
// debug: true,
appId: res.appId,
timestamp: res.timestamp,
nonceStr: res.nonceStr,
signature: res.signature,
jsApiList: apiList,
})
wx.ready(res => {
// 微信SDK准备就绪后执行的回调。
resolve(wx, res)
})
} else {
reject(res)
}
})
})
} else {
return Promise.reject('init-none')
}
},
}
wechatUtil.js中包含微信注入jsdk的公共方法。这里我就不赘述,详情可以看我的博客vue中使用WX-JSSDK的两种方法
接下来在微信过渡页wxLogin.vue中
wxLogin.vue
<template>
<div class="login-contaniner">
<div class="content">
<div class="wx-box" v-if="device === 'wx'">
<div class="header" v-if="userInfo">
<img :src="userInfo.headimgurl" alt="微信头像" />
<span class="name">{{ userInfo.nickname }}</span>
</div>
<div class="title">绑定手机</div>
</div>
<div class="other-box" v-if="device === 'others'">
<div class="title">欢迎来到微信h5-demo</div>
</div>
<div class="input-container">
<div class="phone-box" v-onepx-b>
<input
type="number"
class="phone"
placeholder="请输入手机号"
v-model.trim="phoneNumber"
oninput="if (value.length > 11) value = value.slice(0, 11).replace(/[^\d]/g, '')"
v-reset-page
/>
<div
class="svg-container del-icon"
@click="delPhoneNum"
v-show="!!phoneNumber && phoneNumber.length > 0"
>
<svg-icon icon-class="del-btn"></svg-icon>
</div>
</div>
<div class="code-box">
<input
type="number"
class="code"
placeholder="请输入验证码"
v-model="code"
oninput="if (value.length > 6) value = value.slice(0, 6).replace(/[^\d]/g, '')"
v-reset-page
/>
<button
class="code-btn"
:class="[codeBtnActive ? 'normal' : 'disable']"
@click="getCode"
:disabled="!codeBtnActive"
>
{{ codeBtnText }}
</button>
</div>
<div class="input-tip" v-if="isTipShow">*{{ tipText }}</div>
</div>
<div class="login-btn-box">
<button
class="login-btn"
@click="login"
:class="[loginBtnActive ? '' : 'inactive']"
:disabled="!loginBtnActive"
>
登录
</button>
</div>
<div class="tip">
未注册的手机将自动登录
</div>
</div>
</div>
</template>
<script>
import { loginByPhone, sendPhoneVerifyCode } from '@/api/user'
import { mapMutations, mapGetters } from 'vuex'
import { SET_TOKEN, TO_URL } from '@/utils/constant'
/* *验证码有误,请重新输入
*手机号格式不正确,请重新输入
*手机号码不能为空 */
export default {
name: 'Login',
data() {
return {
phoneNumber: '',
code: '',
codeBtnText: '获取验证码',
isTipShow: false,
tipText: '',
isCodeBtnAble: true, // 获取验证码按钮可用
}
},
computed: {
...mapGetters(['userInfo', 'openId', 'device']),
// 发送验证码的激活状态
codeBtnActive() {
if (
!!this.phoneNumber &&
this.phoneNumber.length === 11 &&
this.isCodeBtnAble
) {
return true
} else {
return false
}
},
// 登录按钮的激活状态
loginBtnActive() {
if (
this.phoneNumber &&
this.code &&
this.phoneNumber.length === 11 &&
this.code.length === 6
) {
return true
} else {
return false
}
},
// 页面来源
source() {
return this.$route.query.source || ''
},
goodsId() {
return this.$route.query.goodsId
},
},
components: {},
created() {
// wechatUtil.hideOptionMenu()
},
mounted() {},
methods: {
...mapMutations({
setUserToken: 'user/' + SET_TOKEN,
}),
// 清空手机号输入框
delPhoneNum() {
if (this.phoneNumber === '') return
this.phoneNumber = ''
},
// 获取验证码
getCode() {
this.isTipShow = false
// 验证手机号是否符合规定
if (!/^1[3456789]\d{9}$/.test(this.phoneNumber)) {
this.isTipShow = true
this.tipText = '手机号格式不正确,请重新输入'
return
}
if (!this.isCodeBtnAble) return
this.isCodeBtnAble = false
this.countDown()
sendPhoneVerifyCode(this.phoneNumber)
.then(() => {
this.$toast('发送成功')
})
.catch(err => {
if (err.code === 2006) {
this.isTipShow = true
this.tipText = '请稍后发送,验证码发送太频繁'
}
})
},
// 获取验证码倒计时
countDown() {
let totalTime = 60
this.codeBtnText = `重新获取(${totalTime}S)`
this.timer = setInterval(() => {
if (totalTime > 0) {
totalTime--
this.codeBtnText = `重新获取(${totalTime}S)`
} else {
this.codeBtnText = '获取验证码'
this.isCodeBtnAble = true
clearInterval(this.timer)
this.timer = null
}
}, 1000)
},
login() {
this.isTipShow = false
if (!/^1[3456789]\d{9}$/.test(this.phoneNumber)) {
this.isTipShow = true
this.tipText = '手机号格式不正确,请重新输入'
return
}
let params
if (this.device === 'wx' && this.openId) {
// 在微信中就传 头像等用户信息
const { headimgurl, nickname, username } = this.userInfo
params = {
headimgurl: headimgurl || '',
nickname: nickname || '',
openId: this.openId,
phone: this.phoneNumber,
username: username || '',
verifyCode: this.code,
}
} else {
params = {
phone: this.phoneNumber,
verifyCode: this.code,
}
}
loginByPhone(params)
.then(data => {
this.setUserToken(data.token)
// 跳转到存储的原来的url
const path = localStorage.getItem(TO_URL)
this.$router.replace({
path,
})
})
.catch(err => {
this.isTipShow = false
if (err.code === 2003) {
this.isTipShow = true
this.tipText = '验证码有误,请重新输入'
}
})
},
},
watch: {
phoneNumber() {
this.isTipShow = false
this.tipText = ''
},
code() {
this.isTipShow = false
this.tipText = ''
},
},
}
</script>
这里只提供主要思路以及代码演示,主要代码还是在我的github项目中。可能运行的时候跳转报代理错误那是因为接口的api地址没有,将vue.config.js中的proxy代理地址改成你们自己开发的地址就行了。
3.如何测试微信公众号
1)首先申请微信公众平台的接口测试账号
申请地址
2)申请好以后将本地的微信登录授权appid改成你申请的
我们可以在开发环境中配置好。在env.development文件中进行配置
3) 然后叫我们的后端开发同事帮忙把他们api开发环境需要用到微信api的appid改成你申请的测试appid
这样前端后端都配置好了,就可以用测试的账号测试微信登录授权了。
不过要注意的是需要给网页授权用户基本信息写上自己的ip
js接口域名也最好也改成你的项目运行ip地址