手把手教你构建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地址在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值