112 lines
3.3 KiB
TypeScript
112 lines
3.3 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import Link from 'next/link';
|
||
|
|
import { usePathname } from 'next/navigation';
|
||
|
|
import {
|
||
|
|
LayoutDashboard,
|
||
|
|
Package,
|
||
|
|
Newspaper,
|
||
|
|
Users,
|
||
|
|
MessageSquare,
|
||
|
|
Settings,
|
||
|
|
UserCog,
|
||
|
|
BookOpen,
|
||
|
|
} from 'lucide-react';
|
||
|
|
import { cn } from '@/lib/utils';
|
||
|
|
import { useAdminStore } from '@/store/adminStore';
|
||
|
|
|
||
|
|
interface NavItem {
|
||
|
|
href: string;
|
||
|
|
label: string;
|
||
|
|
icon: React.ComponentType<{ className?: string }>;
|
||
|
|
/** 仅超级管理员可见 */
|
||
|
|
superAdminOnly?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface NavGroup {
|
||
|
|
label: string;
|
||
|
|
items: NavItem[];
|
||
|
|
}
|
||
|
|
|
||
|
|
const NAV_GROUPS: NavGroup[] = [
|
||
|
|
{
|
||
|
|
label: '概览',
|
||
|
|
items: [
|
||
|
|
{ href: '/admin/dashboard', label: '仪表盘', icon: LayoutDashboard },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: '内容管理',
|
||
|
|
items: [
|
||
|
|
{ href: '/admin/product', label: '产品管理', icon: Package },
|
||
|
|
{ href: '/admin/news', label: '新闻管理', icon: Newspaper },
|
||
|
|
{ href: '/admin/team', label: '团队管理', icon: Users },
|
||
|
|
{ href: '/admin/message', label: '留言管理', icon: MessageSquare },
|
||
|
|
{ href: '/admin/manual', label: '使用手册', icon: BookOpen },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: '系统',
|
||
|
|
items: [
|
||
|
|
{ href: '/admin/site-config', label: '网站配置', icon: Settings },
|
||
|
|
{
|
||
|
|
href: '/admin/admin-user',
|
||
|
|
label: '管理员账号',
|
||
|
|
icon: UserCog,
|
||
|
|
superAdminOnly: true,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
export function AdminSidebar() {
|
||
|
|
const pathname = usePathname();
|
||
|
|
const isSuperAdmin = useAdminStore((s) => s.admin?.role) === 'super_admin';
|
||
|
|
|
||
|
|
return (
|
||
|
|
<aside className="flex h-screen w-60 flex-col bg-slate-900 text-slate-200">
|
||
|
|
<nav className="flex-1 overflow-y-auto px-2 py-2">
|
||
|
|
{NAV_GROUPS.map((group, gIdx) => {
|
||
|
|
const visibleItems = group.items.filter(
|
||
|
|
(item) => !item.superAdminOnly || isSuperAdmin,
|
||
|
|
);
|
||
|
|
if (visibleItems.length === 0) return null;
|
||
|
|
return (
|
||
|
|
<div key={group.label} className={gIdx === 0 ? '' : 'mt-4'}>
|
||
|
|
<p className="px-3 pb-1 pt-3 text-xs font-semibold uppercase tracking-wider text-slate-500">
|
||
|
|
{group.label}
|
||
|
|
</p>
|
||
|
|
<div className="space-y-0.5">
|
||
|
|
{visibleItems.map((item) => {
|
||
|
|
const active =
|
||
|
|
pathname === item.href ||
|
||
|
|
pathname.startsWith(item.href + '/');
|
||
|
|
const Icon = item.icon;
|
||
|
|
return (
|
||
|
|
<Link
|
||
|
|
key={item.href}
|
||
|
|
href={item.href}
|
||
|
|
className={cn(
|
||
|
|
'relative flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors duration-150',
|
||
|
|
active
|
||
|
|
? 'bg-slate-800/60 text-white'
|
||
|
|
: 'text-slate-300 hover:bg-slate-800/40 hover:text-white',
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
{active && (
|
||
|
|
<span className="absolute bottom-2 left-0 top-2 w-[3px] rounded-r bg-brand-500" />
|
||
|
|
)}
|
||
|
|
<Icon className="h-[18px] w-[18px] shrink-0" />
|
||
|
|
{item.label}
|
||
|
|
</Link>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</nav>
|
||
|
|
</aside>
|
||
|
|
);
|
||
|
|
}
|