Vue3 + Element Plus 动态菜单实现:一套代码完美适配多角色权限系统

作者:刘大华日期:2025/12/1

今天分享一个基于Vue3Element Plus的动态菜单实现。这个方案很适用于需要权限管理的后台系统,能够根据用户角色权限显示不同的菜单项。

一、什么是动态菜单?为什么需要它?

在管理后台系统中,不同角色的用户通常需要不同的功能权限。比如:

  • 管理员可以访问所有功能
  • 编辑者只能管理内容
  • 查看者只能浏览数据

如果为每个角色单独开发一套界面,显然效率低下。动态菜单就是解决这个问题的方案——一套代码,根据不同用户角色显示不同的菜单结构

二、实现效果预览

我们先来看看最终实现的效果:

  1. 角色切换:右上角可以切换用户角色(管理员/编辑者/查看者)
  2. 菜单过滤:根据角色自动过滤无权限的菜单项
  3. 侧边栏折叠:支持展开/收起侧边栏
  4. 面包屑导航:显示当前页面位置

老样子,完整源码在文末获取哦~

三、核心实现原理

1. 菜单数据结构设计

合理的菜单数据结构是动态菜单的基础。我们的设计如下:

1const menuData = ref([
2  {
3    id: 'dashboard',        // 唯一标识
4    name: '仪表板',         // 显示名称
5    icon: 'DataBoard',      // 图标
6    route: '/dashboard',    // 路由路径
7    roles: ['admin', 'editor', 'viewer']  // 可访问的角色
8  },
9  {
10    id: 'content',
11    name: '内容管理',
12    icon: 'Document',
13    roles: ['admin', 'editor'],
14    children: [             // 子菜单
15      {
16        id: 'articles',
17        name: '文章管理',
18        route: '/articles',
19        roles: ['admin', 'editor']
20      }
21      // ... 更多子菜单
22    ]
23  }
24  // ... 更多菜单项
25]);
26

这种结构的特点:

  • 支持多级嵌套菜单
  • 每个菜单项明确指定可访问的角色
  • 图标使用 Element Plus 的图标组件

2. 菜单过滤逻辑

核心功能是根据当前用户角色过滤菜单:

1const filteredMenu = computed(() => {
2  return menuData.value
3    .map(item => {
4      // 1. 检查主菜单权限
5      if (!item.roles.includes(currentUser.value.role)) {
6        return null;  // 无权限,过滤掉
7      }
8      
9      // 2. 深拷贝菜单项(避免修改原始数据)
10      const menuItem = { ...item };
11      
12      // 3. 如果有子菜单,过滤子菜单
13      if (menuItem.children) {
14        menuItem.children = menuItem.children.filter(
15          child => child.roles.includes(currentUser.value.role)
16        );
17        
18        // 如果子菜单全被过滤掉,主菜单也不显示
19        if (menuItem.children.length === 0) {
20          return null;
21        }
22      }
23      
24      return menuItem;
25    })
26    .filter(Boolean);  // 过滤掉null值
27});
28

过滤过程详解

  1. 映射(map):遍历每个菜单项,返回处理后的菜单项或null
  2. 权限检查:检查当前用户角色是否在菜单项的角色列表中
  3. 子菜单过滤:对有子菜单的项,递归过滤无权限的子项
  4. 空子菜单处理:如果所有子项都被过滤,父项也不显示
  5. 最终过滤:用filter(Boolean)移除所有null值

计算属性(computed)的优势

  • 自动响应依赖变化(当用户角色变化时自动重新计算)
  • 缓存结果,避免重复计算

3. 用户角色管理

用户信息和角色切换的实现:

1// 当前用户信息
2const currentUser = ref({
3  name: '管理员',
4  role: 'admin',
5  avatar: 'https://example.com/avatar.png'
6});
7
8// 处理角色切换
9const handleRoleChange = (role) => {
10  currentUser.value.role = role;
11  
12  // 角色切换后更新当前激活的菜单
13  if (role === 'viewer') {
14    // 查看者只能访问仪表板
15    activeMenu.value = '/dashboard';
16    currentPageTitle.value = '仪表板';
17  } else {
18    // 其他角色显示第一个可访问的菜单
19    const firstMenu = findFirstAccessibleMenu();
20    if (firstMenu) {
21      activeMenu.value = firstMenu.route;
22      currentPageTitle.value = firstMenu.name;
23    }
24  }
25};
26

