diff --git a/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue b/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue index f99f98a..a854ac6 100644 --- a/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue +++ b/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue @@ -11,7 +11,6 @@

{{ t('Drop image anywhere to remove background') }}

-

{{ t('Remove Background') }}

-
-
-
(null) const uploadedImage = ref(null) const uploadedImageUrl = ref('') -const resultImage = ref('') // Base64编码的结果图片 +const resultImage = ref('') const resultImageUrl = computed(() => { if (!resultImage.value) return '' - // 如果已经是data URL格式,直接返回 if (resultImage.value.startsWith('data:')) { return resultImage.value } - // 否则添加data URL前缀 return `data:image/png;base64,${resultImage.value}` }) -// 历史记录 const historyList = ref([]) -const currentHistoryIndex = ref(-1) // -1 表示当前正在处理的图片,>=0 表示历史记录索引 - -// 状态 +const currentHistoryIndex = ref(-1) const isDragging = ref(false) const dragCounter = ref(0) const processing = ref(false) -const splitPosition = ref(0) // 分割线位置(百分比),0表示最左边,默认只显示结果图 +const splitPosition = ref(0) const comparisonContainerRef = ref(null) const originalImageRef = ref(null) const resultImageRef = ref(null) const isDraggingSplitLine = ref(false) -// 调整容器大小以匹配图片实际显示尺寸 const adjustContainerSize = () => { const container = comparisonContainerRef.value if (!container) return @@ -226,19 +205,16 @@ const adjustContainerSize = () => { const img = originalImageRef.value || resultImageRef.value if (!img) return - // 获取父元素 preview-section 的实际可用尺寸 const previewSection = container.closest('.preview-section') as HTMLElement if (!previewSection) return const previewRect = previewSection.getBoundingClientRect() - // 考虑 padding (12px * 2 = 24px) const padding = 24 const maxAvailableWidth = Math.max(0, previewRect.width - padding) const maxAvailableHeight = Math.max(0, previewRect.height - padding) if (maxAvailableWidth <= 0 || maxAvailableHeight <= 0) return - // 等待图片加载完成 if (!img.complete) { img.addEventListener('load', adjustContainerSize, { once: true }) return @@ -247,24 +223,18 @@ const adjustContainerSize = () => { const { naturalWidth, naturalHeight } = img if (naturalWidth === 0 || naturalHeight === 0) return - // 计算缩放比例,保持宽高比且不超过父元素可用空间 const scale = Math.min( maxAvailableWidth / naturalWidth, maxAvailableHeight / naturalHeight, - 1 // 不放大 + 1 ) - // 设置容器大小为计算出的尺寸 container.style.width = `${naturalWidth * scale}px` container.style.height = `${naturalHeight * scale}px` } -// 监听图片加载完成 -watch([uploadedImageUrl, resultImageUrl], () => { - adjustContainerSize() -}, { immediate: true }) +watch([uploadedImageUrl, resultImageUrl], adjustContainerSize, { immediate: true }) -// 监听窗口大小变化 const handleResize = () => { adjustContainerSize() } @@ -277,16 +247,13 @@ onUnmounted(() => { window.removeEventListener('resize', handleResize) }) -// 触发文件选择 const triggerFileInput = () => { if (fileInputRef.value) { - // 重置文件输入框的值,确保每次点击都能触发 change 事件 fileInputRef.value.value = '' fileInputRef.value.click() } } -// 处理文件选择 const handleFileSelect = (event: Event) => { const target = event.target as HTMLInputElement const file = target.files?.[0] @@ -295,14 +262,12 @@ const handleFileSelect = (event: Event) => { } } -// 判断拖拽是否包含文件 const hasFiles = (event: DragEvent) => { const types = event.dataTransfer?.types if (!types) return false return Array.from(types).includes('Files') } -// 处理拖拽 const handleDragEnter = (event: DragEvent) => { if (!hasFiles(event)) return dragCounter.value += 1 @@ -336,16 +301,13 @@ const handleDrop = (event: DragEvent) => { } } -// 处理文件 const processFile = (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')) return } - // 验证文件大小(最大10MB) const maxSize = 10 * 1024 * 1024 if (file.size > maxSize) { message.warning(t('Image size exceeds 10MB limit')) @@ -354,20 +316,17 @@ const processFile = (file: File) => { uploadedImage.value = file resultImage.value = '' - splitPosition.value = 0 // 重置分割线位置到最左边,默认显示完整结果图 - currentHistoryIndex.value = -1 // 重置为当前处理状态 + splitPosition.value = 0 + currentHistoryIndex.value = -1 - // 创建预览URL const reader = new FileReader() reader.onload = (e) => { uploadedImageUrl.value = e.target?.result as string - // 上传后自动触发去背景 handleRemoveBackground() } reader.readAsDataURL(file) } -// 重置上传 const resetUpload = () => { uploadedImage.value = null uploadedImageUrl.value = '' @@ -377,43 +336,33 @@ const resetUpload = () => { if (fileInputRef.value) { fileInputRef.value.value = '' } - // 重置容器大小 if (comparisonContainerRef.value) { comparisonContainerRef.value.style.width = '' comparisonContainerRef.value.style.height = '' } } -// 选择历史记录项 const selectHistoryItem = (index: number) => { if (index < 0 || index >= historyList.value.length) return const item = historyList.value[index] currentHistoryIndex.value = index - - // 恢复原图和结果图 uploadedImageUrl.value = item.originalImageUrl resultImage.value = item.resultImage splitPosition.value = 0 - - // 恢复文件对象(如果存在) uploadedImage.value = item.originalImageFile } -// 获取历史记录缩略图URL const getHistoryThumbnailUrl = (item: HistoryItem): string => { - // 优先显示结果图(去背景后的图片) if (item.resultImage) { if (item.resultImage.startsWith('data:')) { return item.resultImage } return `data:image/png;base64,${item.resultImage}` } - // 如果没有结果图,显示原图 return item.originalImageUrl } -// 拖动竖线处理 const handleSplitLineMouseDown = (e: MouseEvent) => { e.preventDefault() isDraggingSplitLine.value = true @@ -437,17 +386,12 @@ const handleSplitLineMouseDown = (e: MouseEvent) => { document.addEventListener('mouseup', handleMouseUp) } -// 处理去背景 const handleRemoveBackground = async () => { - // 检查用户是否已登录 if (!authStore.isLoggedIn) { message.error(t('Please login first to use this feature')) return } - // 与应用市场保持一致,不检查 cookie,直接调用 API - // 后端 whitelist 会处理认证,如果没有 cookie 会返回 401 - if (!uploadedImage.value) { message.warning(t('Please upload an image first')) return @@ -457,7 +401,6 @@ const handleRemoveBackground = async () => { resultImage.value = '' try { - // 将文件转换为base64 const reader = new FileReader() const base64Promise = new Promise((resolve, reject) => { reader.onload = (e) => { @@ -469,7 +412,6 @@ const handleRemoveBackground = async () => { reader.readAsDataURL(uploadedImage.value) const base64Data = await base64Promise - // 调用去背景API,需要认证时使用 withCredentials 发送 session cookie const response = await axios.post( '/jingrow.tools.tools.remove_background', { @@ -477,25 +419,20 @@ const handleRemoveBackground = async () => { }, { headers: get_session_api_headers(), - withCredentials: true, // 发送 session cookie 以进行认证 - timeout: 180000 // 3分钟超时 + withCredentials: true, + timeout: 180000 } ) - // whitelist 返回格式: {success: true, data: function_result} - // function_result 格式: {success: true/false, data: [...], ...} if (response.data?.success && response.data?.data) { const result = response.data.data - // 检查函数返回的 success 字段 if (result.success) { - // 检查 data 字段(数组格式) if (result.data && Array.isArray(result.data) && result.data.length > 0) { const firstResult = result.data[0] if (firstResult.success && firstResult.image_content) { resultImage.value = firstResult.image_content - // 将结果添加到历史记录(如果当前是新的处理,不是从历史记录中选择的) if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) { const historyItem: HistoryItem = { id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, @@ -507,7 +444,6 @@ const handleRemoveBackground = async () => { historyList.value.unshift(historyItem) currentHistoryIndex.value = 0 } else if (currentHistoryIndex.value >= 0) { - // 如果是从历史记录中选择的,更新该历史记录项的结果 historyList.value[currentHistoryIndex.value].resultImage = firstResult.image_content } @@ -519,18 +455,15 @@ const handleRemoveBackground = async () => { message.error(t('No image data returned')) } } else { - // 函数返回失败 message.error(result.error || t('Failed to remove background')) } } else { - // whitelist 包装失败 message.error(response.data?.error || response.data?.message || t('Failed to remove background')) } } catch (error: any) { let errorMessage = t('Failed to remove background') if (error.response) { - // 服务器返回了错误响应 if (error.response.data) { errorMessage = error.response.data.error || error.response.data.detail || @@ -543,10 +476,8 @@ const handleRemoveBackground = async () => { errorMessage = t('Authentication failed. Please login again.') } } else if (error.request) { - // 请求已发出但没有收到响应 errorMessage = t('Network error. Please check your connection.') } else { - // 其他错误 errorMessage = error.message || errorMessage } @@ -556,17 +487,14 @@ const handleRemoveBackground = async () => { } } -// 下载结果 const handleDownload = () => { if (!resultImage.value) return try { - // 提取 base64 数据 const base64Data = resultImage.value.includes(',') ? resultImage.value.split(',')[1] : resultImage.value - // 将 base64 转换为二进制数据 const byteCharacters = atob(base64Data) const byteNumbers = new Array(byteCharacters.length) for (let i = 0; i < byteCharacters.length; i++) { @@ -574,10 +502,6 @@ const handleDownload = () => { } const byteArray = new Uint8Array(byteNumbers) const blob = new Blob([byteArray], { type: 'image/png' }) - - // 使用 blob URL 创建下载链接 - // 注意:在 HTTP 环境下,浏览器可能会显示安全警告,这是正常的,不影响下载功能 - // 要消除警告,需要将网站升级到 HTTPS const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url @@ -586,7 +510,6 @@ const handleDownload = () => { document.body.appendChild(link) link.click() - // 延迟清理,确保下载开始后再清理 setTimeout(() => { document.body.removeChild(link) URL.revokeObjectURL(url) @@ -622,13 +545,12 @@ const removeHistoryItem = (index: number) => {