feat: 调整注册逻辑

This commit is contained in:
zdl
2025-10-14 16:02:33 +08:00
parent cd50d718fe
commit e0ca328e1c
12 changed files with 1570 additions and 1206 deletions

View File

@@ -0,0 +1,330 @@
import React, { useState, useEffect, useRef, useCallback } from "react";
import {
Box,
Button,
VStack,
Text,
Icon,
useToast,
Spinner
} from "@chakra-ui/react";
import { FaQrcode } from "react-icons/fa";
import { useNavigate } from "react-router-dom";
import { authService, WECHAT_STATUS, STATUS_MESSAGES } from "../../services/authService";
// 配置常量
const POLL_INTERVAL = 2000; // 轮询间隔2秒
const QR_CODE_TIMEOUT = 300000; // 二维码超时5分钟
export default function WechatRegister() {
// 状态管理
const [wechatAuthUrl, setWechatAuthUrl] = useState("");
const [wechatSessionId, setWechatSessionId] = useState("");
const [wechatStatus, setWechatStatus] = useState(WECHAT_STATUS.NONE);
const [isLoading, setIsLoading] = useState(false);
// 使用 useRef 管理定时器,避免闭包问题和内存泄漏
const pollIntervalRef = useRef(null);
const timeoutRef = useRef(null);
const isMountedRef = useRef(true); // 追踪组件挂载状态
const navigate = useNavigate();
const toast = useToast();
/**
* 显示统一的错误提示
*/
const showError = useCallback((title, description) => {
toast({
title,
description,
status: "error",
duration: 3000,
isClosable: true,
});
}, [toast]);
/**
* 显示成功提示
*/
const showSuccess = useCallback((title, description) => {
toast({
title,
description,
status: "success",
duration: 2000,
isClosable: true,
});
}, [toast]);
/**
* 清理所有定时器
*/
const clearTimers = useCallback(() => {
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);
pollIntervalRef.current = null;
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
}, []);
/**
* 处理登录成功
*/
const handleLoginSuccess = useCallback(async (sessionId, status) => {
try {
const response = await authService.loginWithWechat(sessionId);
if (response?.success) {
// Session cookie 会自动管理,不需要手动存储
// 如果后端返回了 token可以选择性存储兼容旧方式
if (response.token) {
localStorage.setItem('token', response.token);
}
if (response.user) {
localStorage.setItem('user', JSON.stringify(response.user));
}
showSuccess(
status === WECHAT_STATUS.LOGIN_SUCCESS ? "登录成功" : "注册成功",
"正在跳转..."
);
// 延迟跳转,让用户看到成功提示
setTimeout(() => {
navigate("/home");
}, 1000);
} else {
throw new Error(response?.error || '登录失败');
}
} catch (error) {
console.error('登录失败:', error);
showError("登录失败", error.message || "请重试");
}
}, [navigate, showSuccess, showError]);
/**
* 检查微信扫码状态
*/
const checkWechatStatus = useCallback(async () => {
// 检查组件是否已卸载
if (!isMountedRef.current || !wechatSessionId) return;
try {
const response = await authService.checkWechatStatus(wechatSessionId);
// 安全检查:确保 response 存在且包含 status
if (!response || typeof response.status === 'undefined') {
console.warn('微信状态检查返回无效数据:', response);
return;
}
const { status } = response;
// 组件卸载后不再更新状态
if (!isMountedRef.current) return;
setWechatStatus(status);
// 处理成功状态
if (status === WECHAT_STATUS.LOGIN_SUCCESS || status === WECHAT_STATUS.REGISTER_SUCCESS) {
clearTimers(); // 停止轮询
await handleLoginSuccess(wechatSessionId, status);
}
// 处理过期状态
else if (status === WECHAT_STATUS.EXPIRED) {
clearTimers();
if (isMountedRef.current) {
toast({
title: "授权已过期",
description: "请重新获取授权",
status: "warning",
duration: 3000,
isClosable: true,
});
}
}
} catch (error) {
console.error("检查微信状态失败:", error);
// 轮询过程中的错误不显示给用户,避免频繁提示
// 但如果错误持续发生,停止轮询避免无限重试
if (error.message.includes('网络连接失败')) {
clearTimers();
if (isMountedRef.current) {
toast({
title: "网络连接失败",
description: "请检查网络后重试",
status: "error",
duration: 3000,
isClosable: true,
});
}
}
}
}, [wechatSessionId, handleLoginSuccess, clearTimers, toast]);
/**
* 启动轮询
*/
const startPolling = useCallback(() => {
// 清理旧的定时器
clearTimers();
// 启动轮询
pollIntervalRef.current = setInterval(() => {
checkWechatStatus();
}, POLL_INTERVAL);
// 设置超时
timeoutRef.current = setTimeout(() => {
clearTimers();
setWechatStatus(WECHAT_STATUS.EXPIRED);
}, QR_CODE_TIMEOUT);
}, [checkWechatStatus, clearTimers]);
/**
* 获取微信二维码
*/
const getWechatQRCode = async () => {
try {
setIsLoading(true);
const response = await authService.getWechatQRCode();
// 检查组件是否已卸载
if (!isMountedRef.current) return;
// 安全检查:确保响应包含必要字段
if (!response) {
throw new Error('服务器无响应');
}
if (!response.auth_url || !response.session_id) {
throw new Error('获取二维码失败:响应数据不完整');
}
setWechatAuthUrl(response.auth_url);
setWechatSessionId(response.session_id);
setWechatStatus(WECHAT_STATUS.WAITING);
// 启动轮询检查扫码状态
startPolling();
} catch (error) {
console.error('获取微信授权失败:', error);
if (isMountedRef.current) {
showError("获取微信授权失败", error.message || "请稍后重试");
}
} finally {
if (isMountedRef.current) {
setIsLoading(false);
}
}
};
/**
* 组件卸载时清理定时器和标记组件状态
*/
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
clearTimers();
};
}, [clearTimers]);
/**
* 渲染状态提示文本
*/
const renderStatusText = () => {
if (!wechatAuthUrl || wechatStatus === WECHAT_STATUS.NONE || wechatStatus === WECHAT_STATUS.EXPIRED) {
return null;
}
return (
<Text fontSize="xs" color="gray.500">
{STATUS_MESSAGES[wechatStatus] || STATUS_MESSAGES[WECHAT_STATUS.WAITING]}
</Text>
);
};
return (
<VStack spacing={2}>
<Text fontSize="lg" fontWeight="bold" color="gray.700" whiteSpace="nowrap">
微信扫一扫
</Text>
<Box
position="relative"
minH="120px"
display="flex"
alignItems="center"
justifyContent="center"
>
{/* 灰色二维码底图 - 始终显示 */}
<Icon as={FaQrcode} w={24} h={24} color="gray.300" />
{/* 加载动画 */}
{isLoading && (
<Box
position="absolute"
top="0"
left="0"
right="0"
bottom="0"
display="flex"
alignItems="center"
justifyContent="center"
>
<Spinner
size="lg"
color="green.500"
thickness="4px"
/>
</Box>
)}
{/* 显示获取/刷新二维码按钮 */}
{(wechatStatus === WECHAT_STATUS.NONE || wechatStatus === WECHAT_STATUS.EXPIRED) && (
<Box
position="absolute"
top="0"
left="0"
right="0"
bottom="0"
display="flex"
alignItems="center"
justifyContent="center"
bg="rgba(255, 255, 255, 0.3)"
backdropFilter="blur(2px)"
>
<VStack spacing={2}>
<Button
variant="outline"
colorScheme="green"
size="sm"
onClick={getWechatQRCode}
isLoading={isLoading}
leftIcon={<Icon as={FaQrcode} />}
_hover={{ bg: "green.50" }}
>
{wechatStatus === WECHAT_STATUS.EXPIRED ? "点击刷新" : "获取二维码"}
</Button>
{wechatStatus === WECHAT_STATUS.EXPIRED && (
<Text fontSize="xs" color="gray.500">
二维码已过期
</Text>
)}
</VStack>
</Box>
)}
</Box>
{/* 扫码状态提示 */}
{renderStatusText()}
</VStack>
);
}