【vue.js】vue后台项目权限功能实现思路

常见的后台系统中,会存在不同的用户角色,如超级管理员、普通用户,这些用户角色的权限是不同的,所以可以操作或显示的模块也不一致。这里采用如下后台管理系统框架作为模板

地址:https://github.com/PanJiaChen/vue-admin-template

简单说下需求,后台菜单如下图,要求系统中存在两类用户,管理员和普通员工,管理员登录系统可见所有菜单选项,普通用户仅可见首页菜单项。

项目左侧菜单Sidebar组件代码如下,用v-for遍历当前路由实例路由配置this.$router.options.routes传递给<sidebar-item/>,进行展示,这里不做详细介绍,

<el-scrollbar wrap-class="scrollbar-wrapper">
   <el-menu
    :default-active="activeMenu"
    :collapse="isCollapse"
    :background-color="variables.menuBg"
    :text-color="variables.menuText"
    :unique-opened="false"
    :active-text-color="variables.menuActiveText"
    :collapse-transition="false"
     mode="vertical"
    >
      <sidebar-item v-for="route in routes" 
       :key="route.path" 
       :item="route" 
       :base-path="route.path" 
      />
    </el-menu>
 </el-scrollbar>

<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'

export default {
  components: { SidebarItem, Logo },
  computed: {
    ...mapGetters([
      'sidebar'
    ]),
    routes() {
      return this.$router.options.routes;
    },
  }
}

由此可知要实现菜单栏的动态展示,在路由配置文件中,就不能将项目中所有的路由配置对象写在一起,这里将项目中的路由配置拆分成三类,如下所示。分别是constantRoutes常量路由,存放所有用户都可见的路由包括首页,登录页,404页;asyncRoutes异步路由,存放不同的用户需要过滤出筛选的路由;anyRoutes任意路由,当路径出现错误的时候重定向到404页面。这样就可以灵活处理路由配置信息了。

router/index.js

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: '首页', icon: 'dashboard' }
    }]
  },
  
]

export const asyncRoutes = [
  {
    name: 'Acl',
    path: '/acl',
    component: Layout,
    redirect: '/acl/user/list',
    meta: {
      title: '权限管理',
      icon: 'el-icon-lock'
    },
    children: [
      {
        name: 'User',
        path: 'user/list',
        component: () => import('@/views/acl/user/list'),
        meta: {
          title: '用户管理',
        },
      },
      ......
    ]
  },
  {
    path:'/product',
    component:Layout,
    name:'Product',
    meta: { title: '商品管理', icon: 'el-icon-goods' },
    children:[
      {
        path:'tradeMark',
        name:'TradeMark',
        component:() => import('@/views/product/tradeMark'),
        meta:{title:'品牌管理'}
      },
      ......
    ]
  },
];

export const anyRoutes = [
  // 404 page must be placed at the end !!!
  { path: '*', redirect: '/404', hidden: true }
];

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

在用户登录系统后,浏览器会根据用户的token信息获取用户的详细信息,里面除了用户的基本信息外,还包括该用户可以访问的菜单列表(Array),以及用户能够操作的按钮列表。

请求行为在vuex的actions中进行,在登录页面派发该actons,在aciions中将响应的用户信息提交mutations。

const getDefaultState = () => {
  return {
    token: getToken(),
    name: '',
    avatar: '',
    routes:[],  // 服务器返回的菜单标记信息
    buttons:[],  // 服务器返回的按钮标记信息
    role:[],  // 服务器返回的角色信息
  }
}

const state = getDefaultState()
const mutations = {
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  USER_INFO: (state,info) => {
    state.name = info.name;
    state.avator = info.avator;
    state.routes = info.routes;   // 菜单权限标记
    state.buttons = info.buttons;  // 按钮权限标记
    state.role = info.role;  // 用户角色
},
const actions = {
  // get user info
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response
        if (!data) {
          return reject('Verification failed, please Login again.')
        }
        // 
        commit('USER_INFO',data);
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },
}

