這一章主要是講路由權限配置,微信登錄授權的設計思路以及測試微信登錄授權的小技巧
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地址