fix: 管理会话自动恢复与访问来源统计优化
- 新增 AdminSessionRecover:后台离线/后端重启时自动重试恢复会话,明确失效才回跳登录 - 认证 cookie 契约调整(SameSite=Lax 与刷新轮转适配),路由与守卫适配 - 访问来源统计查询优化与测试、管理端 analytics sources 页面适配 - 移动端布局修正:覆盖 body min-h-screen 避免内容区高度异常
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { cookies } from "next/headers";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ShieldAlert } from "lucide-react";
|
||||
import { authCookieHeader, TOKEN_COOKIE } from "@/lib/cookies";
|
||||
import { authCookieHeader, REFRESH_COOKIE, CSRF_COOKIE, TOKEN_COOKIE } from "@/lib/cookies";
|
||||
import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
|
||||
import { canAccessAdminMessages, isStaff } from "@/lib/roles";
|
||||
import AdminShell from "@/components/admin/AdminShell";
|
||||
import AdminSessionRecover from "./AdminSessionRecover";
|
||||
|
||||
// 整个 /admin 树不进入索引(子页面无需重复声明)
|
||||
export const metadata: Metadata = {
|
||||
@@ -25,6 +27,48 @@ export default async function AdminLayout({ children }: { children: React.ReactN
|
||||
!!me.user && (isStaff(me.user.role) || canAccessAdminMessages(me.user));
|
||||
|
||||
if (!allowed || !me.user) {
|
||||
// 已登录但无后台权限:保留提示卡片
|
||||
if (me.user) {
|
||||
return (
|
||||
<div
|
||||
data-admin-viewport
|
||||
id="main"
|
||||
tabIndex={-1}
|
||||
className="min-h-screen flex items-center justify-center px-4 outline-none"
|
||||
>
|
||||
<div className="max-w-md w-full panel p-8 text-center">
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div
|
||||
data-admin-viewport
|
||||
@@ -32,21 +76,7 @@ export default async function AdminLayout({ children }: { children: React.ReactN
|
||||
tabIndex={-1}
|
||||
className="min-h-screen flex items-center justify-center px-4 outline-none"
|
||||
>
|
||||
<div className="max-w-md w-full panel p-8 text-center">
|
||||
<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>
|
||||
<AdminSessionRecover />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user