feat: 新功能改进 #20

Merged
hera merged 57 commits from feat/next-improvements into main 2026-04-10 20:30:37 +00:00
Showing only changes of commit 8a447989ae - Show all commits

View File

@@ -87,8 +87,16 @@
<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 v-if="batchExistingTags.length" style="margin-top:8px">
<div style="font-size:12px;color:#999;margin-bottom:4px">点击移除已有标签</div>
<div class="editor-tags">
<span v-for="tag in batchExistingTags" :key="'rm-'+tag" class="editor-tag tag-removable" @click="batchTagsToRemove.includes(tag) ? batchTagsToRemove.splice(batchTagsToRemove.indexOf(tag),1) : batchTagsToRemove.push(tag)" :class="{ 'tag-marked-remove': batchTagsToRemove.includes(tag) }">
{{ tag }} <span style="margin-left:2px">{{ batchTagsToRemove.includes(tag) ? '✓移除' : '×' }}</span>
</span>
</div>
</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-primary action-btn-sm" @click="applyBatchTags">确认</button>
<button class="action-btn action-btn-sm" @click="showBatchTagPicker = false">取消</button>
</div>
</div>
@@ -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;