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

@@ -1,233 +1,63 @@
"use client";
import React, { useEffect } from 'react';
import React from 'react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Upload, MessageSquare, Monitor } from 'lucide-react';
import { Upload, MessageSquare, Monitor, Github, ExternalLink } from 'lucide-react';
import Hero from '@/components/Hero';
import FileTransfer from '@/components/FileTransfer';
import TextTransfer from '@/components/TextTransfer';
import DesktopShare from '@/components/DesktopShare';
import { RoomStatusDisplay } from '@/components/RoomStatusDisplay';
import { TabSwitchDialog } from '@/components/TabSwitchDialog';
// Hooks
import { useFileTransfer } from '@/hooks/useFileTransfer';
import { useRoomManager } from '@/hooks/useRoomManager';
import { useFileSender } from '@/hooks/useFileSender';
import { useFileReceiver } from '@/hooks/useFileReceiver';
import { useWebSocketHandler } from '@/hooks/useWebSocketHandler';
import { useTabManager } from '@/hooks/useTabManager';
import { useUtilities } from '@/hooks/useUtilities';
import { useUrlHandler } from '@/hooks/useUrlHandler';
import { WebRTCFileTransfer } from '@/components/WebRTCFileTransfer';
import { Button } from '@/components/ui/button';
export default function HomePage() {
// 文件传输相关
const {
fileTransfers,
transferProgresses,
initFileTransfer,
receiveFileChunk,
completeFileDownload,
clearTransfers,
setTransferProgresses
} = useFileTransfer();
// 房间管理相关
const {
selectedFiles,
pickupCode,
pickupLink,
currentRole,
receiverFiles,
isConnecting,
roomStatus,
isConnected,
websocket,
setSelectedFiles,
setReceiverFiles,
setRoomStatus,
setIsConnecting,
setCurrentRole,
resetConnectingState,
generateCode,
joinRoom,
updateFileList,
handleRemoveFile,
clearFiles,
resetRoom,
sendMessage,
disconnect,
connect
} = useRoomManager();
// Tab管理相关
const {
activeTab,
showConfirmDialog,
setShowConfirmDialog,
handleTabChange,
confirmTabSwitch,
cancelTabSwitch,
getModeDescription,
updateUrlParams
} = useTabManager(isConnected, pickupCode, isConnecting);
// 工具函数
const { copyToClipboard, showNotification } = useUtilities();
// 文件发送处理
const { handleFileRequest } = useFileSender(selectedFiles, sendMessage);
// 文件接收处理
const { downloadFile } = useFileReceiver(
receiverFiles,
transferProgresses,
setTransferProgresses,
websocket,
sendMessage
);
// WebSocket连接状态变化处理
useEffect(() => {
resetConnectingState();
}, [resetConnectingState]);
// 额外的连接状态重置逻辑
useEffect(() => {
if (isConnected) {
setIsConnecting(false);
console.log('WebSocket已连接重置连接中状态');
}
}, [isConnected, setIsConnecting]);
// 监听WebSocket错误事件
useEffect(() => {
const handleWebSocketError = (event: CustomEvent) => {
console.error('WebSocket连接错误:', event.detail);
setIsConnecting(false);
showNotification('连接失败,请检查网络或重试', 'error');
};
const handleWebSocketConnected = (event: CustomEvent) => {
console.log('WebSocket连接成功:', event.detail);
setIsConnecting(false);
showNotification('连接成功!', 'success');
};
window.addEventListener('websocket-error', handleWebSocketError as EventListener);
window.addEventListener('websocket-connected', handleWebSocketConnected as EventListener);
return () => {
window.removeEventListener('websocket-error', handleWebSocketError as EventListener);
window.removeEventListener('websocket-connected', handleWebSocketConnected as EventListener);
};
}, [setIsConnecting, showNotification]);
// WebSocket消息处理
useWebSocketHandler({
currentRole,
setReceiverFiles,
setRoomStatus,
setIsConnecting,
initFileTransfer,
receiveFileChunk,
completeFileDownload,
handleFileRequest
});
// URL参数处理
useUrlHandler({
isConnected,
pickupCode,
setCurrentRole,
joinRoom
});
// 处理添加更多文件
const handleAddMoreFiles = () => {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.onchange = async (e) => {
const files = Array.from((e.target as HTMLInputElement).files || []);
const newFiles = [...selectedFiles, ...files];
setSelectedFiles(newFiles);
if (pickupCode && files.length > 0) {
updateFileList(newFiles);
}
};
input.click();
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50">
{/* Hero Section */}
<div className="relative min-h-screen">
{/* Background decorations */}
<div className="absolute inset-0 pointer-events-none">
<div className="absolute -top-40 -right-40 w-80 h-80 bg-gradient-to-br from-blue-400/20 to-indigo-600/20 rounded-full blur-3xl"></div>
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-gradient-to-br from-purple-400/20 to-pink-400/20 rounded-full blur-3xl"></div>
</div>
<div className="relative container mx-auto px-4 sm:px-6 py-8 max-w-6xl">
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50">
<div className="container mx-auto px-4 py-4 sm:py-6 md:py-8">
{/* Hero Section */}
<div className="text-center mb-6 sm:mb-8">
<Hero />
</div>
<div className="max-w-4xl mx-auto">
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
<TabsList className="grid w-full grid-cols-3 bg-white/80 backdrop-blur-sm border-0 shadow-lg h-12 sm:h-14 p-1 mb-6">
{/* Main Content */}
<div className="max-w-4xl mx-auto">
<Tabs defaultValue="webrtc" className="w-full">
{/* Tabs Navigation - 横向布局 */}
<div className="mb-6">
<TabsList className="grid w-full grid-cols-3 max-w-lg mx-auto h-auto bg-white/90 backdrop-blur-sm shadow-lg rounded-xl p-2 border border-slate-200">
<TabsTrigger
value="file"
className="flex items-center justify-center space-x-2 text-sm sm:text-base font-medium data-[state=active]:bg-gradient-to-r data-[state=active]:from-blue-500 data-[state=active]:to-indigo-500 data-[state=active]:text-white data-[state=active]:shadow-lg transition-all duration-300"
value="webrtc"
className="flex items-center justify-center space-x-2 px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 hover:bg-slate-50 data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:shadow-md data-[state=active]:hover:bg-blue-600"
>
<Upload className="w-4 h-4 sm:w-5 sm:h-5" />
<span className="hidden sm:inline"></span>
<Upload className="w-4 h-4" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</TabsTrigger>
<TabsTrigger
value="text"
className="flex items-center justify-center space-x-2 text-sm sm:text-base font-medium data-[state=active]:bg-gradient-to-r data-[state=active]:from-emerald-500 data-[state=active]:to-teal-500 data-[state=active]:text-white data-[state=active]:shadow-lg transition-all duration-300"
className="flex items-center justify-center space-x-2 px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 hover:bg-slate-50 data-[state=active]:bg-emerald-500 data-[state=active]:text-white data-[state=active]:shadow-md data-[state=active]:hover:bg-emerald-600"
>
<MessageSquare className="w-4 h-4 sm:w-5 sm:h-5" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
<MessageSquare className="w-4 h-4" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</TabsTrigger>
<TabsTrigger
value="desktop"
className="flex items-center justify-center space-x-2 text-sm sm:text-base font-medium data-[state=active]:bg-gradient-to-r data-[state=active]:from-purple-500 data-[state=active]:to-pink-500 data-[state=active]:text-white data-[state=active]:shadow-lg transition-all duration-300"
className="flex items-center justify-center space-x-2 px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 hover:bg-slate-50 data-[state=active]:bg-purple-500 data-[state=active]:text-white data-[state=active]:shadow-md data-[state=active]:hover:bg-purple-600"
>
<Monitor className="w-4 h-4 sm:w-5 sm:h-5" />
<Monitor className="w-4 h-4" />
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</TabsTrigger>
</TabsList>
</div>
<TabsContent value="file" className="mt-6 animate-fade-in-up">
<FileTransfer
selectedFiles={selectedFiles}
onFilesChange={setSelectedFiles}
onGenerateCode={generateCode}
pickupCode={pickupCode}
pickupLink={pickupLink}
onCopyCode={() => copyToClipboard(pickupCode, '取件码已复制到剪贴板!')}
onCopyLink={() => copyToClipboard(pickupLink, '取件链接已复制到剪贴板!')}
onAddMoreFiles={handleAddMoreFiles}
onRemoveFile={handleRemoveFile}
onClearFiles={clearFiles}
onReset={resetRoom}
onJoinRoom={joinRoom}
receiverFiles={receiverFiles}
onDownloadFile={downloadFile}
transferProgresses={transferProgresses}
isConnected={isConnected}
isConnecting={isConnecting}
disabled={isConnecting}
/>
<RoomStatusDisplay roomStatus={roomStatus} currentRole={currentRole} />
{/* Tab Content */}
<div>
<TabsContent value="webrtc" className="mt-0 animate-fade-in-up">
<WebRTCFileTransfer />
</TabsContent>
<TabsContent value="text" className="mt-6 animate-fade-in-up">
<TabsContent value="text" className="mt-0 animate-fade-in-up">
<TextTransfer
onSendText={async (text: string) => {
try {
@@ -242,63 +72,40 @@ export default function HomePage() {
const data = await response.json();
if (!response.ok) {
const errorMessage = data.error || '创建文字传输房间失败';
showNotification(errorMessage, 'error');
return '';
throw new Error(data.error || '创建文房间失败');
}
return data.code;
} catch (error) {
console.error('创建文字传输房间失败:', error);
showNotification('网络错误,请重试', 'error');
return '';
console.error('创建文房间失败:', error);
throw error;
}
}}
onReceiveText={async (code: string) => {
console.log('onReceiveText被调用但文字内容将通过WebSocket获取:', code);
return '';
}}
websocket={websocket}
isConnected={isConnected}
currentRole={currentRole}
onCreateWebSocket={(code: string, role: 'sender' | 'receiver') => {
if (websocket) {
disconnect();
try {
const response = await fetch(`/api/get-text-content?code=${code}`);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || '获取文本内容失败');
}
return data.text;
} catch (error) {
console.error('获取文本内容失败:', error);
throw error;
}
connect(code, role);
}}
/>
</TabsContent>
<TabsContent value="desktop" className="mt-6 animate-fade-in-up">
<DesktopShare
onStartSharing={async () => {
showNotification('桌面共享功能开发中', 'info');
return 'DEF456';
}}
onStopSharing={async () => {
showNotification('桌面共享已停止', 'info');
}}
onJoinSharing={async (code: string) => {
showNotification('桌面共享功能开发中', 'info');
}}
/>
<TabsContent value="desktop" className="mt-0 animate-fade-in-up">
<DesktopShare />
</TabsContent>
</Tabs>
</div>
<div className="h-8 sm:h-16"></div>
</div>
</Tabs>
</div>
</div>
{/* 确认对话框 */}
<TabSwitchDialog
open={showConfirmDialog}
onOpenChange={setShowConfirmDialog}
onConfirm={confirmTabSwitch}
onCancel={cancelTabSwitch}
description={getModeDescription()}
/>
</div>
);
}

