vue generates dynamic routing and navigation bar according to permission

Posted by raimis100 on Wed, 15 May 2019 00:16:21 +0200

Basic ideas:
1. Create vueRouter and instantiate it with common routing
2. Create routing objects that need to be filtered according to permissions (add necessary permission judgment fields to routing objects)
3. The login is completed and the backend cooperates to return the current user's privilege set.
4. Screen out the privileged routing objects and use vueRouter's addRoutes method to generate complete routing
5. Processing refresh pages leads to re-instantiation of vueRouter and imperfection of routing objects (using router. beforeEast to navigate guards, using addRoutes() to improve routing objects)
6. Side navigation bar code

Related code
According to the order above
1,

Vue.use(Router)
// Public routing
export const publicRoutes = [
  {
    path: '/',
    name: 'login',
    component: login,
    meta: {
      title: 'Sign in'
    }
  }
]
// Routes that need to be filtered according to permissions
export const asyncRoutes = [
  ...Home,
  ...Seting,
  ...CRM,
  {
    path: '*',
    component: login,
    meta: {
      hidden: true
    },
    redirect: '/'
  }
]

const vr = new Router({
  mode: 'history',
  routes: publicRoutes
})

2
Take the setting module as an example. Role is the right field to judge yes. If a role has the corresponding value of this field, it will have the right of the current page.

import Layout from '@/views/layout/layout.vue'
const account = r => require.ensure([], () => r(require('@/views/seting/account/account.vue')), 'seting');
const logs = r => require.ensure([], () => r(require('@/views/seting/logs/logs.vue')), 'seting');
const role = r => require.ensure([], () => r(require('@/views/seting/role/role.vue')), 'seting');

const Seting = [
  {
    path: '/seting',
    component: Layout,
    redirect: '/seting/account',
    meta: {
      title: 'System setup',
      role: '123c6c6514d416472e640bc3f49297c550',
      icon: 'icon-xitong'
    },
    children: [
      {
        path: 'account',
        name: 'account',
        component: account,
        meta: {
          title: 'account management',
          role: '1325cdeb897cc7f8e951d647de9b1d8e11',
        }
      },
      {
        path: 'logs',
        name: 'logs',
        component: logs,
        meta: {
          title: 'Log management',
          role: '14bfbb0337ad3e7e2c9fc101294c3fe645',
        }
      },
      {
        path: 'role',
        name: 'role',
        component: role,
        meta: {
          title: 'Role management',
          role: '1559d1c05d15a0dce5549b8bf5a58c0cf9',
        }
      }
    ]
  }
]
export default Seting

If there are some details pages that do not need to be displayed in the navigation list, you can also add fields and remove them when generating the navigation bar.
eg:

 {
            path: 'addJoiner',
            name: 'addJoiner',
            component: addJoiner,
            meta: {
              hidden: true,  // Hidden field
              title: '***Details page',
              role: '14bfbb0337ad3e7e2c9fc101294c3fe645',
            }
          },

