114 lines
4.0 KiB
TypeScript
114 lines
4.0 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { Wifi, WifiOff, AlertCircle, Loader2 } from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { useNetworkStatus } from '../hooks/useNetworkStatus';
|
|
|
|
interface NetworkStatusBarProps {
|
|
className?: string;
|
|
}
|
|
|
|
export function NetworkStatusBar({ className = '' }: NetworkStatusBarProps) {
|
|
const { isOnline, isSlowConnection, queueLength } = useNetworkStatus();
|
|
const [showSlowWarning, setShowSlowWarning] = useState(false);
|
|
const [dismissed, setDismissed] = useState(false);
|
|
|
|
// 慢网络警告自动显示
|
|
useEffect(() => {
|
|
if (isSlowConnection && isOnline) {
|
|
setShowSlowWarning(true);
|
|
const timer = setTimeout(() => setShowSlowWarning(false), 5000);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [isSlowConnection, isOnline]);
|
|
|
|
// 离线时重置 dismissed
|
|
useEffect(() => {
|
|
if (!isOnline) setDismissed(false);
|
|
}, [isOnline]);
|
|
|
|
if (dismissed) return null;
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{!isOnline && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -40 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -40 }}
|
|
className={`fixed top-0 left-0 right-0 z-50 bg-red-500 text-white px-4 py-2 ${className}`}
|
|
>
|
|
<div className="max-w-7xl mx-auto flex items-center justify-between">
|
|
<div className="flex items-center gap-2 min-w-0">
|
|
<WifiOff className="w-4 h-4 flex-shrink-0" />
|
|
<span className="text-xs sm:text-sm font-medium whitespace-nowrap truncate">网络已断开</span>
|
|
{queueLength > 0 && (
|
|
<span className="text-[10px] sm:text-xs bg-red-600 px-1.5 sm:px-2 py-0.5 rounded-full whitespace-nowrap flex-shrink-0">
|
|
{queueLength}个待同步
|
|
</span>
|
|
)}
|
|
</div>
|
|
<button
|
|
onClick={() => setDismissed(true)}
|
|
className="text-[10px] sm:text-xs text-red-100 hover:text-white flex-shrink-0 ml-2"
|
|
>
|
|
忽略
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
|
|
{isOnline && showSlowWarning && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -40 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -40 }}
|
|
className={`fixed top-0 left-0 right-0 z-50 bg-yellow-500 text-white px-4 py-2 ${className}`}
|
|
>
|
|
<div className="max-w-7xl mx-auto flex items-center justify-between">
|
|
<div className="flex items-center gap-2 min-w-0">
|
|
<AlertCircle className="w-4 h-4 flex-shrink-0" />
|
|
<span className="text-xs sm:text-sm font-medium whitespace-nowrap truncate">网络较慢</span>
|
|
</div>
|
|
<button
|
|
onClick={() => setShowSlowWarning(false)}
|
|
className="text-[10px] sm:text-xs text-yellow-100 hover:text-white flex-shrink-0 ml-2"
|
|
>
|
|
忽略
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
|
|
{isOnline && queueLength > 0 && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -40 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -40 }}
|
|
className={`fixed top-0 left-0 right-0 z-50 bg-blue-500 text-white px-4 py-2 ${className}`}
|
|
>
|
|
<div className="max-w-7xl mx-auto flex items-center gap-2">
|
|
<Loader2 className="w-4 h-4 animate-spin flex-shrink-0" />
|
|
<span className="text-xs sm:text-sm font-medium whitespace-nowrap truncate">
|
|
同步{queueLength}个操作...
|
|
</span>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|
|
|
|
// 小型网络状态指示器(用于按钮等)
|
|
export function NetworkIndicator({ className = '' }: { className?: string }) {
|
|
const { isOnline } = useNetworkStatus();
|
|
|
|
if (isOnline) return null;
|
|
|
|
return (
|
|
<span className={`inline-flex items-center gap-1 text-xs text-red-500 ${className}`}>
|
|
<WifiOff className="w-3 h-3" />
|
|
离线
|
|
</span>
|
|
);
|
|
}
|