第一次提交
This commit is contained in:
154
app-instance/frontend/components/Header.tsx
Normal file
154
app-instance/frontend/components/Header.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { MessageSquare, Activity, Clock, Puzzle, Blocks, HelpCircle, FolderOpen, Store, LogIn, UserPlus, Bot, ServerCog, Mail, LogOut, UserCircle2 } from 'lucide-react';
|
||||
import { logout } from '@/lib/api';
|
||||
import { useChatStore } from '@/lib/store';
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ name: '对话', href: '/', icon: MessageSquare },
|
||||
{ name: '状态', href: '/status', icon: Activity },
|
||||
{ name: '定时任务', href: '/cron', icon: Clock },
|
||||
{ name: '技能', href: '/skills', icon: Puzzle },
|
||||
{ name: '插件', href: '/plugins', icon: Blocks },
|
||||
{ name: '智能体', href: '/agents', icon: Bot },
|
||||
{ name: 'MCP', href: '/mcp', icon: ServerCog },
|
||||
{ name: 'Outlook', href: '/outlook', icon: Mail },
|
||||
{ name: '市场', href: '/marketplace', icon: Store },
|
||||
{ name: '文件', href: '/files', icon: FolderOpen },
|
||||
{ name: '帮助', href: '/help', icon: HelpCircle },
|
||||
];
|
||||
|
||||
const AUTH_ITEMS = [
|
||||
{ name: '登录', href: '/login', icon: LogIn },
|
||||
{ name: '注册', href: '/register', icon: UserPlus },
|
||||
];
|
||||
|
||||
function ConnectionDot() {
|
||||
const wsStatus = useChatStore((s) => s.wsStatus);
|
||||
const nanobotReady = useChatStore((s) => s.nanobotReady);
|
||||
|
||||
const isOnline = wsStatus === 'connected' && nanobotReady === true;
|
||||
const isChecking = wsStatus === 'connected' && nanobotReady === null;
|
||||
const isConnecting = wsStatus === 'connecting' || isChecking;
|
||||
const isOffline = wsStatus === 'disconnected' || (wsStatus === 'connected' && nanobotReady === false);
|
||||
|
||||
const color = isOnline
|
||||
? 'bg-green-500'
|
||||
: isConnecting
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-red-500';
|
||||
|
||||
const label = isOnline
|
||||
? '已连接'
|
||||
: isChecking
|
||||
? '检查中'
|
||||
: wsStatus === 'connecting'
|
||||
? '连接中'
|
||||
: isOffline && wsStatus === 'connected'
|
||||
? '服务离线'
|
||||
: '未连接';
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<span className={`w-2 h-2 rounded-full ${color}`} />
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Header = () => {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const user = useChatStore((s) => s.user);
|
||||
const isAuthLoading = useChatStore((s) => s.isAuthLoading);
|
||||
const setUser = useChatStore((s) => s.setUser);
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setUser(null);
|
||||
router.replace('/login');
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="fixed top-0 left-0 right-0 bg-background border-b border-border z-50">
|
||||
<div className="max-w-[1560px] mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center h-16 gap-4">
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<span className="text-xl">🐈</span>
|
||||
<span className="text-base font-bold sm:text-lg">Boardware Genius</span>
|
||||
</Link>
|
||||
|
||||
<nav className="flex items-center gap-1.5 whitespace-nowrap">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const isActive =
|
||||
item.href === '/'
|
||||
? pathname === '/'
|
||||
: pathname.startsWith(item.href);
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`flex items-center gap-1.5 px-3.5 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
isActive
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
{item.name}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<div className="ml-2 pl-4 border-l border-border flex items-center gap-1.5">
|
||||
{user ? (
|
||||
<>
|
||||
<div className="flex items-center gap-1.5 px-3.5 py-2 rounded-md text-sm font-medium text-foreground">
|
||||
<UserCircle2 className="w-4 h-4" />
|
||||
<span className="max-w-32 truncate">{user.username}</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLogout}
|
||||
className="flex items-center gap-1.5 px-3.5 py-2 rounded-md text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
退出登录
|
||||
</button>
|
||||
</>
|
||||
) : !isAuthLoading ? (
|
||||
AUTH_ITEMS.map((item) => {
|
||||
const isActive = pathname.startsWith(item.href);
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`flex items-center gap-1.5 px-3.5 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
isActive
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
{item.name}
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
) : null}
|
||||
</div>
|
||||
<div className="ml-4 pl-4 border-l border-border">
|
||||
<ConnectionDot />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
Reference in New Issue
Block a user