From da1f24c710f20aade35755f2f768a81c168355ea Mon Sep 17 00:00:00 2001 From: v6ole Date: Fri, 26 Jun 2026 11:36:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=81=E5=BE=AE=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E7=AE=A1=E7=90=86=20=E2=80=94=20create=5Fmenu=20/=20get=5Fmenu?= =?UTF-8?q?=20/=20delete=5Fmenu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wecom.py 新增: - create_menu(buttons) — 创建/替换应用自定义菜单 - get_menu() — 获取当前菜单配置 - delete_menu() — 删除菜单 API 新增: - POST /api/wecom/setup-menu — 一键部署标准菜单(开始填报 + 绑定账号) - GET /api/wecom/menu — 查看当前菜单 Co-Authored-By: Claude --- backend/app/api/wecom.py | 26 +++++++++++++++++++ backend/app/services/wecom.py | 47 +++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/backend/app/api/wecom.py b/backend/app/api/wecom.py index 462cdb8..661c2df 100644 --- a/backend/app/api/wecom.py +++ b/backend/app/api/wecom.py @@ -137,6 +137,32 @@ async def test_message( return {"success": success, "sent_to": wecom_userid} +@router.post("/setup-menu") +async def setup_menu(current_user: dict = Depends(require_director)): + """Create the standard app menu (开始填报 + 绑定账号).""" + buttons = [ + { + "type": "view", + "name": "📝 开始填报", + "url": "https://qj.dhdx.fun/m", + }, + { + "type": "view", + "name": "🔗 绑定账号", + "url": "https://qj.dhdx.fun/bind-wechat", + }, + ] + success = await wecom_client.create_menu(buttons) + return {"success": success, "message": "菜单已部署" if success else "菜单创建失败"} + + +@router.get("/menu") +async def get_menu(current_user: dict = Depends(require_director)): + """Get current app menu configuration.""" + menu = await wecom_client.get_menu() + return menu or {"message": "No menu configured"} + + @router.post("/trigger-daily-check") async def trigger_daily_check( current_user: dict = Depends(require_director), diff --git a/backend/app/services/wecom.py b/backend/app/services/wecom.py index d2fcc40..c82f60d 100644 --- a/backend/app/services/wecom.py +++ b/backend/app/services/wecom.py @@ -156,4 +156,51 @@ class WecomClient: return False + # ── Menu management ── + + async def create_menu(self, buttons: list[dict]) -> bool: + """Create/replace the app's custom menu (visible in chat window).""" + if not settings.WECOM_AGENT_ID: + return False + try: + body = {"button": buttons} + await self._post_with_retry( + f"https://qyapi.weixin.qq.com/cgi-bin/menu/create?agentid={settings.WECOM_AGENT_ID}", + body, + ) + return True + except Exception: + return False + + async def get_menu(self) -> dict | None: + """Get current app menu configuration.""" + if not settings.WECOM_AGENT_ID: + return None + try: + token = await self._get_token() + url = f"https://qyapi.weixin.qq.com/cgi-bin/menu/get?access_token={token}&agentid={settings.WECOM_AGENT_ID}" + async with httpx.AsyncClient() as client: + resp = await client.get(url, timeout=10) + data = resp.json() + if data.get("errcode") == 0: + return data + return None + except Exception: + return None + + async def delete_menu(self) -> bool: + """Delete the app's custom menu.""" + if not settings.WECOM_AGENT_ID: + return False + try: + token = await self._get_token() + url = f"https://qyapi.weixin.qq.com/cgi-bin/menu/delete?access_token={token}&agentid={settings.WECOM_AGENT_ID}" + async with httpx.AsyncClient() as client: + resp = await client.get(url, timeout=10) + data = resp.json() + return data.get("errcode") == 0 + except Exception: + return False + + wecom_client = WecomClient()