153 lines
5.2 KiB
TypeScript
153 lines
5.2 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import { FileArchive, Plus } from 'lucide-react'
|
||
import {
|
||
hasAllowedFileExtension,
|
||
hasDragLeftDropZone,
|
||
prepareFileDrag,
|
||
} from './drag-drop-utils'
|
||
import { dropZoneActiveClass, dropZoneIdleClass, fieldHintClass, fieldLabelClass } from './upload-field-styles'
|
||
|
||
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 {
|
||
return hasAllowedFileExtension(file, ['zip', 'rar', '7z'])
|
||
}
|
||
|
||
export default function StandardModelSkuZipUpload({
|
||
selectedFile,
|
||
existingFileName = '',
|
||
existingFileSize,
|
||
uploading = false,
|
||
error,
|
||
onSelectFile,
|
||
onRemoveFile,
|
||
}: StandardModelSkuZipUploadProps) {
|
||
const [isDragging, setIsDragging] = useState(false)
|
||
const [validationError, setValidationError] = useState('')
|
||
|
||
const hasExistingFile = !!existingFileName.trim()
|
||
|
||
const handleDrop = (event: React.DragEvent<HTMLLabelElement>) => {
|
||
prepareFileDrag(event, uploading)
|
||
setIsDragging(false)
|
||
if (uploading) {
|
||
setValidationError('文件正在上传,请等待完成后再更换')
|
||
return
|
||
}
|
||
|
||
const file = event.dataTransfer.files?.[0]
|
||
if (!file || !isSupportedZipFile(file)) {
|
||
setValidationError('压缩包仅支持 ZIP、RAR、7Z 格式')
|
||
return
|
||
}
|
||
|
||
setValidationError('')
|
||
onSelectFile(file)
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<label htmlFor="standard-model-sku-zip-upload" className={fieldLabelClass}>模型压缩包 *</label>
|
||
|
||
{(error || validationError) && (
|
||
<div className="mb-2 rounded-[8px] border border-[#fecdca] bg-[#fff1f0] px-3 py-2 text-sm text-[#b42318]">
|
||
{error || validationError}
|
||
</div>
|
||
)}
|
||
|
||
<div>
|
||
<input
|
||
id="standard-model-sku-zip-upload"
|
||
type="file"
|
||
accept=".zip,.rar,.7z"
|
||
className="hidden"
|
||
disabled={uploading}
|
||
onChange={(event) => {
|
||
const file = event.target.files?.[0]
|
||
if (file && isSupportedZipFile(file)) {
|
||
setValidationError('')
|
||
onSelectFile(file)
|
||
} else if (file) {
|
||
setValidationError('压缩包仅支持 ZIP、RAR、7Z 格式')
|
||
}
|
||
event.currentTarget.value = ''
|
||
}}
|
||
/>
|
||
|
||
{/* biome-ignore lint/a11y/noStaticElementInteractions: 上传槽位需要拖拽和点击交互 */}
|
||
<label
|
||
htmlFor="standard-model-sku-zip-upload"
|
||
aria-disabled={uploading}
|
||
className={`group block p-3 ${
|
||
uploading ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'
|
||
} ${isDragging ? dropZoneActiveClass : dropZoneIdleClass}`}
|
||
onDragEnter={(event) => {
|
||
prepareFileDrag(event, uploading)
|
||
if (uploading) return
|
||
setIsDragging(true)
|
||
}}
|
||
onDragLeave={(event) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
if (hasDragLeftDropZone(event)) {
|
||
setIsDragging(false)
|
||
}
|
||
}}
|
||
onDragOver={(event) => prepareFileDrag(event, uploading)}
|
||
onDrop={handleDrop}
|
||
>
|
||
{selectedFile ? (
|
||
<div className="flex min-h-12 items-center gap-3">
|
||
<FileArchive className="h-5 w-5 shrink-0 text-[#6b7075]" aria-hidden="true" />
|
||
<span className="min-w-0 flex-1 truncate text-sm font-medium text-[#1a1c1d]">{selectedFile.name}</span>
|
||
<span className={fieldHintClass}>{formatFileSize(selectedFile.size)}</span>
|
||
<button
|
||
type="button"
|
||
aria-label="移除已选择的模型压缩包"
|
||
disabled={uploading}
|
||
className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-[8px] text-[#b42318] hover:bg-[#fff1f0] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1677ff]"
|
||
onClick={(event) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
onRemoveFile()
|
||
}}
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div className="flex min-h-12 items-center gap-3">
|
||
<Plus className="h-5 w-5 text-[#6b7075]" aria-hidden="true" />
|
||
<span className="text-sm font-medium text-[#1a1c1d]">选择模型压缩包,或拖入文件</span>
|
||
</div>
|
||
)}
|
||
</label>
|
||
</div>
|
||
<p className={`mt-1.5 ${fieldHintClass}`}>
|
||
ZIP、RAR、7Z{hasExistingFile && !selectedFile ? ` · 当前模型:${existingFileName}(${formatFileSize(existingFileSize)})` : ''}
|
||
</p>
|
||
</div>
|
||
)
|
||
}
|