The implementation of Vue dynamic routing (passing the route in the background, getting and generating the sidebar in the front end), in fact, the routing generates the routing table after configuring the front-end request interface in the background

I just finished a project recently, and then a new project requirement was added due to the customer's various difficulties:
the routing table of the current user's corresponding permissions is sent from the background, and the front-end gets the post-processing (back-end processing routing) through the adjustment interface. The
vue project realizes dynamic routing The methods can be roughly divided into two types:
1. The first is that we write the routing on the front end, and dynamically display the routing according to the user's role permissions when logging in. For
details (front-end control routing), you can see what this person wrote. , I feel very good
https://segmentfault.com/a/1190000009275424 , I watched this project for a long time before I understood a little logic,
because there are many layers of judgment in the dynamic routing of the great god, and interspersed with various vuex, the Xiaobai I'm almost confused, and it inspires me a lot. It is this article that provides me with a lot of logic
. 2. The second is that the routing table of the current user's corresponding permissions is sent from the background, and the front-end is obtained through the adjustment interface. To post-processing (back-end processing routing),
these two methods have their own advantages, and the effects can be achieved. Our company requires the second method to achieve this time. The reason is that there is a special user center in the company's project, and the logic inside is very It is complicated, it is not easy to return the permissions to the front-end users, and I am worried that
it is not safe to put the routing on the front-end (the above words are said by the back-end students of the company, in fact, I don't know, there is always nothing wrong according to the requirements), then well, hold all Try and exercise the attitude of your own ability,
let's do the second method.

Ideas:

1.后台同学返回一个json格式的路由表,我用easy mock造了一段:动态路由表,大家可参考(easymock就是个模拟后台数据接口的,你习惯用啥都可以。mock / RAP /等);
2.因为后端同学传回来的都是字符串格式的,但是前端这里需要的是一个组件对象,所以要写个方法遍历一下,将字符串转换为组件对象;
3.利用vue-router的beforeEach、addRoutes、localStorage来配合上边两步实现效果;
4.左侧菜单栏根据拿到转换好的路由列表进行展示;

操作的大体步骤:1.拦截路由->2.后台取到路由->3.保存路由到localStorage(用户登录进来只会从后台取一次,其余都从本地取,所以用户,只有退出在登录路由才会更新)

The first step of the code, the simulated background json data routing table:

Each route uses the component Layout, which is the overall page layout: the menu column on the left, the page on the right, so the first-level route under children is your own developed page, and the meta contains the name of the route. And the icon corresponding to the route; because there may be a multi-level menu, there will be a situation where children are nested under children;

"data": {
    "router": [
      {
        "path": "", // 路径
        "component": "Layout", // 组件
        "redirect": "dashboard", // 重定向
        "children": [ // 子路由菜单
          {
            "path": "dashboard",
            "component": "dashboard/index",
            name: 'dashboard'
            "meta": { // meta 标签
              "title": "首页",
              "icon": "dashboard" // 图标
              noCache: true 
            }
          }
        ]
      },
      {
        "path": "/example",
        "component": "Layout",
        "redirect": "/example/table",
        "name": "Example",
        "meta": {
          "title": "案例",
          "icon": "example"
        },
        "children": [
          {
            "path": "table",
            "name": "Table",
            "component": "table/index",
            "meta": {
              "title": "表格",
              "icon": "table"
            }
          },
          {
            "path": "tree",
            "name": "Tree",
            "component": "tree/index",
            "meta": {
              "title": "树形菜单",
              "icon": "tree"
            }
          }
        ]
      },
      {
        "path": "/form",
        "component": "Layout",
        "children": [
          {
            "path": "index",
            "name": "Form",
            "component": "form/index",
            "meta": {
              "title": "表单",
              "icon": "form"
            }
          }
        ]
      },
      {
        "path": "*",
        "redirect": "/404",
        "hidden": true
      }
    ]
}

When we have the simulation data, we can write normal code, first find the index.js file in the router folder. Our routing configuration is generally in the router folder, unless your company project has multiple layers of encapsulation, but it is enough to find the routing file.

Then we enter code into the router/index.js file and export an empty routing table:
router/index.js

//清空路由表
export const asyncRouterMap = []

Then we write the code in the src file permission.js file (the permission file is directly under the src folder, and it is at the same level as the router. Later, we only need to introduce the permission file into the main.js file)
insert image description here
we import file
insert image description here

	我们将后端传回的"component": "Layout", 转为"component": Layout组件对象处理:
因为有多级路由的出现,所以要写成遍历递归方法,确保把每个component转成对象,
因为后台传回的是字符串,所以要把加载组件的过程 封装成一个方法,用这个方法在遍历中使用;详情查看项目里的router文件夹下的 _import_development.js和_import_production.js文件。

The code of the permission.js file is as follows:

(使用到的技术点 beforeEach、addRoutes、localStorage来配合实现
	beforeEach路由拦截,进入判断,如果发现本地没有路由数据,那就利用axios后台取一次,取完以后,利用localStorage存储起来,利用addRoutes动态添加路由,
	切记切记,得在一开始就加判断要不然一步小心就进入到了他的死循环,浏览器都tm崩了。
	拿到路由了,就直接next(),
	global.antRouter是为了传递数据给左侧菜单组件进行渲染)

import axios from 'axios'
const _import = require('./router/_import_' + process.env.NODE_ENV)//获取组件的方法
import Layout from '@/views/layout/Layout' // Layout 是架构组件,不在后台返回,在文件里单独引入
var getRouter;
// 路由拦截router.beforeEach
router.beforeEach((to, from, next) => {
	if (!getRouter) {//不加这个判断,路由会陷入死循环
	  if (!getObjArr('router')) { (// getObjArr是封装的获取localStorage的方法
        // axios.get('https://www.easy-mock.com/mock/5c70ba7331b3fb6533b94241/example/restful/:id/list').then(res => {
  		axios.get('实际的后台接口').then(res => {
        getRouter = res.data.data.router // 后台拿到路由
        saveObjArr('router', getRouter)  // 存储路由到localStorage的封装方法
        routerGo(to, next) //执行路由跳转方法(导航到一个新的路由,详细解说看官网: https://www.cntofu.com/book/132/api/go.md)
      })
    } else {//从localStorage拿到了路由
      getRouter = getObjArr('router')//拿到路由
      routerGo(to, next)
    }
  } else {
    next()
  }
}

// 导航到一个新的路由方法封装
function routerGo(to, next) {
  getRouter = filterAsyncRouter(getRouter) // 过滤路由
  router.options.routes = getRouter; //必须在addroutes前,使用router.options.routes=XXXXX的方法手动添加
  router.addRoutes(getRouter) //动态添加路由
  global.antRouter = getRouter //将路由数据传递给全局变量,做侧边栏菜单渲染工作
  next({ ...to, replace: true })
}

// 存储 localStorage 数组对象的方法
function saveObjArr(name, data) { // localStorage 存储数组对象的方法
  localStorage.setItem(name, JSON.stringify(data))
}

// 获取 localStorage数组对象的方法封装
function getObjArr(name) { // localStorage 获取数组对象的方法
  return JSON.parse(window.localStorage.getItem(name));

}

//过滤路由方法封装
function filterAsyncRouter(asyncRouterMap) { //遍历后台传来的路由字符串,转换为组件对象
  const accessedRouters = asyncRouterMap.filter(route => {
    if (route.component) {
      if (route.component === 'Layout') {//Layout组件特殊处理
        route.component = Layout
      } else {
        route.component = _import(route.component)
      }
    }
    if (route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children)
    }
    return true
  })
  return accessedRouters
}

Then we add two js files in the router folder, that is,
router/_import_production.js for lazy loading

module.exports = file => () => import('@/views/' + file + '.vue')

router/_import_development.js

module.exports = file => require('@/views/' + file + '.vue').default  // vue-loader at least v13.0.0+

Dynamic routing is basically completed!

After getting the traversed route,
the permission.js file on the left menu is rendered. In the code processing, global.antRouter will be assigned a value, which is a global variable (which can be replaced by vuex), and the route will be obtained from the menu and rendered. (For the part of route interception, there should be many optimizations)
insert image description here
Possible problems:

遇到了 Cannot find module '@/xx/xxx/xxx.vue'的错误. 

The idea is this

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
我在App.vue中进行初始化路由. 完了后执行类似动作
router.addRoutes(newRouters);
只是路由在寻找这个异步component的时候提示找不到模块. 
app.js:890 Error: Cannot find module '_c/layout/Layout'
另外 这个异步加载点有点疑问. 按照我的理解, webpack 根据这个import去分离代码. 那么在build的时候 webpack肯定会扫描到这个import点. 进而为这个点去创建分离点. 当实际网络请求到这个组件的时候在根据这个分离点去后端拿组件并注入.
然后现在的思路却是build后(在此之前并不知道分离点) 动态的生成import. 这时候webpack显然是不知道这个分离点的. 因为已经build完成了. 如果连分离点都不知道 又凭	什么去取组件异步加载呢?
按照上面的理解, 做了个简单的测试. 在import的工具中 我这样定义
export const Layout = () => import('_c/layout/Layout');
	//module.exports = fileName => () => import(${fileName}.vue)
	export default (file) => {
	return Layout
}
也就是说 不管是什么component 我暂时都返回 Layout的异步加载组件. 
唯一的区别就是 Layout是我事先定义好的一个异步加载的分离点. 
而且确实这样过后 就不会在提示找不到module了.
如果这个思路是正确的 是不是意味着我得为所有的页面组件定义一个map. 然后统一定义分离点. 根据后端的返回key 动态的去mao里面把这个分离点加进去

Solution:
in main.js file

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

Then in the APP.vue file

<template>
  <div id="app">
    <div v-if="sysLoading">
      <i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i>
    </div>
    <transition v-else name="fade" mode="out-in">
      <el-row v-if="initError" type="flex" justify="center" align="middle">
        <el-alert :style="{width:'180px'}" :closable="false" type="error" show-icon :title="initError"></el-alert>
      </el-row>
      <router-view v-else></router-view>
    </transition>
  </div>
</template>

<script>
import { resetRouter } from "./router.js";
import AsyncImport from "./utils/AsyncImport.js";
export default {
  name: "app",
  data() {
    return {
      sysLoading: true,
      initError: null
    };
  },
  created() { // 挂载完成执行钩子函数
    this.axios.get("/common/public/menus").then(
      response => {
        const { data: menuList } = response;
        console.log("menuList", menuList);
        this.initRouters(menuList);
        console.log("routers after init:", menuList);
        //设置新的路由
        resetRouter().then(router => {
          console.log("then router:", router);
          router.addRoutes(menuList);
          router.options.routes = menuList;
        });
        this.sysLoading = false;
      },
      () => {
        this.initError = "初始化菜单失败";
        this.sysLoading = false;
      }
    );
  },
  methods: {
    initRouters(menuList = []) {
      menuList.forEach(item => {
        if (item.component && item.component != "") {
          item.component = AsyncImport(item.component);
        }
        if (item.children && item.children.length > 0) {
          this.initRouters(item.children);
        }
      });
    }
  },
};
</script>

AsyncImport.js

const routerViewMap = {
  Layout: () => import('_c/layout/Layout'),
  SysModule: () => import('@/views/sys/SysModule')
}
export default (key) => {
  return routerViewMap[key] || null
}

in router.js file

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

Vue.use(Router)

const createRouter = () => new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: []
})

let router = createRouter();

export function resetRouter() {
  return new Promise((resolve) => {
    const newRouter = createRouter();
    router.matcher = newRouter.matcher;
    resolve(router);
  });
}
export default router;
核心思路依然是动态的路由挂载. 
只是事先定义好了分离点. 
然后根据后端返回的component字符串去匹配这个分离点. 
分离点不存在会抛Cannot find module xxxx

Guess you like

Origin blog.csdn.net/lzfengquan/article/details/122918158