menu.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. from __future__ import annotations
  2. from datetime import datetime
  3. from typing import Any, Dict, List, Optional
  4. from sqlalchemy import and_
  5. from sqlalchemy.orm import Session
  6. from app.core.utils import generate_uuid
  7. from app.models.models import SysMenu
  8. def _row_to_dict(m: SysMenu) -> Dict[str, Any]:
  9. return {
  10. "id": m.id,
  11. "menuName": m.menu_name,
  12. "parentId": m.parent_id,
  13. "menuType": m.menu_type or "M",
  14. "path": m.path or "",
  15. "component": m.component or "",
  16. "perms": m.perms or "",
  17. "isFrame": m.is_frame if m.is_frame is not None else 1,
  18. "visible": m.visible or "1",
  19. "status": m.status or "1",
  20. "tenantNo": m.tenant_no,
  21. "orderNum": m.order_num if m.order_num is not None else 0,
  22. "remark": m.remark or "",
  23. "createTime": m.create_time.strftime("%Y-%m-%d %H:%M:%S") if m.create_time else "",
  24. "updateTime": m.update_time.strftime("%Y-%m-%d %H:%M:%S") if m.update_time else "",
  25. }
  26. def _build_tree(items: List[Dict[str, Any]], parent_id: str = "0") -> List[Dict[str, Any]]:
  27. children = [item for item in items if item.get("parentId") == parent_id]
  28. for child in children:
  29. child["children"] = _build_tree(items, child["id"])
  30. children.sort(key=lambda x: (x.get("orderNum", 0) or 0))
  31. return children
  32. def list_menus(
  33. db: Session,
  34. *,
  35. keyword: Optional[str] = None,
  36. tenant_no: Optional[str] = None,
  37. user_info=None,
  38. ) -> List[Dict[str, Any]]:
  39. q = db.query(SysMenu).filter(SysMenu.del_flag == 0)
  40. if tenant_no:
  41. q = q.filter(SysMenu.tenant_no == tenant_no)
  42. kw = (keyword or "").strip()
  43. if kw:
  44. like = f"%{kw}%"
  45. q = q.filter(SysMenu.menu_name.like(like))
  46. if user_info:
  47. from app.core.data_scope import apply_data_scope
  48. q = apply_data_scope(q, SysMenu, user_info, db, tenant_no)
  49. rows = q.order_by(SysMenu.create_time.desc()).all()
  50. return [_row_to_dict(x) for x in rows]
  51. def list_menus_tree(
  52. db: Session,
  53. *,
  54. keyword: Optional[str] = None,
  55. tenant_no: Optional[str] = None,
  56. user_info=None,
  57. ) -> List[Dict[str, Any]]:
  58. kw = (keyword or "").strip()
  59. # 关键词检索时:仅返回命中节点会导致其父节点缺失,从而无法组装为以 parentId=0 开始的树。
  60. # 这里改为:命中节点 + 其所有祖先节点,再组树。
  61. if not kw:
  62. flat = list_menus(db, keyword=None, tenant_no=tenant_no, user_info=user_info)
  63. return _build_tree(flat, "0")
  64. all_items = list_menus(db, keyword=None, tenant_no=tenant_no, user_info=user_info)
  65. id_map: Dict[str, Dict[str, Any]] = {x.get("id"): x for x in all_items if x.get("id")}
  66. like_items = [
  67. x for x in all_items if kw and kw in (x.get("menuName") or "")
  68. ]
  69. picked: Dict[str, Dict[str, Any]] = {}
  70. def pick_with_ancestors(item_id: str) -> None:
  71. cur = id_map.get(item_id)
  72. while cur and cur.get("id") and cur.get("id") not in picked:
  73. picked[cur["id"]] = cur
  74. parent_id = (cur.get("parentId") or "0").strip()
  75. if not parent_id or parent_id == "0":
  76. break
  77. cur = id_map.get(parent_id)
  78. for it in like_items:
  79. if it.get("id"):
  80. pick_with_ancestors(it["id"])
  81. return _build_tree(list(picked.values()), "0")
  82. def get_menu(db: Session, menu_id: str) -> Optional[SysMenu]:
  83. return (
  84. db.query(SysMenu)
  85. .filter(and_(SysMenu.id == menu_id, SysMenu.del_flag == 0))
  86. .first()
  87. )
  88. def menu_name_exists(
  89. db: Session,
  90. name: str,
  91. tenant_no: str,
  92. exclude_id: Optional[str] = None,
  93. ) -> bool:
  94. q = db.query(SysMenu).filter(
  95. and_(SysMenu.menu_name == name, SysMenu.tenant_no == tenant_no, SysMenu.del_flag == 0)
  96. )
  97. if exclude_id:
  98. q = q.filter(SysMenu.id != exclude_id)
  99. return q.first() is not None
  100. def create_menu(
  101. db: Session,
  102. *,
  103. menu_name: str,
  104. parent_id: str = "0",
  105. menu_type: str = "M",
  106. path: str = "",
  107. component: Optional[str] = None,
  108. perms: Optional[str] = None,
  109. is_frame: int = 1,
  110. visible: str = "1",
  111. status: str = "1",
  112. tenant_no: Optional[str] = None,
  113. order_num: Optional[int] = None,
  114. remark: Optional[str] = None,
  115. user_id: Optional[str] = None,
  116. ) -> SysMenu:
  117. name = (menu_name or "").strip()
  118. if not name:
  119. raise ValueError("菜单名称不能为空")
  120. obj = SysMenu(
  121. id=generate_uuid(),
  122. menu_name=name,
  123. parent_id=(parent_id or "0").strip(),
  124. menu_type=menu_type or "M",
  125. path=(path or "").strip(),
  126. component=(component or "").strip() or None,
  127. perms=(perms or "").strip() or None,
  128. is_frame=is_frame if is_frame is not None else 1,
  129. visible=visible or "1",
  130. status=status or "1",
  131. tenant_no=tenant_no or "zkxw",
  132. order_num=order_num if order_num is not None else 0,
  133. remark=(remark or "").strip() or None,
  134. create_by=user_id,
  135. create_dept_id="",
  136. create_time=datetime.now(),
  137. update_by=user_id,
  138. update_time=datetime.now(),
  139. del_flag=0,
  140. )
  141. db.add(obj)
  142. db.commit()
  143. db.refresh(obj)
  144. return obj
  145. def update_menu(
  146. db: Session,
  147. menu_id: str,
  148. *,
  149. menu_name: Optional[str] = None,
  150. parent_id: Optional[str] = None,
  151. menu_type: Optional[str] = None,
  152. path: Optional[str] = None,
  153. component: Optional[str] = None,
  154. perms: Optional[str] = None,
  155. is_frame: Optional[int] = None,
  156. visible: Optional[str] = None,
  157. status: Optional[str] = None,
  158. order_num: Optional[int] = None,
  159. remark: Optional[str] = None,
  160. user_id: Optional[str] = None,
  161. ) -> SysMenu:
  162. obj = get_menu(db, menu_id)
  163. if not obj:
  164. raise ValueError("菜单不存在")
  165. if menu_name is not None:
  166. obj.menu_name = (menu_name or "").strip() or obj.menu_name
  167. if parent_id is not None:
  168. obj.parent_id = (parent_id or "0").strip()
  169. if menu_type is not None:
  170. obj.menu_type = menu_type
  171. if path is not None:
  172. obj.path = (path or "").strip()
  173. if component is not None:
  174. obj.component = (component or "").strip() or None
  175. if perms is not None:
  176. obj.perms = (perms or "").strip() or None
  177. if is_frame is not None:
  178. obj.is_frame = is_frame
  179. if visible is not None:
  180. obj.visible = visible
  181. if status is not None:
  182. obj.status = status
  183. if order_num is not None:
  184. obj.order_num = order_num
  185. if remark is not None:
  186. obj.remark = (remark or "").strip() or None
  187. obj.update_by = user_id
  188. obj.update_time = datetime.now()
  189. db.add(obj)
  190. db.commit()
  191. db.refresh(obj)
  192. return obj
  193. def _cascade_soft_delete(db: Session, menu_id: str) -> None:
  194. children = (
  195. db.query(SysMenu)
  196. .filter(and_(SysMenu.parent_id == menu_id, SysMenu.del_flag == 0))
  197. .all()
  198. )
  199. for child in children:
  200. _cascade_soft_delete(db, child.id)
  201. child.del_flag = 1
  202. child.update_time = datetime.now()
  203. db.add(child)
  204. def soft_delete_menu(db: Session, menu_id: str) -> None:
  205. obj = get_menu(db, menu_id)
  206. if not obj:
  207. raise ValueError("菜单不存在")
  208. _cascade_soft_delete(db, menu_id)
  209. obj.del_flag = 1
  210. obj.update_time = datetime.now()
  211. db.add(obj)
  212. db.commit()