The previous two articles on VUE permission routing have filled the hole with a lot of theory, so it’s time for a wave.

Summary of vUE permission routing implementation

Summary of vUE permission routing implementation method two

I chose D2-admin because the structure and code of THE elder-UI project are the most comfortable for me, and it’s easy to implement RBAC permissions based on D2-admin without major intrusive changes to D2-admin.

Preview the address

Github

Relevant concepts

For those unfamiliar with RBAC, see the permission Model section in the Enterprise management System Front-end Separation Architecture Design series

  • RBAC model authority control is implemented
  • Menus and routes are managed independently and returned by the back end
  • User Storage user
  • Admin Indicates whether a user is a system administrator
  • Role Stores role information
  • RoleUser Stores the relationship between users and roles
  • menuStore menu informationThe menuwithfunctionThere can be multiple functions under one menu.The menuThe type ofpermissionThe fields identify the functional permissions required to access this menu,functionThe type ofpermissionField is an alternate name for this feature, soThe menuThe type ofpermissionThe field is its somefunctionType of a child nodepermissionvalue
  • Permission stores associations between roles and functions
  • Interface Storage interface information
  • FunctionInterface Stores the association between functions and interfaces. You can find out the interfaces that users can access by searching for roles that users belong to and the function permissions of related roles
  • routeStores front-end routing informationpermissionField Filters out the routes accessible to the user

Run processes and related apis

Using the original login logic of D2admin, the global route guard determines whether permission information has been pulled, and identifies it as obtained.

