"use client"; import React, { useState, useRef, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Upload, FileText, Image, Video, Music, Archive, X } from 'lucide-react'; import RoomInfoDisplay from '@/components/RoomInfoDisplay'; import { ConnectionStatus } from '@/components/ConnectionStatus'; import type { FileInfo } from '@/types'; const getFileIcon = (mimeType: string) => { if (mimeType.startsWith('image/')) return ; if (mimeType.startsWith('video/')) return ; if (mimeType.startsWith('audio/')) return ; if (mimeType.includes('zip') || mimeType.includes('rar')) return ; return ; }; const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; const formatSpeed = (bytesPerSecond: number): string => { if (bytesPerSecond <= 0) return '--'; const k = 1024; if (bytesPerSecond < k) return `${bytesPerSecond.toFixed(0)} B/s`; if (bytesPerSecond < k * k) return `${(bytesPerSecond / k).toFixed(1)} KB/s`; if (bytesPerSecond < k * k * k) return `${(bytesPerSecond / (k * k)).toFixed(2)} MB/s`; return `${(bytesPerSecond / (k * k * k)).toFixed(2)} GB/s`; }; const formatETA = (seconds: number): string => { if (seconds <= 0 || !isFinite(seconds)) return '--'; if (seconds < 60) return `${Math.ceil(seconds)}秒`; if (seconds < 3600) { const m = Math.floor(seconds / 60); const s = Math.ceil(seconds % 60); return `${m}分${s > 0 ? s + '秒' : ''}`; } const h = Math.floor(seconds / 3600); const m = Math.ceil((seconds % 3600) / 60); return `${h}时${m > 0 ? m + '分' : ''}`; }; interface WebRTCFileUploadProps { selectedFiles: File[]; fileList?: FileInfo[]; // 添加文件列表信息(包含状态和进度) onFilesChange: (files: File[]) => void; onGenerateCode: () => void; pickupCode?: string; pickupLink?: string; onCopyCode?: () => void; onCopyLink?: () => void; onAddMoreFiles?: () => void; onRemoveFile?: (updatedFiles: File[]) => void; onClearFiles?: () => void; onReset?: () => void; disabled?: boolean; } export function WebRTCFileUpload({ selectedFiles, fileList = [], onFilesChange, onGenerateCode, pickupCode, pickupLink, onCopyCode, onCopyLink, onAddMoreFiles, onRemoveFile, onClearFiles, onReset, disabled = false }: WebRTCFileUploadProps) { const [isDragOver, setIsDragOver] = useState(false); const fileInputRef = useRef(null); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragOver(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); if (!e.currentTarget.contains(e.relatedTarget as Node)) { setIsDragOver(false); } }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragOver(false); const files = Array.from(e.dataTransfer.files); if (files.length > 0) { onFilesChange([...selectedFiles, ...files]); } }, [selectedFiles, onFilesChange]); const handleFileSelect = useCallback((e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); if (files.length > 0) { onFilesChange([...selectedFiles, ...files]); } }, [selectedFiles, onFilesChange]); const removeFile = useCallback((index: number) => { const updatedFiles = selectedFiles.filter((_, i) => i !== index); onFilesChange(updatedFiles); if (onRemoveFile) { onRemoveFile(updatedFiles); } }, [selectedFiles, onFilesChange, onRemoveFile]); const handleClick = useCallback(() => { if (fileInputRef.current) { fileInputRef.current.click(); } }, []); // 如果没有选择文件,显示上传区域 if (selectedFiles.length === 0 && !pickupCode) { return ( {/* 功能标题 */} 选择文件 拖拽文件到下方区域或点击选择文件 {isDragOver ? '释放文件' : '拖拽文件到这里'} 或者 点击选择文件 支持多个文件同时上传,WebRTC点对点传输 ); } return ( {/* 文件列表 */} {/* 功能标题和状态 */} {/* 标题部分 */} 已选择文件 {selectedFiles.length} 个文件准备传输 {/* 使用 ConnectionStatus 组件 */} {selectedFiles.map((file, index) => { // 查找对应的文件信息(包含状态和进度) const fileInfo = fileList.find(f => f.name === file.name && f.size === file.size); const isTransferringThisFile = fileInfo?.status === 'downloading'; const currentProgress = fileInfo?.progress || 0; const fileStatus = fileInfo?.status || 'ready'; const currentSpeed = fileInfo?.speed; const currentEta = fileInfo?.eta; return ( {getFileIcon(file.type)} {file.name} {formatFileSize(file.size)} {fileStatus === 'downloading' && ( 传输中 )} {fileStatus === 'completed' && ( 已完成 )} removeFile(index)} disabled={disabled || fileStatus === 'downloading'} className="opacity-0 group-hover:opacity-100 text-slate-400 hover:text-red-500 hover:bg-red-50 transition-all duration-200 flex-shrink-0 ml-2 disabled:opacity-50" > {/* 传输进度条 */} {(fileStatus === 'downloading' || fileStatus === 'completed') && currentProgress > 0 && ( {fileStatus === 'downloading' ? '正在发送...' : '发送完成'} {fileStatus === 'downloading' && currentSpeed != null && currentSpeed > 0 && ( {formatSpeed(currentSpeed)} )} {fileStatus === 'downloading' && currentEta != null && currentEta > 0 && ( 剩余 {formatETA(currentEta)} )} {currentProgress.toFixed(1)}% )} ); })} {/* 操作按钮 */} {!pickupCode && ( <> 生成取件码 添加文件 重新选择 > )} {pickupCode && ( <> 添加更多文件 {selectedFiles.length > 0 && onClearFiles && ( 清空文件 )} > )} 关闭房间 {/* 取件码展示 */} {pickupCode && pickupLink && ( {})} onCopyLink={onCopyLink || (() => {})} /> )} ); }
拖拽文件到下方区域或点击选择文件
{isDragOver ? '释放文件' : '拖拽文件到这里'}
或者 点击选择文件
支持多个文件同时上传,WebRTC点对点传输
{selectedFiles.length} 个文件准备传输
{file.name}
{formatFileSize(file.size)}