57 lines
2.2 KiB
TypeScript
57 lines
2.2 KiB
TypeScript
import type { Metadata } from "next";
|
||
import Link from "next/link";
|
||
import { cookies } from "next/headers";
|
||
import { ShieldAlert } from "lucide-react";
|
||
import { authCookieHeader } from "@/lib/cookies";
|
||
import { getMeCached } from "@/lib/serverData";
|
||
import { isStaff } from "@/lib/roles";
|
||
import { AdminSidebar, AdminNavMobile } from "./AdminNav";
|
||
|
||
// 整个 /admin 树不进入索引(子页面无需重复声明)
|
||
export const metadata: Metadata = {
|
||
robots: { index: false, follow: false },
|
||
};
|
||
|
||
// 权限以 Go 端 RequireStaff 为唯一判定;这里仅决定管理界面是否直出,
|
||
// 非管理团队访问任意 /admin/* 时 SSR 统一给出无权限提示(所有写接口仍由后端 403 兜底)。
|
||
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
|
||
const cookie = authCookieHeader(await cookies());
|
||
const me = await getMeCached(cookie || undefined);
|
||
|
||
if (!me.user || !isStaff(me.user.role)) {
|
||
return (
|
||
<div className="max-w-md mx-auto panel p-8 text-center mt-6 lg:mt-10">
|
||
<span
|
||
className="inline-flex w-14 h-14 rounded-full items-center justify-center mb-4"
|
||
style={{ background: "var(--gold-soft)", color: "var(--gold)" }}
|
||
>
|
||
<ShieldAlert size={26} />
|
||
</span>
|
||
<h1 className="text-lg font-extrabold" style={{ color: "var(--ink)" }}>
|
||
需要管理员权限
|
||
</h1>
|
||
<p className="meta mt-2 text-[13px]">此区域仅供站点管理员访问,如有疑问请联系站长。</p>
|
||
<Link href="/" className="btn btn-primary mt-6">
|
||
返回首页
|
||
</Link>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<AdminNavMobile user={me.user} />
|
||
<div className="flex items-start gap-6 xl:gap-7">
|
||
{/* sticky 必须钉在 aside 自身:内层与侧栏同高时等于没钉住,会跟着主栏一起滚 */}
|
||
<aside
|
||
className="hidden lg:block w-60 xl:w-64 shrink-0 sticky self-start z-20 max-h-[calc(100vh-6.5rem)] overflow-y-auto"
|
||
style={{ top: 84 }}
|
||
>
|
||
<AdminSidebar user={me.user} />
|
||
</aside>
|
||
<section className="flex-1 min-w-0">{children}</section>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|