优缺点
优点:
- 不用后端帮助,路由表维护在前端
- 逻辑相对比较简单,比较容易上手
- 权限少的系统用前端鉴权更加方便
缺点:
- 线上版本每次修改权限页面,都需要重新打包项目
- 大型项目不适用
- 如果需要在页面中增加角色并且控制可以访问的页面,则不能用前端鉴权
具体思路
1、前端定义静态路由和动态路由,创建vue实例的时候vue-router挂载静态路由(登录等不需要权限的页面)
2、登录时获取用户信息,存入vuex中,存储token
3、在路由拦截器中,通过token去获取用户角色,拿角色去获取所有可访问的路由
4、调用router.addrouters(store.state.addRouters)添加可访问的路由
5、退出时清空用户信息,清空角色,清空路由
步骤一:前端定义静态路由和动态路由
router/index.js
import Vue from "vue"
import VueRouter from "vue-router"
import Layout from "@/layout"Vue.use(VueRouter)// 解决重复点击路由报错的BUG
// 下面这段代码主要解决这个问题 :Uncaught (in promise) Error: Redirected when going from "/login" to "/index" via a navigation guard.
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {return originalPush.call(this, location).catch((err) => err)
}// 定义好静态路由
export const constantRoutes = [{path: "/login",name: "login",component: () => import("../views/login"),hidden: true,},
]// 定义动态路由,以及每个页面对应的roles(写在meta中,不写代表都可以访问)
export const asyncRoutes = [{id: 1,name: "/",path: "/",component: Layout,redirect: "/index",hidden: false,children: [{name: "index",path: "/index",meta: { title: "index" },component: () => import("@/views/index"),},],},{id: 2,name: "/form",path: "/form",component: Layout,redirect: "/form/index",hidden: false,children: [{name: "/form/index",path: "/form/index",meta: { title: "form" },component: () => import("@/views/form"),},],},{id: 3,name: "/example",path: "/example",component: Layout,redirect: "/example/tree",meta: { title: "example" },hidden: false,children: [{name: "/tree",path: "/example/tree",meta: { title: "tree" },component: () => import("@/views/tree"),},{name: "/copy",path: "/example/copy",meta: { title: "copy" },component: () => import("@/views/tree/copy"),},],},{id: 4,name: "/table",path: "/table",component: Layout,redirect: "/table/index",hidden: false,meta: { roles: ["admin"] },children: [{name: "/table/index",path: "/table/index",meta: { title: "table", roles: ["admin"] },component: () => import("@/views/table"),},],},{id: 5,name: "/admin",path: "/admin",component: Layout,redirect: "/admin/index",hidden: false,meta: { roles: ["admin"] },children: [{name: "/admin/index",path: "/admin/index",meta: { title: "admin", roles: ["admin"] },component: () => import("@/views/admin"),},],},{id: 6,name: "/people",path: "/people",component: Layout,redirect: "/people/index",hidden: false,meta: { roles: ["admin", "common_user"] },children: [{name: "/people/index",path: "/people/index",meta: { title: "people", roles: ["admin", "common_user"] },component: () => import("@/views/people"),},],},{id: 7,name: "/404",path: "/404",component: () => import("@/views/404"),},// 注意404页面要放到最后{ path: "*", redirect: "/404", hidden: true },
]const router = new VueRouter({mode: "history",base: process.env.BASE_URL,routes: constantRoutes,
})export default router
这里我们根据 vue-router官方推荐 的方法通过meta
标签来标示改页面能访问的权限有哪些。如meta: { role: [‘admin’,‘super_editor’] }表示该页面只有admin和超级编辑才能有资格进入。
注意事项
:这里有一个需要非常注意的地方就是 404 页面一定要最后加载,如果放在constantRoutes一同声明了404,后面的所以页面都会被拦截到404
步骤二:登录时获取用户信息,存入vuex中,存储token
login/index.vue
methods: {login () {this.$refs.userForm.validate((valid) => {if (valid) {// 模拟登录接口去请求用户数据setTimeout(() => {// 这里的res就是模拟后台返回的用户数据(不包含用户角色,一般角色是由单独的一个接口返回)const res = dynamicUserData.filter((item) => item.username === this.user.username)[0]console.log(res)// 存储用户的信息及token到vuex,并做sessionStorage持久化处理this.$store.commit('User/saveUserInfo', res)Message({ type: 'success', message: "登录成功", showClose: true, duration: 3000 })this.$router.push({ path: "/index" })}, 1000)} else return false})}}
附:vuex持久化处理
import Vue from 'vue'
import Vuex from 'vuex'
import User from './modules/user'
import permission from './modules/permission'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)export default new Vuex.Store({state: {},mutations: {},actions: {},modules: {User,permission,},plugins: [createPersistedState({storage: window.sessionStorage, // 可选sessionStorage localStoragereducer(val) {return {User: val.User,}},}),],
})
步骤【三四】:在路由拦截器中,通过token去获取用户角色,拿角色去获取所有可访问的路由,调用router.addrouters(store.state.addRouters)添加可访问的路由
路由钩子逻辑:
是否为白名单页面是: 直接进入不是: 判断是否有token无token:跳转到login登录页有token: 判断用户是否有角色权限表有权限表:直接进入无权限表:调接口获取用户角色,并存储到vuex根据返回的角色和路由表每个页面的需要的权限对比,生成可访问的路由表使用router.addRouters()添加路由
路由导航守卫:
import router from "./index"
import NProgress from "nprogress" // progress bar
import store from "@/store"
import menu from "@/mock/menu.js"NProgress.configure({ showSpinner: false }) // NProgress Configuration// 白名单页面直接进入
const whiteList = ["/login"]router.beforeEach((to, from, next) => {NProgress.start()// 白名单页面,不管是否有token,是否登录都直接进入if (whiteList.indexOf(to.path) !== -1) {next()return false}// 有token(代表了有用户信息,但是不确定有没有角色权限数组)if (store.state.User.token) {// 判断当前用户是否有角色权限数组, 是登录状态则一定有路由,直接放行,不是登录状态则去获取路由菜单登录// 刷新时hasRoles会重置为false,重新去获取 用户的角色列表const hasRoles = store.state.permission.roles && store.state.permission.roles.length > 0if (!hasRoles) {setTimeout(async () => {const roles = menu.filter((item) => item.token === store.state.User.token)[0].roles// 将该角色权限数组存储到vuex中store.commit("permission/setRoles", roles)// 根据返回的角色信息去过滤异步路由中该角色可访问的页面const accessRoutes = await store.dispatch("permission/generateRoutes", roles)// dynamically add accessible routesrouter.addRoutes(accessRoutes)// hack方法 router.addRoutes之后的next()可能会失效,因为可能next()的时候路由并没有完全add完成 next(to)解决next({ ...to, replace: true })}, 500)} else {next() //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面}} else {next({ path: "/login" })}
})router.afterEach(() => {// finish progress barNProgress.done()
})
vuex中做的事为: 将定义好的动态路由 通过 角色权限数组(后台返回的)进行过滤,过滤出用户有的路由,然后将该过滤后的路由添加到静态路由后面去
store/permission.js
import { asyncRoutes, constantRoutes } from '@/router'
/*** Filter asynchronous routing tables by recursion* @param routes asyncRoutes* @param roles*/
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
}function hasPermission(roles, route) {console.log(roles)console.log(route)if (route.meta && route.meta.roles) {console.log(roles.some(role => route.meta.roles.includes(role)))return roles.some(role => route.meta.roles.includes(role))} else {return true}
}const state = {roles: [],routes: [],addRoutes: [],
}
const mutations = {setRoles(state, val) {state.roles = val},SET_ROUTES: (state, routes) => {state.addRoutes = routesstate.routes = constantRoutes.concat(routes)},
}
const actions = {generateRoutes({ commit }, roles) {return new Promise(resolve => {let accessedRoutesif (roles.includes('admin')) { // admin直接添加所有权限accessedRoutes = asyncRoutes || []} else {accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)}commit('SET_ROUTES', accessedRoutes)resolve(accessedRoutes)})},
}
export default {namespaced: true,state,mutations,actions,
}
步骤四:退出时清空用户信息,清空角色,清空路由
methods: {// 退出登录handleLogout() {window.localStorage.removeItem("token")// 清除用户信息this.$store.commit("User/removeUserInfo")// 清除角色权限列表this.$store.commit("permission/setRoles", [])// 清除角色权限数组this.$store.commit("permission/SET_ROUTES", [])Message({type: "success",message: "退出登录",showClose: true,duration: 3000,})this.$router.push({ path: "/login" })},}
希望能帮到你
文章参考:
花裤衩大佬:花裤衩大佬
本文代码:github 求 star