From 866950c2f65ac854cf29d70ac647269701ccf81f Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Fri, 10 Apr 2026 18:28:05 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=A1=E6=A0=B8=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=AE=8C=E5=96=84=20+=20=E5=85=B1=E4=BA=AB=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=20+=20=E8=B4=A1=E7=8C=AE=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=90=AB=E6=8B=92=E7=BB=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 审核流程: - 高级编辑者可看到待审核配方,点击推荐通过→通知管理员 - 高级编辑者可直接拒绝(和管理员相同逻辑) - 管理员收到推荐通知后最终决定 - 去审核通知点击自动展开待审核列表 - 新增 /api/recipes/{id}/recommend 端点 共享: - 已共享配方再点共享→提示"已共享,感谢贡献" - 审核中配方再点共享→提示"正在审核中,请耐心等待" 贡献统计: - 被拒绝的配方也计入总申请数(0/1不会变回0/0) - reject_recipe日志记录from_user 其他: - 配方卡片去掉编辑按钮 Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/main.py | 34 +++++++++- .../src/components/RecipeDetailOverlay.vue | 5 -- frontend/src/components/UserMenu.vue | 13 +++- frontend/src/views/RecipeManager.vue | 62 +++++++++++++++---- 4 files changed, 90 insertions(+), 24 deletions(-) diff --git a/backend/main.py b/backend/main.py index bac2082..9708b72 100644 --- a/backend/main.py +++ b/backend/main.py @@ -895,7 +895,7 @@ def adopt_recipe(recipe_id: int, user=Depends(require_role("admin"))): @app.post("/api/recipes/{recipe_id}/reject") -def reject_recipe(recipe_id: int, body: dict = None, user=Depends(require_role("admin"))): +def reject_recipe(recipe_id: int, body: dict = None, user=Depends(require_role("admin", "senior_editor"))): conn = get_db() row = conn.execute("SELECT id, name, owner_id FROM recipes WHERE id = ?", (recipe_id,)).fetchone() if not row: @@ -917,8 +917,30 @@ def reject_recipe(recipe_id: int, body: dict = None, user=Depends(require_role(" conn.execute("DELETE FROM recipe_ingredients WHERE recipe_id = ?", (recipe_id,)) conn.execute("DELETE FROM recipe_tags WHERE recipe_id = ?", (recipe_id,)) conn.execute("DELETE FROM recipes WHERE id = ?", (recipe_id,)) + from_name = (old_owner["display_name"] or old_owner["username"]) if old_owner else "unknown" log_audit(conn, user["id"], "reject_recipe", "recipe", recipe_id, row["name"], - json.dumps({"reason": reason})) + json.dumps({"reason": reason, "from_user": from_name})) + conn.commit() + conn.close() + return {"ok": True} + + +@app.post("/api/recipes/{recipe_id}/recommend") +def recommend_recipe(recipe_id: int, body: dict = None, user=Depends(get_current_user)): + """Senior editor recommends a recipe for admin approval.""" + if user["role"] not in ("senior_editor", "admin"): + raise HTTPException(403, "权限不足") + conn = get_db() + recipe = conn.execute("SELECT name, owner_id FROM recipes WHERE id = ?", (recipe_id,)).fetchone() + if not recipe: + conn.close() + raise HTTPException(404, "配方不存在") + who = user.get("display_name") or user.get("username") + conn.execute( + "INSERT INTO notifications (target_role, title, body) VALUES (?, ?, ?)", + ("admin", "👍 配方推荐通过", + f"{who} 审核了配方「{recipe['name']}」并推荐通过,请最终确认。\n[recipe_id:{recipe_id}]") + ) conn.commit() conn.close() return {"ok": True} @@ -1511,10 +1533,16 @@ def my_contribution(user=Depends(get_current_user)): "SELECT name FROM recipes WHERE owner_id = ?", (user["id"],) ).fetchall() pending_names = [r["name"] for r in pending_rows] + # rejected: count recipes rejected from this user (they were deleted but logged) + rejected_count = conn.execute( + "SELECT COUNT(*) FROM audit_log WHERE action = 'reject_recipe' AND detail LIKE ?", + (f'%"from_user": "{display}"%',) + ).fetchone()[0] conn.close() + total = len(adopted_names) + len(pending_names) + rejected_count return { "adopted_count": len(adopted_names), - "shared_count": len(adopted_names) + len(pending_names), + "shared_count": total, "adopted_names": adopted_names, "pending_names": pending_names, } diff --git a/frontend/src/components/RecipeDetailOverlay.vue b/frontend/src/components/RecipeDetailOverlay.vue index ff569a3..da244db 100644 --- a/frontend/src/components/RecipeDetailOverlay.vue +++ b/frontend/src/components/RecipeDetailOverlay.vue @@ -14,11 +14,6 @@
- diff --git a/frontend/src/components/UserMenu.vue b/frontend/src/components/UserMenu.vue index 116f4a3..2a98152 100644 --- a/frontend/src/components/UserMenu.vue +++ b/frontend/src/components/UserMenu.vue @@ -130,8 +130,14 @@ function isSearchMissing(n) { } function isReviewable(n) { - if (!auth.isAdmin || !n.title) return false - return n.title.includes('待审核') || n.title.includes('商业认证') || n.title.includes('申请') + if (!n.title) return false + // Admin: review recipe/business/applications + if (auth.isAdmin) { + return n.title.includes('待审核') || n.title.includes('商业认证') || n.title.includes('申请') || n.title.includes('推荐通过') + } + // Senior editor: assigned reviews + if (auth.canManage && n.title.includes('请审核')) return true + return false } async function markAdded(n) { @@ -141,7 +147,8 @@ async function markAdded(n) { function goReview(n) { markOneRead(n) emit('close') - if (n.title.includes('配方')) { + if (n.title.includes('配方') || n.title.includes('审核') || n.title.includes('推荐')) { + localStorage.setItem('oil_open_pending', '1') router.push('/manage') } else if (n.title.includes('商业认证') || n.title.includes('申请')) { router.push('/users') diff --git a/frontend/src/views/RecipeManager.vue b/frontend/src/views/RecipeManager.vue index 2c96e68..4959d95 100644 --- a/frontend/src/views/RecipeManager.vue +++ b/frontend/src/views/RecipeManager.vue @@ -1,7 +1,7 @@