经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Vue.js » 查看文章
vue实现动态路由详细
来源:jb51  时间:2021/10/8 17:42:31  对本文有异议

主流的实现方式:

简单聊一下两种方式的优势,毕竟如果你从来没做过,说再多也看不明白,还是得看代码

前端控制

  • 不用后端帮助,路由表维护在前端
  • 逻辑相对比较简单,比较容易上手

后端控制

  • 相对更安全一点
  • 路由表维护在数据库

一、前端控制

思路:在路由配置里,通过meta属性,扩展权限相关的字段,在路由守卫里通过判断这个权限标识,实现路由的动态增加,及页面跳转;如:我们增加一个role字段来控制角色

具体方案:

1、根据登录用户的账号,返回前端用户的角色

2、前端根据用户的角色,跟路由表的meta.role进行匹配

3、讲匹配到的路由形成可访问路由

核心代码逻辑

1、在router.js文件(把静态路由和动态路由分别写在router.js)

  1. import Vue from 'vue'
  2. import Router from 'vue-router'
  3.  
  4. Vue.use(Router)
  5.  
  6. import Layout from '@/layout'
  7.  
  8. // constantRoutes 静态路由,主要是登录页、404页等不需要动态的路由
  9. export const constantRoutes = [
  10. {
  11. path: '/redirect',
  12. component: Layout,
  13. hidden: true,
  14. children: [
  15. {
  16. path: '/redirect/:path*',
  17. component: () => import('@/views/redirect/index')
  18. }
  19. ]
  20. },
  21. {
  22. path: '/login',
  23. component: () => import('@/views/login/index'),
  24. hidden: true
  25. },
  26. {
  27. path: '/404',
  28. component: () => import('@/views/error-page/404'),
  29. hidden: true
  30. },
  31. {
  32. path: '/401',
  33. component: () => import('@/views/error-page/401'),
  34. hidden: true
  35. }
  36. ]
  37.  
  38. // asyncRoutes 动态路由
  39. export const asyncRoutes = [
  40. {
  41. path: '/permission',
  42. component: Layout,
  43. redirect: '/permission/page',
  44. alwaysShow: true,
  45. name: 'Permission',
  46. meta: {
  47. title: 'Permission',
  48. icon: 'lock',
  49. // 核心代码,可以通过配的角色来进行遍历,从而是否展示
  50. // 这个意思就是admin、editor这两个角色,这个菜单是可以显示
  51. roles: ['admin', 'editor']
  52. },
  53. children: [
  54. {
  55. path: 'page',
  56. component: () => import('@/views/permission/page'),
  57. name: 'PagePermission',
  58. meta: {
  59. title: 'Page Permission',
  60. // 这个意思就是只有admin能展示
  61. roles: ['admin']
  62. }
  63. }
  64. ]
  65. }
  66. ]
  67.  
  68. const createRouter = () => new Router({
  69. scrollBehavior: () => ({ y: 0 }),
  70. routes: constantRoutes
  71. })
  72.  
  73. const router = createRouter()
  74.  
  75. // 这个是重置路由用的,很有用,别看这么几行代码
  76. export function resetRouter() {
  77. const newRouter = createRouter()
  78. router.matcher = newRouter.matcher
  79. }
  80.  
  81. export default router

2、store/permission.js(在vuex维护一个state,通过配角色来控制菜单显不显示)

  1. import { asyncRoutes, constantRoutes } from '@/router'
  2.  
  3. // 这个方法是用来把角色和route.meta.role来进行匹配
  4. function hasPermission(roles, route) {
  5. if (route.meta && route.meta.roles) {
  6. return roles.some(role => route.meta.roles.includes(role))
  7. } else {
  8. return true
  9. }
  10. }
  11.  
  12.  
  13. // 这个方法是通过递归来遍历路由,把有权限的路由给遍历出来
  14. export function filterAsyncRoutes(routes, roles) {
  15. const res = []
  16.  
  17. routes.forEach(route => {
  18. const tmp = { ...route }
  19. if (hasPermission(roles, tmp)) {
  20. if (tmp.children) {
  21. tmp.children = filterAsyncRoutes(tmp.children, roles)
  22. }
  23. res.push(tmp)
  24. }
  25. })
  26.  
  27. return res
  28. }
  29.  
  30. const state = {
  31. routes: [],
  32. addRoutes: []
  33. }
  34.  
  35. const mutations = {
  36. SET_ROUTES: (state, routes) => {
  37. // 这个地方维护了两个状态一个是addRouters,一个是routes
  38. state.addRoutes = routes
  39. state.routes = constantRoutes.concat(routes)
  40. }
  41. }
  42.  
  43. const actions = {
  44. generateRoutes({ commit }, roles) {
  45. return new Promise(resolve => {
  46. let accessedRoutes
  47. if (roles.includes('admin')) {
  48. accessedRoutes = asyncRoutes || []
  49. } else {
  50. // 核心代码,把路由和获取到的角色(后台获取的)传进去进行匹配
  51. accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
  52. }
  53. // 把匹配完有权限的路由给set到vuex里面
  54. commit('SET_ROUTES', accessedRoutes)
  55. resolve(accessedRoutes)
  56. })
  57. }
  58. }
  59.  
  60. export default {
  61. namespaced: true,
  62. state,
  63. mutations,
  64. actions
  65. }
  66.  

