// frontend/js/auth-react.jsx // 全局共享的 React 鉴权与档案同步模块 // NOTE: 不在顶层解构 React hooks,避免与页面脚本冲突 /** * 核心 Auth Hook * 用于检查登录状态、获取档案数据、处理登录注册登出 */ function useAuth() { const { useState, useEffect } = React; const [user, setUser] = useState(null); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const fetchProfile = async (token) => { try { const res = await fetch('/api/user/profile', { headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const data = await res.json(); setProfile(data.data); localStorage.setItem('mystic_profile', JSON.stringify(data.data)); } } catch (e) { console.error("Profile fetch failed:", e); } }; const checkLogin = async () => { setLoading(true); const token = localStorage.getItem('mystic_token'); if (!token) { setUser(null); setProfile(null); setLoading(false); return; } try { const res = await fetch('/api/auth/me', { headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const userData = await res.json(); setUser(userData); await fetchProfile(token); } else { localStorage.removeItem('mystic_token'); setUser(null); } } catch (e) { console.error(e); } finally { setLoading(false); } }; useEffect(() => { // 先尝试从本地读取 const cachedProfile = localStorage.getItem('mystic_profile'); if (cachedProfile) { try { setProfile(JSON.parse(cachedProfile)); } catch (e) {} } checkLogin(); }, []); const logout = async () => { const token = localStorage.getItem('mystic_token'); if (token) { fetch('/api/auth/logout', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); } localStorage.removeItem('mystic_token'); localStorage.removeItem('mystic_profile'); setUser(null); setProfile(null); }; const silentUpdateProfile = async (updateData) => { const token = localStorage.getItem('mystic_token'); if (!token || !updateData) return; try { const res = await fetch('/api/user/profile', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(updateData) }); if (res.ok) { await fetchProfile(token); } } catch (e) { console.error("Silent sync failed", e); } }; return { user, profile, loading, checkLogin, logout, silentUpdateProfile }; } /** * 通用精美登录/注册弹窗组件 */ function LoginModal({ isOpen, onClose, onSuccess }) { const { useState } = React; const [mode, setMode] = useState('login'); // 'login', 'register', or 'profile_setup' const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [nickname, setNickname] = useState(''); // 完善档案表单状态 const [profileForm, setProfileForm] = useState({ nickname: '', real_name: '', gender: '女', birth_date: '', birth_time: '12:00', city_name: '北京', mbti: '' }); const [error, setError] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); if (!isOpen) return null; const handleSubmit = async (e) => { e.preventDefault(); setError(''); if (username.length < 2) return setError("用户名太短"); if (password.length < 6) return setError("密码至少6位"); setIsSubmitting(true); try { const endpoint = mode === 'login' ? '/api/auth/login' : '/api/auth/register'; const res = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, nickname }) }); const data = await res.json(); if (!res.ok) { throw new Error(data.detail || data.message || '请求失败'); } if (mode === 'register') { // 注册成功,自动登录 const loginRes = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const loginData = await loginRes.json(); if (loginRes.ok) { localStorage.setItem('mystic_token', loginData.token); setProfileForm({...profileForm, nickname: nickname || ''}); setMode('profile_setup'); } else { setMode('login'); setError('注册成功,请重新登录'); } } else if (mode === 'login') { localStorage.setItem('mystic_token', data.token); // 检查档案是否已填写 const profileRes = await fetch('/api/user/profile', { headers: { 'Authorization': `Bearer ${data.token}` } }); let needsSetup = false; if (profileRes.ok) { const profileData = await profileRes.json(); if (!profileData.data.birth_date) { needsSetup = true; setProfileForm({...profileForm, nickname: profileData.data.nickname || ''}); } } if (needsSetup) { setMode('profile_setup'); } else { if (onSuccess) await onSuccess(); onClose(); } } } catch (err) { setError(err.message); } finally { setIsSubmitting(false); } }; const handleProfileSubmit = async (e) => { e.preventDefault(); setError(''); if (!profileForm.birth_date) return setError("请选择出生日期"); setIsSubmitting(true); try { const token = localStorage.getItem('mystic_token'); const res = await fetch('/api/user/profile', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(profileForm) }); if (!res.ok) { throw new Error('档案保存失败'); } if (onSuccess) await onSuccess(); onClose(); } catch (err) { setError(err.message); } finally { setIsSubmitting(false); } }; const handleSkip = async () => { if (onSuccess) await onSuccess(); onClose(); }; return (
{/* Close Button */} {mode === 'profile_setup' ? (

完善个人档案

一次填写,全站所有测算通用

setProfileForm({...profileForm, nickname: e.target.value})} placeholder="您的称呼 (昵称)" required className="w-full bg-slate-50 px-5 py-3 rounded-xl border-none text-sm font-medium focus:ring-2 focus:ring-rose-200 focus:bg-white outline-none transition-all placeholder-slate-400" />
setProfileForm({...profileForm, real_name: e.target.value})} placeholder="真实姓名 (可选)" className="w-full bg-slate-50 px-5 py-3 rounded-xl border-none text-sm font-medium focus:ring-2 focus:ring-rose-200 focus:bg-white outline-none transition-all placeholder-slate-400" />

💡 真实姓名会算的准

setProfileForm({...profileForm, birth_date: e.target.value})} required className="w-full bg-slate-50 px-4 py-3 rounded-xl border-none text-sm font-medium focus:ring-2 focus:ring-rose-200 focus:bg-white outline-none transition-all text-slate-700" />
setProfileForm({...profileForm, birth_time: e.target.value})} className="w-full bg-slate-50 px-4 py-3 rounded-xl border-none text-sm font-medium focus:ring-2 focus:ring-rose-200 focus:bg-white outline-none transition-all text-slate-700" />
setProfileForm({...profileForm, city_name: e.target.value})} placeholder="如: 北京" className="w-full bg-slate-50 px-5 py-3 rounded-xl border-none text-sm font-medium focus:ring-2 focus:ring-rose-200 focus:bg-white outline-none transition-all placeholder-slate-400" />
{error &&

{error}

}
) : ( <>

MYSTIC

连接您的命运星轨

{mode === 'register' && (
setNickname(e.target.value)} placeholder="您的称呼 (选填)" className="w-full bg-slate-50 px-5 py-3.5 rounded-2xl border-none text-sm font-medium focus:ring-2 focus:ring-rose-200 focus:bg-white outline-none transition-all placeholder-slate-400" />
)}
setUsername(e.target.value)} placeholder="账号 (用于登录)" required className="w-full bg-slate-50 px-5 py-3.5 rounded-2xl border-none text-sm font-medium focus:ring-2 focus:ring-rose-200 focus:bg-white outline-none transition-all placeholder-slate-400" />
setPassword(e.target.value)} placeholder="密码 (不少于6位)" required className="w-full bg-slate-50 px-5 py-3.5 rounded-2xl border-none text-sm font-medium focus:ring-2 focus:ring-rose-200 focus:bg-white outline-none transition-all placeholder-slate-400" />
{error && (

{error}

)}
)}