通常我们在vue项目中都是前端配置好路由的,但在一些项目中我们可能会遇到权限控制,这样我们就涉及到动态路由的设置了。
动态路由设置一般有两种
(1)、简单的角色路由设置:比如只涉及到管理员和普通用户的权限。通常直接在前端进行简单的角色权限设置
(2)、复杂的路由权限设置:比如OA系统、多种角色的权限配置。通常需要后端返回路由列表,前端渲染使用

1、简单的角色路由设置

(1)配置项目路由权限
  1. // router.js
  2. import Vue from 'vue'
  3. import Router from 'vue-router'
  4. import Layout from '@/layout'
  5. Vue.use(Router)
  6. // 权限路由列表
  7. let asyncRoutes = [
  8. {
  9. path: '/permission',
  10. component: Layout,
  11. redirect: '/permission/page',
  12. alwaysShow: true,
  13. name: 'Permission',
  14. meta: {
  15. title: 'Permission',
  16. roles: ['admin', 'editor'] // 普通的用户角色
  17. },
  18. children: [
  19. {
  20. path: 'page',
  21. component: () => import('@/views/permission/page'),
  22. name: 'PagePermission',
  23. meta: {
  24. title: 'Page',
  25. roles: ['editor'] // editor角色的用户才能访问该页面
  26. }
  27. },
  28. {
  29. path: 'role',
  30. component: () => import('@/views/permission/role'),
  31. name: 'RolePermission',
  32. meta: {
  33. title: 'Role',
  34. roles: ['admin'] // admin角色的用户才能访问该页面
  35. }
  36. }
  37. ]
  38. },
  39. ]
  40. // 静态路由
  41. let defaultRouter = [{
  42. path: '/404',
  43. name: '404',
  44. component: () => import('@/views/404'),
  45. meta: {
  46. title: '404'
  47. }
  48. }]
  49. let router = new Router({
  50. mode: 'history',
  51. scrollBehavior: () => ({ y: 0 }),
  52. routes: defaultRouter
  53. })
  54. export default router

(2)新建一个公共的asyncRouter.js文件
  1. // asyncRouter.js
  2. //判断当前角色是否有访问权限
  3. function hasPermission(roles, route) {
  4. if (route.meta && route.meta.roles) {
  5. return roles.some(role => route.meta.roles.includes(role))
  6. } else {
  7. return true
  8. }
  9. }
  10. // 递归过滤异步路由表,筛选角色权限路由
  11. export function filterAsyncRoutes(routes, roles) {
  12. const res = [];
  13. routes.forEach(route => {
  14. const tmp = { ...route }
  15. if (hasPermission(roles, tmp)) {
  16. if (tmp.children) {
  17. tmp.children = filterAsyncRoutes(tmp.children, roles)
  18. }
  19. res.push(tmp)
  20. }
  21. })
  22. return res
  23. }

(3)创建路由守卫:创建公共的permission.js文件,设置路由守卫
  1. import router from './router'
  2. import store from './store'
  3. import NProgress from 'nprogress' // 进度条插件
  4. import 'nprogress/nprogress.css' // 进度条样式
  5. import { getToken } from '@/utils/auth'
  6. import { filterAsyncRoutes } from '@/utils/asyncRouter.js'
  7. NProgress.configure({ showSpinner: false }) // 进度条配置
  8. const whiteList = ['/login']
  9. router.beforeEach(async (to, from, next) => {
  10. // 进度条开始
  11. NProgress.start()
  12. // 获取路由 meta 中的title,并设置给页面标题
  13. document.title = to.meta.title
  14. // 获取用户登录的token
  15. const hasToken = getToken()
  16. // 判断当前用户是否登录
  17. if (hasToken) {
  18. if (to.path === '/login') {
  19. next({ path: '/' })
  20. NProgress.done()
  21. } else {
  22. // 从store中获取用户角色
  23. const hasRoles = store.getters.roles && store.getters.roles.length > 0
  24. if (hasRoles) {
  25. next()
  26. } else {
  27. try {
  28. // 获取用户角色
  29. const roles = await store.state.roles
  30. // 通过用户角色,获取到角色路由表
  31. const accessRoutes = filterAsyncRoutes(await store.state.routers,roles)
  32. // 动态添加路由到router内
  33. router.addRoutes(accessRoutes)
  34. next({ ...to, replace: true })
  35. } catch (error) {
  36. // 清除用户登录信息后,回跳到登录页去
  37. next(`/login?redirect=${to.path}`)
  38. NProgress.done()
  39. }
  40. }
  41. }
  42. } else {
  43. // 用户未登录
  44. if (whiteList.indexOf(to.path) !== -1) {
  45. // 需要跳转的路由是否是whiteList中的路由,若是,则直接条状
  46. next()
  47. } else {
  48. // 需要跳转的路由不是whiteList中的路由,直接跳转到登录页
  49. next(`/login?redirect=${to.path}`)
  50. // 结束精度条
  51. NProgress.done()
  52. }
  53. }
  54. })
  55. router.afterEach(() => {
  56. // 结束精度条
  57. NProgress.done()
  58. })

(4)在main.js中引入permission.js文件

(5)在login登录的时候将roles存储到store中

2、复杂的路由权限设置(后端动态返回路由数据)

