Vue开发实例:el-menu进阶应用之打造可配置化后台管理侧边栏

张开发
2026/4/18 17:52:59 15 分钟阅读

分享文章

Vue开发实例:el-menu进阶应用之打造可配置化后台管理侧边栏
1. 从零开始认识el-menu组件第一次接触Element UI的el-menu组件时我完全被它丰富的配置项搞懵了。这个看似简单的导航菜单组件实际上藏着不少玄机。先来看个最基础的例子template el-menu el-menu-item index1首页/el-menu-item el-menu-item index2用户管理/el-menu-item /el-menu /template就是这么几行代码就能生成一个可点击的导航菜单。但实际项目中我们需要的远不止这些。比如要给菜单加图标Element UI内置的图标库就很方便el-menu-item index2 i classel-icon-user/i span用户管理/span /el-menu-item这里有个小技巧一定要用span包裹文字内容否则在某些情况下会出现样式错乱。我当初就踩过这个坑调试了半天才发现问题所在。2. 动态菜单的数据结构设计要让菜单真正可配置化首先得设计好数据结构。经过多个项目的实践我总结出这样一个JSON结构const menuItems [ { id: 1, title: 系统管理, icon: el-icon-setting, path: /system, children: [ { id: 11, title: 用户管理, icon: el-icon-user, path: /system/users } ] } ]这个结构的特点是支持无限级嵌套通过children字段包含路由路径path有图标配置icon每个菜单项都有唯一ID在实际项目中我通常会把这些数据存在Vuex或者Pinia中方便全局管理。比如这样// store/modules/menu.js export default { state: () ({ menus: [] }), mutations: { setMenus(state, menus) { state.menus menus } } }3. 递归渲染多级菜单有了数据结构接下来就是动态渲染了。这里需要用到递归组件技术我来分享下我的实现方案template el-menu :routertrue menu-item v-foritem in menuList :keyitem.id :itemitem / /el-menu /template script import MenuItem from ./MenuItem.vue export default { components: { MenuItem }, props: { menuList: Array } } /script然后创建一个单独的MenuItem组件来处理递归// MenuItem.vue template template v-ifitem.children el-submenu :indexitem.path template #title i :classitem.icon/i span{{ item.title }}/span /template menu-item v-forchild in item.children :keychild.id :itemchild / /el-submenu /template el-menu-item v-else :indexitem.path i :classitem.icon/i span{{ item.title }}/span /el-menu-item /template这种实现方式有几个优点代码结构清晰逻辑分离支持无限级菜单嵌套易于维护和扩展4. 权限控制的实现方案权限控制是后台系统的核心需求。我常用的方案是基于角色RBAC的权限控制具体实现分三步第一步后端接口设计// 获取用户菜单权限 GET /api/user/menus // 返回数据结构 [ { id: 1, // ...其他字段 roles: [admin, editor] // 有权访问的角色 } ]第二步前端权限过滤在路由守卫中进行权限校验router.beforeEach(async (to, from, next) { const { roles } await store.dispatch(user/getInfo) const accessibleRoutes filterRoutes(allRoutes, roles) router.addRoutes(accessibleRoutes) next() })第三步菜单过滤在渲染菜单前进行过滤computed: { accessibleMenus() { return this.menus.filter(menu { return !menu.roles || menu.roles.includes(this.user.role) }) } }5. 菜单状态持久化技巧用户刷新页面后如何保持菜单的展开状态我的解决方案是使用localStorage存储当前展开的菜单// 存储 localStorage.setItem(openedMenus, JSON.stringify(openedMenus)) // 读取 const openedMenus JSON.parse(localStorage.getItem(openedMenus)) || []在el-menu组件上绑定default-openeds属性el-menu :default-openedsopenedMenus openhandleOpen closehandleClose !-- 菜单内容 -- /el-menu实现open/close事件处理methods: { handleOpen(index) { this.openedMenus.push(index) this.saveOpenedMenus() }, handleClose(index) { this.openedMenus this.openedMenus.filter(i i ! index) this.saveOpenedMenus() } }6. 性能优化实践当菜单项很多时比如超过100个可能会遇到性能问题。我总结了几种优化方案虚拟滚动el-menu virtual-list :size48 :remain10 menu-item v-foritem in bigMenuList :keyitem.id :itemitem / /virtual-list /el-menu按需加载// 只加载一级菜单 const loadChildren async (parentId) { const children await api.getMenuChildren(parentId) // 更新菜单数据... }缓存处理// 使用keep-alive缓存菜单组件 keep-alive menu-component v-ifshowMenu / /keep-alive7. 主题定制与样式覆盖Element UI的默认样式可能不符合项目需求这时候就需要自定义样式。我的经验是使用SCSS变量覆盖// variables.scss $--menu-item-hover-fill: #f0f7ff; $--menu-item-active-color: #1890ff;深度选择器修改组件内部样式::v-deep .el-submenu__title { font-size: 16px; :hover { background-color: #f0f7ff !important; } }使用CSS变量实现动态主题// JS动态设置 document.documentElement.style.setProperty(--menu-bg-color, #304156) // CSS中使用 .el-menu { background-color: var(--menu-bg-color) !important; }8. 常见问题与解决方案在实际开发中我遇到过不少坑这里分享几个典型问题的解决方法问题1路由跳转但菜单不高亮解决方法确保el-menu的router属性为true且index值与路由path一致问题2多级菜单缩进不正常解决方法检查是否有自定义样式覆盖了默认样式特别是padding值问题3菜单折叠后图标消失解决方法使用collapse-transition属性并确保图标组件正确引入问题4动态更新菜单后状态丢失解决方法使用Vue.set或数组的变异方法来更新菜单数据9. 移动端适配经验在移动设备上我通常会将侧边栏改造成可折叠的抽屉式菜单el-drawer :visible.syncdrawerVisible directionltr size200px el-menu !-- 菜单内容 -- /el-menu /el-drawer配合媒体查询实现响应式布局media (max-width: 768px) { .sidebar { display: none; } .mobile-menu-btn { display: block; } }10. 测试与调试技巧为了保证菜单功能的稳定性我建议编写单元测试验证菜单渲染it(should render 3 menu items, () { const wrapper mount(Menu, { propsData: { menuList: mockMenuData } }) expect(wrapper.findAll(.el-menu-item).length).toBe(3) })使用Vue Devtools检查菜单组件状态添加错误边界处理error-boundary menu-component / /error-boundary11. 与Vue Router的深度集成要实现更灵活的路由控制可以在路由配置中添加meta信息const routes [ { path: /dashboard, component: Dashboard, meta: { menu: { title: 控制台, icon: el-icon-monitor } } } ]然后在渲染菜单时读取这些信息computed: { menuList() { return this.$router.options.routes .filter(route route.meta?.menu) .map(route ({ path: route.path, title: route.meta.menu.title, icon: route.meta.menu.icon })) } }12. 国际化支持方案对于多语言项目菜单文本也需要支持国际化。我的实现方式是准备多语言资源// lang/en.js export default { menu: { dashboard: Dashboard, users: User Management } }在菜单组件中使用$t函数el-menu-item index/dashboard {{ $t(menu.dashboard) }} /el-menu-item动态切换语言时更新菜单watch: { $i18n.locale() { this.menuList this.generateMenuList() } }13. 与后端API的交互设计前后端分离的项目中菜单数据通常来自后端API。我推荐的设计是API接口规范GET /api/menus Response: { code: 200, data: [ { id: 1, name: dashboard, title: 控制台, icon: dashboard, path: /dashboard, children: [...] } ] }前端请求处理async function fetchMenus() { try { const { data } await axios.get(/api/menus) store.commit(menu/setMenus, data) } catch (error) { console.error(获取菜单失败, error) } }14. 可访问性优化为了让菜单对屏幕阅读器等辅助设备更友好可以添加ARIA属性el-menu rolenavigation aria-labelMain menu el-menu-item rolemenuitem aria-currentpage ... /el-menu-item /el-menu支持键盘导航mounted() { document.addEventListener(keydown, this.handleKeyNavigation) }, methods: { handleKeyNavigation(e) { // 实现上下左右键导航逻辑 } }15. 项目实战经验分享在最近的一个后台管理系统项目中我遇到了一个特殊需求需要根据用户的操作动态更新菜单。比如当用户创建了新项目后菜单中要自动添加该项目相关的菜单项。我的解决方案是使用Vuex的插件机制持久化菜单状态// store/plugins/menu.js export default function createMenuPlugin() { return store { store.subscribe((mutation, state) { if (mutation.type menu/addDynamicItem) { localStorage.setItem(dynamicMenus, JSON.stringify(state.menu.dynamicItems)) } }) } }动态添加菜单项的方法// store/modules/menu.js actions: { addProjectMenu({ commit }, project) { const menuItem { id: project_${project.id}, title: project.name, path: /projects/${project.id} } commit(addDynamicItem, menuItem) } }在组件中调用this.$store.dispatch(menu/addProjectMenu, newProject)这种方案既满足了业务需求又保持了代码的可维护性。在实际项目中运行良好用户反馈也很正面。

更多文章