-
-
-
(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) => {