5278190750
- 后端: 凭证生成引擎、金蝶导出器、凭证模板服务 - 后端: 成本分析服务、AI问答服务 - 后端: 科目映射CRUD API、分析API、QA API - 后端: 集成测试(认证/任务/凭证) 49个测试全部通过 - 前端: 凭证管理、成本分析、导出中心、知识库、系统设置页面 - 前端: AuthGuard认证守卫、Dashboard AI聊天功能 - 前端: Playwright E2E测试 16 passed, 1 skipped - 基础设施: Docker Compose、Nginx反向代理、.env.example - 文档: 用户手册、管理员手册、发布检查清单
53 lines
1.5 KiB
TypeScript
53 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { useAuthStore } from "@/lib/stores/auth-store";
|
|
|
|
/**
|
|
* 认证守卫组件
|
|
* 检查用户是否已登录,未登录时重定向到登录页
|
|
*/
|
|
export function AuthGuard({ children }: { children: React.ReactNode }) {
|
|
const router = useRouter();
|
|
const { user, token } = useAuthStore();
|
|
const [checked, setChecked] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// zustand persist 从 localStorage 恢复是异步的,需要等待一帧
|
|
if (!user || !token) {
|
|
router.replace("/login");
|
|
} else {
|
|
setChecked(true);
|
|
}
|
|
}, [user, token, router]);
|
|
|
|
if (!checked) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
<div className="flex flex-col items-center gap-3">
|
|
<svg className="animate-spin w-6 h-6 text-primary" viewBox="0 0 24 24">
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
fill="none"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
<span className="text-sm text-muted-foreground">加载中...</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|