Sample code for implementing menu permission control in Vue

Sample code for implementing menu permission control in Vue

When people are working on a backend management system, they usually encounter issues related to menu permission control. Of course, there are only two ways to solve the problem - front-end control and back-end control. Our company's product iteration speed is relatively fast, so we iterate from front-end control routing to back-end control routing. Below I will introduce the advantages and disadvantages of these two methods and how to implement them respectively (students who are not familiar with the vue-router API can go to the official website to take a look at the API first).

Let me first briefly talk about the requirements of the project: As shown in the figure below, there are a first-level menu and a second-level menu, and different menus will be displayed when different people log in.

The idea of ​​front-end control of routing: bring all routing mapping tables to the front-end for maintenance, that is, write all menu paths and corresponding components into my router.js. Later I will mention the disadvantages of writing everything in. Then I wrote my left menu into a component (sidebar.vue), wrote a data like this in this component, and then added hidden to the fixed menu in the data through the level value obtained during login, and then the front end displayed the menu based on hidden.

// router.js pseudocode const Login = r => require.ensure([],()=>r(require('../page/login/Login.vue')),'login');
const Home = r => require.ensure([],()=>r(require('../page/Home.vue')),'home');
const Forbidden = r => require.ensure([],()=>r(require('../page/403.vue')),'forbidden');
const NotFound = r => require.ensure([],()=>r(require('../page/404.vue')),'notfound');
const Dashboard = r => require.ensure([],()=>r(require('../page/dashboard/Dashboard.vue')),'dashboard');
const SplashScreen = r => require.ensure([],()=>r(require('../page/splashScreen/SplashScreen.vue')),'splashScreen');
const AddSplashScreen = r => require.ensure([],()=>r(require('../page/splashScreen/AddSplashScreen.vue')),'addSplashScreen');