四、界面布局与组件使用

1. 整体布局结构

1<div class="app-container">
2  <!-- 侧边栏 -->
3  <div class="sidebar" :class="{ collapsed: isCollapse }">
4    <!-- Logo区域 -->
5    <div class="logo-area">...</div>
6    <!-- 菜单区域 -->
7    <el-menu>...</el-menu>
8  </div>
9  
10  <!-- 主内容区 -->
11  <div class="main-content">
12    <!-- 顶部导航 -->
13    <div class="header">...</div>
14    <!-- 页面内容 -->
15    <div class="content">...</div>
16    <!-- 页脚 -->
17    <div class="footer">...</div>
18  </div>
19</div>
20

这种布局是管理后台的经典设计,具有清晰的视觉层次。

2. Element Plus 菜单组件使用

1<el-menu
2  :default-active="activeMenu"           <!-- 当前激活的菜单 -->
3  class="el-menu-vertical"
4  background-color="#001529"            <!-- 背景色 -->
5  text-color="#bfcbd9"                  <!-- 文字颜色 -->
6  active-text-color="#409EFF"           <!-- 激活项文字颜色 -->
7  :collapse="isCollapse"                <!-- 是否折叠 -->
8  :collapse-transition="false"          <!-- 关闭折叠动画 -->
9  :unique-opened="true"                 <!-- 只保持一个子菜单展开 -->
10>
11  <!-- 菜单项渲染 -->
12  <template v-for="item in filteredMenu" :key="item.id">
13    <!-- 有子菜单的情况 -->
14    <el-sub-menu v-if="item.children" :index="item.id">
15      <!-- 标题区域 -->
16      <template #title>
17        <el-icon><component :is="item.icon" /></el-icon>
18        <span>{{ item.name }}</span>
19      </template>
20      
21      <!-- 子菜单项 -->
22      <el-menu-item v-for="child in item.children" 
23                   :key="child.id" 
24                   :index="child.route"
25                   @click="selectMenu(child)">
26        {{ child.name }}
27      </el-menu-item>
28    </el-sub-menu>
29    
30    <!-- 没有子菜单的情况 -->
31    <el-menu-item v-else :index="item.route" @click="selectMenu(item)">
32      ...
33    </el-menu-item>
34  </template>
35</el-menu>
36

关键点说明

  1. 动态组件<component :is="item.icon"> 实现动态图标渲染
  2. 条件渲染:使用 v-ifv-else 区分子菜单和单菜单项
  3. 循环渲染v-for 遍历过滤后的菜单数据
  4. 唯一key:为每个菜单项设置唯一的 :key="item.id" 提高性能

五、样式设计技巧

1. 侧边栏折叠动画

1.sidebar {
2  width: 240px;
3  background-color: #001529;
4  transition: width 0.3s;  /* 宽度变化动画 */
5  overflow: hidden;
6}
7
8.sidebar.collapsed {
9  width: 64px;
10}
11
12.logo-area .logo-text {
13  margin-left: 10px;
14  transition: opacity 0.3s;  /* 文字淡入淡出 */
15}
16
17.sidebar.collapsed .logo-text {
18  opacity: 0;  /* 折叠时隐藏文字 */
19}
20

2. 布局技巧

1.app-container {
2  display: flex;
3  min-height: 100vh;  /* 全屏高度 */
4}
5
6.main-content {
7  flex: 1;            /* 占据剩余空间 */
8  display: flex;
9  flex-direction: column;
10  overflow: hidden;   /* 防止内容溢出 */
11}
12
13.content {
14  flex: 1;            /* 内容区占据主要空间 */
15  padding: 20px;
16  overflow-y: auto;   /* 内容过多时滚动 */
17}
18

使用 Flex 布局可以轻松实现经典的侧边栏+主内容区布局。

六、实际应用扩展建议

在实际项目中,你还可以进一步扩展这个基础实现:

1. 与路由集成

1import { useRouter, useRoute } from 'vue-router';
2
3const router = useRouter();
4const route = useRoute();
5
6// 菜单点击处理
7const selectMenu = (item) => {
8  // 路由跳转
9  router.push(item.route);
10};
11
12// 根据当前路由设置激活菜单
13watch(route, (newRoute) => {
14  activeMenu.value = newRoute.path;
15  // 根据路由查找对应的页面标题
16  currentPageTitle.value = findTitleByRoute(newRoute.path);
17});
18

