improve RemoveBackground tool UI/UX

This commit is contained in:
jingrow 2025-11-19 15:04:31 +08:00
parent cac901e235
commit 4bd17dd6e0

View File

@ -36,90 +36,66 @@
<!-- 图片预览和处理区域 --> <!-- 图片预览和处理区域 -->
<div v-else class="preview-section"> <div v-else class="preview-section">
<div class="image-preview-container"> <!-- 单窗口对比视图 -->
<div class="preview-header"> <div class="comparison-view" v-if="resultImage">
<h3>{{ t('Image Preview') }}</h3> <div class="comparison-container" ref="comparisonContainerRef">
<div class="header-actions"> <!-- 原图显示左侧部分 -->
<n-button <div
v-if="resultImage" class="comparison-image original-image"
size="medium" :style="{ clipPath: `inset(0 ${100 - splitPosition}% 0 0)` }"
@click="handleDownload" >
class="download-btn"
>
<template #icon>
<i class="fa fa-download"></i>
</template>
{{ t('Download') }}
</n-button>
<button class="change-image-btn" @click="resetUpload">
<i class="fa fa-refresh"></i>
{{ t('Change Image') }}
</button>
</div>
</div>
<!-- 单窗口对比视图 -->
<div class="comparison-view" v-if="resultImage">
<div class="comparison-container" ref="comparisonContainerRef">
<!-- 原图显示左侧部分 -->
<div
class="comparison-image original-image"
:style="{ clipPath: `inset(0 ${100 - splitPosition}% 0 0)` }"
>
<img :src="uploadedImageUrl" alt="Original" />
</div>
<!-- 去背景后的图片显示右侧部分 -->
<div
class="comparison-image result-image"
:style="{ clipPath: `inset(0 0 0 ${splitPosition}%)` }"
>
<img :src="resultImageUrl" alt="Result" />
</div>
<!-- 拖动竖线 -->
<div
class="split-line"
:class="{ 'dragging': isDraggingSplitLine }"
:style="{ left: `${splitPosition}%` }"
@mousedown.prevent.stop="handleSplitLineMouseDown"
>
<div class="split-line-handle">
<i class="fa fa-arrows-h"></i>
</div>
</div>
</div>
</div>
<!-- 处理中或未处理状态 -->
<div v-else class="single-image-view">
<div class="image-wrapper">
<img :src="uploadedImageUrl" alt="Original" /> <img :src="uploadedImageUrl" alt="Original" />
<div v-if="processing" class="processing-overlay"> </div>
<div class="spinner"></div>
<p>{{ t('Processing...') }}</p> <!-- 去背景后的图片显示右侧部分 -->
<div
class="comparison-image result-image"
:style="{ clipPath: `inset(0 0 0 ${splitPosition}%)` }"
>
<img :src="resultImageUrl" alt="Result" />
</div>
<!-- 拖动竖线 -->
<div
class="split-line"
:class="{ 'dragging': isDraggingSplitLine }"
:style="{ left: `${splitPosition}%` }"
@mousedown.prevent.stop="handleSplitLineMouseDown"
>
<div class="split-line-handle">
<i class="fa fa-arrows-h"></i>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 处理中或未处理状态 -->
<div v-else class="single-image-view">
<div class="image-wrapper">
<img :src="uploadedImageUrl" alt="Original" />
<div v-if="processing" class="processing-overlay">
<div class="spinner"></div>
<p>{{ t('Processing...') }}</p>
</div>
</div>
</div>
</div> </div>
</div> </div>
<!-- 使用说明 --> <!-- 操作按钮 -->
<div class="info-section"> <div v-if="uploadedImage" class="action-buttons">
<div class="info-card"> <button
<div class="info-icon"> v-if="resultImage"
<i class="fa fa-info-circle"></i> class="action-btn download-btn"
</div> @click="handleDownload"
<div class="info-content"> >
<h4>{{ t('How it works') }}</h4> <i class="fa fa-download"></i>
<ul> {{ t('Download') }}
<li>{{ t('Upload an image with a clear subject') }}</li> </button>
<li>{{ t('Click \"Remove Background\" to process') }}</li> <button class="action-btn change-image-btn" @click="resetUpload">
<li>{{ t('Download the result with transparent background') }}</li> <i class="fa fa-refresh"></i>
</ul> {{ t('Change Image') }}
</div> </button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -128,7 +104,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { NButton, useMessage } from 'naive-ui' import { useMessage } from 'naive-ui'
import axios from 'axios' import axios from 'axios'
import { t } from '@/shared/i18n' import { t } from '@/shared/i18n'
import { get_session_api_headers } from '@/shared/api/auth' import { get_session_api_headers } from '@/shared/api/auth'
@ -155,7 +131,7 @@ const resultImageUrl = computed(() => {
// //
const isDragging = ref(false) const isDragging = ref(false)
const processing = ref(false) const processing = ref(false)
const splitPosition = ref(50) // 线 const splitPosition = ref(0) // 线0
const comparisonContainerRef = ref<HTMLElement | null>(null) const comparisonContainerRef = ref<HTMLElement | null>(null)
const isDraggingSplitLine = ref(false) const isDraggingSplitLine = ref(false)
@ -210,7 +186,7 @@ const processFile = (file: File) => {
uploadedImage.value = file uploadedImage.value = file
resultImage.value = '' resultImage.value = ''
splitPosition.value = 50 // 线 splitPosition.value = 0 // 线
// URL // URL
const reader = new FileReader() const reader = new FileReader()
@ -227,7 +203,7 @@ const resetUpload = () => {
uploadedImage.value = null uploadedImage.value = null
uploadedImageUrl.value = '' uploadedImageUrl.value = ''
resultImage.value = '' resultImage.value = ''
splitPosition.value = 50 splitPosition.value = 0
if (fileInputRef.value) { if (fileInputRef.value) {
fileInputRef.value.value = '' fileInputRef.value.value = ''
} }
@ -492,6 +468,15 @@ h1 {
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
} }
/* 当显示图片时,移除 padding 和边框 */
.upload-section:has(.preview-section) {
padding: 0;
border-radius: 0;
border: none;
box-shadow: none;
background: transparent;
}
.upload-area { .upload-area {
border: 2px dashed #cbd5e1; border: 2px dashed #cbd5e1;
border-radius: 12px; border-radius: 12px;
@ -560,47 +545,11 @@ h1 {
/* 预览区域 */ /* 预览区域 */
.preview-section { .preview-section {
width: 100%; width: 100%;
}
.preview-header {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: space-between;
margin-bottom: 24px;
h3 {
font-size: 20px;
font-weight: 600;
color: #0f172a;
margin: 0;
}
} }
.change-image-btn {
height: 36px;
padding: 0 16px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: white;
color: #64748b;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
&:hover {
border-color: #1fc76f;
color: #1fc76f;
background: #f0fdf4;
}
i {
font-size: 12px;
}
}
.preview-grid { .preview-grid {
display: grid; display: grid;
@ -632,7 +581,6 @@ h1 {
/* 对比视图 */ /* 对比视图 */
.comparison-view { .comparison-view {
width: 100%; width: 100%;
margin-bottom: 24px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -640,11 +588,11 @@ h1 {
.comparison-container { .comparison-container {
position: relative; position: relative;
width: 60%; width: min(90vw, 1200px, calc(100vh - 200px));
max-width: 600px;
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 12px;
overflow: hidden; overflow: hidden;
border-radius: 12px;
border: 1px solid #e5e7eb;
background: background:
linear-gradient(45deg, #f1f5f9 25%, transparent 25%), linear-gradient(45deg, #f1f5f9 25%, transparent 25%),
linear-gradient(-45deg, #f1f5f9 25%, transparent 25%), linear-gradient(-45deg, #f1f5f9 25%, transparent 25%),
@ -727,7 +675,6 @@ h1 {
/* 单图视图 */ /* 单图视图 */
.single-image-view { .single-image-view {
width: 100%; width: 100%;
margin-bottom: 24px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -735,11 +682,11 @@ h1 {
.image-wrapper { .image-wrapper {
position: relative; position: relative;
width: 60%; width: min(90vw, 1200px, calc(100vh - 200px));
max-width: 600px;
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 8px;
overflow: hidden; overflow: hidden;
border-radius: 12px;
border: 1px solid #e5e7eb;
background: background:
linear-gradient(45deg, #f1f5f9 25%, transparent 25%), linear-gradient(45deg, #f1f5f9 25%, transparent 25%),
linear-gradient(-45deg, #f1f5f9 25%, transparent 25%), linear-gradient(-45deg, #f1f5f9 25%, transparent 25%),
@ -819,104 +766,44 @@ h1 {
gap: 12px; gap: 12px;
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
margin-top: 24px;
} }
.process-btn, .action-btn {
.download-btn { min-width: 140px;
min-width: 180px;
height: 48px;
font-size: 16px;
font-weight: 600;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(31, 199, 111, 0.2);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(31, 199, 111, 0.3);
}
}
.process-btn {
background: linear-gradient(135deg, #1fc76f 0%, #16a085 100%);
border: none;
color: white;
&:hover:not(:disabled) {
background: linear-gradient(135deg, #1dd87f 0%, #1ab894 100%);
}
}
.download-btn {
background: white;
border: 2px solid #1fc76f;
color: #1fc76f;
&:hover {
background: #f0fdf4;
border-color: #1dd87f;
color: #1dd87f;
}
}
/* 信息卡片 */
.info-section {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
border: 1px solid #e5e7eb;
}
.info-card {
display: flex;
gap: 16px;
align-items: flex-start;
}
.info-icon {
width: 40px;
height: 40px; height: 40px;
border-radius: 10px; padding: 0 20px;
background: #ecfdf5; border: 1px solid #e5e7eb;
display: flex; border-radius: 8px;
background: white;
color: #64748b;
cursor: pointer;
display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0; gap: 8px;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
i { i {
font-size: 20px;
color: #1fc76f;
}
}
.info-content {
flex: 1;
h4 {
font-size: 16px;
font-weight: 600;
color: #0f172a;
margin: 0 0 12px 0;
}
ul {
margin: 0;
padding-left: 20px;
color: #64748b;
font-size: 14px; font-size: 14px;
line-height: 1.8; }
li { &:hover {
margin-bottom: 8px; border-color: #1fc76f;
color: #1fc76f;
background: #f0fdf4;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(31, 199, 111, 0.15);
}
&:last-child { &:active {
margin-bottom: 0; transform: translateY(0);
}
}
} }
} }
/* 响应式设计 */ /* 响应式设计 */
@media (max-width: 768px) { @media (max-width: 768px) {
.remove-background-page { .remove-background-page {
@ -947,8 +834,7 @@ h1 {
.action-buttons { .action-buttons {
flex-direction: column; flex-direction: column;
.process-btn, .action-btn {
.download-btn {
width: 100%; width: 100%;
} }
} }