216 lines
8.1 KiB
TypeScript
216 lines
8.1 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { useNavigate, useLocation } from 'react-router-dom';
|
|
import { User, Lock, LogIn, AlertCircle, Loader2 } from 'lucide-react';
|
|
import StarBackground from '@/shared/components/StarBackground';
|
|
import { iamLogin, saveSession } from '@/shared/services/iamAuth';
|
|
import { useTheme } from '@/shared/context/ThemeContext';
|
|
|
|
const inputStyle = (isDark: boolean): React.CSSProperties => ({
|
|
width: '100%',
|
|
boxSizing: 'border-box',
|
|
padding: '12px 14px 12px 42px',
|
|
borderRadius: 10,
|
|
border: `1px solid ${isDark ? 'rgba(0,212,255,0.25)' : 'rgba(0,0,0,0.12)'}`,
|
|
background: isDark ? 'rgba(0,212,255,0.06)' : '#fff',
|
|
color: isDark ? '#e8f4ff' : '#1e293b',
|
|
fontSize: 14,
|
|
outline: 'none',
|
|
transition: 'border-color 0.2s, box-shadow 0.2s',
|
|
});
|
|
|
|
const iconStyle = (isDark: boolean): React.CSSProperties => ({
|
|
position: 'absolute',
|
|
left: 14,
|
|
top: '50%',
|
|
transform: 'translateY(-50%)',
|
|
color: isDark ? 'rgba(0,212,255,0.6)' : '#94a3b8',
|
|
});
|
|
|
|
export default function LoginPage() {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const { theme } = useTheme();
|
|
const isDark = theme === 'dark';
|
|
|
|
const [userId, setUserId] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState('');
|
|
|
|
useEffect(() => {
|
|
document.body.style.overflow = 'auto';
|
|
return () => { document.body.style.overflow = ''; };
|
|
}, []);
|
|
|
|
const handleLogin = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!userId.trim() || !password) {
|
|
setError('请输入账号和密码');
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
setError('');
|
|
try {
|
|
const result = await iamLogin({ userId: userId.trim(), password });
|
|
saveSession(result.token, result.userInfo);
|
|
// 回跳原页面(RequireAuth 记录的 from),否则去首页
|
|
const from = (location.state as { from?: string })?.from ?? '/';
|
|
navigate(from, { replace: true });
|
|
} catch (ex) {
|
|
setError(ex instanceof Error ? ex.message : '登录失败,请稍后重试');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div style={{
|
|
width: '100vw',
|
|
minHeight: '100vh',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
position: 'relative',
|
|
boxSizing: 'border-box',
|
|
fontFamily: "-apple-system, 'SF Pro Display', 'SF Pro Text', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif",
|
|
color: isDark ? '#a8d8ff' : '#1e3a6e',
|
|
}}>
|
|
{isDark ? <StarBackground theme="dark" /> : (
|
|
<div style={{
|
|
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
|
|
background: 'linear-gradient(160deg, #f8f8fb 0%, #f2f3f7 50%, #eef1f7 100%)',
|
|
zIndex: 0,
|
|
}} />
|
|
)}
|
|
|
|
<div style={{
|
|
position: 'relative', zIndex: 1,
|
|
width: 400, maxWidth: '92vw',
|
|
padding: '40px 36px 32px',
|
|
borderRadius: 20,
|
|
background: isDark ? 'rgba(10,22,40,0.75)' : 'rgba(255,255,255,0.9)',
|
|
backdropFilter: 'blur(16px)',
|
|
border: `1px solid ${isDark ? 'rgba(0,212,255,0.2)' : 'rgba(0,0,0,0.06)'}`,
|
|
boxShadow: isDark ? '0 24px 64px rgba(0,0,0,0.5), 0 0 48px rgba(0,212,255,0.08)' : '0 24px 64px rgba(30,58,110,0.12)',
|
|
}}>
|
|
{/* 品牌区 */}
|
|
<div style={{ textAlign: 'center', marginBottom: 32 }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 12 }}>
|
|
<img
|
|
src="/digiwin.svg"
|
|
alt="Digiwin"
|
|
style={{ height: 36, objectFit: 'contain', filter: isDark ? 'drop-shadow(0 0 8px rgba(0,212,255,0.4))' : 'none' }}
|
|
/>
|
|
<span style={{
|
|
fontSize: 26, fontWeight: 800, lineHeight: 1,
|
|
...(isDark ? { color: 'rgba(0,212,255,0.5)' } : { color: 'rgba(30,58,110,0.3)' }),
|
|
}}>·</span>
|
|
<h1 style={{
|
|
fontSize: 26, fontWeight: 800, letterSpacing: '0.04em', margin: 0,
|
|
...(isDark
|
|
? { color: '#e8f4ff', textShadow: '0 0 16px rgba(0,212,255,0.5)' }
|
|
: { color: '#111827' }),
|
|
}}>
|
|
EIOSpace
|
|
</h1>
|
|
</div>
|
|
<p style={{ fontSize: 13, marginTop: 14, ...(isDark ? { color: 'rgba(168,216,255,0.6)' } : { color: '#6b7280' }) }}>
|
|
企业智能 · 数据驱动 · 产业赋能
|
|
</p>
|
|
</div>
|
|
|
|
{/* 登录表单 */}
|
|
<form onSubmit={handleLogin}>
|
|
<div style={{ marginBottom: 16 }}>
|
|
<label style={{ display: 'block', fontSize: 13, marginBottom: 6, color: isDark ? 'rgba(168,216,255,0.8)' : '#475569' }}>
|
|
账号(用户ID / 手机号 / 邮箱)
|
|
</label>
|
|
<div style={{ position: 'relative' }}>
|
|
<User size={16} style={iconStyle(isDark)} />
|
|
<input
|
|
type="text"
|
|
value={userId}
|
|
onChange={(e) => setUserId(e.target.value)}
|
|
placeholder="请输入账号"
|
|
autoComplete="username"
|
|
style={inputStyle(isDark)}
|
|
onFocus={(e) => { e.currentTarget.style.borderColor = '#00d4ff'; }}
|
|
onBlur={(e) => { e.currentTarget.style.borderColor = isDark ? 'rgba(0,212,255,0.25)' : 'rgba(0,0,0,0.12)'; }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ marginBottom: 20 }}>
|
|
<label style={{ display: 'block', fontSize: 13, marginBottom: 6, color: isDark ? 'rgba(168,216,255,0.8)' : '#475569' }}>
|
|
密码
|
|
</label>
|
|
<div style={{ position: 'relative' }}>
|
|
<Lock size={16} style={iconStyle(isDark)} />
|
|
<input
|
|
type="password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
placeholder="请输入密码"
|
|
autoComplete="current-password"
|
|
style={inputStyle(isDark)}
|
|
onFocus={(e) => { e.currentTarget.style.borderColor = '#00d4ff'; }}
|
|
onBlur={(e) => { e.currentTarget.style.borderColor = isDark ? 'rgba(0,212,255,0.25)' : 'rgba(0,0,0,0.12)'; }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{error && (
|
|
<div style={{
|
|
display: 'flex', alignItems: 'center', gap: 8,
|
|
padding: '10px 12px', marginBottom: 16,
|
|
borderRadius: 8, fontSize: 13,
|
|
background: 'rgba(239,68,68,0.12)',
|
|
border: '1px solid rgba(239,68,68,0.4)',
|
|
color: '#fca5a5',
|
|
}}>
|
|
<AlertCircle size={15} style={{ flexShrink: 0 }} />
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
style={{
|
|
width: '100%',
|
|
padding: '13px 0',
|
|
borderRadius: 10,
|
|
border: 'none',
|
|
cursor: loading ? 'wait' : 'pointer',
|
|
fontSize: 15,
|
|
fontWeight: 600,
|
|
letterSpacing: '0.08em',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
gap: 8,
|
|
color: '#04121f',
|
|
background: 'linear-gradient(90deg, #00d4ff, #4f9eff)',
|
|
boxShadow: '0 8px 24px rgba(0,212,255,0.35)',
|
|
transition: 'transform 0.15s, box-shadow 0.15s, opacity 0.15s',
|
|
opacity: loading ? 0.7 : 1,
|
|
}}
|
|
onMouseEnter={(e) => { if (!loading) e.currentTarget.style.transform = 'translateY(-1px)'; }}
|
|
onMouseLeave={(e) => { e.currentTarget.style.transform = 'none'; }}
|
|
>
|
|
{loading ? <Loader2 size={17} style={{ animation: 'spin 1s linear infinite' }} /> : <LogIn size={17} />}
|
|
{loading ? '登录中...' : '登 录'}
|
|
</button>
|
|
</form>
|
|
|
|
<p style={{
|
|
textAlign: 'center', fontSize: 12, marginTop: 24,
|
|
...(isDark ? { color: 'rgba(168,216,255,0.45)' } : { color: '#94a3b8' }),
|
|
}}>
|
|
鼎捷云统一身份认证 · IAM
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|