astock-agent/frontend/src/components/nav.tsx
2026-04-16 14:16:02 +08:00

155 lines
5.6 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 MonitorIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</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 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 SideNavItem({ href, icon, label }: { href: string; icon: React.ReactNode; label: string }) {
const pathname = usePathname();
const isActive = href === "/" ? pathname === "/" : 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="/" icon={<DashboardIcon />} label="总览" />
<SideNavItem href="/recommendations" icon={<TargetIcon />} label="推荐列表" />
<SideNavItem href="/monitor" icon={<MonitorIcon />} label="监控" />
<SideNavItem href="/sectors" icon={<FireIcon />} label="板块分析" />
<SideNavItem href="/diagnose" icon={<DiagnoseIcon />} label="AI 诊断" />
<SideNavItem href="/chat" icon={<ChatIcon />} label="AI 对话" />
{user?.role === "admin" && (
<SideNavItem href="/users" icon={<UsersIcon />} label="用户管理" />
)}
</nav>
);
}
function MobileNavItem({ href, label, children }: { href: string; label: string; children: React.ReactNode }) {
const pathname = usePathname();
const isActive = href === "/" ? pathname === "/" : pathname.startsWith(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() {
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="/" label="总览">
<DashboardIcon />
</MobileNavItem>
<MobileNavItem href="/recommendations" label="推荐">
<TargetIcon />
</MobileNavItem>
<MobileNavItem href="/monitor" label="监控">
<MonitorIcon />
</MobileNavItem>
<MobileNavItem href="/sectors" label="板块">
<FireIcon />
</MobileNavItem>
<MobileNavItem href="/diagnose" label="诊断">
<DiagnoseIcon />
</MobileNavItem>
<MobileNavItem href="/chat" label="对话">
<ChatIcon />
</MobileNavItem>
</div>
</nav>
);
}