批量打标签

This commit is contained in:
tianmo committed 2026-10-17 19:33:26 +08:00
1 parent 92aaf6fbb8
commit 5c1f640a2b
3 files changed
+59 -11

No files matched your search

+5
View File
@@ -180,6 +180,11 @@ const presetLabel = computed(() => {
</UBadge> </UBadge>
</p> </p>
<TagBadgeList
:tags="item.tags"
compact
/>
<UProgress <UProgress
v-if="running" v-if="running"
:model-value="item.transcodeProgress ?? 0" :model-value="item.transcodeProgress ?? 0"
+48 -5
View File
@@ -21,11 +21,15 @@ const {
loadActiveTranscodes, loadActiveTranscodes,
uploadFiles uploadFiles
} = useMediaUpload(props.kind) } = useMediaUpload(props.kind)
const { items: tagItems, fetchTags, batchUpdateTags } = useTags()
const { recordRecent } = useRecentTags()
const batchTagOpen = ref(false)
onMounted(() => { onMounted(() => {
void Promise.all([ void Promise.all([
loadActiveTranscodes(), loadActiveTranscodes(),
loadUploadPolicy().catch(() => {}) loadUploadPolicy().catch(() => {}),
fetchTags()
]) ])
}) })
@@ -89,6 +93,27 @@ async function removeItem(item: FileItem) {
toast.add({ title: t('files.deleteFailed'), color: 'error' }) toast.add({ title: t('files.deleteFailed'), color: 'error' })
} }
} }
async function confirmBatchTags(tagIds: number[]) {
const keys = sessionItems.value.map(item => item.key)
if (!keys.length || !tagIds.length) return
try {
await batchUpdateTags(keys, tagIds, 'add')
batchTagOpen.value = false
recordRecent(tagIds)
await fetchTags(true)
const addedTags = tagItems.value.filter(tag => tagIds.includes(tag.id))
sessionItems.value = sessionItems.value.map((item) => {
const merged = new Map((item.tags ?? []).map(tag => [tag.id, tag]))
for (const tag of addedTags) merged.set(tag.id, tag)
return { ...item, tags: [...merged.values()] }
})
toast.add({ title: t('tags.batchSuccess'), color: 'success' })
} catch {
toast.add({ title: t('tags.batchFailed'), color: 'error' })
}
}
</script> </script>
<template> <template>
@@ -112,10 +137,20 @@ async function removeItem(item: FileItem) {
v-if="sessionItems.length" v-if="sessionItems.length"
class="space-y-4" class="space-y-4"
> >
<h2 class="text-lg font-medium"> <div class="flex w-full items-center justify-between gap-2">
{{ t('upload.sessionTitle') }} <h2 class="text-lg font-medium">
<span class="ml-1 text-sm font-normal text-muted">({{ sessionItems.length }})</span> {{ t('upload.sessionTitle') }}
</h2> <span class="ml-1 text-sm font-normal text-muted">({{ sessionItems.length }})</span>
</h2>
<UButton
icon="i-lucide-tags"
variant="outline"
color="primary"
size="sm"
:label="t('tags.batchTagAll')"
@click="() => { batchTagOpen = true }"
/>
</div>
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> <div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<MediaSessionCard <MediaSessionCard
@@ -129,5 +164,13 @@ async function removeItem(item: FileItem) {
/> />
</div> </div>
</section> </section>
<BatchTagModal
v-model:open="batchTagOpen"
:tags="tagItems"
:selected-count="sessionItems.length"
add-only
@confirm="confirmBatchTags"
/>
</div> </div>
</template> </template>
+6 -6
View File
@@ -46,7 +46,7 @@ export default defineEventHandler(async (event) => {
const action = body?.action === 'remove' ? 'remove' : 'add' const action = body?.action === 'remove' ? 'remove' : 'add'
if (!keys.length) { if (!keys.length) {
createApiError(event, 'INVALID_REQUEST', '请提供图片列表', 400) createApiError(event, 'INVALID_REQUEST', '请提供媒体列表', 400)
} }
if (!tagIds.length) { if (!tagIds.length) {
createApiError(event, 'INVALID_REQUEST', '请提供标签列表', 400) createApiError(event, 'INVALID_REQUEST', '请提供标签列表', 400)
@@ -55,7 +55,7 @@ export default defineEventHandler(async (event) => {
createApiError( createApiError(
event, event,
'INVALID_REQUEST', 'INVALID_REQUEST',
`每次最多处理 ${MAX_DELETE_BATCH} 张图片`, `每次最多处理 ${MAX_DELETE_BATCH} 个媒体`,
400 400
) )
} }
@@ -64,7 +64,7 @@ export default defineEventHandler(async (event) => {
validateKeys(keys) validateKeys(keys)
} catch (error) { } catch (error) {
if (error instanceof Error && error.message.startsWith('INVALID_KEY:')) { if (error instanceof Error && error.message.startsWith('INVALID_KEY:')) {
createApiError(event, 'INVALID_IMAGE_KEY', `无效的图片路径: ${error.message.slice(12)}`, 400) createApiError(event, 'INVALID_IMAGE_KEY', `无效的媒体路径: ${error.message.slice(12)}`, 400)
} }
throw error throw error
} }
@@ -87,10 +87,10 @@ export default defineEventHandler(async (event) => {
logActivity(event, { logActivity(event, {
action: 'tags', action: 'tags',
key: 'images/batch-tags', key: 'media/batch-tags',
originalName: action === 'add' originalName: action === 'add'
? `批量打标(${keys.length} 张)` ? `批量打标(${keys.length} 个媒体)`
: `批量移除标签(${keys.length} 张)`, : `批量移除标签(${keys.length} 个媒体)`,
userId: user.id userId: user.id
}) })