107 lines
4.2 KiB
TypeScript
107 lines
4.2 KiB
TypeScript
import { useState } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { sendCode, register, login } from "../api";
|
|
import { useAppStore } from "../stores/app";
|
|
import { useAuthStore } from "../stores/auth";
|
|
|
|
export default function Login() {
|
|
const navigate = useNavigate();
|
|
const { account, code, nickname, isRegister, loading, setAccount, setCode, setNickname, setMode, setLoading } = useAuthStore();
|
|
const setUser = useAppStore((s) => s.setUser);
|
|
const [status, setStatus] = useState("");
|
|
|
|
const isEmail = account.includes("@");
|
|
const canSendCode = account.length >= 6;
|
|
|
|
const handleSendCode = async () => {
|
|
setLoading(true);
|
|
setStatus("");
|
|
try {
|
|
const result = await sendCode(account);
|
|
setStatus(result);
|
|
} catch (e) {
|
|
setStatus(String(e));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
setLoading(true);
|
|
setStatus("");
|
|
try {
|
|
const email = isEmail ? account : null;
|
|
const phone = isEmail ? null : account;
|
|
const result = isRegister
|
|
? await register(email, phone, code, nickname || "时尚达人")
|
|
: await login(email, phone, code);
|
|
|
|
if (result.success && result.user) {
|
|
setUser(result.user, result.token);
|
|
navigate("/home", { replace: true });
|
|
} else {
|
|
setStatus(result.message);
|
|
}
|
|
} catch (e) {
|
|
setStatus(String(e));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
// 登录页: 上边框相对此前 0 下移一个行高 (20px), 同时避开状态栏/刘海
|
|
<div style={{ height: "100%", display: "flex", flexDirection: "column", justifyContent: "center", padding: "calc(env(safe-area-inset-top) + 20px) 28px 0", maxWidth: 420, margin: "0 auto" }}>
|
|
<div style={{ textAlign: "center", marginBottom: 40 }}>
|
|
<div style={{ fontSize: 56, marginBottom: 8 }}>👕</div>
|
|
<h1 style={{ fontSize: 32, letterSpacing: -0.5 }}>Wardrobe</h1>
|
|
<p style={{ color: "var(--text-secondary)", fontSize: 14, marginTop: 4 }}>你的 AI 穿搭私人管家</p>
|
|
</div>
|
|
|
|
<div className="card" style={{ padding: 24 }}>
|
|
<div style={{ display: "flex", gap: 8, marginBottom: 20 }}>
|
|
<button
|
|
style={{ flex: 1, padding: "10px 0", borderRadius: "var(--radius-sm)", fontWeight: 600, fontSize: 14, background: isRegister ? "transparent" : "var(--text-primary)", color: isRegister ? "var(--text-secondary)" : "#fff" }}
|
|
onClick={() => setMode("login")}
|
|
>
|
|
登录
|
|
</button>
|
|
<button
|
|
style={{ flex: 1, padding: "10px 0", borderRadius: "var(--radius-sm)", fontWeight: 600, fontSize: 14, background: isRegister ? "var(--text-primary)" : "transparent", color: isRegister ? "#fff" : "var(--text-secondary)" }}
|
|
onClick={() => setMode("register")}
|
|
>
|
|
注册
|
|
</button>
|
|
</div>
|
|
|
|
<div className="input-group">
|
|
<label>{isEmail ? "邮箱" : "手机号"}</label>
|
|
<input className="input" placeholder={isEmail ? "you@example.com" : "13800001234"} value={account} onChange={(e) => setAccount(e.target.value.trim())} />
|
|
</div>
|
|
|
|
{isRegister && (
|
|
<div className="input-group">
|
|
<label>昵称</label>
|
|
<input className="input" placeholder="你的昵称" value={nickname} onChange={(e) => setNickname(e.target.value)} />
|
|
</div>
|
|
)}
|
|
|
|
<div className="input-group">
|
|
<label>验证码</label>
|
|
<div className="code-row">
|
|
<input className="input" placeholder="验证码" maxLength={4} inputMode="numeric" value={code} onChange={(e) => setCode(e.target.value)} />
|
|
<button className="code-btn" disabled={!canSendCode || loading} onClick={handleSendCode}>
|
|
获取验证码
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{status && <p style={{ fontSize: 13, color: status.includes("成功") ? "var(--success)" : "var(--danger)", marginBottom: 12 }}>{status}</p>}
|
|
|
|
<button className="btn btn-primary" disabled={loading || !account || code.length < 4} onClick={handleSubmit}>
|
|
{loading ? "处理中..." : isRegister ? "注册" : "登录"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |