You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
98 lines
3.1 KiB
98 lines
3.1 KiB
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
import { type ReactNode, useEffect } from "react";
|
|
import { Truck, SignOut } from "@phosphor-icons/react";
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
|
|
|
const NAV_ITEMS = [
|
|
{ href: "/dashboard", label: "工作台" },
|
|
{ href: "/admin/markup", label: "加价配置" },
|
|
{ href: "/admin/alerts", label: "预警中心" },
|
|
{ href: "/admin/rpa", label: "RPA 状态" },
|
|
{ href: "/admin/dashboard", label: "指标看板" },
|
|
{ href: "/admin/queues", label: "队列监控" },
|
|
];
|
|
|
|
interface AdminLayoutProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function AdminLayout({ children }: AdminLayoutProps) {
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
const { user, token, ready, logout } = useAuth();
|
|
|
|
useEffect(() => {
|
|
if (!ready) return;
|
|
if (!token || !user) {
|
|
const redirect = encodeURIComponent(pathname);
|
|
router.replace(`/login?redirect=${redirect}`);
|
|
}
|
|
}, [ready, token, user, pathname, router]);
|
|
|
|
if (!ready) {
|
|
return (
|
|
<div className="flex min-h-[100dvh] items-center justify-center">
|
|
<LoadingSpinner size={32} className="text-primary" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!token || !user) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-[100dvh] bg-bg">
|
|
<header className="border-b border-border bg-surface">
|
|
<div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4 md:px-6 lg:px-8">
|
|
<div className="flex items-center gap-2">
|
|
<Truck size={24} weight="fill" className="text-primary" />
|
|
<span className="font-semibold">美美与共 · 管理端</span>
|
|
<span className="rounded-sm bg-primary/10 px-2 py-0.5 text-xs text-primary">
|
|
管理员
|
|
</span>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
logout();
|
|
router.replace("/login");
|
|
}}
|
|
className="inline-flex items-center gap-1 text-sm text-text-secondary hover:text-text-primary"
|
|
>
|
|
<SignOut size={16} />
|
|
退出
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<div className="mx-auto flex max-w-7xl gap-6 px-4 py-6 md:px-6 lg:px-8">
|
|
<aside className="hidden w-48 shrink-0 md:block">
|
|
<nav className="space-y-1">
|
|
{NAV_ITEMS.map((item) => {
|
|
const active = pathname === item.href;
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={`block rounded-md px-3 py-2 text-sm font-medium transition-colors ${
|
|
active
|
|
? "bg-primary/10 text-primary"
|
|
: "text-text-secondary hover:bg-bg hover:text-text-primary"
|
|
}`}
|
|
>
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
</aside>
|
|
<main className="min-w-0 flex-1">{children}</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|