feat: app 端 ui 设计完成
This commit is contained in:
274
wei-ai-demo/pages/DeviceManager.tsx
Normal file
274
wei-ai-demo/pages/DeviceManager.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user