Files
insar-management-system-v2/frontend/src/LoginPage.jsx
T

150 lines
5.3 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useState } from 'react';
import apiClient from './api/client';
import clusterDeliveryUrl from './assets/login/cluster-delivery-art.webp';
import processingArchitectureUrl from './assets/login/processing-architecture-art.webp';
import systemOverviewUrl from './assets/login/system-overview-art.webp';
const loginSlides = [
{
title: '统一管理雷达数据生产流程',
description: '覆盖源数据入库、精密轨道匹配、生产编排、成果目录和地图分析,形成可审计的工程闭环。',
image: systemOverviewUrl,
},
{
title: '面向 D-InSAR / SBAS 的处理链路',
description: '扫描与解析先行,生产任务进入队列,运行结果和失败归因统一登记,便于复查和补跑。',
image: processingArchitectureUrl,
},
{
title: '支持集群生产与成果交付',
description: '主服务器负责资产、权限和审计,生产节点承担受控处理服务,结果回传后进入交付申请与下载流程。',
image: clusterDeliveryUrl,
},
];
const LoginPage = ({ onLoginSuccess }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState('');
const [activeSlide, setActiveSlide] = useState(0);
useEffect(() => {
const timer = window.setInterval(() => {
setActiveSlide((current) => (current + 1) % loginSlides.length);
}, 5200);
return () => window.clearInterval(timer);
}, []);
const handleSubmit = async (event) => {
event.preventDefault();
setLoading(true);
setMessage('');
try {
const response = await apiClient.post('/auth/login', {
username: username.trim(),
password,
});
if (onLoginSuccess) {
await onLoginSuccess(response.data?.user || null);
}
} catch (error) {
const status = error.response?.status;
if (status === 401) {
setMessage('用户名或密码不正确。');
} else if (status === 429) {
setMessage(error.response?.data?.detail || '登录尝试过于频繁,请稍后再试。');
} else {
setMessage(error.response?.data?.detail || '登录请求失败,请稍后重试。');
}
} finally {
setLoading(false);
}
};
return (
<div className="login-page-wrapper">
<div className="login-shell">
<section className="login-card" aria-label="系统登录">
<div className="login-card-header">
<div className="login-brand-tag">InSAR Production</div>
<h2>雷达数据生产管理系统</h2>
<p className="login-subtitle">内网受控访问请使用管理员分配的账号登录</p>
</div>
<form onSubmit={handleSubmit} className="login-form-grid">
<label className="login-field">
<span>用户名</span>
<input
type="text"
placeholder="请输入用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
autoComplete="username"
required
/>
</label>
<label className="login-field">
<span>密码</span>
<input
type="password"
placeholder="请输入密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
required
/>
</label>
<button type="submit" className="primary-btn login-submit-btn" disabled={loading}>
{loading ? '登录中...' : '登录'}
</button>
</form>
{message && (
<div className="login-alert error">
{message}
</div>
)}
<div className="login-tips">
<strong>提示</strong>
<span>首次部署请确认已在 `.env` 配置管理员账号与密码</span>
</div>
</section>
<section className="login-visual-panel" aria-label="系统能力介绍">
<div className="login-slide-copy">
<span>系统能力</span>
<h3>{loginSlides[activeSlide].title}</h3>
<p>{loginSlides[activeSlide].description}</p>
</div>
<div className="login-slide-frame">
{loginSlides.map((slide, index) => (
<img
key={slide.title}
src={slide.image}
alt={slide.title}
className={`login-slide-image ${index === activeSlide ? 'active' : ''}`}
aria-hidden={index !== activeSlide}
/>
))}
</div>
<div className="login-slide-dots" aria-label="切换系统介绍图">
{loginSlides.map((slide, index) => (
<button
key={slide.title}
type="button"
className={index === activeSlide ? 'active' : ''}
aria-label={`查看${slide.title}`}
onClick={() => setActiveSlide(index)}
/>
))}
</div>
</section>
</div>
</div>
);
};
export default LoginPage;