add paste image and URL input support
This commit is contained in:
parent
f702b8596a
commit
870091a835
@ -56,6 +56,27 @@
|
|||||||
<h3>{{ t('Upload Image') }}</h3>
|
<h3>{{ t('Upload Image') }}</h3>
|
||||||
<p>{{ t('Drag and drop your image here, or click to browse') }}</p>
|
<p>{{ t('Drag and drop your image here, or click to browse') }}</p>
|
||||||
<p class="upload-hint">{{ t('Supports JPG, PNG, WebP formats') }}</p>
|
<p class="upload-hint">{{ t('Supports JPG, PNG, WebP formats') }}</p>
|
||||||
|
<div class="url-input-wrapper" @click.stop>
|
||||||
|
<div class="url-input-container">
|
||||||
|
<input
|
||||||
|
ref="urlInputRef"
|
||||||
|
v-model="imageUrl"
|
||||||
|
type="text"
|
||||||
|
class="url-input"
|
||||||
|
:placeholder="t('Or paste image URL here')"
|
||||||
|
@keyup.enter="handleUrlSubmit"
|
||||||
|
@paste="handleUrlPaste"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="url-submit-btn"
|
||||||
|
@click="handleUrlSubmit"
|
||||||
|
:disabled="!imageUrl.trim() || processing"
|
||||||
|
>
|
||||||
|
<i class="fa fa-arrow-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -176,9 +197,11 @@ interface HistoryItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fileInputRef = ref<HTMLInputElement | null>(null)
|
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
const urlInputRef = ref<HTMLInputElement | null>(null)
|
||||||
const uploadedImage = ref<File | null>(null)
|
const uploadedImage = ref<File | null>(null)
|
||||||
const uploadedImageUrl = ref<string>('')
|
const uploadedImageUrl = ref<string>('')
|
||||||
const resultImage = ref<string>('')
|
const resultImage = ref<string>('')
|
||||||
|
const imageUrl = ref<string>('')
|
||||||
const resultImageUrl = computed(() => {
|
const resultImageUrl = computed(() => {
|
||||||
if (!resultImage.value) return ''
|
if (!resultImage.value) return ''
|
||||||
// 直接返回图片URL
|
// 直接返回图片URL
|
||||||
@ -237,12 +260,132 @@ const handleResize = () => {
|
|||||||
adjustContainerSize()
|
adjustContainerSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handlePaste = async (event: ClipboardEvent) => {
|
||||||
|
const items = event.clipboardData?.items
|
||||||
|
if (!items) return
|
||||||
|
|
||||||
|
// 检查是否有图片数据
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const item = items[i]
|
||||||
|
if (item.type.indexOf('image') !== -1) {
|
||||||
|
event.preventDefault()
|
||||||
|
const file = item.getAsFile()
|
||||||
|
if (file) {
|
||||||
|
processFile(file)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有文本URL
|
||||||
|
const text = event.clipboardData?.getData('text')
|
||||||
|
if (text && isValidImageUrl(text)) {
|
||||||
|
event.preventDefault()
|
||||||
|
imageUrl.value = text.trim()
|
||||||
|
await handleUrlSubmit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidImageUrl = (url: string): boolean => {
|
||||||
|
if (!url || typeof url !== 'string') return false
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url)
|
||||||
|
const pathname = urlObj.pathname.toLowerCase()
|
||||||
|
const imageExtensions = ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp']
|
||||||
|
return imageExtensions.some(ext => pathname.endsWith(ext)) ||
|
||||||
|
urlObj.pathname.match(/\.(jpg|jpeg|png|webp|gif|bmp)(\?|$)/i) !== null
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUrlPaste = async (event: ClipboardEvent) => {
|
||||||
|
const text = event.clipboardData?.getData('text')
|
||||||
|
if (text && isValidImageUrl(text)) {
|
||||||
|
event.preventDefault()
|
||||||
|
imageUrl.value = text.trim()
|
||||||
|
await handleUrlSubmit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUrlSubmit = async () => {
|
||||||
|
const url = imageUrl.value.trim()
|
||||||
|
if (!url) {
|
||||||
|
message.warning(t('Please enter an image URL'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidImageUrl(url)) {
|
||||||
|
message.warning(t('Please enter a valid image URL'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
processing.value = true
|
||||||
|
resultImage.value = ''
|
||||||
|
splitPosition.value = 0
|
||||||
|
currentHistoryIndex.value = -1
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用代理或直接加载图片
|
||||||
|
const response = await fetch(url, { mode: 'cors' })
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to load image: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob()
|
||||||
|
|
||||||
|
// 验证文件类型
|
||||||
|
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']
|
||||||
|
if (!validTypes.includes(blob.type)) {
|
||||||
|
message.warning(t('Unsupported image format. Please use JPG, PNG, or WebP'))
|
||||||
|
processing.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件大小
|
||||||
|
const maxSize = 10 * 1024 * 1024
|
||||||
|
if (blob.size > maxSize) {
|
||||||
|
message.warning(t('Image size exceeds 10MB limit'))
|
||||||
|
processing.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为File对象
|
||||||
|
const file = new File([blob], 'image-from-url', { type: blob.type })
|
||||||
|
uploadedImage.value = file
|
||||||
|
|
||||||
|
// 创建预览URL
|
||||||
|
uploadedImageUrl.value = URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
// 开始处理
|
||||||
|
await handleRemoveBackground()
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('加载图片URL失败:', error)
|
||||||
|
let errorMessage = t('Failed to load image from URL')
|
||||||
|
|
||||||
|
if (error.message?.includes('CORS')) {
|
||||||
|
errorMessage = t('CORS error. The image server does not allow cross-origin access.')
|
||||||
|
} else if (error.message?.includes('Failed to load')) {
|
||||||
|
errorMessage = t('Failed to load image. Please check the URL and try again.')
|
||||||
|
}
|
||||||
|
|
||||||
|
message.error(errorMessage)
|
||||||
|
processing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('resize', handleResize)
|
window.addEventListener('resize', handleResize)
|
||||||
|
window.addEventListener('paste', handlePaste)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', handleResize)
|
window.removeEventListener('resize', handleResize)
|
||||||
|
window.removeEventListener('paste', handlePaste)
|
||||||
|
// 清理对象URL
|
||||||
|
if (uploadedImageUrl.value && uploadedImageUrl.value.startsWith('blob:')) {
|
||||||
|
URL.revokeObjectURL(uploadedImageUrl.value)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const triggerFileInput = () => {
|
const triggerFileInput = () => {
|
||||||
@ -326,9 +469,14 @@ const processFile = (file: File) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resetUpload = () => {
|
const resetUpload = () => {
|
||||||
|
// 清理对象URL
|
||||||
|
if (uploadedImageUrl.value && uploadedImageUrl.value.startsWith('blob:')) {
|
||||||
|
URL.revokeObjectURL(uploadedImageUrl.value)
|
||||||
|
}
|
||||||
uploadedImage.value = null
|
uploadedImage.value = null
|
||||||
uploadedImageUrl.value = ''
|
uploadedImageUrl.value = ''
|
||||||
resultImage.value = ''
|
resultImage.value = ''
|
||||||
|
imageUrl.value = ''
|
||||||
splitPosition.value = 0
|
splitPosition.value = 0
|
||||||
currentHistoryIndex.value = -1
|
currentHistoryIndex.value = -1
|
||||||
if (fileInputRef.value) {
|
if (fileInputRef.value) {
|
||||||
@ -812,6 +960,75 @@ const removeHistoryItem = (index: number) => {
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.url-input-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-input-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border-color: #1fc76f;
|
||||||
|
box-shadow: 0 0 0 3px rgba(31, 199, 111, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #1f2937;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-submit-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #1fc76f;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: #16a085;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.preview-section {
|
.preview-section {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -1224,6 +1441,25 @@ const removeHistoryItem = (index: number) => {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.url-input-wrapper {
|
||||||
|
max-width: 100%;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-input {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-submit-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.preview-section {
|
.preview-section {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user