"use client"; import { useState, useEffect } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import Link from "next/link"; import { MessagesSquare } from "lucide-react"; import { apiLogin, apiMe } from "@/lib/api"; import { hasAuthCookieHint } from "@/lib/cookies"; import { useAllowComments } from "@/components/CommentsPolicyProvider"; import { useAllowMessages } from "@/components/MessagesPolicyProvider"; // 仅允许同源相对路径跳转,拦截 //host、/\\host 与绝对 URL 形式的开放重定向 function safeRedirect(to: string | null): string { if (!to || !to.startsWith("/") || to.startsWith("//") || to.startsWith("/\\")) { return "/"; } return to; } function RegisterHint() { return (

还没有账号? 立即注册

); } export default function LoginPage() { const router = useRouter(); const searchParams = useSearchParams(); const allowComments = useAllowComments(); const allowMessages = useAllowMessages(); const redirect = safeRedirect(searchParams.get("redirect")); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); // 已登录用户访问登录页:直接回落到安全的跳转目标 useEffect(() => { if (!hasAuthCookieHint()) return; apiMe() .then((res) => { if (res.user) router.replace(redirect); }) .catch(() => {}); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(""); setLoading(true); try { const res = await apiLogin(username, password); // 登录成功后后端通过 Set-Cookie 写入 HttpOnly JWT,前端无需存储 token if (res.user) { // root layout 在客户端导航中持久化,push 后再 refresh 让其重新取登录态 router.push(redirect); router.refresh(); } else { setError(res.error || "登录失败"); } } finally { setLoading(false); } }; return (

欢迎回来

登录后发帖{allowComments ? "、评论" : ""}{allowMessages ? "、发消息" : ""}与点赞

{error &&
{error}
}
setUsername(e.target.value)} required autoFocus autoComplete="username" className="field" placeholder="你的用户名" />
setPassword(e.target.value)} required autoComplete="current-password" className="field" placeholder="输入密码" />

找回密码

); }