















在前端开发中,RBAC(基于角色的访问控制)是企业级应用的核心需求。本文将分享如何使用 Vue3 + TypeScript + Vite + Ant Design Vue 实现一个功能完善的菜单与按钮级别的权限授权组件,支持树形菜单选择、动态按钮联动、状态持久化等高级特性。
src/
├── components/
│ └── MenuAuthModal.vue # 菜单授权弹窗组件
├── store/
│ └── index.ts # Vuex Store,管理全局按钮权限
└── api/
└── contract.ts # 权限相关 API 接口
采用 Ant Design Vue 的 Modal + Tree + Checkbox 组合,实现左右分栏布局:
<template>
<a-modal :visible="visible" width="960px">
<div class="menu-auth-content">
<!-- 标题行 -->
<div class="header-row">
<div class="header-left">可授权菜单</div>
<div class="header-right">可授权按钮</div>
</div>
<!-- 内容区域 -->
<div class="content-row">
<!-- 左侧:树形菜单 -->
<div class="content-left">
<a-tree
v-model:checkedKeys="checkedMenuKeys"
v-model:halfCheckedKeys="halfCheckedMenuKeys"
checkable
:tree-data="menuTreeData"
@select="onMenuSelect"
/>
</div>
<!-- 右侧:按钮列表 -->
<div class="content-right">
<a-checkbox-group v-model:value="checkedButtons">
<div v-for="button in buttonList" :key="button.key">
<a-checkbox :value="button.key" :disabled="button.disabled">
{{ button.label }}
</a-checkbox>
</div>
</a-checkbox-group>
</div>
</div>
</div>
</a-modal>
</template>
使用 全局 Map 保存每个菜单的按钮选中状态,解决切换菜单时状态丢失问题:
// 全局按钮选中状态(菜单ID -> 按钮ID数组)
const globalButtonStates = ref<Map<string, string[]>>(new Map())
// 监听按钮变化,实时保存
watch(checkedButtons, (newValue) => {
if (isRestoringState.value) return // 避免恢复状态时重复触发
if (selectedMenuKeys.value.length > 0) {
const currentMenuId = selectedMenuKeys.value[0]
globalButtonStates.value.set(currentMenuId, [...newValue])
}
}, { deep: true })
将后端返回的嵌套菜单结构转换为 Ant Design Tree 所需格式:
const convertMenuTree = (menu: FindMenuButtonTreeByRoleInfo): any => {
return {
key: String(menu.id),
name: menu.name,
icon: menu.icon || '',
children: menu.children?.map(child => convertMenuTree(child)) || []
}
}
提交时递归收集所有选中菜单(含父级)和按钮:
const collectAllMenuIdsWithParents = (): string[] => {
const allMenuIds = new Set<string>()
// 添加完全选中和半勾选的菜单
checkedMenuKeys.value.forEach(id => allMenuIds.add(id))
halfCheckedMenuKeys.value.forEach(id => allMenuIds.add(id))
// 递归查找所有父级菜单
checkedMenuKeys.value.forEach(menuId => {
const parentIds = findParentMenuIds(menuId)
parentIds.forEach(parentId => allMenuIds.add(parentId))
})
return Array.from(allMenuIds)
}
在 Vuex Store 中构建按钮权限映射表,供全局使用:
// store/index.ts
function buildButtonPermissionMap(menuList: MenuButtonTree[], buttonMap: Map<string, boolean>) {
menuList.forEach((menu: MenuButtonTree) => {
const menuKey = menu.active || (menu as any).key || ''
if (menuKey && menu.buttons) {
menu.buttons.forEach((button: any) => {
const buttonKey = `${menuKey}_${button.id}`
buttonMap.set(buttonKey, true)
})
}
menu.children?.forEach(child =>
buildButtonPermissionMap([child], buttonMap)
)
})
}
// Getter 用于权限判断
hasButtonPermission: (state) => (menuActive: string, buttonId: number | string) => {
const buttonKey = `${menuActive}_${buttonId}`
return state.buttonPermissions.has(buttonKey)
}
<template>
<a-button @click="openAuthModal">菜单授权</a-button>
<MenuAuthModal ref="authModalRef" @ok="handleAuthSuccess" />
</template>
<script setup lang="ts">
const authModalRef = ref()
const openAuthModal = () => {
authModalRef.value.open({
id: '123',
roleName: '超级管理员'
})
}
const handleAuthSuccess = ({ roleId, menuPermissions, buttonPermissions }) => {
console.log('授权成功', { roleId, menuPermissions, buttonPermissions })
// 刷新列表或更新状态
}
</script>
<template>
<a-button
v-if="$store.getters.hasButtonPermission('ContractList', 1001)"
@click="handleEdit"
>
编辑
</a-button>
</template>
使用 Less 实现美观的双栏布局和滚动条样式:
.menu-auth-content {
.header-row {
display: flex;
border-bottom: 1px solid #f0f0f0;
.header-left {
width: 30%;
border-right: 1px solid #f0f0f0;
}
.header-right {
width: 70%;
}
}
.content-row {
display: flex;
.content-left, .content-right {
max-height: 500px;
overflow-y: auto;
}
.content-left {
width: 30%;
border-right: 1px solid #f0f0f0;
}
.content-right {
width: 70%;
.button-checkbox-item:nth-child(even) {
background: #FAFAFA;
}
}
}
}
// 自定义滚动条
.content-left::-webkit-scrollbar {
width: 6px;
}
.content-left::-webkit-scrollbar-thumb {
background: #d9d9d9;
border-radius: 3px;
}
Map<string, Menu> 快速查找菜单对象,避免 O(n) 遍历isRestoringState 标志位,防止 watch 重复触发[...array] 避免引用导致的意外修改本方案实现了企业级应用所需的完整权限授权功能,具有以下优势:
通过本文的实现,你可以快速在自己的项目中集成类似的权限管理系统,提升应用的安全性和灵活性。
技术栈:Vue3 | TypeScript | Vite | Ant Design Vue | Vuex
适用场景:后台管理系统、SaaS 平台、企业级应用
💬 欢迎在评论区交流你的权限管理实践经验!如果觉得有用,请点赞收藏支持一下~
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。