154 lines
6.2 KiB
TypeScript
154 lines
6.2 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import Image from 'next/image'
|
||
import {
|
||
STANDARD_MODEL_WHITE_BG_VIEWS,
|
||
type StandardModelWhiteBgKey,
|
||
} from '@/lib/standard-model-white-background'
|
||
|
||
interface StandardModelWhiteBackgroundUploadProps {
|
||
imageUrls: Partial<Record<StandardModelWhiteBgKey, string>>
|
||
onSelectImage: (key: StandardModelWhiteBgKey, file: File) => void
|
||
onRemoveImage: (key: StandardModelWhiteBgKey) => void
|
||
}
|
||
|
||
export default function StandardModelWhiteBackgroundUpload({
|
||
imageUrls,
|
||
onSelectImage,
|
||
onRemoveImage,
|
||
}: StandardModelWhiteBackgroundUploadProps) {
|
||
const [draggingKey, setDraggingKey] = useState<StandardModelWhiteBgKey | null>(null)
|
||
|
||
const handleDragEnter = (event: React.DragEvent, key: StandardModelWhiteBgKey) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
setDraggingKey(key)
|
||
}
|
||
|
||
const handleDragLeave = (event: React.DragEvent, key: StandardModelWhiteBgKey) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
|
||
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
|
||
const { clientX, clientY } = event
|
||
if (clientX <= rect.left || clientX >= rect.right || clientY <= rect.top || clientY >= rect.bottom) {
|
||
if (draggingKey === key) {
|
||
setDraggingKey(null)
|
||
}
|
||
}
|
||
}
|
||
|
||
const handleDragOver = (event: React.DragEvent) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
event.dataTransfer.dropEffect = 'copy'
|
||
}
|
||
|
||
const handleDrop = (event: React.DragEvent, key: StandardModelWhiteBgKey) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
setDraggingKey(null)
|
||
|
||
const file = event.dataTransfer.files?.[0]
|
||
if (!file) {
|
||
return
|
||
}
|
||
|
||
onSelectImage(key, file)
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<p className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
产品白底图(6张) *
|
||
</p>
|
||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||
{STANDARD_MODEL_WHITE_BG_VIEWS.map((view) => {
|
||
const inputId = `white-bg-${view.key}`
|
||
const imageUrl = imageUrls[view.key]
|
||
const isDragging = draggingKey === view.key
|
||
|
||
return (
|
||
<div key={view.key} className="space-y-2">
|
||
<p className="text-xs font-medium text-gray-700 dark:text-gray-300">{view.label}</p>
|
||
<input
|
||
id={inputId}
|
||
type="file"
|
||
accept="image/*"
|
||
className="hidden"
|
||
onChange={(event) => {
|
||
const file = event.target.files?.[0]
|
||
if (file) {
|
||
onSelectImage(view.key, file)
|
||
}
|
||
event.currentTarget.value = ''
|
||
}}
|
||
/>
|
||
{/* biome-ignore lint/a11y/noStaticElementInteractions: 上传槽位需要处理拖拽上传事件 */}
|
||
<div
|
||
className={`border-2 border-dashed rounded-apple p-3 transition-colors ${
|
||
isDragging
|
||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
|
||
: 'border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800/50 hover:border-gray-400 dark:hover:border-gray-500'
|
||
}`}
|
||
onDragEnter={(event) => handleDragEnter(event, view.key)}
|
||
onDragLeave={(event) => handleDragLeave(event, view.key)}
|
||
onDragOver={handleDragOver}
|
||
onDrop={(event) => handleDrop(event, view.key)}
|
||
>
|
||
{imageUrl ? (
|
||
<div className="space-y-2">
|
||
<div className="relative w-full h-24 rounded-apple overflow-hidden border-2 border-gray-200 dark:border-gray-600">
|
||
<Image
|
||
src={imageUrl}
|
||
alt={view.label}
|
||
fill
|
||
unoptimized
|
||
className="object-cover"
|
||
/>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
<label
|
||
htmlFor={inputId}
|
||
className="flex-1 text-center text-xs px-2 py-1 rounded-apple bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600 cursor-pointer transition-colors"
|
||
>
|
||
更换
|
||
</label>
|
||
<button
|
||
type="button"
|
||
className="text-xs px-2 py-1 rounded-apple bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/30 transition-colors"
|
||
onClick={() => onRemoveImage(view.key)}
|
||
>
|
||
删除
|
||
</button>
|
||
</div>
|
||
<p className="text-[11px] text-blue-600 dark:text-blue-400">支持拖拽图片替换</p>
|
||
</div>
|
||
) : (
|
||
<label
|
||
htmlFor={inputId}
|
||
className="group w-full h-24 rounded-apple bg-gray-100 dark:bg-gray-700 border-2 border-dashed border-gray-300 dark:border-gray-600 flex flex-col items-center justify-center hover:border-gray-400 dark:hover:border-gray-500 transition-colors cursor-pointer"
|
||
>
|
||
<svg className="w-5 h-5 text-gray-400 group-hover:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<title>上传{view.label}</title>
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||
</svg>
|
||
<span className="mt-1 text-xs text-gray-500 dark:text-gray-400">拖拽或点击上传</span>
|
||
</label>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||
白底图需一次上传完整6张,系统会自动打包为白底图压缩包用于下载。
|
||
</p>
|
||
<p className="mt-1 text-xs text-blue-600 dark:text-blue-400">
|
||
💡 每个角度支持拖拽上传,拖入后会自动替换对应位置图片。
|
||
</p>
|
||
</div>
|
||
)
|
||
}
|