3
Log in to get permission sets and basic information

 // Sign in
    login () {
      this.rememberPassword()
      this.$refs.form.validate((valid) => {
        if (valid) {
          this.loading = true
          let params = {
            login_name: this.form.user,
            password: this.form.password,
            code: this.form.yanzhenma,
          }
          this.$api.post('api/user/login', params).then(res => {
            if (res.err_code === 1) {
              // Store the user's basic information in vuex
              this.$store.dispatch('setBaseInfo', res.data).then(() => {
                // Get the privileged routing table and add it to the routing
                router.addRoutes(this.$store.getters.addRouters)
                this.$router.push({ name: 'home' })
              })
            }
            this.loading = false
          })
        }
      })

Part of vuex code
If you don't understand it, point out:
vuex refresh has no solution
Seconds understand vuex
actions.js

// import api from '@/api'
const actions = {
  setBaseInfo ({
    commit
  }, data) {
    return new Promise(resolve => {
      commit('set_userInfo', data.userInfo)
      commit('set_token', data.token)
      commit('set_roles', data.menus)
      // Keep basic information locally to prevent loss after refresh
      sessionStorage.setItem('baseInfo', JSON.stringify(data))
      resolve()
    })
  }
}
export default actions

mutations.js

const setStorage = (key, value) => {
  if (typeof (value) === 'object') {
    value = JSON.stringify(value)
  }
  sessionStorage.setItem(key, value)
}
/*
* Avoid vuex being reset after refresh and make a backup in session store
 */
const mutations = {
  set_userInfo (state, payload) {
    state.userInfo = payload
    setStorage('userInfo', payload)
  },
  set_token (state, payload) {
    state.token = payload
    setStorage('token', payload)
  },
  set_roles (state, payload) {
    state.roles = payload
    setStorage('roles', payload)
  },
  set_breadcrumb (state, payload) {
    state.breadcrumb = payload
    setStorage('breadcrumb', payload)/*  */
  },
  changeCollapsed (state, payload) {
    state.isCollapsed = payload
  }
}
export default mutations

getters.js

import createdRoutes from '@/utils/createdRoutes.js'
import { asyncRoutes } from '@/router/index.js'
let getStoryage = (item) => {
  let str = sessionStorage.getItem(item)
  return JSON.parse(str)
}
const getters = {
  get_userInfo: (state) => {
    return state.userInfo ? state.userInfo : getStoryage('userInfo')
  },
  get_token: (state) => {
    return state.token ? state.token : sessionStorage.getItem('token')
  },
  get_roles: (state) => {
    return state.roles.length ? state.roles : getStoryage('roles')
  },
  addRouters: (state, getters) => {
    let routes = createdRoutes(asyncRoutes, getters.get_roles)
    return routes
  },
  get_breadcrumb: (state, getters) => {
    return state.breadcrumb.length ? state.breadcrumb : getStoryage('getStoryage')
  }
}
export default getters;

4,
createdRoutes()
That's 3 getters, the method used, without reservation.

/**
 * Decide whether the current routing object is within the login's authority
 * @param {Array} roles Jurisdiction
 * @param {Object} route Route
 */
function hasPermission (roles, route) {
  if (route.meta && route.meta.role) { // Routing requires privileges to be judged in the privilege array
    return roles.includes(route.meta.role)
  } else { // Pass directly without permission
    return true
  }
}

/**
 * The side navigation bar of the current user is dynamically generated according to the list of privileges acquired by the interface, and the route array validated by the privilege is returned.
 * @param {Array} asyncRoutes Routes to be filtered
 * @param {Array} roles Jurisdiction
 */
function createdRoutes (asyncRoutes, roles) {
  const accessedRouters = asyncRoutes.filter(route => {
    if (hasPermission(roles, route)) { // Current routes pass through privilege validation directly
      if (route.children && route.children.length) { // Recursive validation for current routing with subrouting
        route.children = createdRoutes(route.children, roles)
      }
      return true
    }
    return false
  })
  return accessedRouters
}
export default createdRoutes

5,
Dealing with problems caused by refresh
Actually, the code here is connected to 1. Note the comments.

// Global Navigation Guard
vr.beforeEach((to, from, next) => {
  // When the page is refreshed, the vue-router and vuex are reset, and the routing is lost. What is used is that the state of vuex is reset after the page is refreshed.
  if (to.name !== 'login' && !store.state.token) {
    // Avoid not landing on the page directly
    if (!sessionStorage.getItem('token')) {
      location.href = '/'
      return
    }
    let data = JSON.parse(sessionStorage.getItem('baseInfo'))
    store.dispatch('setBaseInfo', data).then(() => {
      vr.addRoutes(store.getters.addRouters)
    })
  }
  // Setting up breadcrumb navigation
  let breadcrumb = to.matched.filter(item => item.meta.title)
  if (breadcrumb.length) {
    breadcrumb = breadcrumb.map(item => item.meta.title)
    store.commit('set_breadcrumb', breadcrumb)
  }
  // Set title
  document.title = to.meta.title
  next()
})

6. The complete code of the side navigation bar.
Or traverse the permission-based routing table to eliminate some details that need to be hidden pages and so on.
Here, the code and the core function of filtering l routing should be handled according to their own business.

menu of element-ui

<template>
  <el-menu class="el-menu-vertical-demo" :collapse="isCollapsed" background-color="#545c64" :default-active='activeIndex' text-color="#fff" active-text-color="#7EA8F5">
    <section v-for="(item,index) in addRouters" :key="item.name" :class="isCollapsed ? 'collapsed':''">
      <!-- There are submenus. -->
      <el-submenu :index=" `${index+1}`" v-if="!item.meta.hidden && item.children && item.children.length">
        <template slot="title">
          <i :class="`icon iconfont ${item.meta.icon}`"></i>
          <span slot="title">{{item.meta.title}}</span>
        </template>
        <section v-for="(item2,index2) in item.children" :key="item2.name">
          <!-- Secondary menu has submenu -->
          <el-submenu :index="`${index+1}-${index2+1}`" v-if="item2.children && item2.children.length" class="sub2">
            <template slot="title">
              <span slot="title">{{item2.meta.title}}</span>
            </template>
            <!-- Three level menu -->
            <el-menu-item v-for="(item3,index3) in item2.children" v-if="!item3.meta.hidden" :index="item3.name" :key="index3" @click.native="$router.push({name:item3.name})">
              <span slot="title">{{item3.meta.title}}</span>
            </el-menu-item>
          </el-submenu>
          <!-- Level 2 menu without submenu -->
          <!-- Not hidden, details page hidden -->
          <el-menu-item :index="item2.name" v-else-if="!item2.meta.hidden" @click.native="$router.push({name:item2.name})">
            <span slot="title">{{item2.meta.title}}</span>
          </el-menu-item>
        </section>
      </el-submenu>
      <!-- No child menu -->
      <el-menu-item v-else-if="item.meta.hidden && item.children && item.children.length" :index="item.children[0].name" @click.native="$router.push({name:item.children[0].name})" class="item">
        <i :class="`iconfont ${item.children[0].meta.icon}`"></i>
        <span slot="title">{{item.children[0].meta.title}}</span>
      </el-menu-item>
    </section>
  </el-menu>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
  props: {
    isCollapsed: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    ...mapGetters(['addRouters']),
    activeIndex () { //Fire collection menu
      return this.$router.name
    }
  }
}
</script>
<style lang="scss" scoped>
section {
  /deep/ .el-submenu__title {
    .icon {
      margin-right: 10px;
    }
    i {
      color: white;
      font-size: 14px;
    }
  }
  /deep/ .el-menu-item {
    padding-left: 50px !important;
  }
  /deep/ .el-menu-item.item {
    padding-left: 19px !important;
    i {
      color: white;
      font-size: 14px;
      margin-right: 12px;
    }
  }
  /deep/ .el-submenu .el-menu-item {
    min-width: 0;
  }
  /deep/ .el-submenu.sub2 .el-submenu__title {
    padding-left: 50px !important;
    i {
      margin-right: 0px;
    }
  }
  /*   /deep/ .el-submenu.sub2 .el-menu-item {
    text-indent: 12px;
  } */
}
.collapsed {
  width: 50px;
  /deep/ .el-submenu__title {
    .el-icon-arrow-right {
      display: none;
    }
    span[slot="title"] {
      display: none;
    }
  }
}
</style>

Routing diagram

Topics: Vue JSON Session