Files
djangoadmin-wiki/DjangoAdmin_FastAPI_AntdVue/zh/volume4/business/menu.md
T
2026-09-12 23:12:15 +08:00

174 lines
6.9 KiB
Markdown
Raw 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.
:::tip 菜单管理
菜单管理是 RBAC 权限系统的基础,负责管理系统的导航菜单和权限节点。支持目录/菜单(type=0)和权限按钮节点(type=1)两种类型,通过树形结构组织。权限节点由系统根据前端勾选自动生成,采用 `sys:module:action` 三段式命名。
:::
## 菜单类型
| type | 名称 | 说明 | 用途 |
|------|------|------|------|
| `0` | 目录/菜单 | 页面路由 | 前端侧边栏分组 + 路由生成 |
| `1` | 节点 | 权限按钮 | 控制操作权限(由系统自动生成) |
## 菜单模型
```python
# src/modules/system/menu/models.py
# ============================================================
# 菜单模型
# ============================================================
class Menu(base_model, base_db):
"""菜单模型类"""
# ============================================================
# 表名配置
# ============================================================
__tablename__ = DB_PREFIX + "menu"
__table_comment__ = "菜单表"
# ============================================================
# 字段定义
# ============================================================
# 菜单名称
name = Column(String(150), nullable=False, index=True, comment="菜单名称")
# 菜单图标
icon = Column(String(50), nullable=True, comment="菜单图标")
# 菜单图标(备用)
icon2 = Column(String(50), nullable=True, comment="菜单图标")
# 上级ID
parent_id = Column(Integer, default=0, server_default=text('0'), index=True, comment="上级ID")
# 菜单路径
path = Column(String(255), nullable=True, comment="菜单路径")
# 菜单组件
component = Column(String(255), nullable=True, comment="菜单组件")
# 权限节点
permission = Column(String(150), nullable=True, comment="权限节点")
# 打开方式:0-组件 1-内链 2-外链
target = Column(Integer, default=1, server_default=text('1'), comment="打开方式:0-组件 1-内链 2-外链")
# 菜单类型:0-菜单 1-节点
type = Column(Integer, default=0, server_default=text('0'), index=True, comment="菜单类型:0-菜单 1-节点")
# 状态选项
status = Column(Integer, default=0, server_default=text('0'), index=True, comment="菜单状态:0-正常 1-停用")
# 是否可见:0-可见 1-不可见
hide = Column(Integer, default=1, server_default=text('1'), comment="是否可见:0-可见 1-不可见")
# 排序
sort = Column(Integer, default=0, server_default=text('0'), comment="排序")
# 备注
note = Column(String(255), nullable=True, comment="备注")
# ============================================================
# 内置方法
# ============================================================
def __str__(self):
"""返回菜单ID作为字符串表示"""
return "菜单{}".format(self.id)
```
## 权限节点自动生成
当菜单类型为目录(type=0)且勾选了权限按钮时,系统自动在该菜单下生成 type=1 的权限节点。按钮 sort 值与权限操作的映射关系:
```python
# src/modules/system/menu/service.py
_BUTTON_SORT_VALUES = (1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65)
# sort 值与权限操作的映射
1 → 查询(sys:{module}:page)
5 → 添加(sys:{module}:add)
10 → 修改(sys:{module}:update)
15 → 删除(sys:{module}:delete)
20 → 详情(sys:{module}:detail)
25 → 设置状态(sys:{module}:status)
30 → 批量删除(sys:{module}:batchDelete)
50 → 导出(sys:{module}:export)
55 → 导入(sys:{module}:import)
60 → 分配权限(sys:{module}:permission)
65 → 重置密码(sys:{module}:resetPwd)
```
:::tip 权限节点生命周期
新增/编辑菜单时,`set_permission` 函数根据前端传入的 `checkedList` 数组,复用已有节点(保持 id 稳定,role_menu 授权不失效)或新建节点。本次未勾选的历史节点自动软删。删除菜单时,其下所有 type=1 权限节点一并软删,并清理 `role_menu` 关联。
:::
## 菜单树构建
使用 children_map 字典实现非递归树构建,避免深层递归性能问题:
```python
# src/modules/system/menu/service.py
def get_tree(data, parent_id):
"""获取树状结构(非递归,children_map 分组)"""
children_map = {}
for item in data:
pid = item["parentId"]
if pid not in children_map:
children_map[pid] = []
children_map[pid].append(item)
def build(pid):
result = []
for item in children_map.get(pid, []):
item["children"] = build(item["id"])
result.append(item)
return result
return build(parent_id)
```
## 用户权限查询
权限查询通过 ORM join 实现,仅有效角色(未删除、未禁用)的权限生效:
```python
# src/modules/system/menu/service.py
def get_permissions_list_sync(user_id):
"""同步查询用户权限节点"""
if user_id == 1:
menu_list = menu_repo.get_all(type=1) # 超管拥有全部权限
else:
menu_list = (
menu_repo.filter()
.join(RoleMenu, RoleMenu.menu_id == Menu.id)
.join(UserRole, UserRole.role_id == RoleMenu.role_id)
.join(Role, Role.id == UserRole.role_id)
.filter(
and_(
UserRole.user_id == user_id,
or_(Menu.type == 1, and_(Menu.type == 0, Menu.permission != "")),
Menu.status == 0,
Menu.is_delete == 0,
Role.is_delete == 0,
)
)
.distinct()
.all()
)
return [item.permission for item in (menu_list or [])]
```
## API 接口
| 接口 | 方法 | 权限节点 | 说明 |
|------|------|---------|------|
| `/api/v1/menu/list` | GET | `sys:menu:list` | 菜单列表 |
| `/api/v1/menu/detail/{id}` | GET | `sys:menu:detail` | 菜单详情(含权限节点 checkedList) |
| `/api/v1/menu/add` | POST | `sys:menu:add` | 新增菜单 |
| `/api/v1/menu/update` | PUT | `sys:menu:update` | 编辑菜单 |
| `/api/v1/menu/delete/{id}` | DELETE | `sys:menu:delete` | 删除菜单 |
:::warning 父级校验
编辑菜单时会校验父级:不能设为自身、不能设为自己的下级(沿父链向上走检测环路),避免菜单树成环导致渲染死循环。
:::
## 总结
菜单管理模块具备以下特点:
```
1. 树形结构:通过 parent_id 形成多级菜单树,非递归构建
2. 权限节点:type=1 按钮节点由 set_permission 自动生成和维护
3. 权限格式:sys:module:action 三段式,sort 值映射操作类型
4. 缓存联动:菜单增删改后通过 invalidate_menu_change 清理全量权限缓存
5. 环路防护:编辑时检测父级不能是自身或下级
6. 级联删除:删除菜单时一并软删其下权限节点和 role_menu 关联
```