Files
leaudit-platform-frontend/app/routes/login.tsx
T

304 lines
10 KiB
TypeScript

import { useEffect, useState } from "react";
import { useSearchParams, Form } from "@remix-run/react";
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs, redirect } from "@remix-run/node";
import { OAuthClient } from "~/api/login/oauth-client";
import { OAUTH_CONFIG } from "~/config/api-config";
import { getUserSession, getSession, simpleRootLogin } from "~/api/login/auth.server";
import styles from "~/styles/pages/login.css?url";
import { toastService } from "~/components/ui";
export const links = () => [
{ rel: "stylesheet", href: styles }
];
export const meta: MetaFunction = () => {
return [
{ title: "中国烟草AI合同及卷宗审核系统 - 登录" },
{ name: "description", content: "中国烟草AI合同及卷宗审核系统登录页面" },
];
};
// 加载器,获取当前会话状态
export async function loader({ request }: LoaderFunctionArgs) {
const { isAuthenticated } = await getUserSession(request);
// 如果已登录,重定向到首页
if (isAuthenticated) {
return redirect("/");
}
// 获取重定向URL并保存到session
const url = new URL(request.url);
const redirectTo = url.searchParams.get("redirect") || "/";
const session = await getSession(request);
session.set("redirectTo", redirectTo);
return Response.json({
isAuthenticated: false,
redirectTo
});
}
// 处理表单提交的action函数
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const intent = formData.get("intent");
const username = formData.get("username")?.toString().trim();
const password = formData.get("password")?.toString().trim();
if (intent === "password_login") {
// 获取重定向目标
const session = await getSession(request);
const redirectTo = session.get("redirectTo") || "/";
// 调用 simpleRootLogin 方法进行登录
const response = await simpleRootLogin(username || "", password || "", redirectTo);
// 检查响应状态
if (response.status === 302) {
// 登录成功,直接返回重定向响应
return response;
} else {
// 登录失败,解析错误信息并重定向到登录页面
const errorData = await response.json();
const errorMsg = errorData.error || "登录失败";
return redirect(`/login?error=${encodeURIComponent(errorMsg)}`);
}
}
return null;
}
export default function Login() {
const [searchParams] = useSearchParams();
const error = searchParams.get("error");
const [isFlipped, setIsFlipped] = useState(false);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
// 获取错误消息的友好描述
const getErrorMessage = (error: string | null) => {
if (!error) return null;
switch (error) {
case "missing_code":
return "登录过程中缺少授权码,请重新登录";
case "invalid_state":
return "登录状态验证失败,请重新登录";
case "token_error":
return "获取访问令牌失败,请重新登录";
case "userinfo_error":
return "获取用户信息失败,请重新登录";
case "callback_error":
return "登录回调处理失败,请重新登录";
case "用户名和密码不能为空":
case "用户名和密码不能为空,请重新输入":
return "用户名和密码不能为空,请重新输入";
case "登录失败,请检查用户名和密码":
case "用户名或密码错误,请重新输入":
return "用户名或密码错误,请重新输入";
case "登录请求失败,请稍后重试":
case "网络连接失败,请稍后重试":
return "网络连接失败,请稍后重试";
default:
return decodeURIComponent(error);
}
};
// 处理OAuth2.0登录
const handleOAuthLogin = () => {
try {
// 创建OAuth客户端
const oauthClient = new OAuthClient(OAUTH_CONFIG);
// 生成状态值
const state = oauthClient.generateState();
// 将状态值保存到localStorage(用于后续验证)
localStorage.setItem("oauth_state", state);
// 获取授权URL
const authorizeUrl = oauthClient.getAuthorizeUrl(state);
console.log("授权URL:", authorizeUrl);
// 重定向到IDaaS登录页面
window.location.href = authorizeUrl;
} catch (error) {
console.error("启动OAuth2.0登录失败:", error);
alert("登录系统初始化失败,请联系系统管理员");
}
};
// 处理管理员登录
const handleAdminLogin = () => {
setIsFlipped(true);
};
// 处理返回OAuth登录
const handleBackToOAuth = () => {
setIsFlipped(false);
setUsername("");
setPassword("");
};
// 处理账号密码登录表单提交
const handlePasswordLoginSubmit = (e: React.FormEvent) => {
// 客户端验证
if (!username.trim()) {
e.preventDefault();
toastService.error("请输入用户名");
return;
}
if (!password.trim()) {
e.preventDefault();
toastService.error("请输入密码");
return;
}
// 验证通过,让表单正常提交
};
useEffect(() => {
// 检查OAuth配置是否完整
if (!OAUTH_CONFIG.serverUrl || !OAUTH_CONFIG.clientId || !OAUTH_CONFIG.clientSecret) {
console.error("OAuth2.0配置不完整:", OAUTH_CONFIG);
}
}, []);
return (
<div className="login-page">
<div className={`login-container ${isFlipped ? 'flipped' : ''}`}>
<div className="login-card">
{/* 正面 - OAuth登录 */}
<div className="login-card-front">
<div className="login-header">
<h1 className="login-title">AI合同及卷宗审核系统</h1>
</div>
<div className="login-form-container">
<h2 className="login-subtitle"></h2>
{error && (
<div className="error-message-container">
<div className="error-icon"><i className="ri-error-warning-line"></i></div>
<div className="error-text">{getErrorMessage(error)}</div>
</div>
)}
<div className="oauth-login-section">
<div className="login-description">
<p></p>
</div>
<button
onClick={handleOAuthLogin}
className="oauth-login-button"
type="button"
>
<i className="ri-shield-user-line"></i>
</button>
<div className="login-tips">
<p>
<i className="ri-information-line"></i>
</p>
</div>
</div>
{/* 管理员登录链接 */}
<div className="admin-login-link">
<button
onClick={handleAdminLogin}
className="admin-login-text"
type="button"
>
</button>
</div>
</div>
<div className="login-footer">
<p>© 2025 </p>
</div>
</div>
{/* 背面 - 管理员登录 */}
<div className="login-card-back">
<div className="login-header">
<h1 className="login-title">AI合同及卷宗审核系统</h1>
</div>
<div className="login-form-container">
<h2 className="login-subtitle"></h2>
{error && (
<div className="error-message-container">
<div className="error-icon"><i className="ri-error-warning-line"></i></div>
<div className="error-text">{getErrorMessage(error)}</div>
</div>
)}
<Form method="post" className="admin-login-form" onSubmit={handlePasswordLoginSubmit}>
<input type="hidden" name="intent" value="password_login" />
<div className="form-group">
<label htmlFor="username" className="form-label"></label>
<input
type="text"
id="username"
name="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="form-input"
placeholder="请输入用户名"
required
/>
</div>
<div className="form-group">
<label htmlFor="password" className="form-label"></label>
<input
type="password"
id="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="form-input"
placeholder="请输入密码"
required
/>
</div>
<button
type="submit"
className="admin-login-button"
>
<i className="ri-login-box-line"></i>
</button>
</Form>
<div className="back-to-oauth">
<button
onClick={handleBackToOAuth}
className="back-button"
type="button"
>
<i className="ri-arrow-left-line"></i>
</button>
</div>
</div>
<div className="login-footer">
<p>© 2025 </p>
</div>
</div>
</div>
</div>
</div>
);
}