AdminSidebar.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. 'use client';
  2. import { useState, useEffect } from 'react';
  3. import { usePathname, useRouter } from 'next/navigation';
  4. import Link from 'next/link';
  5. import {
  6. LayoutDashboard,
  7. ShoppingBag,
  8. LifeBuoy,
  9. Settings,
  10. LogOut,
  11. Activity,
  12. CreditCard,
  13. Users,
  14. ChevronLeft,
  15. ChevronRight,
  16. Plane,
  17. LucideIcon,
  18. CalendarClock,
  19. LayoutGrid
  20. } from 'lucide-react';
  21. interface MenuItem {
  22. name: string;
  23. href: string;
  24. icon: LucideIcon;
  25. }
  26. export default function AdminSidebar() {
  27. const pathname = usePathname();
  28. const router = useRouter();
  29. // 控制折叠状态
  30. const [isCollapsed, setIsCollapsed] = useState(false);
  31. const [mounted, setMounted] = useState(false); // 用于解决 Hydration 不匹配问题
  32. const menu: MenuItem[] = [
  33. { name: '概览', href: '/admin', icon: LayoutDashboard },
  34. { name: '用户管理', href: '/admin/users', icon: Users },
  35. { name: '工单处理', href: '/admin/tickets', icon: LifeBuoy },
  36. { name: '订单管理', href: '/admin/orders', icon: ShoppingBag },
  37. { name: '支付配置', href: '/admin/payments', icon: CreditCard },
  38. { name: '商品配置', href: '/admin/products', icon: Settings },
  39. { name: '系统任务', href: '/admin/tasks', icon: Activity },
  40. { name: 'TROOV Slot监控', href: '/admin/slots', icon: CalendarClock }, // 新增
  41. { name: '卡片管理', href: '/admin/cards', icon: LayoutGrid },
  42. ];
  43. // 初始化:从 localStorage 读取状态
  44. useEffect(() => {
  45. setMounted(true);
  46. const savedState = localStorage.getItem('sidebar_collapsed');
  47. if (savedState) {
  48. setIsCollapsed(JSON.parse(savedState));
  49. }
  50. }, []);
  51. // 切换并保存状态
  52. const toggleSidebar = () => {
  53. const newState = !isCollapsed;
  54. setIsCollapsed(newState);
  55. localStorage.setItem('sidebar_collapsed', JSON.stringify(newState));
  56. };
  57. const handleLogout = () => {
  58. if (confirm('确定要退出管理后台吗?')) {
  59. localStorage.removeItem('rsid');
  60. localStorage.removeItem('user_info');
  61. router.push('/login');
  62. }
  63. };
  64. // 防止服务端渲染和客户端渲染不一致导致的闪烁
  65. if (!mounted) return null;
  66. return (
  67. <aside
  68. className={`bg-slate-900 text-white flex-shrink-0 hidden md:flex flex-col h-screen sticky top-0 transition-all duration-300 ease-in-out
  69. ${isCollapsed ? 'w-20' : 'w-64'}
  70. `}
  71. >
  72. {/* Header / Logo Area */}
  73. <div className={`h-16 flex items-center border-b border-slate-800 transition-all duration-300 ${isCollapsed ? 'justify-center px-0' : 'px-6 gap-3'}`}>
  74. <Plane className="text-blue-500 flex-shrink-0" size={24} />
  75. {/* 文字部分:折叠时隐藏 */}
  76. <div className={`font-bold text-xl whitespace-nowrap overflow-hidden transition-all duration-300 ${isCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}>
  77. <span className="text-blue-400">Visafly</span> Admin
  78. </div>
  79. </div>
  80. {/* Navigation */}
  81. <nav className="flex-1 p-3 space-y-1 overflow-y-auto overflow-x-hidden">
  82. {menu.map((item) => {
  83. const Icon = item.icon;
  84. const isActive = pathname === item.href; // 精确匹配
  85. // 如果有子路由(比如 /admin/orders/new),可以使用 pathname.startsWith(item.href)
  86. return (
  87. <Link
  88. key={item.href}
  89. href={item.href}
  90. title={isCollapsed ? item.name : ''} // 折叠时显示 tooltip
  91. className={`flex items-center rounded-lg transition-all duration-200 group relative
  92. ${isCollapsed ? 'justify-center px-2 py-3' : 'px-4 py-3 gap-3'}
  93. ${isActive
  94. ? 'bg-blue-600 text-white shadow-lg shadow-blue-900/50'
  95. : 'text-slate-400 hover:bg-slate-800 hover:text-white'
  96. }
  97. `}
  98. >
  99. <Icon size={20} className={`flex-shrink-0 transition-transform duration-200 ${!isCollapsed && isActive ? 'scale-110' : ''}`} />
  100. {/* 菜单文字 */}
  101. <span className={`whitespace-nowrap overflow-hidden transition-all duration-300 ${isCollapsed ? 'w-0 opacity-0 hidden' : 'w-auto opacity-100 font-medium'}`}>
  102. {item.name}
  103. </span>
  104. {/* 折叠时的悬浮提示 (可选) */}
  105. {isCollapsed && (
  106. <div className="absolute left-full ml-2 px-2 py-1 bg-slate-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity z-50 whitespace-nowrap">
  107. {item.name}
  108. </div>
  109. )}
  110. </Link>
  111. );
  112. })}
  113. </nav>
  114. {/* Footer Actions */}
  115. <div className="p-3 border-t border-slate-800 space-y-1">
  116. {/* 折叠切换按钮 */}
  117. <button
  118. onClick={toggleSidebar}
  119. className="flex items-center w-full rounded-lg text-slate-500 hover:bg-slate-800 hover:text-white transition-colors
  120. justify-center py-2
  121. "
  122. title={isCollapsed ? "展开侧边栏" : "折叠侧边栏"}
  123. >
  124. {isCollapsed ? <ChevronRight size={20} /> : <ChevronLeft size={20} />}
  125. </button>
  126. {/* 退出按钮 */}
  127. <button
  128. onClick={handleLogout}
  129. title={isCollapsed ? "退出登录" : ""}
  130. className={`flex items-center w-full rounded-lg text-slate-400 hover:text-red-400 hover:bg-slate-800 transition-colors
  131. ${isCollapsed ? 'justify-center py-3' : 'px-4 py-3 gap-3'}
  132. `}
  133. >
  134. <LogOut size={20} className="flex-shrink-0" />
  135. <span className={`whitespace-nowrap overflow-hidden transition-all duration-300 ${isCollapsed ? 'w-0 opacity-0 hidden' : 'w-auto opacity-100 font-medium'}`}>
  136. 退出登录
  137. </span>
  138. </button>
  139. </div>
  140. </aside>
  141. );
  142. }