(1)配置项目路由文件,该文件中没有路由,或者存在一部分公共路由,即没有权限的路由
  1. import Vue from 'vue'
  2. import Router from 'vue-router'
  3. import Layout from '@/layout';
  4. Vue.use(Router)
  5. // 配置项目中没有涉及权限的公共路由
  6. export const constantRoutes = [
  7. {
  8. path: '/login',
  9. component: () => import('@/views/login'),
  10. hidden: true
  11. },
  12. {
  13. path: '/404',
  14. component: () => import('@/views/404'),
  15. hidden: true
  16. },
  17. ]
  18. const createRouter = () => new Router({
  19. mode: 'history',
  20. scrollBehavior: () => ({ y: 0 }),
  21. routes: constantRoutes
  22. })
  23. const router = createRouter()
  24. export function resetRouter() {
  25. const newRouter = createRouter()
  26. router.matcher = newRouter.matcher
  27. }
  28. export default router

(2)新建一个公共的asyncRouter.js文件
  1. // 引入路由文件这种的公共路由
  2. import { constantRoutes } from '../router';
  3. // Layout 组件是项目中的主页面,切换路由时,仅切换Layout中的组件
  4. import Layout from '@/layout';
  5. export function getAsyncRoutes(routes) {
  6. const res = []
  7. // 定义路由中需要的自定名
  8. const keys = ['path', 'name', 'children', 'redirect', 'meta', 'hidden']
  9. // 遍历路由数组去重组可用的路由
  10. routes.forEach(item => {
  11. const newItem = {};
  12. if (item.component) {
  13. // 判断 item.component 是否等于 'Layout',若是则直接替换成引入的 Layout 组件
  14. if (item.component === 'Layout') {
  15. newItem.component = Layout
  16. } else {
  17. // item.component 不等于 'Layout',则说明它是组件路径地址,因此直接替换成路由引入的方法
  18. newItem.component = resolve => require([`@/views/${item.component}`],resolve)
  19. // 此处用reqiure比较好,import引入变量会有各种莫名的错误
  20. // newItem.component = (() => import(`@/views/${item.component}`));
  21. }
  22. }
  23. for (const key in item) {
  24. if (keys.includes(key)) {
  25. newItem[key] = item[key]
  26. }
  27. }
  28. // 若遍历的当前路由存在子路由,需要对子路由进行递归遍历
  29. if (newItem.children && newItem.children.length) {
  30. newItem.children = getAsyncRoutes(item.children)
  31. }
  32. res.push(newItem)
  33. })
  34. // 返回处理好且可用的路由数组
  35. return res
  36. }

(3)创建路由守卫:创建公共的permission.js文件,设置路由守卫
  1. // 进度条引入设置如上面第一种描述一样
  2. import router from './router'
  3. import store from './store'
  4. import NProgress from 'nprogress' // progress bar
  5. import 'nprogress/nprogress.css' // progress bar style
  6. import { getToken } from '@/utils/auth' // get token from cookie
  7. import { getAsyncRoutes } from '@/utils/asyncRouter'
  8. const whiteList = ['/login'];
  9. router.beforeEach( async (to, from, next) => {
  10. NProgress.start()
  11. document.title = to.meta.title;
  12. // 获取用户token,用来判断当前用户是否登录
  13. const hasToken = getToken()
  14. if (hasToken) {
  15. if (to.path === '/login') {
  16. next({ path: '/' })
  17. NProgress.done()
  18. } else {
  19. //异步获取store中的路由
  20. let route = await store.state.addRoutes;
  21. const hasRouters = route && route.length>0;
  22. //判断store中是否有路由,若有,进行下一步
  23. if ( hasRouters ) {
  24. next()
  25. } else {
  26. //store中没有路由,则需要获取获取异步路由,并进行格式化处理
  27. try {
  28. const accessRoutes = getAsyncRoutes(await store.state.addRoutes );
  29. // 动态添加格式化过的路由
  30. router.addRoutes(accessRoutes);
  31. next({ ...to, replace: true })
  32. } catch (error) {
  33. // Message.error('出错了')
  34. next(`/login?redirect=${to.path}`)
  35. NProgress.done()
  36. }
  37. }
  38. }
  39. } else {
  40. if (whiteList.indexOf(to.path) !== -1) {
  41. next()
  42. } else {
  43. next(`/login?redirect=${to.path}`)
  44. NProgress.done()
  45. }
  46. }
  47. })
  48. router.afterEach(() => {
  49. NProgress.done()
  50. })

(4)在main.js中引入permission.js文件

(5)在login登录的时候将路由信息存储到store中
  1. // 登录接口调用后,调用路由接口,后端返回相应用户的路由res.router,我们需要存储到store中,方便其他地方拿取
  2. this.$store.dispatch("addRoutes", res.router);

到这里,整个动态路由就可以走通了,但是页面跳转、路由守卫处理是异步的,会存在动态路由添加后跳转的是空白页面,这是因为路由在执行next()时,router里面的数据还不存在,此时,你可以通过window.location.reload()来刷新路由
后端返回的路由格式:

  1. routerList = [
  2. {
  3. "path": "/other",
  4. "component": "Layout",
  5. "redirect": "noRedirect",
  6. "name": "otherPage",
  7. "meta": {
  8. "title": "测试",
  9. },
  10. "children": [
  11. {
  12. "path": "a",
  13. "component": "file/a",
  14. "name": "a",
  15. "meta": { "title": "a页面", "noCache": "true" }
  16. },
  17. {
  18. "path": "b",
  19. "component": "file/b",
  20. "name": "b",
  21. "meta": { "title": "b页面", "noCache": "true" }
  22. },
  23. ]
  24. }
  25. ]

注意:vue是单页面应用程序,所以页面一刷新数据部分数据也会跟着丢失,所以我们需要将store中的数据存储到本地,才能保证路由不丢失。关于vue页面刷新保存页面状态,可以查看 vue如何在页面刷新时保留状态信息