From 6d2620eb6a98fcda17f7657daef4a0e93cce05e7 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Fri, 10 Apr 2026 20:12:10 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=96=B0=E5=A2=9E=E9=85=8D=E6=96=B9?= =?UTF-8?q?=E5=8E=BB=E9=87=8D+=E7=BC=96=E8=BE=91=E8=80=85=E6=9D=83?= =?UTF-8?q?=E9=99=90+=E5=85=B1=E4=BA=AB=E5=8E=BB=E9=87=8D=E7=BB=9F?= =?UTF-8?q?=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增去重: - 新增配方保存前检查公共库和个人配方同名 - 完全相同提示已有,内容不同显示差异可改名 编辑者权限: - editor可编辑所有公共配方(前端+后端) - editor不能编辑精油价目(已有) Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/main.py | 4 +--- frontend/src/stores/auth.js | 6 ++---- frontend/src/views/RecipeManager.vue | 32 ++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/backend/main.py b/backend/main.py index 0a78a20..18a7bea 100644 --- a/backend/main.py +++ b/backend/main.py @@ -824,9 +824,7 @@ def _check_recipe_permission(conn, recipe_id, user): row = conn.execute("SELECT owner_id, name FROM recipes WHERE id = ?", (recipe_id,)).fetchone() if not row: raise HTTPException(404, "Recipe not found") - if user["role"] in ("admin", "senior_editor"): - return row - if user["role"] in ("editor",) and row["owner_id"] == user.get("id"): + if user["role"] in ("admin", "senior_editor", "editor"): return row raise HTTPException(403, "权限不足") diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js index 5f59086..d0dd0a7 100644 --- a/frontend/src/stores/auth.js +++ b/frontend/src/stores/auth.js @@ -73,10 +73,8 @@ export const useAuthStore = defineStore('auth', () => { user.value = { ...DEFAULT_USER } } - function canEditRecipe(recipe) { - if (isAdmin.value || user.value.role === 'senior_editor') return true - if (canEdit.value && recipe._owner_id === user.value.id) return true - return false + function canEditRecipe() { + return canEdit.value } return { diff --git a/frontend/src/views/RecipeManager.vue b/frontend/src/views/RecipeManager.vue index 26ae57e..32a6124 100644 --- a/frontend/src/views/RecipeManager.vue +++ b/frontend/src/views/RecipeManager.vue @@ -964,6 +964,38 @@ async function saveCurrentRecipe() { tags: formTags.value, } + // Dedup check for new recipes (not editing) + if (!editingRecipe.value) { + const name = formName.value.trim() + // Check public library + const pubDup = recipeStore.recipes.find(r => r.name === name) + // Check personal diary + const diaryDup = diaryStore.userDiary.find(d => d.name === name) + const dup = pubDup || diaryDup + if (dup) { + const dupIngs = (dup.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil)) + const myIngs = cleanIngs.filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil)) + const identical = dupIngs.length === myIngs.length && dupIngs.every((ing, i) => ing.oil === myIngs[i].oil && ing.drops === myIngs[i].drops) + const where = pubDup ? '公共配方库' : '我的配方' + if (identical) { + ui.showToast(`${where}中已有一模一样的配方「${name}」`) + return + } + // Show difference + const existIngs = dupIngs.map(i => `${i.oil}${i.drops}滴`).join('、') + const newIngs = myIngs.map(i => `${i.oil}${i.drops}滴`).join('、') + const ok = await showConfirm( + `${where}中已有同名配方「${name}」,内容不同:\n\n已有:${existIngs}\n新的:${newIngs}\n\n是否改名后保存?`, + { okText: '改名', cancelText: '取消' } + ) + if (!ok) return + const newName = await showPrompt('请输入新名称:', name) + if (!newName || !newName.trim()) return + formName.value = newName.trim() + diaryPayload.name = newName.trim() + } + } + if (editingRecipe.value && editingRecipe.value._diary_id) { // Editing an existing diary recipe try {