RuoYi-Vue3动态菜单配置:数据库驱动的菜单管理方案

RuoYi-Vue3动态菜单配置:数据库驱动的菜单管理方案

【免费下载链接】RuoYi-Vue3 :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统 【免费下载链接】RuoYi-Vue3 项目地址: https://gitcode.com/GitHub_Trending/ruo/RuoYi-Vue3

引言:告别硬编码的菜单管理困境

你是否还在为管理系统中频繁变动的菜单结构而烦恼?每次增减功能都需要修改前端代码、重新打包部署?RuoYi-Vue3框架提供的动态菜单解决方案彻底解决了这一痛点。本文将深入剖析基于数据库驱动的菜单管理实现,带你掌握从后端接口设计到前端动态渲染的完整流程,实现真正意义上的零代码配置菜单。

读完本文,你将获得:

  • 理解动态菜单的核心实现原理
  • 掌握RuoYi-Vue3菜单权限控制机制
  • 学会自定义菜单渲染逻辑
  • 解决动态菜单加载中的常见问题

一、动态菜单的技术架构

1.1 整体流程图

mermaid

1.2 核心技术组件

组件位置文件名主要功能
src/apimenu.js提供菜单数据获取接口
src/store/modulespermission.js处理菜单权限逻辑
src/layout/componentsSidebar菜单UI渲染
src/routerindex.js路由配置与管理

二、后端接口设计与数据结构

2.1 核心API接口

RuoYi-Vue3通过/getRouters接口获取动态菜单数据,前端实现如下:

// src/api/menu.js
import request from '@/utils/request'

// 获取路由
export const getRouters = () => {
  return request({
    url: '/getRouters',
    method: 'get'
  })
}

2.2 典型菜单数据结构

后端返回的JSON数据通常包含以下字段:

{
  "path": "/system",
  "component": "Layout",
  "redirect": "/system/user",
  "name": "System",
  "meta": {
    "title": "系统管理",
    "icon": "system",
    "roles": ["admin"]
  },
  "children": [
    {
      "path": "user",
      "component": "system/user/index",
      "name": "User",
      "meta": {
        "title": "用户管理",
        "icon": "user",
        "permissions": ["system:user:list"]
      }
    }
  ]
}

三、前端动态路由生成机制

3.1 路由生成流程

mermaid

3.2 核心实现代码

// src/store/modules/permission.js
import { getRouters } from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView'

const usePermissionStore = defineStore('permission', {
  state: () => ({
    routes: [],
    addRoutes: [],
    sidebarRouters: []
  }),
  actions: {
    generateRoutes(roles) {
      return new Promise(resolve => {
        // 向后端请求路由数据
        getRouters().then(res => {
          const sdata = JSON.parse(JSON.stringify(res.data))
          const sidebarRoutes = filterAsyncRouter(sdata)
          this.setRoutes(sidebarRoutes)
          this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
          resolve(sidebarRoutes)
        })
      })
    }
  }
})

四、路由数据处理与组件映射

4.1 组件路径解析

RuoYi-Vue3使用动态导入机制将后端返回的组件路径映射为实际Vue组件:

// src/store/modules/permission.js
// 匹配views里面所有的.vue文件
const modules = import.meta.glob('./../../views/**/*.vue')

export const loadView = (view) => {
  let res
  for (const path in modules) {
    const dir = path.split('views/')[1].split('.vue')[0]
    if (dir === view) {
      res = () => modules[path]()
    }
  }
  return res
}

4.2 路由过滤与转换

// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
  return asyncRouterMap.filter(route => {
    if (route.component) {
      // Layout ParentView 组件特殊处理
      if (route.component === 'Layout') {
        route.component = Layout
      } else if (route.component === 'ParentView') {
        route.component = ParentView
      } else {
        route.component = loadView(route.component)
      }
    }
    if (route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children, route, type)
    } else {
      delete route['children']
      delete route['redirect']
    }
    return true
  })
}

五、权限控制与菜单显示逻辑

5.1 基于角色的权限控制

// 动态路由遍历,验证是否具备权限
export function filterDynamicRoutes(routes) {
  const res = []
  routes.forEach(route => {
    if (route.permissions) {
      if (auth.hasPermiOr(route.permissions)) {
        res.push(route)
      }
    } else if (route.roles) {
      if (auth.hasRoleOr(route.roles)) {
        res.push(route)
      }
    }
  })
  return res
}

5.2 权限判断插件

// src/plugins/auth.js
export default {
  // 验证权限
  hasPermi(value) {
    if (value && value instanceof Array && value.length > 0) {
      const permissions = store.getters && store.getters.permissions
      const all_permission = "*:*:*"
      if (permissions.includes(all_permission)) {
        return true
      }
      return value.some(per => {
        return permissions.includes(per)
      })
    } else {
      return false
    }
  },
  
  // 验证角色
  hasRole(value) {
    if (value && value instanceof Array && value.length > 0) {
      const roles = store.getters && store.getters.roles
      const super_admin = "admin"
      if (roles.includes(super_admin)) {
        return true
      }
      return value.some(role => {
        return roles.includes(role)
      })
    } else {
      return false
    }
  }
}

