# 动态菜单系统实现指南 ## 📋 概述 已成功将菜单管理模块扩展为**完全动态化的菜单生成系统**。系统现在支持: - ✅ 从数据库动态加载菜单 - ✅ 根据用户权限自动过滤菜单 - ✅ 动态生成路由配置 - ✅ 动态生成侧边栏菜单 - ✅ 支持多级菜单结构 - ✅ 菜单级别的权限控制 ## 🏗️ 架构设计 ### 后端架构 ``` 数据库 (Menu表) ↓ MenusService.findUserMenus() ↓ 根据用户权限过滤菜单 ↓ 返回树形菜单结构 ``` ### 前端架构 ``` 用户登录 ↓ 获取用户菜单 (API) ↓ 存储到 Auth Store ↓ 转换为路由配置 (convertMenusToRoutes) ↓ 动态注册路由 (router.addRoute) ↓ 转换为菜单项 (convertMenusToMenuItems) ↓ 渲染到侧边栏 ``` ## 🔧 实现细节 ### 1. 数据库 Schema 更新 在 `Menu` 模型中添加了 `permission` 字段: ```prisma model Menu { // ... 其他字段 permission String? /// 权限编码(用于控制菜单显示,如:menu:read) // ... 其他字段 } ``` **需要运行数据库迁移:** ```bash cd backend npx prisma migrate dev --name add_menu_permission ``` ### 2. 后端 API #### 新增接口:`GET /menus/user-menus` 获取当前用户的菜单(根据权限自动过滤) **响应示例:** ```json [ { "id": 1, "name": "系统管理", "path": "/system", "icon": "SettingOutlined", "permission": null, "children": [ { "id": 2, "name": "用户管理", "path": "/system/users", "component": "system/users/Index", "permission": "user:read" } ] } ] ``` ### 3. 前端工具函数 创建了 `frontend/src/utils/menu.ts`,包含: - `convertMenusToMenuItems()`: 将数据库菜单转换为 Ant Design Vue Menu 格式 - `convertMenusToRoutes()`: 将数据库菜单转换为 Vue Router 路由配置 - `getIconComponent()`: 动态加载图标组件 - `flattenMenus()`: 扁平化菜单树 ### 4. Auth Store 扩展 在 `auth.ts` 中添加了: - `menus`: 存储用户菜单数据 - `fetchUserMenus()`: 获取用户菜单 - 登录时自动获取菜单 - 登出时清空菜单 ### 5. 路由动态注册 在 `router/index.ts` 中: - 基础路由(登录页、404等)保持静态 - 业务路由从数据库动态加载 - 登录后自动注册动态路由 - 支持路由权限检查 ### 6. 布局组件更新 `BasicLayout.vue` 现在: - 从 `authStore.menus` 读取菜单数据 - 使用 `convertMenusToMenuItems()` 转换菜单 - 自动展开包含当前路径的父菜单 ## 📝 使用方法 ### 1. 创建菜单 在菜单管理界面创建菜单时,需要填写: - **菜单名称**: 显示名称 - **路由路径**: 如 `/system/users` - **图标**: Ant Design Icons 名称,如 `UserOutlined` - **组件路径**: Vue 组件路径,如 `system/users/Index`(相对于 `@/views/`) - **权限编码**: 可选,如 `user:read`(留空则所有用户可见) - **父菜单**: 可选,用于创建多级菜单 - **排序**: 数字,控制显示顺序 ### 2. 权限控制 #### 菜单级别权限 在菜单的"权限编码"字段设置权限码,如: - `user:read` - 需要用户查看权限 - `role:create` - 需要角色创建权限 - 留空 - 所有用户可见 #### 路由级别权限 菜单的权限会自动添加到路由的 `meta.permissions` 中,路由守卫会自动检查。 ### 3. 组件路径规则 组件路径应该相对于 `frontend/src/views/` 目录: - ✅ `system/users/Index` → `@/views/system/users/Index.vue` - ✅ `dashboard/Index` → `@/views/dashboard/Index.vue` - ❌ `@/views/system/users/Index` (不需要 `@/views/` 前缀) ## 🔄 工作流程 ### 用户登录流程 ``` 1. 用户输入账号密码 ↓ 2. 调用 login API ↓ 3. 获取 token 和用户信息 ↓ 4. 调用 fetchUserMenus() 获取菜单 ↓ 5. 菜单数据存储到 authStore.menus ↓ 6. 动态路由注册 (addDynamicRoutes) ↓ 7. 跳转到首页 ``` ### 页面访问流程 ``` 1. 用户访问 /system/users ↓ 2. 路由守卫检查认证 ↓ 3. 检查路由权限 (meta.permissions) ↓ 4. 如果通过,渲染组件 ↓ 5. BasicLayout 显示侧边栏菜单 ``` ## 🎯 示例场景 ### 场景1: 创建带权限的菜单 1. 进入"菜单管理" 2. 点击"新增菜单" 3. 填写: - 名称: 用户管理 - 路径: `/system/users` - 图标: `UserOutlined` - 组件路径: `system/users/Index` - 权限编码: `user:read` 4. 保存 结果:只有拥有 `user:read` 权限的用户才能看到此菜单。 ### 场景2: 创建多级菜单 1. 创建父菜单: - 名称: 系统管理 - 路径: `/system` - 图标: `SettingOutlined` - (不填组件路径和权限编码) 2. 创建子菜单: - 名称: 用户管理 - 路径: `/system/users` - 父菜单: 选择"系统管理" - 组件路径: `system/users/Index` 结果:侧边栏显示为: ``` 系统管理 └─ 用户管理 ``` ## ⚠️ 注意事项 1. **数据库迁移**: 添加 `permission` 字段后,需要运行 Prisma 迁移 2. **组件路径**: 确保组件文件存在,否则路由会失败 3. **权限编码**: 必须与权限系统中定义的权限码一致 4. **路由名称**: 自动生成,基于路径(如 `/system/users` → `SystemUsers`) 5. **动态路由**: 只在登录后添加一次,刷新页面不会重复添加 ## 🐛 故障排除 ### 菜单不显示 1. 检查菜单的 `validState` 是否为 1(有效) 2. 检查用户是否有菜单对应的权限 3. 检查浏览器控制台是否有错误 ### 路由404 1. 检查组件路径是否正确 2. 检查组件文件是否存在 3. 检查路由是否已动态注册(查看 Vue DevTools) ### 图标不显示 1. 检查图标名称是否正确(Ant Design Icons) 2. 检查图标是否已导入到项目中 ## 📚 相关文件 - 后端菜单服务: `backend/src/menus/menus.service.ts` - 后端菜单控制器: `backend/src/menus/menus.controller.ts` - 前端菜单工具: `frontend/src/utils/menu.ts` - 前端路由配置: `frontend/src/router/index.ts` - 前端布局组件: `frontend/src/layouts/BasicLayout.vue` - 前端菜单管理: `frontend/src/views/system/menus/Index.vue` ## 🚀 下一步优化建议 1. **菜单缓存**: 可以缓存菜单数据,减少API调用 2. **菜单刷新**: 提供手动刷新菜单的功能 3. **菜单搜索**: 在侧边栏添加菜单搜索功能 4. **菜单收藏**: 允许用户收藏常用菜单 5. **菜单拖拽排序**: 在管理界面支持拖拽排序