0


vue pc项目 router.js 配置详解 ,vue项目中路由懒加载的正确使用,vue实现动态路由一步到位

这个是我之前一个项目的router.js 文件
xmjd

import Vue from'vue'import http from'./plugins/axios'import Router from'vue-router'import{ getChnPinyin }from"@/utils/chnpinyin";import{ clearLoginInfo, handlerMenu }from'@/utils'

Vue.use(Router)// 解决ElementUI导航栏中的vue-router在3.0版本以上重复点菜单报错问题const originalPush = Router.prototype.push
Router.prototype.push=functionpush(location){returnoriginalPush.call(this, location).catch(err => err)}// 开发环境不使用懒加载, 因为懒加载页面太多的话会造成webpack热更新太慢, 所以只有生产环境使用懒加载const _import =require('./import-'+ process.env.NODE_ENV)/**
 * 全局路由
 */const globalRoutes =[{
    path:'/login',
    name:'login',
    meta:{
      title:"登录",},
    isHide:true,component:()=>import(/* webpackChunkName: 'login' */"./views/login")},{
    path:'/404',
    name:"404",
    isHide:true,component:()=>import("./views/404")}]// 主入口路由(需嵌套上左右整体布局)const mainRoutes =[{
  path:'/',
  name:'main',
  meta:{
    title:"主页",},
  redirect:{ name:'home'},component:()=>import(/* webpackChunkName: 'main' */'./views/main'),
  children:[{
      path:"/home",
      name:"home",
      meta:{
        title:"主页",
        icon:"fa-zhuye"},component:()=>import(/* webpackChunkName: 'main' */'./views/home')},{
      path:"/userinfo",
      name:"userinfo",
      meta:{
        title:"用户信息"},
      isHide:true,component:()=>import(/* webpackChunkName: 'user' */"./views/modules/user"),
      children:[{
          path:"profile",
          name:"profile",
          meta:{
            title:"基本信息"},component:()=>import(/* webpackChunkName: 'userprofile' */"./views/modules/user/profile")},{
          path:"pwmanage",
          name:"pwmanage",
          meta:{
            title:"修改密码"},component:()=>import(/* webpackChunkName: 'userpwmanage' */"./views/modules/user/pwmanage")},]},{
      path:'/wsview',
      name:'wsview',
      meta:{
        title:"查看文书",
        icon:"fa-zhuye"},
      isHide:true,component:()=>import(/* webpackChunkName: 'wsviewer' */"./views/wsviewer")},{
      path:"/yuejuan",
      name:"yuejuan",
      meta:{
        title:"阅卷",},
      isHide:true,component:()=>import(/* webpackChunkName: 'yuejuan' */"./views/yuejuan")}],beforeEnter(to, from, next){let token =''if(window.top == window.self){
      token = Vue.cookie.get('token')}elseif(window.top !== window.self){
      token = window.localStorage.getItem("token")}else{
      token = window.localStorage.getItem("token")}if(!token ||!/\S/.test(token)){clearLoginInfo()next({ name:'login'})}next()}}]/**
 * 判断当前路由类型, global: 全局路由, main: 主入口路由
 * @param {*} route 当前路由
 */functionfnCurrentRouteType(route, globalRoutes =[]){let temp =[]for(let i =0; i < globalRoutes.length; i++){if(route.name === globalRoutes[i].name/* route.path === globalRoutes[i].path */){return'global'}elseif(globalRoutes[i].children && globalRoutes[i].children.length >=1){
      temp = temp.concat(globalRoutes[i].children)}}return temp.length >=1?fnCurrentRouteType(route, temp):'main'}const router =newRouter({
  mode:'history',scrollBehavior:()=>({ y:0}),
  base: process.env.BASE_URL,
  routes: globalRoutes.concat(mainRoutes)})

