Reprint please indicate the source (juejin.cn/post/684490…)

These days, I was developing the routing permission of the background management system. Before I started to do it, I looked up a lot of information and found that the permission management of the separation of the front and back ends is basically the following two ways:

  1. The front end generates the route corresponding to the current user (using the API provided by the Vue Router)addRoutesLoad routes dynamically.
  2. The front end writes all the routes, and the back end returns the role of the current user, and allocates the routes for each role according to the routes specified in advance.

The difference between the two methods

  • First, the back-end controls the route completely, but this also means that if the front-end needs to modify or add or subtract routes, the back-end needs to agree with it greatly.

  • Second, relative to the first one, the front-end relative will be some freedom, but if the role authorization changed will need to modify together before and after the end, and if some of the (technical) user role in front-end amended their permissions can through routing to see some of the page should not be allowed to see, although can’t get data, But some pages still don’t want to be seen by the wrong people.

Next I will mainly talk about the first way to do and step on some pits.

Data format required by addRoutes

Official documents:

router.addRoutes

Function signature:

router.addRoutes(routes: Array<RouteConfig>)
Copy the code

Dynamically add more routing rules. The argument must be an array that matches the Routes option.

The front-end initializes the route

Personally, I think addRoutes can be understood as adding new routes to existing routes, so before addRoutes, we need to initialize some routing pages without permission, such as login page, home page, 404 page, etc. This process is very simple, just add static routes to the routing file. I won’t go into it here.

The next step is to design the back-end routing table and determine the data format for the front-end and back-end interactions.

Design the back-end routing table

The field name instructions
*id id
*pid The parent id
*path Routing path
name The name of the routing
*component Route component path
redirect Redirect path
hidden Whether to hide
meta logo

Fields marked with * are mandatory fields

Receive and parse routes generated by the back end

According to the routing table designed above, it can be found that the upper and lower levels are determined by PID when routing data is received from the back end. Therefore, we need to parse the routing data into the format of addRoutes input parameter in the front end.

After receiving the route generated by the back end, it is parsed into the corresponding format through the following functions:

parse_routes.js