六、菜单渲染组件实现

6.1 Sidebar组件结构

<template>
  <div :class="{'has-logo':showLogo}">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar wrap-class="scrollbar-wrapper">
      <el-menu
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="variables.menuBg"
        :text-color="variables.menuText"
        :unique-opened="false"
        :active-text-color="variables.menuActiveText"
        :collapse-transition="false"
        mode="vertical"
      >
        <sidebar-item
          v-for="route in routes"
          :key="route.path"
          :item="route"
          :base-path="route.path"
        />
      </el-menu>
    </el-scrollbar>
  </div>
</template>

6.2 递归渲染菜单

<!-- SidebarItem.vue -->
<template>
  <div v-if="!item.hidden">
    <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren)">
      <app-link :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
          <span slot="title">{{ onlyOneChild.meta.title }}</span>
        </el-menu-item>
      </app-link>
    </template>

    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" :popper-append-to-body="false">
      <template slot="title">
        <svg-icon :icon-class="item.meta && item.meta.icon" />
        <span slot="title">{{ item.meta.title }}</span>
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :item="child"
        :base-path="resolvePath(child.path)"
        :is-nest="true"
      />
    </el-submenu>
  </div>
</template>

七、实战配置指南

7.1 数据库菜单配置

在数据库中配置菜单记录时,需要注意以下关键字段:

字段名说明示例值
path路由路径/system/user
component组件路径system/user/index
redirect重定向路径/system/user
title菜单显示文本用户管理
icon菜单图标user
roles访问角色["admin"]
permissions权限标识["system:user:list"]
parent_id父菜单ID1
order_num排序号1

7.2 前端菜单状态管理

// 存储菜单状态
setRoutes(routes) {
  this.addRoutes = routes
  this.routes = constantRoutes.concat(routes)
},
setSidebarRouters(routes) {
  this.sidebarRouters = routes
}

八、高级自定义与优化

8.1 动态菜单缓存策略

// 在permission.js中添加缓存逻辑
actions: {
  generateRoutes(roles) {
    return new Promise(resolve => {
      // 先检查本地缓存
      const cachedRoutes = localStorage.getItem('dynamicRoutes')
      if (cachedRoutes) {
        const routes = JSON.parse(cachedRoutes)
        this.setSidebarRouters(constantRoutes.concat(routes))
        resolve(routes)
      } else {
        // 缓存不存在则请求接口
        getRouters().then(res => {
          const routes = filterAsyncRouter(res.data)
          // 缓存路由数据
          localStorage.setItem('dynamicRoutes', JSON.stringify(routes))
          this.setSidebarRouters(constantRoutes.concat(routes))
          resolve(routes)
        })
      }
    })
  }
}

8.2 菜单显示隐藏控制

通过路由元信息的hidden属性控制菜单是否显示:

{
  path: 'profile',
  component: 'system/user/profile/index',
  name: 'Profile',
  hidden: true,
  meta: { 
    title: '个人中心', 
    icon: 'user' 
  }
}

九、常见问题解决方案

9.1 菜单不显示问题排查流程

mermaid

9.2 动态路由刷新404问题

// router/index.js
const router = createRouter({
  history: createWebHashHistory(),
  routes: constantRoutes
})

// 重置路由
export function resetRouter() {
  const newRouter = createRouter({
    history: createWebHashHistory(),
    routes: constantRoutes
  })
  router.matcher = newRouter.matcher // 重置路由匹配器
}

十、总结与展望

RuoYi-Vue3的动态菜单系统通过数据库驱动的方式,实现了菜单结构的灵活配置与权限的精细控制。核心在于通过后端接口动态生成路由配置,结合Vue的异步组件加载机制,实现了无需前端编码即可动态调整菜单结构。

未来,该机制可以进一步扩展,实现:

  • 菜单个性化布局
  • 拖拽式菜单排序
  • 多维度权限组合控制
  • 菜单操作审计日志

通过本文介绍的方案,开发者可以彻底告别硬编码菜单的时代,构建真正灵活可控的企业级管理系统菜单架构。

十一、互动与资源

如果本文对你有所帮助,请点赞、收藏、关注三连支持!下一期我们将深入探讨RuoYi-Vue3的权限控制细粒度优化,敬请期待。

欢迎在评论区分享你的动态菜单实现经验或遇到的问题,我们一起交流进步!

【免费下载链接】RuoYi-Vue3 :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统 【免费下载链接】RuoYi-Vue3 项目地址: https://gitcode.com/GitHub_Trending/ruo/RuoYi-Vue3

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值