diff --git a/backend/main.py b/backend/main.py index 05fa945..0a78a20 100644 --- a/backend/main.py +++ b/backend/main.py @@ -799,9 +799,16 @@ def create_recipe(recipe: RecipeIn, user=Depends(get_current_user)): c.execute("INSERT OR IGNORE INTO tags (name) VALUES (?)", (tag,)) c.execute("INSERT OR IGNORE INTO recipe_tags (recipe_id, tag_name) VALUES (?, ?)", (rid, tag)) log_audit(conn, user["id"], "create_recipe", "recipe", rid, recipe.name) - # Notify admin when non-admin/non-senior_editor creates a recipe (needs review) - if user["role"] not in ("admin", "senior_editor"): - who = user.get("display_name") or user["username"] + who = user.get("display_name") or user["username"] + if user["role"] == "senior_editor": + # Senior editor adds directly — just inform admin + conn.execute( + "INSERT INTO notifications (target_role, title, body) VALUES (?, ?, ?)", + ("admin", "📋 新配方已添加", + f"{who} 将配方「{recipe.name}」添加到了公共配方库。\n[recipe_id:{rid}]") + ) + elif user["role"] not in ("admin",): + # Other users need review conn.execute( "INSERT INTO notifications (target_role, title, body) VALUES (?, ?, ?)", ("admin", "📝 新配方待审核", diff --git a/frontend/src/views/RecipeManager.vue b/frontend/src/views/RecipeManager.vue index a265b01..26ae57e 100644 --- a/frontend/src/views/RecipeManager.vue +++ b/frontend/src/views/RecipeManager.vue @@ -1199,22 +1199,21 @@ function openRecipeDetail(recipe) { if (idx >= 0) previewRecipeIndex.value = idx } +function diaryMatchesPublic(d) { + const pub = recipeStore.recipes.find(r => r.name === d.name) + if (!pub) return false + const dIngs = (d.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil)) + const pIngs = (pub.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil)) + return dIngs.length === pIngs.length && dIngs.every((ing, i) => ing.oil === pIngs[i].oil && ing.drops === pIngs[i].drops) +} + function getDiaryShareStatus(d) { - // Check pending first (exact name match in public library owned by user) + // Check pending (owned by user in public library, not yet adopted) if (sharedCount.value.pendingNames.includes(d.name)) return 'pending' - // Check adopted — but if diary content was modified, allow re-sharing - if (sharedCount.value.adoptedNames.includes(d.name)) { - // Compare with the public recipe to see if content changed - const pub = recipeStore.recipes.find(r => r.name === d.name) - if (pub) { - const dIngs = (d.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil)) - const pIngs = (pub.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil)) - const same = dIngs.length === pIngs.length && dIngs.every((ing, i) => ing.oil === pIngs[i].oil && ing.drops === pIngs[i].drops) - if (same) return 'shared' - } - // Content changed or public recipe not found — can re-share - return null - } + // Check if public library has same recipe with same content + if (diaryMatchesPublic(d)) return 'shared' + // Check adopted names from audit log + if (sharedCount.value.adoptedNames.includes(d.name) && diaryMatchesPublic(d)) return 'shared' return null } @@ -1244,6 +1243,27 @@ async function recommendApprove(recipe) { } async function shareDiaryToPublic(diary) { + // Check for duplicates in public library + const dup = recipeStore.recipes.find(r => r.name === diary.name) + if (dup) { + const dIngs = (diary.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil)) + const pIngs = (dup.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil)) + const identical = dIngs.length === pIngs.length && dIngs.every((ing, i) => ing.oil === pIngs[i].oil && ing.drops === pIngs[i].drops) + if (identical) { + ui.showToast('公共配方库中已有一模一样的配方「' + diary.name + '」') + return + } + // Same name, different content + const action = await showConfirm( + `公共配方库中已有同名配方「${diary.name}」但内容不同,是否改名后共享?`, + { okText: '改名', cancelText: '取消' } + ) + if (!action) return + const newName = await showPrompt('请输入新名称:', diary.name) + if (!newName || !newName.trim()) return + diary = { ...diary, name: newName.trim() } + } + const ok = await showConfirm(`将「${diary.name}」共享到公共配方库?`) if (!ok) return try { @@ -1257,7 +1277,7 @@ async function shareDiaryToPublic(diary) { }), }) if (res.ok) { - if (auth.isAdmin) { + if (auth.isAdmin || auth.canManage) { ui.showToast('已共享到公共配方库') } else { ui.showToast('已提交,等待管理员审核')