diff --git a/chuan-next/src/app/HomePage.tsx b/chuan-next/src/app/HomePage.tsx
index 99f1d28..d7a7151 100644
--- a/chuan-next/src/app/HomePage.tsx
+++ b/chuan-next/src/app/HomePage.tsx
@@ -1,7 +1,6 @@
"use client";
import React, { useEffect, useState } from 'react';
-import { useSearchParams } from 'next/navigation';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Upload, MessageSquare, Monitor, Users } from 'lucide-react';
import Hero from '@/components/Hero';
@@ -10,12 +9,20 @@ import { WebRTCTextImageTransfer } from '@/components/WebRTCTextImageTransfer';
import DesktopShare from '@/components/DesktopShare';
import WeChatGroup from '@/components/WeChatGroup';
import { WebRTCUnsupportedModal } from '@/components/WebRTCUnsupportedModal';
+import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { useWebRTCSupport } from '@/hooks/connection';
+import { useTabNavigation, TabType } from '@/hooks/ui';
export default function HomePage() {
- const searchParams = useSearchParams();
- const [activeTab, setActiveTab] = useState('webrtc');
- const [hasInitialized, setHasInitialized] = useState(false);
+ // 使用tab导航hook
+ const {
+ activeTab,
+ handleTabChange,
+ getConnectionInfo,
+ hasInitialized,
+ confirmDialogState,
+ closeConfirmDialog
+ } = useTabNavigation();
// WebRTC 支持检测
const {
@@ -26,34 +33,25 @@ export default function HomePage() {
closeUnsupportedModal,
showUnsupportedModalManually,
} = useWebRTCSupport();
-
- // 根据URL参数设置初始标签(仅首次加载时)
- useEffect(() => {
- if (!hasInitialized) {
- const urlType = searchParams.get('type');
-
- console.log('=== HomePage URL处理 ===');
- console.log('URL type参数:', urlType);
- console.log('所有搜索参数:', Object.fromEntries(searchParams.entries()));
-
- // 将旧的text类型重定向到message
- if (urlType === 'text') {
- console.log('检测到text类型,重定向到message标签页');
- setActiveTab('message');
- } else if (urlType === 'webrtc') {
- // webrtc类型对应文件传输标签页
- console.log('检测到webrtc类型,切换到webrtc标签页(文件传输)');
- setActiveTab('webrtc');
- } else if (urlType && ['message', 'desktop'].includes(urlType)) {
- console.log('切换到对应标签页:', urlType);
- setActiveTab(urlType);
- } else {
- console.log('没有有效的type参数,使用默认标签页:webrtc(文件传输)');
- }
-
- setHasInitialized(true);
- }
- }, [searchParams, hasInitialized]);
+
+ // 桌面共享功能的占位符函数(保持向后兼容)
+ const handleStartSharing = async () => {
+ console.log('开始桌面共享');
+ };
+
+ const handleStopSharing = async () => {
+ console.log('停止桌面共享');
+ };
+
+ const handleJoinSharing = async (code: string) => {
+ console.log('加入桌面共享:', code);
+ };
+
+ // 处理Tabs组件的字符串参数
+ const handleTabChangeWrapper = (value: string) => {
+ // 类型转换并调用实际的处理函数
+ handleTabChange(value as TabType);
+ };
return (
@@ -97,7 +95,7 @@ export default function HomePage() {
)}
-
+
{/* Tabs Navigation - 横向布局 */}
@@ -180,6 +178,20 @@ export default function HomePage() {
webrtcSupport={webrtcSupport}
/>
)}
+
+ {/* 自定义确认对话框 */}
+ {confirmDialogState && (
+
+ )}
);
}
diff --git a/chuan-next/src/components/ui/confirm-dialog.tsx b/chuan-next/src/components/ui/confirm-dialog.tsx
new file mode 100644
index 0000000..8b36a71
--- /dev/null
+++ b/chuan-next/src/components/ui/confirm-dialog.tsx
@@ -0,0 +1,106 @@
+"use client";
+
+import React from 'react';
+import { Button } from '@/components/ui/button';
+import { AlertTriangle, Wifi, WifiOff } from 'lucide-react';
+
+interface ConfirmDialogProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onConfirm: () => void;
+ title: string;
+ message: string;
+ confirmText?: string;
+ cancelText?: string;
+ type?: 'warning' | 'danger' | 'info';
+}
+
+export function ConfirmDialog({
+ isOpen,
+ onClose,
+ onConfirm,
+ title,
+ message,
+ confirmText = '确认',
+ cancelText = '取消',
+ type = 'warning'
+}: ConfirmDialogProps) {
+ const handleConfirm = () => {
+ onConfirm();
+ onClose();
+ };
+
+ const handleCancel = () => {
+ onClose();
+ };
+
+ const getIcon = () => {
+ switch (type) {
+ case 'danger':
+ return ;
+ case 'warning':
+ return ;
+ case 'info':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getButtonStyles = () => {
+ switch (type) {
+ case 'danger':
+ return 'bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700';
+ case 'warning':
+ return 'bg-gradient-to-r from-yellow-500 to-orange-500 hover:from-yellow-600 hover:to-orange-600';
+ case 'info':
+ return 'bg-gradient-to-r from-blue-500 to-indigo-500 hover:from-blue-600 hover:to-indigo-600';
+ default:
+ return 'bg-gradient-to-r from-yellow-500 to-orange-500 hover:from-yellow-600 hover:to-orange-600';
+ }
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+ {/* Header */}
+
+
+ {getIcon()}
+
+
+
+ {title}
+
+
+
+
+ {/* Content */}
+
+
+ {/* Actions */}
+
+
+
+
+
+
+ );
+}
diff --git a/chuan-next/src/hooks/ui/index.ts b/chuan-next/src/hooks/ui/index.ts
index 6ae9dc9..4c5bff6 100644
--- a/chuan-next/src/hooks/ui/index.ts
+++ b/chuan-next/src/hooks/ui/index.ts
@@ -1,3 +1,5 @@
// UI状态管理相关的hooks
export { useURLHandler } from './useURLHandler';
export { useWebRTCStore } from './webRTCStore';
+export { useTabNavigation } from './useTabNavigation';
+export type { TabType } from './useTabNavigation';
diff --git a/chuan-next/src/hooks/ui/useConfirmDialog.ts b/chuan-next/src/hooks/ui/useConfirmDialog.ts
new file mode 100644
index 0000000..452a421
--- /dev/null
+++ b/chuan-next/src/hooks/ui/useConfirmDialog.ts
@@ -0,0 +1,52 @@
+import { useState, useCallback } from 'react';
+
+export interface ConfirmDialogOptions {
+ title: string;
+ message: string;
+ confirmText?: string;
+ cancelText?: string;
+ type?: 'warning' | 'danger' | 'info';
+}
+
+export interface ConfirmDialogState extends ConfirmDialogOptions {
+ isOpen: boolean;
+ onConfirm: () => void;
+ onCancel: () => void;
+}
+
+export const useConfirmDialog = () => {
+ const [dialogState, setDialogState] = useState(null);
+
+ const showConfirmDialog = useCallback((options: ConfirmDialogOptions): Promise => {
+ return new Promise((resolve) => {
+ const handleConfirm = () => {
+ setDialogState(null);
+ resolve(true);
+ };
+
+ const handleCancel = () => {
+ setDialogState(null);
+ resolve(false);
+ };
+
+ setDialogState({
+ ...options,
+ isOpen: true,
+ onConfirm: handleConfirm,
+ onCancel: handleCancel,
+ });
+ });
+ }, []);
+
+ const closeDialog = useCallback(() => {
+ if (dialogState) {
+ dialogState.onCancel();
+ }
+ }, [dialogState]);
+
+ return {
+ dialogState,
+ showConfirmDialog,
+ closeDialog,
+ };
+};
diff --git a/chuan-next/src/hooks/ui/useTabNavigation.ts b/chuan-next/src/hooks/ui/useTabNavigation.ts
new file mode 100644
index 0000000..cafe0bb
--- /dev/null
+++ b/chuan-next/src/hooks/ui/useTabNavigation.ts
@@ -0,0 +1,171 @@
+import { useState, useEffect, useCallback } from 'react';
+import { useSearchParams } from 'next/navigation';
+import { useURLHandler, FeatureType } from './useURLHandler';
+import { useWebRTCStore } from './webRTCStore';
+import { useConfirmDialog } from './useConfirmDialog';
+
+// Tab类型定义(包括非WebRTC功能)
+export type TabType = 'webrtc' | 'message' | 'desktop' | 'wechat';
+
+// Tab显示名称
+const TAB_NAMES: Record = {
+ webrtc: '文件传输',
+ message: '文字传输',
+ desktop: '桌面共享',
+ wechat: '微信群'
+};
+
+// WebRTC功能的映射
+const WEBRTC_FEATURES: Record = {
+ webrtc: 'webrtc',
+ message: 'message',
+ desktop: 'desktop'
+};
+
+export const useTabNavigation = () => {
+ const searchParams = useSearchParams();
+ const [activeTab, setActiveTab] = useState('webrtc');
+ const [hasInitialized, setHasInitialized] = useState(false);
+ const { showConfirmDialog, dialogState, closeDialog } = useConfirmDialog();
+
+ // 获取WebRTC全局状态
+ const {
+ isConnected,
+ isConnecting,
+ isPeerConnected,
+ currentRoom,
+ reset: resetWebRTCState
+ } = useWebRTCStore();
+
+ // 创建一个通用的URL处理器(用于断开连接)
+ const { hasActiveConnection } = useURLHandler({
+ featureType: 'webrtc', // 默认值,实际使用时会被覆盖
+ onModeChange: () => {},
+ });
+
+ // 根据URL参数设置初始标签(仅首次加载时)
+ useEffect(() => {
+ if (!hasInitialized) {
+ const urlType = searchParams.get('type');
+
+ console.log('=== HomePage URL处理 ===');
+ console.log('URL type参数:', urlType);
+ console.log('所有搜索参数:', Object.fromEntries(searchParams.entries()));
+
+ // 将旧的text类型重定向到message
+ if (urlType === 'text') {
+ console.log('检测到text类型,重定向到message标签页');
+ setActiveTab('message');
+ } else if (urlType === 'webrtc') {
+ console.log('检测到webrtc类型,切换到webrtc标签页(文件传输)');
+ setActiveTab('webrtc');
+ } else if (urlType && ['message', 'desktop'].includes(urlType)) {
+ console.log('切换到对应标签页:', urlType);
+ setActiveTab(urlType as TabType);
+ } else {
+ console.log('没有有效的type参数,使用默认标签页:webrtc(文件传输)');
+ // 保持默认的webrtc标签
+ }
+
+ setHasInitialized(true);
+ }
+ }, [searchParams, hasInitialized]);
+
+ // 处理tab切换
+ const handleTabChange = useCallback(async (newTab: TabType) => {
+ console.log('=== Tab切换 ===');
+ console.log('当前tab:', activeTab, '目标tab:', newTab);
+
+ // 如果切换到wechat tab(非WebRTC功能),可以直接切换
+ if (newTab === 'wechat') {
+ // 如果有活跃连接,需要确认
+ if (hasActiveConnection()) {
+ const currentTabName = TAB_NAMES[activeTab];
+ const confirmed = await showConfirmDialog({
+ title: '切换功能确认',
+ message: `切换到微信群功能需要关闭当前的${currentTabName}连接,是否继续?`,
+ confirmText: '确认切换',
+ cancelText: '取消',
+ type: 'warning'
+ });
+
+ if (!confirmed) {
+ return false;
+ }
+
+ // 断开连接并清除状态
+ resetWebRTCState();
+ console.log('已清除WebRTC连接状态,切换到微信群');
+ }
+
+ setActiveTab(newTab);
+ // 清除URL参数
+ const newUrl = new URL(window.location.href);
+ newUrl.search = '';
+ window.history.pushState({}, '', newUrl.toString());
+ return true;
+ }
+
+ // 如果有活跃连接且切换到不同的WebRTC功能,需要确认
+ if (hasActiveConnection() && newTab !== activeTab && WEBRTC_FEATURES[newTab]) {
+ const currentTabName = TAB_NAMES[activeTab];
+ const targetTabName = TAB_NAMES[newTab];
+
+ const confirmed = await showConfirmDialog({
+ title: '切换功能确认',
+ message: `切换到${targetTabName}功能需要关闭当前的${currentTabName}连接,是否继续?`,
+ confirmText: '确认切换',
+ cancelText: '取消',
+ type: 'warning'
+ });
+
+ if (!confirmed) {
+ return false;
+ }
+
+ // 用户确认后,重置WebRTC状态
+ resetWebRTCState();
+ console.log(`已断开${currentTabName}连接,切换到${targetTabName}`);
+ }
+
+ // 执行tab切换
+ setActiveTab(newTab);
+
+ // 更新URL(对于WebRTC功能)
+ if (WEBRTC_FEATURES[newTab]) {
+ const params = new URLSearchParams();
+ params.set('type', WEBRTC_FEATURES[newTab]);
+ params.set('mode', 'send'); // 默认模式
+ const newUrl = `?${params.toString()}`;
+ window.history.pushState({}, '', newUrl);
+ } else {
+ // 非WebRTC功能,清除URL参数
+ const newUrl = new URL(window.location.href);
+ newUrl.search = '';
+ window.history.pushState({}, '', newUrl.toString());
+ }
+
+ return true;
+ }, [activeTab, hasActiveConnection, resetWebRTCState]);
+
+ // 获取连接状态信息
+ const getConnectionInfo = useCallback(() => {
+ return {
+ hasConnection: hasActiveConnection(),
+ currentRoom: currentRoom,
+ isConnected,
+ isConnecting,
+ isPeerConnected
+ };
+ }, [hasActiveConnection, currentRoom, isConnected, isConnecting, isPeerConnected]);
+
+ return {
+ activeTab,
+ handleTabChange,
+ getConnectionInfo,
+ hasInitialized,
+ // 导出确认对话框状态
+ confirmDialogState: dialogState,
+ closeConfirmDialog: closeDialog
+ };
+};
diff --git a/chuan-next/src/hooks/ui/useURLHandler.ts b/chuan-next/src/hooks/ui/useURLHandler.ts
index 060ccbf..33945e3 100644
--- a/chuan-next/src/hooks/ui/useURLHandler.ts
+++ b/chuan-next/src/hooks/ui/useURLHandler.ts
@@ -1,21 +1,24 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { useToast } from '@/components/ui/toast-simple';
+import { useWebRTCStore } from './webRTCStore';
+import { useConfirmDialog } from './useConfirmDialog';
// 支持的功能类型
export type FeatureType = 'webrtc' | 'message' | 'desktop';
-// 支持的模式映射
-const MODE_MAPPINGS: Record = {
- webrtc: { send: 'send', receive: 'receive' },
- message: { send: 'send', receive: 'receive' },
- desktop: { send: 'send', receive: 'receive' } // desktop内部可能使用 share/view,但URL统一使用send/receive
+// 功能类型的显示名称
+const FEATURE_NAMES: Record = {
+ webrtc: '文件传输',
+ message: '文字传输',
+ desktop: '桌面共享'
};
interface UseURLHandlerProps {
featureType: FeatureType;
onModeChange: (mode: T) => void;
onAutoJoinRoom?: (code: string) => void;
+ onDisconnect?: () => void; // 新增:断开连接的回调
modeConverter?: {
// 将URL模式转换为组件内部模式
fromURL: (urlMode: 'send' | 'receive') => T;
@@ -28,12 +31,91 @@ export const useURLHandler = ({
featureType,
onModeChange,
onAutoJoinRoom,
+ onDisconnect,
modeConverter
}: UseURLHandlerProps) => {
const searchParams = useSearchParams();
const router = useRouter();
+ const { showToast } = useToast();
+ const { showConfirmDialog, dialogState, closeDialog } = useConfirmDialog();
const [hasProcessedInitialUrl, setHasProcessedInitialUrl] = useState(false);
const urlProcessedRef = useRef(false);
+
+ // 获取WebRTC全局状态
+ const {
+ isConnected,
+ isConnecting,
+ isPeerConnected,
+ currentRoom,
+ reset: resetWebRTCState
+ } = useWebRTCStore();
+
+ // 检查是否有活跃连接
+ const hasActiveConnection = useCallback(() => {
+ return isConnected || isConnecting || isPeerConnected;
+ }, [isConnected, isConnecting, isPeerConnected]);
+
+ // 功能切换确认
+ const switchToFeature = useCallback(async (targetFeatureType: FeatureType, mode?: 'send' | 'receive', code?: string) => {
+ // 如果是同一个功能,直接切换
+ if (targetFeatureType === featureType) {
+ if (mode) {
+ const params = new URLSearchParams(searchParams.toString());
+ params.set('type', targetFeatureType);
+ params.set('mode', mode);
+ if (code) {
+ params.set('code', code);
+ } else if (mode === 'send') {
+ params.delete('code');
+ }
+ router.push(`?${params.toString()}`, { scroll: false });
+ }
+ return true;
+ }
+
+ // 如果有活跃连接,需要确认
+ if (hasActiveConnection()) {
+ const currentFeatureName = FEATURE_NAMES[featureType];
+ const targetFeatureName = FEATURE_NAMES[targetFeatureType];
+
+ const confirmed = await showConfirmDialog({
+ title: '切换功能确认',
+ message: `切换到${targetFeatureName}功能需要关闭当前的${currentFeatureName}连接,是否继续?`,
+ confirmText: '确认切换',
+ cancelText: '取消',
+ type: 'warning'
+ });
+
+ if (!confirmed) {
+ return false;
+ }
+
+ // 用户确认后,断开当前连接
+ try {
+ if (onDisconnect) {
+ await onDisconnect();
+ }
+ resetWebRTCState();
+ showToast(`已断开${currentFeatureName}连接`, 'info');
+ } catch (error) {
+ console.error('断开连接时出错:', error);
+ showToast('断开连接时出错,但将继续切换功能', 'error');
+ }
+ }
+
+ // 切换到新功能
+ const params = new URLSearchParams();
+ params.set('type', targetFeatureType);
+ if (mode) {
+ params.set('mode', mode);
+ }
+ if (code) {
+ params.set('code', code);
+ }
+
+ router.push(`?${params.toString()}`, { scroll: false });
+ return true;
+ }, [featureType, hasActiveConnection, onDisconnect, resetWebRTCState, showToast, router, searchParams, showConfirmDialog]);
// 从URL参数中获取初始模式(仅在首次加载时处理)
useEffect(() => {
@@ -118,6 +200,11 @@ export const useURLHandler = ({
updateMode,
updateRoomCode,
getCurrentRoomCode,
- clearURLParams
+ clearURLParams,
+ switchToFeature,
+ hasActiveConnection,
+ // 导出对话框状态供组件使用
+ confirmDialogState: dialogState,
+ closeConfirmDialog: closeDialog
};
};