feat: 批量打标签改为标签选择器
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Successful in 4s
PR Preview / deploy-preview (pull_request) Successful in 14s
Test / e2e-test (push) Failing after 55s
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Successful in 4s
PR Preview / deploy-preview (pull_request) Successful in 14s
Test / e2e-test (push) Failing after 55s
- 点击批量打标签展开标签选择面板 - 已选标签(绿色pill可删除)+ 候选标签(点击添加)+ 新标签输入 - 和编辑器内的标签样式一致 - 确认后批量添加到所有选中配方 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,31 @@
|
|||||||
<button v-if="selectedDiaryIds.size > 0 && selectedIds.size === 0" class="batch-menu-btn" @click="doBatch('share_public')">📤 批量共享到公共库</button>
|
<button v-if="selectedDiaryIds.size > 0 && selectedIds.size === 0" class="batch-menu-btn" @click="doBatch('share_public')">📤 批量共享到公共库</button>
|
||||||
<button class="batch-menu-btn batch-delete" @click="doBatch('delete')">🗑 批量删除</button>
|
<button class="batch-menu-btn batch-delete" @click="doBatch('delete')">🗑 批量删除</button>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Batch Tag Picker -->
|
||||||
|
<div v-if="showBatchTagPicker" class="batch-tag-picker">
|
||||||
|
<div class="editor-tags">
|
||||||
|
<span v-for="tag in batchTagsSelected" :key="tag" class="editor-tag">
|
||||||
|
{{ tag }}
|
||||||
|
<span class="tag-remove" @click="batchTagsSelected = batchTagsSelected.filter(t => t !== tag)">×</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="candidate-tags">
|
||||||
|
<span
|
||||||
|
v-for="tag in recipeStore.allTags.filter(t => !batchTagsSelected.includes(t))"
|
||||||
|
:key="tag"
|
||||||
|
class="candidate-tag"
|
||||||
|
@click="batchTagsSelected.push(tag)"
|
||||||
|
>+ {{ tag }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tag-input-row">
|
||||||
|
<input v-model="batchNewTag" class="editor-input" placeholder="新标签..." @keydown.enter="addBatchTag" style="flex:1;max-width:120px" />
|
||||||
|
<button class="action-btn action-btn-sm" @click="addBatchTag" :disabled="!batchNewTag.trim()">+</button>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:6px;margin-top:8px">
|
||||||
|
<button class="action-btn action-btn-primary action-btn-sm" @click="applyBatchTags">确认添加</button>
|
||||||
|
<button class="action-btn action-btn-sm" @click="showBatchTagPicker = false">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- My Recipes Section (from diary) -->
|
<!-- My Recipes Section (from diary) -->
|
||||||
<div class="recipe-section">
|
<div class="recipe-section">
|
||||||
@@ -550,6 +575,43 @@ function clearSelection() {
|
|||||||
showBatchMenu.value = false
|
showBatchMenu.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addBatchTag() {
|
||||||
|
const tag = batchNewTag.value.trim()
|
||||||
|
if (tag && !batchTagsSelected.value.includes(tag)) {
|
||||||
|
batchTagsSelected.value.push(tag)
|
||||||
|
}
|
||||||
|
batchNewTag.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyBatchTags() {
|
||||||
|
const tags = batchTagsSelected.value
|
||||||
|
if (!tags.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 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showBatchTagPicker.value = false
|
||||||
|
ui.showToast(`已为 ${pubIds.length + diaryIds.length} 个配方添加 ${tags.length} 个标签`)
|
||||||
|
clearSelection()
|
||||||
|
}
|
||||||
|
|
||||||
function doBatch(action) {
|
function doBatch(action) {
|
||||||
showBatchMenu.value = false
|
showBatchMenu.value = false
|
||||||
executeBatchAction(action)
|
executeBatchAction(action)
|
||||||
@@ -601,26 +663,10 @@ async function executeBatchAction(action) {
|
|||||||
}
|
}
|
||||||
ui.showToast(`已删除 ${totalCount} 个配方`)
|
ui.showToast(`已删除 ${totalCount} 个配方`)
|
||||||
} else if (action === 'tag') {
|
} else if (action === 'tag') {
|
||||||
const tagName = await showPrompt('输入要添加的标签:')
|
batchTagsSelected.value = []
|
||||||
if (!tagName) return
|
batchNewTag.value = ''
|
||||||
for (const id of pubIds) {
|
showBatchTagPicker.value = true
|
||||||
const recipe = recipeStore.recipes.find(r => r._id === id)
|
return // don't clear selection yet
|
||||||
if (recipe && !recipe.tags.includes(tagName)) {
|
|
||||||
recipe.tags.push(tagName)
|
|
||||||
await recipeStore.saveRecipe(recipe)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const id of diaryIds) {
|
|
||||||
const d = diaryStore.userDiary.find(r => r.id === id)
|
|
||||||
if (d) {
|
|
||||||
const tags = [...(d.tags || [])]
|
|
||||||
if (!tags.includes(tagName)) {
|
|
||||||
tags.push(tagName)
|
|
||||||
await diaryStore.updateDiary(id, { ...d, tags })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui.showToast(`已为 ${totalCount} 个配方添加标签`)
|
|
||||||
} else if (action === 'share_public') {
|
} else if (action === 'share_public') {
|
||||||
const ok = await showConfirm(`将 ${diaryIds.length} 个配方分享到公共配方库?`)
|
const ok = await showConfirm(`将 ${diaryIds.length} 个配方分享到公共配方库?`)
|
||||||
if (!ok) return
|
if (!ok) return
|
||||||
@@ -976,6 +1022,9 @@ async function saveAllParsed() {
|
|||||||
const sharedCount = ref({ adopted: 0, total: 0 })
|
const sharedCount = ref({ adopted: 0, total: 0 })
|
||||||
const previewRecipeIndex = ref(null)
|
const previewRecipeIndex = ref(null)
|
||||||
const showBatchMenu = ref(false)
|
const showBatchMenu = ref(false)
|
||||||
|
const showBatchTagPicker = ref(false)
|
||||||
|
const batchTagsSelected = ref([])
|
||||||
|
const batchNewTag = ref('')
|
||||||
const totalSelected = computed(() => selectedIds.size + selectedDiaryIds.size)
|
const totalSelected = computed(() => selectedIds.size + selectedDiaryIds.size)
|
||||||
const isMyAllSelected = computed(() => myFilteredRecipes.value.length > 0 && selectedDiaryIds.size === myFilteredRecipes.value.length)
|
const isMyAllSelected = computed(() => myFilteredRecipes.value.length > 0 && selectedDiaryIds.size === myFilteredRecipes.value.length)
|
||||||
const isPubAllSelected = computed(() => publicFilteredRecipes.value.length > 0 && selectedIds.size === publicFilteredRecipes.value.length)
|
const isPubAllSelected = computed(() => publicFilteredRecipes.value.length > 0 && selectedIds.size === publicFilteredRecipes.value.length)
|
||||||
@@ -1641,6 +1690,9 @@ watch(() => recipeStore.recipes, () => {
|
|||||||
.batch-menu-btn:hover { border-color: #7ec6a4; color: #4a9d7e; }
|
.batch-menu-btn:hover { border-color: #7ec6a4; color: #4a9d7e; }
|
||||||
.batch-delete { color: #c0392b; border-color: #e8b4b0; }
|
.batch-delete { color: #c0392b; border-color: #e8b4b0; }
|
||||||
.batch-delete:hover { background: #fdf0ee; border-color: #c0392b; }
|
.batch-delete:hover { background: #fdf0ee; border-color: #c0392b; }
|
||||||
|
.batch-tag-picker {
|
||||||
|
padding: 12px; background: #f8faf8; border: 1.5px solid #d4e8d4; border-radius: 10px; margin-bottom: 10px;
|
||||||
|
}
|
||||||
.mini-select {
|
.mini-select {
|
||||||
width: 18px; height: 18px; border: 1.5px solid #d4cfc7; border-radius: 4px;
|
width: 18px; height: 18px; border: 1.5px solid #d4cfc7; border-radius: 4px;
|
||||||
background: #fff; color: transparent; font-size: 11px; cursor: pointer;
|
background: #fff; color: transparent; font-size: 11px; cursor: pointer;
|
||||||
|
|||||||
Reference in New Issue
Block a user