From 8a447989aee3a76ea4bfe1525d08cf3eb56a408b Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Fri, 10 Apr 2026 18:42:08 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=89=B9=E9=87=8F=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=A7=BB=E9=99=A4=E5=B7=B2=E6=9C=89=E6=A0=87?= =?UTF-8?q?=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 批量打标签面板底部显示选中配方的所有已有标签 - 点击标签切换"移除"状态(红色删除线) - 确认后同时添加新标签和移除标记的标签 Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/views/RecipeManager.vue | 80 +++++++++++++++++++++------- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/frontend/src/views/RecipeManager.vue b/frontend/src/views/RecipeManager.vue index 323a330..f6285a6 100644 --- a/frontend/src/views/RecipeManager.vue +++ b/frontend/src/views/RecipeManager.vue @@ -87,8 +87,16 @@ +
+
点击移除已有标签:
+
+ + {{ tag }} {{ batchTagsToRemove.includes(tag) ? '✓移除' : '×' }} + +
+
- +
@@ -585,31 +593,43 @@ function addBatchTag() { } async function applyBatchTags() { - const tags = batchTagsSelected.value - if (!tags.length) { ui.showToast('请选择至少一个标签'); return } + const tagsToAdd = batchTagsSelected.value + const tagsToRemove = batchTagsToRemove.value + if (!tagsToAdd.length && !tagsToRemove.length) { ui.showToast('请选择要添加或移除的标签'); return } const pubIds = [...selectedIds] const diaryIds = [...selectedDiaryIds] - for (const tagName of tags) { - for (const id of pubIds) { - const recipe = recipeStore.recipes.find(r => r._id === id) - if (recipe && !recipe.tags.includes(tagName)) { - recipe.tags.push(tagName) - await recipeStore.saveRecipe(recipe) - } + for (const id of pubIds) { + const recipe = recipeStore.recipes.find(r => r._id === id) + if (!recipe) continue + let changed = false + for (const t of tagsToAdd) { + if (!recipe.tags.includes(t)) { recipe.tags.push(t); changed = true } } - for (const id of diaryIds) { - const d = diaryStore.userDiary.find(r => r.id === id) - if (d) { - const dtags = [...(d.tags || [])] - if (!dtags.includes(tagName)) { - dtags.push(tagName) - await diaryStore.updateDiary(id, { ...d, tags: dtags }) - } - } + for (const t of tagsToRemove) { + const idx = recipe.tags.indexOf(t) + if (idx >= 0) { recipe.tags.splice(idx, 1); changed = true } } + if (changed) await recipeStore.saveRecipe(recipe) + } + for (const id of diaryIds) { + const d = diaryStore.userDiary.find(r => r.id === id) + if (!d) continue + let dtags = [...(d.tags || [])] + let changed = false + for (const t of tagsToAdd) { + if (!dtags.includes(t)) { dtags.push(t); changed = true } + } + for (const t of tagsToRemove) { + const idx = dtags.indexOf(t) + if (idx >= 0) { dtags.splice(idx, 1); changed = true } + } + if (changed) await diaryStore.updateDiary(id, { ...d, tags: dtags }) } showBatchTagPicker.value = false - ui.showToast(`已为 ${pubIds.length + diaryIds.length} 个配方添加 ${tags.length} 个标签`) + const msgs = [] + if (tagsToAdd.length) msgs.push(`添加${tagsToAdd.length}个`) + if (tagsToRemove.length) msgs.push(`移除${tagsToRemove.length}个`) + ui.showToast(`已为 ${pubIds.length + diaryIds.length} 个配方${msgs.join('、')}标签`) clearSelection() } @@ -680,6 +700,7 @@ async function executeBatchAction(action) { ui.showToast(`已删除 ${totalCount} 个配方`) } else if (action === 'tag') { batchTagsSelected.value = [] + batchTagsToRemove.value = [] batchNewTag.value = '' showBatchTagPicker.value = true return // don't clear selection yet @@ -1062,6 +1083,22 @@ const showBatchMenu = ref(false) const showBatchTagPicker = ref(false) const batchTagsSelected = ref([]) const batchNewTag = ref('') +const batchTagsToRemove = ref([]) + +const batchExistingTags = computed(() => { + const tagSets = [] + for (const id of selectedIds) { + const r = recipeStore.recipes.find(x => x._id === id) + if (r) tagSets.push(r.tags || []) + } + for (const id of selectedDiaryIds) { + const d = diaryStore.userDiary.find(x => x.id === id) + if (d) tagSets.push(d.tags || []) + } + if (!tagSets.length) return [] + const all = new Set(tagSets.flat()) + return [...all].sort((a, b) => a.localeCompare(b, 'zh')) +}) const totalSelected = computed(() => selectedIds.size + selectedDiaryIds.size) const isMyAllSelected = computed(() => myFilteredRecipes.value.length > 0 && selectedDiaryIds.size === myFilteredRecipes.value.length) const isPubAllSelected = computed(() => publicFilteredRecipes.value.length > 0 && selectedIds.size === publicFilteredRecipes.value.length) @@ -1846,6 +1883,9 @@ watch(() => recipeStore.recipes, () => { .batch-tag-picker { padding: 12px; background: #f8faf8; border: 1.5px solid #d4e8d4; border-radius: 10px; margin-bottom: 10px; } +.tag-removable { cursor: pointer; transition: all 0.15s; } +.tag-removable:hover { background: #fce4ec; border-color: #e8b4b0; color: #c62828; } +.tag-marked-remove { background: #ffebee !important; color: #c62828 !important; text-decoration: line-through; } .mini-select { width: 18px; height: 18px; border: 1.5px solid #d4cfc7; border-radius: 4px; background: #fff; color: transparent; font-size: 11px; cursor: pointer;