"use client"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import { useEffect, useRef, useState } 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, CircleDollarSign, Columns3, } from "lucide-react"; import type { User } from "@/lib/api"; import { apiAdminPendingCounts, apiLogout } from "@/lib/api"; import { canModerateAny, canAccessAdminMessages, isAdminOrAbove, isOwner, isStaff, isSuperOrOwner, 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"; 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 }, ], }, { title: "内容", items: [ { 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) => isAdminOrAbove(u.role), }, { href: "/admin/pages", label: "单页管理", icon: FileText, visible: (u) => isAdminOrAbove(u.role), }, { href: "/admin/ads", label: "推广与赞助", icon: CircleDollarSign, visible: (u) => isAdminOrAbove(u.role), }, ], }, { title: "成员与经济", items: [ { href: "/admin/users", label: "用户管理", icon: Users, visible: (u) => isSuperOrOwner(u.role), }, { href: "/admin/economy", label: "积分看板", icon: Coins, visible: (u) => isAdminOrAbove(u.role), }, ], }, { title: "板块", items: [ { href: "/admin/boards", label: "板块管理", icon: Columns3, visible: (u) => isOwner(u.role), }, ], }, { title: "站点", items: [ { href: "/admin/settings", label: "站点设置", icon: Globe, visible: (u) => isSuperOrOwner(u.role), }, { href: "/admin/appearance", label: "外观", icon: Palette, 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}