手把手教学vue的路由权限问题

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

手把手教学vue的路由权限问题

哆来A梦没有口袋   2022-12-02 我要评论

后台管理类系统大多都涉及权限管理,菜单权限,按钮权限。

菜单权限

菜单权限对应 - 路由。菜单权限 - 根据用户角色不同,路由文件动态配置。

相关知识点了解

vue-router

vue-router是vue项目在进行开发过程中必不可少缺少的插件,目前vue2依赖的是vue-router3,vue3依赖的vue-router4

在进行权限控制之前一定要了解哪些路由需要权限哪些不需要

知识点集结

  • router.addRoutes()

动态添加更多的路由规则。参数必须是一个符合 routes 选项要求的数组。

已废弃目前版本再使用该api会被提示已经废弃,但是暂时依旧可以使用

router.addRoutes(routes: Array<RouteConfig>)
  • router.addRoute()

添加一条新路由规则。如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它。

addRoute(route: RouteConfig)
  • router.getRoutes()

获取所有活跃的路由记录列表。注意只有文档中记录下来的 property 才被视为公共 API,避免使用任何其它 property,例如 regex,因为它在 Vue Router 4 中不存在。

路由导航守卫 - beforeEach

router.beforeEach((to, from, next) => {
  /* 必须调用 `next` */
})

全局前置守卫 - 跳转一个路由之前都会执行.

3个参数:

  • to: 即将进入的目标的路由对象
  • from:当前导航正在离开的路由
  • next: 是个函数,进入下一个钩子
  • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
  • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
  • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
  • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

功能实现过程

路由权限第一种可以是后端全部返回,直接调用接口使用后端的可以使用的路由,但是这种情况一般较少。

第二种前端有一个份完整路由,根据后端接口进行筛选,这里讲解第二种情况。

(1)定义路由文件

router -> index.js

import Vue from 'vue'
import Router from 'vue-router'
import store  from '../store'
 
Vue.use(Router)
 
//没有权限的路由
export const constantRoutes = [
  { path: '/', name: 'index', redirect: '/login' },
  { path: '/login', name: 'login', component: () => import('@/views/login') },
  { path: '/register', name: 'register', component: () => import('@/views/register') },
  { path: '/forget', name: 'forget', component: () => import('@/views/forget') },
  { path: '/404', name: 'notfing', component: () => import('@/views/404')}
]
 
//有权限的路由
export const  myAsyncRoute = [{
  path: '/portal',
  name: 'portal',
  component: LayoutPortal,
  redirect: '/portal/home',
  meta: {id: 'no'},
  children: [
    {
      path: '/portal/home',
      name: 'portal-home',
      component: () => import('@/views/portal/home/index.vue'),
      meta: {id: 100100, title: '首页', show: true}
    },
    {
      path: '/portal/user',
      name: 'portal-user',
      component: () => import('@/views/layout/submenu'),
      meta: {id: 100200, title: '统一身份认证', show: true},
      redirect: '/portal/user/userInfo',
      children: [
        {
          path: '/portal/user/userInfo',
          name: 'portal-user-userInfo',
          component: () => import('@/views/portal/userInfo/index.vue'),
          meta: {id: 100201, title: '统一用户管理', show: true}
        },
        {
          path: '/portal/user/userInfo/detial',
          name: 'portal-userInfo-detial',
          component: () => import('@/views/portal/userInfo/detial.vue'),
          meta: { id: 100201, activeMenu: '/portal/user/userInfo', title: '统一用户管理', show: false},
        },
        {
          path: '/portal/user/userAuth',
          name: 'portal-user-userAuth',
          component: () => import('@/views/portal/userAuth/index.vue'),
          meta: {id: 100202, title: '用户认证', show: true}
        },
      ]
    },
    {
      path: '/portal/journal',
      name: 'portal-journal',
      component: () => import('@/views/portal/journal/index.vue'),
      meta: {id: 100303, title: '统一日志管理', show: true},
    },
    {
      path: 'personal',
      name: 'portal-personal',
      component: () => import('@/views/portal/personal/index.vue'),
      meta: {
        id: 'no',
        activeMenu: '/portal/home',
        show: false
      },
    },
  ],
}]
 
export default new Router({
  routes: constantRoutes,
})

(2)注册路由

main.js

import router from './router'
 
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

正常注册完路由就可以开始进行权限设置了

(3)获取有权限路由

不同的项目获取路由权限并不相同,大多数 - 登录接口返回/单独接口进行获取

登录获取权限保存

