Vue 利用 addRoutes 动态创建路由,并在页面刷新后保留动态路由的完整 Demo

Vue 利用 addRoutes 动态创建路由,并在页面刷新后保留动态路由的完整 Demo

这个包含登录之后对于路由的处理,动态添加,以及刷新后的为了防止动态路由丢失做的操作。这只是个小 demo,因为路由这块儿牵扯到的内容还挺多的,比如权限限制,Token 验证等等操作,这些都需要根据具体项目具体处理。当然这个 Demo 我个人觉得结构还算清晰,不会影响项目中其他业务逻辑的处理,该单独封装的,都封装了。

一、部分目录结构

  src
    components
      content.vue
      home.vue
      login.vue
      menu.vue
      page-1.vue
      page-2.vue
      page-3.vue
    router
      index.js
      async.js
    store
      index.js

二、初始状态下的代码

1. 部分组件

<!-- App.vue -->
<template>
  <div id="app">
    <!-- 
      这个 router-vue 主要渲染 login 和 home,
      路由初始状态是 login,登录成功之后跳转 home
      home 和 home 中的部分路由就是 demo 中示范的,需要动态创建的路由
    -->
    <router-view />
  </div>
</template>
...
<!-- login.vue -->
<template>
  <div class="login">
    <button @click="login()">登录</button>
  </div>
</template>
<script>
  export default {
    name: "login",
    methods: {
      login() {
        // 登录操作
      }
    }
  }
</script>
...
<!-- home.vue -->
<template>
  <div class="home">
    <Menu />
    <Content />
  </div>
</template>

<script>
  import Menu from './menu'
  import Content from './content'
  export default {
    name: "home",
    components: {
      Menu, Content
    }
  }
</script>
...
<!-- menu.vue -->
<template>
  <div class="menu">
    菜单列表
  </div>
</template>
<!-- content.vue -->
<template>
  <div class="content">
    <!-- 这个 router-view 渲染的是动态创建的 home 的子路由内容 -->
    <router-view />
  </div>
</template>
...

至于 page-1page-3 属于动态路由的测试界面,随便写一些测试内容就可以了。

2. 路由

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router);

/* 
  这里的 defaultRoutes 是项目中不需要动态创建的组件,也就是任意权限都可访问的组件
  一般情况下都包含 登录组件、404 组件、首页等
  要注意至少包含登录组件,因为动态创建路由的先行条件是,用户登录后根据其权限,获取路由列表,从而动态创建
 */
export const defaultRoutes = [{
  path: '/login',
  name: 'login',
  component: () => import('@/components/login.vue')
}];

const router = new Router({
  routes: [
    ...defaultRoutes
  ]
});

export default router

三、根据登录后获取的用户路由列表动态创建路由和菜单

  • 处理菜单的前一个步骤是登录或者获取菜单的 http 请求,这里假设已经请求成功并拿到了菜单,菜单也可能不是这个格式,如果不是,最终要处理成这种格式;
  • 注意要提前约定好返回的数据格式,尽量和路由格式保持一致;
  • 当然如果因为某些因素,格式和路由默认格式不统一,那就要单独将路由处理成我们想要的格式;
  • 路由对应的组件不可能以组件的形式存在于数据库中,所以获取到的路由列表中,component 的呈现形式是一个路径,如果路径只是文件名或其他格式,在处理菜单时,统一拼接成我们需要的格式;
  • 这个 menu 格式我只写了一部分简单的必须内容,具体包含的内容需要根据项目需求确定,很大可能在 meta 中包含权限列表等数据;
  • 如果项目嵌套层级复杂,不能确定层级数,需要用递归去处理;
// 登录
...
  methods: {
    login() {
        const menu = [
          {
            path: '/',
            name: 'index',
            component: 'components/home',
            meta: {
              // 这个值,指代路由是否被配置,刚开始引入时,没有被配置,配置之后将其变为 true
              // 也不一定要将这个判断条件加在这里,可以将其声明在 state 中,或者本地,看个人项目需求
              // 当然这个值也可以不设置,直接拿为 undefined 时,代表并没有配置,所有的子路由就没有添加,这里只是一个示范
              require: false
            },
            children: [
              {
                path: '/page-1',
                name: 'page1',
                meta: {
                  title: '页面一'
                },
                component: 'components/page-1'
              }, {
                path: '/page-2',
                name: 'page2',
                meta: {
                  title: '页面二'
                },
                component: 'components/page-2'
              }, {
                path: '/page-3',
                name: 'page3',
                meta: {
                  title: '页面三'
                },
                component: 'components/page-3'
              }
            ]
          }
        ];
        // 除了要动态创建路由之外,还要根据获取到的这个权限列表动态菜单,所以存在 store 中,方便不同的组件获取
        this.$store.commit('SET_ROUTES', menu);
        // 登录成功的下一步,就是从 login 跳转到 home,由于在 store 中的 SET_ROUTES 函数中已经动态创建了路由,所以这里可以直接跳转
        this.$router.push('/');
      }
  }