const routes = [
  {
    path: '/',
    redirect: '/login'
  },{
    path: '/login',
    component: Login
  },{
    path: '/404',
    component: NotFound
  },{
    path: '/home',
    component: Home,
    redirect: '/home/splashScreen',
    children: [
      {
        path: '/home/splashScreen',
        component: SplashScreen,
        meta: {
          title: 'National Service Li Bai}
      },{
        path: '/home/addSplashScreen',
        component: AddSplashScreen,
        meta: {
          title: National Service Lu Bu'
        }
      }
    ]
  }
];

Below is the pseudo code of the menu component

// sidebar.vue
<template>
  <div class="sidebar">
    <el-menu>
      ...
    </el-menu>
  </div>
</template>
<script>
  export default {
    data() {
      return {
        routes: [
          {
           index: '1',
           title: 'National Service Jungle',
           icon: 'iconfont icon-guanggao',
           children: [
            {
             index: 'splashScreen',
             title: 'Li Bai',
             children: []
            }, 
           ]
          },
          {  
            index: '2', 
            title: 'National Service Midfielder', 
            icon:'iconfont icon-tuisongguanli-',
          }
        ]
      }
    },
    methods: {
      getLevel(){
        const level = sessionStorage.getItem('level');
        if(level === '0'){
          this.routes.forEach(function(value){
            if(value.title == "National Service Single"){
              value.hidden = true;
              value.children.forEach(function(value){
                if(value.title=="Guan Yu"){
                  value.hidden = true;
                }
              })
            }
          })
        }else if(level === '1'){
          this.routes.forEach(function(value){
            value.hidden = true
            value.children.forEach(function(value){
              value.hidden = true;
            })
          })
        }
      }
    },
    created(){
      this.getLevel();
    }
  }
</script>

Although this can realize the permission function, there are two problems.

  1. What is stored in the session is the level. We can open the browser console to manually control the level, which makes the permission meaningless.
  2. If we remember the path, we can manually enter the path in the browser URL bar, and then press Enter to view any page. This is also the disadvantage of hard-coding all routes in the front-end router.js.

Here, the front-end only displays/hides the router through the level returned by the back-end. This makes it more complicated for the front-end to maintain the entire route and poses major risks.

Now let's talk about backend control routing. Let's start with the operation process. We have added a dashboard middle page. This page only displays the first-level routes at different levels. By clicking the corresponding first-level route, you can enter the corresponding Page page, which also only displays all the corresponding second-level routes.

There are two new concepts here called "dynamically adding routes" and "navigation guards", which means that in my front-end router.js, I only write routing tables that are accessible to everyone, such as login and 404 pages. All other component resources are written into a new components.js file, and then the menuData returned by the backend is used to map the key in components.js. If there is a corresponding key, it is dynamically added to the router through addRoutes. The method of dynamically adding routes should be written to the hook function of the navigation guard beforeEach.

Navigation guard means what I should do before routing to the next page. That is to say, after we log in, we will jump to the dashboard page. Before entering this page, we need to re-encapsulate the menuData requested by the backend, and map the data returned according to the permissions with our front-end components.js. Push the final data to our route through addRoutes, and then we can enter our dashboard page, and then enter the corresponding page page through the dashboard page. That is to say, we have completed all the permission control before entering the dashboard page.

There is also a small optimization point here: when we access a non-authorized page or a non-existent page through the browser menu bar mentioned above, we need to addRoutes 404 and * this page according to the matching priority in vue-router, so that we can directly reach the 404 page instead of an empty page.


// components.js all page resources const home = () => import('../page/Home.vue');
const splashScreen = () => import('../page/splashScreen/SplashScreen.vue');
const addSplashScreen = () => import('../page/splashScreen/AddSplashScreen.vue');
const editSplashScreen = () => import('../page/splashScreen/EditSplashScreen.vue');

export default {
  home,
  splashScreen,
  addSplashScreen,
  editSplashScreen,
  
};

// router.js See, isn't it refreshing to just write common pages? import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

const Login = () => import('../page/login/Login.vue');
const Home = () => import('../page/Home.vue');
const Forbidden = () => import('../page/403.vue');
const Dashboard = () => import('../page/dashboard/Dashboard.vue');
const routes = [
  {
    path: '/',
    redirect: '/login'
  },{
    path: '/login',
    component: Login
  },{
    path: '/403',
    component: Forbidden
  },
  {
    path: '/dashboard',
    component: Dashboard,
  },
];
export default new Router({
  mode: 'history',
  routes: routes,
  base: __dirname,
  linkActiveClass: 'link-active'
  
})

// main.js pseudo code only retains specific related logic import routeMap from './router/component.js';
const NotFound = () => import('./page/404.vue');
const formatRoutes = function (routes, routeData) {
  if (!routeData) {
    routeData = {
      name: 'home',
      path: '/home',
      // Only when the component matches successfully can you access the specific page component: routeMap['home'],
      children: [],
    };
  }
  routes.length && routes.forEach(route => {
    if(route.component) {
      route.component = routeMap[route.component];
      routeData.children.push({
        path: route.path,
        name: route.index,
        component: route.component,
        meta: {
          title: route.title,
        },
      })
    }
    if (route.children && route.children.length) {
      formatRoutes(route.children, routeData);
    }
  });
  return routeData;
};

let isFetchRemote = true;

//Use the hook function to redirect the route router.beforeEach((to, from, next) => {
  const username = sessionStorage.getItem('username');
  if(!username && to.path !== '/login'){
    next({path: '/login'});
  }
  else if (isFetchRemote && to.path !== '/login') {
    ajaxPost('/resourceAPI/getMenuData').then(res =>{
      if (res.status === 200 && res.data.errno === 0) {
        isFetchRemote = false;
        const menuData = res.data.result;
        localStorage.setItem('menudata', JSON.stringify(menuData));
        const routeData = formatRoutes(menuData);
        resourceApp.$router.addRoutes([routeData].concat([
          {name:'404',path:'/404',component:NotFound},
          {path:'*',redirect:'/404'}]));
        resourceApp.$router.push({
          path: to.path,
          query: to.query
        });
      }
      else {
        isFetchRemote = true;
      }
      next();
    })
    .catch(err => {
      console.log(err);
    });  
  }
  else {
    next();
  }
});


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

