Vue后台开发实例教程(九)实现权限校验,vue动态菜单实现
小白浏览:4412024-03-26 08:58:42本文累计收益:0我也要赚钱

本实例教程是.net core 系列实例开发教程-权限管理系统前端开发教程,使用PanJiaChen写的Vue后台模板。

本文源码下载地址:http://www.80cxy.com/Blog/ResourceView?arId=202403191532545995NAAqJh

系列教程地址:http://www.80cxy.com/Blog/ArticleView?arId=202403191517574161ay3s5V

本文实现根据用户权限显示相应权限的菜单及功能按钮。整个项目需要修改4个地方。

一、路由模块修改

将路由分为常量路由、异步路由、任意路由三个常量。用户最终显示路由使用异步路由跟接口返回用户角色进行计算,得出应该显示的路由。修改src/router//index.js文件,代码修改如下:

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* Layout */
import Layout from '@/layout'

/**
 * Note: sub-menu only appear when route children.length >= 1
 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
 *
 * hidden: true                   if set true, item will not show in the sidebar(default is false)
 * alwaysShow: true               if set true, will always show the root menu
 *                                if not set alwaysShow, when item has more than one children route,
 *                                it will becomes nested mode, otherwise not show the root menu
 * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
 * name:'router-name'             the name is used by <keep-alive> (must set!!!)
 * meta : {
    roles: ['admin','editor']    control the page roles (you can set multiple roles)
    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
    icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
  }
 */

/**
 * constantRoutes
 * a base page that does not have permission requirements
 * all roles can be accessed
 */
//需要把项目中的路由进行拆分
//常量路由:不管什么角色都可以看见的路由 登录 404 首页
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/home',
    children: [
      {
        path: 'home',
        name: 'Home',
        component: () => import('@/views/home/index'),
        meta: { title: '首页', icon: 'dashboard' }
      }
    ]
  },
]
//异步路由:不同的用户(角色),需要过滤筛选出的路由。
export const asyncRoutes = [
  {
    name: 'Recruit',
    path: '/recruit',
    component: Layout,
    meta: { title: '招聘管理', icon: 'el-icon-goods' },
    children: [
      {
        name: 'RecruitBase',
        path: 'recruitbase',
        component: () => import('@/views/recruit/recruitBase'),
        meta: { title: '招聘管理', icon: 'dashboard' }
      },
      {
        name: 'RecruitPosition',
        path: 'recruitposition',
        component: () => import('@/views/recruit/recruitPosition'),
        meta: { title: '职位管理', icon: 'dashboard' }
      },
      {
        name: 'RecruitUser',
        path: 'recruituser',
        component: () => import('@/views/recruit/recruitUser'),
        meta: { title: '用户管理', icon: 'dashboard' }
      },
    ]
  },
  {
    name: 'System',
    path: '/system',
    component: Layout,
    meta: { title: '系统管理', icon: 'el-icon-goods' },
    children: [
      {
        name: 'User',
        path: 'user',
        component: () => import('@/views/system/user'),
        meta: { title: '用户管理', icon: 'dashboard' }
      },
      {
        name: 'Menu',
        path: 'menu',
        component: () => import('@/views/system/menu'),
        meta: { title: '菜单管理', icon: 'dashboard' }
      },
      {
        name: 'Role',
        path: 'role',
        component: () => import('@/views/system/role'),
        meta: { title: '角色管理', icon: 'dashboard' }
      },
      {
        name: 'IndexAuth',
        path: 'role/auth/:id',
        component: () => import('@/views/system/role/indexAuth'),
        meta: {
          activeMenu: '/acl/role',
          title: '角色授权',
        },
        hidden: true,
      },
    ]
  },
];
//任意路由:当路径出现错误的时候,重定向到404
export const anyRoutes = { path: '*', redirect: '/404', hidden: true };// 404 page must be placed at the end !!!

