dromara/plus-ui菜单配置系统深度解析

dromara/plus-ui菜单配置系统深度解析

【免费下载链接】plus-ui RuoYi-Vue-Plus 5.X 与 RuoYi-Cloud-Plus 2.X 统一 UI 前端代码仓库 问题请到主框架反馈 【免费下载链接】plus-ui 项目地址: https://gitcode.com/dromara/plus-ui

前言:企业级权限管理的核心枢纽

在企业级应用开发中,菜单权限管理往往是系统架构的核心难点。你是否曾遇到过这样的困境:

  • 菜单层级复杂,难以维护清晰的权限结构
  • 动态路由加载性能瓶颈,影响用户体验
  • 多类型菜单(目录、菜单、按钮)统一管理困难
  • 权限控制与菜单显示逻辑耦合度过高

dromara/plus-ui 的菜单配置系统正是为解决这些痛点而生,它提供了一套完整、灵活且高性能的菜单管理解决方案。本文将深入解析这套系统的设计理念、核心实现和最佳实践。

系统架构概览

菜单类型体系

dromara/plus-ui 采用三级菜单类型体系,每种类型都有明确的职责划分:

export enum MenuTypeEnum {
  /**
   * 目录 - 用于组织菜单的容器
   */
  M = 'M',
  
  /**
   * 菜单 - 具体的功能页面
   */
  C = 'C',

  /**
   * 按钮 - 页面内的操作权限
   */
  F = 'F'
}

核心组件关系图

mermaid

核心实现机制

1. 动态路由加载系统

系统采用异步路由加载机制,根据用户权限动态生成路由结构:

// 权限存储管理
export const usePermissionStore = defineStore('permission', () => {
  const routes = ref<RouteRecordRaw[]>([]);
  const addRoutes = ref<RouteRecordRaw[]>([]);
  const defaultRoutes = ref<RouteRecordRaw[]>([]);
  const topbarRouters = ref<RouteRecordRaw[]>([]);
  const sidebarRouters = ref<RouteRecordRaw[]>([]);

  // 生成动态路由
  const generateRoutes = async (): Promise<RouteRecordRaw[]> => {
    const res = await getRouters();
    const { data } = res;
    const sdata = JSON.parse(JSON.stringify(data));
    const rdata = JSON.parse(JSON.stringify(data));
    const defaultData = JSON.parse(JSON.stringify(data));
    
    const sidebarRoutes = filterAsyncRouter(sdata);
    const rewriteRoutes = filterAsyncRouter(rdata, undefined, true);
    const defaultRoutes = filterAsyncRouter(defaultData);
    
    setRoutes(rewriteRoutes);
    setSidebarRouters(constantRoutes.concat(sidebarRoutes));
    setDefaultRoutes(sidebarRoutes);
    setTopbarRoutes(defaultRoutes);
    
    return new Promise<RouteRecordRaw[]>((resolve) => resolve(rewriteRoutes));
  };
});

2. 路由过滤与组件映射

系统通过智能的路由过滤机制,将后端返回的菜单数据转换为前端可用的路由配置:

const filterAsyncRouter = (asyncRouterMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw, type = false): RouteRecordRaw[] => {
  return asyncRouterMap.filter((route) => {
    // Layout组件特殊处理
    if (route.component?.toString() === 'Layout') {
      route.component = Layout;
    } else if (route.component?.toString() === 'ParentView') {
      route.component = ParentView;
    } else if (route.component?.toString() === 'InnerLink') {
      route.component = InnerLink;
    } else {
      route.component = loadView(route.component, route.name as string);
    }
    
    // 递归处理子路由
    if (route.children != null && route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children, route, type);
    } else {
      delete route.children;
      delete route.redirect;
    }
    return true;
  });
};

3. 视图组件动态加载

系统支持按需加载视图组件,显著提升应用性能:

export const loadView = (view: any, name: string) => {
  let res;
  for (const path in modules) {
    const viewsIndex = path.indexOf('/views/');
    let dir = path.substring(viewsIndex + 7);
    dir = dir.substring(0, dir.lastIndexOf('.vue'));
    if (dir === view) {
      res = createCustomNameComponent(modules[path], { name });
      return res;
    }
  }
  return res;
};

菜单配置详解

菜单属性配置表

属性名类型必填说明示例
menuNamestring菜单显示名称用户管理
menuTypeMenuTypeEnum菜单类型(M/C/F)C
parentIdnumber父菜单ID0
pathstring路由路径system/user
componentstring条件必填组件路径system/user/index
iconstring图标名称user
orderNumnumber显示顺序1
isFramestring是否外链(0/1)1
isCachestring是否缓存(0/1)0
visiblestring显示状态(0/1)0
statusstring启用状态(0/1)0
permsstring权限标识system:user:list

路由配置规则说明

mermaid

权限控制机制

1. 路由级别权限控制

