The preface

Recently, the project demand is not very much, I think the company’s back office management project is too bloated, help it lose weight.

How you intended, but she was ruthless.

JQ + Bootstrapt non-mainstream underwear, Template-Web hardcore coat, HTML + CSS + JS honest long underwear, CDN nameplate bag, look good at a glance, isn’t it? Follow her back home, a door silly! .

If you want to love, love deeply. If you don’t love, bye.

Obviously I chose the latter, so let’s get down to business.

Project Architecture – Permission configuration

Analysis of the

Generally a relatively simple background management system is the system login user roles are few, maintenance personnel are not many, may be admin and editor two, this kind of permission is relatively simple, in the meta tag of the route is almost enough to add a role, for example

{
    path: '/dynamic'.component: Layout,
    name: 'dynamic'.meta: {
      title: 'Dynamic management'.icon: 'icon-duihuakuang'.roles: ['admin'.'editor'] // you can set roles in root nav
    },
    children: [{path: 'dynamicList'.component: () = > import(' '),
        name: 'dynamicList'.meta: { title: 'Dynamic List'.noCache: true}}, {path: 'rootdynamic'.component: () = > import(' '),
        name: 'rootdynamic'.meta: { title: 'Robot Dynamic List'.noCache: true}}}]Copy the code

But this one is a little different, a little more complex roles of permission level, and more users. So that’s obviously not going to happen.

thinking

The login process is as follows

Obtain the token- > obtain the user information with the token and return the route from the backend -> filter the permission route from the front-end -> login success -> dynamically display the route of the user

We have no token, there is a scheduled task on the back end, the backend interface returns 403 when expired, and the front end directly redirects to the login page, so the first step does not matter, but in this article we still go through the normal process (in the project, we also need to be flexible, take an unusual way to get all the way!) .

practice

Access token

Do you store your data locally in localStorage or using Vuex? (Looking forward to your opinion)

I’ve written down here that the choice is up to the individual (don’t use vuex for the sake of using vuex, most projects really don’t have to)

  login({ commit }, userInfo) {
    const {
      username,
      password,
      verification
    } = userInfo
    return new Promise((resolve, reject) = > {
      login({
        userName: username.trim(),
        passWord: password,
        captcha: verification
      }).then(response= > {
        if (response) {
          //localStorage.setItem('info', JSON.stringify(response.data))
          // Pretend to have a token. If you have a token, write the one given by the back end
          commit('SET_TOKEN'.new Date().getTime())
          commit('SET_LEVEL', response.data.level)
          setToken(new Date().getTime())
          resolve(response)
        } else {
          this.$message({
            type: 'error'.message: response.errMsg
          })
        }
      }).catch(error= > {
        reject(error)
      })
    })
  },
Copy the code

Get the token to obtain the user information and the back-end return route

getInfo({ commit, state }) {
    return new Promise((resolve, reject) = > {
      getInfo(state.token).then(response= > {
          // Route information is stored locally
        localStorage.setItem('sidebars'.JSON.stringify(response.data))
        const data = JSON.parse(localStorage.getItem('info'))
        const {
          roleName,
          userName,
          department
        } = data
        commit('SET_ROLES', roleName)
        commit('SET_NAME', userName)
        commit('SET_AVATAR'.'avatar')
        commit('SET_INTRODUCTION', department)
        resolve(data)
      }).catch(error= > {
        reject(error)
      })
    })
  }
Copy the code

Front-end filter permission routes

The route returned by the backend is of type tree, which obviously cannot be taken directly.

In that case, do it yourself. Front-end router. Js has two routing menus: constantRoutes and asyncRoutes

  • ConstantRoutes: Routes that each user has, such as login page, home page, 404…
  • AsyncRoutes: All permission pages
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: '/'.component: Layout,
    redirect: '/dashboard'.children: [{path: 'dashboard'.component: () = > import('@/views/dashboard/index'),
        name: 'Dashboard'.meta: { title: 'home'.icon: 'icon-shouye'.affix: true}}]}]export const asyncRoutes = [
]
const router = createRouter()
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}
export default router
Copy the code

Import ‘./permission’ in main.js

Permission-js: Permission-js: permission-js

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'.'/auth-redirect'] // no redirect whitelist

router.beforeEach(async(to, from, next) => {
  // Start the progress bar
  NProgress.start()
  // Set the page title
  document.title = getPageTitle(to.meta.title)
  // Determines whether the user is logged in
  const hasToken = getToken()
  if (hasToken) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({ path: '/' })
      NProgress.done() 
    } else {
      // Determine whether the user has obtained his or her permission role through getInfo
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          // Get the user's personal information
          const roles = await store.dispatch('user/getInfo')
          // Permission route matching
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          // Add a route dynamically
          router.addRoutes(accessRoutes)
          // The hack method ensures that the addRoutes are complete
          // Set replace:true so that the navigation does not leave a historynext({ ... to,replace: true})}catch (error) {
          / / initialization
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login? redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* has no token*/
    if(whiteList.indexOf(to.path) ! = = -1) {
      // In the free login whitelist, enter directly
      next()
    } else {
      // Other pages that do not have access will be redirected to the login page.
      next(`/login? redirect=${to.path}`)
      // next()
      NProgress.done()
    }
  }
})
router.afterEach(() = > {
  // finish progress bar
  NProgress.done()
})
Copy the code

Look at the generateRoutes where roles is actually the user information, prove that you got the backend routing and then the next step

