update. 完成导航菜单接口

This commit is contained in:
ngfchl
2026-04-07 22:44:30 +08:00
parent fdc10692af
commit 5588bbaac8
2 changed files with 138 additions and 2 deletions
+32
View File
@@ -69,3 +69,35 @@ class PictureLinkUpdateSchema(Schema):
url: Optional[str] = None
order: Optional[int] = None
is_active: Optional[bool] = None
class MainMenuBase(Schema):
"""基础菜单字段"""
title: str
code: str
url: str = ""
is_external: bool = False
is_carousel: bool = False
order: int = 0
visible: bool = True
class MainMenuCreate(MainMenuBase):
"""创建时的 Schema"""
parent_id: Optional[int] = None
class MainMenuUpdate(MainMenuBase):
"""更新时的 Schema"""
parent_id: Optional[int] = None
class MainMenuOut(MainMenuBase):
"""输出用的 Schema,包含 ID 和递归的子菜单"""
id: int
parent_id: Optional[int] = None
children: List['MainMenuOut'] = [] # 递归引用自身
# 告诉 Pydantic 如何处理递归模型
MainMenuOut.model_rebuild()
+106 -2
View File
@@ -10,9 +10,9 @@ from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from ninja import Router
from base.models import FriendLink, LinkCategory, PictureLink
from base.models import FriendLink, LinkCategory, PictureLink, MainMenu
from base.schema import FriendLinkCreateSchema, FriendLinkSchema, LinkCategoryCreateSchema, LinkCategorySchema, \
PictureLinkSchema, PictureLinkCreateSchema, PictureLinkUpdateSchema
PictureLinkSchema, PictureLinkCreateSchema, PictureLinkUpdateSchema, MainMenuOut, MainMenuCreate, MainMenuUpdate
# Create your views here.
router = Router(tags=['base'])
@@ -178,3 +178,107 @@ def delete_picture_link(request, link_id: int):
link = get_object_or_404(PictureLink, id=link_id)
link.delete()
return {"success": True}
def build_menu_tree(menus: List[MainMenu]) -> List[MainMenuOut]:
"""
将平铺的菜单列表转换为嵌套的树形结构。
这是一个高效的 O(n) 算法。
"""
menu_map = {}
root_menus = []
# 第一步:将所有菜单放入字典,并初始化 children 列表
for menu in menus:
menu_data = MainMenuOut(
id=menu.id,
parent_id=menu.parent_id,
title=menu.title,
code=menu.code,
url=menu.url,
is_external=menu.is_external,
is_carousel=menu.is_carousel,
order=menu.order,
visible=menu.visible,
children=[]
)
menu_map[menu.id] = menu_data
if menu.parent_id is None:
root_menus.append(menu_data)
# 第二步:遍历字典,将子菜单挂载到父菜单上
for menu_id, menu_obj in menu_map.items():
if menu_obj.parent_id is not None and menu_obj.parent_id in menu_map:
parent = menu_map[menu_obj.parent_id]
parent.children.append(menu_obj)
# 第三步:对每个层级的菜单按 'order' 排序
def sort_children(menu_list):
menu_list.sort(key=lambda x: (x.order, x.title))
for menu in menu_list:
sort_children(menu.children)
sort_children(root_menus)
return root_menus
@router.get("main-menu/", response=List[MainMenuOut])
def get_menu_tree(request):
"""
获取完整的导航菜单树。
GET /api/main-menu/
"""
all_menus = MainMenu.objects.all().select_related('parent')
return build_menu_tree(all_menus)
@router.post("/main-menu/", response=MainMenuOut)
def create_menu(request, payload: MainMenuCreate):
"""
创建一个新的菜单项。
POST /api/main-menu/
"""
parent = None
if payload.parent_id:
parent = get_object_or_404(MainMenu, id=payload.parent_id)
menu = MainMenu.objects.create(
parent=parent,
**payload.dict(exclude={'parent_id'})
)
return menu
@router.put("/main-menu/{menu_id}", response=MainMenuOut)
def update_menu(request, menu_id: int, payload: MainMenuUpdate):
"""
更新一个菜单项。
PUT /api/main-menu/{menu_id}/
"""
menu = get_object_or_404(MainMenu, id=menu_id)
# 处理 parent_id
if payload.parent_id is not None:
if payload.parent_id != menu.parent_id:
new_parent = get_object_or_404(MainMenu, id=payload.parent_id)
menu.parent = new_parent
else:
menu.parent = None
# 更新其他字段
for attr, value in payload.dict(exclude={'parent_id'}).items():
setattr(menu, attr, value)
menu.save()
return menu
@router.delete("/main-menu/{menu_id}")
def delete_menu(request, menu_id: int):
"""
删除一个菜单项(及其所有子项)。
DELETE /api/main-menu/{menu_id}/
"""
menu = get_object_or_404(MainMenu, id=menu_id)
menu.delete() # Django 的级联删除会自动处理子项
return {"success": True}