116 lines
4.1 KiB
TypeScript
116 lines
4.1 KiB
TypeScript
|
|
'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>
|
|||
|
|
);
|
|||
|
|
}
|