generateRoutes({ commit }, roles) {
    return new Promise(resolve= > {
      let accessedRoutes
      if (roles) {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }  
      /** Parse (localstorage.getitem ('sidebars')) const Permission = [] data.foreach (element => {if (element.children) { element.children.forEach(item => { item.children.forEach(i => { permission.push(i.menuId) }) }) } }) commit('SET_PERMISSION', permission) */
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
Copy the code

Take a closer look at key route filtering and adding dynamic routes (see appendix for full documentation)

/** * Recursively filter asynchronous routing table *@param routes asyncRoutes
 * @param roles* /
export function filterAsyncRoutes(routes, roles) {
  const res = []
  routes.forEach(route= > {
    consttmp = { ... route }if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  return res
}
/** * Compare with the backend routing menu to determine whether the current user has permissions *@param roles
 * @param route* /
function hasPermission(roles, route) {
// Get the route back from the backend and recurse the menu name
  const data = JSON.parse(localStorage.getItem('sidebars'))
  const name = []
  data.forEach(element= > {
    name.push(element.menuName)
    if (element.children) {
      element.children.forEach(item= > {
        name.push(item.menuName)
      })
    }
  })
  // Compare the route returned by the backend with the current route. If yes, pass. If no, pass
  if (route.meta && route.meta.title) {
    return name.some(item= > {
      return route.meta.title === item
    })
  } else {
    return false}}Copy the code

Successful login dynamically displays the route of the user

Since there are page permissions, in theory, there should be functional permissions in fact, there should also be functional permissions, that is, he may not be able to add in the page, only to see the list, this is the page permission point configuration, generally using custom instructions is more convenient

Permission configuration – Page function permission point configuration

The function permission points in the route returned by the back end are extracted by recursion

So let’s first import the file

MainJS

import directives from '@/utils/directives.js'
Vue.use(directives)
Copy the code

And then write the logic

Encapsulate the directives in directives

// Page function permission judgment
function checkPermission(el, binding) {
  const { value } = binding
  if (value) {
  // This is saved by the way when I filter routes
    const permissionArr = store.getters && store.getters.permissionArr
    const permissionRoles = Number(value)

    const hasPermission = permissionArr.some(val= > {
      return permissionRoles === val
    })
    if(! hasPermission) { el.parentNode && el.parentNode.removeChild(el) } }else {
    throw new Error(`need roles! Like v-permission="['admin','editor']"`)}}export default (Vue) => {
  Vue.directive('permission', {
    inserted(el, binding) {// Initialize the call
      checkPermission(el, binding)
    }
    Update (el, binding) {// This is called when refreshing
    // checkPermission(el, binding)
    // }})}Copy the code

The last page uses

<el-button
v-for="(item) in dataSource.tool"
:key="item.key"
v-permission="item.permission"
class="filter-item"
type="primary"
@click="handleAdd(item.name)"
>
{{ item.name }}
</el-button>
Copy the code

At the end

Talk about architecture framework choices

Originally, I wanted to use React +ant, but I feel that react is js file, which is not my appetite. (Personally, I feel that the js file hierarchy is not very clear, relatively speaking, vUE has a lower cost of familiarity)

Since we have a choice, let’s use Vue,

Vue3.0 works, but it will take time to perfect it, given the uncertainties

Let’s go with the solid 2.x version and Element UI.

Permission configuration ends here, what is the problem we can leave a message to discuss together

Now I am refactoring the page coding, but I also encountered some problems, such as the display problem of Amap combined with Echarts, the role level problem of the role page, and the secondary encapsulation of the TABLE component.

I’ll share it with you when I have enough potholes.

This is the end of Vue refactoring project permission configuration, thank you for reading!! See you next time

The appendix

Permission section of vuex

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

/** * Compare with the backend routing menu to determine whether the current user has permissions *@param roles
 * @param route* /
function hasPermission(roles, route) {
  const data = JSON.parse(localStorage.getItem('sidebars'))
  const name = []
  data.forEach(element= > {
    name.push(element.menuName)
    if (element.children) {
      element.children.forEach(item= > {
        name.push(item.menuName)
      })
    }
  })
  if (route.meta && route.meta.title) {
    return name.some(item= > {
      return route.meta.title === item
    })
  } else {
    return false}}/** * Recursively filter asynchronous routing table *@param routes asyncRoutes
 * @param roles* /
export function filterAsyncRoutes(routes, roles) {
  const res = []
  routes.forEach(route= > {
    consttmp = { ... route }if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  return res
}

const state = {
  routes: [].addRoutes: [].permissionArr: []}const mutations = {
  SET_ROUTES: (state, routes) = > {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  },
  SET_PERMISSION: (state, arr) = > {
    state.permissionArr = arr
  }
}

const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve= > {
      let accessedRoutes
      if (roles) {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      const data = JSON.parse(localStorage.getItem('sidebars'))
      const permission = []
      data.forEach(element= > {
        if (element.children) {
          element.children.forEach(item= > {
            item.children.forEach(i= > {
              permission.push(i.menuId)
            })
          })
        }
      })
      commit('SET_ROUTES', accessedRoutes)
      commit('SET_PERMISSION', permission)
      resolve(accessedRoutes)
    })
  }
}

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

Copy the code

Write in the last

I am Liangcheng A, a front end, who loves technology and life.

I’m glad to meet you.

If you want to learn more, please click here and look forward to your little ⭐⭐

  • Feel free to correct any mistakes in the comments section, and if this post helped you, feel free to like and follow 😊

  • This article was originally published in Nuggets and cannot be reproduced without permission at 💌