Dynamic Routing Front End Control or Back End Control?(with code)

Keywords: Vue Database

Regarding the routing of the background management system, I want to take a moment to thoroughly organize a bit of routing to achieve dynamic routing.

First of all, this article is a usage note based on Flower Pants god's "Hand Touch, Take You Background with vue ken", which builds on his project to help a buddy who wants to achieve dynamic routing.

segmentfault.com/a/119000000...

Why is dynamic routing implemented?

In the process of developing the background management system, there will be different people to operate the system, admin (administrator), superAdmin (super tube), as well as various operators and financial personnel.To differentiate these people, we assign different roles to different people to show different menus, which must be done through dynamic routing.

Mainstream implementation:

Simply talk about the advantages of the two ways, after all, if you've never done it before, and you can't understand it any more, you still have to see the code.

front control

1. Routing table maintenance on the front end without backend help
 2. The logic is relatively simple and easy to use.
Copy Code

2. Backend control

1. A little safer
 2. Routing table maintenance in database
 Copy Code

1. Front End Control

The Trousers god's scheme is front-end control, whose core is to control the loading of routes through role s through the meta-attributes of routes.Specific implementation:

1. Return the role of the front-end user based on the account of the logged-in user

2. Front end matches meta.role of routing table according to user's role

3. Talk about matching routes to form accessible routes
 Copy Code

Specific code logic:

1. Write both static and dynamic routes in router.js

2. Maintain a state in vuex to control menu display by assigning roles

3. Create a new routing guard function, either in main.js or pull out a file

4. Sidebar can take data from vuex for rendering
 Copy Code

The core code consists of four files, basically with source code and explanations:

1. In router.js file (write static and dynamic routes in router.js, respectively)

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

Vue.use(Router)

import Layout from '@/layout'

// constantRoutes static routing, mainly login pages, 404 pages, etc. do not require dynamic routing

export const constantRoutes = [
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path*',
        component: () => import('@/views/redirect/index')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/error-page/404'),
    hidden: true
  },
  {
    path: '/401',
    component: () => import('@/views/error-page/401'),
    hidden: true
  }
] 

// asyncRoutes Dynamic Routing
export const asyncRoutes = [
  {
    path: '/permission',
    component: Layout,
    redirect: '/permission/page',
    alwaysShow: true, 
    name: 'Permission',
    meta: {
      title: 'Permission',
      icon: 'lock',
      // Core code that can be traversed through matching roles to show whether or not
      // This means admin and editor. This menu can be displayed
      roles: ['admin', 'editor']
    },
    children: [
      {
        path: 'page',
        component: () => import('@/views/permission/page'),
        name: 'PagePermission',
        meta: {
          title: 'Page Permission',
          // This means that only admin can show
          roles: ['admin']
        }
      }
     ]
    }
]

const createRouter = () => new Router({
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

// This is useful for resetting routes, not to mention lines of code
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher 
}

export default router
Copy Code

2. store/permission.js (maintains a state in vuex to control menu display by matching roles)

import { asyncRoutes, constantRoutes } from '@/router'

// This method is used to match roles to route.meta.role s
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}


// This method traverses routes recursively, giving privileged routes to traverse
export function filterAsyncRoutes(routes, roles) {
  const res = []

  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })

  return res
}

const state = {
  routes: [],
  addRoutes: []
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    // This place maintains two states: addRouters and routes.
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}

const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('admin')) {
        accessedRoutes = asyncRoutes || []
      } else {
        // Core code that matches routes and acquired roles (background acquired)
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      // Route the matched permissions to set into vuex
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}
Copy Code

3. src/permission.js (create a new routing guard function, either in main.js or pull out a file)

The main code here is to check which routes are accessible before controlling route jumps. Logic for jumps after login can be written here

