Sidebar.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. "use client";
  2. import {
  3. GraduationCap,
  4. LayoutDashboard,
  5. BarChart2,
  6. Users,
  7. Settings,
  8. LogOut
  9. } from "lucide-react";
  10. import Image from "next/image";
  11. import Link from "next/link";
  12. import { usePathname } from "next/navigation";
  13. import { useLayout } from "@/components/LayoutProvider";
  14. import { useAuth } from "@/components/AuthProvider";
  15. export default function Sidebar() {
  16. const pathname = usePathname();
  17. const { mobileMenuOpen, setMobileMenuOpen } = useLayout();
  18. const { user, logout } = useAuth();
  19. const navItems = [
  20. { name: "总览面板", href: "/", icon: LayoutDashboard, roles: ["stu", "teacher"] },
  21. { name: "能力分析", href: "/analysis", icon: BarChart2, roles: ["stu"] },
  22. { name: "学生管理", href: "/students", icon: Users, roles: ["teacher"] },
  23. { name: "系统设置", href: "/settings", icon: Settings, roles: ["teacher"] },
  24. ].filter(item => item.roles.includes(user?.role || "stu"));
  25. return (
  26. <>
  27. {/* Mobile backdrop */}
  28. {mobileMenuOpen && (
  29. <div
  30. className="fixed inset-0 bg-background/80 backdrop-blur-sm z-30 lg:hidden"
  31. onClick={() => setMobileMenuOpen(false)}
  32. />
  33. )}
  34. <aside
  35. className={`fixed inset-y-0 left-0 bg-surface border-r border-outline-variant flex flex-col justify-between z-40 w-64 lg:w-72 lg:static shrink-0 transition-transform duration-300 ease-in-out ${
  36. mobileMenuOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
  37. }`}
  38. >
  39. <div>
  40. <div className="h-20 flex items-center justify-center px-4 lg:px-6 w-full border-b border-outline-variant/30">
  41. <Link href="/" className="flex items-center justify-center hover:opacity-80 transition-opacity w-full">
  42. <Image
  43. src="/banner.png"
  44. alt="SEEC Metrics Banner"
  45. width={160}
  46. height={40}
  47. className="object-contain mx-auto"
  48. priority
  49. />
  50. </Link>
  51. </div>
  52. <nav className="mt-4 px-3 space-y-2">
  53. {navItems.map((item) => {
  54. const isActive = pathname === item.href || (pathname?.startsWith("/metrics") && item.href === "/analysis");
  55. const activeClass = isActive
  56. ? "bg-primary-container text-on-primary-container"
  57. : "text-secondary hover:bg-surface-container-high hover:text-on-surface";
  58. return (
  59. <Link
  60. key={item.name}
  61. className={`flex items-center px-4 py-3 rounded-full group transition-colors ${activeClass}`}
  62. href={item.href}
  63. >
  64. <item.icon className="w-5 h-5 shrink-0" />
  65. <span className="ml-3 font-medium text-sm">
  66. {item.name}
  67. </span>
  68. </Link>
  69. )
  70. })}
  71. </nav>
  72. </div>
  73. <div className="p-4 lg:p-6 mt-auto">
  74. <div className="flex items-center justify-between p-3 rounded-xl bg-surface-container border border-outline-variant/30 hover:bg-surface-container-high transition-colors group">
  75. <Link
  76. href="/profile"
  77. className="flex items-center flex-1 cursor-pointer"
  78. onClick={() => setMobileMenuOpen(false)}
  79. >
  80. <div className="relative shrink-0">
  81. <Image
  82. alt="User Profile"
  83. className="h-10 w-10 rounded-lg object-contain ring-2 ring-surface group-hover:ring-primary/50 transition-all bg-white"
  84. src={user?.avatar || "/images/profile-avatar.png"}
  85. width={40}
  86. height={40}
  87. referrerPolicy="no-referrer"
  88. />
  89. <span className="absolute bottom-0 right-0 h-2.5 w-2.5 bg-green-400 rounded-full ring-2 ring-surface-container group-hover:ring-surface-container-high transition-colors"></span>
  90. </div>
  91. <div className="ml-3">
  92. <p className="text-sm font-medium text-on-surface group-hover:text-primary transition-colors">{user?.name || "未知用户"}</p>
  93. <p className="text-xs text-outline group-hover:text-secondary transition-colors">{user?.role === 'teacher' ? '指导教师' : '学生'}</p>
  94. </div>
  95. </Link>
  96. <button
  97. onClick={() => {
  98. setMobileMenuOpen(false);
  99. logout();
  100. }}
  101. className="p-2 text-outline hover:text-metric-low hover:bg-metric-low/10 rounded-lg transition-colors ml-2"
  102. title="退出登录"
  103. >
  104. <LogOut className="w-5 h-5" />
  105. </button>
  106. </div>
  107. </div>
  108. </aside>
  109. </>
  110. );
  111. }