274 lines
14 KiB
TypeScript
274 lines
14 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
ChevronLeft,
|
|
Bluetooth,
|
|
RefreshCw,
|
|
Zap,
|
|
Smartphone,
|
|
Wifi,
|
|
Battery,
|
|
Settings,
|
|
Power,
|
|
Activity,
|
|
CheckCircle2
|
|
} from 'lucide-react';
|
|
|
|
interface DeviceManagerProps {
|
|
onBack: () => void;
|
|
}
|
|
|
|
type ScanStatus = 'scanning' | 'found' | 'connected';
|
|
|
|
interface Device {
|
|
id: string;
|
|
name: string;
|
|
signal: number;
|
|
isPaired: boolean;
|
|
}
|
|
|
|
const DeviceManager: React.FC<DeviceManagerProps> = ({ onBack }) => {
|
|
const [status, setStatus] = useState<ScanStatus>('scanning');
|
|
const [scannedDevices, setScannedDevices] = useState<Device[]>([]);
|
|
const [connectStep, setConnectStep] = useState(0); // 0: idle, 1: connecting, 2: success
|
|
|
|
// Simulation: Scan for devices
|
|
useEffect(() => {
|
|
if (status === 'scanning') {
|
|
const timer = setTimeout(() => {
|
|
setScannedDevices([
|
|
{ id: '1', name: 'Link-X Pro', signal: 92, isPaired: true },
|
|
{ id: '2', name: 'Unknown Signal (Weak)', signal: 30, isPaired: false },
|
|
]);
|
|
setStatus('found');
|
|
}, 2500);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [status]);
|
|
|
|
const handleConnect = (device: Device) => {
|
|
setConnectStep(1);
|
|
// Simulate connection delay
|
|
setTimeout(() => {
|
|
setConnectStep(2);
|
|
setTimeout(() => {
|
|
setStatus('connected');
|
|
setConnectStep(0);
|
|
}, 1000);
|
|
}, 1500);
|
|
};
|
|
|
|
const handleDisconnect = () => {
|
|
if(window.confirm('确定要断开与设备的连接吗?')) {
|
|
setStatus('scanning');
|
|
setScannedDevices([]);
|
|
}
|
|
};
|
|
|
|
const testVibration = () => {
|
|
if (window.navigator.vibrate) {
|
|
window.navigator.vibrate([200, 100, 200]);
|
|
}
|
|
alert('已发送震动测试指令');
|
|
};
|
|
|
|
// --- RENDER: Connected View ---
|
|
if (status === 'connected') {
|
|
return (
|
|
<div className="fixed inset-0 z-[60] bg-[#0F1014] flex flex-col h-full overflow-hidden animate-in fade-in duration-300">
|
|
{/* Header */}
|
|
<div className="relative z-20 pt-safe-top px-4 py-3 flex items-center justify-between border-b border-white/5 bg-[#0F1014]/80 backdrop-blur-md">
|
|
<button onClick={onBack} className="p-2 -ml-2 text-white/70 hover:text-white active:scale-95 transition-transform">
|
|
<ChevronLeft size={24} />
|
|
</button>
|
|
<h1 className="text-base font-bold text-white tracking-wider">设备管理</h1>
|
|
<div className="w-8"></div>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto no-scrollbar p-6">
|
|
|
|
{/* Device Visual Card */}
|
|
<div className="relative w-full aspect-video bg-[#1C1F26] rounded-2xl border border-white/10 overflow-hidden mb-6 flex items-center justify-center group">
|
|
<div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/carbon-fibre.png')] opacity-20"></div>
|
|
<div className="absolute inset-0 bg-gradient-to-br from-[#8B5CF6]/10 via-transparent to-[#0F1014]"></div>
|
|
|
|
{/* Glowing Center */}
|
|
<div className="relative z-10 flex flex-col items-center">
|
|
<div className="w-24 h-24 rounded-full bg-[#0F1014] border-2 border-[#8B5CF6] shadow-[0_0_30px_#8B5CF6] flex items-center justify-center mb-4 relative">
|
|
<Bluetooth size={40} className="text-white" />
|
|
<div className="absolute inset-0 rounded-full border border-[#8B5CF6] animate-ping opacity-20"></div>
|
|
</div>
|
|
<h2 className="text-xl font-bold text-white tracking-widest">Link-X Pro</h2>
|
|
<div className="flex items-center gap-1.5 mt-1">
|
|
<div className="w-1.5 h-1.5 rounded-full bg-[#10B981] animate-pulse"></div>
|
|
<span className="text-xs text-[#10B981] font-mono">已连接</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Status Grid */}
|
|
<div className="grid grid-cols-2 gap-4 mb-6">
|
|
<div className="bg-[#1C1F26]/60 p-4 rounded-xl border border-white/5 flex flex-col gap-2">
|
|
<div className="flex items-center gap-2 text-[#94A3B8]">
|
|
<Battery size={16} />
|
|
<span className="text-xs">剩余电量</span>
|
|
</div>
|
|
<span className="text-2xl font-bold text-white font-mono">85<span className="text-sm text-[#94A3B8]">%</span></span>
|
|
<div className="w-full h-1 bg-white/10 rounded-full overflow-hidden">
|
|
<div className="h-full w-[85%] bg-[#10B981]"></div>
|
|
</div>
|
|
</div>
|
|
<div className="bg-[#1C1F26]/60 p-4 rounded-xl border border-white/5 flex flex-col gap-2">
|
|
<div className="flex items-center gap-2 text-[#94A3B8]">
|
|
<Wifi size={16} />
|
|
<span className="text-xs">信号强度</span>
|
|
</div>
|
|
<span className="text-2xl font-bold text-white font-mono">-42<span className="text-sm text-[#94A3B8]">dBm</span></span>
|
|
<div className="flex gap-1 h-1 mt-auto">
|
|
<div className="flex-1 bg-[#8B5CF6] rounded-full"></div>
|
|
<div className="flex-1 bg-[#8B5CF6] rounded-full"></div>
|
|
<div className="flex-1 bg-[#8B5CF6] rounded-full"></div>
|
|
<div className="flex-1 bg-white/10 rounded-full"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<h3 className="text-xs font-bold text-[#64748B] uppercase tracking-widest mb-3">设备控制</h3>
|
|
<div className="space-y-3">
|
|
<button onClick={testVibration} className="w-full p-4 bg-[#1C1F26] active:bg-[#2D3039] rounded-xl flex items-center justify-between group transition-all border border-white/5 hover:border-[#8B5CF6]/50">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 rounded-lg bg-[#8B5CF6]/10 text-[#8B5CF6] group-hover:bg-[#8B5CF6] group-hover:text-white transition-colors">
|
|
<Activity size={18} />
|
|
</div>
|
|
<span className="text-sm text-white font-medium">震动马达测试</span>
|
|
</div>
|
|
<span className="text-xs text-[#64748B]">点击运行</span>
|
|
</button>
|
|
|
|
<button className="w-full p-4 bg-[#1C1F26] active:bg-[#2D3039] rounded-xl flex items-center justify-between group transition-all border border-white/5">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 rounded-lg bg-white/5 text-[#94A3B8] group-hover:text-white transition-colors">
|
|
<RefreshCw size={18} />
|
|
</div>
|
|
<div className="flex flex-col items-start">
|
|
<span className="text-sm text-white font-medium">固件更新</span>
|
|
<span className="text-[10px] text-[#64748B]">当前版本: v2.0.4 (最新)</span>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
|
|
<button className="w-full p-4 bg-[#1C1F26] active:bg-[#2D3039] rounded-xl flex items-center justify-between group transition-all border border-white/5">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 rounded-lg bg-white/5 text-[#94A3B8] group-hover:text-white transition-colors">
|
|
<Settings size={18} />
|
|
</div>
|
|
<span className="text-sm text-white font-medium">更多设置</span>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
|
|
<button
|
|
onClick={handleDisconnect}
|
|
className="w-full mt-8 p-4 rounded-xl border border-[#F43F5E]/30 text-[#F43F5E] flex items-center justify-center gap-2 text-sm font-bold active:bg-[#F43F5E]/10 transition-colors"
|
|
>
|
|
<Power size={16} /> 断开连接
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// --- RENDER: Scanning View ---
|
|
return (
|
|
<div className="fixed inset-0 z-[60] bg-[#0F1014] flex flex-col h-full overflow-hidden animate-in slide-in-from-right duration-300">
|
|
{/* Header */}
|
|
<div className="relative z-20 pt-safe-top px-4 py-3 flex items-center justify-between border-b border-white/5 bg-[#0F1014]/80 backdrop-blur-md">
|
|
<button onClick={onBack} className="p-2 -ml-2 text-white/70 hover:text-white active:scale-95 transition-transform">
|
|
<ChevronLeft size={24} />
|
|
</button>
|
|
<h1 className="text-base font-bold text-white tracking-wider">搜索设备</h1>
|
|
<div className="w-8">
|
|
{status === 'scanning' && <RefreshCw size={18} className="text-[#8B5CF6] animate-spin" />}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 flex flex-col items-center pt-12 px-6">
|
|
|
|
{/* Radar Animation */}
|
|
<div className="relative w-64 h-64 flex items-center justify-center mb-12">
|
|
{/* Rings */}
|
|
<div className="absolute inset-0 rounded-full border border-white/5"></div>
|
|
<div className="absolute inset-12 rounded-full border border-white/5"></div>
|
|
<div className="absolute inset-24 rounded-full border border-white/5"></div>
|
|
|
|
{/* Active Scan Line */}
|
|
{status === 'scanning' && (
|
|
<div className="absolute inset-0 rounded-full animate-[spin_3s_linear_infinite] bg-gradient-to-tr from-transparent via-transparent to-[#8B5CF6]/20 border-t border-[#8B5CF6]/50" style={{ clipPath: 'polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 50%)' }}></div>
|
|
)}
|
|
|
|
{/* Center Icon */}
|
|
<div className="relative z-10 w-20 h-20 bg-[#1C1F26] rounded-full flex items-center justify-center border border-white/10 shadow-[0_0_30px_rgba(0,0,0,0.5)]">
|
|
<Bluetooth size={32} className={`${status === 'scanning' ? 'text-[#8B5CF6] animate-pulse' : 'text-white'}`} />
|
|
</div>
|
|
|
|
{/* Status Text */}
|
|
<div className="absolute -bottom-10 left-0 w-full text-center">
|
|
<p className="text-xs text-[#94A3B8] font-mono tracking-wider">
|
|
{status === 'scanning' ? 'SCANNING FOR SIGNALS...' : 'SCAN COMPLETE'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Device List */}
|
|
<div className="w-full space-y-3">
|
|
{scannedDevices.map(device => (
|
|
<button
|
|
key={device.id}
|
|
onClick={() => handleConnect(device)}
|
|
disabled={connectStep !== 0}
|
|
className="w-full bg-[#1C1F26]/80 p-4 rounded-xl flex items-center justify-between border border-white/5 active:scale-[0.98] transition-all group hover:border-[#8B5CF6]/50"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-10 h-10 rounded-full bg-black/40 flex items-center justify-center text-white">
|
|
{connectStep === 1 ? <RefreshCw size={18} className="animate-spin text-[#8B5CF6]" /> : <Smartphone size={18} />}
|
|
</div>
|
|
<div className="flex flex-col items-start">
|
|
<span className="text-sm font-bold text-white group-hover:text-[#8B5CF6] transition-colors">{device.name}</span>
|
|
<div className="flex items-center gap-2 mt-0.5">
|
|
<div className="flex gap-0.5">
|
|
{[1,2,3,4].map(i => (
|
|
<div key={i} className={`w-0.5 h-2 rounded-full ${i*25 <= device.signal ? 'bg-[#10B981]' : 'bg-[#334155]'}`}></div>
|
|
))}
|
|
</div>
|
|
<span className="text-[10px] text-[#64748B]">RSSI {device.signal}dBm</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{device.isPaired && (
|
|
<span className="text-[10px] bg-[#8B5CF6]/20 text-[#8B5CF6] px-2 py-0.5 rounded border border-[#8B5CF6]/30">
|
|
已配对
|
|
</span>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Help */}
|
|
<div className="mt-auto pb-12 flex items-center gap-2 text-[#64748B] text-xs">
|
|
<Zap size={12} />
|
|
<span>请确保设备已开机并处于配对模式</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Full Screen Loading Overlay for Connection */}
|
|
{connectStep === 2 && (
|
|
<div className="absolute inset-0 z-50 bg-[#0F1014]/90 backdrop-blur-md flex flex-col items-center justify-center animate-in fade-in">
|
|
<CheckCircle2 size={48} className="text-[#10B981] mb-4 animate-bounce" />
|
|
<h3 className="text-lg font-bold text-white">连接成功</h3>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DeviceManager; |