"use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useEffect, useState } from "react"; import type { LucideIcon } from "lucide-react"; import { LayoutDashboard, Users, Megaphone, Palette, ShieldCheck, ArrowLeft, Coins, Globe, FolderOpen, } from "lucide-react"; import type { User } from "@/lib/api"; import { apiAdminPendingCounts } from "@/lib/api"; import { canModerateAny, isAdminOrAbove, isSuperOrOwner, type RoleUserLike, } from "@/lib/roles"; import { realtime, RT_MODERATION_CHANGED } from "@/lib/realtime"; import { onDocumentVisible } from "@/lib/visibilityReconcile"; import Avatar from "@/components/Avatar"; import RoleBadge from "@/components/RoleBadge"; 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/announcements", label: "公告管理", icon: Megaphone, 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/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); } export function AdminSidebar({ user }: { user: User }) { const pathname = usePathname(); const groups = visibleGroups(user); const pendingTotal = usePendingCounts(flatItems(user).some((i) => i.pendingBadge)); return (
{user.nickname}
@{user.username}