library-picturebook-activity/docs/DYNAMIC_MENU_GUIDE.md
2025-12-09 11:10:36 +08:00

281 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 动态菜单系统实现指南
## 📋 概述
已成功将菜单管理模块扩展为**完全动态化的菜单生成系统**。系统现在支持:
- ✅ 从数据库动态加载菜单
- ✅ 根据用户权限自动过滤菜单
- ✅ 动态生成路由配置
- ✅ 动态生成侧边栏菜单
- ✅ 支持多级菜单结构
- ✅ 菜单级别的权限控制
## 🏗️ 架构设计
### 后端架构
```
数据库 (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. **菜单拖拽排序**: 在管理界面支持拖拽排序