feat:UI大调整,WEBRTC切换

This commit is contained in:
MatrixSeven
2025-08-02 21:56:14 +08:00
parent b43ea79c47
commit 3a4a762cc9
23 changed files with 2307 additions and 1461 deletions

View File

@@ -0,0 +1,413 @@
"use client";
import React, { useState, useRef, useCallback } from 'react';
import { Button } from '@/components/ui/button';
import { toast } from '@/hooks/use-toast';
import { Upload, FileText, Image, Video, Music, Archive, X } from 'lucide-react';
interface FileInfo {
id: string;
name: string;
size: number;
type: string;
status: 'ready' | 'downloading' | 'completed';
progress: number;
}
const getFileIcon = (mimeType: string) => {
if (mimeType.startsWith('image/')) return <Image className="w-5 h-5 text-white" />;
if (mimeType.startsWith('video/')) return <Video className="w-5 h-5 text-white" />;
if (mimeType.startsWith('audio/')) return <Music className="w-5 h-5 text-white" />;
if (mimeType.includes('zip') || mimeType.includes('rar')) return <Archive className="w-5 h-5 text-white" />;
return <FileText className="w-5 h-5 text-white" />;
};
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];
};
interface WebRTCFileUploadProps {
selectedFiles: File[];
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;
isConnected?: boolean;
isWebSocketConnected?: boolean;
}
export function WebRTCFileUpload({
selectedFiles,
onFilesChange,
onGenerateCode,
pickupCode,
pickupLink,
onCopyCode,
onCopyLink,
onAddMoreFiles,
onRemoveFile,
onClearFiles,
onReset,
disabled = false,
isConnected = false,
isWebSocketConnected = false
}: WebRTCFileUploadProps) {
const [isDragOver, setIsDragOver] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(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<HTMLInputElement>) => {
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 (
<div className="space-y-6">
{/* 功能标题和状态 */}
<div className="flex items-center">
<div className="flex items-center space-x-3 flex-1">
<div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-indigo-500 rounded-xl flex items-center justify-center">
<Upload className="w-5 h-5 text-white" />
</div>
<div>
<h2 className="text-lg font-semibold text-slate-800"></h2>
<p className="text-sm text-slate-600"></p>
</div>
</div>
{/* 竖线分割 */}
<div className="w-px h-12 bg-slate-200 mx-4"></div>
{/* 状态显示 */}
<div className="text-right">
<div className="text-sm text-slate-500 mb-1"></div>
<div className="flex items-center justify-end space-x-3 text-sm">
{/* WebSocket状态 */}
<div className="flex items-center space-x-1">
<div className="w-2 h-2 rounded-full bg-slate-400"></div>
<span className="text-slate-600">WS</span>
</div>
{/* 分隔符 */}
<div className="text-slate-300">|</div>
{/* WebRTC状态 */}
<div className="flex items-center space-x-1">
<div className="w-2 h-2 rounded-full bg-slate-400"></div>
<span className="text-slate-600">RTC</span>
</div>
</div>
</div>
</div>
<div
className={`upload-area rounded-xl p-6 sm:p-8 md:p-12 text-center cursor-pointer ${
isDragOver ? 'drag-active' : ''
}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={handleClick}
>
<div className={`transition-all duration-300 ${isDragOver ? 'scale-110' : ''}`}>
<div className="w-16 h-16 sm:w-20 sm:h-20 mx-auto mb-4 sm:mb-6 bg-gradient-to-br from-blue-100 to-indigo-100 rounded-full flex items-center justify-center">
<Upload className={`w-8 h-8 sm:w-10 sm:h-10 transition-colors duration-300 ${
isDragOver ? 'text-blue-600' : 'text-slate-400'
}`} />
</div>
<div className="space-y-2">
<p className="text-lg sm:text-xl font-medium text-slate-700">
{isDragOver ? '释放文件' : '拖拽文件到这里'}
</p>
<p className="text-sm sm:text-base text-slate-500">
<span className="text-blue-600 font-medium underline"></span>
</p>
<p className="text-xs sm:text-sm text-slate-400 mt-4">
WebRTC点对点传输
</p>
</div>
</div>
<input
ref={fileInputRef}
type="file"
multiple
className="hidden"
onChange={handleFileSelect}
disabled={disabled}
/>
</div>
</div>
);
}
return (
<div className="space-y-4 sm:space-y-6">
{/* 文件列表 */}
<div>
{/* 功能标题和状态 */}
<div className="flex items-center mb-4 sm:mb-6">
<div className="flex items-center space-x-3 flex-1">
<div className="w-10 h-10 bg-gradient-to-br from-emerald-500 to-teal-500 rounded-xl flex items-center justify-center">
<FileText className="w-5 h-5 text-white" />
</div>
<div>
<h3 className="text-lg font-semibold text-slate-800"></h3>
<p className="text-sm text-slate-500">{selectedFiles.length} </p>
</div>
</div>
{/* 竖线分割 */}
<div className="w-px h-12 bg-slate-200 mx-4"></div>
{/* 状态显示 */}
<div className="text-right">
<div className="text-sm text-slate-500 mb-1"></div>
<div className="flex items-center justify-end space-x-3 text-sm">
{/* WebSocket状态 */}
<div className="flex items-center space-x-1">
{isWebSocketConnected ? (
<>
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></div>
<span className="text-emerald-600">WS</span>
</>
) : (
<>
<div className="w-2 h-2 rounded-full bg-slate-400"></div>
<span className="text-slate-600">WS</span>
</>
)}
</div>
{/* 分隔符 */}
<div className="text-slate-300">|</div>
{/* WebRTC状态 */}
<div className="flex items-center space-x-1">
{isConnected ? (
<>
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></div>
<span className="text-emerald-600">RTC</span>
</>
) : pickupCode ? (
<>
<div className="w-2 h-2 rounded-full bg-orange-500 animate-pulse"></div>
<span className="text-orange-600">RTC</span>
</>
) : (
<>
<div className="w-2 h-2 rounded-full bg-slate-400"></div>
<span className="text-slate-600">RTC</span>
</>
)}
</div>
</div>
</div>
</div>
<div className="space-y-3 mb-4 sm:mb-6">
{selectedFiles.map((file, index) => (
<div
key={`${file.name}-${file.size}-${index}`}
className="group flex items-center justify-between p-3 sm:p-4 bg-gradient-to-r from-slate-50 to-blue-50 border border-slate-200 rounded-xl hover:shadow-md transition-all duration-200"
>
<div className="flex items-center space-x-3 sm:space-x-4 min-w-0 flex-1">
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-gradient-to-br from-blue-500 to-indigo-500 rounded-lg flex items-center justify-center flex-shrink-0">
{getFileIcon(file.type)}
</div>
<div className="min-w-0 flex-1">
<p className="font-medium text-slate-800 truncate text-sm sm:text-base">{file.name}</p>
<p className="text-xs sm:text-sm text-slate-500">{formatFileSize(file.size)}</p>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => removeFile(index)}
disabled={disabled}
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"
>
<X className="w-4 h-4" />
</Button>
</div>
))}
</div>
{/* 操作按钮 */}
<div className="flex flex-col sm:flex-row gap-3">
{!pickupCode && (
<>
<Button
onClick={onGenerateCode}
disabled={disabled || selectedFiles.length === 0}
className="button-primary text-white px-6 sm:px-8 py-3 rounded-xl font-medium flex-1 min-w-0 shadow-lg"
>
<Upload className="w-5 h-5 mr-2" />
</Button>
<Button
onClick={onAddMoreFiles}
variant="outline"
disabled={disabled}
className="px-6 sm:px-8 py-3 rounded-xl font-medium"
>
</Button>
<Button
onClick={onReset}
variant="outline"
disabled={disabled}
className="text-red-600 hover:bg-red-50 px-6 sm:px-8 py-3 rounded-xl font-medium"
>
</Button>
</>
)}
{pickupCode && (
<>
<Button
variant="outline"
onClick={onAddMoreFiles}
disabled={disabled}
className="px-6 py-3 rounded-xl border-slate-300 text-slate-600 hover:bg-slate-50 flex-1"
>
</Button>
{selectedFiles.length > 0 && onClearFiles && (
<Button
variant="outline"
onClick={onClearFiles}
disabled={disabled}
className="px-6 py-3 rounded-xl border-orange-300 text-orange-600 hover:bg-orange-50"
>
</Button>
)}
</>
)}
<Button
variant="outline"
onClick={onReset}
disabled={disabled}
className="px-6 py-3 rounded-xl border-red-300 text-red-600 hover:bg-red-50"
>
</Button>
</div>
</div>
{/* 取件码展示 */}
{pickupCode && (
<div className="border-t border-slate-200 pt-6">
<div className="text-center mb-4 sm:mb-6">
<div className="w-12 h-12 sm:w-16 sm:h-16 mx-auto mb-4 bg-gradient-to-br from-emerald-500 to-teal-500 rounded-2xl flex items-center justify-center animate-float">
<FileText className="w-6 h-6 sm:w-8 sm:h-8 text-white" />
</div>
<h3 className="text-xl sm:text-2xl font-bold bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent mb-2">
</h3>
<p className="text-sm sm:text-base text-slate-600"></p>
</div>
<div className="space-y-4 sm:space-y-6">
{/* 取件码 */}
<div>
<label className="block text-sm font-medium text-slate-700 mb-3"></label>
<div className="flex flex-col sm:flex-row gap-3">
<div className="flex-1 code-display rounded-xl p-4 sm:p-6 text-center">
<div className="text-2xl sm:text-3xl font-bold font-mono bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent tracking-wider">
{pickupCode}
</div>
</div>
<Button
onClick={onCopyCode}
className="px-4 sm:px-6 py-3 bg-emerald-500 hover:bg-emerald-600 text-white rounded-xl font-medium shadow-lg transition-all duration-200 hover:shadow-xl w-full sm:w-auto"
>
</Button>
</div>
</div>
{/* 取件链接 */}
{pickupLink && (
<div>
<label className="block text-sm font-medium text-slate-700 mb-3"></label>
<div className="flex flex-col sm:flex-row gap-3">
<div className="flex-1 code-display rounded-xl p-3 sm:p-4">
<div className="text-xs sm:text-sm text-slate-700 break-all font-mono">
{pickupLink}
</div>
</div>
<Button
onClick={onCopyLink}
className="px-4 sm:px-6 py-3 bg-blue-500 hover:bg-blue-600 text-white rounded-xl font-medium shadow-lg transition-all duration-200 hover:shadow-xl w-full sm:w-auto"
>
</Button>
</div>
</div>
)}
</div>
{/* 使用提示 */}
<div className="mt-4 sm:mt-6 p-3 sm:p-4 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl border border-blue-200">
<p className="text-xs sm:text-sm text-slate-600 text-center">
💡 <span className="font-medium">使</span>访
</p>
</div>
</div>
)}
</div>
);
}