fix: 高级编辑直接添加公共库+编辑者权限精确控制
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 4s
Test / build-check (push) Successful in 3s
PR Preview / test (pull_request) Successful in 4s
PR Preview / deploy-preview (pull_request) Successful in 17s
Test / e2e-test (push) Failing after 56s

公共库添加:
- 高级编辑直接添加到公共库时owner_id设为admin,所有人可见
- 高级编辑添加不触发审核通知

精油价目权限:
- 编辑精油改为canManage(senior_editor+admin)
- editor只能编辑配方,不能编辑精油价目

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 19:55:21 +00:00
parent b570ef5093
commit 650c04a972
2 changed files with 13 additions and 7 deletions

View File

@@ -781,8 +781,14 @@ def create_recipe(recipe: RecipeIn, user=Depends(get_current_user)):
raise HTTPException(401, "请先登录")
conn = get_db()
c = conn.cursor()
# Senior editors adding directly to public library: set owner to admin so everyone can see
owner_id = user["id"]
if user["role"] in ("senior_editor",):
admin = c.execute("SELECT id FROM users WHERE role = 'admin' LIMIT 1").fetchone()
if admin:
owner_id = admin["id"]
c.execute("INSERT INTO recipes (name, note, owner_id) VALUES (?, ?, ?)",
(recipe.name, recipe.note, user["id"]))
(recipe.name, recipe.note, owner_id))
rid = c.lastrowid
for ing in recipe.ingredients:
c.execute(
@@ -793,8 +799,8 @@ 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 only when non-admin creates a recipe
if user["role"] != "admin":
# 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"]
conn.execute(
"INSERT INTO notifications (target_role, title, body) VALUES (?, ?, ?)",

View File

@@ -98,15 +98,15 @@
<button @click="viewMode = 'drop'" :style="viewMode === 'drop' ? 'background:var(--sage);color:white' : 'background:white;color:var(--text-mid)'" style="border:none;border-radius:0;font-size:12px;padding:6px 12px;cursor:pointer">每滴价</button>
</div>
<!-- Desktop: text buttons -->
<button v-if="auth.canEdit" class="toolbar-btn-text" @click="showAddForm = !showAddForm">{{ showAddForm ? '收起' : ' 新增' }}</button>
<button v-if="auth.canManage" class="toolbar-btn-text" @click="showAddForm = !showAddForm">{{ showAddForm ? '收起' : ' 新增' }}</button>
<button v-if="auth.isAdmin" class="toolbar-btn-text" @click="exportPDF">📥 导出PDF</button>
<!-- Mobile: emoji-only buttons -->
<button v-if="auth.canEdit" class="toolbar-btn-icon" @click="showAddForm = !showAddForm" title="新增精油"></button>
<button v-if="auth.canManage" class="toolbar-btn-icon" @click="showAddForm = !showAddForm" title="新增精油"></button>
<button v-if="auth.isAdmin" class="toolbar-btn-icon" @click="exportPDF" title="导出PDF">📄</button>
</div>
<!-- Add Oil Form (toggleable) -->
<div v-if="showAddForm && auth.canEdit" class="add-oil-form">
<div v-if="showAddForm && auth.canManage" class="add-oil-form">
<div class="form-row">
<input v-model="newOilName" style="flex:1;min-width:120px" placeholder="精油名称" class="form-input-sm" />
<input v-model="newOilEnName" style="flex:1;min-width:100px" placeholder="英文名" class="form-input-sm" />
@@ -152,7 +152,7 @@
</div>
</template>
</div>
<div v-if="auth.canEdit" class="oil-chip-actions" @click.stop>
<div v-if="auth.canManage" class="oil-chip-actions" @click.stop>
<button @click="editOil(name)" title="编辑"></button>
<button @click="removeOil(name)" title="删除">🗑</button>
</div>