vue前端权限管理和动态路由
在毕设中使用了vue做为前端,不免会遇到权限管理的问题,以及动态路由和动态页面问题
vue使用
vue很适合开发的单页网页应用,并且官网有详细的文档教程,入门友好
权限管理
先说下设计思路,使用vue-router路由拦截请求。用户登录时记录下服务器返回的token或者session,保存到cookie中。然后每次路由跳转时,先获取登录状态,然后再对比权限路径。但是这样做有个缺点,应用特别大的时候,会变慢。至于后端的数据都是进行权限拦截的,就算前端越过拦截,还是无法获取数据。
具体实现
- 占坑,有时间填坑,
- 占坑,前端权限已经实现
更新
利用 Vue Router
Vue Router 中提供了路由导航守卫,在每次链接转跳的时候可以进行检查
全局前置守卫 router.beforeEach(),当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
具体用法:
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
每个守卫方法接收三个参数:
to: Route
: 即将要进入的目标 路由对象from: Route
: 当前导航正要离开的路由
next: Function
: 一定要调用该方法来resolve
这个钩子。执行效果依赖next
方法的调用参数。
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址。next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next
传递任意位置对象,且允许设置诸如replace: true
、name: 'home'
之类的选项以及任何用在router-link
的to
prop 或router.push
中的选项。next(error)
: (2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给router.onError()
注册过的回调。
确保要调用 next
方法,否则钩子就不会被 resolved。 如果这个next方法没有调用,网页无法完成跳转。
使用 Vue Router 路由元信息
我在meta
字段里面定义了roles
字段完成动态路由的生成,具体用法如下。
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, // a meta meta: { roles: ['ROLE_ADMIN', 'ROLE_TEA'] } } ] } ] })
具体路由守卫代码。
import router from './router' import store from './store' import {Message} from 'element-ui' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import {getToken} from './utils/auth' import getPageTitle from './utils/get-page-title' import {whiteList} from './utils/validate' NProgress.configure({showSpinner: false}); router.beforeEach(async (to, from, next) => { NProgress.start(); document.title = getPageTitle(to.meta.title); //确定用户是否已登录 const hasToken = getToken(); if (hasToken) { /*检查登录状态*/ if (to.path === '/login') { //如果已登录,请重定向到主页 next({path: '/home'}); NProgress.done() } else { const hasRoles = store.getters.roles && store.getters.roles.length > 0; if (hasRoles) { next() } else { try { //获取用户信息 const {role} = await store.dispatch('user/getInfo'); /*加载权限路径*/ const authorityRouter = await store.dispatch('permission/generateRoutes', role); router.addRoutes(authorityRouter); next({...to, replace: true}) } catch (error) { //删除令牌并转到登录页面以重新登录 await store.dispatch('user/resetToken'); Message.error(error || 'Has Error'); next(`/login?redirect=${to.path}`); NProgress.done() } } } } else { /* has no token*/ // if (whiteList(to.path)) { //在登录白名单中,直接进入 next(); NProgress.done(); } else { //将其他无权访问的页面重定向到登录页面. next(`/public?redirect=${to.path}`); NProgress.done() } } }) router.afterEach(() => { // 完成进度栏 NProgress.done() })
这里使用到了js-cookie保存token信息,并且使用了 Vuex 状态管理。前者直接调用,后者使用如下:
store.dispatch()
调用路径位于store
是全局注册Vuex.Store
中的Module
部分(具体用法)。
这里使用到的:
store.dispatch('user/getInfo');
是获取当前用户角色信息,保存到Vuex中进行全局管理。store.dispatch('permission/generateRoutes', role)
,根据角色动态加载路由。store.dispatch('user/resetToken')
,移除状态信息。
Vuex模块注册
代码:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: {/*保存数据,全局*/ }, getters, mutations: {/*修改state*/ }, /** * Action 提交的是 mutation,而不是直接变更状态。 * Action 可以包含任意异步操作 */ actions: { }, /** * 模块 */ modules: { user, permission, } })
user
模块:
import {getInfo, logout, postFrom} from '../../api/api' import { getToken, setToken, removeToken } from '../../utils/auth' import { resetRouter } from '../../router' const state = { token: getToken(), name: '', introduction: '', roles: []/*做权限管理*/ } const mutations = { SET_TOKEN: (state, token) => { state.token = token }, SET_INTRODUCTION: (state, introduction) => { state.introduction = introduction }, SET_NAME: (state, name) => { state.name = name }, SET_AVATAR: (state, avatar) => { state.avatar = avatar }, SET_ROLES: (state, roles) => { state.roles = roles } } const actions = { // 用户登录 异步 login({commit}, userInfo) { return new Promise((resolve, reject) => { postFrom("/login", userInfo) .then(response => { const {data} = response commit('SET_TOKEN', data.token)//使用用户名 setToken(data.token) resolve(response) // this.getInfo(commit) }) .catch(errors => { reject(errors) }) }) }, // 获取用户信息 getInfo({ commit, state }) { return new Promise((resolve, reject) => { getInfo(state.token).then(response => { const { data } = response; if (!data) { reject('验证失败,请重新登录.') }else if(data.code === 0 ){ reject(data.message); this.resetToken(commit) } const { role, message} = data; // 角色必须是非空数组 if (!role || role.length <= 0) { reject(message) } commit('SET_ROLES', role); commit('SET_NAME', data.data.name); // commit('SET_AVATAR', avatar) // commit('SET_INTRODUCTION', introduction) resolve(data) }).catch(error => { reject(error) }) }) }, // 用户注销 logout({ commit, state, dispatch }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { commit('SET_TOKEN', null); commit('SET_ROLES', []); removeToken(); resetRouter(); dispatch('tagsView/delAllViews', null, { root: true }) resolve() }).catch(error => { reject(error) }) }) }, // 删除令牌 resetToken({ commit ,dispatch}) { return new Promise(resolve => { dispatch('tagsView/delAllViews', null, { root: true }) commit('SET_TOKEN', null); commit('SET_ROLES', []); removeToken(); resolve(); }) }, }; export default { namespaced: true, state, mutations, actions }
permission
模块:
import { asyncRoutes, publicRoutes } from '../../router' /** * 使用meta.role确定当前用户是否具有权限 * @param roles * @param route */ function hasPermission(roles, route) { if (route.meta && route.meta.roles) { return roles.some(role => route.meta.roles.includes(role)) } else { return true } } /** * * 动态获取路由,根据路由元信息 meta:中的roles * @param routes * @param roles * @returns {[]} */ export function filterAsyncRoutes(routes, roles) { const res = [] routes.forEach(route => { const tmp = { ...route } if (hasPermission(roles, tmp)) { if (tmp.children) { tmp.children = filterAsyncRoutes(tmp.children, roles) } res.push(tmp) } }) return res } const state = { routes: [], addRoutes: [] } const mutations = { SET_ROUTES: (state, routes) => { state.addRoutes = routes state.routes = publicRoutes.concat(routes) } } const actions = { generateRoutes({ commit }, roles) { return new Promise(resolve => { let accessedRoutes if (roles.includes('ROLE_ADMIN') || roles.includes('ROLE_TEA')||roles.includes('ROLE_STU')) { accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) } else { accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) } commit('SET_ROUTES', accessedRoutes) resolve(accessedRoutes) }) } } export default { namespaced: true, state, mutations, actions }
把生成路由信息加载到Router
上
你可能注意到router.addRoutes(authorityRouter);
这是vue router 提供动态添加路由的方法(具体使用方式)
除了拦截需要权限的路径,还必须开放公共部分的的路由。下面是实现代码。
/** * 验证 whiteList,可以在reg里面添加正则验证 * @param path * @returns {boolean} */ export function whiteList(path) { const reg = /([public]|[password]|[login]|[404])/ return reg.test(path); }
最后,
到这里基本上已经完成了动态路由和前端权限管理的实现,这里只是基本的使用。有问题欢迎在下面留言。