From 5588bbaac8522333f8f883de88bde493aa8748f8 Mon Sep 17 00:00:00 2001 From: ngfchl Date: Tue, 7 Apr 2026 22:44:30 +0800 Subject: [PATCH] =?UTF-8?q?update.=20=E5=AE=8C=E6=88=90=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base/schema.py | 32 +++++++++++++++ base/views.py | 108 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/base/schema.py b/base/schema.py index 241f892..71cb3d3 100644 --- a/base/schema.py +++ b/base/schema.py @@ -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() diff --git a/base/views.py b/base/views.py index ce7813b..256b76c 100644 --- a/base/views.py +++ b/base/views.py @@ -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}