Route Permission Overview

The process is similar to the original VUe2 version

Convention data format

Confirm the data format with the backend

{children: [// children: null, // children: [// children: null, // children: [// children: null, // children: [// children: null, // children: [// children: null, // children: [// children: null, // ParentId: 1, parentName: "permission management ", // parentName perms: ", type: 1, // type 0 is directory, 1 is menu url: "/authority/subsite" // menu address}], icon: "system", id: 1, isShow: 1, name: "Permission management ", open: null, orderNum: 0, parentId: 0, parentName: null, perms: null, type: 0, "/ /authority"}Copy the code

Routing design

Route with vuex, sessionStorage, NProgress edible

1. Add Progress to achieve the navigation progress bar effect

configuration

// Simply configure nprogress. inc(2); NProgress.configure({ easing: 'ease', speed: 500, showSpinner: false }); / / actionCopy the code

use

BeforeEach (async (to, from, next) => {nprogress.start (); // Navigate to jump start router.beforeeach (async (to, from, next) => {nprogress.start (); Router.aftereach ((to, from) => {nprogress.done (); });Copy the code

2. Configure the router

1. Configure the default route

The login page must be loaded by default, so it is placed in the default route and directly obtained by the front end

If you have pages that don’t need permissions, you can put them here

Export const routes = [{path: '/login', name:'login', component: () = > import (' @ / views/login_page/index. The vue '), meta: {title: 'login screen, show: true}}, {path: '/ vuex' name: 'vuex' component: () = > import (' @ / views/vuex_page/index. The vue '), meta: {title: '2' test interface, show: true}},];Copy the code

2. Permission page control

The main logic is the same as the flow chart above

router.beforeEach(async (to, from, Next) => {if (to.meta. Title) {document.title = to.meta. Title Sessionstorage. getItem("token") if (hasLogin) {// There is login status console.log('state', to, The from / / to judge whether there is deposited in the routing data in the vuex if (store. State. MenuList. Length = = = 0) {/ / to this step the user has logged on, but again refresh the browser, Let res = await store.dispatch('getMenuList') // code is not 200. If (res! == 200) {// Clear storage cache clearLoginInfo() // jump login page next({path: '/login', replace: {confirmButtonText: 'confirmButtonText ', callback: {confirmButtonText:' confirmButtonText ', callback: () => {}})} else {// There is routing data // Dynamically added routing information by addRoute Store.state.menulist. map((item) => {router.addroute (item); }) // router.addroutes is asynchronous, using next({... To, replace: true}) reload next({... to, replace: True})}} if (to.name === 'login') {// If (to.name === 'login') {// If (to.name === 'login') {// If (to.name === 'login') {// If (to.name === 'login') { Router.getroutes ()[0].path,})} else {// this route is not matched if (to.matched. Length === 0) {// This route is not matched. Router.getroutes ()[0].path,}) // Navigation status automatically updated to the first menu sessionStorage.setitem ('activeIndex', Return (); return (); return (); return (); return (); return (); return (); return (); return (); Jump to if (to.name! == 'login') { next({ path: '/login', }) } else { next() } } });Copy the code

3. Configure the router

import { createRouter, createWebHistory } from 'vue-router' import { ElMessageBox } from 'element-plus'; import NProgress from 'nprogress'; import store from '.. /store' import { clearLoginInfo } from ".. /utils/common"; // Simply configure nprogress. inc(2); NProgress.configure({ easing: 'ease', speed: 500, showSpinner: false }); Export const routes = [{path: '/login', name:'login', Component: () = > import (' @ / views/login_page/index. The vue '), meta: {title: 'login screen, show: true}}, {path: '/ vuex' name: 'vuex' component: () = > import (' @ / views/vuex_page/index. The vue '), meta: {title: '2' test interface, show: true}},]; Export const router = createRouter({// Build router with hash mode) // history: CreateWebHashHistory (), // build route using history mode (no # numbers in URL, but special configuration for production environment) createWebHashHistory(), routes:routes }); router.beforeEach(async (to, from, next) => { NProgress.start(); If (to.meta. Title) {document.title = to.meta. Title} let hasLogin = sessionStorage.getitem ("token") // Console. log('state', hasLogin, to, from) if (hasLogin) {console.log('state', to, The from / / to judge whether there is deposited in the routing data in the vuex if (store. State. MenuList. Length = = = 0) {/ / to this step the user has logged on, but again refresh the browser, Let res = await store.dispatch('getMenuList') // code is not 200. If (res! == 200) { clearLoginInfo() next({ path: '/login', replace: {confirmButtonText: 'confirmButtonText ', callback: {confirmButtonText:' confirmButtonText ', callback: () => {// clearLoginInfo() // jump to login page}})} else {// There is routing data store.state.menulist.map ((item) => {router.addroute (item); }) // router.addroutes is asynchronous, using next({... To, replace: true}) reload next({... to, replace: True})}} if (to.name === 'login') {// If (to.name === 'login') {// If (to.name === 'login') {// If (to.name === 'login') {// If (to.name === 'login') { Router.getroutes ()[0].path,})} else {// this route is not matched if (to.matched. Length === 0) {// This route is not matched. router.getRoutes()[0].path, }) sessionStorage.setItem('activeIndex', Router.getroutes ()[0].meta. MenuId)} else {next()} // console.log('333', router.getroutes ())}} else {// No login, If (to.name! = if (to.name! = if (to.name! == 'login') { next({ path: '/login', }) } else { next() } } }); router.afterEach((to, from) => { NProgress.done(); Console. log(to, from, 3333) // Refresh after redirection navigation button display if (sessionStorage.getitem ('activeIndex')! == to.meta.menuId) { sessionStorage.setItem('activeIndex', to.meta.menuId) } }); export default routerCopy the code

3. Vuex configuration

The logic here is that every time you refresh or jump to a route, you need to check whether the page still has permissions and whether the menu returns normally

import { createStore, createLogger } from 'vuex' import HttpAxios from '.. /utils/httpTool' const debug = process.env.NODE_ENV ! == 'production' const store = createStore({ state: { count: 0, collapse: false, menuList: [] }, getters: { menuList: Mutations: {setMenuList (state, value) {return value; }}, actions: {// get dynamic route async getMenuList({commit}) {// Get route data via interface let URL ='/sys/menu/nav'; let requestData = {}; let res = await HttpAxios.axiosGet(url, requestData); // Use axios here, If (res.code === 200 && res.data.menus.length > 0) {// success // generate routing information let menus = Menus (res.data.), commit(' menulist ', menus) return res.code} else {// fail to return 500}}}, modules: { }, strict: debug, plugins: debug ? [createLogger()] : [] }); Let needRoutes = (data) => {if (! Array.isArray(data)) { return new TypeError('arr must be an array.'); } let arr = formatComponent(data) return arr; } // A recursive function to associate components and routes, Let formatComponent = (data) => {data.map((obj) => {if (obj. Url && /\S/.test(obj. Url)) {if (obj. Type === 1) { // menu logic obj.url = obj.url.replace(/^\//, ") const component = obj.url Obj.component = () => import(' @/views/${component}/index.vue ') // Regex route obj.path = '/' + obj.url.replace('/', '-').match(/(\S*)-/)[1] + '/' + obj.url.replace('/', '-') obj.title = obj.name let name = obj.path.split('/') obj.name = name[name.length-1] obj.meta = { menuId: obj.id.toString(), title: obj.title, isDynamic: true, show: obj.isShow === 1 ? true: false, isTab: true, }} else if (obj.type === 0) {// Directory logic obj.component= () => import('@/views/home_page/index.vue') obj.path = obj.url obj.meta = { menuId: obj.id.toString(), title: obj.name, isDynamic: true, show: false, isTab: True,}}} if (obj.children && obj.children. Length > 0) {// children have length, Return formatComponent(obj.children)}}) return data} export default StoreCopy the code

Implementation effect of routing

In the data format designed by my side

{children: [// children: null, // children: [// children: null, // children: [// children: null, // children: [// children: null, // children: [// children: null, // children: [// children: null, // ParentId: 1, // parentName: "permission management ", perms: "", type: Url: "/authority/subsite" // menu address}], icon: "system", id: 1, isShow: 1, name: "permission management ", open: Null, orderNum: 0, parentId: 0, parentName: null, perms: null, type: 0,Copy the code

There are currently two layers of routing, so the secondary route is “/authority/subsite”.

The first level route is /authority and the second level route is /authority/authority-subsite after processing by formatComponent

After adding the router dynamically

4. Element-plus configuration home page

1. Page Page Settings

In order to facilitate the front-end page construction, and with routing design, the front-end page structure is directory -> menu -> sub-menu, I temporarily here is the secondary menu, did not involve the tertiary menu

1. The configuration of the app.vue page, using the Transition configuration, is a little different from vue2

<template>
  <div id="app">
    <router-view v-slot="{ Component }">
      <transition name="slide-fade" mode="out-in">
        <component :is="Component" />
      </transition>
    </router-view>
  </div>
</template>
Copy the code

2. Set a home page

Create a homepage page and configure it. The homepage I have configured here is Element’s official navigation bar

1. The content page configured in el-main is the content page after route switching. 2Copy the code
<template> <div id="homePage"> <el-container style="height: 100%"> <el-aside height="100%" style="transition: all .3s" :style="{width:collapse? 56+'px':256+'px'}"> <nav-menu></nav-menu> </el-aside> <el-container> <el-header style="padding: 0; height: 120px"> <nav-header></nav-header> </el-header> <el-main style="padding: 20px; width: 100%; height: 100%; background: #EBEDF2"> <router-view v-slot="{ Component }"> <transition name="mode-fade" mode="out-in"> <component :is="Component" />  </transition> </router-view> </el-main> </el-container> </el-container> </div> </template>Copy the code

Here’s the nav-menu component, which needs a subcomponent called menu, which needs to be recursive

The refRouter field is the navigation data stored in the VUEX

<template> <div id="navMenu"> <el-menu router class="el-menu-vertical-demo" :collapse="collapse" :default-active="activeIndex" el-menu background-color="#2A354E" text-color="#fff" active-text-color="#1890FF" @select="handleSelect" @open="handleOpen" @close="handleClose" > <Menu :routerList="refRouter"></Menu> </el-menu> </div>  </template>Copy the code

Menu component, where v-if judgment is corresponding to the previous data format definition,

// 1. If there are children, And children have length <el-submenu V-if ="item.children && item.children. Length > 0" :key="item.path" :index="item.meta. MenuId "> <template #title style="padding-left:10px" v-if="! item.meta.show"> <i class="el-icon-menu"></i> <span>{{ item.meta.title}}</span> </template> <! <Menu :routerList="item.children"></Menu> </el-submenu>Copy the code
<el-menu-item v-if="! <el-menu-item v-if="! item.children && item.meta.show" :route="item.path" :index="item.meta.menuId" :key="item.path"> <i class="el-icon-menu"></i> <span>{{item.meta.title}}</span> </el-menu-item>Copy the code

The entire page is as follows

<template> <template v-for="item in routerList"> <el-submenu v-if="item.children && item.children.length > 0" :key="item.path" :index="item.meta.menuId"> <template #title style="padding-left:10px" v-if="! item.meta.show"> <i class="el-icon-menu"></i> <span>{{ item.meta.title}}</span> </template> <! <Menu :routerList="item.children"></Menu> </el-submenu> <el-menu-item V-if ="! item.children && item.meta.show" :route="item.path" :index="item.meta.menuId" :key="item.path"> <i class="el-icon-menu"></i> <span>{{item.meta.title}}</span> </el-menu-item> </template> </template>Copy the code

The directory structure is as follows

Function permission Overview

Considering the need for detailed page permission control, a judgment is made when requesting the interface upon page entry

1. Permissions mainly involve the addition, deletion and modification of data. 2. Control mode layer 2, background rewriting logic, no permission allocation request, direct filteringCopy the code

The identity of the permission is obtained together with the acquisition of the menu. After the acquisition, it is directly stored in the cache. Here you can directly see the previous vuex step

// Cache permissions configured to storage sessionStorage.setitem ('permissions', json.stringify (res.data.authorities))Copy the code

Permission identification can be involved according to the actual service, and one of the logical things to do is to judge at the button

I’m going to write a public function that checks whether there is a permission flag and returns true if there is, false if there is not

export function isAuth (key) { return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) ! == -1 || false }Copy the code

So when I use it in a page, I can add it directly, in this case, to the prototype chain, in main.js

Vue3’s prototype chain is not used in the same way as VUe2’s

Import {isAuth} from "./utils/common"; app.config.globalProperties.isAuth = isAuthCopy the code

Finally in the page can be directly used

<el-button v-if="isAuth('sys:web:save')" type="primary" @click="addOrUpdateHandle()"> new </el-button>Copy the code