// permission.js
router.beforeEach((to, from, next) => {
  if (store.getters.token) { // Determine whether there is a token
    if (to.path === '/login') {
      next({ path: '/' });
    } else {
        // Determine if the current user has pulled out user_info information
      if (store.getters.roles.length === 0) {
        store.dispatch('GetInfo').then(res => { // Pull info
          const roles = res.data.role;
          // Match the role s you get in to generate accessible routes
          store.dispatch('GenerateRoutes', { roles }).then(() => { 
            // Dynamically add accessible routing tables (core code, nothing to do without it)
            router.addRoutes(store.getters.addRouters)
            
            // The hack method ensures that addRoutes are complete
            next({ ...to, replace: true })
          })
        }).catch(err => {
          console.log(err);
        });
      } else {
        next() //When user privileges are available, all accessible routes are generated. If access is not privileged, full access will automatically go to page 404
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) { // Enter directly from the logon-free whitelist
      next();
    } else {
      next('/login'); // Otherwise redirect all to login page
    }
  }
})
Copy Code

4. Sidebar can take data from vuex for rendering

The core code is to render the sidebar by taking usable routing objects from the router, whether it is a front-end dynamic load or a back-end dynamic load route.

layout/components/siderbar/index.vue
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
mode="vertical"
>
    // Loop the route fetched as a parameter to the subcomponent
    <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
</el-menu>
// Get privileged routes
routes() {
  return this.$router.options.routes
}
Copy Code
layout/components/siderbar/siderbarItem.vue
  <template slot="title">
    <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
  </template>
  <sidebar-item
    v-for="child in item.children"
    :key="child.path"
    :is-nest="true"
    :item="child"
    :base-path="resolvePath(child.path)"
    class="nest-menu"
  />

  props: {
    // route object
    item: {
      type: Object,
      required: true
    },
    isNest: {
      type: Boolean,
      default: false
    },
    basePath: {
      type: String,
      default: ''
    }
  }
Copy Code

The front-end controls the route, and the logic is relatively simple. The back-end only needs to store the user's role. The front-end matches the user's role.But if you add a new role, it will be very painful, and each will be added.

2. Backend Control Routing

Back-end control routing is the solution for most back-end management systems. Our company also manages routing in this way.The specific ideas are as follows:

1. After a user logs in, the backend generates accessible routing data directly based on the user's role, noting that this is the data

2. Front-end converts the routing data returned by the back-end into the routing structure it needs.
Copy Code

Specific code logic:

1. There are only some static routes in router.js, such as login, 404, etc.

2. Organize a data structure and store it in a table

3. Get routing data from the backend, write a data conversion method, talk about data into accessible routes

4. It also maintains a vuex state to store the converted routes inside the vuex

5. Sidebar is also rendering data from routes
 Copy Code

Because most of the processes behind the front-end control and back-end control are the same, this place only looks at the different processes ahead:

1. store/permission.js, send a request to get data in vuex

GenerateRoutes({ commit }, data) {
  return new Promise((resolve, reject) => {
    getRoute(data).then(res => {
     // Convert the obtained data and save it in vuex
      const accessedRouters = arrayToMenu(res.data)
      accessedRouters.concat([{ path: '*', redirect: '/404', hidden: true }])
      commit('SET_ROUTERS', accessedRouters)
      resolve()
    }).catch(error => {
      reject(error)
    })
  })
}
Copy Code

2. Organize a data structure and store it in a table

We know that the data structure specified by vue's router is as follows:

{
    path: '/form',
    component: Layout,
    children: [
      {
        path: 'index',
        name: 'Form',
        component: () => import('@/views/form/index'),
        meta: { title: 'Form', icon: 'form' }
      }
    ]
}
Copy Code

Therefore, there are several parameters for the first-level menu: id, path, name, component, title. The second-level menu children is an array and the relationship between the child and the parent, so you can add a fid or parentId to match, which will be explained in detail when you write the conversion method later. The data format is probably like this:

// Level 1 Menu
// A parentId of 0 serves as a first-level menu, with an id of 4 digits, so you'll know why when you develop your project
{
    id: 1300
    parentId: 0
    title: "Business management"
    path: "/enterprise"
    hidden: false
    component: null
    hidden: false
    name: "enterprise"
},

// Level 2 Menu
// If the parentId is not zero, you can match the parentId with the id of the first-level menu. push on the matching is inside the child ren
{
    id: 1307
    parentId: 1300
    title: "Business Information"
    hidden: false
    path: "merchantInfo"
    component: "enterprise/merchantInfo" // To match the local file address
    hidden: false
    name: "merchantInfo"
}
Copy Code

3. Write a transformation method to convert the obtained data into a router structure

The data just obtained cannot be directly converted to router for rendering. It needs a method of arrayToMenu. I have just said some ideas. Let's analyze this method together:

export function arrayToMenu(array) {
  const nodes = []
  // Get Top Node
  for (let i = 0; i < array.length; i++) {
    const row = array[i]
    // This exists method is to determine if there are children
    if (!exists(array, row.parentId)) {
      nodes.push({
        path: row.path, // Routing Address
        hidden: row.hidden, // true for all ports, if backend does not match
        component: Layout, // Typically, it's the component that matches your file
        name: row.name, // Route Name
        meta: { title: row.title, icon: row.name }, // title is the name displayed
        id: row.id, // id of the route
        redirect: 'noredirect'
      })
    }
  }
  const toDo = Array.from(nodes)
  while (toDo.length) {
    const node = toDo.shift()
    // Get Child Nodes
    for (let i = 0; i < array.length; i++) {
      const row = array[i]
      // parentId equals the id of the parent, push to which
      if (row.parentId === node.id) {
        const child = {
          path: row.path,
          name: row.name,
          hidden: row.hidden,
          // Core code because the component of the secondary route needs to match the page
          component: require('@/views/' + row.component + '/index.vue'),
          meta: { title: row.title, icon: row.name },
          id: row.id
        }
        if (node.children) {
          node.children.push(child)
        } else {
          node.children = [child]
        }
        toDo.push(child)
      }
    }
  }
  return nodes
}
// See if there are children
function exists(rows, parentId) {
  for (let i = 0; i < rows.length; i++) {
    if (rows[i].id === parentId) return true
  }
  return false
}
Copy Code

Sidebar code is the same as static code, so don't say it again

summary

Is dynamic routing well controlled at the front end or at the back end?We can only say that each has its own advantages, after all, the business scenarios are different, so you can try them first.There are many other areas of code that need to be optimized. Every God is welcome to criticize and correct them.

Personal Public Number: Little Jerry says

Also welcome to follow my Personal Public Number and share some interesting front-end knowledge.

Posted by designationlocutus on Fri, 01 May 2020 17:06:26 -0700