2025-12-29 14:37:03 +08:00
|
|
|
"use client"
|
|
|
|
|
import {
|
|
|
|
|
Activity,
|
|
|
|
|
BarChart3,
|
|
|
|
|
ChevronsLeft,
|
|
|
|
|
ChevronsRight,
|
|
|
|
|
ClipboardList,
|
|
|
|
|
Code,
|
2025-12-29 18:01:16 +08:00
|
|
|
ComputerIcon,
|
2025-12-29 14:37:03 +08:00
|
|
|
DollarSign,
|
|
|
|
|
Home,
|
2026-03-18 17:13:31 +08:00
|
|
|
KeyRound,
|
2025-12-29 14:37:03 +08:00
|
|
|
type LucideIcon,
|
|
|
|
|
Package,
|
|
|
|
|
Shield,
|
|
|
|
|
Users,
|
|
|
|
|
} from "lucide-react"
|
|
|
|
|
import Link from "next/link"
|
|
|
|
|
import { usePathname } from "next/navigation"
|
|
|
|
|
import { createContext, type ReactNode, useContext, useState } from "react"
|
2025-12-29 18:01:16 +08:00
|
|
|
import { twJoin } from "tailwind-merge"
|
2025-12-29 14:37:03 +08:00
|
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
|
|
|
|
import { Separator } from "@/components/ui/separator"
|
2025-12-29 18:01:16 +08:00
|
|
|
import {
|
|
|
|
|
Tooltip,
|
|
|
|
|
TooltipContent,
|
|
|
|
|
TooltipProvider,
|
|
|
|
|
TooltipTrigger,
|
|
|
|
|
} from "@/components/ui/tooltip"
|
2025-12-29 10:41:23 +08:00
|
|
|
|
2025-12-29 14:37:03 +08:00
|
|
|
// Navigation Context
|
|
|
|
|
interface NavigationContextType {
|
|
|
|
|
collapsed: boolean
|
|
|
|
|
pathname: string
|
|
|
|
|
isActive: (path: string) => boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NavigationContext = createContext<NavigationContextType | undefined>(
|
|
|
|
|
undefined,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const useNavigation = () => {
|
|
|
|
|
const context = useContext(NavigationContext)
|
|
|
|
|
if (!context) {
|
|
|
|
|
throw new Error("Navigation components must be used within Navigation")
|
|
|
|
|
}
|
|
|
|
|
return context
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 18:01:16 +08:00
|
|
|
// NavGroup Component
|
|
|
|
|
interface NavGroupProps {
|
2025-12-29 14:37:03 +08:00
|
|
|
title: string
|
|
|
|
|
children: ReactNode
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 18:01:16 +08:00
|
|
|
function NavGroup({ title, children }: NavGroupProps) {
|
2025-12-29 14:37:03 +08:00
|
|
|
const { collapsed } = useNavigation()
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="px-3">
|
|
|
|
|
{!collapsed && (
|
2025-12-29 18:01:16 +08:00
|
|
|
<h3 className="px-3 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
2025-12-29 14:37:03 +08:00
|
|
|
{title}
|
|
|
|
|
</h3>
|
|
|
|
|
)}
|
|
|
|
|
<ul className={`${collapsed ? "mt-0" : "mt-2"} space-y-1`}>{children}</ul>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 18:01:16 +08:00
|
|
|
// NavItem Component
|
|
|
|
|
interface NavItemProps {
|
2025-12-29 14:37:03 +08:00
|
|
|
href: string
|
|
|
|
|
icon: LucideIcon
|
|
|
|
|
label: string
|
|
|
|
|
}
|
2025-12-29 10:41:23 +08:00
|
|
|
|
2025-12-29 18:01:16 +08:00
|
|
|
function NavItem({ href, icon: Icon, label }: NavItemProps) {
|
2025-12-29 14:37:03 +08:00
|
|
|
const { collapsed, isActive } = useNavigation()
|
|
|
|
|
const active = isActive(href)
|
|
|
|
|
|
2025-12-29 18:01:16 +08:00
|
|
|
const linkContent = (
|
|
|
|
|
<Link
|
|
|
|
|
href={href}
|
|
|
|
|
className={`flex items-center ${
|
|
|
|
|
collapsed ? "justify-center w-10 h-10" : "px-3 py-2"
|
|
|
|
|
} rounded-md transition-colors ${
|
|
|
|
|
active
|
|
|
|
|
? "bg-accent text-accent-foreground"
|
|
|
|
|
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
2026-01-06 14:57:55 +08:00
|
|
|
<Icon className={`h-5 w-5 ${collapsed ? "" : "shrink-0"}`} />
|
2025-12-29 18:01:16 +08:00
|
|
|
{!collapsed && <span className="ml-3 font-medium text-sm">{label}</span>}
|
|
|
|
|
</Link>
|
2025-12-29 14:37:03 +08:00
|
|
|
)
|
2025-12-29 18:01:16 +08:00
|
|
|
|
|
|
|
|
if (collapsed) {
|
|
|
|
|
return (
|
|
|
|
|
<li>
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>{linkContent}</TooltipTrigger>
|
|
|
|
|
<TooltipContent side="right">
|
|
|
|
|
<p>{label}</p>
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</li>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return <li>{linkContent}</li>
|
2025-12-29 10:41:23 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-29 18:01:16 +08:00
|
|
|
// NavSeparator Component
|
|
|
|
|
function NavSeparator() {
|
2025-12-29 14:37:03 +08:00
|
|
|
const { collapsed } = useNavigation()
|
|
|
|
|
|
|
|
|
|
if (collapsed) return null
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="my-4">
|
|
|
|
|
<Separator />
|
|
|
|
|
</div>
|
|
|
|
|
)
|
2025-12-29 10:41:23 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-29 14:37:03 +08:00
|
|
|
// Main Navigation Component
|
|
|
|
|
export default function Navigation() {
|
2025-12-29 10:41:23 +08:00
|
|
|
const [collapsed, setCollapsed] = useState(false)
|
|
|
|
|
const pathname = usePathname()
|
|
|
|
|
|
|
|
|
|
const isActive = (path: string) => {
|
2025-12-29 14:37:03 +08:00
|
|
|
return path === "/" ? pathname === path : pathname.startsWith(path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const contextValue: NavigationContextType = {
|
|
|
|
|
collapsed,
|
|
|
|
|
pathname,
|
|
|
|
|
isActive,
|
2025-12-29 10:41:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2025-12-29 18:01:16 +08:00
|
|
|
<TooltipProvider delayDuration={0}>
|
|
|
|
|
<NavigationContext.Provider value={contextValue}>
|
|
|
|
|
<aside
|
|
|
|
|
className={twJoin(
|
|
|
|
|
"bg-background border-r border-border transition-all duration-300 ease-in-out flex flex-col",
|
|
|
|
|
collapsed ? "w-16" : "w-64",
|
2025-12-29 14:37:03 +08:00
|
|
|
)}
|
2025-12-29 18:01:16 +08:00
|
|
|
>
|
|
|
|
|
{/* Logo */}
|
|
|
|
|
<div className="h-16 flex items-center justify-center border-b border-border">
|
|
|
|
|
{!collapsed ? (
|
|
|
|
|
<span className="text-xl font-bold tracking-wide text-foreground">
|
|
|
|
|
管理系统
|
|
|
|
|
</span>
|
2025-12-29 14:37:03 +08:00
|
|
|
) : (
|
2025-12-29 18:01:16 +08:00
|
|
|
<span className="text-xl font-bold mx-auto text-foreground">
|
|
|
|
|
<ComputerIcon />
|
|
|
|
|
</span>
|
2025-12-29 14:37:03 +08:00
|
|
|
)}
|
2025-12-29 18:01:16 +08:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Navigation Menu */}
|
|
|
|
|
<ScrollArea className="flex-1 py-3">
|
|
|
|
|
<nav className="space-y-3">
|
|
|
|
|
{/* 概览 */}
|
|
|
|
|
<NavGroup title="概览">
|
|
|
|
|
<NavItem href="/" icon={Home} label="首页" />
|
|
|
|
|
<NavItem href="/statistics" icon={BarChart3} label="数据统计" />
|
|
|
|
|
</NavGroup>
|
|
|
|
|
|
2026-01-06 14:57:55 +08:00
|
|
|
{/*<NavSeparator />*/}
|
2025-12-29 18:01:16 +08:00
|
|
|
|
|
|
|
|
{/* IP 资源 */}
|
2026-01-06 14:57:55 +08:00
|
|
|
{/*<NavGroup title="IP 资源">
|
2025-12-29 18:01:16 +08:00
|
|
|
<NavItem href="/proxy/nodes" icon={Globe} label="节点列表" />
|
|
|
|
|
<NavItem href="/proxy/pools" icon={Server} label="IP池管理" />
|
2026-01-06 14:57:55 +08:00
|
|
|
</NavGroup>*/}
|
2025-12-29 18:01:16 +08:00
|
|
|
|
|
|
|
|
<NavSeparator />
|
|
|
|
|
|
|
|
|
|
{/* 客户 */}
|
|
|
|
|
<NavGroup title="客户">
|
|
|
|
|
<NavItem href="/user" icon={Users} label="客户管理" />
|
2025-12-30 18:35:37 +08:00
|
|
|
<NavItem href="/trade" icon={Activity} label="交易明细" />
|
|
|
|
|
<NavItem href="/billing" icon={DollarSign} label="账单详情" />
|
2025-12-29 18:01:16 +08:00
|
|
|
</NavGroup>
|
|
|
|
|
|
|
|
|
|
<NavSeparator />
|
|
|
|
|
|
|
|
|
|
{/* 运营 */}
|
|
|
|
|
<NavGroup title="运营">
|
2025-12-30 18:35:37 +08:00
|
|
|
<NavItem href="/resources" icon={Package} label="套餐管理" />
|
|
|
|
|
<NavItem href="/batch" icon={ClipboardList} label="使用记录" />
|
|
|
|
|
<NavItem href="/channel" icon={Code} label="IP管理" />
|
2025-12-29 18:01:16 +08:00
|
|
|
</NavGroup>
|
|
|
|
|
|
|
|
|
|
<NavSeparator />
|
|
|
|
|
|
|
|
|
|
{/* 系统 */}
|
|
|
|
|
<NavGroup title="系统">
|
2026-01-06 14:57:55 +08:00
|
|
|
{/*<NavItem href="/settings" icon={Settings} label="系统设置" />*/}
|
2026-03-18 17:13:31 +08:00
|
|
|
<NavItem href="/admin" icon={Shield} label="管理员" />
|
|
|
|
|
<NavItem href="/roles" icon={KeyRound} label="角色列表" />
|
|
|
|
|
<NavItem href="/permissions" icon={Shield} label="权限列表" />
|
2026-01-06 14:57:55 +08:00
|
|
|
{/*<NavItem href="/logs" icon={FileText} label="系统日志" />*/}
|
2025-12-29 18:01:16 +08:00
|
|
|
</NavGroup>
|
|
|
|
|
</nav>
|
|
|
|
|
</ScrollArea>
|
|
|
|
|
|
|
|
|
|
{/* Toggle Button */}
|
|
|
|
|
<div className="p-4 border-t border-border mt-auto">
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
onClick={() => setCollapsed(!collapsed)}
|
|
|
|
|
className="w-full justify-center text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
|
|
|
>
|
|
|
|
|
{collapsed ? (
|
|
|
|
|
<ChevronsRight className="h-5 w-5" />
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<ChevronsLeft className="h-5 w-5" />
|
|
|
|
|
<span className="ml-2 text-sm">收起菜单</span>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
</NavigationContext.Provider>
|
|
|
|
|
</TooltipProvider>
|
2025-12-29 10:41:23 +08:00
|
|
|
)
|
|
|
|
|
}
|