//menuData request data// The difference between the first-level menu and the second-level menu is that the first-level menu has the value of component. For example, the following SMS management only has the first-level menu {
  "errno": 0, 
  "errmsg": "Permissions obtained successfully", 
  "result": [
    {
      "index": "1", 
      "title": "Jungle Position", 
      "icon": "iconfont icon-guanggao", 
      "children": [
        {
          "index": "splashScreen", 
          "icon": "", 
          "title": "Nakorulu", 
          "path": "/home/splashAdverse", 
          "component": "splashAdverse", 
          "isShow": true
        }, 
        {
          "index": "addSplashScreen", 
          "icon": "", 
          "title": "Li Bai", 
          "path": "/home/addAdverse", 
          "component": "addAdverse", 
          "isShow": false
        }, 
        
      ]
    }, 
    {
      "index": "message", 
      "title": "National Service Top Order", 
      "icon": "iconfont icon-duanxinguanli", 
      "path": "/home/message", 
      "component": "message", 
      "children": [
        {
          "index": "addMessage", 
          "title": "The best Guan Yu in China", 
          "icon": "", 
          "path": "/home/addMessage", 
          "component": "addMessage", 
          "isShow": false
        }
        
      ]
    } 
  ]
}

The two components, sidebar and dashboard, only need to get the backend menudate through the session.

// dashboard pseudocode <template>
  <div class="nav_list">
    <div class="nav_list_item" v-for="item in navList" @click="goPage(item)">
      <i :class="item.icon"></i>
      <h2>{{item.title}}</h2>
    </div>
  </div>            
</template>

<script> 
  created(){
    const routeArr = JSON.parse(localStorage.getItem('menudata'));
    this.navList = routeArr;
  }, 
  methods: {
    goPage(item){
      // Only one level menu if (item.component) {
        this.$router.push(item.path);
      }else{
        // The data structure of the secondary menu only has path in children
        this.$router.push(item.children[0]['path']);
      }
    }
  }
</script>
// sidebar pseudocode <script>
  export default {
    data() {
      return {
        routes: [],
      }
    },
    methods: {
      bouncer(arr){
        return arr.filter(function(val){
         return !(!val || val === "");
        });
      }
    },
    created(){
      const menuData = JSON.parse(localStorage.getItem('menudata'));
      // Map the entire routing array corresponding to the current router's path let routes = menuData.map((item)=>{

        // Only one level of routing if (item.component && item.path == this.$route.path) {
          console.log(item)
          return item;
        }else{
          if(item.children[0]['path'] == this.$route.path){
            console.log(item)
            return item;
          }
        }
      })
      // Remove undefined, null and other empty and false values ​​in the array this.routes = this.bouncer(routes);
    }
  }
</script>

By controlling permissions in this way, if we change the level in the session in the browser console or change the path in the browser navigation bar, we will return to the navigation guard, that is, send a request to re-obtain menuData. If this value is not matched after I addRoutes, it will return to 404. Of course, changing the level will not achieve the control of modifying permissions, because we dynamically obtain the route, not the previous front-end control route.

This concludes this article about the sample code for implementing menu permission control in Vue. For more relevant Vue menu permission content, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Vue implements the background management permission system and the top bar three-level menu display function
  • Example of implementing dynamic permission routing menu using vue addRoutes

<<:  Architecture and component description of docker private library Harbor

>>:  Solution to MySQL startup successfully but not listening to the port

Recommend

How to use Linux tr command

01. Command Overview The tr command can replace, ...

How to modify the location of data files in CentOS6.7 mysql5.6.33

Problem: The partition where MySQL stores data fi...

MySQL 8.0.21 installation steps and problem solutions

Download the official website First go to the off...

Text mode in IE! Introduction to the role of DOCTYPE

After solving the form auto-fill problem discussed...

How to create LVM for XFS file system in Ubuntu

Preface lvm (Logical Volume Manager) logical volu...

How to write the introduction content of the About page of the website

All websites, whether official, e-commerce, socia...

Database query which object contains which field method statement

The database queries which object contains which ...

javascript input image upload and preview, FileReader preview image

FileReader is an important API for front-end file...

JavaScript to implement the back to top button

This article shares the specific code for JavaScr...

Complete steps for vue dynamic binding icons

0 Differences between icons and images Icons are ...

How to modify the scroll bar style in Vue

Table of contents First of all, you need to know ...

The functions and differences between disabled and readonly

1: readonly is to lock this control so that it can...

MySQL 8.0.15 installation tutorial for Windows 64-bit

First go to the official website to download and ...

Summary of Linux sftp command usage

sftp is the abbreviation of Secure File Transfer ...