系统支持基于角色和权限的双重控制机制:

export const filterDynamicRoutes = (routes: RouteRecordRaw[]) => {
  const res: RouteRecordRaw[] = [];
  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;
};

2. 组件级别权限控制

通过自定义指令实现细粒度的权限控制:

<template>
  <el-button v-hasPermi="['system:user:add']" @click="handleAdd">
    新增用户
  </el-button>
</template>

高级功能特性

1. 级联删除机制

系统提供安全的级联删除功能,防止误操作导致的数据不一致:

const handleCascadeDelete = () => {
  menuTreeRef.value?.setCheckedKeys([]);
  getTreeselect();
  deleteDialog.visible = true;
};

const submitDeleteForm = async () => {
  const menuIds = menuTreeRef.value?.getCheckedKeys();
  if (menuIds.length < 0) {
    proxy?.$modal.msgWarning('请选择要删除的菜单');
    return;
  }
  await cascadeDelMenu(menuIds);
};

2. 懒加载树形结构

优化大型菜单树的性能表现:

const getChildrenList = async (row: any, treeNode: unknown, resolve: (data: any[]) => void) => {
  menuExpandMap.value[row.menuId] = { row, treeNode, resolve };
  const children = menuChildrenListMap.value[row.menuId] || [];
  resolve(children);
};

3. 路由重复检查

防止路由配置冲突导致的404问题:

function duplicateRouteChecker(localRoutes: Route[], routes: Route[]) {
  const allRoutes = flatRoutes([...localRoutes, ...routes]);
  const nameList: string[] = [];
  
  allRoutes.forEach((route) => {
    const name = route.name.toString();
    if (name && nameList.includes(name)) {
      console.error(`路由名称: [${name}] 重复, 会造成 404`);
      return;
    }
    nameList.push(route.name.toString());
  });
}

最佳实践指南

1. 菜单命名规范

菜单类型命名规范示例
目录使用名词,描述功能模块系统管理
菜单动词+名词,描述具体操作用户列表
按钮动词,描述操作动作新增、编辑、删除

2. 权限标识设计原则

mermaid

3. 性能优化建议

  1. 分页加载:当菜单数量超过1000时,建议启用分页加载
  2. 图标优化:使用SVG图标,避免字体图标带来的性能开销
  3. 缓存策略:合理使用路由缓存,提升重复访问性能
  4. 懒加载:对深层级菜单使用懒加载机制

常见问题解决方案

问题1:菜单显示异常

症状:菜单在侧边栏不显示或显示错乱

解决方案

// 检查菜单配置
const checkMenuConfig = (menu: MenuForm) => {
  if (menu.menuType === 'C' && !menu.component) {
    throw new Error('菜单类型必须配置组件路径');
  }
  if (menu.visible === '1' && menu.status === '0') {
    console.warn('菜单已隐藏但状态为启用,可能无法正常显示');
  }
};

问题2:权限控制失效

症状:用户可以看到但没有权限操作的菜单

解决方案

// 双重权限验证
const validatePermission = (route: RouteRecordRaw) => {
  const hasRoutePermission = !route.permissions || auth.hasPermiOr(route.permissions);
  const hasRolePermission = !route.roles || auth.hasRoleOr(route.roles);
  return hasRoutePermission && hasRolePermission;
};

问题3:路由重复冲突

症状:页面出现404错误或路由跳转异常

解决方案

// 路由名称唯一性检查
const ensureUniqueRouteNames = (routes: RouteRecordRaw[]) => {
  const nameSet = new Set();
  routes.forEach(route => {
    if (route.name && nameSet.has(route.name)) {
      console.error(`路由名称重复: ${route.name}`);
    }
    nameSet.add(route.name);
  });
};

总结与展望

dromara/plus-ui 的菜单配置系统通过精心的架构设计和丰富的功能特性,为企业级应用提供了强大的权限管理能力。系统的主要优势包括:

  1. 灵活的权限控制:支持角色和权限双重验证机制
  2. 高性能动态加载:异步路由加载,优化用户体验
  3. 完整的类型支持:目录、菜单、按钮三级权限体系
  4. 安全的数据管理:级联删除和重复检查机制
  5. 易用的管理界面:直观的可视化配置界面

未来,该系统还可以进一步扩展以下功能:

  • 菜单版本管理和回滚功能
  • 菜单使用情况统计分析
  • 多语言菜单支持
  • 菜单导入导出模板

通过深入理解和合理运用这套菜单配置系统,开发者可以构建出更加安全、稳定、易用的企业级应用系统。

【免费下载链接】plus-ui RuoYi-Vue-Plus 5.X 与 RuoYi-Cloud-Plus 2.X 统一 UI 前端代码仓库 问题请到主框架反馈 【免费下载链接】plus-ui 项目地址: https://gitcode.com/dromara/plus-ui

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

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

抵扣说明:

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

余额充值