Files
blog/write-server/src/app/login/page.tsx
T
2026-06-22 15:11:09 +08:00

122 lines
4.0 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
export default function LoginPage() {
const router = useRouter();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
// 已登录则跳转
const token = localStorage.getItem("wauth_token");
if (token) {
fetch("/api/auth/login", {
headers: { "X-Auth-User": token },
}).then(r => {
if (r.ok) router.replace("/");
else localStorage.removeItem("wauth_token");
}).catch(() => localStorage.removeItem("wauth_token"));
}
}, [router]);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setLoading(true);
try {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
const data = await res.json();
if (!res.ok) {
setError(data.error || "登录失败");
return;
}
// 存储用户信息
localStorage.setItem("wauth_token", data.user.username);
localStorage.setItem("wauth_user", JSON.stringify(data.user));
// 注册 Service Worker
if ("serviceWorker" in navigator) {
const reg = await navigator.serviceWorker.register("/sw.js");
if (reg.installing) {
await new Promise<void>(resolve => {
reg.installing!.addEventListener("statechange", function () {
if (this.state === "activated") resolve();
});
});
}
const sw = reg.active || reg.waiting || reg.installing;
if (sw) {
sw.postMessage({ type: "SET_TOKEN", token: data.user.username });
}
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({ type: "SET_TOKEN", token: data.user.username });
}
}
router.replace("/");
} catch {
setError("网络错误,请重试");
} finally {
setLoading(false);
}
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-bg">
<div className="bg-surface rounded-2xl shadow-lg p-10 w-full max-w-sm mx-4">
<h1 className="text-xl font-bold text-accent mb-1">写作后台</h1>
<p className="text-[13px] text-muted mb-8">优世界 · Blog Admin</p>
{error && (
<div className="bg-red-50 text-red-600 text-[13px] px-4 py-2.5 rounded-lg mb-5">
{error}
</div>
)}
<form onSubmit={handleLogin} className="space-y-5">
<div>
<label className="block text-[13px] text-text font-medium mb-1.5">用户名</label>
<input
type="text"
value={username}
onChange={e => setUsername(e.target.value)}
className="w-full px-3.5 py-2.5 rounded-lg border border-line bg-bg text-text text-sm outline-none focus:border-accent transition-colors"
autoFocus
required
/>
</div>
<div>
<label className="block text-[13px] text-text font-medium mb-1.5">密码</label>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
className="w-full px-3.5 py-2.5 rounded-lg border border-line bg-bg text-text text-sm outline-none focus:border-accent transition-colors"
required
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-2.5 bg-accent text-white rounded-lg text-sm font-semibold hover:opacity-90 disabled:opacity-50 transition-opacity"
>
{loading ? "登录中..." : "登 录"}
</button>
</form>
</div>
</div>
);
}