const createRouter = () => new Router({
  // mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router
二、根据接口返回权限计算显示路由

修改用户登录模块,将接口返回权限数据与异步路由计算得出显示路由,修改src/store/modules/user.js,代码如下:

import { login, logout, getInfo } from '@/api/system'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { anyRoutes, resetRouter, asyncRoutes, constantRoutes } from '@/router'
import router from '@/router'

const getDefaultState = () => {
  return {
    token: getToken(),//token
    name: '',//用户名
    avatar: '',//用户头像
    //服务器返回的菜单信息【根据不同的角色:返回的标记信息,数组里面的元素是字符串】
    routes: [],
    //当前用户角色信息
    roles: [],
    //按钮权限的信息
    buttons: [],
    //对比之后[项目中已有的异步路由与服务器返回的标记的信息进行对比,最终需要展示的路由]
    resultAsyncRoutes: [],
    //用户最终需要展示的全部路由
    resultAllRoutes: [],
  }
}

const state = getDefaultState()

const mutations = {
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  //存储用户信息
  SET_USERINFO: (state, userInfo) => {
    //用户名
    state.name = userInfo.name;
    //用户头像
    state.avatar = userInfo.avatar;
    //菜单权限标记(路由的名称)
    state.routes = userInfo.routes;
    //按钮权限标记
    state.buttons = userInfo.buttons;
    //角色信息
    state.roles = userInfo.roles;
  },
  SET_NAME: (state, name) => {
    state.name = name
  },
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  },
  //最终计算出来的路由
  SET_RESULTASYNCROUTES: (state, asyncRoutes) => {
    state.resultAsyncRoutes = asyncRoutes;
    //计算出当前用户需要展示的所有路由
    state.resultAllRoutes = constantRoutes.concat(state.resultAsyncRoutes, anyRoutes);
    //给路由器添加新的路由
    router.addRoutes(state.resultAllRoutes);
  }
}
//定义一个函数:两个数组进行对比,对比出当前用户到底展示哪些异步路由
const computedAsyncRoutes = (asyncRoutes, routes) => {
  return asyncRoutes.filter(item => {
    //数组当中没有这个元素返回索引值-1
    if (routes.indexOf(item.name) != -1) {
      //递归
      if (item.children && item.children.length) {
        item.children = computedAsyncRoutes(item.children, routes)
      }
      return true;
    }
  })
}
const actions = {
  //用户登录
  async login({ commit }, userInfo) {
    const { username, password } = userInfo
    let result = await login({ username: username.trim(), password: password })
    if (result.code == 200) {
      commit('SET_TOKEN', result.data.token)
      setToken(result.data.token)
      return 'ok'
    }
    else {
      return Promise.reject(new Error('faile'))
    }
  },

  // 获取用户信息
  async getInfo({ commit, state }) {
    let result = await getInfo(state.token)
    if (result.code == 200) {
      //返回数据包含:用户名name、用户头像avatar、routes[返回的标志:不同用户应该展示哪些菜单的标记]、roles(用户角色信息)、buttons[按钮的信息:按钮权限用的标记]
      const { data } = result
      commit('SET_USERINFO', data)
      commit('SET_RESULTASYNCROUTES', computedAsyncRoutes(asyncRoutes, data.routes));
      //执行成功返回data数据
      return 'ok'
      //return Promise.resolve(result.data)
    }
    else {
      return Promise.reject(new Error('faile'))
    }
  },

  // 退出登录
  async logout({ commit, state }) {
    let result = await logout(state.token)
    if (result.code == 200) {
      removeToken() // must remove  token  first
      resetRouter()
      commit('RESET_STATE')
      //执行成功返回data数据
      return 'ok'
      //return Promise.resolve(result.data)
    }
    else {
      return Promise.reject(new Error('faile'))
    }
  },

  // 移除token
  resetToken({ commit }) {
    return new Promise(resolve => {
      removeToken() // must remove  token  first
      commit('RESET_STATE')
      resolve()
    })
  }
}

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

三、修改菜单组件的路由获取源改为计算的路由

修改src/layout/components/sidebar/index.vue组件

routes() {
   return this.$store.state.user.resultAllRoutes;
},
四、返回页面刷新空白页面

修改src/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'] // no redirect whitelist
//跳转路由前执行代码
router.beforeEach(async(to, from, next) => {
  // 开启进度条
  NProgress.start()

  // 设置页面标题
  document.title = getPageTitle(to.meta.title)

  // 判断用户是否已登录
  const hasToken = getToken()
  //用户已经登录
  if (hasToken) {
    //如何用户访问的是登录页面,
    if (to.path === '/login') {
      // 如果登录跳转到首页
      next({ path: '/' })
      NProgress.done()
    } else {
      //判断是否获取到用户信息,如果没有执行获取用户信息
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          // 获取用户信息
          await store.dispatch('user/getInfo')
          //{...to}解决页面刷新返回空页面问题
          next({...to})
        } catch (error) {
          // remove token and go to login page to re-login
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } 
  //用户未登录
  else {
    //如果用户访问的是登录页面则直接跳转
    if (whiteList.indexOf(to.path) !== -1) {
      //in the free login whitelist, go directly
      //在免费登录白名单中,直接进入
      next()
    } 
    //如果用户访问非登录页面,需要跳转到登录页面
    else {
      // other pages that do not have permission to access are redirected to the login page.
      //其他没有访问权限的页面被重定向到登录页面。
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})
五、功能按钮权限控制
<el-button type="primary" @click="showAddUser" v-show="$store.state.user.buttons.indexOf('btn.user.add')!=-1">添加</el-button>
学习交流

评论列表
发表评论
+ 关注