Spaces:
Paused
Paused
Download frontend/src/components/Layout.tsx from lbb123/p01: direct link, hf CLI and curl.
- Browser
- Download file 4.09 kB
-
https://huggingface.co/spaces/lbb123/p01/resolve/main/frontend/src/components/Layout.tsx
- Command line
-
hf download hf://spaces/lbb123/p01/frontend/src/components/Layout.tsx
-
curl -L -o Layout.tsx https://huggingface.co/spaces/lbb123/p01/resolve/main/frontend/src/components/Layout.tsx
4.09 kB
| import { useState } from 'react' | |
| import { NavLink, useNavigate } from 'react-router-dom' | |
| import { LayoutDashboard, KeyRound, KeySquare, ScrollText, SlidersHorizontal, Receipt, LogOut, Zap, Menu, X } from 'lucide-react' | |
| import { clearToken } from '../api' | |
| import { ThemeToggle } from './Theme' | |
| const nav = [ | |
| { to: '/', label: '仪表盘', icon: LayoutDashboard, end: true }, | |
| { to: '/keys', label: 'API Keys', icon: KeyRound, end: false }, | |
| { to: '/upstream', label: '上游 Key', icon: KeySquare, end: false }, | |
| { to: '/logs', label: '请求日志', icon: ScrollText, end: false }, | |
| { to: '/settings', label: '设置', icon: SlidersHorizontal, end: false }, | |
| { to: '/billing', label: '计费', icon: Receipt, end: false }, | |
| ] | |
| function Brand() { | |
| return ( | |
| <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-violet-600 shadow-sm shadow-indigo-500/40"> | |
| <Zap size={15} className="text-white" fill="currentColor" /> | |
| </div> | |
| ) | |
| } | |
| export default function Layout({ children }: { children: JSX.Element }) { | |
| const navigate = useNavigate() | |
| const [open, setOpen] = useState(false) | |
| const logout = () => { | |
| clearToken() | |
| navigate('/login', { replace: true }) | |
| } | |
| return ( | |
| <div className="flex min-h-screen flex-col md:flex-row"> | |
| {/* 移动端顶栏 */} | |
| <header className="sticky top-0 z-30 flex h-14 shrink-0 items-center justify-between border-b border-slate-800 bg-slate-900 px-4 text-white md:hidden"> | |
| <div className="flex items-center gap-2.5 font-semibold"> | |
| <Brand /> | |
| <span>Pasl Relay</span> | |
| </div> | |
| <button onClick={() => setOpen(true)} className="-mr-2 p-2 text-slate-400 hover:text-white" aria-label="打开菜单"> | |
| <Menu size={20} /> | |
| </button> | |
| </header> | |
| {/* 移动端遮罩 */} | |
| {open && <div className="fixed inset-0 z-40 animate-fade-in bg-black/50 md:hidden" onClick={() => setOpen(false)} />} | |
| {/* 侧边栏:桌面常驻,移动端抽屉 */} | |
| <aside | |
| className={`fixed md:static inset-y-0 left-0 z-50 flex w-64 shrink-0 flex-col bg-slate-900 transition-transform duration-200 ${ | |
| open ? 'translate-x-0' : '-translate-x-full md:translate-x-0' | |
| }`} | |
| > | |
| <div className="flex h-14 shrink-0 items-center gap-2.5 border-b border-slate-800/80 px-4 text-white"> | |
| <Brand /> | |
| <div className="min-w-0 flex-1"> | |
| <div className="text-sm font-semibold leading-tight">Pasl Relay</div> | |
| <div className="text-[10px] leading-tight text-slate-500">中转管理后台</div> | |
| </div> | |
| <ThemeToggle /> | |
| <button onClick={() => setOpen(false)} className="p-1 text-slate-400 hover:text-white md:hidden" aria-label="关闭菜单"> | |
| <X size={18} /> | |
| </button> | |
| </div> | |
| <nav className="flex-1 space-y-1 overflow-y-auto px-3 py-4"> | |
| {nav.map((n) => ( | |
| <NavLink key={n.to} to={n.to} end={n.end} onClick={() => setOpen(false)} className="block"> | |
| {({ isActive }) => ( | |
| <span | |
| className={`flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors ${ | |
| isActive ? 'bg-indigo-500/15 text-white' : 'text-slate-400 hover:bg-white/5 hover:text-slate-100' | |
| }`} | |
| > | |
| <n.icon size={16} className={isActive ? 'text-indigo-400' : ''} /> | |
| {n.label} | |
| </span> | |
| )} | |
| </NavLink> | |
| ))} | |
| </nav> | |
| <div className="shrink-0 border-t border-slate-800/80 p-3"> | |
| <button | |
| onClick={logout} | |
| className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm text-slate-400 transition-colors hover:bg-white/5 hover:text-white" | |
| > | |
| <LogOut size={16} /> | |
| 退出登录 | |
| </button> | |
| </div> | |
| </aside> | |
| <main className="min-w-0 flex-1 overflow-x-hidden">{children}</main> | |
| </div> | |
| ) | |
| } | |