menu.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. from sqlalchemy.orm import Session
  2. from app.models.menu import Menu
  3. from app.schemas.menu import MenuOut
  4. from app.services.base import CrudService
  5. class MenuService:
  6. @staticmethod
  7. def get_by_id(db: Session, id: int) -> Menu | None:
  8. return CrudService.get_by_id(Menu, db, id)
  9. @staticmethod
  10. def get_paged(db: Session, page: int, page_size: int, filters: list | None = None):
  11. return CrudService.get_paged(Menu, db, page, page_size, filters)
  12. @staticmethod
  13. def create(db: Session, obj_in: dict) -> Menu:
  14. return CrudService.create(Menu, db, obj_in)
  15. @staticmethod
  16. def update(db: Session, id: int, obj_in: dict) -> Menu | None:
  17. return CrudService.update(Menu, db, id, obj_in)
  18. @staticmethod
  19. def soft_delete(db: Session, id: int) -> bool:
  20. return CrudService.soft_delete(Menu, db, id)
  21. @staticmethod
  22. def get_menu_tree(db: Session) -> list[MenuOut]:
  23. """管理端菜单树。relationship.children 未带 del_flag,joinedload 会把已软删子节点一并查出;
  24. 这里只查 del_flag=0 的记录并组装为 MenuOut,避免界面删除成功仍显示旧数据,且不修改 ORM 以免 commit 误写。"""
  25. all_alive = (
  26. db.query(Menu)
  27. .filter(Menu.del_flag == 0)
  28. .order_by(Menu.sort_order)
  29. .all()
  30. )
  31. by_parent: dict[int, list[Menu]] = {}
  32. for m in all_alive:
  33. by_parent.setdefault(m.parent_id, []).append(m)
  34. def _to_out(m: Menu, children: list[MenuOut] | None) -> MenuOut:
  35. return MenuOut(
  36. id=m.id,
  37. parent_id=m.parent_id,
  38. menu_name=m.menu_name,
  39. menu_code=m.menu_code,
  40. menu_type=m.menu_type,
  41. path=m.path,
  42. sort_order=m.sort_order,
  43. status=m.status,
  44. children=children,
  45. )
  46. def _build(pid: int) -> list[MenuOut]:
  47. items = by_parent.get(pid, [])
  48. result: list[MenuOut] = []
  49. for m in items:
  50. kids = _build(m.id)
  51. result.append(_to_out(m, kids if kids else None))
  52. return result
  53. return _build(0)
  54. @staticmethod
  55. def get_menus_by_role(db: Session, role_id: int) -> list[Menu]:
  56. from app.models.role import Role
  57. role = db.query(Role).filter(Role.id == role_id, Role.del_flag == 0).first()
  58. if not role:
  59. return []
  60. return list(role.menus)
  61. @staticmethod
  62. def get_user_menu_tree(db: Session, user) -> list[MenuOut]:
  63. """根据用户角色构建可访问的菜单树"""
  64. # 收集用户所有角色关联的菜单 ID
  65. menu_ids: set[int] = set()
  66. for role in user.roles:
  67. for menu in role.menus:
  68. if menu.del_flag == 0 and menu.status == 1:
  69. menu_ids.add(menu.id)
  70. if not menu_ids:
  71. return []
  72. # 向上补齐祖先菜单,保证树结构路径完整
  73. all_ids = set(menu_ids)
  74. changed = True
  75. while changed:
  76. changed = False
  77. rows = db.query(Menu.parent_id).filter(
  78. Menu.id.in_(all_ids),
  79. Menu.parent_id != 0,
  80. Menu.del_flag == 0,
  81. ).all()
  82. for (pid,) in rows:
  83. if pid and pid not in all_ids:
  84. all_ids.add(pid)
  85. changed = True
  86. # 查询所有相关菜单
  87. menus = (
  88. db.query(Menu)
  89. .filter(Menu.id.in_(all_ids), Menu.del_flag == 0, Menu.status == 1)
  90. .order_by(Menu.sort_order)
  91. .all()
  92. )
  93. # 构建树形结构
  94. def _to_out(m: Menu, children: list[MenuOut] | None = None) -> MenuOut:
  95. return MenuOut(
  96. id=m.id,
  97. parent_id=m.parent_id,
  98. menu_name=m.menu_name,
  99. menu_code=m.menu_code,
  100. menu_type=m.menu_type,
  101. path=m.path,
  102. sort_order=m.sort_order,
  103. status=m.status,
  104. children=children,
  105. )
  106. # 按 parent_id 分组
  107. children_map: dict[int, list[Menu]] = {}
  108. for m in menus:
  109. children_map.setdefault(m.parent_id, []).append(m)
  110. def _build(pid: int) -> list[MenuOut]:
  111. items = children_map.get(pid, [])
  112. return [_to_out(m, _build(m.id)) for m in items]
  113. return _build(0)