"use client"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import { useEffect, useRef, useState, useCallback } from "react"; import type { LucideIcon } from "lucide-react"; import { LayoutDashboard, Users, Megaphone, Palette, ShieldCheck, Coins, Globe, FolderOpen, Menu, MessageCircle, X, ArrowLeft, LogOut, User as UserIcon, Settings, ChevronDown, FileText, BookOpen, CircleDollarSign, Columns3, PanelLeft, BarChart3, Award, Layers, Images, Link2, } from "lucide-react"; import type { User } from "@/lib/api"; import { apiAdminPendingCounts, apiLogout } from "@/lib/api"; import { canModerateAny, canAccessAdminMessages, hasPerm, isAdminOrAbove, isOwner, isStaff, isSuperOrOwner, PERMS, type RoleUserLike, } from "@/lib/roles"; import { realtime, RT_MODERATION_CHANGED } from "@/lib/realtime"; import { onDocumentVisible } from "@/lib/visibilityReconcile"; import { onForceLogout, onUserUpdate } from "@/lib/userEvents"; import Avatar from "@/components/Avatar"; import RoleBadge from "@/components/RoleBadge"; import NotificationBell from "@/components/NotificationBell"; import ThemeToggle from "@/components/ThemeToggle"; import { useUrlStyle } from "@/components/SiteBrandProvider"; import { userPath } from "@/lib/urlStyle"; interface NavItem { href: string; label: string; icon: LucideIcon; exact?: boolean; visible: (u: RoleUserLike) => boolean; pendingBadge?: boolean; } interface NavGroup { title: string; items: NavItem[]; } // 分组导航:概览 → 内容 → 成员与经济 → 外观展示 → 系统 const NAV_GROUPS: NavGroup[] = [ { title: "概览", items: [ { href: "/admin", label: "仪表盘", icon: LayoutDashboard, exact: true, visible: () => true }, { href: "/admin/analytics", label: "访问统计", icon: BarChart3, visible: (u) => isSuperOrOwner(u.role) }, ], }, { title: "内容", items: [ { href: "/admin/boards", label: "板块管理", icon: Columns3, visible: (u) => isOwner(u.role), }, { href: "/admin/content", label: "内容管理", icon: FolderOpen, visible: (u) => canModerateAny(u), pendingBadge: true, }, { href: "/admin/messages", label: "消息管理", icon: MessageCircle, visible: (u) => canAccessAdminMessages(u), }, { href: "/admin/announcements", label: "公告管理", icon: Megaphone, visible: (u) => hasPerm(u, PERMS.ANNOUNCEMENTS), }, { href: "/admin/pages", label: "单页管理", icon: FileText, visible: (u) => hasPerm(u, PERMS.PAGES), }, { href: "/admin/library", label: "书库管理", icon: BookOpen, visible: (u) => hasPerm(u, PERMS.LIBRARY), }, { href: "/admin/friend-links", label: "友情链接", icon: Link2, visible: (u) => isSuperOrOwner(u.role), }, ], }, { title: "成员与经济", items: [ { href: "/admin/users", label: "用户管理", icon: Users, visible: (u) => isSuperOrOwner(u.role), }, { href: "/admin/points", label: "积分管理", icon: Coins, visible: (u) => isAdminOrAbove(u.role), }, { href: "/admin/levels", label: "等级管理", icon: Layers, visible: (u) => isSuperOrOwner(u.role), }, { href: "/admin/badges", label: "徽章管理", icon: Award, visible: (u) => isSuperOrOwner(u.role), }, { href: "/admin/ads", label: "推广与赞助", icon: CircleDollarSign, visible: (u) => hasPerm(u, PERMS.ADS), }, ], }, { title: "外观展示", items: [ { href: "/admin/appearance", label: "外观", icon: Palette, visible: (u) => isSuperOrOwner(u.role), }, { href: "/admin/sidebar", label: "侧栏管理", icon: PanelLeft, visible: (u) => isSuperOrOwner(u.role), }, { href: "/admin/media", label: "媒体库", icon: Images, visible: (u) => isSuperOrOwner(u.role), }, ], }, { title: "系统", items: [ { href: "/admin/settings", label: "站点设置", icon: Globe, visible: (u) => isSuperOrOwner(u.role), }, ], }, ]; function isActive(pathname: string, href: string, exact?: boolean) { return exact ? pathname === href : pathname === href || pathname.startsWith(`${href}/`); } function usePendingCounts(enabled: boolean) { const [counts, setCounts] = useState({ posts: 0, comments: 0 }); useEffect(() => { if (!enabled) return; let alive = true; const load = () => { apiAdminPendingCounts() .then((c) => { if (alive) setCounts(c); }) .catch(() => {}); }; load(); const off = realtime.on(RT_MODERATION_CHANGED, () => load()); const offVisible = onDocumentVisible(load); return () => { alive = false; off(); offVisible(); }; }, [enabled]); return counts.posts + counts.comments; } function PendingBadge({ count }: { count: number }) { if (count <= 0) return null; return ( {count > 99 ? "99+" : count} ); } function visibleGroups(user: RoleUserLike) { return NAV_GROUPS.map((g) => ({ ...g, items: g.items.filter((i) => i.visible(user)), })).filter((g) => g.items.length > 0); } function flatItems(user: RoleUserLike) { return visibleGroups(user).flatMap((g) => g.items); } function AdminBrand({ siteName }: { siteName: string }) { return (
{user.nickname}
@{user.username}