feat: app 端 ui 设计完成

This commit is contained in:
liqupan
2026-01-28 19:10:19 +08:00
commit a4e7898e94
149 changed files with 11302 additions and 0 deletions

View File

@@ -0,0 +1,274 @@
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;