feat: 企微菜单管理 — create_menu / get_menu / delete_menu

wecom.py 新增:
- create_menu(buttons) — 创建/替换应用自定义菜单
- get_menu() — 获取当前菜单配置
- delete_menu() — 删除菜单

API 新增:
- POST /api/wecom/setup-menu — 一键部署标准菜单(开始填报 + 绑定账号)
- GET /api/wecom/menu — 查看当前菜单

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-26 11:36:16 +08:00
parent 5006642354
commit da1f24c710
2 changed files with 73 additions and 0 deletions
+26
View File
@@ -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),
+47
View File
@@ -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()