Files
O96u 737235acff release: v1.4.0 — 标签系统、操作日志扩展与文档截图更新
- 每用户标签与彩色打标,图库 OR/AND 筛选与批量打标
- 操作日志记录登录、删除、设置与标签操作
- API 标签/打标接口;README 与文档站截图同步
2026-09-09 14:46:52 +08:00

124 lines
3.2 KiB
TypeScript

import type {
UploadProgressItem,
UploadResponse
} from '~/types/image'
import { isUnauthorizedError } from './useAuth'
import { prepareFilesForUpload } from './useClipboardImage'
const MAX_FILES = 10
export function useImageUpload() {
const toast = useToast()
const { t } = useI18n()
const { compressEnabled, clientWebpQuality, lastTagIds } = useUploadPreferences()
const uploading = ref(false)
const progressItems = ref<UploadProgressItem[]>([])
const lastUploadResult = ref<UploadResponse | null>(null)
async function uploadFiles(
files: File[],
options?: { notify?: boolean, tagIds?: number[] }
) {
if (uploading.value) return
if (!files.length) {
toast.add({ title: t('upload.selectImages'), color: 'warning' })
return
}
if (files.length > MAX_FILES) {
toast.add({
title: t('upload.maxFiles', { n: MAX_FILES }),
color: 'warning'
})
return
}
uploading.value = true
progressItems.value = files.map(file => ({
name: file.name,
status: 'pending',
progress: 0
}))
try {
const prepared = await prepareFilesForUpload(files, {
enabled: compressEnabled.value,
quality: clientWebpQuality.value / 100
})
progressItems.value = prepared.map((file, index) => ({
name: file.name,
status: 'uploading',
progress: Math.round(((index + 1) / prepared.length) * 50)
}))
const formData = new FormData()
for (const file of prepared) {
formData.append('files', file, file.name)
}
const tagIds = options?.tagIds ?? lastTagIds.value
if (tagIds.length) {
formData.append('tagIds', JSON.stringify(tagIds))
lastTagIds.value = tagIds
}
const response = await $fetch<UploadResponse>('/api/images/upload', {
method: 'POST',
body: formData
})
lastUploadResult.value = response
progressItems.value = prepared.map((file) => {
const failed = response.errors.find(error => error.name === file.name)
return {
name: file.name,
status: failed ? 'error' : 'success',
progress: 100,
error: failed?.error.message
}
})
if (response.items.length && options?.notify !== false) {
toast.add({
title: t('upload.uploadSuccess', { n: response.items.length }),
color: 'success'
})
}
if (response.errors.length) {
toast.add({
title: t('upload.uploadPartialFail', { n: response.errors.length }),
color: 'warning'
})
}
return response
} catch (error: unknown) {
if (isUnauthorizedError(error)) {
throw error
}
const message = error instanceof Error ? error.message : t('upload.uploadFailed')
progressItems.value = progressItems.value.map(item => ({
...item,
status: 'error',
progress: 100,
error: message
}))
toast.add({ title: message, color: 'error' })
throw error
} finally {
uploading.value = false
}
}
return {
uploading,
progressItems,
lastUploadResult,
uploadFiles
}
}