2. 后端动态菜单

在实际项目中,菜单数据通常来自后端:

1// 从API获取菜单数据
2const fetchMenuData = async () => {
3  try {
4    const response = await axios.get('/api/menus', {
5      params: { role: currentUser.value.role }
6    });
7    menuData.value = response.data;
8  } catch (error) {
9    console.error('获取菜单数据失败:', error);
10  }
11};
12

3. 权限控制增强

除了菜单过滤,还可以添加更细粒度的权限控制:

1// 权限指令
2app.directive('permission', {
3  mounted(el, binding) {
4    const { value: requiredRoles } = binding;
5    const userRole = currentUser.value.role;
6    
7    if (!requiredRoles.includes(userRole)) {
8      el.parentNode && el.parentNode.removeChild(el);
9    }
10  }
11});
12
13// 在模板中使用
14<button v-permission="['admin', 'editor']">编辑内容</button>
15

总结

通过这个 Vue 3 + Element Plus 的动态菜单实现,我们学到了:

  1. 设计合理的菜单数据结构是动态菜单的基础
  2. 使用计算属性实现菜单过滤,自动响应角色变化
  3. 利用 Element Plus 组件快速构建美观的界面
  4. Flex 布局技巧实现响应式侧边栏
  5. 扩展思路,如路由集成、后端动态菜单等

这个实现方案具有很好的可扩展性,你可以根据实际需求进行调整和增强。

完整源码GitHub地址github.com/1344160559-…

你可以直接复制到HTML文件中运行体验。尝试切换不同的用户角色,观察菜单的变化,加深对动态菜单工作原理的理解。

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《SpringBoot+MySQL+Vue实现文件共享系统》

《这20条SQL优化方案,让你的数据库查询速度提升10倍》

《SpringBoot 动态菜单权限系统设计的企业级解决方案》

《Vue3和Vue2的核心区别?很多开发者都没完全搞懂的10个细节》


Vue3 + Element Plus 动态菜单实现:一套代码完美适配多角色权限系统》 是转载文章,点击查看原文


相关推荐


从客户端自适应码率流媒体迁移到服务端自适应码率流媒体
AKAMAI2025/11/28

