"use client" import type React from "react" import { useState, useRef, useCallback } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Alert, AlertDescription } from "@/components/ui/alert" import { Upload, X, AlertCircle, Eye, RotateCcw, FileImage } from "lucide-react" import { validateImageFile, createImageFile, revokeImageUrl, formatFileSize, compressImage, MAX_FILES_PER_UPLOAD, MAX_TOTAL_FILES, MAX_FILE_SIZE, type ImageFile, } from "@/lib/image-utils" import { soundManager } from "@/lib/sound-effects" interface ImageUploadProps { images: ImageFile[] onImagesChange: (images: ImageFile[]) => void disabled?: boolean className?: string } export default function ImageUpload({ images, onImagesChange, disabled = false, className = "" }: ImageUploadProps) { const [dragActive, setDragActive] = useState(false) const [uploading, setUploading] = useState(false) const [errors, setErrors] = useState([]) const fileInputRef = useRef(null) const handleFiles = useCallback( async (files: FileList) => { if (disabled) return setUploading(true) setErrors([]) const newErrors: string[] = [] const validFiles: File[] = [] // 檢查總數量限制 if (images.length + files.length > MAX_TOTAL_FILES) { newErrors.push(`最多只能上傳 ${MAX_TOTAL_FILES} 張圖片`) setErrors(newErrors) setUploading(false) return } // 檢查單次上傳數量 if (files.length > MAX_FILES_PER_UPLOAD) { newErrors.push(`單次最多只能上傳 ${MAX_FILES_PER_UPLOAD} 張圖片`) } // 驗證每個檔案 const filesToProcess = Array.from(files).slice(0, MAX_FILES_PER_UPLOAD) for (const file of filesToProcess) { const validation = validateImageFile(file) if (validation.isValid) { validFiles.push(file) } else { newErrors.push(`${file.name}: ${validation.error}`) } } if (newErrors.length > 0) { setErrors(newErrors) } if (validFiles.length > 0) { try { // 壓縮並創建圖片物件 const newImageFiles: ImageFile[] = [] for (const file of validFiles) { let processedFile = file // 如果檔案過大,嘗試壓縮 if (file.size > MAX_FILE_SIZE * 0.8) { try { processedFile = await compressImage(file, 1920, 0.8) } catch (error) { console.warn("圖片壓縮失敗,使用原檔案:", error) } } // 轉換為 base64 格式 const imageFile = await createImageFile(processedFile) newImageFiles.push(imageFile) } onImagesChange([...images, ...newImageFiles]) await soundManager.play("success") } catch (error) { newErrors.push("圖片處理失敗,請重試") setErrors(newErrors) } } setUploading(false) }, [images, onImagesChange, disabled], ) const handleDrag = useCallback((e: React.DragEvent) => { e.preventDefault() e.stopPropagation() }, []) const handleDragIn = useCallback((e: React.DragEvent) => { e.preventDefault() e.stopPropagation() if (e.dataTransfer.items && e.dataTransfer.items.length > 0) { setDragActive(true) } }, []) const handleDragOut = useCallback((e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setDragActive(false) }, []) const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setDragActive(false) if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { handleFiles(e.dataTransfer.files) } }, [handleFiles], ) const handleFileInput = useCallback( (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { handleFiles(e.target.files) } // 清空 input 值,允許重複選擇相同檔案 e.target.value = "" }, [handleFiles], ) const removeImage = useCallback( async (imageId: string) => { const imageToRemove = images.find((img) => img.id === imageId) if (imageToRemove) { revokeImageUrl(imageToRemove) onImagesChange(images.filter((img) => img.id !== imageId)) await soundManager.play("click") } }, [images, onImagesChange], ) const clearAllImages = useCallback(async () => { images.forEach(revokeImageUrl) onImagesChange([]) setErrors([]) await soundManager.play("click") }, [images, onImagesChange]) return (
{/* 上傳區域 */} !disabled && fileInputRef.current?.click()} >
{uploading ? (
) : ( )}

{dragActive ? "放開以上傳圖片" : "上傳相關圖片"}

{uploading ? "正在處理圖片..." : "拖拽圖片到此處或點擊選擇檔案"}

支援 JPG、PNG、WebP、GIF 單檔最大 5MB 最多 {MAX_TOTAL_FILES} 張
{/* 錯誤訊息 */} {errors.length > 0 && (
{errors.map((error, index) => (
• {error}
))}
)} {/* 已上傳的圖片 */} {images.length > 0 && (

已上傳圖片 ({images.length}/{MAX_TOTAL_FILES})

{images.length > 1 && ( )}
{images.map((image) => (
{image.name} { // 如果圖片載入失敗,顯示預設圖片 const target = e.target as HTMLImageElement target.src = "/placeholder.svg?height=200&width=200&text=圖片載入失敗" }} /> {/* 懸停覆蓋層 */}
{/* 檔案資訊 */}
{image.name}
{formatFileSize(image.size)}
))}
)}
) }