import Router from '@/router'/** * @desc: @param {Array} Menus - (retrieved from the back end) menu routing information * @param {String} to - The route to be jumped after the successful parsing * @menus * // add parse_routes * const menus = {"id": 1, "pid": 0."path": "/receipt"."name": ""."component": "layout/Layout"."redirect": ""."hidden": "false"."meta": ""* {},"id": 2."pid": 1, "path": "index"."name": "Receipt"."component": "receipt/index"."redirect": ""."hidden": "false"."meta": "{\"title\": \" Receipt Management \", \"icon\": \"receipt\"}"* {},"id": 3."pid": 0."path": "/payment"."name": ""."component": "layout/Layout"."redirect": ""."hidden": "false"."meta": ""* {},"id": 4."pid": 3."path": "index"."name": "Payment"."component": "payment/index"."redirect": ""."hidden": "false"."meta": "{\"title\ : \" Payment management \", \"icon\": \"payment\"}"* {},"id": 5, "pid": 0."path": "/crm"."name":""."component": "layout/Layout"."redirect": ""."hidden": "false"."meta": ""* {},"id": 6, "pid": 5, "path": "index"."name": "Crm"."component": "crm/index"."redirect": ""."hidden": "false"."meta": "{\"title\ : \" Customer Management \", \" Icon \": \"people\"}"* {},"id": 7, "pid": 0."path": "/upload_product"."name":""."component": "layout/Layout"."redirect": ""."hidden": "false"."meta": ""* {},"id": 8, "pid": 7, "path": "index"."name": "productUpload"."component": "productUpload/index"."redirect": ""."hidden": "false"."meta": "{\"title\": \" Test product upload\", \"icon\": \"upload\}" }
 * ]
 * ParseRoutes(menus, '/payment/index') * /export default (menus, to = '/') => {// initial route const defRoutes = [{path:'/login',
      name: 'Login',
      component: () => import('@/views/login/index'),
      hidden: true
    },
    {
      path: '/',
      component: () => import('@/views/layout/Layout'),
      redirect: '/dashboard',
      name: 'Dashboard',
      children: [
        {
          path: 'dashboard',
          meta: { title: 'home', icon: 'home' },
          component: () => import('@/views/dashboard/index')
        }
      ]
    },
    {
      path: '/ 404',
      name: '404',
      component: () => import('@/views/404'),
      hidden: true
    },
    {
      path: The '*',
      redirect: '/ 404',
      hidden: true}] // Initialize the routing information object const menusMap = {} menus.map(v => {path, name, component, redirect, hidden, Meta} = v // Rebuild route object const item = {path, name, Component: () => import(' @/views/ '${component}`), redirect, hidden: JSON.parse(hidden) } meta.length ! Parse == 0 && (item.meta = json.parse (meta)) // Check whether it is the root nodeif (v.pid === 0) {
      menusMap[v.id] = item
    } else{! MenusMap [v.id].children && (menusMap[v.id].children = []) menusMap[v.id].children.push(item)}}) // Will generate an array tree structure of the menu Const routes = object. values(menusMap) const integralRoutes = defroutes.concat (routes) Router.options.routes = integralRoutes Router.addRoutes(routes) Router.push({ path: to }) }Copy the code

Render the sidebar menu

Once you’ve successfully parsed the data, you’ll need to render the sidebar. I’ll refer to PanJiaChen’s element-UI-admin code, which I won’t go into here.

If you’ve seen this, congratulations, you’re almost ready to load routes dynamically through addRoutes.

Let’s start with some of the pitfalls I’ve encountered with addRoutes. (OS: MMP, finally getting down to business ~)

Key and Difficult Points 1:404 after the page is redirected

After we successfully added the route dynamically, change the address bar or refresh the page, and you will see the page jump to 404.

According to our routing configuration above:

[
    {
      path: '/login',
      name: 'Login',
      component: () => import('@/views/login/index'),
      hidden: true
    },
    {
      path: '/',
      component: () => import('@/views/layout/Layout'),
      redirect: '/dashboard',
      name: 'Dashboard',
      children: [
        {
          path: 'dashboard',
          meta: { title: 'home', icon: 'home' },
          component: () => import('@/views/dashboard/index')
        }
      ]
    },
    {
      path: '/ 404',
      name: '404',
      component: () => import('@/views/404'),
      hidden: true
    },
    {
      path: The '*',
      redirect: '/ 404',
      hidden: true}]Copy the code

You’ll notice that we initialized the 404 route in here, so if the route doesn’t find a strong match, it redirects to the 404 page.

There are many ways to solve this problem, but we’re only going to talk about one.

The solution

Instead of initializing the 404 route, you can fix the problem by concatenating the route when parsing the received route data.

parse_routes.js

. Const routes = object.values (menusMap).concat(notFoundRoutes) const routes = object.values (menusMap).concat(notFoundRoutes)Copy the code

Key difficulty 2: The route to refresh the page is invalid

After the 404 problem is resolved, the page will be blank again after refreshing the page because the Router instance will be reinitialized to its initial state.

The solution

When we get back end data, we store it in vuex and browser cache (I used sessionStorage). Note that we are storing the retrieved data directly because sessionStorage can only store strings, and we need to parse certain fields (component, hidden, etc.) during conversion.

actions.js

. // Save the obtained data to sessionStorage and vuex sessionStorage.setitem ('_c_unparseRoutes', JSON.stringify(menus))
commit('GET_ROUTES', Menus) // The function ParseRoutes(menus)Copy the code

A created() or Mounted () hook in app.vue checks whether vuex is empty and sessionStorage contains stored data, and listen for page refresh.

App.vue

.created() {
  const unparseRoutes = JSON.parse(sessionStorage.getItem('_c_unparseRoutes'))
  if (this.localRoutes.length === 0 && unparseRoutes) {
    const toPath = sessionStorage.getItem('_c_lastPath') ParseRoutes(unparseRoutes, toPath)} // Listen to the page refresh window.adDeventListener ('beforeunload', () => {
    sessionStorage.setItem('_c_lastPath', this.$router.currentRoute.path)
  })
}
Copy the code

Analytic Functions (full version)

import Router from '@/router'/** * @desc: @param {Array} Menus - (retrieved from the back end) menu routing information * @param {String} to - The route to be jumped after the successful parsing * @menus * // add parse_routes * const menus = {"id": 1, "pid": 0."path": "/receipt"."name": ""."component": "layout/Layout"."redirect": ""."hidden": "false"."meta": ""* {},"id": 2."pid": 1, "path": "index"."name": "Receipt"."component": "receipt/index"."redirect": ""."hidden": "false"."meta": "{\"title\": \" Receipt Management \", \"icon\": \"receipt\"}"* {},"id": 3."pid": 0."path": "/payment"."name": ""."component": "layout/Layout"."redirect": ""."hidden": "false"."meta": ""* {},"id": 4."pid": 3."path": "index"."name": "Payment"."component": "payment/index"."redirect": ""."hidden": "false"."meta": "{\"title\ : \" Payment management \", \"icon\": \"payment\"}"* {},"id": 5, "pid": 0."path": "/crm"."name":""."component": "layout/Layout"."redirect": ""."hidden": "false"."meta": ""* {},"id": 6, "pid": 5, "path": "index"."name": "Crm"."component": "crm/index"."redirect": ""."hidden": "false"."meta": "{\"title\ : \" Customer Management \", \" Icon \": \"people\"}"* {},"id": 7, "pid": 0."path": "/upload_product"."name":""."component": "layout/Layout"."redirect": ""."hidden": "false"."meta": ""* {},"id": 8, "pid": 7, "path": "index"."name": "productUpload"."component": "productUpload/index"."redirect": ""."hidden": "false"."meta": "{\"title\": \" Test product upload\", \"icon\": \"upload\}" }
 * ]
 * ParseRoutes(menus, '/payment/index') * /export default (menus, to = '/') => {// initial route const defRoutes = [{path:'/login',
      name: 'Login',
      component: () => import('@/views/login/index'),
      hidden: true
    },
    {
      path: '/',
      component: () => import('@/views/layout/Layout'),
      redirect: '/dashboard',
      name: 'Dashboard',
      children: [
        {
          path: 'dashboard',
          meta: { title: 'home', icon: 'home' },
          component: () => import('@/views/dashboard/index')}]}] // 404 route const notFoundRoutes = [{path:'/ 404', name: '404', component: () => import('@/views/404'), hidden: true },
    { path: The '*', redirect: '/ 404', hidden: true}] // Initialize the routing information object const menusMap = {} menus.map(v => {path, name, component, redirect, hidden, Meta} = v // Rebuild route object const item = {path, name, Component: () => import(' @/views/ '${component}`), redirect, hidden: JSON.parse(hidden) } meta.length ! Parse == 0 && (item.meta = json.parse (meta)) // Check whether it is the root nodeif (v.pid === 0) {
      menusMap[v.id] = item
    } else{! menusMap[v.pid].children && (menusMap[v.pid].children = []) menusMap[v.pid].children.push(item) } }) // Const routes = Object.values(menusMap).concat(notFoundRoutes) // Default route concatenated route (note the order) const integralRoutes = defRoutes.concat(routes) Router.options.routes = integralRoutes Router.addRoutes(routes) Router.push({ path: to }) }Copy the code

Last but not least, these are some of the pitfalls I encountered when I used addRoutes to dynamically load routes while managing write permissions.

It is the first time to write such a long article, if there is anything wrong with the content, please point it out to Haihan! If there are any better suggestions, please also point out!!

If you like the old iron, please double click to add a like ~ (just kidding)