拿到了服务器返回的可显示的异步路由标记信息后,接下去要做的就是将其和项目中所有的异步路由信息进行对比,得到最终需要展示的异步路由信息,这里在登录后再提交一个mutations,命名为SEY_RESULTASYNCROUTES传给mutations一个方法computedAsyncRoutes,在这个方法中做路由的信息对比,将当前用户要显示的异步路由作为返回值返回。

getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response
        console.log(response.data);
        if (!data) {
          return reject('Verification failed, please Login again.')
        }
        // 
        commit('USER_INFO',data);
        commit('SEY_RESULTASYNCROUTES',computedAsyncRoutes(asyncRoutes,data.routes));
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

定义computedAsyncRoutes方法如下,将异步路由asyncRoutes引入,遍历asyncRoutes数组,将每一个路由的name和服务器传来的标记信息routes挨个对比(indexOf方法),过滤出服务器标记数组中包含的路由信息;

这里值得注意一点,由于路由存在二级甚至三级路由,这些路由是写在路由配置对象的children属性中,它们一样也要遍历并对比判断它们是否需要显示,需要递归调用computedAsyncRoutes方法,对每个以及路由的children再次进行过滤。

import { resetRouter,asyncRoutes,anyRoutes,constantRoutes } from '@/router'
// 两个数组进行对比,计算出当前用户显示哪些异步路由
const computedAsyncRoutes = (asyncRoutes,routes) => {
  return asyncRoutes.filter((item) => {
    // 服务器返回的数组中包含的路由信息
    if(routes.indexOf(item.name) !== -1) {
      // 如果存在多级路由,递归
      if(item.children && item.children.length) {
        item.children = computedAsyncRoutes(item.children,routes);
      }
      return true;
    }
  })
}

这样提交到的SEY_RESULTASYNCROUTES这个mutations中就可以拿到上述处理完后的路由配置信息asyncRoutes了,但还需要做路由的合并操作,就是将先前定义的常量路由、任意路由和刚刚对比过滤完的异步路由三者进行合并(数组的concat方法),形成一个最终完整的路由信息,并给当前项目添加这个路由规则router.addRoutes(state.resultAllRoutes)

import router from '@/router'
const getDefaultState = () => {
  return {
    token: getToken(),
    name: '',
    avatar: '',
    routes:[],  // 服务器返回的菜单标记信息
    buttons:[],  // 服务器返回的按钮标记信息
    role:[],  // 服务器返回的角色信息
    resultAsyncRoutes:[],
    resultAllRoutes:[]
  }
}

const state = getDefaultState();

mutations:{
    // 最终计算出的异步路由
    SEY_RESULTASYNCROUTES(state,asyncRoutes){
        // vuex保存当前用户的异步路由
        state.resultAsyncRoutes = asyncRoutes;
        // 计算出当前用户的所有路由
        state.resultAllRoutes = constantRoutes.concat(state.resultAsyncRoutes,anyRoutes);
        // 给路由添加新的路由
        router.addRoutes(state.resultAllRoutes);
    }
}

此时登陆后依旧无法正确显示左侧的菜单。

虽然此时正确的路由已经存在于router实例中,但左侧导航栏<Sidebar>组件遍历的依旧只有首页菜单,那是因为在routes/index.js中注册是常量路由如下所示,而修改操作router.addRoutes(state.resultAllRoutes)是异步操作以后的结果,在sidebar组件中通过 this.$router.options.routes只能获取初始的常量路由

 router/index.js:

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

sidebar.vue

export default {
  components: { SidebarItem, Logo },
  computed: {
    routes() {
      return this.$router.options.routes;
    },
  }
}

只需将获取方式改为仓库中最新的路由配置信息this.$store.state.user.resultAllRoutes

sidebar.vue

routes() {
    return this.$store.state.user.resultAllRoutes;
},

这样就实现了不同身份的用户登录后显示不同的菜单的效果。

此外,要在项目中实现按钮权限,即同一个按钮,有的用户显示按钮,有的用户不显示,只要拿到上述的buttons数组,在对应的按钮标签上使用v-show进行判断即可

<button v-show="$store.state.xxxModule.buttons.indexOf('对应的按钮标签名')">删除</button>

猜你喜欢

转载自blog.csdn.net/weixin_43655896/article/details/122748233