// import React from 'react'; import { Links, // LiveReload, // 不再需要,使用Vite时会与内置HMR冲突 Meta, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse, useRouteError, type MetaFunction, useLoaderData } from "@remix-run/react"; import { LoaderFunctionArgs, redirect, createCookieSessionStorage, ActionFunctionArgs } from "@remix-run/node"; import { Layout } from "~/components/layout/Layout"; import { ErrorBoundary as AppErrorBoundary } from "~/components/error/ErrorBoundary"; import { MessageModalProvider } from "~/components/ui/MessageModal"; import { ToastProvider } from "~/components/ui/Toast"; import "remixicon/fonts/remixicon.css"; // 导入样式 import styles from "~/styles/main.css?url"; import messageModalStyles from "~/styles/components/message-modal.css?url"; import toastStyles from "~/styles/components/toast.css?url"; import LoadingBarContainer from "~/components/ui/LoadingBar"; import RouteChangeLoader from "~/components/ui/RouteChangeLoader"; // import { useState, useEffect } from "react"; // 定义用户角色类型 export type UserRole = 'common' | 'developer'; // 定义需要高级权限的路径 export const developerOnlyPaths = [ '/settings', '/config-lists', '/document-types', '/prompts', ]; // 创建基于Cookie的会话存储 // 在实际应用中,应该使用环境变量来设置密钥 export const sessionStorage = createCookieSessionStorage({ cookie: { name: "__lgsession", httpOnly: true, path: "/", sameSite: "lax", secrets: ["s3cr3t"], // 应该从环境变量读取 // secure: process.env.NODE_ENV === "production", maxAge: 60 * 60 * 24 * 1, // 1天 secure: false, // 开发环境中禁用secure }, }); // 获取会话对象 export async function getSession(request: Request) { const cookie = request.headers.get("Cookie"); return sessionStorage.getSession(cookie); } // 获取用户登录状态 export async function getUserSession(request: Request) { const session = await getSession(request); const isAuthenticated = session.get("isAuthenticated") === true; const userRole = session.get("userRole") || 'common' as UserRole; // console.log("获取会话状态:", // // "Cookie:", request.headers.get("Cookie"), // "是否认证:", isAuthenticated, // "用户角色:", userRole // ); return { isAuthenticated, userRole }; } // 创建登录会话 export async function createUserSession(isAuthenticated: boolean, userRole: UserRole, redirectTo: string) { const session = await sessionStorage.getSession(); session.set("isAuthenticated", isAuthenticated); session.set("userRole", userRole); const cookie = await sessionStorage.commitSession(session); console.log("创建会话 - 设置Cookie:", !!cookie); console.log("创建会话 - 用户角色:", userRole); console.log("创建会话 - 重定向到:", redirectTo); return redirect(redirectTo, { headers: { "Set-Cookie": cookie, }, }); } // 销毁会话(登出) export async function logout(request: Request) { const session = await getSession(request); return redirect("/login", { headers: { "Set-Cookie": await sessionStorage.destroySession(session), }, }); } // 添加action处理登录/登出请求 export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const intent = formData.get("intent"); if (intent === "logout") { return logout(request); } return null; } // 添加loader函数进行全局认证检查并传递环境变量给客户端 export async function loader({ request }: LoaderFunctionArgs) { // 获取当前路径 const url = new URL(request.url); const pathname = url.pathname; // 排除不需要登录验证的路径 const publicPaths = ['/login', '/favicon.ico']; const isPublicPath = publicPaths.some(path => pathname.startsWith(path)); // 获取用户会话 const { isAuthenticated, userRole } = await getUserSession(request); // console.log("是否公开路径:", isPublicPath, "是否已认证:", isAuthenticated); // 如果访问需要认证的路径但未登录,重定向到登录页 if (!isPublicPath && !isAuthenticated) { console.log("未认证,需要重定向到登录页"); // 保存请求的URL,以便登录后重定向回来 const session = await getSession(request); // 如果路径是/home,则将重定向目标设置为/ const redirectTarget = pathname !== "/" ? "/" : pathname; // const redirectTarget = pathname === "home" ? "/" : pathname; // 保存重定向目标 session.set("redirectTo", redirectTarget); return redirect("/login", { headers: { "Set-Cookie": await sessionStorage.commitSession(session), }, }); } // 如果已登录且访问登录页,重定向到首页 if (pathname === "/login" && isAuthenticated) { console.log("已认证,重定向到首页"); return redirect("/"); } // 检查访问权限 - 如果是common用户访问了开发者专属页面,重定向到首页 if (userRole === 'common' && developerOnlyPaths.some(path => pathname.startsWith(path))) { console.log("用户没有访问权限,重定向到首页"); return redirect("/"); } // 向组件传递认证状态、当前路径和环境变量 return Response.json({ isAuthenticated, userRole, pathname, ENV: { NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, NEXT_PUBLIC_APP_ID: process.env.NEXT_PUBLIC_APP_ID, NEXT_PUBLIC_APP_KEY: process.env.NEXT_PUBLIC_APP_KEY, }, }); } export const meta: MetaFunction = () => { return [ { charSet: "utf-8" }, { name: "viewport", content: "width=device-width,initial-scale=1" }, { title: "中国烟草AI合同及卷宗审核系统" }, { name: "description", content: "专业的AI合同及卷宗评查系统,提供智能审核、风险评估和规范化建议" }, { name: "robots", content: "noindex,nofollow" } // 内部系统,防止被搜索引擎索引 ]; }; // 使用links函数为应用加载CSS和其他资源 export function links() { return [ { rel: "stylesheet", href: styles }, { rel: "stylesheet", href: messageModalStyles }, { rel: "stylesheet", href: toastStyles }, // 添加 Antd 样式 { rel: "stylesheet", href: "https://cdn.jsdelivr.net/npm/antd@5/dist/reset.css" }, { rel: "icon", type: "image/svg+xml", href: "/logo.svg" }, // { rel: "preconnect", href: "https://fonts.googleapis.com" }, // { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }, // { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" } ]; } export default function App() { const { userRole, ENV } = useLoaderData(); return (