116 lines
4.1 KiB
TypeScript
Raw Normal View History

2026-06-22 14:43:46 +08:00
'use client';
import { useEffect, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Diamond, ChevronDown, LogOut, Crown } from 'lucide-react';
import { useAdminStore } from '@/store/adminStore';
import { Badge } from '@/components/ui/badge';
import { Avatar } from './Avatar';
/**
* + + 退
* /admin/* /admin/login
*/
export function TopBar() {
const router = useRouter();
const admin = useAdminStore((s) => s.admin);
const [menuOpen, setMenuOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
// 下拉打开时:监听外部点击 + Esc 键关闭
useEffect(() => {
if (!menuOpen) return;
const onMouseDown = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
setMenuOpen(false);
}
};
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') setMenuOpen(false);
};
document.addEventListener('mousedown', onMouseDown);
document.addEventListener('keydown', onKeyDown);
return () => {
document.removeEventListener('mousedown', onMouseDown);
document.removeEventListener('keydown', onKeyDown);
};
}, [menuOpen]);
const handleLogout = () => {
setMenuOpen(false);
useAdminStore.getState().logout();
router.replace('/admin/login');
};
const nickname = admin?.nickname ?? admin?.username ?? '管理员';
const username = admin?.username ?? 'admin';
const isSuperAdmin = admin?.role === 'super_admin';
return (
<header className="sticky top-0 z-40 flex h-16 items-center justify-between border-b border-gray-200 bg-white/95 px-6 backdrop-blur">
<div className="flex items-center gap-2">
<Diamond className="h-6 w-6 text-brand-600" />
<span className="text-base font-semibold text-gray-900">
</span>
</div>
<div className="relative" ref={menuRef}>
<button
type="button"
onClick={() => setMenuOpen((v) => !v)}
className="flex items-center gap-2 rounded-md px-2 py-1.5 transition-colors hover:bg-gray-100"
aria-haspopup="menu"
aria-expanded={menuOpen}
>
<Avatar src={admin?.avatar} name={nickname} size={32} />
<span className="text-sm font-medium text-gray-700">{nickname}</span>
{isSuperAdmin && (
<Badge variant="warning">
<Crown className="h-3 w-3" />
</Badge>
)}
<ChevronDown className="h-4 w-4 text-gray-500" />
</button>
{menuOpen && (
<div
role="menu"
className="absolute right-0 top-full mt-2 w-60 overflow-hidden rounded-lg border border-gray-200 bg-white shadow-lg"
>
<div className="border-b border-gray-100 px-4 py-3">
<div className="flex items-center gap-3">
<Avatar src={admin?.avatar} name={nickname} size={40} />
<div className="min-w-0">
<p className="truncate text-sm font-medium text-gray-900">
{nickname}
</p>
<p className="truncate text-xs text-gray-500">@{username}</p>
</div>
</div>
<div className="mt-2">
{isSuperAdmin ? (
<Badge variant="warning">
<Crown className="h-3 w-3" />
</Badge>
) : (
<Badge variant="secondary"></Badge>
)}
</div>
</div>
<button
type="button"
role="menuitem"
onClick={handleLogout}
className="flex w-full items-center gap-2 px-4 py-2.5 text-left text-sm text-red-600 transition-colors hover:bg-red-50"
>
<LogOut className="h-4 w-4" />
退
</button>
</div>
)}
</div>
</header>
);
}