vue一步到位的实现动态路由

作者:ds_surk 时间:2024-05-21 10:16:05 

最近在写vue项目,需要由后台传来当前用户对应权限的路由表,前端通过调接口拿到后处理(后端处理路由),就是配置vue动态路由啦。

由于错信了一些网上的文章:(,导致在这个问题上耗费了不少时间,想想,还是自己写一篇文章来记录一下那些遇到的坑吧。

接下来手把手记录一下,如何从零开始配置vue动态路由。

首先呢,先看看静态路由的配置,简单预览一下,熟悉的可以直接跳过,说这部分,是为了熟悉一下路由的配置,配置动态路由其实就是把静态路由存到数据库,在取出来放进路由表(静态是直接写在路由表里)。

静态路由的回顾

1.创建router/index.js文件,这里只有一些简单的页面

import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/view/login/Login'
import Index from '@/layout/Index'
import Welcome from '@/layout/welcome/Welcome'

Vue.use(Router)

export default new Router({
 routes: [
   {
     path: '/login',
     name: 'Login',
     component: Login
   },
   {
     path: '/',
     component: Index,
     redirect: '/welcome',
     meta: {requireAuth: true},
     children: [
       {
         path: '/welcome',
         component: Welcome,
         name: '首页',
         meta: {title: '首页', requireAuth: true}
       }
     ]
   }
 ]
})

2.创建router/permission.js文件,配置导航守卫,可以在每次路由跳转前做一些操作,待会获取动态路由操作也在这里配置

import router from '@/router/index'
import 'element-ui/lib/theme-chalk/index.css'
import '@fortawesome/fontawesome-free/css/all.min.css'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

NProgress.configure({
 easing: 'ease', // 动画方式
 speed: 500, // 递增进度条的速度
 showSpinner: false, // 是否显示加载ico
 trickleSpeed: 200, // 自动递增间隔
 minimum: 0.3 // 初始化时的最小百分比
})
// 白名单
const whiteList = ['/login'] // no redirect whitelist

// 导航守卫
router.beforeEach((to, from, next) => {
 NProgress.start()
 if (to.meta.requireAuth) {
   // 判断该路由是否需要登录权限
   if (sessionStorage.getItem('loginName') !== null) {
     // 判断本地是否存在token
     next()
     // 这里是待会获取异步路由的地方
   } else {
     // 未登录,跳转到登陆页面
     next({
       path: '/login'
     })
   }
 } else {
   if (whiteList.indexOf(to.path) !== -1) {
     next()
   } else {
     if (sessionStorage.getItem('loginName') !== null) {
       // 判断本地是否存在token
       next(`/?redirect=${to.path}`)
     } else {
       next(`/login?redirect=${to.path}`)
     }
   }
 }
})

router.afterEach(() => {
 // 在即将进入新的页面组件前,关闭掉进度条
 NProgress.done()
})

这里引用了一个插件,Nprogress,就是下图的蓝色小进度条。不用也可以

vue一步到位的实现动态路由

 3.在main.js里引入刚才创建的router下的index.js文件和permission.js文件,如下

import router from '@/router'
import './router/permission'

new Vue({
 el: '#app',
 router,
 store,
 components: { App },
 template: '<App/>'
})

4.在App入口中放入路由跳转的视图区,跳转的路由页面都显示在这里

// APP.vue文件
<div id="app">
   <router-view />
</div>

现在静态路由就配好了,可以正常的跳转了!

文件夹长这样

vue一步到位的实现动态路由

动态路由的配置

接下来开始配置动态路由了!

有坑的地方我会说明一下。

1.首先呢,router/index.js,没有什么需要改的,把你需要动态获取的路由信息删除掉即可,可以留一部分静态路由,如登录页,首页等等,若全删,routes赋为[]即可。

2.创建一个文件,我命名为getAsyncRouter.js,用来处理获取到的动态路由信息。

// 用于处理动态菜单数据,将其转为 route 形式
export function fnAddDynamicMenuRoutes (menuList = [], routes = []) {
 // 用于保存普通路由数据
 let temp = []
 // 用于保存存在子路由的路由数据
 let route = []
 // 遍历数据
 for (let i = 0; i < menuList.length; i++) {
   // 存在子路由,则递归遍历,并返回数据作为 children 保存
   if (menuList[i].children && menuList[i].children.length > 0) {
     // 获取路由的基本格式
     route = getRoute(menuList[i])
     // 递归处理子路由数据,并返回,将其作为路由的 children 保存
     route.children = fnAddDynamicMenuRoutes(menuList[i].children)
     // 保存存在子路由的路由
     routes.push(route)
   } else {
     // 保存普通路由
     temp.push(getRoute(menuList[i]))
   }
 }
 // 返回路由结果
 return routes.concat(temp)
}

// 返回路由的基本格式
function getRoute (item) {
 // 路由基本格式
 let route = {
   // 路由的路径
   path: item.url,
   // 路由名
   name: item.name,
   // 路由所在组件
   // component: (resolve) => require([`@/layout/Index`], resolve),
   component: (resolve) => require([`@/view${item.curl}`], resolve),
   meta: {
     id: item.id,
     icon: item.icon
   },
   // 路由的子路由
   children: []
 }
 // 返回 route
 return route
}

这里是两个函数,把获取到的数据处理成路由表数据,将其变化成 route 的形式。

  • fnAddDynamicMenuRoutes 用于递归菜单数据。

  • getRoute 用于返回某个数据转换的 路由格式。

注释写的应该算是很详细了,主要讲一下思路:

  • 对数据进行遍历,

  • 定义两个数组(temp,route),temp 用于保存没有子路由的路由,route 用于保存存在子路由的路由。

  • 如果某个数据存在 子路由,则对子路由进行遍历,并将其返回结果作为当前数据的 children。并使用 route 保存路由。

  • 如果某个数据不存在子路由,则直接使用 temp 保存路由。

  • 最后,返回两者拼接的结果,即为转换后的数据。

route 格式一般如下:

  • path:指路由路径(可以根据路径定位路由)。

  • name:指路由名(可以根据路由名定位路由)。

  • component:指路由所在的组件。

  • children:指的是路由组件中嵌套的子路由。

  • meta:用于定义路由的一些元信息。

这里有个大坑!!! 在配置component的时候,需要动态导入组件,也就是vue异步组件,查看文档如下

vue一步到位的实现动态路由

 可知,有两种导入方式,import和require方式。这里简单说一下,

1 import属于es6导入方式,只支持静态导入,也就是说,你不能够使用变量或表达式,只能使用字符串。

2 require属于commonJS方式,可以支持动态的导入,但笔者尝试中,可以使用``模板字符串方式,貌似也无法直接使用变量。这里尽量用字符串拼接,而不是用变量去代替拼接的字符串!

由于import不支持动态导入方式,笔者采用的是require的方式,并且在webpack文档中有这样一段话。

vue一步到位的实现动态路由

综上所述,就是说,组件的导入可以使用字符串拼接的方式导入,可以使用模板字符串导入(字符串中含有变量),但是使用模板字符串导入时不能够只使用变量!!必须指定一个目录,不能全用变量代替!

// 字符串拼接方式
component: import('@/view'+'/sys/user')
// 模板字符串方式,webpack4+版本需使用require方式,注意,`@${item.curl}`,是不行的!必须指定一个目录,不能全用变量代替
component: (resolve) => require([`@/view${item.curl}`], resolve),

这个现象其实是与webpack import()的实现高度相关的。由于webpack需要将所有import()的模块都进行单独打包,所以在工程打包阶段,webpack会进行依赖收集。

此时,webpack会找到所有import()的调用,将传入的参数处理成一个正则,如:

import('./app'+path+'/util') => /^\.\/app.*\/util$/

也就是说,import参数中的所有变量,都会被替换为【.*】,而webpack就根据这个正则,查找所有符合条件的包,将其作为package进行打包。

所以动态导入的正确姿势,应该是尽可能静态化表达包所处的路径,最小化变量控制的区域。

3.有了处理动态路由数据的函数,那就需要在合适的地方调用,以发挥作用。这时就需要用到beforeEach了,在router/permission.js文件,配置导航守卫,可以在每次路由跳转前做一些操作,这里就是获取动态路由了

import router from '@/router/index'
import 'element-ui/lib/theme-chalk/index.css'
import '@fortawesome/fontawesome-free/css/all.min.css'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import {fnAddDynamicMenuRoutes} from '@/router/getAsyncRouter'
import {getRouter} from '@/api/sys/Menu'
import store from '@/store'

NProgress.configure({
 easing: 'ease', // 动画方式
 speed: 500, // 递增进度条的速度
 showSpinner: false, // 是否显示加载ico
 trickleSpeed: 200, // 自动递增间隔
 minimum: 0.3 // 初始化时的最小百分比
})
// 白名单
const whiteList = ['/login'] // no redirect whitelist

// 导航守卫
router.beforeEach((to, from, next) => {
 NProgress.start()
 try {
   // 判断是否已经获取过动态菜单,未获取,则需要获取一次
   if (store.getters.dynamicRoutes.length === 0) {
     if (whiteList.indexOf(to.path) !== -1) {
       next()
     } else {
       getRouter().then(res => {
         if (res.code === 0) {
           let menuRouter = fnAddDynamicMenuRoutes(res.data[0].children)
           store.dispatch('app/dynamicRoutes', menuRouter).then(() => {
             router.addRoutes(store.getters.dynamicRoutes)
             next({...to, replace: true})
           })
         } else {
           console.log('获取动态路由失败!')
           next({path: '/login'})
         }
       })
     }
   } else {
     // 路由已存在或已缓存路由
     if (to.meta.requireAuth) {
       if (sessionStorage.getItem('loginName') !== null) {
         // 判断本地是否存在token
         next()
       } else {
         // 未登录,跳转到登陆页面
         next({path: '/login'})
       }
     } else {
       if (whiteList.indexOf(to.path) !== -1) {
         next()
       } else {
         if (sessionStorage.getItem('loginName') !== null) {
           // 判断本地是否存在token
           next(`/?redirect=${to.path}`)
         } else {
           next(`/login?redirect=${to.path}`)
         }
       }
     }
   }
 } catch (error) {
   console.log('出错了')
   next(`/login?redirect=${to.path}`)
 }
})

router.afterEach(() => {
 // 在即将进入新的页面组件前,关闭掉进度条
 NProgress.done()
})

注释写的比较详细了,主要说下思路:

首先确定获取动态菜单数据的时机。一般在登录成功跳转到主页面时,获取动态菜单数据。

  • 由于涉及到路由的跳转(登录页面 -> 主页面),所以可以使用 beforeEach 进行路由导航守卫操作。   

  • 但由于每次路由跳转均会触发 beforeEach ,所以为了防止频繁获取动态路由值,这里采用vuex方式存储了获取的路由信息,如果已获取路由,就直接跳转,否则就获取一次动态菜单数据并处理。

如果不采用vuex方式也可以存到sessionStorage里面,总之就是保存一下获取的路由信息,以免重复获取。

4接下来看一看,store的写法

const state = {
 dynamicRoutes: []
}

const mutations = {
 DYNAMIC_ROUTES (state, routes) {
   state.dynamicRoutes = routes
 }
}

const actions = {
 dynamicRoutes ({commit}, routes) {
   commit('DYNAMIC_ROUTES', routes)
 }
}

注意:vue是单页面应用程序,所以页面一刷新数据部分数据也会跟着丢失,所以我们需要将store中的数据存储到本地,才能保证路由不丢失。

到这里,动态路由就已经配置好了,总体来说不是很难,主要是有坑!但只要理清了思路,写起代码就会很清晰。

说一些笔者遇到的问题

1.首先是component动态导入的问题,由于webpack的版本不同,需要根据情况来采用import和require方式来导入,如果导入不正确多半会报错,找不到module等等。或者不报错,但路由跳转是空白页。如果导入正确则一定可以跳转正常,否则,请查找正确的导入方法!路由跳转正确,能显示跳转的组件,就完成了一大半!

2.路由的跳转的地方,由于我们项目采用的是左侧菜单栏,有二级菜单,所以当路由跳转正常时,还需确定路由跳转所渲染的位置是否正确!

后记

在刷新页面时遇到一个问题,动态路由表会随着vuex的刷新而消失。

处理:这里在app.vue页面刷新时利用sessionStorage存储一下store,防止刷新丢失vuex。

如下:

export default {
 name: 'App',
 created () {
   // 在页面加载时读取sessionStorage里的状态信息
   if (sessionStorage.getItem('storeData')) {
     this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(sessionStorage.getItem('storeData'))))
   }
   // 在页面刷新时将vuex里的信息保存到sessionStorage里
   window.addEventListener('beforeunload', () => {
     sessionStorage.setItem('storeData', JSON.stringify(this.$store.state))
   })
   // 兼容iphone手机
   window.addEventListener('pagehide', () => {
     sessionStorage.setItem('storeData', JSON.stringify(this.$store.state))
   })
 }
}