流媒体平台需要扩展以容纳数百万在不同设备和网络条件下同时观看的观众,这使得高效的自适应码率(ABR)流媒体技术至关重要。在这篇博文中,我们将探讨从客户端 ABR 过渡到服务端 ABR 的技术细节,重点关注实现细节和性能优化。 如您所在的企业也在考虑采购云服务或进行云迁移, 点击链接了解Akamai Linode解决方案,现在申请试用可得高达500美元专属额度 自适应码率流媒体基础 自适应码率流媒体究竟是如何工作的?让我们一步步来看。首先,视频内容需要经过准备,即将其编码为多种码率(例如 5


大模型瘦身术:量化与蒸馏技术全解析
居然JuRan2025/11/26

为什么要给大模型"瘦身"? 在AI技术飞速发展的今天,大语言模型已经成为各行各业的得力助手。但你是否知道,部署一个大模型的成本有多高? 一个千亿参数级别的模型,不仅需要占用大量的存储空间,在实际运行时更是需要惊人的计算资源。对于企业来说,这意味着高昂的硬件成本和运营开支。因此,如何在保持模型性能的同时降低部署成本,成为了AI工程师们必须面对的挑战。 今天,我们就来聊聊大模型压缩的两大主流技术——量化(Quantization)和蒸馏(Distillation)。 量化:用更少的位数存储参数 什


go ants pool 协程池学习笔记
用户7227868123442025/11/24

概述 使用 Go 开发并发程序很容易,一个 go 关键字就可以启动协程处理任务。Go 创建一个 goroutine 只需要 2K 内存空间,并且 go 协程上下文信息仅存储在两个寄存器中,对于 Go 运行时来说,切换上下文特别快。 不过凡事不加限制就会出问题,如果不加节制的滥用 goroutine 就可能导致内存泄露,增加 Go 运行时调度负担等。 下面看一段 Go http 标准库的代码: func (s *Server) Serve(l net.Listener) error { for


Cursor 2.1 版本更新日志
是魔丸啊2025/11/22

转载 发布日期: 2025年11月21日 改进的计划模式、编辑器中的 AI 代码审查,以及即时 Grep 主要功能 改进的计划模式 (Improved Plan Mode) 当创建计划时,Cursor 会通过澄清性问题来回应,以提高计划质量。Cursor 现在显示一个交互式 UI,让回答问题变得简单。 你也可以使用 ⌘+F 在生成的计划内进行搜索。 AI 代码审查 (AI Code Reviews) 你现在可以直接在 Cursor 中通过 AI 代码审查来发现和修复 bug。它会检查你的更改并


JMeter 自动化测试 + 飞书通知完整指南
兔子蟹子2025/11/20

JMeter 自动化测试 + 飞书通知完整指南 从零开始搭建 JMeter 自动化测试系统,并通过飞书机器人实时推送测试报告 📖 目录 项目简介系统架构环境准备配置参数说明实施步骤代码实现常见问题最佳实践 项目简介 背景 在持续集成/持续部署(CI/CD)流程中,自动化测试是保证代码质量的重要环节。本项目实现了: ✅ 自动化执行:批量运行 JMeter 测试脚本✅ 智能分析:自动统计成功率、响应时间、失败详情✅ 实时通知:测试完成后立即推送飞书消息✅ 可视化报告:美


Gemini 3深夜来袭:力压GPT 5.1,大模型谷歌时代来了
机器之心2025/11/19

Gemini 3 还没现身,推特先崩为敬。 没有哪家模型的发布比 Gemini 3 更万众瞩目,根据 Gemini 之前 3 个月更新一次的频率,AI 社区自 9 月起便对 Gemini 3 翘首以盼。 今天,谷歌开发者关系负责人、Google AI Studio 负责人一条仅含「Gemini」一词的推文,积蓄了数月的期待终于迎来了爆发点,推特相关话题瞬间沸腾。 有趣的是,临近发布节点,推特竟「应景」地崩了几次。尽管「幕后黑手」是 Cloudflare,但这崩溃的时机简直精准得让人怀疑有人背后


AI 为啥能回答你的问题?大模型 5 步工作流程,看完秒懂!
印刻君2025/11/18

如今,大语言模型(LLM)已成为我们学习和工作中的常用工具。当你在对话框输入问题时,或许会好奇,这些模型为何能精准、迅速地生成回答?本文将用通俗易懂的语言,为你拆解背后的核心工作流程。 简单来说,大模型处理问题主要包含五个关键环节,分别是: 分词(Tokenization) 词嵌入(Word Embedding) 位置编码(Positional Encoding) 自注意力机制(Self-Attention) 自回归生成(Autoregressive Generation) 这些专业名词虽然


整数序列权重排序——基于深度优先搜索(DFS)以及记忆化搜索
w24167178402025/11/16

提示:本文适合对算法设计感兴趣的道友一起互相学习,如有疑问,欢迎评论区或者私信讨论。 文章目录 前言 一、题目介绍 二、前置知识扩展 1.深度优先遍历 1.1递归DFS 1.1非递归DFS 2.@cache装饰器 3.range()函数 4.sorted()函数 三、解题思路及代码解读


前端开发小技巧-【JavaScript】- 获取元素距离 document 顶部的距离
禁止摆烂_才浅2025/11/15

获取元素距离 document 顶部的距离 方案1:使用 offsetTop(最简单) const element = document.getElementById('myDiv') const distance = element.offsetTop console.log(distance) // 500(像素) 方案2:使用 getBoundingClientRect() + scrollY(最准确) const element = document.getElementById(


稳定边界层高度参数化方案的回归建模
mayubins2025/11/14

稳定边界层高度参数化方案的回归建模 为了发展一个适用于CAS-ESM气候系统模式的稳定边界层高度参数化方案,本研究基于湍流尺度分析理论,采用多元线性回归方法,对Zilitinkevich类型公式中的经验系数进行确定性拟合。该公式综合考虑了地表机械强迫、热力强迫以及自由大气静力稳定度的综合影响。 理论框架 我们所采用的参数化公式源于稳定层结下湍流动能的平衡关系,其函数形式如下: 1/h² = C1 * (f² / τ) + C2 * (N |f| / τ) + C3 * (|f β F₊| / τ

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读