You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
4.8 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import copy
from apiflask import APIBlueprint
from flask_jwt_extended import jwt_required
from iti.applications.common.exceptions.biz_exp import BizException
from iti.applications.extensions import db
from iti.applications.models import SysMenu, sys_role_menu
from iti.applications.common.utils import success
from iti.applications.routes.sys.schemas.menu import (
MenuCreateRequest,
MenuUpdateRequest,
MenuExistsRequest,
)
from iti.applications.service.sys_menu import get_menu_tree
from iti.applications.models import SysMenuSchema
from iti.applications.common.enums import MenuTypeEnum
from sqlalchemy import select, delete, func, exists
from iti.applications.common.events import MenuEvents
from iti.applications.extensions import eventbus
from iti.applications.common import permission
bp = APIBlueprint("sys_menu", __name__, url_prefix="/menu", tag="系统.菜单管理")
@bp.get("/list")
@jwt_required()
@bp.output(SysMenuSchema(many=True))
def get_menu_list():
"""
获取菜单树列表
"""
return success(get_menu_tree())
@bp.get("/tree")
@jwt_required()
@bp.output(SysMenuSchema(many=True))
def get_menu_tree_api():
"""
获取菜单树列表接口
过滤条件:
- 状态为启用
- 类型为非按钮
"""
return success(
get_menu_tree(
type_filter=[
MenuTypeEnum.MENU,
MenuTypeEnum.CATALOG,
MenuTypeEnum.EMBEDDED,
MenuTypeEnum.LINK,
]
)
)
@bp.post("")
@jwt_required()
@permission("system:menu:create")
@bp.input(MenuCreateRequest, location="json")
def create_menu(json_data: dict):
"""
创建菜单
"""
menu = SysMenu(**json_data)
# 基于同级菜单的最大排序计算出新的排序并更新到menu的sort字段和meta的order字段
max_sort = db.session.scalar(
select(func.max(SysMenu.sort)).filter_by(parent_id=menu.parent_id)
)
if max_sort is not None:
menu.sort = max_sort + 1
else:
menu.sort = 0
menu.meta["order"] = menu.sort
db.session.add(menu)
db.session.commit()
return success()
@bp.put("/<string:id>")
@jwt_required()
@permission("system:menu:edit")
@bp.input(MenuUpdateRequest(partial=True), location="json")
def update_menu(id: str, json_data: dict):
"""
更新菜单
"""
menu = db.session.scalar(select(SysMenu).filter_by(id=id))
if not menu:
raise BizException("菜单不存在")
old_menu = copy.deepcopy(menu)
for key, value in json_data.items():
if value is not None:
setattr(menu, key, value)
# 提交事务
db.session.commit()
# 触发菜单事件
eventbus.emit(MenuEvents.MENU_UPDATED.value, menu, old_menu)
return success()
@bp.delete("/<string:id>")
@jwt_required()
@permission("system:menu:delete")
def delete_menu(id: str):
"""
删除菜单
"""
menu = db.session.scalar(select(SysMenu).filter_by(id=id))
if not menu:
raise BizException("菜单不存在")
try:
# 删除菜单,需要同时删除 菜单-角色 关联关系
db.session.execute(delete(sys_role_menu).filter_by(menu_id=id))
# 删除菜单
db.session.execute(menu)
# 提交事务
db.session.commit()
# 触发菜单删除事件
eventbus.emit(MenuEvents.MENU_DELETED.value, menu)
except Exception as e:
db.session.rollback()
raise BizException(f"删除菜单失败: {str(e)}")
return success()
@bp.get("/exists")
@jwt_required()
@bp.input(MenuExistsRequest, location="query")
def menu_exists(query_data: dict):
"""
检查菜单的 path 或 name 是否已被其他记录使用(重复检查)
return: bool
- True: 存在重复(冲突)
- False: 不存在重复(可用)
使用场景:
- 创建菜单:检查 path/name 是否已存在
- 更新菜单:检查其他记录是否使用了相同的 path/name排除自己
"""
path = query_data.get("path")
name = query_data.get("name")
exclude_id = query_data.get("id")
# 优先检查 path
if path:
# 构建查询条件path 相同,但排除自己(如果提供了 id
conditions = [SysMenu.path == path]
if exclude_id:
conditions.append(SysMenu.id != exclude_id)
ret = db.session.scalar(select(exists().where(*conditions)))
return success(ret)
# 检查 name
if name:
# 构建查询条件name 相同,但排除自己(如果提供了 id
conditions = [SysMenu.name == name]
if exclude_id:
conditions.append(SysMenu.id != exclude_id)
ret = db.session.scalar(select(exists().where(*conditions)))
return success(ret)
# 没有提供检查字段,返回 False
return success(False)