import type { Metadata } from "next"; import Link from "next/link"; import { cookies, headers } from "next/headers"; import { redirect } from "next/navigation"; import { ShieldAlert } from "lucide-react"; import { authCookieHeader, REFRESH_COOKIE, CSRF_COOKIE, TOKEN_COOKIE } from "@/lib/cookies"; import { getMeCached, getPublicSettingsCached } from "@/lib/serverData"; import { canAccessAdminMessages, hasAnyChannelPerm, isStaff } from "@/lib/roles"; import AdminShell from "@/components/admin/AdminShell"; import AdminSessionRecover from "./AdminSessionRecover"; // 整个 /admin 树不进入索引(子页面无需重复声明) export const metadata: Metadata = { robots: { index: false, follow: false }, }; // 权限以 Go 端为唯一判定;此处仅决定管理界面是否直出。 // 允许:管理角色 / 消息管理/群管权限 / 任一渠道权限(公告/单页/书库/广告)。 export default async function AdminLayout({ children }: { children: React.ReactNode }) { const cookieStore = await cookies(); const cookie = authCookieHeader(cookieStore); const me = await getMeCached(cookie || undefined); const settings = await getPublicSettingsCached(); const theme = cookieStore.get("j13-theme")?.value === "dark" ? "dark" : "light"; const allowed = !!me.user && (isStaff(me.user.role) || canAccessAdminMessages(me.user) || hasAnyChannelPerm(me.user)); if (!allowed || !me.user) { // 已登录但无后台权限:保留提示卡片 if (me.user) { return (

需要管理员权限

此区域仅供站点管理员访问,如有疑问请联系站长。

返回首页
); } // SSR 侧未识别出用户(/api/me 瞬断、access 过期轮转竞态等)时,原实现直接 // 落到无壳的权限卡片——手机端表现为「首次进入后台整块导航 header 消失」, // 无法切换管理板块,再进一次才恢复。这里按凭据状态分流入修复: // ① 完全无凭据:直达登录页并回跳目标地址,不再给无导航的死胡同卡片; // ② 仍带凭据:客户端经 fetchWithRefresh 自动续期后 router.refresh 自愈。 const hasAuthHint = !!( cookieStore.get(TOKEN_COOKIE)?.value || cookieStore.get(REFRESH_COOKIE)?.value || cookieStore.get(CSRF_COOKIE)?.value ); if (!hasAuthHint) { const pathname = (await headers()).get("x-pathname") || "/admin"; redirect(`/login?redirect=${encodeURIComponent(pathname)}`); } return (
); } const user = me.user; // 未读仅在有 access cookie 时才可信;壳层铃铛用 SSR 直出避免挂载后才出红点 const unread = cookieStore.get(TOKEN_COOKIE)?.value ? me.unread_count : 0; return ( {children} ); }