权限控制

Zap Admin Vue3提供了完善的权限控制系统,包括路由权限、菜单权限和按钮权限, 可以根据用户角色和权限动态控制用户可访问的功能。

权限控制架构

权限控制系统主要包含以下几个部分:

  • 基于角色的访问控制(RBAC)
  • 动态路由生成
  • 权限指令(v-permission)
  • 权限组件(Permission)

权限数据结构

// 用户角色
const roles = ['admin', 'editor', 'user']

// 权限列表
const permissions = [
  'system:user:list',
  'system:user:create',
  'system:user:edit',
  'system:user:delete',
  // 更多权限...
]

路由权限控制

通过路由元信息和路由守卫实现路由级别的权限控制:

// 路由配置中添加权限控制
const routes = [
  {
    path: '/admin/users',
    component: () => import('@/views/admin/users/index.vue'),
    meta: {
      title: '用户管理',
      roles: ['admin'],  // 只有admin角色可访问
      permissions: ['system:user:list']  // 需要的权限
    }
  }
]

// 路由守卫中检查权限
router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore()
  const permissionStore = usePermissionStore()
  
  if (userStore.isLoggedIn) {
    // 检查路由权限
    if (to.meta.roles && !hasRole(userStore.roles, to.meta.roles)) {
      next({ path: '/403' })
    } else if (to.meta.permissions && !hasPermission(permissionStore.permissions, to.meta.permissions)) {
      next({ path: '/403' })
    } else {
      next()
    }
  } else {
    // 处理未登录情况
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
    }
  }
})

动态路由生成

根据用户角色和权限动态生成路由:

// src/permission.js
import router from './router'
import { useUserStore } from './stores/user'
import { usePermissionStore } from './stores/permission'

// 权限控制逻辑
export async function setupPermission() {
  const userStore = useUserStore()
  const permissionStore = usePermissionStore()

  // 获取用户信息
  await userStore.fetchUserInfo()
  
  // 根据角色生成可访问路由
  const accessRoutes = await permissionStore.generateRoutes(userStore.roles)
  
  // 动态添加路由
  accessRoutes.forEach(route => {
    router.addRoute(route)
  })
}

权限指令

自定义权限指令,用于控制元素的显示:

// src/directives/permission.js
import { usePermissionStore } from '@/stores/permission'

export default {
  mounted(el, binding) {
    const { value } = binding
    const permissionStore = usePermissionStore()
    
    if (value && value instanceof Array && value.length > 0) {
      const hasPermission = permissionStore.hasPermissions(value)
      
      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error('需要指定权限值')
    }
  }
}

使用权限指令

<template>
  <div>
    <!-- 按钮权限控制 -->
    <el-button v-permission="['system:user:add']" type="primary">添加用户</el-button>
    <el-button v-permission="['system:user:edit']" type="warning">编辑用户</el-button>
    <el-button v-permission="['system:user:delete']" type="danger">删除用户</el-button>
  </div>
</template>

权限组件

封装权限判断组件,用于条件渲染:

// src/components/Permission/index.vue
<template>
  <div v-if="hasPermission">
    <slot></slot>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { usePermissionStore } from '@/stores/permission'

const props = defineProps({
  permission: {
    type: [String, Array],
    required: true
  }
})

const permissionStore = usePermissionStore()

const hasPermission = computed(() => {
  const { permission } = props
  if (typeof permission === 'string') {
    return permissionStore.hasPermission(permission)
  }
  if (permission instanceof Array && permission.length > 0) {
    return permissionStore.hasPermissions(permission)
  }
  return false
})
</script>

使用权限组件

<template>
  <div>
    <Permission :permission="['system:user:add']">
      <el-button type="primary">添加用户</el-button>
    </Permission>
    
    <Permission :permission="['system:user:export']">
      <el-button>导出数据</el-button>
    </Permission>
  </div>
</template>

菜单权限

根据用户权限动态生成菜单:

// src/composables/useMenu.js
import { computed } from 'vue'
import { usePermissionStore } from '@/stores/permission'

export function useMenu() {
  const permissionStore = usePermissionStore()
  
  // 过滤有权限的路由作为菜单
  const menuRoutes = computed(() => {
    return filterMenuRoutes(permissionStore.routes)
  })
  
  // 过滤路由,只保留需要显示在菜单中的路由
  function filterMenuRoutes(routes) {
    return routes.filter(route => {
      if (route.meta && route.meta.hidden) {
        return false
      }
      
      if (route.children) {
        route.children = filterMenuRoutes(route.children)
      }
      
      return true
    })
  }
  
  return {
    menuRoutes
  }
}

权限测试

在开发环境中可以使用权限测试工具切换不同角色:

// src/utils/auth.js
export function switchRole(role) {
  const userStore = useUserStore()
  const permissionStore = usePermissionStore()
  
  // 切换角色
  userStore.setRole(role)
  
  // 重新生成路由
  permissionStore.generateRoutes([role])
  
  // 刷新页面应用新权限
  window.location.reload()
}