ProtectedRoute.tsx 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. import React, { useEffect, useState } from 'react';
  2. import { useNavigate } from 'react-router-dom';
  3. import { authService } from '../services/authService';
  4. import { Loader2 } from '../icons/commonIcons';
  5. interface ProtectedRouteProps {
  6. children: React.ReactNode;
  7. }
  8. /**
  9. * 路由保护组件
  10. * 确保只有已登录用户才能访问受保护的页面
  11. */
  12. const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
  13. const navigate = useNavigate();
  14. const [isChecking, setIsChecking] = useState(true);
  15. const [isAuthenticated, setIsAuthenticated] = useState(false);
  16. useEffect(() => {
  17. const checkAuth = () => {
  18. const authenticated = authService.isAuthenticated();
  19. setIsAuthenticated(authenticated);
  20. if (!authenticated) {
  21. // 未登录,跳转到登录页
  22. navigate('/login', { replace: true });
  23. }
  24. setIsChecking(false);
  25. };
  26. // 初始检查
  27. checkAuth();
  28. // 订阅认证状态变化
  29. const unsubscribe = authService.subscribe(() => {
  30. const authenticated = authService.isAuthenticated();
  31. setIsAuthenticated(authenticated);
  32. if (!authenticated) {
  33. navigate('/login', { replace: true });
  34. }
  35. });
  36. return () => {
  37. unsubscribe();
  38. };
  39. }, [navigate]);
  40. if (isChecking) {
  41. return (
  42. <div className="min-h-screen bg-gray-50 flex items-center justify-center">
  43. <div className="flex items-center gap-3">
  44. <Loader2 className="w-6 h-6 text-blue-600 animate-spin" />
  45. <span className="text-gray-600">验证登录状态...</span>
  46. </div>
  47. </div>
  48. );
  49. }
  50. if (!isAuthenticated) {
  51. return null;
  52. }
  53. return <>{children}</>;
  54. };
  55. export default ProtectedRoute;