另外,在登录成功后也获取了一下动态路由。这样处理下来,刷新时也不会丢失路由了!

// 获取动态路由
getRouter().then(res => {
 if (res.code === 0) {
   let menuRouter = fnAddDynamicMenuRoutes(res.data[0].children)
   store.dispatch('app/dynamicRoutes', menuRouter).then(() => {
     router.addRoutes(store.getters.dynamicRoutes)
   })
   console.log(store.getters.dynamicRoutes)
 } else {
   console.log('获取动态路由失败!')
   router.push('/login')
 }
})

以上为个人经验,希望能给大家一个参考,也希望大家多多支持asp之家。 

来源:https://blog.csdn.net/hunt_er/article/details/110661577

标签:vue,动态,路由
0
投稿

猜你喜欢

  • PHP 使用redis简单示例分享

    2024-05-05 09:18:16
  • Python字典中的键映射多个值的方法(列表或者集合)

    2021-03-07 04:50:40
  • python目标检测非极大抑制NMS与Soft-NMS

    2022-05-13 21:05:08
  • SQL Server中的T-SQL的基本对象

    2024-01-17 21:10:35
  • js模拟实现Array的sort方法

    2024-04-10 11:03:22
  • 使用Jupyter notebooks上传文件夹或大量数据到服务器

    2023-02-26 11:49:16
  • 如何配置一个稳定的SQL Server数据库

    2008-12-09 14:07:00
  • 解决python3 安装完Pycurl在import pycurl时报错的问题

    2023-08-23 10:40:08
  • Python赋值逻辑的实现

    2023-09-14 21:20:27
  • SQL Server 远程更新目标表数据的存储过程

    2024-01-21 07:38:04
  • python 集合常用操作汇总

    2023-11-15 08:41:22
  • Python OrderedDict的使用案例解析

    2021-11-20 22:47:25
  • SQL 多条件查询几种实现方法详细介绍

    2024-01-16 23:12:31
  • Python实现的栈、队列、文件目录遍历操作示例

    2022-06-10 00:12:35
  • 对Python新手编程过程中如何规避一些常见问题的建议

    2021-04-01 19:27:16
  • MySQL两种表存储结构MyISAM和InnoDB的性能比较测试

    2024-01-28 02:35:55
  • Python数据模型与Python对象模型的相关总结

    2021-05-15 02:21:43
  • 如何判断js脚本加载完成

    2008-11-04 13:53:00
  • Python 函数装饰器应用教程

    2022-08-17 05:53:24
  • 使用phpMyAdmin进行mysql数据库备份和还原的方法

    2008-10-13 20:56:00
  • asp之家 网络编程 m.aspxhome.com