Most of the time we do the background system, the crowd may be multifarious, most of the data displayed in the background system is the company’s related operation data, so it must strictly control the user’s rights. Whether the user has the right to access this menu, after the user visits this menu, whether the user has the right to add, delete, change and check, this is a qualified drop background system to have the function (type blackboard).

Define the JSON structure of the data returned by the permission interface

Authority module can be said to be the most important background system, it can be simple, can be complex, depending on how to define the product.

Usually background brother interface returned data body structure, he is in charge, he how to drop, we how to render. But in fact, this is very passive, in order to improve our development efficiency, we should put more energy on the page rather than put energy on racking their brains to think how to background data traversal into the structure I want, the secondary processing of data is sometimes one of the reasons we are mocked development slow ah! (slam cup

Therefore, properly communicate with the big brother of the background about the structure of the returned data body, and let the big brother of the background deal with it. Believe me, in fact, it is not so difficult to communicate.

But again, because of the special nature of the permission module, what is the structure of this piece returned? We need to provide the general dimension structure for the big brothers in the background.

My project defines the structure like this:

Here is the simplified structure, keeping the core fields. In this project, the menu has a secondary structure, the first level is the menu category, children represents the second level page, and below the second level is the route name of the page and the permission of the user under this menu. Here defined add, delete delete, edit, check four

[
    {
        name: 'Table',
        children: [
            {
                name: 'TableDemo',
                auth: {
                    add: true,
                    check: true,
                    delete: true,
                    edit: true}}]}]Copy the code

Define the routes that need to be dynamically loaded and mock interfaces

If there is a route that requires permission to access, we define a table.js file under router/ Modules, and the demo page below is only accessible if the relevant menu is returned in the background.

// table.js
const table = {
  path: 'table'.component: (a)= > import('@/layout'),
  redirect: '/table/demo'.name: 'Table'.meta: {
    title: 'parentTitle'.icon: 'table'
  },
  children: [{path: '/table/demo'.name: 'TableDemo'.component: resolve= > void require(['@/views/table/demo'], resolve),
      meta: {
        title: 'tableDemo'}}, {path: '/table/demoTest'.name: 'DemoTest'.component: resolve= > void require(['@/views/table/demoTest'], resolve),
      meta: {
        title: 'demoTest'}}}]export default table

Copy the code

Mock interface data, here we only show the user the first submenu, the second is not shown

// mock/index.js
const permissionData = (a)= > {
  result.data = [
    {
      name: 'Table'.children: [{name: 'TableDemo'.auth: {
            add: true.check: true.delete: true.edit: true}}]}]return result
}

Mock.mock('/apiReplace/permission'.'post', permissionData)
Copy the code

The interface data has been defined in the mock. Can we start writing the logic of how to get dynamic routes

Define vuEX repository files that handle permission related logic.

To create permission.js in store/modules, we need to define the logic of routes and permissions in VUex, including initializing dynamic routes, resetting routes, etc.

// permission.js

/** The router file is the same as the router file. The router file is the same as the router file. Access without permission * asyncRoutes Route that requires access permission * notFoundRoutes 404 route * resetRouter route reset method */
import { asyncRoutes, constantRoutes, notFoundRoutes, resetRouter } from '@/router'
import API from '@/assets/http/apiUrl'
import Request from '@/assets/http'

const permission = {
  state: {
    routes: [].addRoutes: [] // Asynchronously loaded routes
  },

  mutations: {
    SET_ROUTES: (state, routes) = > {
      state.addRoutes = routes
      state.routes = constantRoutes.concat(routes)
    }
  },

  actions: {
    // Get the dynamic route
    GenerateRoutes({ commit }, isSuperAdmin) {
      resetRouter() // Initialize the route
      return new Promise((resolve, reject) = > {
        // If you are the super administrator, mount all routes and permissions
        if (isSuperAdmin) {
          // Redirect 404's matching rule must be at the end of the entire route definition, otherwise the refresh will fail.
          const accessedRoutes = [...asyncRoutes, ...notFoundRoutes]
          accessedRoutes.forEach(item= > {
            if (item.children) {
              // Assign all permissions to the super administrator
              item.children.forEach(elem= >{ elem.meta = { ... elem.meta,check: true.delete: true.add: true.edit: true
                }
              })
            }
          })
          commit('SET_ROUTES', accessedRoutes)
          resolve(accessedRoutes)
        } else {
          Request.httpRequest({
            method: 'post'.url: API.GetPermissionData,
            noLoading: true.params: {},
            success: (data) = > {
              console.log(data)
              let accessedRoutes = []
              // Match the front end route with the back end menu
              accessedRoutes = filterAsyncRoutes(asyncRoutes, data)
              // Redirect 404's matching rule must be at the end of the entire route definition, otherwise the refresh will fail.accessedRoutes.push(... notFoundRoutes) commit('SET_ROUTES', accessedRoutes)
              resolve(accessedRoutes)
            },
            error: res= > {
              reject(res)
            }
          })
        }
      })
    }
  }
}

/** * Filter asynchronous routing tables by recursion * @param routes @param menus The menu returned in the background */
export function filterAsyncRoutes(routes = [], menus = []) {
  const res = []

  routes.forEach(route= > {
    // Copy the route so that changes to TMP will not affect the route
    consttmp = { ... route }// Whether a match is found
    if (hasPermission(menus, tmp)) { // There are matches
      // Find the successful route entry
      const findMenu = menus.find((menu, index, menus) = > {
        return menu.name.includes(tmp.name)
      })

      / / empowerment
      if (findMenu.hasOwnProperty('auth')) { tmp.meta = { ... tmp.meta, ... findMenu.auth } }// If the route entry contains child routes, the child routes also need to be matched with the menu
      if (findMenu.hasOwnProperty('children') && findMenu.children.length) {
        // The steps for child routes are the same as those for parent routes
        tmp.children = filterAsyncRoutes(tmp.children, findMenu.children)
      } else {
        // Delete the unmatched child route from the route
        delete tmp.children
      }

      // The result is an asynchronous route value matching the background return menu
      res.push(tmp)
    }
  })

  return res
}

/** * Use meta. Role to determine if the current user has permission * @param menus back menu * @param route defined by the front end of the asynchronous route * /
function hasPermission(menus, route) {
  // Match
  if (route.name) { // The asynchronous route must have a name
    // The matching rule is that the name must be the same, as long as the match is true, stop the loop
    return menus.some(menu= > route.name.includes(menu.name))
  } else {
    return true}}export default permission

Copy the code

Generate dynamic routes in the project

With everything in place, it’s just a matter of where to call the method that generates the dynamic route. I prefer to make a judgment call each time I switch routes. If the current user is entering the project for the first time, I call the method of generating dynamic routes before the route jump, and then go down after the route is generated. So we can call methods that generate dynamic routes in the router.beforeEach hook function.

Create permission.js in the SRC directory to define the logic in router.beforeeach

import router from '@/router'
import store from '@/store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // Progress bar
import 'nprogress/nprogress.css'// Progress Indicates the Progress bar style
import getPageTitle from '@/assets/utils/get-page-title'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

const whiteList = ['/login'.'/register'.'/resetPsw'] // Do not redirect the whitelist

router.beforeEach(async(to, from, next) => {
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)

  // Check whether there is a token
  const token = localStorage.getItem('ADMIN_TOKEN')
  if (token) {
    if (whiteList.includes(to.path)) {
      next()
      NProgress.done()
    } else {
      // Check whether the current user refreshes the vuEX to prevent an infinite loop. If the user refreshes the VUEX, it indicates that the vuEX is normal. If the user does not refresh the VUEX, it indicates that the VUEX is refreshed
      const hasUser = store.state.user.token
      if (hasUser) {
        next()
      } else {
        try {
          // Prevent an infinite loop
          await store.commit('SET_TOKEN', token)
          // Is the super administrator
          const isSuperAdmin = store.state.user.roles.some(item= > item.id === 1)
          const accessRoutes = await store.dispatch('GenerateRoutes', isSuperAdmin)
          // Load routes asynchronously
          router.addRoutes(accessRoutes)
          router.options.routes = store.state.permission.routes
          // Set replace: true, navigation will not leave historynext({ ... to,replace: true})}catch (error) {
          // Remove the token and redirect to the login page
          await store.dispatch('ResetToken')
          Message.error(error || 'Authentication error, please log in again. ')
          next(`/login? redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    / / not token
    if(whiteList.indexOf(to.path) ! = =- 1) {
      next()
    } else {
      // next(`/login? Redirect =${to.path} ') // Otherwise all redirect to the login page
      next('/login') // Otherwise all redirects to the login page
      NProgress.done()
    }
  }
})

router.afterEach((a)= > {
  NProgress.done() / / end of Progress
})

Copy the code

Then in the entry file import, global registration:

// main.js
import '@/permission'
Copy the code

Then run the project and you’ll see that the user can only access the first submenu.

Five, according to the permission to add restrictions for the page

If you are careful, you will notice that we have defined four permissions: Add, delete, delete, edit, and check under the meta header of each page route. $route.meta allows you to add, delete, change, and check the url. Here is a chestnut, and we define a table:

<template>
  <div class="table-demo">
    <el-card class="list-content" shadow="hover">
      <template v-if="$route.meta.check">
        <el-table
          v-loading="tableLoading"
          :data="tableData"
          :cell-style="{ whiteSpace: 'nowrap'}"
          :header-row-style="{ background: '#EBEEF5'}"
          style="width: 100%"
          class="table-content"
        >
          <el-table-column
            type="index"
            label="Serial number"
            align="center"
            sortable
            width="50"
          />
          <el-table-column
            v-for="(item,index) in tableHeader"
            :key="index"
            :prop="index"
            sortable
            :label="item"
            align="center"
          />
          <el-table-column
            label="Operation"
            width="230"
            align="center"
            class-name="operation"
          >
            <template slot-scope="scope">
              <a v-if="$route.meta.edit" class="item" @click="test(scope.row)"> modify </a> <a v-if="$route.meta.delete" class="item" @click="test(scope.row)"> Delete </a> </template> </el-table-column> </el-table> </template> <div V-else class="no-data"</div> </el-card> <! --> <el-pagination v-if="$route.meta.check"
      :total="total"
      :pager-count="5"
      :page-sizes="[10, 20, 30, 50]"
      :page-size="pageSize"
      :current-page="currentPage"
      background
      layout="total, sizes, prev, pager, next, jumper"
      class="pagination"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>
Copy the code

We can base it on

$route.meta.add
$route.meta.edit
$route.meta.delete
$route.meta.check
Copy the code

To control whether the corresponding entry is displayed or not

There are many details that have not been written out in detail. I will post the project address here. If you are interested, you can have a look

  • A responsive background management system based on Vuecli3 and vuE-admin-Template transformation

Effect: