diff --git a/apps/jingrow/frontend/src/views/HomePage.vue b/apps/jingrow/frontend/src/views/HomePage.vue index ecde458..1a8d858 100644 --- a/apps/jingrow/frontend/src/views/HomePage.vue +++ b/apps/jingrow/frontend/src/views/HomePage.vue @@ -211,6 +211,7 @@ const urlInputRef = ref(null) const uploadedImage = ref(null) const uploadedImageUrl = ref('') const resultImage = ref('') +const resultImageBlobUrl = ref('') // 缓存的 blob URL,用于下载 const imageUrl = ref('') 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) + } }) diff --git a/apps/jingrow/frontend/src/views/tools/remove_background/remove_background.vue b/apps/jingrow/frontend/src/views/tools/remove_background/remove_background.vue index eeb28af..507555e 100644 --- a/apps/jingrow/frontend/src/views/tools/remove_background/remove_background.vue +++ b/apps/jingrow/frontend/src/views/tools/remove_background/remove_background.vue @@ -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(null) const uploadedImage = ref(null) const uploadedImageUrl = ref('') const resultImage = ref('') +const resultImageBlobUrl = ref('') // 缓存的 blob URL,用于下载 const imageUrl = ref('') 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)