const token = util.cookies.get('token')
    if(token && token ! = ='undefined') {// Pull permission informationif(! isFetchPermissionInfo) { await fetchPermissionInfo(); isFetchPermissionInfo =true;
        next(to.path, true)}else {
        next()
      }
    } else{// This cookie redirect automatically deletes util.cookies. Set () {// This cookie redirect automatically deletes util.cookies.'redirect', to.fullpath) // Jump to the login interface when there is no login next({name:'login'})}Copy the code
// Flags whether permission information has been pulled
let isFetchPermissionInfo = false

let fetchPermissionInfo = async() = > {// Handle dynamically added routes
  const formatRoutes = function (routes) {
    routes.forEach(route= > {
      route.component = routerMapComponents[route.component]
      if (route.children) {
        formatRoutes(route.children)
      }
    })
  }
  try {
    let userPermissionInfo = await userService.getUserPermissionInfo()
    permissionMenu = userPermissionInfo.accessMenus
    permissionRouter = userPermissionInfo.accessRoutes
    permission.functions = userPermissionInfo.userPermissions
    permission.roles = userPermissionInfo.userRoles
    permission.interfaces = util.formatInterfaces(userPermissionInfo.accessInterfaces)
    permission.isAdmin = userPermissionInfo.isAdmin == 1
  } catch (ex) {
    console.log(ex)
  }
  formatRoutes(permissionRouter)
  let allMenuAside = [...menuAside, ...permissionMenu]
  let allMenuHeader = [...menuHeader, ...permissionMenu]
  // Dynamically add routes
  router.addRoutes(permissionRouter);
  // Process routing to get routing Settings for each level
  store.commit('d2admin/page/init', [...frameInRoutes, ...permissionRouter])
  // Set the top bar menu
  store.commit('d2admin/menu/headerSet', allMenuHeader)
  // Set the sidebar menu
  store.commit('d2admin/menu/fullAsideSet', allMenuAside)
  // Initialize the menu search function
  store.commit('d2admin/search/init', allMenuHeader)
  // Set permission information
  store.commit('d2admin/permission/set', permission)
  // Load the multi-page list from the last exit
  store.dispatch('d2admin/page/openedLoad')
  await Promise.resolve()
}
Copy the code

The permission information returned by the backend includes the role code set, function code set, interface information set, menu list, route list, and system administrator ID. Format is as follows

{
  "statusCode": 200."msg": ""."data": {
    "userName": "MenuManager"."userRoles": [
      "R_MENUADMIN"]."userPermissions": [
      "p_menu_view"."p_menu_edit"."p_menu_menu"]."accessMenus": [{"title": "System"."path": "/system"."icon": "cogs"."children": [{"title": "System Settings"."icon": "cogs"."children": [{"title": "Menu Management"."path": "/system/menu"."icon": "th-list"}]}, {"title": "Organizational Structure"."icon": "pie-chart"."children": [{"title": "Department Management"."icon": "html5"
              },
              {
                "title": "Job Management"."icon": "opencart"}]}],"accessRoutes": [{"name": "System"."path": "/system"."component": "layoutHeaderAside"."componentPath": "layout/header-aside/layout"."meta": {
          "title": "System Settings"."cache": true
        },
        "children": [{"name": "MenuPage"."path": "/system/menu"."component": "menu"."componentPath": "pages/sys/menu/index"."meta": {
              "title": "Menu Management"."cache": true}}, {"name": "RoutePage"."path": "/system/route"."component": "route"."componentPath": "pages/sys/route/index"."meta": {
              "title": Route Management."cache": true}}, {"name": "RolePage"."path": "/system/role"."component": "role"."componentPath": "pages/sys/role/index"."meta": {
              "title": "Role Management"."cache": true}}, {"name": "UserPage"."path": "/system/user"."component": "user"."componentPath": "pages/sys/user/index"."meta": {
              "title": "User Management"."cache": true}}, {"name": "InterfacePage"."path": "/system/interface"."component": "interface"."meta": {
              "title": "Interface Management"}}]}],"accessInterfaces": [{"path": "/menu/:id"."method": "get"
      },
      {
        "path": "/menu"."method": "get"
      },
      {
        "path": "/menu/save"."method": "post"
      },
      {
        "path": "/interface/paged"."method": "get"}]."isAdmin": 0."avatarUrl": "https://api.adorable.io/avatars/85/[email protected]"}}Copy the code

The Settings menu

Combine fixed menus (/menu/header, /menu/aside) with accessMenus returned by the back end, and store them in the corresponding VUex Store module

. let allMenuAside = [...menuAside, ...permissionMenu]let allMenuHeader = [...menuHeader, ...permissionMenu]
...
// Set the top bar menu
store.commit('d2admin/menu/headerSet', allMenuHeader)
// Set the sidebar menu
store.commit('d2admin/menu/fullAsideSet', allMenuAside)
// Initialize the menu search function
store.commit('d2admin/search/init', allMenuHeader)
Copy the code

Processing route

By default, the permission routes returned by the back end are processed using routerMapComponents

// Handle dynamically added routes
const formatRoutes = function (routes) {
    routes.forEach(route= > {
        route.component = routerMapComponents[route.component]
        if (route.children) {
        formatRoutes(route.children)
        }
    })
}
...
formatRoutes(permissionRouter)
// Dynamically add routes
router.addRoutes(permissionRouter);
// Process routing to get routing Settings for each level
store.commit('d2admin/page/init', [...frameInRoutes, ...permissionRouter])
Copy the code

Route processing methods and differences can be seen in the vUE permission route implementation method summary ii

Setting Permission Information

The role code set, function code set, interface information set, and whether the system administrator identity is stored in the vuex Store module

. permission.functions = userPermissionInfo.userPermissions permission.roles = userPermissionInfo.userRoles permission.interfaces = util.formatInterfaces(userPermissionInfo.accessInterfaces) permission.isAdmin = userPermissionInfo.isAdmin ==1.// Set permission information
store.commit('d2admin/permission/set', permission)
Copy the code

Interface permission control and loading configuration

Control by role coding, function coding, and interface permissions, as shown in the following table

export function getMenuList() {
    return request({
        url: '/menu'.method: 'get'.interfaceCheck: true.permission: ["p_menu_view"].loading: {
            type: 'loading'.options: {
                fullscreen: true.lock: true.text: 'Loading... '.spinner: 'el-icon-loading'.background: 'rgba (0, 0, 0, 0.8)'}},success: {
            type: 'message'.options: {
                message: 'Menu loaded successfully'.type: 'success'}}})}Copy the code

InterfaceCheck: true: indicates that the interface permission is used for control. If the interface information stored in vuex Store matches the interface to be requested, a request can be sent. Otherwise, the request will be intercepted.

Permission :[“p_menu_view”] Indicates that permissions are verified using the role code and function code. If the role code or function code stored in vuex Store matches the current representation code, a request can be initiated; otherwise, the request will be blocked.

The source code is located in libs/permission.js and can be modified according to your needs

The source code for loading configuration is in libs/ load.js, and can be configured according to your own needs. The same is true for success, and the source code is in libs/ load.js. Along these lines, you can configure other functions, such as request failures.

Page element permission control

Use the instruction v-permission:

 <el-button
    v-permission:function.all="['p_menu_edit']"
    type="primary"
    icon="el-icon-edit"
    size="mini"
    @click="batchEdit"
    >The batch editor</el-button>
Copy the code

The parameter can be function or role, indicating that the function code or role code is used for verification. If the parameter is blank, the function code or role code is used for verification.

The modifier all indicates that all encodings in the instruction value must be matched.

Source location in the plugin/permission/index, js, according to their actual needs.

Use the v-if+ global method:

<el-button
    v-if="canAdd"
    type="primary"
    icon="el-icon-circle-plus-outline"
    size="mini"
    @click="add"
    >add</el-button>
Copy the code
data() {
    return {
      canAdd: this.hasPermissions(["p_menu_edit"])}; },Copy the code

By default, both the role code and the function code are used for verification. Only one of them matches.

Similar methods include hasFunctions, hasRoles.

Source location in the plugin/permission/index, js, according to their actual needs.

Do not use v-if=”hasPermissions([‘p_menu_edit’])” as this will cause the method to be executed multiple times

You can also read permission information from the Vuex Store in the component for verification.

Development Suggestions

  • Page level components into the pages/directory, and in routerMapCompnonents/index. The exported in the form of the key – value in js

  • Fixed menus that do not require permission control go into menu/aside. Js and menu/header.js

  • Routes that do not require permission control are placed in router/routes.js frameIn

  • For keep-alive to take effect, add menus and routes that require permission control through the management function of the interface. Ensure that the path of the menu is the same as the path of the route and the name of the route is the same as the name of the component on the page. Routing component in routerMapCompnonents/index can through key matching to js.

  • The addition of menus and routes in the development stage can be maintained by developers themselves, and a list can be maintained, and the list can be handed over to relevant people to maintain after the launch.

If you feel trouble, don’t want to menu and routing, returned by the backend can maintain a menu at the front, and routing (component or using a string of routing, refer to the mock/permissionMenuAndRouter js), and on the menu and routing maintenance corresponding access codes, You usually use functional coding. The back end does not need to return menus and routing information, but other permission information, such as role codes, function codes, etc., is required. Through the function code list returned by the backend, menus and routes with user permissions are filtered out in the front end. The format of menus and routes after filtering is consistent with that returned by the backend. Then, the processed menus and routes are processed as those returned by the backend.

Data mocks and code generation

Data mock d2-admin-server is modified from lazy-mock. Data is actually from the backend. Compared with other tools, data persistence is supported and json files are used to store data without database installation. Simple configuration can automatically generate add, delete, modify and check interface.

The backend uses middleware to control access, such as:

 .get('/menu', PermissionCheck(), controllers.menu.getMenuList)
Copy the code

By default, PermissionCheck uses interfaces to check whether any of the apis accessible to the user matches the current API. PermissionCheck([“p_menu_edit”],[“r_menu_admin”],true), the first parameter is function code, the second parameter is role code, and the third parameter is whether to use the interface for verification.

See the lazy-mock documentation for more details

Front-end code generation is still in development…