3、src/permission.js

(新建一个路由守卫函数,可以在main.js,也可以抽离出来一个文件)

这里面的代码主要是控制路由跳转之前,先查一下有哪些可访问的路由,登录以后跳转的逻辑可以在这个地方写

  1. // permission.js
  2. router.beforeEach((to, from, next) => {
  3. if (store.getters.token) { // 判断是否有token
  4. if (to.path === '/login') {
  5. next({ path: '/' });
  6. } else {
  7. // 判断当前用户是否已拉取完user_info信息
  8. if (store.getters.roles.length === 0) {
  9. store.dispatch('GetInfo').then(res => { // 拉取info
  10. const roles = res.data.role;
  11. // 把获取到的role传进去进行匹配,生成可以访问的路由
  12. store.dispatch('GenerateRoutes', { roles }).then(() => {
  13. // 动态添加可访问路由表(核心代码,没有它啥也干不了)
  14. router.addRoutes(store.getters.addRouters)
  15. // hack方法 确保addRoutes已完成
  16. next({ ...to, replace: true })
  17. })
  18. }).catch(err => {
  19. console.log(err);
  20. });
  21. } else {
  22. next() //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面
  23. }
  24. }
  25. } else {
  26. if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
  27. next();
  28. } else {
  29. next('/login'); // 否则全部重定向到登录页
  30. }
  31. }
  32. })
  33.  

4、侧边栏的可以从vuex里面取数据来进行渲染

核心代码是从router取可以用的路由对象,来进行侧边栏的渲染,不管是前端动态加载还是后端动态加载路由,这个代码都是一样的

  1. <!-- layout/components/siderbar.vue -->
  2. <el-menu
  3. :default-active="activeMenu"
  4. :collapse="isCollapse"
  5. :background-color="variables.menuBg"
  6. :text-color="variables.menuText"
  7. :unique-opened="false"
  8. :active-text-color="variables.menuActiveText"
  9. :collapse-transition="false"
  10. mode="vertical"
  11. >
  12. // 把取到的路由进行循环作为参数传给子组件
  13. <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
  14. </el-menu>
  15. // 获取有权限的路由
  16. routes() {
  17. return this.$router.options.routes
  18. }
  19.  
  20.  
  21. <!-- layout/components/siderbarItem.vue -->
  22. <template slot="title">
  23. <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
  24. </template>
  25. <sidebar-item
  26. v-for="child in item.children"
  27. :key="child.path"
  28. :is-nest="true"
  29. :item="child"
  30. :base-path="resolvePath(child.path)"
  31. class="nest-menu"
  32. />
  33.  
  34. props: {
  35. // route object
  36. item: {
  37. type: Object,
  38. required: true
  39. },
  40. isNest: {
  41. type: Boolean,
  42. default: false
  43. },
  44. basePath: {
  45. type: String,
  46. default: ''
  47. }
  48. }
  49.  

前端控制路由,逻辑相对简单,后端只需要存这个用户的角色就可以了,前端拿用户的角色进行匹配。但是如果新增角色,就会非常痛苦,每一个都要加。

二、后端控制路由

后端控制大致思路是:路由配置放在数据库表里,用户登录成功后,根据角色权限,把有权限的菜单传给前端,前端格式化成页面路由识别的结构,再渲染到页面菜单上;

  • 用户登录以后,后端根据该用户的角色,直接生成可访问的路由数据,注意这个地方是数据
  • 前端根据后端返回的路由数据,转成自己需要的路由结构

具体逻辑:

  • router.js里面只放一些静态的路由,login、404之类
  • 整理一份数据结构,存到表里
  • 从后端获取路由数据,写一个数据转换的方法,讲数据转成可访问的路由
  • 也是维护一个vuex状态,将转换好的路由存到vuex里面
  • 侧边栏也是从路由取数据进行渲染

因为前段控制和后端控制,后面的流程大部分都是一样的,所以这个地方只看看前面不一样的流程:

1、store/permission.js,在vuex里面发送请求获取数据

  1. GenerateRoutes({ commit }, data) {
  2. return new Promise((resolve, reject) => {
  3. getRoute(data).then(res => {
  4. // 将获取到的数据进行一个转换,然后存到vuex里
  5. const accessedRouters = arrayToMenu(res.data)
  6. accessedRouters.concat([{ path: '*', redirect: '/404', hidden: true }])
  7. commit('SET_ROUTERS', accessedRouters)
  8. resolve()
  9. }).catch(error => {
  10. reject(error)
  11. })
  12. })
  13. }
  14.  

2、整理一份数据结构,存到表里

  1. // 页面路由格式
  2. {
  3. path: '/form',
  4. component: Layout,
  5. children: [
  6. {
  7. path: 'index',
  8. name: 'Form',
  9. component: () => import('@/views/form/index'),
  10. meta: { title: 'Form', icon: 'form' }
  11. }
  12. ]
  13. }
  14.  
  15. // 整理后的数据格式
  16. // 一级菜单
  17. // parentId为0的就可以当做一级菜单,id最好是可以选4位数,至于为什么等你开发项目的时候就知道了
  18. {
  19. id: 1300
  20. parentId: 0
  21. title: "菜单管理"
  22. path: "/menu"
  23. hidden: false
  24. component: null
  25. hidden: false
  26. name: "menu"
  27. },
  28.  
  29. // 二级菜单
  30. // parentId不为0的,就可以拿parentId跟一级菜单的id去匹配,匹配上的就push到children里面
  31. {
  32. id: 1307
  33. parentId: 1300
  34. title: "子菜单"
  35. hidden: false
  36. path: "menuItem"
  37. component: "menu/menuItem" // 要跟本地的文件地址匹配上
  38. hidden: false
  39. name: "menuItem"
  40. }
  41.  

3、写一个转化方法,把获取到的数据转换成router结构

  1. export function arrayToMenu(array) {
  2. const nodes = []
  3. // 获取顶级节点
  4. for (let i = 0; i < array.length; i++) {
  5. const row = array[i]
  6. // 这个exists方法就是判断下有没有子级
  7. if (!exists(array, row.parentId)) {
  8. nodes.push({
  9. path: row.path, // 路由地址
  10. hidden: row.hidden, // 全部true就行,如果后端没配
  11. component: Layout, // 一般就是匹配你文件的component
  12. name: row.name, // 路由名称
  13. meta: { title: row.title, icon: row.name }, // title就是显示的名字
  14. id: row.id, // 路由的id
  15. redirect: 'noredirect'
  16. })
  17. }
  18. }
  19. const toDo = Array.from(nodes)
  20. while (toDo.length) {
  21. const node = toDo.shift()
  22. // 获取子节点
  23. for (let i = 0; i < array.length; i++) {
  24. const row = array[i]
  25. // parentId等于哪个父级的id,就push到哪个
  26. if (row.parentId === node.id) {
  27. const child = {
  28. path: row.path,
  29. name: row.name,
  30. hidden: row.hidden,
  31. // 核心代码,因为二级路由的component是需要匹配页面的
  32. component: require('@/views/' + row.component + '/index.vue'),
  33. meta: { title: row.title, icon: row.name },
  34. id: row.id
  35. }
  36. if (node.children) {
  37. node.children.push(child)
  38. } else {
  39. node.children = [child]
  40. }
  41. toDo.push(child)
  42. }
  43. }
  44. }
  45. return nodes
  46. }
  47. // 看下有没有子级
  48. function exists(rows, parentId) {
  49. for (let i = 0; i < rows.length; i++) {
  50. if (rows[i].id === parentId) return true
  51. }
  52. return false
  53. }

到此这篇关于vue实现动态路由详细的文章就介绍到这了,更多相关vue实现动态路由内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号