
上代码
根据以上思路,现在开始编码环节。笔者采用的是 Vue3,Vue2 的开发者还请根据情况做适当调整。
为了方便唤起用户授权登录逻辑,笔者打算将授权登录封住为一个 login 页面,这样做的好处是我们在任何判断到需要登录的地方直接通过 Vue Router 的 push 方法跳转到登录页面即可。
通常情况下,我们的应用并不是所有页面都需要登录后才能访问,只有在访问特定页面时候,才需要用户登录,那么我们就需要标识哪些页面需要进行登录鉴权。这里我们可以利用 Vue Router 的 meta 属性来进行标识,官方文档对 meta 解释如下:
有时,你可能希望将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的
meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到。
刚好 Vue Router 官方就有示例,如下:
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 需要登录后才能访问的页面
meta: { requiresAuth: true }
},
{
path: ':id',
component: PostsDetail,
// 任何人都可访问的页面
meta: { requiresAuth: false }
}
]
}
]
接下来我们就可以在 Vue Router 的全局守卫beforeEach中获取到这个元信息从而做登录跳转了。
router.beforeEach((to, from) => {
// 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !userStore.isLogin) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: '/login',
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
}
}
})
需要补充说明的是,userStore.isLogin的实现。这里和我们实际采用的登录态维护方案有关,如果是采用token方式的话,那就是检查token是否已经存在。笔者采用了vuex作为来保存token,然后借助插件来将Store中的数据持久化到localStorage。
接下来我们来看具体的实现:
login.vue: 登录组件
<template>
<div ></div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { jump2Auth, getUserInfo } from '@/hooks/useWechatAuth'
import { userStore } from '@/store/modules/user'
import { redirectTo, getRouteQuery } from '@/hooks/usePage'
export default defineComponent({
name: 'Login',
setup() {
let code = getRouteQuery().code as string
// 3.如果有 code,则已经授权
if (code) {
getUserInfo(code as string).then((res: any) => {
// 记录 token
userStore.saveToken(res.access_token)
const redirect = userStore.userState.landPageRoute || '/'
// 跳转到授权前访问的页面
redirectTo(redirect)
})
} else {
// 1.记录上一个页面的地址
const { redirect } = getRouteQuery()
if (redirect) {
userStore.setLandPage(redirect as string)
}
// 2.跳转授权
const callbackUrl = window.location.origin + window.location.pathname
jump2Auth(callbackUrl)
}
},
})
</script>
可以看到,login 页面其实并没有什么内容,跳转到该页面后,我们会直接重定向到微信授权的页面,授权回调回来也会回到该页面,此时我们再通过获取路由参数的方式获取 code 参数。
@/hooks/usePage.ts: 该文件主要是封装了 router 相关的常用方法。
import router from '@/router'
import { cloneDeep } from 'lodash'
import { toRaw } from 'vue'
/**
* 重定向
* @param path 路径
*/
export function redirectTo(path: string) {
const { replace } = router
replace({
path,
})
}
/**
* 获取路由上 query 参数
*/
export function getRouteQuery() {
const { currentRoute } = router
const { query } = currentRoute.value
return cloneDeep(query)
}
@/hooks/useWechatAuth.ts:该文件封装了微信授权与后端交互的请求。
import { useAxios } from '@/hooks/useAxios'
/**
* 获取微信授权的跳转地址
* @param callbackUrl 授权后回调链接
* @returns
*/
export function jump2Auth(callbackUrl: string) {
useAxios({
url: '/api/wechat/auth',
params: {
redirect_url: callbackUrl,
},
}).then((authUrl: any) => {
if (process.env.NODE_ENV === 'development') {
window.location.href = callbackUrl + '?code=test'
} else {
window.location.href = authUrl
}
})
}
/**
* 提交 code 进行登录
* @param code
* @returns
*/
export async function getUserInfo(code: string) {
const userInfo = await useAxios({
method: 'POST',
url: '/api/wechat/auth',
params: {
code,
},
})
return userInfo
}
@/store/modules/user.ts: 全局状态存储,主要是记录 token 和登录前访问页面。
import { Module, VuexModule, Mutation, getModule, Action } from 'vuex-module-decorators'
import store from '@/store'
import { initialUnencryptedStorage } from '../globals'
interface UserState {
token: string
landPageRoute: string
}
const NAME = 'user'
// name: 模块名字
// namespaced 表示开启命名空间
// dynamic 设置为 true 时,表示创建动态模块,运行时将模块注册到存储中
// preserveState 如果数据有持久化,该变量为 true 时可以从 storage 中拿取初始值
@Module({
namespaced: true,
name: NAME,
dynamic: true,
store,
preserveState: Boolean(initialUnencryptedStorage[NAME]),
})
export class User extends VuexModule {
userState: UserState = {
token: '',
/** 登录前访问页面 */
landPageRoute: '',
}
get isLogin(): boolean {
return !!this.userState.token
}
@Mutation
saveToken(token: string): void {
this.userState.token = token
}
@Mutation
setLandPage(route: string): void {
this.userState.landPageRoute = route
}
}
export const userStore = getModule(User)
笔者借助vuex-persistedstate插件将store中数据存储到了localStorage,这样做的好处是用户关闭页面后,再次访问,即不用重新触发微信授权流程,大大优化了用户体验。
总结
不得不说,Vue3 在代码抽象和复用这块上,写起来着实舒服很多,希望大家也也尝试着按照官方实践那样,多将逻辑代码解耦抽离,生成一个个的 hook,这样代码就显得优雅多啦。该方案经过笔者尝试论证,不论是代码整洁优雅程度,还是业务需求的实现上,都几乎完美(请容我装一波 b)。当然,这里可能存在我没发现的 bug 或者痛点,毕竟从来没有十全十美的架构嘛,这里也欢迎看官大佬们和我交流探讨,提供更好的方案和想法。