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 */} +
+

+ {message} +

+
+ + {/* 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 }; };