feat添加切换tab提示

This commit is contained in:
MatrixSeven
2025-08-28 16:45:27 +08:00
parent 4bf0ce447d
commit 0fd8899fc6
6 changed files with 469 additions and 39 deletions

View File

@@ -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 (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50">
@@ -97,7 +95,7 @@ export default function HomePage() {
</div>
)}
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<Tabs value={activeTab} onValueChange={handleTabChangeWrapper} className="w-full">
{/* Tabs Navigation - 横向布局 */}
<div className="mb-6">
<TabsList className="grid w-full grid-cols-4 max-w-2xl mx-auto h-auto bg-white/90 backdrop-blur-sm shadow-lg rounded-xl p-2 border border-slate-200">
@@ -180,6 +178,20 @@ export default function HomePage() {
webrtcSupport={webrtcSupport}
/>
)}
{/* 自定义确认对话框 */}
{confirmDialogState && (
<ConfirmDialog
isOpen={confirmDialogState.isOpen}
onClose={closeConfirmDialog}
onConfirm={confirmDialogState.onConfirm}
title={confirmDialogState.title}
message={confirmDialogState.message}
confirmText={confirmDialogState.confirmText}
cancelText={confirmDialogState.cancelText}
type={confirmDialogState.type}
/>
)}
</div>
);
}

View File

@@ -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 <WifiOff className="w-6 h-6 text-red-500" />;
case 'warning':
return <AlertTriangle className="w-6 h-6 text-yellow-500" />;
case 'info':
return <Wifi className="w-6 h-6 text-blue-500" />;
default:
return <AlertTriangle className="w-6 h-6 text-yellow-500" />;
}
};
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 (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className="bg-white/95 backdrop-blur-md rounded-2xl shadow-2xl border border-white/20 max-w-md w-full mx-4 animate-in zoom-in-95 duration-200">
{/* Header */}
<div className="flex items-center space-x-4 p-6 pb-4">
<div className="flex-shrink-0">
{getIcon()}
</div>
<div>
<h3 className="text-lg font-semibold text-slate-800">
{title}
</h3>
</div>
</div>
{/* Content */}
<div className="px-6 pb-6">
<p className="text-slate-600 leading-relaxed">
{message}
</p>
</div>
{/* Actions */}
<div className="flex items-center justify-end space-x-3 px-6 pb-6">
<Button
variant="outline"
onClick={handleCancel}
className="px-6 py-2 border-slate-200 text-slate-600 hover:text-slate-800 hover:border-slate-300 rounded-lg"
>
{cancelText}
</Button>
<Button
onClick={handleConfirm}
className={`px-6 py-2 text-white font-medium rounded-lg shadow-lg transition-all duration-200 hover:shadow-xl ${getButtonStyles()}`}
>
{confirmText}
</Button>
</div>
</div>
</div>
);
}

View File

@@ -1,3 +1,5 @@
// UI状态管理相关的hooks
export { useURLHandler } from './useURLHandler';
export { useWebRTCStore } from './webRTCStore';
export { useTabNavigation } from './useTabNavigation';
export type { TabType } from './useTabNavigation';

View File

@@ -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<ConfirmDialogState | null>(null);
const showConfirmDialog = useCallback((options: ConfirmDialogOptions): Promise<boolean> => {
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,
};
};

View File

@@ -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<TabType, string> = {
webrtc: '文件传输',
message: '文字传输',
desktop: '桌面共享',
wechat: '微信群'
};
// WebRTC功能的映射
const WEBRTC_FEATURES: Record<string, FeatureType> = {
webrtc: 'webrtc',
message: 'message',
desktop: 'desktop'
};
export const useTabNavigation = () => {
const searchParams = useSearchParams();
const [activeTab, setActiveTab] = useState<TabType>('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
};
};

View File

@@ -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<FeatureType, { send: string; receive: string }> = {
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<FeatureType, string> = {
webrtc: '文件传输',
message: '文字传输',
desktop: '桌面共享'
};
interface UseURLHandlerProps<T = string> {
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 = <T = 'send' | 'receive'>({
featureType,
onModeChange,
onAutoJoinRoom,
onDisconnect,
modeConverter
}: UseURLHandlerProps<T>) => {
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 = <T = 'send' | 'receive'>({
updateMode,
updateRoomCode,
getCurrentRoomCode,
clearURLParams
clearURLParams,
switchToFeature,
hasActiveConnection,
// 导出对话框状态供组件使用
confirmDialogState: dialogState,
closeConfirmDialog: closeDialog
};
};