...
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import {setAsyncRoutes} from "../router/async"

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    routes: []
  },
  mutations: {
    SET_ROUTES(state, routes) {
      // 动态配置路由
      setAsyncRoutes(routes);
      // 为了防止用户刷新页面导致动态创建的路由失效,将其存储在本地中
      // 这里见一个好用的 session 工具,vue-session,可直接安装,使用方式可以在 gitub 上搜索
      sessionStorage.setItem('menu', JSON.stringify(routes));
      // 将路由存储在 store 中
      state.routes = routes;
    }
  }
});
// router/async.js
import router from "./index";
// 导入默认的配置的静态路由
import { defaultRoutes } from "./index"

// 遍历后台返回的路由列表,处理成我们需要的格式,这里的处理是一种理想化状态,非理想化状态的处理,代码多多了
// 有时候后台返回的数据并不会有明确的父子级路由嵌套关系,就只是一个一维数组,我们要根据我们的匹配规则,将其处理成想要的格式
// 这里的 arr 其实就是登录成功之后拿到的那个 menu
export const getAsyncRoutes = arr => {
  return arr.map(({path, name, component, meta, children}) => {
    const route = {
      path,
      name,
      meta: {
        ...meta,
        // 标记路由已经被配置了,如果项目需要的话,要在路由拦截器里根据这个值做相应的处理
        require: true 
      },
      // 根据后台返回的 component 的路径,引入路由对应的组件
      component: () => import(`@/${component}.vue`)
    };
    if(children) {
      // 如果存在 children,使用递归,将 children 也处理成我们需要的格式,并绑定给父级路由
      route.redirect = children[0].path;
      route.children = getAsyncRoutes(children);
    }
    return route;
  });
};

export const setAsyncRoutes = menu => {
  const _menu = getAsyncRoutes(menu);
  // 将处理好的动态配置的路由通过 vue 提供的方式添加到 router 中,注意这个 _menu 的格式是和配置路由时的键 routes 一样格式的数组
  router.addRoutes(_menu);
  // 路由 options 并不会随着 addRoutes 动态响应,所以要在这里进行设置
  router.options.routes = defaultRoutes.concat(_menu);
};
<!-- menu.vue -->
<template>
  <div class="menu">
    <!--
      菜单嵌套层级复杂的情况下,需要用递归生成,我之前写过一篇递归生成菜单的
     -->
    <router-link
      v-for="item in menuList"
      :key="item.name"
      :to="item.path"
    >
      {{item.meta.title}}
    </router-link>
  </div>
</template>

<script>
  export default {
    name: "menu",
    computed: {
      menuList() {
        return this.$store.state.routes[0].children || [];
      }
    },
    created() {
      // 页面刷新后,store 中的数据会丢失不见,这个时候需要从 session 中获取
      const menu = JSON.parse(sessionStorage.getItem('menu'));
      if(menu) this.$store.commit('SET_ROUTES', menu);
    }
  }
</script>

其实这个时候路由已经可以用了,但是为了防止用户刷新之后动态创建的路由丢失,需要在 router 中做一次处理

// router/index.js
import {setAsyncRoutes} from "./async"
...
// 在 router 被导出前,添加一个路由拦截器
router.beforeEach((to, from ,next) => {
  // 当路由没被配置的时候,meta 中的 require 字段为 undefined
  if(!to.meta.require && to.path !== '/login') {
    // 从 session 中获取菜单
    const menu = JSON.parse(sessionStorage.getItem('menu'));
    // 重新配置路由
    setAsyncRoutes(menu);
    router.replace(to.path);
  } else next();
});
...

猜你喜欢

转载自blog.csdn.net/FlowGuanEr/article/details/107314253
今日推荐