mirror of
https://github.com/MatrixSeven/file-transfer-go.git
synced 2026-05-20 12:27:30 +08:00
feat:UI大调整,WEBRTC切换
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user