astock-agent/frontend/src/components/nav.tsx
2026-04-30 23:29:52 +08:00

177 lines
6.8 KiB
TypeScript

"use client";
import { useAuth } from "@/hooks/use-auth";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { ThemeToggle } from "@/components/theme-toggle";
function DashboardIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="7" height="7" rx="1.5" />
<rect x="14" y="3" width="7" height="7" rx="1.5" />
<rect x="3" y="14" width="7" height="7" rx="1.5" />
<rect x="14" y="14" width="7" height="7" rx="1.5" />
</svg>
);
}
function TargetIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<circle cx="12" cy="12" r="6" />
<circle cx="12" cy="12" r="2" />
</svg>
);
}
function FireIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 2c.5 2.5-.5 5-2 7 1 0 2.5.5 3 2.5.5-2 2-3 3-4-1 3-1 6-4 8.5-1.5 1-3.5 1.5-5 1-1.5-.5-2.5-2-2.5-3.5 0-3 3-5 5-7.5C10 5 11 3.5 12 2z" />
</svg>
);
}
function StrategyIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<path d="M4 19V5" />
<path d="M4 19h16" />
<path d="M7 15l3-4 3 2 4-7" />
<path d="M17 6h3v3" />
</svg>
);
}
function DiagnoseIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<path d="M9 11l3 3L22 4" />
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
</svg>
);
}
function ChatIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" />
</svg>
);
}
function WatchlistIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
</svg>
);
}
function UsersIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
);
}
function SettingsIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>
);
}
function SideNavItem({ href, icon, label }: { href: string; icon: React.ReactNode; label: string }) {
const pathname = usePathname();
const isActive = pathname === href || (href !== "/dashboard" && pathname.startsWith(href));
return (
<Link
href={href}
className={`flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm transition-all duration-200 ${
isActive
? "text-text-primary bg-surface-4"
: "text-text-secondary hover:text-text-primary hover:bg-surface-3"
}`}
>
<span className="text-base opacity-70">{icon}</span>
<span className="font-medium">{label}</span>
</Link>
);
}
export function SidebarNav() {
const { user } = useAuth();
return (
<nav className="flex-1 py-5 px-3 space-y-1">
<SideNavItem href="/dashboard" icon={<DashboardIcon />} label="今日作战" />
<SideNavItem href="/recommendations" icon={<TargetIcon />} label="推荐池" />
<SideNavItem href="/sectors" icon={<FireIcon />} label="板块主线" />
<SideNavItem href="/watchlists" icon={<WatchlistIcon />} label="自选股" />
<SideNavItem href="/chat" icon={<ChatIcon />} label="系统智能体" />
<SideNavItem href="/diagnose" icon={<DiagnoseIcon />} label="个股诊断" />
{user?.role === "admin" && (
<>
<SideNavItem href="/strategy" icon={<StrategyIcon />} label="系统校准" />
<SideNavItem href="/settings" icon={<SettingsIcon />} label="系统设置" />
</>
)}
</nav>
);
}
function MobileNavItem({ href, label, children }: { href: string; label: string; children: React.ReactNode }) {
const pathname = usePathname();
const isActive = pathname === href;
return (
<Link
href={href}
className={`flex flex-col items-center gap-1 transition-colors active:scale-95 ${
isActive ? "text-amber-400" : "text-text-muted hover:text-text-primary"
}`}
>
<span className="text-lg">{children}</span>
<span className="text-xs font-medium">{label}</span>
</Link>
);
}
export function MobileBottomNav() {
const { user } = useAuth();
return (
<nav className="fixed bottom-0 left-0 right-0 md:hidden z-50 bg-bg-secondary/95 backdrop-blur-xl border-t border-border-subtle">
<div className="flex justify-around py-2 pb-[max(0.5rem,env(safe-area-inset-bottom))]">
<MobileNavItem href="/dashboard" label="作战">
<DashboardIcon />
</MobileNavItem>
<MobileNavItem href="/recommendations" label="推荐池">
<TargetIcon />
</MobileNavItem>
<MobileNavItem href="/chat" label="智能体">
<ChatIcon />
</MobileNavItem>
<MobileNavItem href="/watchlists" label="自选">
<WatchlistIcon />
</MobileNavItem>
{user?.role === "admin" ? (
<MobileNavItem href="/strategy" label="校准">
<StrategyIcon />
</MobileNavItem>
) : null}
</div>
</nav>
);
}