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,57 @@
import React from 'react';
import { Compass, PlayCircle, Radio, User } from 'lucide-react';
import { UserTab } from '../types';
interface BottomNavProps {
currentTab: UserTab;
onTabChange: (tab: UserTab) => void;
}
const BottomNav: React.FC<BottomNavProps> = ({ currentTab, onTabChange }) => {
const navItems = [
{ id: UserTab.Discovery, icon: Compass, label: '发现' },
{ id: UserTab.Library, icon: PlayCircle, label: '剧本' },
{ id: UserTab.Control, icon: Radio, label: '操控' },
{ id: UserTab.Profile, icon: User, label: '我的' },
];
return (
<div className="fixed bottom-0 left-0 w-full z-50">
{/* Main Bar Content - High Transparency Glass */}
<div className="bg-black/20 backdrop-blur-2xl border-t border-white/10 pb-safe">
<div className="flex justify-around items-center h-[60px]">
{navItems.map((item) => {
const isActive = currentTab === item.id;
return (
<button
key={item.id}
onClick={() => onTabChange(item.id)}
className="relative flex-1 flex flex-col items-center justify-center h-full group"
>
{/* Active Glow Background */}
{isActive && (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-12 h-12 bg-white/10 rounded-full blur-lg"></div>
)}
{/* Icon */}
<div className={`transition-all duration-300 relative z-10 ${isActive ? 'text-white scale-110 drop-shadow-[0_0_10px_rgba(255,255,255,0.6)]' : 'text-white/50 group-hover:text-white/80'}`}>
<item.icon
size={26}
strokeWidth={isActive ? 2.5 : 1.5}
/>
</div>
{/* Label */}
<span className={`text-[10px] font-medium mt-1 transition-colors duration-300 relative z-10 ${isActive ? 'text-white' : 'text-white/40'}`}>
{item.label}
</span>
</button>
);
})}
</div>
</div>
</div>
);
};
export default BottomNav;

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { Zap, Bluetooth } from 'lucide-react';
import { DeviceStatus } from '../types';
interface HardwareStatusProps {
status: DeviceStatus;
onClick?: () => void;
className?: string;
}
const HardwareStatus: React.FC<HardwareStatusProps> = ({ status, onClick, className = '' }) => {
return (
<button
onClick={onClick}
className={`flex items-center justify-end transition-all duration-500 shrink-0 ${className}`}
>
<div className={`
relative flex items-center h-7 pl-3 pr-3 rounded-full
bg-[#1A1625]/80 backdrop-blur-md border border-white/10 shadow-sm
active:scale-95 transition-all duration-300 group hover:border-[#A855F7]/30
`}>
{/* Connection Status Icon */}
<div className="relative flex items-center justify-center w-3 h-3 mr-2">
{status.connected ? (
<>
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#10B981] opacity-20"></span>
<Bluetooth size={12} className="text-[#10B981] relative z-10" />
</>
) : (
<div className="w-1.5 h-1.5 rounded-full bg-gray-600"></div>
)}
</div>
{/* Divider */}
<div className="w-[1px] h-2.5 bg-white/10 mr-2"></div>
{/* Battery Status */}
{status.connected ? (
<div className="flex items-center gap-1">
<span className={`text-[10px] font-mono font-bold tracking-wide tabular-nums ${status.battery < 20 ? 'text-[#F43F5E]' : 'text-[#E2E8F0]'}`}>
{Math.floor(status.battery)}%
</span>
<Zap
size={9}
className={`${status.battery < 20 ? 'text-[#F43F5E]' : 'text-[#A855F7]'} ${status.battery < 20 ? 'animate-pulse' : ''}`}
fill={status.battery < 20 ? "currentColor" : "currentColor"}
/>
</div>
) : (
<span className="text-[9px] text-gray-500 font-medium tracking-wide"></span>
)}
{/* Subtle Inner Glow */}
<div className="absolute inset-0 rounded-full border border-white/5 pointer-events-none group-hover:border-[#A855F7]/20 transition-colors"></div>
</div>
</button>
);
};
export default HardwareStatus;

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { Hexagon } from 'lucide-react';
import BottomNav from './BottomNav';
import HardwareStatus from './HardwareStatus';
import { UserTab, DeviceStatus } from '../types';
interface LayoutProps {
children: React.ReactNode;
currentTab: UserTab;
onTabChange: (tab: UserTab) => void;
title: string;
subtitle?: string;
deviceStatus: DeviceStatus;
onDeviceClick: () => void;
}
const Layout: React.FC<LayoutProps> = ({
children,
currentTab,
onTabChange,
title,
subtitle,
deviceStatus,
onDeviceClick
}) => {
return (
<div className="h-screen w-screen bg-transparent text-[#F8FAFC] overflow-hidden flex flex-col relative">
{/* 1. Top Template Header (Super Clean) */}
<div className="shrink-0 pt-safe-top z-20">
<div className="px-5 py-4 flex items-end justify-between">
{/* Left: Branding & Title */}
<div className="flex flex-col justify-end">
<div className="flex items-center gap-1.5 mb-1">
<Hexagon size={12} className="text-white fill-white/20" strokeWidth={2.5} />
<span className="text-[10px] font-bold tracking-[0.2em] text-white/80 uppercase shadow-sm">Wei AI</span>
</div>
<h1 className="text-2xl font-bold text-white leading-none tracking-tight drop-shadow-md">{title}</h1>
</div>
{/* Right: Embedded Hardware Status */}
<div className="pb-0.5">
<HardwareStatus status={deviceStatus} onClick={onDeviceClick} />
</div>
</div>
</div>
{/* 2. Middle Content (Scrollable) */}
<main className="flex-1 overflow-y-auto no-scrollbar relative z-10 w-full">
{/* Top fade mask for content scrolling under header */}
<div className="absolute top-0 left-0 w-full h-8 bg-gradient-to-b from-[#2e1065]/20 to-transparent pointer-events-none z-10"></div>
{children}
</main>
{/* 3. Bottom Tab (Fixed) */}
<BottomNav currentTab={currentTab} onTabChange={onTabChange} />
</div>
);
};
export default Layout;