router.beforeEach((to, from, next)=>{console.log(to, from, next)if(router.options.isAddDynamicMenuRoutes ||fnCurrentRouteType(to, globalRoutes)==='global'){next()}else{http({
      url:'/sys/menu/nav',
      method:'get',
      notip:true,}).then(({ data })=>{console.log(data);if(data.code =="0000"|| data.code =="0"){var dat = data;var nlist =handlerMenu(dat.menuList)console.log(nlist)//先清除路由再动态添加resetRouter();fnAddDynamicMenuRoutes(nlist,[])
        router.options.isAddDynamicMenuRoutes =true
        sessionStorage.setItem('menuList',JSON.stringify(nlist ||'[]'))
        sessionStorage.setItem('permissions',JSON.stringify(dat.permissions ||'[]'))
        sessionStorage.setItem('dictList',JSON.stringify(dat.dictList ||'[]'))
        sessionStorage.setItem('orgList',JSON.stringify(dat.orgList ||'[]'))
        sessionStorage.setItem('userList',JSON.stringify(dat.userList ||'[]'))
        router.replace(to)}else{
        sessionStorage.setItem('menuList','[]')
        sessionStorage.setItem('permissions','[]')
        sessionStorage.setItem('dictList','[]')
        sessionStorage.setItem('orgList','[]')
        sessionStorage.setItem('userList','[]')next()}}).catch((e)=>{console.error(e);console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`,'color:blue')// router.push({ name: 'login' })// router.replace({path:"/login"});// setTimeout(()=>{//   location.reload();// },200)})}})functionresetRouter(){const newRouter =newRouter({
    mode:'history',scrollBehavior:()=>({ y:0}),
    base: process.env.BASE_URL,
    routes: globalRoutes.concat(mainRoutes)});
  router.matcher = newRouter.matcher;// reset router}functiondiguiAddMenu(list, pid){for(let i =0; i < list.length; i++){let v = list[i];
    v.meta ={
      title: v.name,
      icon: v.icon,
      isDynamic:true,
      menuId: v.menuId,
      fg: v.fg,
      datasource: v.datasource,
      compareModuleId: v.compareModuleId,
      blockColorParams: v.blockColorParams ?JSON.parse(v.blockColorParams):[]}
    v.title = v.name;if(v.url){
      v.path = v.url.split("/").pop();
      v.name = v.url.split("/").pop();}else{
      v.path =getChnPinyin(v.name);
      v.name =getChnPinyin(v.name);}console.log(v.url);
    v.component=()=>import(/* webpackChunkName: 'jumpper' */"./views/jump");if(!v.datasource || v.datasource =="local"){// obj.realComponent = _import("modules/"+(v.url.indexOf("sys")>-1?v.url:v.url+"/index"));
      v.realComponent = v.url ?("modules/"+(v.url +"/index")):"modules/common/index";}if(v.list){
      v.children = v.list;diguiAddMenu(v.list);}}}functionfnAddDynamicMenuRoutes(menuList =[], routes =[]){diguiAddMenu(menuList);for(let i =0; i < menuList.length; i++){if(menuList[i].children && menuList[i].children[0]){
      menuList[i].redirect ={ name: menuList[i].children[0].name }}
    routes.push(menuList[i]);}
  mainRoutes[0].children = mainRoutes[0].children.concat(routes);console.log(router);
  router.addRoutes([
    mainRoutes[0],{ path:'*', redirect:{ name:'404'}}])console.log(mainRoutes)}exportdefault router;

路由懒加载:
使用() => import();的方式导入组件
这样可以在进入当前路由的时候加载路由
如果不使用懒加载 会在第一次进入页面后 一次性加载所有路由 所以首屏加载会慢

如果一个项目过大 大量的使用路由懒加载会导致页面的热更新过慢
推荐在开发环境下不使用懒加载 这样有利于开发的效率
在生产环境可以使用路由懒加载

通过process.env.NODE_ENV可以访问到当前是什么环境
可以在utils文件夹下创建两个文件
util.import.development.js文件里是开发环境的导入方式

// file : 传入的文件名 和view拼接成路径// 开发环境不需要使用懒加载  只要正常方式引入文件
module.exports= file =>require('@/views/'+ file).default;

util.import.production.js文件里一个是生产环境的导入方式

// file : 传入的文件名 和view拼接成路径// 生产环境使用懒加载
module.exports= file =>()=>import('@/views/'+ file);

然后在导入路由的时候

// 根据process.env.NODE_ENV的字段拼接路径const _import =require('@/util.import.'+ process.env.NODE_ENV);}
    path:'index',
    name:'index',
    meta:{
      title:'首页',},
    component:_import('system/index')// 传入文件名即可使用导入},

xmjd就是
最近在写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)exportdefaultnewRouter({
  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){// 判断本地是否存在tokennext()// 这里是待会获取异步路由的地方}else{// 未登录,跳转到登陆页面next({
        path:'/login'})}}else{if(whiteList.indexOf(to.path)!==-1){next()}else{if(sessionStorage.getItem('loginName')!==null){// 判断本地是否存在tokennext(`/?redirect=${to.path}`)}else{next(`/login?redirect=${to.path}`)}}}})
 
router.afterEach(()=>{// 在即将进入新的页面组件前,关闭掉进度条
  NProgress.done()})

这里引用了一个插件,Nprogress,就是下图的蓝色小进度条。不用也可以
在这里插入图片描述
3.在main.js里引入刚才创建的router下的index.js文件和permission.js文件,如下

import router from'@/router'import'./router/permission'newVue({
  el:'#app',
  router,
  store,
  components:{ App },
  template:'<App/>'})

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

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

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

文件夹长这样
在这里插入图片描述
动态路由的配置
接下来开始配置动态路由了!

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

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

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

// 用于处理动态菜单数据,将其转为 route 形式exportfunctionfnAddDynamicMenuRoutes(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)}// 返回路由的基本格式functiongetRoute(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:[]}// 返回 routereturn route
}

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

fnAddDynamicMenuRoutes 用于递归菜单数据。
getRoute 用于返回某个数据转换的 路由格式。
注释写的应该算是很详细了,主要讲一下思路:
  对数据进行遍历,
  定义两个数组(temp,route),temp 用于保存没有子路由的路由,route 用于保存存在子路由的路由。
  如果某个数据存在 子路由,则对子路由进行遍历,并将其返回结果作为当前数据的 children。并使用 route 保存路由。
  如果某个数据不存在子路由,则直接使用 temp 保存路由。
  最后,返回两者拼接的结果,即为转换后的数据。
route 格式一般如下:
  path:指路由路径(可以根据路径定位路由)。
  name:指路由名(可以根据路由名定位路由)。
  component:指路由所在的组件。
  children:指的是路由组件中嵌套的子路由。
  meta:用于定义路由的一些元信息。

这里有个大坑!!! 在配置component的时候,需要动态导入组件,也就是vue异步组件,查看文档如下
在这里插入图片描述
可知,有两种导入方式,import和require方式。这里简单说一下,

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

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

由于import不支持动态导入方式,笔者采用的是require的方式,并且在webpack文档中有这样一段话。
在这里插入图片描述
综上所述,就是说,组件的导入可以使用字符串拼接的方式导入,可以使用模板字符串导入(字符串中含有变量),但是使用模板字符串导入时不能够只使用变量!!必须指定一个目录,不能全用变量代替!

// 字符串拼接方式
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){// 判断本地是否存在tokennext()}else{// 未登录,跳转到登陆页面next({path:'/login'})}}else{if(whiteList.indexOf(to.path)!==-1){next()}else{if(sessionStorage.getItem('loginName')!==null){// 判断本地是否存在tokennext(`/?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。

如下:

exportdefault{
  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')}})

本文转载自: https://blog.csdn.net/m0_49515138/article/details/127904882
版权归原作者 前端不加班 所有, 如有侵权,请联系我们删除。

“vue pc项目 router.js 配置详解 ,vue项目中路由懒加载的正确使用,vue实现动态路由一步到位”的评论:

还没有评论