View File

@@ -1,38 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
const GO_BACKEND_URL = process.env.GO_BACKEND_URL || 'http://localhost:8080';
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const code = searchParams.get('code');
if (!code) {
return NextResponse.json(
{ error: 'Missing code parameter' },
{ status: 400 }
);
}
console.log('API Route: Getting room info, proxying to:', `${GO_BACKEND_URL}/api/room-info?code=${code}`);
const response = await fetch(`${GO_BACKEND_URL}/api/room-info?code=${code}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const data = await response.json();
console.log('Backend response:', response.status, data);
return NextResponse.json(data, { status: response.status });
} catch (error) {
console.error('API Route Error:', error);
return NextResponse.json(
{ error: 'Failed to get room info', details: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
);
}
}

View File

@@ -1,38 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
const GO_BACKEND_URL = process.env.GO_BACKEND_URL || 'http://localhost:8080';
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const code = searchParams.get('code');
if (!code) {
return NextResponse.json(
{ error: 'Missing code parameter' },
{ status: 400 }
);
}
console.log('API Route: Getting room status, proxying to:', `${GO_BACKEND_URL}/api/room-status?code=${code}`);
const response = await fetch(`${GO_BACKEND_URL}/api/room-status?code=${code}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const data = await response.json();
console.log('Backend response:', response.status, data);
return NextResponse.json(data, { status: response.status });
} catch (error) {
console.error('API Route Error:', error);
return NextResponse.json(
{ error: 'Failed to get room status', details: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
);
}
}

View File

@@ -1,31 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
const GO_BACKEND_URL = process.env.GO_BACKEND_URL || 'http://localhost:8080';
export async function POST(request: NextRequest) {
try {
console.log('API Route: Updating files, proxying to:', `${GO_BACKEND_URL}/api/update-files`);
const body = await request.json();
const response = await fetch(`${GO_BACKEND_URL}/api/update-files`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const data = await response.json();
console.log('Backend response:', response.status, data);
return NextResponse.json(data, { status: response.status });
} catch (error) {
console.error('API Route Error:', error);
return NextResponse.json(
{ error: 'Failed to update files', details: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
);
}
}