|
|
@@ -0,0 +1,331 @@
|
|
|
+import { useState, useEffect } from 'react';
|
|
|
+import { useSearchParams, useNavigate } from 'react-router-dom';
|
|
|
+import { useNotify } from 'react-admin';
|
|
|
+import {
|
|
|
+ Box,
|
|
|
+ Button,
|
|
|
+ TextField,
|
|
|
+ Typography,
|
|
|
+ Container,
|
|
|
+ Paper,
|
|
|
+ Link,
|
|
|
+ CircularProgress,
|
|
|
+ Alert,
|
|
|
+} from '@mui/material';
|
|
|
+import { ArrowBack, LockReset } from '@mui/icons-material';
|
|
|
+
|
|
|
+// 密码重置API调用函数
|
|
|
+const validatePasswordResetToken = async (token: string) => {
|
|
|
+ const response = await fetch('/system/password/validate', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify({ token }),
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error('验证失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ return response.json();
|
|
|
+};
|
|
|
+
|
|
|
+const resetPassword = async (token: string, newPassword: string, confirmPassword: string) => {
|
|
|
+ const response = await fetch('/system/password/reset', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify({ token, newPassword, confirmPassword }),
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error('重置失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ return response.json();
|
|
|
+};
|
|
|
+
|
|
|
+export const ResetPasswordPage = () => {
|
|
|
+ const [searchParams] = useSearchParams();
|
|
|
+ const navigate = useNavigate();
|
|
|
+ const [token, setToken] = useState('');
|
|
|
+ const [newPassword, setNewPassword] = useState('');
|
|
|
+ const [confirmPassword, setConfirmPassword] = useState('');
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
+ const [validating, setValidating] = useState(true);
|
|
|
+ const [tokenValid, setTokenValid] = useState(false);
|
|
|
+ const [tokenError, setTokenError] = useState('');
|
|
|
+ const [submitted, setSubmitted] = useState(false);
|
|
|
+ const notify = useNotify();
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ const tokenFromUrl = searchParams.get('token');
|
|
|
+ if (tokenFromUrl) {
|
|
|
+ setToken(tokenFromUrl);
|
|
|
+ validateToken(tokenFromUrl);
|
|
|
+ } else {
|
|
|
+ setValidating(false);
|
|
|
+ setTokenValid(false);
|
|
|
+ setTokenError('无效的重置链接');
|
|
|
+ }
|
|
|
+ }, [searchParams]);
|
|
|
+
|
|
|
+ const validateToken = async (tokenToValidate: string) => {
|
|
|
+ try {
|
|
|
+ const result = await validatePasswordResetToken(tokenToValidate);
|
|
|
+ if (result.code === 200) {
|
|
|
+ setTokenValid(true);
|
|
|
+ setTokenError('');
|
|
|
+ } else {
|
|
|
+ setTokenValid(false);
|
|
|
+ setTokenError(result.msg || '令牌验证失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ setTokenValid(false);
|
|
|
+ setTokenError(error instanceof Error ? error.message : '验证失败');
|
|
|
+ } finally {
|
|
|
+ setValidating(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSubmit = async (e: React.FormEvent) => {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ if (!newPassword || !confirmPassword) {
|
|
|
+ notify('请输入新密码和确认密码', { type: 'warning' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newPassword !== confirmPassword) {
|
|
|
+ notify('两次输入的密码不一致', { type: 'warning' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newPassword.length < 6) {
|
|
|
+ notify('密码长度不能少于6位', { type: 'warning' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setLoading(true);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const result = await resetPassword(token, newPassword, confirmPassword);
|
|
|
+ if (result.code === 200) {
|
|
|
+ setSubmitted(true);
|
|
|
+ notify(result.msg || '密码重置成功', { type: 'success' });
|
|
|
+ // 3秒后自动跳转到登录页面
|
|
|
+ setTimeout(() => {
|
|
|
+ navigate('/login');
|
|
|
+ }, 3000);
|
|
|
+ } else {
|
|
|
+ notify(result.msg || '密码重置失败', { type: 'error' });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ notify(error instanceof Error ? error.message : '重置失败,请稍后重试', { type: 'error' });
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleBackToLogin = () => {
|
|
|
+ navigate('/login');
|
|
|
+ };
|
|
|
+
|
|
|
+ if (validating) {
|
|
|
+ return (
|
|
|
+ <Container component="main" maxWidth="sm">
|
|
|
+ <Box
|
|
|
+ sx={{
|
|
|
+ marginTop: 8,
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ alignItems: 'center',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Paper
|
|
|
+ elevation={3}
|
|
|
+ sx={{
|
|
|
+ padding: 4,
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ alignItems: 'center',
|
|
|
+ width: '100%',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <CircularProgress sx={{ mb: 2 }} />
|
|
|
+ <Typography variant="h6">验证重置链接...</Typography>
|
|
|
+ </Paper>
|
|
|
+ </Box>
|
|
|
+ </Container>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!tokenValid) {
|
|
|
+ return (
|
|
|
+ <Container component="main" maxWidth="sm">
|
|
|
+ <Box
|
|
|
+ sx={{
|
|
|
+ marginTop: 8,
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ alignItems: 'center',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Paper
|
|
|
+ elevation={3}
|
|
|
+ sx={{
|
|
|
+ padding: 4,
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ alignItems: 'center',
|
|
|
+ width: '100%',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Alert severity="error" sx={{ width: '100%', mb: 2 }}>
|
|
|
+ {tokenError || '重置链接无效或已过期'}
|
|
|
+ </Alert>
|
|
|
+ <Typography variant="body2" color="text.secondary" sx={{ mb: 3, textAlign: 'center' }}>
|
|
|
+ 请重新请求密码重置或联系系统管理员
|
|
|
+ </Typography>
|
|
|
+
|
|
|
+ <Button
|
|
|
+ fullWidth
|
|
|
+ variant="outlined"
|
|
|
+ onClick={handleBackToLogin}
|
|
|
+ startIcon={<ArrowBack />}
|
|
|
+ >
|
|
|
+ 返回登录页面
|
|
|
+ </Button>
|
|
|
+ </Paper>
|
|
|
+ </Box>
|
|
|
+ </Container>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (submitted) {
|
|
|
+ return (
|
|
|
+ <Container component="main" maxWidth="sm">
|
|
|
+ <Box
|
|
|
+ sx={{
|
|
|
+ marginTop: 8,
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ alignItems: 'center',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Paper
|
|
|
+ elevation={3}
|
|
|
+ sx={{
|
|
|
+ padding: 4,
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ alignItems: 'center',
|
|
|
+ width: '100%',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Alert severity="success" sx={{ width: '100%', mb: 2 }}>
|
|
|
+ ✓ 密码重置成功
|
|
|
+ </Alert>
|
|
|
+ <Typography variant="body2" color="text.secondary" sx={{ mb: 3, textAlign: 'center' }}>
|
|
|
+ 密码已成功重置,3秒后将自动跳转到登录页面
|
|
|
+ </Typography>
|
|
|
+
|
|
|
+ <Button
|
|
|
+ fullWidth
|
|
|
+ variant="outlined"
|
|
|
+ onClick={handleBackToLogin}
|
|
|
+ startIcon={<ArrowBack />}
|
|
|
+ >
|
|
|
+ 立即登录
|
|
|
+ </Button>
|
|
|
+ </Paper>
|
|
|
+ </Box>
|
|
|
+ </Container>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Container component="main" maxWidth="sm">
|
|
|
+ <Box
|
|
|
+ sx={{
|
|
|
+ marginTop: 8,
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ alignItems: 'center',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Paper
|
|
|
+ elevation={3}
|
|
|
+ sx={{
|
|
|
+ padding: 4,
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ alignItems: 'center',
|
|
|
+ width: '100%',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <LockReset sx={{ fontSize: 40, mb: 2, color: 'primary.main' }} />
|
|
|
+ <Typography component="h1" variant="h4" gutterBottom>
|
|
|
+ 重置密码
|
|
|
+ </Typography>
|
|
|
+
|
|
|
+ <Typography variant="body2" color="text.secondary" sx={{ mb: 3, textAlign: 'center' }}>
|
|
|
+ 请输入您的新密码
|
|
|
+ </Typography>
|
|
|
+
|
|
|
+ <Box component="form" onSubmit={handleSubmit} sx={{ mt: 1, width: '100%' }}>
|
|
|
+ <TextField
|
|
|
+ margin="normal"
|
|
|
+ required
|
|
|
+ fullWidth
|
|
|
+ name="newPassword"
|
|
|
+ label="新密码"
|
|
|
+ type="password"
|
|
|
+ id="newPassword"
|
|
|
+ value={newPassword}
|
|
|
+ onChange={(e) => setNewPassword(e.target.value)}
|
|
|
+ helperText="密码长度不能少于6位"
|
|
|
+ />
|
|
|
+ <TextField
|
|
|
+ margin="normal"
|
|
|
+ required
|
|
|
+ fullWidth
|
|
|
+ name="confirmPassword"
|
|
|
+ label="确认密码"
|
|
|
+ type="password"
|
|
|
+ id="confirmPassword"
|
|
|
+ value={confirmPassword}
|
|
|
+ onChange={(e) => setConfirmPassword(e.target.value)}
|
|
|
+ helperText="请再次输入新密码"
|
|
|
+ />
|
|
|
+
|
|
|
+ <Button
|
|
|
+ type="submit"
|
|
|
+ fullWidth
|
|
|
+ variant="contained"
|
|
|
+ sx={{ mt: 3, mb: 2 }}
|
|
|
+ disabled={loading}
|
|
|
+ startIcon={loading ? <CircularProgress size={20} /> : <LockReset />}
|
|
|
+ >
|
|
|
+ {loading ? '重置中...' : '重置密码'}
|
|
|
+ </Button>
|
|
|
+
|
|
|
+ <Box sx={{ mt: 2, textAlign: 'center' }}>
|
|
|
+ <Link
|
|
|
+ component="button"
|
|
|
+ variant="body2"
|
|
|
+ onClick={handleBackToLogin}
|
|
|
+ sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
|
|
+ >
|
|
|
+ <ArrowBack sx={{ fontSize: 16, mr: 0.5 }} />
|
|
|
+ 返回登录
|
|
|
+ </Link>
|
|
|
+ </Box>
|
|
|
+ </Box>
|
|
|
+ </Paper>
|
|
|
+ </Box>
|
|
|
+ </Container>
|
|
|
+ );
|
|
|
+};
|