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 4s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 14s
Test / e2e-test (push) Failing after 57s
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 4s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 14s
Test / e2e-test (push) Failing after 57s
- 修复 create_diary 不保存 tags 的问题 - 新建标签后加入全局标签列表,移除后显示在候选区 - 标签筛选区:编辑者可新增标签,管理员可删除标签 - 标签筛选区每个标签旁加×删除按钮(管理员) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1238,14 +1238,15 @@ def create_diary(body: dict, user=Depends(get_current_user)):
|
||||
name = body.get("name", "").strip()
|
||||
ingredients = body.get("ingredients", [])
|
||||
note = body.get("note", "")
|
||||
tags = body.get("tags", [])
|
||||
source_id = body.get("source_recipe_id")
|
||||
if not name:
|
||||
raise HTTPException(400, "请输入配方名称")
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"INSERT INTO user_diary (user_id, source_recipe_id, name, ingredients, note) VALUES (?, ?, ?, ?, ?)",
|
||||
(user["id"], source_id, name, json.dumps(ingredients, ensure_ascii=False), note)
|
||||
"INSERT INTO user_diary (user_id, source_recipe_id, name, ingredients, note, tags) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(user["id"], source_id, name, json.dumps(ingredients, ensure_ascii=False), note, json.dumps(tags, ensure_ascii=False))
|
||||
)
|
||||
conn.commit()
|
||||
did = c.lastrowid
|
||||
|
||||
@@ -54,7 +54,11 @@
|
||||
class="tag-chip"
|
||||
:class="{ active: selectedTags.includes(tag) }"
|
||||
@click="toggleTag(tag)"
|
||||
>{{ tag }}</span>
|
||||
>{{ tag }}<span v-if="auth.isAdmin" class="tag-delete" @click.stop="deleteGlobalTag(tag)">×</span></span>
|
||||
<div v-if="auth.canEdit" class="tag-add-row">
|
||||
<input v-model="globalNewTag" class="tag-add-input" placeholder="新标签..." @keydown.enter="addGlobalTag" />
|
||||
<button class="tag-add-btn" @click="addGlobalTag" :disabled="!globalNewTag.trim()">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -486,6 +490,38 @@ function filterBySearchAndTags(list) {
|
||||
const myFilteredRecipes = computed(() => filterBySearchAndTags(myRecipes.value))
|
||||
const publicFilteredRecipes = computed(() => filterBySearchAndTags(publicRecipes.value))
|
||||
|
||||
const globalNewTag = ref('')
|
||||
|
||||
async function addGlobalTag() {
|
||||
const tag = globalNewTag.value.trim()
|
||||
if (!tag) return
|
||||
try {
|
||||
await api('/api/tags', { method: 'POST', body: JSON.stringify({ name: tag }) })
|
||||
if (!recipeStore.allTags.includes(tag)) {
|
||||
recipeStore.allTags.push(tag)
|
||||
recipeStore.allTags.sort((a, b) => a.localeCompare(b, 'zh'))
|
||||
}
|
||||
globalNewTag.value = ''
|
||||
ui.showToast('标签已添加')
|
||||
} catch {
|
||||
ui.showToast('添加失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteGlobalTag(tag) {
|
||||
const { showConfirm } = await import('../composables/useDialog')
|
||||
const ok = await showConfirm(`确定删除标签「${tag}」?`)
|
||||
if (!ok) return
|
||||
try {
|
||||
await api(`/api/tags/${encodeURIComponent(tag)}`, { method: 'DELETE' })
|
||||
const idx = recipeStore.allTags.indexOf(tag)
|
||||
if (idx >= 0) recipeStore.allTags.splice(idx, 1)
|
||||
ui.showToast('标签已删除')
|
||||
} catch {
|
||||
ui.showToast('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTag(tag) {
|
||||
const idx = selectedTags.value.indexOf(tag)
|
||||
if (idx >= 0) selectedTags.value.splice(idx, 1)
|
||||
@@ -712,9 +748,15 @@ const formCandidateTags = computed(() =>
|
||||
|
||||
function addNewFormTag() {
|
||||
const tag = newTagInput.value.trim()
|
||||
if (tag && !formTags.value.includes(tag)) {
|
||||
if (!tag) return
|
||||
if (!formTags.value.includes(tag)) {
|
||||
formTags.value.push(tag)
|
||||
}
|
||||
// Also add to global tags so it appears in candidates if removed
|
||||
if (!recipeStore.allTags.includes(tag)) {
|
||||
recipeStore.allTags.push(tag)
|
||||
recipeStore.allTags.sort((a, b) => a.localeCompare(b, 'zh'))
|
||||
}
|
||||
newTagInput.value = ''
|
||||
}
|
||||
|
||||
@@ -1546,6 +1588,14 @@ watch(() => recipeStore.recipes, () => {
|
||||
.drops-sm:focus { border-color: #7ec6a4; }
|
||||
.select-sm { padding: 4px 6px; border: 1.5px solid #d4cfc7; border-radius: 6px; font-size: 12px; font-family: inherit; background: #fff; width: auto; }
|
||||
.btn-select-active { background: #e8f5e9; color: #2e7d5a; border: 1.5px solid #7ec6a4; border-radius: 10px; padding: 7px 14px; font-size: 13px; cursor: pointer; font-family: inherit; font-weight: 600; }
|
||||
.tag-delete { margin-left: 4px; cursor: pointer; font-size: 12px; color: #ccc; }
|
||||
.tag-delete:hover { color: #c0392b; }
|
||||
.tag-add-row { display: flex; gap: 4px; align-items: center; width: 100%; margin-top: 4px; }
|
||||
.tag-add-input { flex: 1; padding: 4px 8px; border: 1px solid #e5e4e7; border-radius: 8px; font-size: 12px; outline: none; font-family: inherit; }
|
||||
.tag-add-input:focus { border-color: #7ec6a4; }
|
||||
.tag-add-btn { border: none; background: #e8f5e9; color: #4a9d7e; border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer; font-family: inherit; }
|
||||
.tag-add-btn:disabled { opacity: 0.4; }
|
||||
|
||||
.export-btn {
|
||||
margin-left: auto;
|
||||
border: none; background: none; cursor: pointer; font-size: 16px; padding: 4px 6px;
|
||||
|
||||
Reference in New Issue
Block a user