diff --git a/package.json b/package.json index ff6605f4..d783c618 100755 --- a/package.json +++ b/package.json @@ -132,7 +132,6 @@ "prettier": "2.2.1", "react-error-overlay": "6.0.9", "sharp": "^0.34.4", - "tailwindcss": "^3.4.17", "ts-node": "^10.9.2", "webpack-bundle-analyzer": "^4.10.2", "yn": "^5.1.0" diff --git a/src/components/Navbars/components/Navigation/DesktopNav.js b/src/components/Navbars/components/Navigation/DesktopNav.js index 02c6a189..c9512352 100644 --- a/src/components/Navbars/components/Navigation/DesktopNav.js +++ b/src/components/Navbars/components/Navigation/DesktopNav.js @@ -69,9 +69,10 @@ const DesktopNav = memo(({ isAuthenticated, user }) => { > 高频跟踪 - + { + onHighFreqClose(); // 先关闭菜单 // 🎯 追踪菜单项点击 navEvents.trackMenuItemClicked('事件中心', 'dropdown', '/community'); navigate('/community'); @@ -92,6 +93,7 @@ const DesktopNav = memo(({ isAuthenticated, user }) => { { + onHighFreqClose(); // 先关闭菜单 // 🎯 追踪菜单项点击 navEvents.trackMenuItemClicked('概念中心', 'dropdown', '/concepts'); navigate('/concepts'); @@ -127,9 +129,12 @@ const DesktopNav = memo(({ isAuthenticated, user }) => { > 行情复盘 - + navigate('/limit-analyse')} + onClick={() => { + onMarketReviewClose(); // 先关闭菜单 + navigate('/limit-analyse'); + }} borderRadius="md" bg={location.pathname.includes('/limit-analyse') ? 'blue.50' : 'transparent'} borderLeft={location.pathname.includes('/limit-analyse') ? '3px solid' : 'none'} @@ -142,7 +147,10 @@ const DesktopNav = memo(({ isAuthenticated, user }) => { navigate('/stocks')} + onClick={() => { + onMarketReviewClose(); // 先关闭菜单 + navigate('/stocks'); + }} borderRadius="md" bg={location.pathname.includes('/stocks') ? 'blue.50' : 'transparent'} borderLeft={location.pathname.includes('/stocks') ? '3px solid' : 'none'} @@ -155,7 +163,10 @@ const DesktopNav = memo(({ isAuthenticated, user }) => { navigate('/trading-simulation')} + onClick={() => { + onMarketReviewClose(); // 先关闭菜单 + navigate('/trading-simulation'); + }} borderRadius="md" bg={location.pathname.includes('/trading-simulation') ? 'blue.50' : 'transparent'} borderLeft={location.pathname.includes('/trading-simulation') ? '3px solid' : 'none'} @@ -181,7 +192,7 @@ const DesktopNav = memo(({ isAuthenticated, user }) => { > AGENT社群 - + { > 联系我们 - + 敬请期待 diff --git a/src/components/Navbars/components/Navigation/MoreMenu.js b/src/components/Navbars/components/Navigation/MoreMenu.js index 936a21f3..4a2293b8 100644 --- a/src/components/Navbars/components/Navigation/MoreMenu.js +++ b/src/components/Navbars/components/Navigation/MoreMenu.js @@ -52,11 +52,14 @@ const MoreMenu = memo(({ isAuthenticated, user }) => { > 更多 - + {/* 高频跟踪组 */} 高频跟踪 navigate('/community')} + onClick={() => { + onClose(); // 先关闭菜单 + navigate('/community'); + }} borderRadius="md" bg={location.pathname.includes('/community') ? 'blue.50' : 'transparent'} > @@ -69,7 +72,10 @@ const MoreMenu = memo(({ isAuthenticated, user }) => { navigate('/concepts')} + onClick={() => { + onClose(); // 先关闭菜单 + navigate('/concepts'); + }} borderRadius="md" bg={location.pathname.includes('/concepts') ? 'blue.50' : 'transparent'} > @@ -84,7 +90,10 @@ const MoreMenu = memo(({ isAuthenticated, user }) => { {/* 行情复盘组 */} 行情复盘 navigate('/limit-analyse')} + onClick={() => { + onClose(); // 先关闭菜单 + navigate('/limit-analyse'); + }} borderRadius="md" bg={location.pathname.includes('/limit-analyse') ? 'blue.50' : 'transparent'} > @@ -94,7 +103,10 @@ const MoreMenu = memo(({ isAuthenticated, user }) => { navigate('/stocks')} + onClick={() => { + onClose(); // 先关闭菜单 + navigate('/stocks'); + }} borderRadius="md" bg={location.pathname.includes('/stocks') ? 'blue.50' : 'transparent'} > @@ -104,7 +116,10 @@ const MoreMenu = memo(({ isAuthenticated, user }) => { navigate('/trading-simulation')} + onClick={() => { + onClose(); // 先关闭菜单 + navigate('/trading-simulation'); + }} borderRadius="md" bg={location.pathname.includes('/trading-simulation') ? 'blue.50' : 'transparent'} > diff --git a/src/components/Navbars/components/Navigation/PersonalCenterMenu.js b/src/components/Navbars/components/Navigation/PersonalCenterMenu.js index cac0e876..d68a1611 100644 --- a/src/components/Navbars/components/Navigation/PersonalCenterMenu.js +++ b/src/components/Navbars/components/Navigation/PersonalCenterMenu.js @@ -57,7 +57,7 @@ const PersonalCenterMenu = memo(({ user, handleLogout }) => { > 个人中心 - + {/* 用户信息区 */} {getDisplayName()} @@ -71,24 +71,36 @@ const PersonalCenterMenu = memo(({ user, handleLogout }) => { {/* 前往个人中心 */} - } onClick={() => navigate('/home/center')}> + } onClick={() => { + onClose(); // 先关闭菜单 + navigate('/home/center'); + }}> 前往个人中心 {/* 账户管理组 */} - } onClick={() => navigate('/home/profile')}> + } onClick={() => { + onClose(); // 先关闭菜单 + navigate('/home/profile'); + }}> 个人资料 - } onClick={() => navigate('/home/settings')}> + } onClick={() => { + onClose(); // 先关闭菜单 + navigate('/home/settings'); + }}> 账户设置 {/* 功能入口组 */} - } onClick={() => navigate('/home/pages/account/subscription')}> + } onClick={() => { + onClose(); // 先关闭菜单 + navigate('/home/pages/account/subscription'); + }}> 订阅管理 diff --git a/src/index.js b/src/index.js index 5fd7dfcb..79c2e646 100755 --- a/src/index.js +++ b/src/index.js @@ -25,7 +25,12 @@ async function startApp() { // Render the app with Router wrapper root.render( - + diff --git a/src/layouts/Auth.js b/src/layouts/Auth.js deleted file mode 100755 index d608ac95..00000000 --- a/src/layouts/Auth.js +++ /dev/null @@ -1,67 +0,0 @@ -// src/layouts/Auth.js -import React from 'react'; -import { Routes, Route, Navigate } from 'react-router-dom'; -import { Box } from '@chakra-ui/react'; -import { useAuth } from '../contexts/AuthContext'; -import ErrorBoundary from '../components/ErrorBoundary'; - -// 导入认证相关页面 -import SignInIllustration from '../views/Authentication/SignIn/SignInIllustration'; -import SignUpIllustration from '../views/Authentication/SignUp/SignUpIllustration'; - -// 认证路由组件 - 已登录用户不能访问登录页 -const AuthRoute = ({ children }) => { - const { isAuthenticated, isLoading } = useAuth(); - - // 加载中不做跳转 - if (isLoading) { - return children; - } - - // 已登录用户跳转到首页 - if (isAuthenticated) { - // 检查是否有记录的重定向路径 - const redirectPath = localStorage.getItem('redirectPath'); - if (redirectPath && redirectPath !== '/auth/signin' && redirectPath !== '/auth/sign-up') { - localStorage.removeItem('redirectPath'); - return ; - } - return ; - } - - return children; -}; - -export default function Auth() { - return ( - - - - {/* 登录页面 */} - - - - } - /> - - {/* 注册页面 */} - - - - } - /> - - {/* 默认重定向到登录页 */} - } /> - } /> - - - - ); -} \ No newline at end of file diff --git a/src/layouts/Home.js b/src/layouts/Home.js deleted file mode 100755 index 922619ed..00000000 --- a/src/layouts/Home.js +++ /dev/null @@ -1,98 +0,0 @@ -// src/layouts/Home.js -import React from "react"; -import { Routes, Route } from "react-router-dom"; -import { Box } from '@chakra-ui/react'; - -// 导航栏已由 MainLayout 提供,此处不再导入 -// import HomeNavbar from "../components/Navbars/HomeNavbar"; - -// 导入页面组件 -import HomePage from "views/Home/HomePage"; -import ProfilePage from "views/Profile/ProfilePage"; -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")); -const TradingSimulation = React.lazy(() => import("views/TradingSimulation")); - -// 导入保护路由组件 -import ProtectedRoute from "../components/ProtectedRoute"; - -export default function Home() { - return ( - - {/* 导航栏已由 MainLayout 提供,此处不再渲染 */} - - {/* 主要内容区域 */} - - - {/* 首页默认路由 */} - } /> - - - - } - /> - - {/* 需要登录保护的页面 */} - - - - } - /> - - - - - } - /> - - {/* 订阅管理页面 */} - - - - } - /> - - {/* 模拟盘交易页面 */} - - - - } - /> - - {/* 隐私政策页面 - 无需登录 */} - } /> - - {/* 用户协议页面 - 无需登录 */} - } /> - - {/* 微信授权回调页面 - 无需登录 */} - } /> - - {/* 其他可能的路由 */} - } /> - - - - ); -} \ No newline at end of file diff --git a/src/layouts/MainLayout.js b/src/layouts/MainLayout.js index b4c4423f..61ffd9eb 100644 --- a/src/layouts/MainLayout.js +++ b/src/layouts/MainLayout.js @@ -1,13 +1,14 @@ // src/layouts/MainLayout.js // 主布局组件 - 为所有带导航栏的页面提供统一布局 -import React, { memo } from "react"; -import { Outlet, useLocation } from "react-router-dom"; +import React, { memo, Suspense } from "react"; +import { Outlet } from "react-router-dom"; import { Box } from '@chakra-ui/react'; import HomeNavbar from "../components/Navbars/HomeNavbar"; import AppFooter from "./AppFooter"; import BackToTopButton from "./components/BackToTopButton"; -import PageTransitionWrapper from "./components/PageTransitionWrapper"; -import { ANIMATION_CONFIG, BACK_TO_TOP_CONFIG } from "./config/layoutConfig"; +import ErrorBoundary from "../components/ErrorBoundary"; +import PageLoader from "../components/Loading/PageLoader"; +import { BACK_TO_TOP_CONFIG } from "./config/layoutConfig"; // ✅ P0 性能优化:缓存静态组件,避免路由切换时不必要的重新渲染 // HomeNavbar (1623行) 和 AppFooter 不依赖路由参数,使用 memo 可大幅减少渲染次数 @@ -20,38 +21,27 @@ const MemoizedAppFooter = memo(AppFooter); * 使用 渲染子路由,确保导航栏只渲染一次 * 页面切换时只有 Outlet 内的内容会更新,导航栏保持不变 * - * 架构优化(2024-10-30): - * - ✅ P0: 组件拆分 - BackToTopButton 独立复用(37行 → 独立文件) - * - ✅ P0: 组件拆分 - PageTransitionWrapper 封装复杂逻辑(18行 → 独立文件) - * - ✅ P0: 性能优化 - 使用 memo 避免导航栏和页脚重新渲染(性能提升 50%+) - * - ✅ P1: 性能优化 - 使用 RAF 节流滚动事件(性能提升 80%) - * - ✅ P1: 错误隔离 - ErrorBoundary 包裹页面内容,确保导航栏可用 - * - ✅ P2: 用户体验 - 页面过渡动画(framer-motion) - * - ✅ P2: 配置集中 - layoutConfig 统一管理配置常量 - * - ✅ P3: 用户体验 - 返回顶部按钮(滚动 > 300px 显示) - * - * 代码优化成果: - * - 代码量:115 行 → 42 行(减少 63%) - * - 复杂度:内联组件 → 独立模块 - * - 可维护性:配置分散 → 集中管理 - * - 可复用性:耦合 → 解耦 + * 架构优化: + * - ✅ 组件拆分 - BackToTopButton 独立复用 + * - ✅ 性能优化 - 使用 memo 避免导航栏和页脚重新渲染 + * - ✅ 错误隔离 - ErrorBoundary 包裹页面内容,确保导航栏可用 + * - ✅ 懒加载支持 - Suspense 统一处理懒加载 + * - ✅ 布局简化 - 直接内联容器逻辑,减少嵌套层级 */ export default function MainLayout() { - const location = useLocation(); - return ( {/* 导航栏 - 在所有页面间共享,memo 后不会在路由切换时重新渲染 */} - {/* 页面内容区域 - 包含动画、错误边界、懒加载 */} - - - + {/* 页面内容区域 - flex: 1 占据剩余空间,包含错误边界、懒加载 */} + + + }> + + + + {/* 页脚 - 在所有页面间共享,memo 后不会在路由切换时重新渲染 */} diff --git a/src/layouts/components/PageTransitionWrapper.js b/src/layouts/components/PageTransitionWrapper.js deleted file mode 100644 index 504167ee..00000000 --- a/src/layouts/components/PageTransitionWrapper.js +++ /dev/null @@ -1,66 +0,0 @@ -// src/layouts/components/PageTransitionWrapper.js -import React, { Suspense, memo } from 'react'; -import { Box } from '@chakra-ui/react'; -import { motion, AnimatePresence } from 'framer-motion'; -import ErrorBoundary from '../../components/ErrorBoundary'; -import PageLoader from '../../components/Loading/PageLoader'; - -// 创建 motion 包裹的 Box 组件 -const MotionBox = motion(Box); - -/** - * 页面过渡动画包裹组件 - * - * 功能: - * - 页面切换时的过渡动画(AnimatePresence) - * - 错误边界隔离(ErrorBoundary) - * - 懒加载支持(Suspense) - * - * 优化: - * - ✅ 使用 memo 避免不必要的重新渲染 - * - ✅ 支持自定义动画配置 - * - ✅ 错误隔离,确保导航栏不受影响 - * - * @param {React.ReactNode} children - 要渲染的子组件(通常是 ) - * @param {object} location - 路由位置对象(用于动画 key) - * @param {object} animationConfig - 自定义动画配置 - * @param {string} loaderMessage - 加载时显示的消息 - */ -const PageTransitionWrapper = memo(({ - children, - location, - animationConfig = { - initial: { opacity: 0, y: 20 }, - animate: { opacity: 1, y: 0 }, - exit: { opacity: 0, y: -20 }, - transition: { duration: 0.2 } - }, - loaderMessage = '页面加载中...' -}) => { - return ( - - - - {/* 错误边界:隔离页面错误,确保导航栏仍可用 */} - - {/* Suspense:支持 React.lazy() 懒加载 */} - }> - {children} - - - - - - ); -}); - -PageTransitionWrapper.displayName = 'PageTransitionWrapper'; - -export default PageTransitionWrapper; diff --git a/src/routes/components/RouteContainer.js b/src/routes/components/RouteContainer.js deleted file mode 100644 index 55a8dd27..00000000 --- a/src/routes/components/RouteContainer.js +++ /dev/null @@ -1,53 +0,0 @@ -// src/routes/components/RouteContainer.js -// 路由容器组件 - 提供统一的错误边界、加载状态和主题背景 - -import React, { Suspense } from 'react'; -import { Box, useColorMode } from '@chakra-ui/react'; -import ErrorBoundary from '@components/ErrorBoundary'; -import PageLoader from '@components/Loading/PageLoader'; - -/** - * RouteContainer - 路由容器组件 - * - * 为路由系统提供统一的外层包装,包含: - * 1. 主题感知的背景色(深色/浅色模式) - * 2. Suspense 懒加载边界(显示加载提示) - * 3. ErrorBoundary 错误边界(隔离路由错误) - * - * 这个组件确保: - * - 所有路由页面都有一致的背景色 - * - 懒加载组件有统一的加载提示 - * - 单个路由的错误不会导致整个应用崩溃 - * - * @param {Object} props - * @param {React.ReactNode} props.children - 子组件(通常是 Routes) - * @param {string} [props.loadingMessage='加载页面中...'] - 加载提示文本 - * - * @example - * - * - * } /> - * - * - */ -export function RouteContainer({ - children, - loadingMessage = "加载页面中..." -}) { - const { colorMode } = useColorMode(); - - return ( - - {/* Suspense 统一处理懒加载组件的加载状态 */} - }> - {/* ErrorBoundary 隔离路由错误,防止整个应用崩溃 */} - - {children} - - - - ); -} diff --git a/src/routes/components/index.js b/src/routes/components/index.js deleted file mode 100644 index 7a74d66a..00000000 --- a/src/routes/components/index.js +++ /dev/null @@ -1,4 +0,0 @@ -// src/routes/components/index.js -// 统一导出所有路由组件 - -export { RouteContainer } from './RouteContainer'; diff --git a/src/routes/constants/index.js b/src/routes/constants/index.js index dc4a5214..2a04fb3b 100644 --- a/src/routes/constants/index.js +++ b/src/routes/constants/index.js @@ -1,5 +1,5 @@ // src/routes/constants/index.js // 统一导出所有路由常量 -export { LAYOUT_COMPONENTS } from './layoutComponents'; export { PROTECTION_WRAPPER_MAP } from './protectionWrappers'; +export { PROTECTION_MODES } from './protectionModes'; diff --git a/src/routes/constants/layoutComponents.js b/src/routes/constants/layoutComponents.js deleted file mode 100644 index a098af91..00000000 --- a/src/routes/constants/layoutComponents.js +++ /dev/null @@ -1,26 +0,0 @@ -// src/routes/constants/layoutComponents.js -// 布局组件映射表 - -import Auth from '@layouts/Auth'; -import HomeLayout from '@layouts/Home'; - -/** - * 特殊布局组件映射表 - * - * 用于将字符串标识符映射到实际的组件。 - * 这些是非懒加载的布局组件,在 routeConfig.js 中通过字符串引用。 - * - * @example - * // 在 routeConfig.js 中: - * { - * path: 'auth/*', - * component: 'Auth', // 字符串标识符 - * ... - * } - * - * // 通过 LAYOUT_COMPONENTS['Auth'] 获取实际组件 - */ -export const LAYOUT_COMPONENTS = { - Auth, - HomeLayout, -}; diff --git a/src/routes/constants/protectionModes.js b/src/routes/constants/protectionModes.js new file mode 100644 index 00000000..ebb5a013 --- /dev/null +++ b/src/routes/constants/protectionModes.js @@ -0,0 +1,14 @@ +// src/routes/constants/protectionModes.js +// 路由保护模式常量 + +/** + * 路由保护模式 + * - 'modal': 使用 ProtectedRoute (弹窗模式登录) + * - 'redirect': 使用 ProtectedRouteRedirect (跳转模式登录) + * - 'public': 公开访问,无需登录 + */ +export const PROTECTION_MODES = { + MODAL: 'modal', + REDIRECT: 'redirect', + PUBLIC: 'public', +}; diff --git a/src/routes/constants/protectionWrappers.js b/src/routes/constants/protectionWrappers.js index 574611cb..0e34695a 100644 --- a/src/routes/constants/protectionWrappers.js +++ b/src/routes/constants/protectionWrappers.js @@ -3,7 +3,7 @@ import ProtectedRoute from '@components/ProtectedRoute'; import ProtectedRouteRedirect from '@components/ProtectedRouteRedirect'; -import { PROTECTION_MODES } from '../routeConfig'; +import { PROTECTION_MODES } from './protectionModes'; /** * 保护模式包装器映射表 diff --git a/src/routes/homeRoutes.js b/src/routes/homeRoutes.js new file mode 100644 index 00000000..5ad2ed5c --- /dev/null +++ b/src/routes/homeRoutes.js @@ -0,0 +1,115 @@ +// src/routes/homeRoutes.js +// Home 模块子路由配置 + +import { lazyComponents } from './lazy-components'; +import { PROTECTION_MODES } from './constants/protectionModes'; + +/** + * Home 模块的子路由配置 + * 这些路由将作为 /home/* 的嵌套路由 + * + * 注意: + * - 使用相对路径(不带前导斜杠) + * - 空字符串 '' 表示索引路由,匹配 /home + * - 这些路由将通过 Outlet 渲染到父路由中 + */ +export const homeRoutes = [ + // 首页 - /home + { + path: '', + component: lazyComponents.HomePage, + protection: PROTECTION_MODES.PUBLIC, + meta: { + title: '首页', + description: '价值前沿首页' + } + }, + + // 个人中心 - /home/center + { + path: 'center', + component: lazyComponents.CenterDashboard, + protection: PROTECTION_MODES.MODAL, + meta: { + title: '个人中心', + description: '用户个人中心' + } + }, + + // 个人资料 - /home/profile + { + path: 'profile', + component: lazyComponents.ProfilePage, + protection: PROTECTION_MODES.MODAL, + meta: { + title: '个人资料', + description: '用户个人资料' + } + }, + + // 账户设置 - /home/settings + { + path: 'settings', + component: lazyComponents.SettingsPage, + protection: PROTECTION_MODES.MODAL, + meta: { + title: '账户设置', + description: '用户账户设置' + } + }, + + // 订阅管理 - /home/pages/account/subscription + { + path: 'pages/account/subscription', + component: lazyComponents.Subscription, + protection: PROTECTION_MODES.MODAL, + meta: { + title: '订阅管理', + description: '管理订阅服务' + } + }, + + // 隐私政策 - /home/privacy-policy + { + path: 'privacy-policy', + component: lazyComponents.PrivacyPolicy, + protection: PROTECTION_MODES.PUBLIC, + meta: { + title: '隐私政策', + description: '隐私保护政策' + } + }, + + // 用户协议 - /home/user-agreement + { + path: 'user-agreement', + component: lazyComponents.UserAgreement, + protection: PROTECTION_MODES.PUBLIC, + meta: { + title: '用户协议', + description: '用户使用协议' + } + }, + + // 微信授权回调 - /home/wechat-callback + { + path: 'wechat-callback', + component: lazyComponents.WechatCallback, + protection: PROTECTION_MODES.PUBLIC, + meta: { + title: '微信授权', + description: '微信授权回调页面' + } + }, + + // 回退路由 - 匹配任何未定义的 /home/* 路径 + { + path: '*', + component: lazyComponents.HomePage, + protection: PROTECTION_MODES.PUBLIC, + meta: { + title: '首页', + description: '价值前沿首页' + } + }, +]; diff --git a/src/routes/index.js b/src/routes/index.js index 65ec97d1..75dfbda1 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -10,9 +10,8 @@ import { getMainLayoutRoutes, getStandaloneRoutes } from './routeConfig'; // 布局组件 import MainLayout from '@layouts/MainLayout'; -// 路由工具和组件 +// 路由工具 import { renderRoute } from './utils'; -import { RouteContainer } from './components'; /** * AppRoutes - 应用路由组件 @@ -31,7 +30,11 @@ import { RouteContainer } from './components'; * 目录结构: * - constants/ - 常量配置(布局映射、保护包装器) * - utils/ - 工具函数(renderRoute, wrapWithProtection) - * - components/ - 路由组件(RouteContainer) + * - components/ - 路由相关组件 + * + * 注意: + * - Suspense/ErrorBoundary 由 PageTransitionWrapper 统一处理 + * - 全屏容器由 MainLayout 提供(minH="100vh") */ export function AppRoutes() { // 🎯 性能优化:使用 useMemo 缓存路由计算结果 @@ -39,23 +42,21 @@ export function AppRoutes() { const standaloneRoutes = useMemo(() => getStandaloneRoutes(), []); return ( - - - {/* 主布局路由 - 带导航栏和页脚 */} - }> - {mainLayoutRoutes.map(renderRoute)} - + + {/* 主布局路由 - 带导航栏和页脚 */} + }> + {mainLayoutRoutes.map(renderRoute)} + - {/* 独立路由 - 无布局(如登录页)*/} - {standaloneRoutes.map(renderRoute)} + {/* 独立路由 - 无布局(如登录页)*/} + {standaloneRoutes.map(renderRoute)} - {/* 默认路由 - 重定向到首页 */} - } /> + {/* 默认路由 - 重定向到首页 */} + } /> - {/* 404 页面 - 捕获所有未匹配的路由 */} - } /> - - + {/* 404 页面 - 捕获所有未匹配的路由 */} + } /> + ); } diff --git a/src/routes/lazy-components.js b/src/routes/lazy-components.js index dc1e668a..6659d6e1 100644 --- a/src/routes/lazy-components.js +++ b/src/routes/lazy-components.js @@ -8,6 +8,16 @@ import React from 'react'; * 使用 React.lazy() 实现路由懒加载,大幅减少初始 JS 包大小 */ export const lazyComponents = { + // Home 模块 + HomePage: React.lazy(() => import('../views/Home/HomePage')), + CenterDashboard: React.lazy(() => import('../views/Dashboard/Center')), + ProfilePage: React.lazy(() => import('../views/Profile/ProfilePage')), + SettingsPage: React.lazy(() => import('../views/Settings/SettingsPage')), + Subscription: React.lazy(() => import('../views/Pages/Account/Subscription')), + PrivacyPolicy: React.lazy(() => import('../views/Pages/PrivacyPolicy')), + UserAgreement: React.lazy(() => import('../views/Pages/UserAgreement')), + WechatCallback: React.lazy(() => import('../views/Pages/WechatCallback')), + // 社区/内容模块 Community: React.lazy(() => import('../views/Community')), ConceptCenter: React.lazy(() => import('../views/Concept')), @@ -31,6 +41,14 @@ export const lazyComponents = { * 按需导出单个组件(可选) */ export const { + HomePage, + CenterDashboard, + ProfilePage, + SettingsPage, + Subscription, + PrivacyPolicy, + UserAgreement, + WechatCallback, Community, ConceptCenter, StockOverview, diff --git a/src/routes/routeConfig.js b/src/routes/routeConfig.js index aa668b3d..64dd9c7d 100644 --- a/src/routes/routeConfig.js +++ b/src/routes/routeConfig.js @@ -2,35 +2,30 @@ // 声明式路由配置 import { lazyComponents } from './lazy-components'; +import { homeRoutes } from './homeRoutes'; +import { PROTECTION_MODES } from './constants/protectionModes'; -/** - * 路由保护模式 - * - 'modal': 使用 ProtectedRoute (弹窗模式登录) - * - 'redirect': 使用 ProtectedRouteRedirect (跳转模式登录) - * - 'public': 公开访问,无需登录 - */ -export const PROTECTION_MODES = { - MODAL: 'modal', - REDIRECT: 'redirect', - PUBLIC: 'public', -}; +// 重新导出 PROTECTION_MODES 以保持向后兼容 +export { PROTECTION_MODES }; /** * 路由配置 * 每个路由对象包含: * - path: 路由路径 - * - component: 组件(从 lazyComponents 引用) + * - component: 组件(从 lazyComponents 引用,或设为 null 使用 Outlet) * - protection: 保护模式 (modal/redirect/public) * - layout: 布局类型 (main/auth/none) + * - children: 子路由配置数组(可选,用于嵌套路由) * - meta: 路由元数据(可选,用于面包屑、标题等) */ export const routeConfig = [ // ==================== 首页 ==================== { - path: 'home/*', - component: 'HomeLayout', // 非懒加载,直接在 App.js 导入 + path: 'home', + component: null, // 使用 Outlet 渲染子路由 protection: PROTECTION_MODES.PUBLIC, layout: 'main', + children: homeRoutes, // 子路由配置 meta: { title: '首页', description: '价值前沿首页' @@ -107,7 +102,7 @@ export const routeConfig = [ { path: 'forecast-report', component: lazyComponents.ForecastReport, - protection: PROTECTION_MODES.REDIRECT, + protection: PROTECTION_MODES.MODAL, layout: 'main', meta: { title: '财报预测', @@ -115,7 +110,7 @@ export const routeConfig = [ } }, { - path: 'Financial', + path: 'financial', component: lazyComponents.FinancialPanorama, protection: PROTECTION_MODES.MODAL, layout: 'main', @@ -154,18 +149,6 @@ export const routeConfig = [ description: '实时市场数据' } }, - - // ==================== 认证模块 ==================== - { - path: 'auth/*', - component: 'Auth', // 非懒加载,直接在 App.js 导入 - protection: PROTECTION_MODES.PUBLIC, - layout: 'none', - meta: { - title: '登录/注册', - description: '用户认证' - } - }, ]; /** diff --git a/src/routes/utils/renderRoute.js b/src/routes/utils/renderRoute.js index f030fec1..53a4794a 100644 --- a/src/routes/utils/renderRoute.js +++ b/src/routes/utils/renderRoute.js @@ -2,8 +2,7 @@ // 路由渲染工具函数 import React from 'react'; -import { Route } from 'react-router-dom'; -import { LAYOUT_COMPONENTS } from '../constants'; +import { Route, Outlet } from 'react-router-dom'; import { wrapWithProtection } from './wrapWithProtection'; /** @@ -11,14 +10,16 @@ import { wrapWithProtection } from './wrapWithProtection'; * * 根据路由配置项生成 React Router 的 Route 组件。 * 处理以下逻辑: - * 1. 解析组件(特殊布局组件 vs 懒加载组件) + * 1. 解析组件(懒加载组件 or Outlet) * 2. 应用路由保护(根据 protection 字段) - * 3. 生成唯一 key + * 3. 处理嵌套路由(children 数组) + * 4. 生成唯一 key * * @param {Object} routeItem - 路由配置项(来自 routeConfig.js) * @param {string} routeItem.path - 路由路径 - * @param {React.ComponentType|string} routeItem.component - 组件或组件标识符 + * @param {React.ComponentType|null} routeItem.component - 懒加载组件或 null(null 表示使用 Outlet) * @param {string} routeItem.protection - 保护模式 (modal/redirect/public) + * @param {Array} [routeItem.children] - 子路由配置数组(可选) * @param {number} index - 路由索引,用于生成唯一 key * * @returns {React.ReactElement} Route 组件 @@ -27,19 +28,41 @@ import { wrapWithProtection } from './wrapWithProtection'; * // 使用示例 * const routes = [ * { path: 'community', component: CommunityComponent, protection: 'modal' }, - * { path: 'auth/*', component: 'Auth', protection: 'public' }, + * { path: 'home', component: null, protection: 'public', children: [...] }, * ]; * * routes.map((route, index) => renderRoute(route, index)); */ export function renderRoute(routeItem, index) { - const { path, component, protection } = routeItem; + const { path, component, protection, children } = routeItem; // 解析组件: - // - 如果是字符串(如 'Auth', 'HomeLayout'),从 LAYOUT_COMPONENTS 映射表查找 + // - 如果是 null,使用 用于嵌套路由 // - 否则直接使用(懒加载组件) - const Component = LAYOUT_COMPONENTS[component] || component; + let Component; + let isOutletRoute = false; + if (component === null) { + Component = Outlet; // 用于嵌套路由 + isOutletRoute = true; + } else { + Component = component; // 直接使用懒加载组件 + } + + // 如果有子路由,递归渲染 + if (children && children.length > 0) { + return ( + : wrapWithProtection(Component, protection)} + > + {children.map((childRoute, childIndex) => renderRoute(childRoute, childIndex))} + + ); + } + + // 没有子路由,渲染单个路由 return ( { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value - })); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!formData.email || !formData.password) { - toast({ - title: "请填写完整信息", - description: "邮箱和密码都是必填项", - status: "warning", - duration: 3000, - isClosable: true, - }); - return; - } - - const result = await login(formData.email, formData.password, 'email'); - - if (result.success) { - // 登录成功,跳转到首页 - navigate("/home"); - } - }; - - return ( - - - - - 价小前投研 - - - 登录您的账户 - - - -
- - - 邮箱地址 - - - - 密码 - - - - : } - onClick={() => setShowPassword(!showPassword)} - variant="ghost" - size="sm" - /> - - - - - - - - - 还没有账户?{" "} - navigate("/auth/signup")}> - 立即注册 - - - - -
-
-
-
- ); -} diff --git a/src/views/Authentication/SignIn/SignInCentered.js b/src/views/Authentication/SignIn/SignInCentered.js deleted file mode 100755 index 76af5d56..00000000 --- a/src/views/Authentication/SignIn/SignInCentered.js +++ /dev/null @@ -1,207 +0,0 @@ -import React, { useState } from "react"; -import { - Box, - Button, - FormControl, - FormLabel, - Input, - VStack, - Heading, - Text, - Link, - useColorMode, - InputGroup, - InputRightElement, - IconButton, - Spinner, -} from "@chakra-ui/react"; -import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; -import { useNavigate } from "react-router-dom"; -import { useAuth } from "../../../contexts/AuthContext"; - -export default function SignInCentered() { - const { colorMode } = useColorMode(); - const navigate = useNavigate(); - const { login, isLoading } = useAuth(); - - // 表单状态 - const [formData, setFormData] = useState({ - email: "", - password: "", - }); - - // UI状态 - const [showPassword, setShowPassword] = useState(false); - const [errors, setErrors] = useState({}); - - // 处理输入变化 - const handleInputChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value - })); - // 清除对应字段的错误 - if (errors[name]) { - setErrors(prev => ({ - ...prev, - [name]: "" - })); - } - }; - - // 表单验证 - const validateForm = () => { - const newErrors = {}; - - if (!formData.email) { - newErrors.email = "邮箱是必填项"; - } else if (!/\S+@\S+\.\S+/.test(formData.email)) { - newErrors.email = "请输入有效的邮箱地址"; - } - - if (!formData.password) { - newErrors.password = "密码是必填项"; - } else if (formData.password.length < 6) { - newErrors.password = "密码至少需要6个字符"; - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - // 处理表单提交 - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!validateForm()) { - return; - } - - const result = await login(formData.email, formData.password); - - if (result.success) { - // 登录成功,跳转到首页 - navigate("/home"); - } - }; - - return ( - - - - - 欢迎回来1 - 请输入您的凭据登录 - - -
- - - 邮箱地址 - - {errors.email && ( - - {errors.email} - - )} - - - - 密码 - - - - : } - variant="ghost" - onClick={() => setShowPassword(!showPassword)} - /> - - - {errors.password && ( - - {errors.password} - - )} - - - - -
- - - - 还没有账户?{" "} - navigate("/auth/signup")} - _hover={{ textDecoration: "underline" }} - > - 立即注册 - - - - - - 忘记密码? - - - 还没有账户?{" "} - navigate('/auth/sign-up')} - > - 立即注册 - - - - -
-
-
- ); -} \ No newline at end of file diff --git a/src/views/Authentication/SignIn/SignInCover.js b/src/views/Authentication/SignIn/SignInCover.js deleted file mode 100755 index 86690d2c..00000000 --- a/src/views/Authentication/SignIn/SignInCover.js +++ /dev/null @@ -1,223 +0,0 @@ -/*! - -========================================================= -* Argon Dashboard Chakra PRO - v1.0.0 -========================================================= - -* Product Page: https://www.creative-tim.com/product/argon-dashboard-chakra-pro -* Copyright 2022 Creative Tim (https://www.creative-tim.com/) - -* Designed and Coded by Simmmple & Creative Tim - -========================================================= - -* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -*/ - -// Chakra imports -import { - Box, - Button, - Flex, - FormControl, - FormLabel, - HStack, - Icon, - Input, - Link, - Switch, - Text, - useColorModeValue, -} from "@chakra-ui/react"; -// Assets -import CoverImage from "assets/img/CoverImage.png"; -import React from "react"; -import { FaApple, FaFacebook, FaGoogle } from "react-icons/fa"; -import AuthCover from "layouts/AuthCover"; - -function SignInCover() { - // Chakra color mode - const textColor = useColorModeValue("gray.400", "white"); - const bgForm = useColorModeValue("white", "navy.800"); - const titleColor = useColorModeValue("gray.700", "blue.500"); - const colorIcons = useColorModeValue("gray.700", "white"); - const bgIcons = useColorModeValue("trasnparent", "navy.700"); - const bgIconsHover = useColorModeValue("gray.50", "whiteAlpha.100"); - return ( - - - - - Sign In with - - - - - - - - - - - - - - - - - - - - or - - - - Name - - - - Password - - - - - - Remember me - - - - - - - Don’t have an account? - - Sign up - - - - - - - ); -} - -export default SignInCover; diff --git a/src/views/Authentication/SignIn/SignInIllustration.js b/src/views/Authentication/SignIn/SignInIllustration.js deleted file mode 100755 index ec125eda..00000000 --- a/src/views/Authentication/SignIn/SignInIllustration.js +++ /dev/null @@ -1,538 +0,0 @@ -// src/views/Authentication/SignIn/SignInIllustration.js - Session版本 -import React, { useState, useEffect, useRef } from "react"; -import { - Box, - Button, - Flex, - FormControl, - Input, - Text, - Heading, - VStack, - HStack, - useToast, - Icon, - InputGroup, - InputRightElement, - IconButton, - Link as ChakraLink, - Center, - useDisclosure, - FormErrorMessage -} from "@chakra-ui/react"; -import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; -import { FaMobile, FaLock } from "react-icons/fa"; -import { useNavigate, useLocation } from "react-router-dom"; -import { useAuth } from "../../../contexts/AuthContext"; -import PrivacyPolicyModal from "../../../components/PrivacyPolicyModal"; -import UserAgreementModal from "../../../components/UserAgreementModal"; -import AuthBackground from "../../../components/Auth/AuthBackground"; -import AuthHeader from "../../../components/Auth/AuthHeader"; -import AuthFooter from "../../../components/Auth/AuthFooter"; -import VerificationCodeInput from "../../../components/Auth/VerificationCodeInput"; -import WechatRegister from "../../../components/Auth/WechatRegister"; -import { logger } from "../../../utils/logger"; - -export default function SignInIllustration() { - const navigate = useNavigate(); - const location = useLocation(); - const toast = useToast(); - const { login, checkSession } = useAuth(); - - // 追踪组件挂载状态,防止内存泄漏 - const isMountedRef = useRef(true); - - // 页面状态 - const [isLoading, setIsLoading] = useState(false); - const [errors, setErrors] = useState({}); - - // 检查URL参数中的错误信息(微信登录失败时) - useEffect(() => { - const params = new URLSearchParams(location.search); - const error = params.get('error'); - - if (error) { - let errorMessage = '登录失败'; - switch (error) { - case 'wechat_auth_failed': - errorMessage = '微信授权失败'; - break; - case 'session_expired': - errorMessage = '会话已过期,请重新登录'; - break; - case 'token_failed': - errorMessage = '获取微信授权失败'; - break; - case 'userinfo_failed': - errorMessage = '获取用户信息失败'; - break; - case 'login_failed': - errorMessage = '登录处理失败,请重试'; - break; - default: - errorMessage = '登录失败,请重试'; - } - - toast({ - title: "登录失败", - description: errorMessage, - status: "error", - duration: 5000, - isClosable: true, - }); - - // 清除URL参数 - const newUrl = window.location.pathname; - window.history.replaceState({}, document.title, newUrl); - } - }, [location, toast]); - - // 传统登录数据 - // 表单数据初始化 - const [formData, setFormData] = useState({ - username: "", // 用户名称 - email: "", // 邮箱 - phone: "", // 电话 - password: "", // 密码 - verificationCode: "", // 添加验证码字段 - }); - - // 验证码登录状态 是否开启验证码 - const [useVerificationCode, setUseVerificationCode] = useState(false); - // 密码展示状态 - const [showPassword, setShowPassword] = useState(false); - - - const [verificationCodeSent, setVerificationCodeSent] = useState(false); // 验证码发送状态 - const [sendingCode, setSendingCode] = useState(false); // 发送验证码状态 - - - // 隐私政策弹窗状态 - const { isOpen: isPrivacyModalOpen, onOpen: onPrivacyModalOpen, onClose: onPrivacyModalClose } = useDisclosure(); - - // 用户协议弹窗状态 - const { isOpen: isUserAgreementModalOpen, onOpen: onUserAgreementModalOpen, onClose: onUserAgreementModalClose } = useDisclosure(); - - // 输入框输入 - const handleInputChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value - })); - }; - - // ========== 发送验证码逻辑 ============= - // 倒计时效果 - const [countdown, setCountdown] = useState(0); - useEffect(() => { - let timer; - let isMounted = true; - - if (countdown > 0) { - timer = setInterval(() => { - if (isMounted) { - setCountdown(prev => prev - 1); - } - }, 1000); - } else if (countdown === 0 && isMounted) { - setVerificationCodeSent(false); - } - - return () => { - isMounted = false; - if (timer) clearInterval(timer); - }; - }, [countdown]); - - // 发送验证码 - const sendVerificationCode = async () => { - const credential = formData.phone; - const type = 'phone'; - - if (!credential) { - toast({ - title: "请先输入手机号", - status: "warning", - duration: 3000, - }); - return; - } - - // 基本格式验证 - if (!/^1[3-9]\d{9}$/.test(credential)) { - toast({ - title: "请输入有效的手机号", - status: "warning", - duration: 3000, - }); - return; - } - - try { - setSendingCode(true); - const response = await fetch('/api/auth/send-verification-code', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - body: JSON.stringify({ - credential, - type, - purpose: 'login' - }), - }); - - // ✅ 安全检查:验证 response 存在 - if (!response) { - throw new Error('网络请求失败,请检查网络连接'); - } - - const data = await response.json(); - - // 组件卸载后不再执行后续操作 - if (!isMountedRef.current) return; - - // ✅ 安全检查:验证 data 存在 - if (!data) { - throw new Error('服务器响应为空'); - } - - if (response.ok && data.success) { - toast({ - title: "验证码已发送", - description: "验证码已发送到您的手机号", - status: "success", - duration: 3000, - }); - setVerificationCodeSent(true); - setCountdown(60); // 60秒倒计时 - } else { - throw new Error(data.error || '发送验证码失败'); - } - } catch (error) { - if (isMountedRef.current) { - toast({ - title: "发送验证码失败", - description: error.message || "请稍后重试", - status: "error", - duration: 3000, - }); - } - } finally { - if (isMountedRef.current) { - setSendingCode(false); - } - } - }; - - - // 验证码登录函数 - const loginWithVerificationCode = async (credential, verificationCode, authLoginType) => { - try { - const response = await fetch('/api/auth/login-with-code', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - body: JSON.stringify({ - credential, - verification_code: verificationCode, - login_type: authLoginType - }), - }); - - // ✅ 安全检查:验证 response 存在 - if (!response) { - throw new Error('网络请求失败,请检查网络连接'); - } - - const data = await response.json(); - - // 组件卸载后不再执行后续操作 - if (!isMountedRef.current) { - return { success: false, error: '操作已取消' }; - } - - // ✅ 安全检查:验证 data 存在 - if (!data) { - throw new Error('服务器响应为空'); - } - - if (response.ok && data.success) { - // 更新认证状态 - await checkSession(); - - if (isMountedRef.current) { - toast({ - title: "登录成功", - description: "欢迎回来!", - status: "success", - duration: 3000, - }); - } - return { success: true }; - } else { - throw new Error(data.error || '验证码登录失败'); - } - } catch (error) { - if (isMountedRef.current) { - toast({ - title: "登录失败", - description: error.message || "请检查验证码是否正确", - status: "error", - duration: 3000, - }); - } - return { success: false, error: error.message }; - } - }; - - - // 传统行业登陆 - const handleTraditionalLogin = async (e) => { - e.preventDefault(); - setIsLoading(true); - - try { - const credential = formData.phone; - const authLoginType = 'phone'; - - if (useVerificationCode) { // 验证码登陆 - if (!credential || !formData.verificationCode) { - toast({ - title: "请填写完整信息", - description: "手机号和验证码不能为空", - status: "warning", - duration: 3000, - }); - return; - } - - const result = await loginWithVerificationCode(credential, formData.verificationCode, authLoginType); - - if (result.success) { - navigate("/home"); - } - } else { // 密码登陆 - if (!credential || !formData.password) { - toast({ - title: "请填写完整信息", - description: `手机号和密码不能为空`, - status: "warning", - duration: 3000, - }); - return; - } - - const result = await login(credential, formData.password, authLoginType); - - if (result.success) { - // ✅ 显示成功提示 - toast({ - title: "登录成功", - description: "欢迎回来!", - status: "success", - duration: 3000, - isClosable: true, - }); - navigate("/home"); - } else { - // ❌ 显示错误提示 - toast({ - title: "登录失败", - description: result.error || "请检查您的登录信息", - status: "error", - duration: 3000, - isClosable: true, - }); - } - } - } catch (error) { - logger.error('SignInIllustration', 'handleTraditionalLogin', error, { - phone: formData.phone ? formData.phone.substring(0, 3) + '****' + formData.phone.substring(7) : 'N/A', - useVerificationCode, - loginType: 'phone' - }); - toast({ - title: "登录失败", - description: error.message || "发生未预期的错误,请重试", - status: "error", - duration: 3000, - isClosable: true, - }); - } finally { - setIsLoading(false); - } - }; - - // 切换登录方式 - const handleChangeMethod = () => { - setUseVerificationCode(!useVerificationCode); - // 切换到密码模式时清空验证码 - if (useVerificationCode) { - setFormData(prev => ({ ...prev, verificationCode: "" })); - } - }; - - // 组件卸载时清理 - useEffect(() => { - isMountedRef.current = true; - - return () => { - isMountedRef.current = false; - }; - }, []); - - return ( - - {/* 背景 */} - - - {/* 主要内容 */} - - {/* 登录卡片 */} - - {/* 头部区域 */} - - {/* 左右布局 */} - - {/* 左侧:手机号登陆 - 80% 宽度 */} - -
- - - 手机号登陆 - - - - {errors.phone} - - - {/* 密码/验证码输入框 */} - {useVerificationCode ? ( - - ) : ( - - - - - : } - onClick={() => setShowPassword(!showPassword)} - aria-label={showPassword ? "Hide password" : "Show password"} - /> - - - {errors.password} - - )} - - - - - - - -
-
- {/* 右侧:微信登陆 - 20% 宽度 */} - -
- -
-
-
- - {/* 底部链接 */} - - {/* 协议同意勾选框 */} - - 注册登录即表示阅读并同意{" "} - - 《用户协议》 - - {" "}和{" "} - - 《隐私政策》 - - - -
-
- - - {/* 隐私政策弹窗 */} - - - {/* 用户协议弹窗 */} - -
- ); -} \ No newline at end of file diff --git a/src/views/Authentication/SignUp/SignUpBasic.js b/src/views/Authentication/SignUp/SignUpBasic.js deleted file mode 100755 index a2b1cd5e..00000000 --- a/src/views/Authentication/SignUp/SignUpBasic.js +++ /dev/null @@ -1,254 +0,0 @@ -/*! - -========================================================= -* Argon Dashboard Chakra PRO - v1.0.0 -========================================================= - -* Product Page: https://www.creative-tim.com/product/argon-dashboard-chakra-pro -* Copyright 2022 Creative Tim (https://www.creative-tim.com/) - -* Designed and Coded by Simmmple & Creative Tim - -========================================================= - -* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -*/ -/*! - -========================================================= -* Argon Dashboard Chakra PRO - v1.0.0 -========================================================= - -* Product Page: https://www.creative-tim.com/product/argon-dashboard-chakra-pro -* Copyright 2022 Creative Tim (https://www.creative-tim.com/) - -* Designed and Coded by Simmmple & Creative Tim - -========================================================= - -* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -*/ - -// Chakra imports -import React, { useState } from "react"; -import { - Box, - Button, - Flex, - FormControl, - FormLabel, - Heading, - Input, - Stack, - useColorModeValue, - Text, - Link, - InputGroup, - InputRightElement, - IconButton, - useToast, - Checkbox, -} from "@chakra-ui/react"; -import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; -import { useNavigate } from "react-router-dom"; - -export default function SignUpBasic() { - const [showPassword, setShowPassword] = useState(false); - const [showConfirmPassword, setShowConfirmPassword] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [formData, setFormData] = useState({ - username: "", - email: "", - password: "", - confirmPassword: "", - agreeToTerms: false, - }); - - const navigate = useNavigate(); - const toast = useToast(); - - const handleInputChange = (e) => { - const { name, value, type, checked } = e.target; - setFormData(prev => ({ - ...prev, - [name]: type === "checkbox" ? checked : value - })); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - if (formData.password !== formData.confirmPassword) { - toast({ - title: "密码不匹配", - description: "请确保两次输入的密码相同", - status: "error", - duration: 3000, - isClosable: true, - }); - return; - } - - if (!formData.agreeToTerms) { - toast({ - title: "请同意条款", - description: "请阅读并同意用户协议和隐私政策", - status: "error", - duration: 3000, - isClosable: true, - }); - return; - } - - setIsLoading(true); - - // 模拟注册过程 - setTimeout(() => { - setIsLoading(false); - toast({ - title: "注册成功", - description: "欢迎加入价值前沿投资助手", - status: "success", - duration: 3000, - isClosable: true, - }); - navigate("/home"); - }, 1500); - }; - - return ( - - - - - 价小前投研 - - - 创建您的账户 - - - -
- - - 用户名 - - - - - 邮箱地址 - - - - - 密码 - - - - : } - onClick={() => setShowPassword(!showPassword)} - variant="ghost" - size="sm" - /> - - - - - - 确认密码 - - - - : } - onClick={() => setShowConfirmPassword(!showConfirmPassword)} - variant="ghost" - size="sm" - /> - - - - - - - - 我已阅读并同意{" "} - - 用户协议 - {" "} - 和{" "} - - 隐私政策 - - - - - - - - - - - 已有账户?{" "} - navigate("/auth/signin")}> - 立即登录 - - - - -
-
-
-
- ); -} diff --git a/src/views/Authentication/SignUp/SignUpCentered.js b/src/views/Authentication/SignUp/SignUpCentered.js deleted file mode 100755 index 32aeebd7..00000000 --- a/src/views/Authentication/SignUp/SignUpCentered.js +++ /dev/null @@ -1,282 +0,0 @@ -import React, { useState } from "react"; -import { - Box, - Button, - FormControl, - FormLabel, - Input, - VStack, - Heading, - Text, - Link, - useColorMode, - InputGroup, - InputRightElement, - IconButton, - Spinner, - Checkbox, - HStack, -} from "@chakra-ui/react"; -import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; -import { useNavigate } from "react-router-dom"; -import { useAuth } from "../../../contexts/AuthContext"; - -export default function SignUpCentered() { - const { colorMode } = useColorMode(); - const navigate = useNavigate(); - const { register, isLoading } = useAuth(); - - // 表单状态 - const [formData, setFormData] = useState({ - name: "", - email: "", - password: "", - confirmPassword: "", - }); - - // UI状态 - const [showPassword, setShowPassword] = useState(false); - const [showConfirmPassword, setShowConfirmPassword] = useState(false); - const [errors, setErrors] = useState({}); - const [agreedToTerms, setAgreedToTerms] = useState(false); - - // 处理输入变化 - const handleInputChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value - })); - // 清除对应字段的错误 - if (errors[name]) { - setErrors(prev => ({ - ...prev, - [name]: "" - })); - } - }; - - // 表单验证 - const validateForm = () => { - const newErrors = {}; - - if (!formData.name.trim()) { - newErrors.name = "姓名是必填项"; - } else if (formData.name.trim().length < 2) { - newErrors.name = "姓名至少需要2个字符"; - } - - if (!formData.email) { - newErrors.email = "邮箱是必填项"; - } else if (!/\S+@\S+\.\S+/.test(formData.email)) { - newErrors.email = "请输入有效的邮箱地址"; - } - - if (!formData.password) { - newErrors.password = "密码是必填项"; - } else if (formData.password.length < 6) { - newErrors.password = "密码至少需要6个字符"; - } else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(formData.password)) { - newErrors.password = "密码必须包含大小写字母和数字"; - } - - if (!formData.confirmPassword) { - newErrors.confirmPassword = "请确认密码"; - } else if (formData.password !== formData.confirmPassword) { - newErrors.confirmPassword = "两次输入的密码不一致"; - } - - if (!agreedToTerms) { - newErrors.terms = "请同意服务条款和隐私政策"; - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - // 处理表单提交 - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!validateForm()) { - return; - } - - const result = await register( - formData.name, // username - formData.email, - formData.password - ); - - if (result.success) { - // 注册成功,跳转到首页 - navigate("/home"); - } - }; - - return ( - - - - - 创建账户 - 加入价值前沿,开启智能投资之旅 - - -
- - - 姓名 - - {errors.name && ( - - {errors.name} - - )} - - - - 邮箱地址 - - {errors.email && ( - - {errors.email} - - )} - - - - 密码 - - - - : } - variant="ghost" - onClick={() => setShowPassword(!showPassword)} - /> - - - {errors.password && ( - - {errors.password} - - )} - - - - 确认密码 - - - - : } - variant="ghost" - onClick={() => setShowConfirmPassword(!showConfirmPassword)} - /> - - - {errors.confirmPassword && ( - - {errors.confirmPassword} - - )} - - - - - setAgreedToTerms(e.target.checked)} - colorScheme="blue" - > - - 我同意{" "} - - 服务条款 - - {" "}和{" "} - - 隐私政策 - - - - - {errors.terms && ( - - {errors.terms} - - )} - - - - -
- - - - 已有账户?{" "} - navigate("/auth/signin")} - _hover={{ textDecoration: "underline" }} - > - 立即登录 - - - -
-
-
- ); -} \ No newline at end of file diff --git a/src/views/Authentication/SignUp/SignUpCover.js b/src/views/Authentication/SignUp/SignUpCover.js deleted file mode 100755 index db045c26..00000000 --- a/src/views/Authentication/SignUp/SignUpCover.js +++ /dev/null @@ -1,234 +0,0 @@ -/*! - -========================================================= -* Argon Dashboard Chakra PRO - v1.0.0 -========================================================= - -* Product Page: https://www.creative-tim.com/product/argon-dashboard-chakra-pro -* Copyright 2022 Creative Tim (https://www.creative-tim.com/) - -* Designed and Coded by Simmmple & Creative Tim - -========================================================= - -* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -*/ - -// Chakra imports -import { - Button, - Flex, - FormControl, - FormLabel, - HStack, - Icon, - Input, - Link, - Switch, - Text, - useColorModeValue, -} from "@chakra-ui/react"; -// Assets -import CoverImage from "assets/img/CoverImage.png"; -import React from "react"; -import { FaApple, FaFacebook, FaGoogle } from "react-icons/fa"; -import AuthCover from "layouts/AuthCover"; - -function SignUpCover() { - // Chakra color mode - const textColor = useColorModeValue("gray.400", "white"); - const bgForm = useColorModeValue("white", "navy.800"); - const titleColor = useColorModeValue("gray.700", "blue.500"); - const colorIcons = useColorModeValue("gray.700", "white"); - const bgIcons = useColorModeValue("trasnparent", "navy.700"); - const bgIconsHover = useColorModeValue("gray.50", "whiteAlpha.100"); - return ( - - - - - Sign In with - - - - - - - - - - - - - - - - - - - - or - - - - Name - - - - Email - - - - Password - - - - - - Remember me - - - - - - - Don’t have an account? - - Sign up - - - - - - - ); -} - -export default SignUpCover; diff --git a/src/views/Authentication/SignUp/SignUpIllustration.js b/src/views/Authentication/SignUp/SignUpIllustration.js deleted file mode 100755 index fa719b8d..00000000 --- a/src/views/Authentication/SignUp/SignUpIllustration.js +++ /dev/null @@ -1,445 +0,0 @@ -// src\views\Authentication\SignUp/SignUpIllustration.js -import React, { useState, useEffect, useRef } from "react"; -import { getApiBase } from '../../../utils/apiConfig'; -import { - Box, - Button, - Flex, - FormControl, - Input, - Text, - Heading, - VStack, - HStack, - useToast, - InputGroup, - InputRightElement, - IconButton, - Center, - FormErrorMessage, - Link as ChakraLink, - useDisclosure -} from "@chakra-ui/react"; -import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; -import { useNavigate } from "react-router-dom"; -import axios from "axios"; -import AuthBackground from '../../../components/Auth/AuthBackground'; -import AuthHeader from '../../../components/Auth/AuthHeader'; -import AuthFooter from '../../../components/Auth/AuthFooter'; -import VerificationCodeInput from '../../../components/Auth/VerificationCodeInput'; -import WechatRegister from '../../../components/Auth/WechatRegister'; -import PrivacyPolicyModal from '../../../components/PrivacyPolicyModal'; -import UserAgreementModal from '../../../components/UserAgreementModal'; - -const isProduction = process.env.NODE_ENV === 'production'; -const API_BASE_URL = getApiBase(); - -export default function SignUpPage() { - const [showPassword, setShowPassword] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [countdown, setCountdown] = useState(0); - const [errors, setErrors] = useState({}); - - const [formData, setFormData] = useState({ - username: "", - email: "", - phone: "", - password: "", - confirmPassword: "", - verificationCode: "" - }); - - const navigate = useNavigate(); - const toast = useToast(); - - // 追踪组件挂载状态,防止内存泄漏 - const isMountedRef = useRef(true); - - // 隐私政策弹窗状态 - const { isOpen: isPrivacyModalOpen, onOpen: onPrivacyModalOpen, onClose: onPrivacyModalClose } = useDisclosure(); - - // 用户协议弹窗状态 - const { isOpen: isUserAgreementModalOpen, onOpen: onUserAgreementModalOpen, onClose: onUserAgreementModalClose } = useDisclosure(); - - // 验证码登录状态 是否开启验证码 - const [useVerificationCode, setUseVerificationCode] = useState(false); - - // 切换注册方式 - const handleChangeMethod = () => { - setUseVerificationCode(!useVerificationCode); - // 切换到密码模式时清空验证码 - if (useVerificationCode) { - setFormData(prev => ({ ...prev, verificationCode: "" })); - } - }; - - // 发送验证码 - const sendVerificationCode = async () => { - const contact = formData.phone; - const endpoint = "send-sms-code"; - const fieldName = "phone"; - - if (!contact) { - toast({ - title: "请输入手机号", - status: "warning", - duration: 2000, - }); - return; - } - - if (!/^1[3-9]\d{9}$/.test(contact)) { - toast({ - title: "请输入正确的手机号", - status: "warning", - duration: 2000, - }); - return; - } - - try { - setIsLoading(true); - const response = await axios.post(`${API_BASE_URL}/api/auth/${endpoint}`, { - [fieldName]: contact - }, { - timeout: 10000 // 添加10秒超时 - }); - - // 组件卸载后不再执行后续操作 - if (!isMountedRef.current) return; - - // ✅ 安全检查:验证 response 和 data 存在 - if (!response || !response.data) { - throw new Error('服务器响应为空'); - } - - toast({ - title: "验证码已发送", - description: "请查收短信", - status: "success", - duration: 3000, - }); - - setCountdown(60); - } catch (error) { - if (isMountedRef.current) { - toast({ - title: "发送失败", - description: error.response?.data?.error || error.message || "请稍后重试", - status: "error", - duration: 3000, - }); - } - } finally { - if (isMountedRef.current) { - setIsLoading(false); - } - } - }; - - // 倒计时效果 - useEffect(() => { - let isMounted = true; - - if (countdown > 0) { - const timer = setTimeout(() => { - if (isMounted) { - setCountdown(countdown - 1); - } - }, 1000); - - return () => { - isMounted = false; - clearTimeout(timer); - }; - } - }, [countdown]); - - // 表单验证 - const validateForm = () => { - const newErrors = {}; - - // 手机号验证(两种方式都需要) - if (!formData.phone) { - newErrors.phone = "请输入手机号"; - } else if (!/^1[3-9]\d{9}$/.test(formData.phone)) { - newErrors.phone = "请输入正确的手机号"; - } - - if (useVerificationCode) { - // 验证码注册方式:只验证手机号和验证码 - if (!formData.verificationCode) { - newErrors.verificationCode = "请输入验证码"; - } - } else { - // 密码注册方式:验证用户名、密码和确认密码 - if (!formData.password || formData.password.length < 6) { - newErrors.password = "密码至少6个字符"; - } - - if (formData.password !== formData.confirmPassword) { - newErrors.confirmPassword = "两次密码不一致"; - } - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - // 处理注册提交 - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!validateForm()) { - return; - } - - setIsLoading(true); - - try { - let endpoint, data; - - if (useVerificationCode) { - // 验证码注册:只发送手机号和验证码 - endpoint = "/api/auth/register/phone-code"; - data = { - phone: formData.phone, - code: formData.verificationCode - }; - } else { - // 密码注册:发送手机号、用户名和密码 - endpoint = "/api/auth/register/phone"; - data = { - phone: formData.phone, - username: formData.username, - password: formData.password - }; - } - - const response = await axios.post(`${API_BASE_URL}${endpoint}`, data, { - timeout: 10000 // 添加10秒超时 - }); - - // 组件卸载后不再执行后续操作 - if (!isMountedRef.current) return; - - // ✅ 安全检查:验证 response 和 data 存在 - if (!response || !response.data) { - throw new Error('注册请求失败:服务器响应为空'); - } - - toast({ - title: "注册成功", - description: "即将跳转到登录页面", - status: "success", - duration: 2000, - }); - - setTimeout(() => { - if (isMountedRef.current) { - navigate("/auth/sign-in"); - } - }, 2000); - } catch (error) { - if (isMountedRef.current) { - toast({ - title: "注册失败", - description: error.response?.data?.error || error.message || "请稍后重试", - status: "error", - duration: 3000, - }); - } - } finally { - if (isMountedRef.current) { - setIsLoading(false); - } - } - }; - - const handleInputChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); - if (errors[name]) { - setErrors(prev => ({ ...prev, [name]: "" })); - } - }; - - // 组件卸载时清理 - useEffect(() => { - isMountedRef.current = true; - - return () => { - isMountedRef.current = false; - }; - }, []); - - // 公用的用户名和密码输入框组件 - const commonAuthFields = ( - - - - - - : } - onClick={() => setShowPassword(!showPassword)} - aria-label={showPassword ? "Hide password" : "Show password"} - /> - - - {errors.password} - - - - - {errors.confirmPassword} - - - ); - - return ( - - {/* 背景 */} - - - {/* 主要内容 */} - - - {/* 头部区域 */} - - {/* 左右布局 */} - - {/* 左侧:手机号注册 - 80% 宽度 */} - -
- - - 注册 - - - - {errors.phone} - - - {/* 表单字段区域 */} - - { - useVerificationCode ? ( - - - {/* 隐藏的占位元素,保持与密码模式相同的高度 */} - - - ) : ( - <> - {commonAuthFields} - - ) - } - - - - - - - {/* 协议同意文本 */} - - 注册登录即表示阅读并同意{" "} - - 《用户协议》 - - {" "}和{" "} - - 《隐私政策》 - - - -
-
- - {/* 右侧:微信注册 - 20% 宽度 */} - -
- -
-
-
-
-
- - {/* 隐私政策弹窗 */} - - - {/* 用户协议弹窗 */} - -
- ); -} \ No newline at end of file diff --git a/src/views/Community/components/EventList.js b/src/views/Community/components/EventList.js index 0dabe86d..6e98a675 100644 --- a/src/views/Community/components/EventList.js +++ b/src/views/Community/components/EventList.js @@ -349,12 +349,13 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai {/* 左侧占位 */} - + {/* 中间:分页器 */} {pagination.total > 0 && localEvents.length > 0 ? ( - + - + 第 {pagination.current} / {Math.ceil(pagination.total / pagination.pageSize)} 页 - + 共 {pagination.total} 条 ) : ( - + )} {/* 右侧:控制按钮 */} - + {/* WebSocket 连接状态 */} {/* 桌面推送开关 */} - + 推送 @@ -420,7 +423,7 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai {/* 视图切换控制 */} - + 精简 @@ -440,7 +443,7 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai {/* 事件列表内容 */} {localEvents.length > 0 ? ( - + {localEvents.map((event, index) => ( ) : ( -
+
- - + + 暂无事件数据 @@ -472,6 +475,7 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai {pagination.total > 0 && ( } variant="ghost" size="sm" - onClick={() => navigate('/stock-analysis/overview')} + onClick={() => navigate('/stocks')} aria-label="添加自选股" /> @@ -300,7 +300,7 @@ export default function CenterDashboard() { size="sm" variant="outline" colorScheme="blue" - onClick={() => navigate('/stock-analysis/overview')} + onClick={() => navigate('/stocks')} > 添加自选股 @@ -321,7 +321,7 @@ export default function CenterDashboard() { {stock.stock_name || stock.stock_code} @@ -365,7 +365,7 @@ export default function CenterDashboard() { diff --git a/src/views/Home/HomePage.backup.js b/src/views/Home/HomePage.backup.js deleted file mode 100644 index 5ca88f42..00000000 --- a/src/views/Home/HomePage.backup.js +++ /dev/null @@ -1,629 +0,0 @@ -// src/views/Home/HomePage.js -import React, { useState, useEffect } from 'react'; -import { - Box, - Flex, - Text, - Button, - Container, - VStack, - HStack, - Icon, - Heading, - useBreakpointValue, - Link, - SimpleGrid, - Divider -} from '@chakra-ui/react'; -import { useNavigate } from 'react-router-dom'; -import { useAuth } from '../../contexts/AuthContext'; // 添加这个导入来调试 -import heroBg from 'assets/img/BackgroundCard1.png'; -import teamWorkingImg from 'assets/img/background-card-reports.png'; - -export default function HomePage() { - const navigate = useNavigate(); - const { user, isAuthenticated, isLoading } = useAuth(); // 添加这行来调试 - - // 添加调试信息 - useEffect(() => { - console.log('🏠 HomePage AuthContext 状态:', { - user, - isAuthenticated, - isLoading, - hasUser: !!user, - userInfo: user ? { - id: user.id, - username: user.username, - nickname: user.nickname - } : null - }); - }, [user?.id, isAuthenticated, isLoading]); // 只依赖 user.id,避免无限循环 - - // 统计数据动画 - const [stats, setStats] = useState({ - dataSize: 0, - dataSources: 0, - researchTargets: 0 - }); - - useEffect(() => { - const targetStats = { - dataSize: 17, - dataSources: 300, - researchTargets: 45646 - }; - - // 动画效果 - const animateStats = () => { - const duration = 2000; // 2秒动画 - const startTime = Date.now(); - - const animate = () => { - const elapsed = Date.now() - startTime; - const progress = Math.min(elapsed / duration, 1); - - setStats({ - dataSize: Math.floor(targetStats.dataSize * progress), - dataSources: Math.floor(targetStats.dataSources * progress), - researchTargets: Math.floor(targetStats.researchTargets * progress) - }); - - if (progress < 1) { - requestAnimationFrame(animate); - } - }; - - animate(); - }; - - const timer = setTimeout(animateStats, 500); - return () => clearTimeout(timer); - }, []); - - return ( - - {/* 临时调试信息栏 - 完成调试后可以删除 */} - {process.env.NODE_ENV === 'development' && ( - - 🐛 调试信息: - 认证状态: {isAuthenticated ? '✅ 已登录' : '❌ 未登录'} - 加载状态: {isLoading ? '⏳ 加载中' : '✅ 加载完成'} - 用户信息: {user ? `👤 ${user.nickname || user.username} (ID: ${user.id})` : '❌ 无用户信息'} - localStorage: {localStorage.getItem('user') ? '✅ 有数据' : '❌ 无数据'} - - )} - - {/* Hero Section - Brainwave风格 */} - - {/* 同心圆背景装饰 */} - - - - - - - {/* 动态装饰点 */} - - - - - {/* 主要内容 */} - - - - 探索 - - 人工智能 - - 的无限可能 -
- - 价值前沿 AI 投研助手 - -
- - 释放AI的力量,升级您的投研效率。 - 体验超越ChatGPT的专业投资分析平台。 - - -
-
- - {/* 渐变底部 */} - - - {/* CSS动画定义 */} - - - - {/* 统计数据区域 - 玻璃拟态效果 */} - - - - - - - {stats.dataSize}TB - - 基础数据 - - 我们收集来自全世界的各类数据,打造您的专属智能投资助手 - - - - - - {stats.dataSources}+ - - 数据源 - - 我们即时采集来自300多家数据源的实时数据,随时满足您的投研需求 - - - - - - {stats.researchTargets.toLocaleString()} - - 研究标的 - - 我们的研究范围涵盖全球主流市场,包括股票、外汇、大宗等交易类型,给您足够宏观的视角 - - - - - - - - {/* 特色功能介绍 - Brainwave深色风格 */} - - {/* 背景装饰几何图形 */} - - - - - - {/* 左侧功能介绍 - 深色主题版本 */} - - {/* 第一行 */} - - - - - - - - - - 人工智能驱动 - - • 收集海量投研资料和数据,确保信息全面丰富
- • 训练专注于投研的大语言模型,专业度领先
- • 在金融投资领域表现卓越,优于市面其他模型 -
-
-
- - - - - - - - - - 投研数据湖 - - • AI Agent 24/7 全天候采集全球数据,确保实时更新
- • 整合多种数据源,覆盖范围广泛、信息丰富
- • 构建独特数据湖,提供业内无可比拟的数据深度 -
-
-
-
- - {/* 第二行 */} - - - - - - - - - - - 投研Agent - - • 采用 AI 模拟人类分析师,智能化程度高
- • 具备独特的全球视角,全面审视各类资产
- • 提供最佳投资建议,支持科学决策 -
-
-
- - - - - - - - - - 新闻事件驱动 - - • 基于AI的信息挖掘技术
- • Agent 赋能的未来推演和数据关联
- • 自由交流,我们相信集体的力量 -
-
-
- - {/* 深研系统 → 盈利预测报表 入口 */} - - - 深研系统 - - - -
-
- - {/* 右侧卡片 - 完全按照原网站设计 */} - - - {/* 黑色遮罩 */} - - - {/* 内容 */} - - {/* 3D盒子图标 */} - - - - - - - - - - - 事件催化 -
- 让成功有迹可循 -
- - -
-
-
-
-
-
- - {/* AI投研专题应用区域 - Brainwave风格 */} - - - - - - AI投研专题应用 - - - By 价小前投研 - - - 人工智能+专业投研流程—最强投资AI助手 - - - - - - - {/* 页脚 - Brainwave深色主题 */} - - - - {/* 价值前沿 */} - - 价值前沿 - - 更懂投资者的AI投研平台 - - - - {/* 关于我们 */} - - 关于我们 - - 公司介绍 - 团队架构 - 联系方式 - 反馈评价 - - - - {/* 免费资源 */} - - 免费资源 - - 投研日报 - 资讯速递 - 免费试用 - - - - {/* 产品介绍 */} - - 产品介绍 - - 行情复盘 - 高频跟踪 - 深研系统 - 了解更多 - - - - {/* 产品下载 */} - - 产品下载 - - 手机APP - Win终端 - Mac终端 - - - - - {/* 版权信息 */} - - - All rights reserved. Copyright © {new Date().getFullYear()} 投研系统 by{' '} - - 价值前沿 - - . - - - -
- ); -} \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js deleted file mode 100644 index d6be43a4..00000000 --- a/tailwind.config.js +++ /dev/null @@ -1,135 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -import { fontFamily } from "tailwindcss/defaultTheme"; -import plugin from "tailwindcss/plugin"; - -module.exports = { - content: [ - "./src/**/*.{js,jsx,ts,tsx}", - "./src/templates/**/*.{js,jsx,ts,tsx}", - "./src/components/**/*.{js,jsx,ts,tsx}", - "./src/views/**/*.{js,jsx,ts,tsx}", - "./public/index.html", - ], - theme: { - extend: { - colors: { - color: { - 1: "#AC6AFF", - 2: "#FFC876", - 3: "#FF776F", - 4: "#7ADB78", - 5: "#858DFF", - 6: "#FF98E2", - }, - stroke: { - 1: "#26242C", - }, - n: { - 1: "#FFFFFF", - 2: "#CAC6DD", - 3: "#ADA8C3", - 4: "#757185", - 5: "#3F3A52", - 6: "#252134", - 7: "#15131D", - 8: "#0E0C15", - }, - }, - fontFamily: { - sans: ["var(--font-sora)", ...fontFamily.sans], - code: "var(--font-code)", - grotesk: "var(--font-grotesk)", - }, - letterSpacing: { - tagline: ".15em", - }, - spacing: { - 0.25: "0.0625rem", - 7.5: "1.875rem", - 15: "3.75rem", - }, - opacity: { - 15: ".15", - }, - transitionDuration: { - DEFAULT: "200ms", - }, - transitionTimingFunction: { - DEFAULT: "linear", - }, - zIndex: { - 1: "1", - 2: "2", - 3: "3", - 4: "4", - 5: "5", - }, - borderWidth: { - DEFAULT: "0.0625rem", - }, - backgroundImage: { - "radial-gradient": "radial-gradient(var(--tw-gradient-stops))", - "conic-gradient": - "conic-gradient(from 225deg, #FFC876, #79FFF7, #9F53FF, #FF98E2, #FFC876)", - }, - }, - }, - plugins: [ - plugin(function ({ addBase, addComponents, addUtilities }) { - addBase({}); - addComponents({ - ".container": { - "@apply max-w-[77.5rem] mx-auto px-5 md:px-10 lg:px-15 xl:max-w-[87.5rem]": - {}, - }, - ".h1": { - "@apply font-semibold text-[2.5rem] leading-[3.25rem] md:text-[2.75rem] md:leading-[3.75rem] lg:text-[3.25rem] lg:leading-[4.0625rem] xl:text-[3.75rem] xl:leading-[4.5rem]": - {}, - }, - ".h2": { - "@apply text-[1.75rem] leading-[2.5rem] md:text-[2rem] md:leading-[2.5rem] lg:text-[2.5rem] lg:leading-[3.5rem] xl:text-[3rem] xl:leading-tight": - {}, - }, - ".h3": { - "@apply text-[2rem] leading-normal md:text-[2.5rem]": {}, - }, - ".h4": { - "@apply text-[2rem] leading-normal": {}, - }, - ".h5": { - "@apply text-2xl leading-normal": {}, - }, - ".h6": { - "@apply font-semibold text-lg leading-8": {}, - }, - ".body-1": { - "@apply text-[0.875rem] leading-[1.5rem] md:text-[1rem] md:leading-[1.75rem] lg:text-[1.25rem] lg:leading-8": - {}, - }, - ".body-2": { - "@apply font-light text-[0.875rem] leading-6 md:text-base": - {}, - }, - ".caption": { - "@apply text-sm": {}, - }, - ".tagline": { - "@apply font-grotesk font-light text-xs tracking-tagline uppercase": - {}, - }, - ".quote": { - "@apply font-code text-lg leading-normal": {}, - }, - ".button": { - "@apply font-code text-xs font-bold uppercase tracking-wider": - {}, - }, - }); - addUtilities({ - ".tap-highlight-color": { - "-webkit-tap-highlight-color": "rgba(0, 0, 0, 0)", - }, - }); - }), - ], -};