权限控制
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()
}