diff --git a/src/components/Auth/AuthFormContent.js b/src/components/Auth/AuthFormContent.js index 7a7cd6f7..98f6540b 100644 --- a/src/components/Auth/AuthFormContent.js +++ b/src/components/Auth/AuthFormContent.js @@ -10,6 +10,7 @@ import { Heading, VStack, HStack, + Stack, useToast, Icon, FormErrorMessage, @@ -22,10 +23,14 @@ import { AlertDialogOverlay, Text, Link as ChakraLink, + useBreakpointValue, + Divider, + IconButton, } from "@chakra-ui/react"; -import { FaLock } from "react-icons/fa"; +import { FaLock, FaWeixin } from "react-icons/fa"; import { useAuth } from "../../contexts/AuthContext"; import { useAuthModal } from "../../contexts/AuthModalContext"; +import { authService } from "../../services/authService"; import AuthHeader from './AuthHeader'; import VerificationCodeInput from './VerificationCodeInput'; import WechatRegister from './WechatRegister'; @@ -37,7 +42,7 @@ const API_BASE_URL = isProduction ? "" : "http://49.232.185.254:5000"; // 统一配置对象 const AUTH_CONFIG = { // UI文本 - title: "欢迎使用价值前沿", + title: "价值前沿", subtitle: "开启您的投资之旅", formTitle: "手机号验证", buttonText: "登录/注册", @@ -79,6 +84,10 @@ export default function AuthFormContent() { const [showNicknamePrompt, setShowNicknamePrompt] = useState(false); const [currentPhone, setCurrentPhone] = useState(""); + // 响应式布局配置 + const isMobile = useBreakpointValue({ base: true, md: false }); + const stackDirection = useBreakpointValue({ base: "column", md: "row" }); + const stackSpacing = useBreakpointValue({ base: 4, md: 8 }); // 表单数据 const [formData, setFormData] = useState({ @@ -298,6 +307,44 @@ export default function AuthFormContent() { } }; + // 微信H5登录处理 + const handleWechatH5Login = async () => { + try { + // 1. 构建回调URL + const redirectUrl = `${window.location.origin}/home/wechat-callback`; + + // 2. 显示提示 + toast({ + title: "即将跳转", + description: "正在跳转到微信授权页面...", + status: "info", + duration: 2000, + isClosable: true, + }); + + // 3. 获取微信H5授权URL + const response = await authService.getWechatH5AuthUrl(redirectUrl); + + if (!response || !response.auth_url) { + throw new Error('获取授权链接失败'); + } + + // 4. 延迟跳转,让用户看到提示 + setTimeout(() => { + window.location.href = response.auth_url; + }, 500); + } catch (error) { + console.error('微信H5登录失败:', error); + toast({ + title: "跳转失败", + description: error.message || "请稍后重试", + status: "error", + duration: 3000, + isClosable: true, + }); + } + }; + // 组件卸载时清理 useEffect(() => { isMountedRef.current = true; @@ -311,8 +358,8 @@ export default function AuthFormContent() { <> - - + +
{config.formTitle} @@ -320,12 +367,43 @@ export default function AuthFormContent() { {errors.phone} - + + {/* 验证码输入框 + 移动端微信图标 */} + + + + {/* 移动端:验证码下方的微信登录图标 */} + {isMobile && ( + + 其他登录方式: + } + size="sm" + variant="ghost" + color="#07C160" + borderRadius="md" + minW="24px" + minH="24px" + _hover={{ + bg: "green.50", + color: "#06AD56" + }} + _active={{ + bg: "green.100" + }} + onClick={handleWechatH5Login} + isDisabled={isLoading} + /> + + )} + + {/* 隐私声明 */} - 登录即表示你同意价值前沿{" "} + 登录即表示您同意价值前沿{" "}
- -
-
-
+ + {/* 桌面端:右侧二维码扫描 */} + {!isMobile && ( + +
+ +
+
+ )} +
{/* 只在需要时才渲染 AlertDialog,避免创建不必要的 Portal */} diff --git a/src/components/Auth/AuthModalManager.js b/src/components/Auth/AuthModalManager.js index 6fcacf62..40e38cd6 100644 --- a/src/components/Auth/AuthModalManager.js +++ b/src/components/Auth/AuthModalManager.js @@ -23,10 +23,30 @@ export default function AuthModalManager() { // 响应式尺寸配置 const modalSize = useBreakpointValue({ - base: "full", // 移动端:全屏 - sm: "xl", // 小屏:xl - md: "2xl", // 中屏:2xl - lg: "4xl" // 大屏:4xl + base: "md", // 移动端:md(不占满全屏) + sm: "md", // 小屏:md + md: "lg", // 中屏:lg + lg: "xl" // 大屏:xl(更紧凑) + }); + + // 响应式宽度配置 + const modalMaxW = useBreakpointValue({ + base: "90%", // 移动端:屏幕宽度的90% + sm: "90%", // 小屏:90% + md: "700px", // 中屏:固定700px + lg: "700px" // 大屏:固定700px + }); + + // 响应式水平边距 + const modalMx = useBreakpointValue({ + base: 4, // 移动端:左右各16px边距 + md: "auto" // 桌面端:自动居中 + }); + + // 响应式垂直边距 + const modalMy = useBreakpointValue({ + base: 8, // 移动端:上下各32px边距 + md: 8 // 桌面端:上下各32px边距 }); // 条件渲染:只在打开时才渲染 Modal,避免创建不必要的 Portal @@ -56,8 +76,9 @@ export default function AuthModalManager() { bg="white" boxShadow="2xl" borderRadius="2xl" - maxW={modalSize === "full" ? "100%" : "900px"} - my={modalSize === "full" ? 0 : 8} + maxW={modalMaxW} + mx={modalMx} + my={modalMy} position="relative" > {/* 关闭按钮 */} @@ -75,7 +96,7 @@ export default function AuthModalManager() { /> {/* 弹窗主体内容 */} - + diff --git a/src/layouts/Home.js b/src/layouts/Home.js index 165d0f2e..edc8545a 100755 --- a/src/layouts/Home.js +++ b/src/layouts/Home.js @@ -13,9 +13,10 @@ import SettingsPage from "views/Settings/SettingsPage"; import CenterDashboard from "views/Dashboard/Center"; import Subscription from "views/Pages/Account/Subscription"; -// 懒加载隐私政策和用户协议页面 +// 懒加载隐私政策、用户协议和微信回调页面 const PrivacyPolicy = React.lazy(() => import("views/Pages/PrivacyPolicy")); const UserAgreement = React.lazy(() => import("views/Pages/UserAgreement")); +const WechatCallback = React.lazy(() => import("views/Pages/WechatCallback")); // 导入保护路由组件 import ProtectedRoute from "../components/ProtectedRoute"; @@ -76,6 +77,9 @@ export default function Home() { {/* 用户协议页面 - 无需登录 */} } /> + {/* 微信授权回调页面 - 无需登录 */} + } /> + {/* 其他可能的路由 */} } /> diff --git a/src/routes.js b/src/routes.js index c357fec0..7f692fef 100755 --- a/src/routes.js +++ b/src/routes.js @@ -85,6 +85,7 @@ const StockOverview = React.lazy(() => import("views/StockOverview")); const TradingSimulation = React.lazy(() => import("views/TradingSimulation")); const PrivacyPolicy = React.lazy(() => import("views/Pages/PrivacyPolicy")); const UserAgreement = React.lazy(() => import("views/Pages/UserAgreement")); +const WechatCallback = React.lazy(() => import("views/Pages/WechatCallback")); const dashRoutes = [ { name: "Dashboard", @@ -229,6 +230,14 @@ const dashRoutes = [ layout: "/home", invisible: true, // 不在侧边栏显示 }, + { + name: "微信授权回调", + path: "/wechat-callback", + icon: , + component: WechatCallback, + layout: "/home", + invisible: true, // 不在侧边栏显示 + }, { name: "PAGES", category: "pages", diff --git a/src/services/authService.js b/src/services/authService.js index 9edc57ab..b7e55a8f 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -63,13 +63,38 @@ const apiRequest = async (url, options = {}) => { export const authService = { /** - * 获取微信二维码授权链接 + * 获取微信二维码授权链接(PC扫码登录) * @returns {Promise<{auth_url: string, session_id: string}>} */ getWechatQRCode: async () => { return await apiRequest('/api/auth/wechat/qrcode'); }, + /** + * 获取微信H5授权链接(移动端网页授权) + * @param {string} redirectUrl - 授权成功后的回调地址 + * @returns {Promise<{auth_url: string}>} + */ + getWechatH5AuthUrl: async (redirectUrl) => { + return await apiRequest('/api/auth/wechat/h5-auth', { + method: 'POST', + body: JSON.stringify({ redirect_url: redirectUrl }), + }); + }, + + /** + * 微信H5授权回调处理 + * @param {string} code - 微信授权code + * @param {string} state - 状态参数 + * @returns {Promise<{success: boolean, user?: object, token?: string}>} + */ + handleWechatH5Callback: async (code, state) => { + return await apiRequest('/api/auth/wechat/h5-callback', { + method: 'POST', + body: JSON.stringify({ code, state }), + }); + }, + /** * 检查微信扫码状态 * @param {string} sessionId - 会话ID diff --git a/src/views/Pages/WechatCallback.js b/src/views/Pages/WechatCallback.js new file mode 100644 index 00000000..b795e81f --- /dev/null +++ b/src/views/Pages/WechatCallback.js @@ -0,0 +1,144 @@ +// src/views/Pages/WechatCallback.js +import React, { useEffect, useState } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { + Box, + Container, + VStack, + Spinner, + Text, + Icon, + useColorModeValue, + Heading, +} from "@chakra-ui/react"; +import { FaCheckCircle, FaTimesCircle } from "react-icons/fa"; +import { authService } from "../../services/authService"; +import { useAuth } from "../../contexts/AuthContext"; + +/** + * 微信H5授权回调页面 + * 处理微信授权后的回调,完成登录流程 + */ +export default function WechatCallback() { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const { checkSession } = useAuth(); + + const [status, setStatus] = useState("loading"); // loading, success, error + const [message, setMessage] = useState("正在处理微信授权..."); + + const bgColor = useColorModeValue("gray.50", "gray.900"); + const boxBg = useColorModeValue("white", "gray.800"); + + useEffect(() => { + const handleCallback = async () => { + try { + // 1. 获取URL参数 + const code = searchParams.get("code"); + const state = searchParams.get("state"); + + // 2. 参数验证 + if (!code) { + throw new Error("授权失败:缺少授权码"); + } + + // 3. 调用后端处理回调 + const response = await authService.handleWechatH5Callback(code, state); + + if (!response || !response.success) { + throw new Error(response?.error || "授权失败,请重试"); + } + + // 4. 存储用户信息(如果有返回token) + if (response.token) { + localStorage.setItem("token", response.token); + } + if (response.user) { + localStorage.setItem("user", JSON.stringify(response.user)); + } + + // 5. 更新session + await checkSession(); + + // 6. 显示成功状态 + setStatus("success"); + setMessage("登录成功!正在跳转..."); + + // 7. 延迟跳转到首页 + setTimeout(() => { + navigate("/home", { replace: true }); + }, 1500); + } catch (error) { + console.error("微信授权回调处理失败:", error); + setStatus("error"); + setMessage(error.message || "授权失败,请重试"); + + // 3秒后返回首页 + setTimeout(() => { + navigate("/home", { replace: true }); + }, 3000); + } + }; + + handleCallback(); + }, [searchParams, navigate, checkSession]); + + return ( + + + + + {/* 状态图标 */} + {status === "loading" && ( + <> + + + 处理中 + + + )} + + {status === "success" && ( + <> + + + 授权成功 + + + )} + + {status === "error" && ( + <> + + + 授权失败 + + + )} + + {/* 提示信息 */} + + {message} + + + + + + ); +}