- 移动42个文档文件到 docs/ 目录 - 更新 .gitignore 允许 docs/ 下的 .md 文件 - 删除根目录下的重复文档文件 📁 文档分类: - StockDetailPanel 重构文档(3个) - PostHog 集成文档(6个) - 系统架构和API文档(33个) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
29 KiB
登录跳转改造为弹窗方案
改造日期: 2025-10-14 改造范围: 全项目登录/注册交互流程 改造目标: 将所有页面跳转式登录改为弹窗式登录,提升用户体验
📋 目录
1. 改造目标
1.1 用户体验提升
改造前:
用户访问需登录页面 → 页面跳转到 /auth/signin → 登录成功 → 跳转回原页面
改造后:
用户访问需登录页面 → 弹出登录弹窗 → 登录成功 → 弹窗关闭,继续访问原页面
1.2 优势
✅ 减少页面跳转:无需离开当前页面,保持上下文 ✅ 流畅体验:弹窗式交互更现代、更友好 ✅ 保留页面状态:当前页面的表单数据、滚动位置等不会丢失 ✅ 支持快速切换:在弹窗内切换登录/注册,无页面刷新 ✅ 更好的 SEO:减少不必要的 URL 跳转
2. 影响范围分析
2.1 需要登录/注册的场景统计
| 场景类别 | 触发位置 | 当前实现 | 影响文件 | 优先级 |
|---|---|---|---|---|
| 导航栏登录按钮 | HomeNavbar、AdminNavbarLinks | navigate('/auth/signin') |
2个文件 | 🔴 高 |
| 导航栏注册按钮 | HomeNavbar("登录/注册"按钮) | 集成在登录按钮中 | 1个文件 | 🔴 高 |
| 用户登出 | AuthContext.logout() | navigate('/auth/signin') |
1个文件 | 🔴 高 |
| 受保护路由拦截 | ProtectedRoute组件 | <Navigate to="/auth/signin" /> |
1个文件 | 🔴 高 |
| 登录/注册页面切换 | SignInIllustration、SignUpIllustration | linkTo="/auth/sign-up" |
2个文件 | 🟡 中 |
| 其他认证页面 | SignInBasic、SignUpCentered等 | navigate() |
4个文件 | 🟢 低 |
2.2 详细文件列表
🔴 核心文件(必须修改)
-
src/contexts/AuthContext.js(459行, 466行)logout()函数中的navigate('/auth/signin')- 影响:所有登出操作
-
src/components/ProtectedRoute.js(30行, 34行)<Navigate to={redirectUrl} replace />- 影响:所有受保护路由的未登录拦截
-
src/components/Navbars/HomeNavbar.js(236行, 518-530行)handleLoginClick()函数- "登录/注册"按钮(需拆分为登录和注册两个选项)
- 影响:首页顶部导航栏登录/注册按钮
-
src/components/Navbars/AdminNavbarLinks.js(86行, 147行)navigate("/auth/signin")- 影响:管理后台导航栏登录按钮
🟡 次要文件(建议修改)
-
src/views/Authentication/SignIn/SignInIllustration.js(464行)- AuthFooter组件的
linkTo="/auth/sign-up" - 影响:登录页面内的"去注册"链接
- AuthFooter组件的
-
src/views/Authentication/SignUp/SignUpIllustration.js(373行)- AuthFooter组件的
linkTo="/auth/sign-in" - 影响:注册页面内的"去登录"链接
- AuthFooter组件的
🟢 可选文件(保持兼容)
7-10. 其他认证页面变体:
src/views/Authentication/SignIn/SignInCentered.jssrc/views/Authentication/SignIn/SignInBasic.jssrc/views/Authentication/SignUp/SignUpBasic.jssrc/views/Authentication/SignUp/SignUpCentered.js
这些是模板中的备用页面,可以保持现有实现,不影响核心功能。
3. 技术方案设计
3.1 架构设计
┌─────────────────────────────────────────────┐
│ AuthModalContext │
│ - isLoginModalOpen │
│ - isSignUpModalOpen │
│ - openLoginModal(redirectUrl?) │
│ - openSignUpModal() │
│ - closeModal() │
│ - onLoginSuccess(callback?) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ AuthModalManager 组件 │
│ - 渲染登录/注册弹窗 │
│ - 管理弹窗状态 │
│ - 处理登录成功回调 │
└─────────────────────────────────────────────┘
↓
┌──────────────────┬─────────────────────────┐
│ LoginModal │ SignUpModal │
│ - 复用现有UI │ - 复用现有UI │
│ - Chakra Modal │ - Chakra Modal │
└──────────────────┴─────────────────────────┘
3.2 核心组件设计
3.2.1 AuthModalContext
// src/contexts/AuthModalContext.js
import { createContext, useContext, useState, useCallback } from 'react';
const AuthModalContext = createContext();
export const useAuthModal = () => {
const context = useContext(AuthModalContext);
if (!context) {
throw new Error('useAuthModal must be used within AuthModalProvider');
}
return context;
};
export const AuthModalProvider = ({ children }) => {
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false);
const [isSignUpModalOpen, setIsSignUpModalOpen] = useState(false);
const [redirectUrl, setRedirectUrl] = useState(null);
const [onSuccessCallback, setOnSuccessCallback] = useState(null);
// 打开登录弹窗
const openLoginModal = useCallback((url = null, callback = null) => {
setRedirectUrl(url);
setOnSuccessCallback(() => callback);
setIsLoginModalOpen(true);
setIsSignUpModalOpen(false);
}, []);
// 打开注册弹窗
const openSignUpModal = useCallback((callback = null) => {
setOnSuccessCallback(() => callback);
setIsSignUpModalOpen(true);
setIsLoginModalOpen(false);
}, []);
// 切换到注册弹窗
const switchToSignUp = useCallback(() => {
setIsLoginModalOpen(false);
setIsSignUpModalOpen(true);
}, []);
// 切换到登录弹窗
const switchToLogin = useCallback(() => {
setIsSignUpModalOpen(false);
setIsLoginModalOpen(true);
}, []);
// 关闭弹窗
const closeModal = useCallback(() => {
setIsLoginModalOpen(false);
setIsSignUpModalOpen(false);
setRedirectUrl(null);
setOnSuccessCallback(null);
}, []);
// 登录成功处理
const handleLoginSuccess = useCallback((user) => {
if (onSuccessCallback) {
onSuccessCallback(user);
}
// 如果有重定向URL,则跳转
if (redirectUrl) {
window.location.href = redirectUrl;
}
closeModal();
}, [onSuccessCallback, redirectUrl, closeModal]);
const value = {
isLoginModalOpen,
isSignUpModalOpen,
openLoginModal,
openSignUpModal,
switchToSignUp,
switchToLogin,
closeModal,
handleLoginSuccess,
redirectUrl
};
return (
<AuthModalContext.Provider value={value}>
{children}
</AuthModalContext.Provider>
);
};
3.2.2 AuthModalManager 组件
// src/components/Auth/AuthModalManager.js
import React from 'react';
import {
Modal,
ModalOverlay,
ModalContent,
ModalBody,
ModalCloseButton,
useBreakpointValue
} from '@chakra-ui/react';
import { useAuthModal } from '../../contexts/AuthModalContext';
import LoginModalContent from './LoginModalContent';
import SignUpModalContent from './SignUpModalContent';
export default function AuthModalManager() {
const {
isLoginModalOpen,
isSignUpModalOpen,
closeModal
} = useAuthModal();
const modalSize = useBreakpointValue({
base: "full",
sm: "xl",
md: "2xl",
lg: "4xl"
});
const isOpen = isLoginModalOpen || isSignUpModalOpen;
return (
<Modal
isOpen={isOpen}
onClose={closeModal}
size={modalSize}
isCentered
closeOnOverlayClick={false}
>
<ModalOverlay bg="blackAlpha.700" backdropFilter="blur(10px)" />
<ModalContent
bg="transparent"
boxShadow="none"
maxW={modalSize === "full" ? "100%" : "900px"}
>
<ModalCloseButton
position="absolute"
right={4}
top={4}
zIndex={10}
color="white"
bg="blackAlpha.500"
_hover={{ bg: "blackAlpha.700" }}
/>
<ModalBody p={0}>
{isLoginModalOpen && <LoginModalContent />}
{isSignUpModalOpen && <SignUpModalContent />}
</ModalBody>
</ModalContent>
</Modal>
);
}
3.2.3 LoginModalContent 组件
// src/components/Auth/LoginModalContent.js
// 复用 SignInIllustration.js 的核心UI逻辑
// 移除页面级的 Flex minH="100vh",改为 Box
// 移除 navigate 跳转,改为调用 useAuthModal 的方法
3.2.4 SignUpModalContent 组件
// src/components/Auth/SignUpModalContent.js
// 复用 SignUpIllustration.js 的核心UI逻辑
// 移除页面级的 Flex minH="100vh",改为 Box
// 注册成功后调用 handleLoginSuccess 而不是 navigate
3.3 集成到 App.js
// src/App.js
import { AuthModalProvider } from "contexts/AuthModalContext";
import AuthModalManager from "components/Auth/AuthModalManager";
export default function App() {
return (
<ChakraProvider theme={theme}>
<ErrorBoundary>
<AuthProvider>
<AuthModalProvider>
<AppContent />
<AuthModalManager /> {/* 全局弹窗管理器 */}
</AuthModalProvider>
</AuthProvider>
</ErrorBoundary>
</ChakraProvider>
);
}
4. 实施步骤
阶段1:创建基础设施(1-2小时)
-
Step 1.1: 创建
AuthModalContext.js- 实现状态管理
- 实现打开/关闭方法
- 实现成功回调处理
-
Step 1.2: 创建
AuthModalManager.js- 实现 Modal 容器
- 处理响应式布局
- 添加关闭按钮
-
Step 1.3: 提取登录UI组件
- 从
SignInIllustration.js提取核心UI - 创建
LoginModalContent.js - 移除页面级布局代码
- 替换 navigate 为 modal 方法
- 从
-
Step 1.4: 提取注册UI组件
- 从
SignUpIllustration.js提取核心UI - 创建
SignUpModalContent.js - 移除页面级布局代码
- 替换 navigate 为 modal 方法
- 从
阶段2:集成到应用(0.5-1小时)
-
Step 2.1: 在
App.js中集成- 导入
AuthModalProvider - 包裹
AppContent - 添加
<AuthModalManager />
- 导入
-
Step 2.2: 验证基础功能
- 测试弹窗打开/关闭
- 测试登录/注册切换
- 测试响应式布局
阶段3:替换现有跳转(1-2小时)
-
Step 3.1: 修改
HomeNavbar.js- 添加登录和注册弹窗// 修改前 const handleLoginClick = () => { navigate('/auth/signin'); }; // 未登录状态显示"登录/注册"按钮 <Button onClick={handleLoginClick}>登录 / 注册</Button> // 修改后 import { useAuthModal } from '../../contexts/AuthModalContext'; import { Menu, MenuButton, MenuList, MenuItem } from '@chakra-ui/react'; const { openLoginModal, openSignUpModal } = useAuthModal(); // 方式1:下拉菜单方式(推荐) <Menu> <MenuButton as={Button} colorScheme="blue" variant="solid" size="sm" borderRadius="full" rightIcon={<ChevronDownIcon />} > 登录 / 注册 </MenuButton> <MenuList> <MenuItem onClick={() => openLoginModal()}> 🔐 登录 </MenuItem> <MenuItem onClick={() => openSignUpModal()}> ✍️ 注册 </MenuItem> </MenuList> </Menu> // 方式2:并排按钮方式(备选) <HStack spacing={2}> <Button size="sm" variant="ghost" onClick={() => openLoginModal()} > 登录 </Button> <Button size="sm" colorScheme="blue" onClick={() => openSignUpModal()} > 注册 </Button> </HStack> -
Step 3.2: 修改
AdminNavbarLinks.js- 替换
navigate("/auth/signin")为openLoginModal()
- 替换
-
Step 3.3: 修改
AuthContext.jslogout函数// 修改前 const logout = async () => { // ... 清理逻辑 navigate('/auth/signin'); }; // 修改后 const logout = async () => { // ... 清理逻辑 // 不再跳转,用户留在当前页面 toast({ title: "已登出", description: "您已成功退出登录", status: "info", duration: 2000 }); }; -
Step 3.4: 修改
ProtectedRoute.js// 修改前 if (!isAuthenticated || !user) { return <Navigate to={redirectUrl} replace />; } // 修改后 import { useAuthModal } from '../contexts/AuthModalContext'; import { useEffect } from 'react'; const { openLoginModal, isLoginModalOpen } = useAuthModal(); useEffect(() => { if (!isAuthenticated && !user && !isLoginModalOpen) { openLoginModal(currentPath); } }, [isAuthenticated, user, isLoginModalOpen, currentPath, openLoginModal]); // 未登录时显示占位符(不再跳转) if (!isAuthenticated || !user) { return ( <Box height="100vh" display="flex" alignItems="center" justifyContent="center"> <VStack spacing={4}> <Spinner size="xl" color="blue.500" /> <Text>请先登录...</Text> </VStack> </Box> ); }
阶段4:测试与优化(1-2小时)
- Step 4.1: 功能测试(见第5节)
- Step 4.2: 边界情况处理
- Step 4.3: 性能优化
- Step 4.4: 用户体验优化
5. 测试用例
5.1 基础功能测试
| 测试项 | 测试步骤 | 预期结果 | 状态 |
|---|---|---|---|
| 登录弹窗打开 | 1. 点击导航栏"登录/注册"下拉菜单 2. 点击"登录" |
弹窗正常打开,显示登录表单 | ⬜ |
| 注册弹窗打开 | 1. 点击导航栏"登录/注册"下拉菜单 2. 点击"注册" |
弹窗正常打开,显示注册表单 | ⬜ |
| 登录弹窗关闭 | 1. 打开登录弹窗 2. 点击关闭按钮 |
弹窗正常关闭,返回原页面 | ⬜ |
| 注册弹窗关闭 | 1. 打开注册弹窗 2. 点击关闭按钮 |
弹窗正常关闭,返回原页面 | ⬜ |
| 从登录切换到注册 | 1. 打开登录弹窗 2. 点击"去注册" |
弹窗切换到注册表单,无页面刷新 | ⬜ |
| 从注册切换到登录 | 1. 打开注册弹窗 2. 点击"去登录" |
弹窗切换到登录表单,无页面刷新 | ⬜ |
| 手机号+密码登录 | 1. 打开登录弹窗 2. 输入手机号和密码 3. 点击登录 |
登录成功,弹窗关闭,显示成功提示 | ⬜ |
| 验证码登录 | 1. 打开登录弹窗 2. 切换到验证码登录 3. 发送并输入验证码 4. 点击登录 |
登录成功,弹窗关闭 | ⬜ |
| 微信登录 | 1. 打开登录弹窗 2. 点击微信登录 3. 扫码授权 |
登录成功,弹窗关闭 | ⬜ |
| 手机号+密码注册 | 1. 打开注册弹窗 2. 填写手机号、密码等信息 3. 点击注册 |
注册成功,弹窗关闭,自动登录 | ⬜ |
| 验证码注册 | 1. 打开注册弹窗 2. 切换到验证码注册 3. 发送并输入验证码 4. 点击注册 |
注册成功,弹窗关闭,自动登录 | ⬜ |
| 微信注册 | 1. 打开注册弹窗 2. 点击微信注册 3. 扫码授权 |
注册成功,弹窗关闭,自动登录 | ⬜ |
5.2 受保护路由测试
| 测试项 | 测试步骤 | 预期结果 | 状态 |
|---|---|---|---|
| 未登录访问概念中心 | 1. 未登录状态 2. 访问 /concepts |
自动弹出登录弹窗 | ⬜ |
| 登录后继续访问 | 1. 在上述弹窗中登录 2. 查看页面状态 |
弹窗关闭,概念中心页面正常显示 | ⬜ |
| 未登录访问社区 | 1. 未登录状态 2. 访问 /community |
自动弹出登录弹窗 | ⬜ |
| 未登录访问个股中心 | 1. 未登录状态 2. 访问 /stocks |
自动弹出登录弹窗 | ⬜ |
| 未登录访问模拟盘 | 1. 未登录状态 2. 访问 /trading-simulation |
自动弹出登录弹窗 | ⬜ |
| 未登录访问管理后台 | 1. 未登录状态 2. 访问 /admin/* |
自动弹出登录弹窗 | ⬜ |
5.3 登出测试
| 测试项 | 测试步骤 | 预期结果 | 状态 |
|---|---|---|---|
| 从导航栏登出 | 1. 已登录状态 2. 点击用户菜单"退出登录" |
登出成功,留在当前页面,显示未登录状态 | ⬜ |
| 登出后访问受保护页面 | 1. 登出后 2. 尝试访问 /concepts |
自动弹出登录弹窗 | ⬜ |
5.4 边界情况测试
| 测试项 | 测试步骤 | 预期结果 | 状态 |
|---|---|---|---|
| 登录失败 | 1. 输入错误的手机号或密码 2. 点击登录 |
显示错误提示,弹窗保持打开 | ⬜ |
| 网络断开 | 1. 断开网络 2. 尝试登录 |
显示网络错误提示 | ⬜ |
| 倒计时中关闭弹窗 | 1. 发送验证码(60秒倒计时) 2. 关闭弹窗 3. 重新打开 |
倒计时正确清理,无内存泄漏 | ⬜ |
| 重复打开弹窗 | 1. 快速连续点击登录按钮多次 | 只显示一个弹窗,无重复 | ⬜ |
| 响应式布局 | 1. 在手机端打开登录弹窗 | 弹窗全屏显示,UI适配良好 | ⬜ |
5.5 兼容性测试
| 测试项 | 测试步骤 | 预期结果 | 状态 |
|---|---|---|---|
| 直接访问登录页面 | 1. 访问 /auth/sign-in |
页面正常显示(保持路由兼容) | ⬜ |
| 直接访问注册页面 | 1. 访问 /auth/sign-up |
页面正常显示(保持路由兼容) | ⬜ |
| SEO爬虫访问 | 1. 模拟搜索引擎爬虫访问 | 页面可访问,无JavaScript错误 | ⬜ |
6. 兼容性处理
6.1 保留现有路由
为了兼容性和SEO,保留现有的登录/注册页面路由:
// src/layouts/Auth.js
// 保持不变,继续支持 /auth/sign-in 和 /auth/sign-up 路由
<Route path="signin" element={<SignInIllustration />} />
<Route path="sign-up" element={<SignUpIllustration />} />
好处:
- 外部链接(邮件、短信中的登录链接)仍然有效
- SEO友好,搜索引擎可以正常抓取
- 用户可以直接访问登录页面(如果他们更喜欢)
6.2 渐进式迁移
阶段1:保留两种方式
- 弹窗登录(新实现)
- 页面跳转登录(旧实现)
阶段2:逐步迁移
- 核心场景使用弹窗(导航栏、受保护路由)
- 非核心场景保持原样(备用认证页面)
阶段3:全面切换(可选)
- 所有场景统一使用弹窗
- 页面路由仅作为后备
6.3 微信登录兼容
微信登录涉及OAuth回调,需要特殊处理:
// WechatRegister.js 中
// 微信授权成功后会跳转回 /auth/callback
// 需要在回调页面检测到登录成功后:
// 1. 更新 AuthContext 状态
// 2. 如果是从弹窗发起的,关闭弹窗并回到原页面
// 3. 如果是从页面发起的,跳转到目标页面
7. 实施时间表
总预计时间:4-6小时
| 阶段 | 预计时间 | 实际时间 | 负责人 | 状态 |
|---|---|---|---|---|
| 阶段1:创建基础设施 | 1-2小时 | - | - | ⬜ 待开始 |
| 阶段2:集成到应用 | 0.5-1小时 | - | - | ⬜ 待开始 |
| 阶段3:替换现有跳转 | 1-2小时 | - | - | ⬜ 待开始 |
| 阶段4:测试与优化 | 1-2小时 | - | - | ⬜ 待开始 |
8. 风险评估
8.1 技术风险
| 风险 | 等级 | 应对措施 |
|---|---|---|
| 微信登录回调兼容性 | 🟡 中 | 保留页面路由,微信回调仍跳转到页面 |
| 受保护路由逻辑复杂化 | 🟡 中 | 详细测试,确保所有场景覆盖 |
| 弹窗状态管理冲突 | 🟢 低 | 使用独立的Context,避免与AuthContext冲突 |
| 内存泄漏 | 🟢 低 | 复用已有的内存管理模式(isMountedRef) |
8.2 用户体验风险
| 风险 | 等级 | 应对措施 |
|---|---|---|
| 用户不习惯弹窗登录 | 🟢 低 | 保留页面路由,提供选择 |
| 移动端弹窗体验差 | 🟡 中 | 移动端使用全屏Modal |
| 弹窗被误关闭 | 🟢 低 | 添加确认提示或表单状态保存 |
9. 后续优化建议
9.1 短期优化(1周内)
- 添加登录/注册进度指示器
- 优化弹窗动画效果
- 添加键盘快捷键支持(Esc关闭)
- 优化移动端触摸体验
9.2 中期优化(1月内)
- 添加第三方登录(Google、GitHub等)
- 实现记住登录状态
- 添加生物识别登录(指纹、Face ID)
- 优化表单验证提示
9.3 长期优化(3月内)
- 实现SSO单点登录
- 添加多因素认证(2FA)
- 实现社交账号关联
- 完善审计日志
10. 参考资料
文档维护:
- 创建日期:2025-10-14
- 最后更新:2025-10-14
- 维护人:Claude Code
- 状态:📝 规划阶段
附录A:关键代码片段
A.1 修改前后对比 - HomeNavbar.js
// src/components/Navbars/HomeNavbar.js
- import { useNavigate } from 'react-router-dom';
+ import { useAuthModal } from '../../contexts/AuthModalContext';
export default function HomeNavbar() {
- const navigate = useNavigate();
+ const { openLoginModal, openSignUpModal } = useAuthModal();
- // 处理登录按钮点击
- const handleLoginClick = () => {
- navigate('/auth/signin');
- };
return (
// ... 其他代码
{/* 未登录状态 */}
- <Button onClick={handleLoginClick}>
- 登录 / 注册
- </Button>
+ {/* 方式1:下拉菜单(推荐) */}
+ <Menu>
+ <MenuButton
+ as={Button}
+ colorScheme="blue"
+ size="sm"
+ borderRadius="full"
+ rightIcon={<ChevronDownIcon />}
+ >
+ 登录 / 注册
+ </MenuButton>
+ <MenuList>
+ <MenuItem onClick={() => openLoginModal()}>
+ 🔐 登录
+ </MenuItem>
+ <MenuItem onClick={() => openSignUpModal()}>
+ ✍️ 注册
+ </MenuItem>
+ </MenuList>
+ </Menu>
+
+ {/* 方式2:并排按钮(备选) */}
+ <HStack spacing={2}>
+ <Button
+ size="sm"
+ variant="ghost"
+ onClick={() => openLoginModal()}
+ >
+ 登录
+ </Button>
+ <Button
+ size="sm"
+ colorScheme="blue"
+ onClick={() => openSignUpModal()}
+ >
+ 注册
+ </Button>
+ </HStack>
);
}
A.2 修改前后对比 - ProtectedRoute.js
// src/components/ProtectedRoute.js
+ import { useAuthModal } from '../contexts/AuthModalContext';
+ import { useEffect } from 'react';
const ProtectedRoute = ({ children }) => {
- const { isAuthenticated, isLoading, user } = useAuth();
+ const { isAuthenticated, isLoading, user } = useAuth();
+ const { openLoginModal, isLoginModalOpen } = useAuthModal();
- if (isLoading) {
- return <Box>...Loading Spinner...</Box>;
- }
let currentPath = window.location.pathname + window.location.search;
- let redirectUrl = `/auth/signin?redirect=${encodeURIComponent(currentPath)}`;
+ // 未登录时自动弹出登录窗口
+ useEffect(() => {
+ if (!isAuthenticated && !user && !isLoginModalOpen) {
+ openLoginModal(currentPath);
+ }
+ }, [isAuthenticated, user, isLoginModalOpen, currentPath, openLoginModal]);
if (!isAuthenticated || !user) {
- return <Navigate to={redirectUrl} replace />;
+ return (
+ <Box height="100vh" display="flex" alignItems="center" justifyContent="center">
+ <VStack spacing={4}>
+ <Spinner size="xl" color="blue.500" />
+ <Text>请先登录...</Text>
+ </VStack>
+ </Box>
+ );
}
return children;
};
A.3 修改前后对比 - AuthContext.js
// src/contexts/AuthContext.js
const logout = async () => {
try {
await fetch(`${API_BASE_URL}/api/auth/logout`, {
method: 'POST',
credentials: 'include'
});
setUser(null);
setIsAuthenticated(false);
toast({
title: "已登出",
description: "您已成功退出登录",
status: "info",
duration: 2000,
isClosable: true,
});
- navigate('/auth/signin');
} catch (error) {
console.error('Logout error:', error);
setUser(null);
setIsAuthenticated(false);
- navigate('/auth/signin');
}
};
A.4 修改前后对比 - LoginModalContent 和 SignUpModalContent 切换
// src/components/Auth/LoginModalContent.js
+ import { useAuthModal } from '../../contexts/AuthModalContext';
export default function LoginModalContent() {
+ const { switchToSignUp, handleLoginSuccess } = useAuthModal();
// 登录成功处理
const handleSubmit = async (e) => {
e.preventDefault();
// ... 登录逻辑
if (loginSuccess) {
- navigate("/home");
+ handleLoginSuccess(userData);
}
};
return (
<Box>
{/* 登录表单 */}
<form onSubmit={handleSubmit}>
{/* ... 表单内容 */}
</form>
{/* 底部切换链接 */}
<AuthFooter
linkText="还没有账号,"
linkLabel="去注册"
- linkTo="/auth/sign-up"
+ onClick={() => switchToSignUp()}
/>
</Box>
);
}
// src/components/Auth/SignUpModalContent.js
+ import { useAuthModal } from '../../contexts/AuthModalContext';
export default function SignUpModalContent() {
+ const { switchToLogin, handleLoginSuccess } = useAuthModal();
// 注册成功处理
const handleSubmit = async (e) => {
e.preventDefault();
// ... 注册逻辑
if (registerSuccess) {
- toast({ title: "注册成功" });
- setTimeout(() => navigate("/auth/sign-in"), 2000);
+ toast({ title: "注册成功,自动登录中..." });
+ // 注册成功后自动登录,然后关闭弹窗
+ handleLoginSuccess(userData);
}
};
return (
<Box>
{/* 注册表单 */}
<form onSubmit={handleSubmit}>
{/* ... 表单内容 */}
</form>
{/* 底部切换链接 */}
<AuthFooter
linkText="已有账号?"
linkLabel="去登录"
- linkTo="/auth/sign-in"
+ onClick={() => switchToLogin()}
/>
</Box>
);
}
A.5 AuthFooter 组件修改(支持弹窗切换)
// src/components/Auth/AuthFooter.js
export default function AuthFooter({
linkText,
linkLabel,
- linkTo,
+ onClick,
useVerificationCode,
onSwitchMethod
}) {
return (
<VStack spacing={3}>
<HStack justify="space-between" width="100%">
<Text fontSize="sm" color="gray.600">
{linkText}
- <Link to={linkTo} color="blue.500">
+ <Link onClick={onClick} color="blue.500" cursor="pointer">
{linkLabel}
</Link>
</Text>
{onSwitchMethod && (
<Button size="sm" variant="link" onClick={onSwitchMethod}>
{useVerificationCode ? "密码登录" : "验证码登录"}
</Button>
)}
</HStack>
</VStack>
);
}
准备好开始实施了吗?
请确认以下事项:
- 已备份当前代码(git commit)
- 已在开发环境测试
- 团队成员已了解改造方案
- 准备好测试设备(桌面端、移动端)
开始命令:
# 创建功能分支
git checkout -b feature/login-modal-refactor
# 开始实施...