54 lines
1.8 KiB
TypeScript
54 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { useAuthStore } from "@/lib/stores/auth-store";
|
|
import { useRouter } from "next/navigation";
|
|
import type { ReactNode } from "react";
|
|
|
|
import { AppShell } from "@/components/layout/AppShell";
|
|
import { LoadingState } from "@/components/shared/StateViews";
|
|
|
|
/**
|
|
* 主域壳层:日常对话 / 人物 / 人格 / 记忆 / 轨迹等路由共用 AppShell。
|
|
*
|
|
* 认证状态由 AuthInitializer(根布局)通过 BFF 初始化(user 首屏防闪用 localStorage),
|
|
* 此布局仅做路由守卫:未认证时重定向到 /login。
|
|
*/
|
|
export default function AppGroupLayout({ children }: { children: ReactNode }) {
|
|
const router = useRouter();
|
|
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
const logout = useAuthStore((s) => s.logout);
|
|
const [ready, setReady] = useState(false);
|
|
|
|
// 等待 AuthInitializer 完成(给 loadFromBff 机会)
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => setReady(true), 100);
|
|
return () => clearTimeout(timer);
|
|
}, []);
|
|
|
|
// 认证失败时登出并跳转登录页
|
|
const handleAuthError = useCallback(() => {
|
|
logout();
|
|
router.push("/login");
|
|
}, [logout, router]);
|
|
|
|
// 全局监听 ApiError "unauthorized"(通过自定义事件)
|
|
useEffect(() => {
|
|
const handler = () => handleAuthError();
|
|
window.addEventListener("auth:unauthorized", handler);
|
|
return () => window.removeEventListener("auth:unauthorized", handler);
|
|
}, [handleAuthError]);
|
|
|
|
useEffect(() => {
|
|
if (ready && !isAuthenticated) {
|
|
router.push("/login");
|
|
}
|
|
}, [ready, isAuthenticated, router]);
|
|
|
|
return (
|
|
<AppShell>
|
|
{ready && isAuthenticated ? children : <LoadingState label="辰光未启…" />}
|
|
</AppShell>
|
|
);
|
|
}
|