手把手教你構建vue項目(微信h5以及hybrid混合開發)(五)——路由權限配置,微信登錄授權的設計思路以及測試微信登錄授權的小技巧

這一章主要是講路由權限配置,微信登錄授權的設計思路以及測試微信登錄授權的小技巧

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地址在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章