add image compression and client-side download cache
This commit is contained in:
parent
ea6d1b218c
commit
ad43d0f8bb
@ -211,6 +211,7 @@ const urlInputRef = ref<HTMLInputElement | null>(null)
|
||||
const uploadedImage = ref<File | null>(null)
|
||||
const uploadedImageUrl = ref<string>('')
|
||||
const resultImage = ref<string>('')
|
||||
const resultImageBlobUrl = ref<string>('') // 缓存的 blob URL,用于下载
|
||||
const imageUrl = ref<string>('')
|
||||
const resultImageUrl = computed(() => {
|
||||
if (!resultImage.value) return ''
|
||||
@ -459,6 +460,9 @@ const handleRemoveBackground = async () => {
|
||||
if (firstResult.success && firstResult.image_url) {
|
||||
resultImage.value = firstResult.image_url
|
||||
|
||||
// 缓存图片 blob URL 用于下载
|
||||
await cacheResultImage(firstResult.image_url)
|
||||
|
||||
// 添加到历史记录
|
||||
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
||||
const historyItem: HistoryItem = {
|
||||
@ -495,6 +499,30 @@ const handleRemoveBackground = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存结果图片为 blob URL,用于下载
|
||||
*/
|
||||
const cacheResultImage = async (imageUrl: string) => {
|
||||
try {
|
||||
// 清理旧的 blob URL
|
||||
if (resultImageBlobUrl.value) {
|
||||
URL.revokeObjectURL(resultImageBlobUrl.value)
|
||||
resultImageBlobUrl.value = ''
|
||||
}
|
||||
|
||||
// 获取图片并转换为 blob URL
|
||||
const response = await fetch(imageUrl)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch image: ${response.status}`)
|
||||
}
|
||||
const blob = await response.blob()
|
||||
resultImageBlobUrl.value = URL.createObjectURL(blob)
|
||||
} catch (error) {
|
||||
console.error('缓存图片失败:', error)
|
||||
// 缓存失败不影响显示,只是下载时需要重新获取
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (!resultImage.value) return
|
||||
|
||||
@ -506,15 +534,27 @@ const handleDownload = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(resultImage.value)
|
||||
const blob = await response.blob()
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
// 优先使用缓存的 blob URL
|
||||
let blobUrl = resultImageBlobUrl.value
|
||||
|
||||
// 如果没有缓存,则获取并缓存
|
||||
if (!blobUrl) {
|
||||
const response = await fetch(resultImage.value)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch image: ${response.status}`)
|
||||
}
|
||||
const blob = await response.blob()
|
||||
blobUrl = URL.createObjectURL(blob)
|
||||
resultImageBlobUrl.value = blobUrl
|
||||
}
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = blobUrl
|
||||
link.download = `removed-background-${Date.now()}.png`
|
||||
link.click()
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
// 不立即清理 blob URL,保留缓存供后续下载使用
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error)
|
||||
message.error('下载失败')
|
||||
}
|
||||
}
|
||||
@ -523,6 +563,11 @@ const resetUpload = () => {
|
||||
if (uploadedImageUrl.value && uploadedImageUrl.value.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(uploadedImageUrl.value)
|
||||
}
|
||||
// 清理结果图片的 blob URL 缓存
|
||||
if (resultImageBlobUrl.value) {
|
||||
URL.revokeObjectURL(resultImageBlobUrl.value)
|
||||
resultImageBlobUrl.value = ''
|
||||
}
|
||||
uploadedImage.value = null
|
||||
uploadedImageUrl.value = ''
|
||||
resultImage.value = ''
|
||||
@ -771,7 +816,7 @@ const handleUrlSubmit = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const selectHistoryItem = (index: number) => {
|
||||
const selectHistoryItem = async (index: number) => {
|
||||
if (index < 0 || index >= historyList.value.length) return
|
||||
|
||||
const item = historyList.value[index]
|
||||
@ -780,6 +825,11 @@ const selectHistoryItem = (index: number) => {
|
||||
resultImage.value = item.resultImage
|
||||
splitPosition.value = 0
|
||||
uploadedImage.value = item.originalImageFile
|
||||
|
||||
// 缓存历史记录的结果图片
|
||||
if (item.resultImage) {
|
||||
await cacheResultImage(item.resultImage)
|
||||
}
|
||||
}
|
||||
|
||||
const getHistoryThumbnailUrl = (item: HistoryItem): string => {
|
||||
@ -836,6 +886,10 @@ onUnmounted(() => {
|
||||
if (uploadedImageUrl.value && uploadedImageUrl.value.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(uploadedImageUrl.value)
|
||||
}
|
||||
// 清理结果图片的 blob URL 缓存
|
||||
if (resultImageBlobUrl.value) {
|
||||
URL.revokeObjectURL(resultImageBlobUrl.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -191,6 +191,7 @@ import { t } from '@/shared/i18n'
|
||||
import { get_session_api_headers } from '@/shared/api/auth'
|
||||
import { useAuthStore } from '@/shared/stores/auth'
|
||||
import { useSEO } from '@/shared/composables/useSEO'
|
||||
import { compressImageFile } from '@/shared/utils/imageResize'
|
||||
|
||||
const message = useMessage()
|
||||
const authStore = useAuthStore()
|
||||
@ -217,6 +218,7 @@ const urlInputRef = ref<HTMLInputElement | null>(null)
|
||||
const uploadedImage = ref<File | null>(null)
|
||||
const uploadedImageUrl = ref<string>('')
|
||||
const resultImage = ref<string>('')
|
||||
const resultImageBlobUrl = ref<string>('') // 缓存的 blob URL,用于下载
|
||||
const imageUrl = ref<string>('')
|
||||
const resultImageUrl = computed(() => {
|
||||
if (!resultImage.value) return ''
|
||||
@ -367,14 +369,27 @@ const handleUrlSubmit = async () => {
|
||||
}
|
||||
|
||||
// 转换为File对象
|
||||
const file = new File([blob], 'image-from-url', { type: blob.type })
|
||||
uploadedImage.value = file
|
||||
const originalFile = new File([blob], 'image-from-url', { type: blob.type })
|
||||
|
||||
// 创建预览URL
|
||||
uploadedImageUrl.value = URL.createObjectURL(blob)
|
||||
|
||||
// 开始处理
|
||||
await handleRemoveBackground()
|
||||
// 压缩图片到 1024x1024
|
||||
try {
|
||||
const compressedFile = await compressImageFile(originalFile, {
|
||||
maxWidth: 1024,
|
||||
maxHeight: 1024,
|
||||
quality: 0.92,
|
||||
mode: 'contain'
|
||||
})
|
||||
|
||||
uploadedImage.value = compressedFile
|
||||
uploadedImageUrl.value = URL.createObjectURL(compressedFile)
|
||||
|
||||
// 开始处理
|
||||
await handleRemoveBackground()
|
||||
} catch (error) {
|
||||
console.error('图片压缩失败:', error)
|
||||
message.error('图片处理失败,请重试')
|
||||
processing.value = false
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('加载图片URL失败:', error)
|
||||
let errorMessage = t('Failed to load image from URL')
|
||||
@ -402,6 +417,10 @@ onUnmounted(() => {
|
||||
if (uploadedImageUrl.value && uploadedImageUrl.value.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(uploadedImageUrl.value)
|
||||
}
|
||||
// 清理结果图片的 blob URL 缓存
|
||||
if (resultImageBlobUrl.value) {
|
||||
URL.revokeObjectURL(resultImageBlobUrl.value)
|
||||
}
|
||||
})
|
||||
|
||||
const triggerFileInput = () => {
|
||||
@ -458,7 +477,7 @@ const handleDrop = (event: DragEvent) => {
|
||||
}
|
||||
}
|
||||
|
||||
const processFile = (file: File) => {
|
||||
const processFile = async (file: File) => {
|
||||
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']
|
||||
if (!validTypes.includes(file.type)) {
|
||||
message.warning(t('Unsupported image format. Please use JPG, PNG, or WebP'))
|
||||
@ -471,17 +490,30 @@ const processFile = (file: File) => {
|
||||
return
|
||||
}
|
||||
|
||||
uploadedImage.value = file
|
||||
resultImage.value = ''
|
||||
splitPosition.value = 0
|
||||
currentHistoryIndex.value = -1
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
uploadedImageUrl.value = e.target?.result as string
|
||||
handleRemoveBackground()
|
||||
// 压缩图片到 1024x1024
|
||||
try {
|
||||
const compressedFile = await compressImageFile(file, {
|
||||
maxWidth: 1024,
|
||||
maxHeight: 1024,
|
||||
quality: 0.92,
|
||||
mode: 'contain'
|
||||
})
|
||||
|
||||
uploadedImage.value = compressedFile
|
||||
resultImage.value = ''
|
||||
splitPosition.value = 0
|
||||
currentHistoryIndex.value = -1
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
uploadedImageUrl.value = e.target?.result as string
|
||||
handleRemoveBackground()
|
||||
}
|
||||
reader.readAsDataURL(compressedFile)
|
||||
} catch (error) {
|
||||
console.error('图片压缩失败:', error)
|
||||
message.error('图片处理失败,请重试')
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
const resetUpload = () => {
|
||||
@ -489,6 +521,11 @@ const resetUpload = () => {
|
||||
if (uploadedImageUrl.value && uploadedImageUrl.value.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(uploadedImageUrl.value)
|
||||
}
|
||||
// 清理结果图片的 blob URL 缓存
|
||||
if (resultImageBlobUrl.value) {
|
||||
URL.revokeObjectURL(resultImageBlobUrl.value)
|
||||
resultImageBlobUrl.value = ''
|
||||
}
|
||||
uploadedImage.value = null
|
||||
uploadedImageUrl.value = ''
|
||||
resultImage.value = ''
|
||||
@ -504,7 +541,7 @@ const resetUpload = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const selectHistoryItem = (index: number) => {
|
||||
const selectHistoryItem = async (index: number) => {
|
||||
if (index < 0 || index >= historyList.value.length) return
|
||||
|
||||
const item = historyList.value[index]
|
||||
@ -513,6 +550,11 @@ const selectHistoryItem = (index: number) => {
|
||||
resultImage.value = item.resultImage
|
||||
splitPosition.value = 0
|
||||
uploadedImage.value = item.originalImageFile
|
||||
|
||||
// 缓存历史记录的结果图片
|
||||
if (item.resultImage) {
|
||||
await cacheResultImage(item.resultImage)
|
||||
}
|
||||
}
|
||||
|
||||
const getHistoryThumbnailUrl = (item: HistoryItem): string => {
|
||||
@ -581,6 +623,9 @@ const handleRemoveBackground = async () => {
|
||||
if (firstResult.success && firstResult.image_url) {
|
||||
resultImage.value = firstResult.image_url
|
||||
|
||||
// 缓存图片 blob URL 用于下载
|
||||
await cacheResultImage(firstResult.image_url)
|
||||
|
||||
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
||||
const historyItem: HistoryItem = {
|
||||
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
@ -633,18 +678,47 @@ const handleRemoveBackground = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存结果图片为 blob URL,用于下载
|
||||
*/
|
||||
const cacheResultImage = async (imageUrl: string) => {
|
||||
try {
|
||||
// 清理旧的 blob URL
|
||||
if (resultImageBlobUrl.value) {
|
||||
URL.revokeObjectURL(resultImageBlobUrl.value)
|
||||
resultImageBlobUrl.value = ''
|
||||
}
|
||||
|
||||
// 获取图片并转换为 blob URL
|
||||
const response = await fetch(imageUrl)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch image: ${response.status}`)
|
||||
}
|
||||
const blob = await response.blob()
|
||||
resultImageBlobUrl.value = URL.createObjectURL(blob)
|
||||
} catch (error) {
|
||||
console.error('缓存图片失败:', error)
|
||||
// 缓存失败不影响显示,只是下载时需要重新获取
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (!resultImage.value) return
|
||||
|
||||
try {
|
||||
// 获取图片数据并转换为 Blob,确保跨域图片也能下载
|
||||
const response = await fetch(resultImage.value)
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
// 优先使用缓存的 blob URL
|
||||
let blobUrl = resultImageBlobUrl.value
|
||||
|
||||
const blob = await response.blob()
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
// 如果没有缓存,则获取并缓存
|
||||
if (!blobUrl) {
|
||||
const response = await fetch(resultImage.value)
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
const blob = await response.blob()
|
||||
blobUrl = URL.createObjectURL(blob)
|
||||
resultImageBlobUrl.value = blobUrl
|
||||
}
|
||||
|
||||
// 创建下载链接
|
||||
const link = document.createElement('a')
|
||||
@ -655,10 +729,9 @@ const handleDownload = async () => {
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
|
||||
// 清理:移除 DOM 元素并释放 blob URL
|
||||
// 清理:移除 DOM 元素,但不释放 blob URL(保留缓存供后续下载使用)
|
||||
requestAnimationFrame(() => {
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('下载图片失败:', error)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user