Files
d8d-admin-mobile-starter-pu…/client/mobile/pages_register.tsx
2025-05-08 12:25:51 +00:00

193 lines
7.5 KiB
TypeScript
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, { useState } from 'react';
import { useNavigate } from 'react-router';
import { ArrowRightIcon, EnvelopeIcon, LockClosedIcon, UserIcon } from '@heroicons/react/24/outline';
import { AuthAPI } from './api.ts';
import { handleApiError } from './utils.ts';
const RegisterPage: React.FC = () => {
const navigate = useNavigate();
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleRegister = async (e: React.FormEvent) => {
e.preventDefault();
// 表单验证
if (!username.trim()) {
setError('用户名不能为空');
return;
}
if (!email.trim()) {
setError('邮箱不能为空');
return;
}
if (!password.trim()) {
setError('密码不能为空');
return;
}
if (password !== confirmPassword) {
setError('两次输入的密码不一致');
return;
}
setLoading(true);
setError(null);
try {
await AuthAPI.register(username, email, password);
// 注册成功后跳转到登录页
navigate('/mobile/login');
} catch (err) {
setError(handleApiError(err));
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen flex flex-col bg-gradient-to-b from-blue-500 to-blue-700 p-6">
{/* 顶部Logo和标题 */}
<div className="flex flex-col items-center justify-center mt-10 mb-8">
<div className="w-20 h-20 bg-white rounded-2xl flex items-center justify-center shadow-lg mb-4">
<svg className="w-12 h-12 text-blue-600" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L2 7L12 12L22 7L12 2Z" fill="currentColor" />
<path d="M2 17L12 22L22 17" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M2 12L12 17L22 12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<h1 className="text-3xl font-bold text-white">
{window.CONFIG?.APP_NAME || '移动应用'}
</h1>
<p className="text-blue-100 mt-2"></p>
</div>
{/* 注册表单 */}
<div className="bg-white rounded-xl shadow-xl p-6 w-full">
{error && (
<div className="bg-red-50 text-red-700 p-3 rounded-lg mb-4 text-sm">
{error}
</div>
)}
<form onSubmit={handleRegister}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-medium mb-2" htmlFor="username">
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<UserIcon className="h-5 w-5 text-gray-400" />
</div>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入用户名"
/>
</div>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-medium mb-2" htmlFor="email">
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<EnvelopeIcon className="h-5 w-5 text-gray-400" />
</div>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入邮箱"
/>
</div>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-medium mb-2" htmlFor="password">
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<LockClosedIcon className="h-5 w-5 text-gray-400" />
</div>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入密码"
/>
</div>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-medium mb-2" htmlFor="confirmPassword">
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<LockClosedIcon className="h-5 w-5 text-gray-400" />
</div>
<input
id="confirmPassword"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请再次输入密码"
/>
</div>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 flex items-center justify-center"
>
{loading ? (
<svg className="animate-spin -ml-1 mr-2 h-5 w-5 text-white" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : (
<ArrowRightIcon className="h-5 w-5 mr-2" />
)}
{loading ? '注册中...' : '注册'}
</button>
</form>
<div className="mt-6 text-center">
<button
type="button"
className="text-sm text-blue-600 hover:text-blue-700"
onClick={() => navigate('/mobile/login')}
>
</button>
</div>
</div>
{/* 底部文本 */}
<div className="mt-auto pt-8 text-center text-blue-100 text-sm">
&copy; {new Date().getFullYear()} {window.CONFIG?.APP_NAME || '移动应用'}
<p className="mt-1"></p>
</div>
</div>
);
};
export default RegisterPage;