171 lines
7.0 KiB
TypeScript
171 lines
7.0 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
|
||
interface StandardModelSkuZipUploadProps {
|
||
selectedFile: File | null
|
||
existingFileName?: string
|
||
existingFileSize?: string | number
|
||
uploading?: boolean
|
||
error?: string
|
||
onSelectFile: (file: File) => void
|
||
onRemoveFile: () => void
|
||
}
|
||
|
||
function formatFileSize(bytes: string | number | undefined): string {
|
||
const size = typeof bytes === 'string' ? parseInt(bytes, 10) : Number(bytes || 0)
|
||
if (!size || Number.isNaN(size)) {
|
||
return '未知'
|
||
}
|
||
|
||
const units = ['B', 'KB', 'MB', 'GB']
|
||
const index = Math.min(Math.floor(Math.log(size) / Math.log(1024)), units.length - 1)
|
||
return `${(size / Math.pow(1024, index)).toFixed(2)} ${units[index]}`
|
||
}
|
||
|
||
function isSupportedZipFile(file: File): boolean {
|
||
const lowerName = file.name.toLowerCase()
|
||
return ['.zip', '.rar', '.7z'].some((ext) => lowerName.endsWith(ext))
|
||
}
|
||
|
||
export default function StandardModelSkuZipUpload({
|
||
selectedFile,
|
||
existingFileName = '',
|
||
existingFileSize,
|
||
uploading = false,
|
||
error,
|
||
onSelectFile,
|
||
onRemoveFile,
|
||
}: StandardModelSkuZipUploadProps) {
|
||
const [isDragging, setIsDragging] = useState(false)
|
||
|
||
const hasExistingFile = !!existingFileName.trim()
|
||
|
||
const handleDrop = (event: React.DragEvent<HTMLLabelElement>) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
setIsDragging(false)
|
||
|
||
const file = event.dataTransfer.files?.[0]
|
||
if (!file || !isSupportedZipFile(file)) {
|
||
return
|
||
}
|
||
|
||
onSelectFile(file)
|
||
}
|
||
|
||
return (
|
||
<div className="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
<span className="inline-flex items-center rounded-full bg-blue-50 px-3 py-1 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-300">
|
||
模型压缩包 {selectedFile ? '已选择' : hasExistingFile ? '可重传' : '待上传'}
|
||
</span>
|
||
{uploading && (
|
||
<span className="inline-flex items-center rounded-full bg-amber-50 px-3 py-1 text-xs font-medium text-amber-700 dark:bg-amber-900/30 dark:text-amber-300">
|
||
上传处理中
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
<div className="mt-3 rounded-xl border border-dashed border-gray-200 bg-gray-50/80 px-3 py-2 text-xs text-gray-600 dark:border-gray-700 dark:bg-gray-900/30 dark:text-gray-300">
|
||
{selectedFile
|
||
? `待上传:${selectedFile.name}(${formatFileSize(selectedFile.size)})`
|
||
: hasExistingFile
|
||
? `当前模型:${existingFileName}(${formatFileSize(existingFileSize)})`
|
||
: '当前还没有模型压缩包,请先选择要上传的文件。'}
|
||
</div>
|
||
|
||
{error && (
|
||
<div className="mt-4 rounded-xl border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900/40 dark:bg-red-900/20 dark:text-red-300">
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
<div className="mt-5">
|
||
<input
|
||
id="standard-model-sku-zip-upload"
|
||
type="file"
|
||
accept=".zip,.rar,.7z"
|
||
className="hidden"
|
||
onChange={(event) => {
|
||
const file = event.target.files?.[0]
|
||
if (file && isSupportedZipFile(file)) {
|
||
onSelectFile(file)
|
||
}
|
||
event.currentTarget.value = ''
|
||
}}
|
||
/>
|
||
|
||
{/* biome-ignore lint/a11y/noStaticElementInteractions: 上传槽位需要拖拽和点击交互 */}
|
||
<label
|
||
htmlFor="standard-model-sku-zip-upload"
|
||
className={`group block min-h-40 cursor-pointer rounded-2xl border-2 border-dashed p-4 transition-colors ${
|
||
isDragging
|
||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
|
||
: 'border-gray-300 bg-gray-50 dark:border-gray-600 dark:bg-gray-800/40 hover:border-gray-400 dark:hover:border-gray-500'
|
||
}`}
|
||
onDragEnter={(event) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
setIsDragging(true)
|
||
}}
|
||
onDragLeave={(event) => {
|
||
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) {
|
||
setIsDragging(false)
|
||
}
|
||
}}
|
||
onDragOver={(event) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
event.dataTransfer.dropEffect = 'copy'
|
||
}}
|
||
onDrop={handleDrop}
|
||
>
|
||
{selectedFile ? (
|
||
<div className="flex min-h-32 flex-col items-center justify-center text-center">
|
||
<div className="relative flex w-full max-w-sm flex-col items-center rounded-2xl border border-gray-200 bg-white/90 px-4 py-6 dark:border-gray-700 dark:bg-gray-900/40">
|
||
<button
|
||
type="button"
|
||
aria-label="移除已选择的模型压缩包"
|
||
className="absolute right-3 top-3 inline-flex h-7 w-7 items-center justify-center rounded-full bg-black/55 text-sm font-semibold text-white transition hover:bg-black/70"
|
||
onClick={(event) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
onRemoveFile()
|
||
}}
|
||
>
|
||
×
|
||
</button>
|
||
<svg className="h-8 w-8 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||
</svg>
|
||
<p className="mt-3 max-w-full truncate text-sm font-medium text-gray-900 dark:text-white">
|
||
{selectedFile.name}
|
||
</p>
|
||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||
{formatFileSize(selectedFile.size)}
|
||
</p>
|
||
<p className="mt-3 text-[11px] text-gray-500 dark:text-gray-400">
|
||
点击或拖拽可重新选择模型压缩包
|
||
</p>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div className="flex min-h-32 flex-col items-center justify-center text-center">
|
||
<svg className="h-8 w-8 text-gray-400 group-hover:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||
</svg>
|
||
<span className="mt-3 text-sm font-medium text-gray-700 dark:text-gray-200">拖拽压缩包到这里,或点击上传</span>
|
||
<span className="mt-1 text-xs text-gray-500 dark:text-gray-400">支持 ZIP / RAR / 7Z</span>
|
||
</div>
|
||
)}
|
||
</label>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|