menu_favorite.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. """用户菜单收藏 CRUD"""
  2. from __future__ import annotations
  3. import uuid
  4. from datetime import datetime
  5. from typing import Any, Dict, List, Optional
  6. from sqlalchemy.orm import Session
  7. from app.models.models import SysUserMenuFavorite
  8. MAX_FAVORITES = 10
  9. def normalize_menu_path(path: str) -> str:
  10. p = (path or "").strip()
  11. if not p:
  12. return ""
  13. if not p.startswith("/"):
  14. p = "/" + p
  15. return p
  16. def _row_to_dict(row: SysUserMenuFavorite) -> Dict[str, Any]:
  17. return {
  18. "id": row.id,
  19. "path": row.menu_path or "",
  20. "orderNum": row.order_num,
  21. "createTime": row.create_time.isoformat(sep=" ", timespec="seconds") if row.create_time else None,
  22. }
  23. def _next_order_num(db: Session, *, user_id: str, tenant_no: str) -> int:
  24. max_order = (
  25. db.query(SysUserMenuFavorite.order_num)
  26. .filter(
  27. SysUserMenuFavorite.del_flag == 0,
  28. SysUserMenuFavorite.user_id == user_id,
  29. SysUserMenuFavorite.tenant_no == tenant_no,
  30. )
  31. .order_by(SysUserMenuFavorite.order_num.desc())
  32. .first()
  33. )
  34. return (max_order[0] if max_order and max_order[0] is not None else 0) + 1
  35. def list_favorites(
  36. db: Session,
  37. *,
  38. user_id: str,
  39. tenant_no: str,
  40. ) -> List[Dict[str, Any]]:
  41. rows = (
  42. db.query(SysUserMenuFavorite)
  43. .filter(
  44. SysUserMenuFavorite.del_flag == 0,
  45. SysUserMenuFavorite.user_id == user_id,
  46. SysUserMenuFavorite.tenant_no == tenant_no,
  47. )
  48. .order_by(
  49. SysUserMenuFavorite.order_num.asc(),
  50. SysUserMenuFavorite.create_time.asc(),
  51. )
  52. .all()
  53. )
  54. return [_row_to_dict(r) for r in rows]
  55. def count_active(db: Session, *, user_id: str, tenant_no: str) -> int:
  56. return (
  57. db.query(SysUserMenuFavorite)
  58. .filter(
  59. SysUserMenuFavorite.del_flag == 0,
  60. SysUserMenuFavorite.user_id == user_id,
  61. SysUserMenuFavorite.tenant_no == tenant_no,
  62. )
  63. .count()
  64. )
  65. def find_active(
  66. db: Session, *, user_id: str, tenant_no: str, menu_path: str
  67. ) -> Optional[SysUserMenuFavorite]:
  68. return (
  69. db.query(SysUserMenuFavorite)
  70. .filter(
  71. SysUserMenuFavorite.del_flag == 0,
  72. SysUserMenuFavorite.user_id == user_id,
  73. SysUserMenuFavorite.tenant_no == tenant_no,
  74. SysUserMenuFavorite.menu_path == menu_path,
  75. )
  76. .first()
  77. )
  78. def find_soft_deleted(
  79. db: Session, *, user_id: str, tenant_no: str, menu_path: str
  80. ) -> Optional[SysUserMenuFavorite]:
  81. return (
  82. db.query(SysUserMenuFavorite)
  83. .filter(
  84. SysUserMenuFavorite.del_flag == 1,
  85. SysUserMenuFavorite.user_id == user_id,
  86. SysUserMenuFavorite.tenant_no == tenant_no,
  87. SysUserMenuFavorite.menu_path == menu_path,
  88. )
  89. .first()
  90. )
  91. def add_favorite(
  92. db: Session,
  93. *,
  94. user_id: str,
  95. tenant_no: str,
  96. menu_path: str,
  97. create_dept_id: str = "",
  98. ) -> Dict[str, Any]:
  99. path = normalize_menu_path(menu_path)
  100. if not path:
  101. raise ValueError("菜单路径无效")
  102. existing = find_active(db, user_id=user_id, tenant_no=tenant_no, menu_path=path)
  103. if existing:
  104. return _row_to_dict(existing)
  105. if count_active(db, user_id=user_id, tenant_no=tenant_no) >= MAX_FAVORITES:
  106. raise ValueError(f"最多收藏 {MAX_FAVORITES} 个菜单")
  107. now = datetime.now()
  108. next_order = _next_order_num(db, user_id=user_id, tenant_no=tenant_no)
  109. deleted = find_soft_deleted(db, user_id=user_id, tenant_no=tenant_no, menu_path=path)
  110. if deleted:
  111. deleted.del_flag = 0
  112. deleted.order_num = next_order
  113. deleted.update_by = user_id
  114. deleted.update_time = now
  115. db.commit()
  116. db.refresh(deleted)
  117. return _row_to_dict(deleted)
  118. row = SysUserMenuFavorite(
  119. id=str(uuid.uuid4()),
  120. user_id=user_id,
  121. menu_path=path,
  122. tenant_no=tenant_no,
  123. order_num=next_order,
  124. create_by=user_id,
  125. create_dept_id=create_dept_id or "",
  126. create_time=now,
  127. update_by=user_id,
  128. update_time=now,
  129. del_flag=0,
  130. )
  131. db.add(row)
  132. db.commit()
  133. db.refresh(row)
  134. return _row_to_dict(row)
  135. def soft_delete_favorite(
  136. db: Session,
  137. *,
  138. user_id: str,
  139. tenant_no: str,
  140. menu_path: str,
  141. ) -> bool:
  142. path = normalize_menu_path(menu_path)
  143. row = find_active(db, user_id=user_id, tenant_no=tenant_no, menu_path=path)
  144. if not row:
  145. return False
  146. row.del_flag = 1
  147. row.update_by = user_id
  148. row.update_time = datetime.now()
  149. db.commit()
  150. return True
  151. def reorder_favorites(
  152. db: Session,
  153. *,
  154. user_id: str,
  155. tenant_no: str,
  156. paths: List[str],
  157. ) -> List[Dict[str, Any]]:
  158. """按传入 paths 顺序重写 order_num(1-based)。paths 须覆盖当前全部有效收藏。"""
  159. normalized: List[str] = []
  160. seen = set()
  161. for raw in paths or []:
  162. p = normalize_menu_path(raw)
  163. if not p or p in seen:
  164. continue
  165. seen.add(p)
  166. normalized.append(p)
  167. current = list_favorites(db, user_id=user_id, tenant_no=tenant_no)
  168. current_paths = {item["path"] for item in current}
  169. if set(normalized) != current_paths or len(normalized) != len(current_paths):
  170. raise ValueError("排序列表与当前收藏不一致")
  171. now = datetime.now()
  172. for idx, path in enumerate(normalized, start=1):
  173. row = find_active(db, user_id=user_id, tenant_no=tenant_no, menu_path=path)
  174. if not row:
  175. raise ValueError("排序列表与当前收藏不一致")
  176. row.order_num = idx
  177. row.update_by = user_id
  178. row.update_time = now
  179. db.commit()
  180. return list_favorites(db, user_id=user_id, tenant_no=tenant_no)