let res = await this.$api.user.login(data);
 if(res.token) {
      this.$store.commit('setToken', res.token);
      this.$store.commit('setUsername', res.nickName);
      this.$store.commit('setUserInfo', res);
      //存储权限
      localStorage.setItem('menuIdList', JSON.stringify(res.menuIdList))
           this.$message({
              message: '登录成功',
              type: 'success'
            });
 
            setTimeout(() => {
               this.loading = false;
               this.$router.push('/portal')
            }, 1000);
   }

在main.js中拦截 

获取权限进行匹配 - beforeEach一定要有一个next()的出口,不然会陷入死循环

let flag = true;
router.beforeEach((to, from, next) => {
  if (['/login', '/forget', '/register'].includes(to.path)) {
    next();
  } else {
    let token = localStorage.getItem("token");
    if (token) {
      if(flag) {
        try {
          //获取有权限的路由进行组装
          let route = asyncRoute() || [];
          router.addRoutes(route)
          router.addRoute({
            path: '*',
            redirect: '/404'
          })
          flag = false
          next({...to, replace:true})
        }catch(e) {
          next('/login')
        }
       }else {
         next();
       }
      }else {
        next({ path: '/login' })
      }
    }
  }
);

注意: addRoute之后,打印router是看不见的,要获取所有的权限,必须使用router.getRoute()进行查看

(4)路由权限匹配

router.js-根据后端返回的权限,和自己的路由权限匹配,组织成新的路由,和自己想要的格式

export const asyncRoute = function() {
  let menuIdList = localStorage.getItem('menuIdList') ? JSON.parse(localStorage.getItem('menuIdList')) : [];
 
  let tempArr = filterRoute(myAsyncRoute, menuIdList);
 
  store.dispatch('getRoute', tempArr)
 
  let showRoute = [];
  let nowRoute =JSON.parse(JSON.stringify(tempArr))
  if(nowRoute[0].children && nowRoute[0].children.length){
    nowRoute[0].children.forEach(item => {
      let arr = [];
      if(item.children && item.children.length){
        arr = item.children.filter(obj => obj.meta.show)
      }
      if(item.meta.show){
        item['showRouteChildren'] = arr;
        showRoute.push(item)
      }
    })
  }
  store.dispatch('getShowRoute', showRoute)
 
  return tempArr
}
 
 
function filterRoute(arr, menuIdList) {
  if(!arr.length) return [];
  return arr.filter(item => {
    if(item.children && item.children.length) {
      item.children = filterRoute(item.children, menuIdList);
    }
    return (item.meta && item.meta.id && menuIdList.includes(item.meta.id)) || (item.meta && item.meta.id == 'no') || (item.children && item.children.length > 0)
  })
}

在这个过程中,在store存储了路由

import Vue from 'vue'
import Vuex from 'vuex'
 
Vue.use(Vuex)
 
export default new Vuex.Store({
    state: {
        routes: JSON.parse(localStorage.getItem('routes')) ||  [],
        addRoutes: JSON.parse(localStorage.getItem('addRoutes')) ||[],
        showRoutes: []
    },
    mutations: {
        setRoutes(state, routes) {
            state.addRoutes = routes;
            state.routes = constantRoutes.concat(routes)
            localStorage.setItem('routes', JSON.stringify(state.routes))
            localStorage.setItem('addRoutes', JSON.stringify(state.addRoutes))
        },
        setShowRoutes(state, routes) {
            state.showRoutes = routes;
        }
    },
    actions: {
        getRoute({commit}, list) {
            return new Promise(resolve => {
                commit('setRoutes', list)
                resolve(list)
            })
        },
        getShowRoute({commit}, list) {
            return new Promise(resolve => {
                commit('setShowRoutes', list)
                resolve(list)
            })
        }
    }
})

总结: 最后在理一下整个过程 - 存储权限路由数据,在进行跳转的时候进行筛选组合路由。其实筛选路由不一定要写在router.js,只要是你认为合适的地方都可以,在权限控制过程中最重要的是路由拦截。

模拟一下路由拦截的过程

假设login之后进入的/index,路由拦截的过程

/index - > token -> flag(true) ->获取路由权限 -> next('/index') -> 重新进入beforeEach-> token->flag(false) -> next() ->结束

按钮权限 - 操作(自定义指令)

按钮权限主要涉及的知识点就是全局自定义指令

写在main.js。或者单独js文件,main.js进行引入

import Vue from "vue"
import store from "../store"
 
//自定义指令 v-has进行权限判断
Vue.directive("has",{
  inserted : function (el,binding){
    //按钮权限
    const data = store.state.buttons;
    //按钮的值 <el-button v-has>aa</el-button>
    const value = binding.value; //a
    const hasPermissions = data.includes(value);
    if(!hasPermissions){
       //隐藏按钮
      el.style.display = "none";
      setTimeout(()=>{
        el.parentNode.removeChild(el)
      },0)
    }
  }
})

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们