259 lines
7.7 KiB
TypeScript
259 lines
7.7 KiB
TypeScript
// 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";
|
|
|
|
// 创建基于Cookie的会话存储
|
|
// 在实际应用中,应该使用环境变量来设置密钥
|
|
// const sessionStorage = createCookieSessionStorage({
|
|
// cookie: {
|
|
// name: "__session",
|
|
// httpOnly: true,
|
|
// path: "/",
|
|
// sameSite: "lax",
|
|
// secrets: ["s3cr3t"], // 应该从环境变量读取
|
|
// secure: process.env.NODE_ENV === "production",
|
|
// },
|
|
// });
|
|
|
|
// // 获取会话对象
|
|
// 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);
|
|
// return {
|
|
// isAuthenticated: session.get("isAuthenticated") === true,
|
|
// };
|
|
// }
|
|
|
|
// // 创建登录会话
|
|
// export async function createUserSession(isAuthenticated: boolean, redirectTo: string) {
|
|
// const session = await sessionStorage.getSession();
|
|
// session.set("isAuthenticated", isAuthenticated);
|
|
|
|
// return redirect(redirectTo, {
|
|
// headers: {
|
|
// "Set-Cookie": await sessionStorage.commitSession(session),
|
|
// },
|
|
// });
|
|
// }
|
|
|
|
// // 销毁会话(登出)
|
|
// 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 } = await getUserSession(request);
|
|
|
|
// // 如果访问需要认证的路径但未登录,重定向到登录页
|
|
// if (!isPublicPath && !isAuthenticated) {
|
|
// // 保存请求的URL,以便登录后重定向回来
|
|
// const session = await getSession(request);
|
|
// session.set("redirectTo", pathname);
|
|
|
|
// return redirect("/login", {
|
|
// headers: {
|
|
// "Set-Cookie": await sessionStorage.commitSession(session),
|
|
// },
|
|
// });
|
|
// }
|
|
|
|
// // 如果已登录且访问登录页,重定向到首页
|
|
// if (pathname === "/login" && isAuthenticated) {
|
|
// return redirect("/home");
|
|
// }
|
|
|
|
// // 向组件传递认证状态和当前路径
|
|
// return Response.json({ isAuthenticated, pathname });
|
|
// }
|
|
|
|
// 添加客户端hydration错误处理
|
|
// if (typeof window !== "undefined") {
|
|
// window.addEventListener("error", (event) => {
|
|
// if (event.message && event.message.includes("Hydration failed")) {
|
|
// console.warn("Hydration error detected, refreshing page...");
|
|
// setTimeout(() => {
|
|
// window.location.reload();
|
|
// }, 100);
|
|
// event.preventDefault();
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
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 },
|
|
// { 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 { pathname } = useLoaderData<typeof loader>();
|
|
|
|
// // 确定哪些路径不需要Layout
|
|
// const noLayoutPaths = ['/login', '/home'];
|
|
// const needsLayout = !noLayoutPaths.includes(pathname);
|
|
|
|
return (
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charSet="utf-8" />
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
<style dangerouslySetInnerHTML={{ __html: `
|
|
:root {
|
|
--color-primary: #00684a;
|
|
--color-primary-hover: #005a3f;
|
|
--color-primary-light: rgba(0, 104, 74, 0.1);
|
|
--primary-color: #00684a;
|
|
|
|
/* 成功、警告、错误颜色 */
|
|
--color-success: #52c41a;
|
|
--color-warning: #faad14;
|
|
--color-error: #f5222d;
|
|
}
|
|
` }} />
|
|
<Meta />
|
|
<Links />
|
|
</head>
|
|
<body className="font-sans">
|
|
<MessageModalProvider>
|
|
<ToastProvider>
|
|
{/* {needsLayout ? (
|
|
<Layout>
|
|
<Outlet />
|
|
</Layout>
|
|
) : (
|
|
<Outlet />
|
|
)} */}
|
|
|
|
<Layout>
|
|
<Outlet />
|
|
</Layout>
|
|
|
|
<RouteChangeLoader />
|
|
</ToastProvider>
|
|
</MessageModalProvider>
|
|
<ScrollRestoration />
|
|
<Scripts />
|
|
<LoadingBarContainer />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
|
|
export function ErrorBoundary() {
|
|
const error = useRouteError();
|
|
|
|
if (isRouteErrorResponse(error)) {
|
|
return (
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<Meta />
|
|
<Links />
|
|
<title>错误 {error.status}</title>
|
|
</head>
|
|
<body>
|
|
<AppErrorBoundary
|
|
status={error.status}
|
|
statusText={error.statusText}
|
|
message={error.data?.message || "发生了一个错误,请稍后重试"}
|
|
/>
|
|
<Scripts />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<Meta />
|
|
<Links />
|
|
<title>意外错误</title>
|
|
</head>
|
|
<body>
|
|
<AppErrorBoundary
|
|
status={500}
|
|
statusText="服务器错误"
|
|
message="服务器发生了意外错误,请稍后重试"
|
|
/>
|
|
<Scripts />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|