diff --git a/src/components/Navbars/HomeNavbar.js b/src/components/Navbars/HomeNavbar.js index b86e68f7..0fea6d3c 100644 --- a/src/components/Navbars/HomeNavbar.js +++ b/src/components/Navbars/HomeNavbar.js @@ -60,6 +60,9 @@ import { useSubscription } from '../../hooks/useSubscription'; // Phase 3 优化: 提取的用户菜单组件 import { DesktopUserMenu, TabletUserMenu } from './components/UserMenu'; +// Phase 4 优化: 提取的导航菜单组件 +import { DesktopNav, MoreMenu, PersonalCenterMenu } from './components/Navigation'; + /** 二级导航栏组件 - 显示当前一级菜单下的所有二级菜单项 */ const SecondaryNav = ({ showCompletenessAlert }) => { const navigate = useNavigate(); @@ -211,294 +214,7 @@ const SecondaryNav = ({ showCompletenessAlert }) => { }; /** 中屏"更多"菜单 - 用于平板和小笔记本 */ -const MoreNavMenu = ({ isAuthenticated, user }) => { - const navigate = useNavigate(); - const location = useLocation(); - - // 辅助函数:判断导航项是否激活 - const isActive = useCallback((paths) => { - return paths.some(path => location.pathname.includes(path)); - }, [location.pathname]); - - if (!isAuthenticated || !user) return null; - - return ( - - } - fontWeight="medium" - > - 更多 - - - {/* 高频跟踪组 */} - 高频跟踪 - navigate('/community')} - borderRadius="md" - bg={location.pathname.includes('/community') ? 'blue.50' : 'transparent'} - > - - 事件中心 - - HOT - NEW - - - - navigate('/concepts')} - borderRadius="md" - bg={location.pathname.includes('/concepts') ? 'blue.50' : 'transparent'} - > - - 概念中心 - NEW - - - - - - {/* 行情复盘组 */} - 行情复盘 - navigate('/limit-analyse')} - borderRadius="md" - bg={location.pathname.includes('/limit-analyse') ? 'blue.50' : 'transparent'} - > - - 涨停分析 - FREE - - - navigate('/stocks')} - borderRadius="md" - bg={location.pathname.includes('/stocks') ? 'blue.50' : 'transparent'} - > - - 个股中心 - HOT - - - navigate('/trading-simulation')} - borderRadius="md" - bg={location.pathname.includes('/trading-simulation') ? 'blue.50' : 'transparent'} - > - - 模拟盘 - NEW - - - - - - {/* AGENT社群组 */} - AGENT社群 - - 今日热议 - - - 个股社区 - - - - - {/* 联系我们 */} - - 联系我们 - - - - ); -}; - -/** 桌面端导航 - 完全按照原网站 - * @TODO 添加逻辑 不展示导航case - * 1.未登陆状态 && 是首页 - * 2. !isMobile - */ -const NavItems = ({ isAuthenticated, user }) => { - const navigate = useNavigate(); - const location = useLocation(); - - // ⚠️ 必须在组件顶层调用所有Hooks(不能在JSX中调用) - const contactTextColor = useColorModeValue('gray.500', 'gray.300'); - - // 🎯 初始化导航埋点Hook - const navEvents = useNavigationEvents({ component: 'top_nav' }); - - // 辅助函数:判断导航项是否激活 - const isActive = useCallback((paths) => { - return paths.some(path => location.pathname.includes(path)); - }, [location.pathname]); - - if (isAuthenticated && user) { - return ( - - - } - bg={isActive(['/community', '/concepts']) ? 'blue.50' : 'transparent'} - color={isActive(['/community', '/concepts']) ? 'blue.600' : 'inherit'} - fontWeight={isActive(['/community', '/concepts']) ? 'bold' : 'normal'} - borderBottom={isActive(['/community', '/concepts']) ? '2px solid' : 'none'} - borderColor="blue.600" - _hover={{ bg: isActive(['/community', '/concepts']) ? 'blue.100' : 'gray.50' }} - > - 高频跟踪 - - - { - // 🎯 追踪菜单项点击 - navEvents.trackMenuItemClicked('事件中心', 'dropdown', '/community'); - navigate('/community'); - }} - borderRadius="md" - bg={location.pathname.includes('/community') ? 'blue.50' : 'transparent'} - borderLeft={location.pathname.includes('/community') ? '3px solid' : 'none'} - borderColor="blue.600" - fontWeight={location.pathname.includes('/community') ? 'bold' : 'normal'} - > - - 事件中心 - - HOT - NEW - - - - { - // 🎯 追踪菜单项点击 - navEvents.trackMenuItemClicked('概念中心', 'dropdown', '/concepts'); - navigate('/concepts'); - }} - borderRadius="md" - bg={location.pathname.includes('/concepts') ? 'blue.50' : 'transparent'} - borderLeft={location.pathname.includes('/concepts') ? '3px solid' : 'none'} - borderColor="blue.600" - fontWeight={location.pathname.includes('/concepts') ? 'bold' : 'normal'} - > - - 概念中心 - NEW - - - - - - - } - bg={isActive(['/limit-analyse', '/stocks']) ? 'blue.50' : 'transparent'} - color={isActive(['/limit-analyse', '/stocks']) ? 'blue.600' : 'inherit'} - fontWeight={isActive(['/limit-analyse', '/stocks']) ? 'bold' : 'normal'} - borderBottom={isActive(['/limit-analyse', '/stocks']) ? '2px solid' : 'none'} - borderColor="blue.600" - _hover={{ bg: isActive(['/limit-analyse', '/stocks']) ? 'blue.100' : 'gray.50' }} - > - 行情复盘 - - - navigate('/limit-analyse')} - borderRadius="md" - bg={location.pathname.includes('/limit-analyse') ? 'blue.50' : 'transparent'} - borderLeft={location.pathname.includes('/limit-analyse') ? '3px solid' : 'none'} - borderColor="blue.600" - fontWeight={location.pathname.includes('/limit-analyse') ? 'bold' : 'normal'} - > - - 涨停分析 - FREE - - - navigate('/stocks')} - borderRadius="md" - bg={location.pathname.includes('/stocks') ? 'blue.50' : 'transparent'} - borderLeft={location.pathname.includes('/stocks') ? '3px solid' : 'none'} - borderColor="blue.600" - fontWeight={location.pathname.includes('/stocks') ? 'bold' : 'normal'} - > - - 个股中心 - HOT - - - navigate('/trading-simulation')} - borderRadius="md" - bg={location.pathname.includes('/trading-simulation') ? 'blue.50' : 'transparent'} - borderLeft={location.pathname.includes('/trading-simulation') ? '3px solid' : 'none'} - borderColor="blue.600" - fontWeight={location.pathname.includes('/trading-simulation') ? 'bold' : 'normal'} - > - - 模拟盘 - NEW - - - - - - {false && isAuthenticated && ( - - }>自选股 - - )} - - - }> - AGENT社群 - - - - 今日热议 - - - 个股社区 - - - - - {false && isAuthenticated && ( - - }>关注的事件 - - )} - - - }> - 联系我们 - - - 敬请期待 - - - - ) - } else { - return null; - } -}; +// Phase 4: MoreNavMenu 和 NavItems 组件已提取到 Navigation 目录 export default function HomeNavbar() { const { isOpen, onOpen, onClose } = useDisclosure(); @@ -863,7 +579,7 @@ export default function HomeNavbar() { {/* Logo - 价小前投研 */} - {/* 中间导航区域 - 响应式 */} + {/* 中间导航区域 - 响应式 (Phase 4 优化) */} {isMobile ? ( // 移动端:汉堡菜单 ) : isTablet ? ( // 中屏(平板):"更多"下拉菜单 - + ) : ( // 大屏(桌面):完整导航菜单 - + )} {/* 右侧:日夜模式切换 + 登录/用户区 */} @@ -1081,53 +797,9 @@ export default function HomeNavbar() { /> )} - {/* 个人中心下拉菜单 - 仅大屏显示 */} + {/* 个人中心下拉菜单 - 仅大屏显示 (Phase 4 优化) */} {isDesktop && ( - - } - _hover={{ bg: useColorModeValue('gray.100', 'gray.700') }} - > - 个人中心 - - - - {getDisplayName()} - {user.email} - {user.phone && ( - {user.phone} - )} - {user.has_wechat && ( - 微信已绑定 - )} - - {/* 前往个人中心 */} - } onClick={() => navigate('/home/center')}> - 前往个人中心 - - - {/* 账户管理组 */} - } onClick={() => navigate('/home/profile')}> - 个人资料 - - } onClick={() => navigate('/home/settings')}> - 账户设置 - - - {/* 功能入口组 */} - } onClick={() => navigate('/home/pages/account/subscription')}> - 订阅管理 - - - {/* 退出 */} - } onClick={handleLogout} color="red.500"> - 退出登录 - - - + )} ) : ( diff --git a/src/components/Navbars/components/Navigation/DesktopNav.js b/src/components/Navbars/components/Navigation/DesktopNav.js new file mode 100644 index 00000000..fc8dba05 --- /dev/null +++ b/src/components/Navbars/components/Navigation/DesktopNav.js @@ -0,0 +1,200 @@ +// src/components/Navbars/components/Navigation/DesktopNav.js +// 桌面版主导航菜单 - 完整的导航栏 + +import React, { memo, useCallback } from 'react'; +import { + HStack, + Menu, + MenuButton, + MenuList, + MenuItem, + Button, + Text, + Flex, + Badge, + useColorModeValue +} from '@chakra-ui/react'; +import { ChevronDownIcon } from '@chakra-ui/icons'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useNavigationEvents } from '../../../../hooks/useNavigationEvents'; + +/** + * 桌面版主导航菜单组件 + * 大屏幕 (lg+) 显示,包含完整的下拉导航菜单 + * + * @param {Object} props + * @param {boolean} props.isAuthenticated - 用户是否已登录 + * @param {Object} props.user - 用户信息 + */ +const DesktopNav = memo(({ isAuthenticated, user }) => { + const navigate = useNavigate(); + const location = useLocation(); + + // ⚠️ 必须在组件顶层调用所有Hooks(不能在JSX中调用) + const contactTextColor = useColorModeValue('gray.500', 'gray.300'); + + // 🎯 初始化导航埋点Hook + const navEvents = useNavigationEvents({ component: 'top_nav' }); + + // 辅助函数:判断导航项是否激活 + const isActive = useCallback((paths) => { + return paths.some(path => location.pathname.includes(path)); + }, [location.pathname]); + + if (!isAuthenticated || !user) return null; + + return ( + + {/* 高频跟踪 */} + + } + bg={isActive(['/community', '/concepts']) ? 'blue.50' : 'transparent'} + color={isActive(['/community', '/concepts']) ? 'blue.600' : 'inherit'} + fontWeight={isActive(['/community', '/concepts']) ? 'bold' : 'normal'} + borderBottom={isActive(['/community', '/concepts']) ? '2px solid' : 'none'} + borderColor="blue.600" + _hover={{ bg: isActive(['/community', '/concepts']) ? 'blue.100' : 'gray.50' }} + > + 高频跟踪 + + + { + // 🎯 追踪菜单项点击 + navEvents.trackMenuItemClicked('事件中心', 'dropdown', '/community'); + navigate('/community'); + }} + borderRadius="md" + bg={location.pathname.includes('/community') ? 'blue.50' : 'transparent'} + borderLeft={location.pathname.includes('/community') ? '3px solid' : 'none'} + borderColor="blue.600" + fontWeight={location.pathname.includes('/community') ? 'bold' : 'normal'} + > + + 事件中心 + + HOT + NEW + + + + { + // 🎯 追踪菜单项点击 + navEvents.trackMenuItemClicked('概念中心', 'dropdown', '/concepts'); + navigate('/concepts'); + }} + borderRadius="md" + bg={location.pathname.includes('/concepts') ? 'blue.50' : 'transparent'} + borderLeft={location.pathname.includes('/concepts') ? '3px solid' : 'none'} + borderColor="blue.600" + fontWeight={location.pathname.includes('/concepts') ? 'bold' : 'normal'} + > + + 概念中心 + NEW + + + + + + {/* 行情复盘 */} + + } + bg={isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? 'blue.50' : 'transparent'} + color={isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? 'blue.600' : 'inherit'} + fontWeight={isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? 'bold' : 'normal'} + borderBottom={isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? '2px solid' : 'none'} + borderColor="blue.600" + _hover={{ bg: isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? 'blue.100' : 'gray.50' }} + > + 行情复盘 + + + navigate('/limit-analyse')} + borderRadius="md" + bg={location.pathname.includes('/limit-analyse') ? 'blue.50' : 'transparent'} + borderLeft={location.pathname.includes('/limit-analyse') ? '3px solid' : 'none'} + borderColor="blue.600" + fontWeight={location.pathname.includes('/limit-analyse') ? 'bold' : 'normal'} + > + + 涨停分析 + FREE + + + navigate('/stocks')} + borderRadius="md" + bg={location.pathname.includes('/stocks') ? 'blue.50' : 'transparent'} + borderLeft={location.pathname.includes('/stocks') ? '3px solid' : 'none'} + borderColor="blue.600" + fontWeight={location.pathname.includes('/stocks') ? 'bold' : 'normal'} + > + + 个股中心 + HOT + + + navigate('/trading-simulation')} + borderRadius="md" + bg={location.pathname.includes('/trading-simulation') ? 'blue.50' : 'transparent'} + borderLeft={location.pathname.includes('/trading-simulation') ? '3px solid' : 'none'} + borderColor="blue.600" + fontWeight={location.pathname.includes('/trading-simulation') ? 'bold' : 'normal'} + > + + 模拟盘 + NEW + + + + + + {/* AGENT社群 */} + + }> + AGENT社群 + + + + 今日热议 + + + 个股社区 + + + + + {/* 联系我们 */} + + }> + 联系我们 + + + 敬请期待 + + + + ); +}); + +DesktopNav.displayName = 'DesktopNav'; + +export default DesktopNav; diff --git a/src/components/Navbars/components/Navigation/MoreMenu.js b/src/components/Navbars/components/Navigation/MoreMenu.js new file mode 100644 index 00000000..b6b24ed3 --- /dev/null +++ b/src/components/Navbars/components/Navigation/MoreMenu.js @@ -0,0 +1,135 @@ +// src/components/Navbars/components/Navigation/MoreMenu.js +// 平板版"更多"下拉菜单 - 包含所有导航项 + +import React, { memo, useCallback } from 'react'; +import { + Menu, + MenuButton, + MenuList, + MenuItem, + MenuDivider, + Button, + Text, + Flex, + HStack, + Badge +} from '@chakra-ui/react'; +import { ChevronDownIcon } from '@chakra-ui/icons'; +import { useNavigate, useLocation } from 'react-router-dom'; + +/** + * 平板版"更多"下拉菜单组件 + * 中屏幕 (sm-md) 显示,包含所有导航项的折叠菜单 + * + * @param {Object} props + * @param {boolean} props.isAuthenticated - 用户是否已登录 + * @param {Object} props.user - 用户信息 + */ +const MoreMenu = memo(({ isAuthenticated, user }) => { + const navigate = useNavigate(); + const location = useLocation(); + + // 辅助函数:判断导航项是否激活 + const isActive = useCallback((paths) => { + return paths.some(path => location.pathname.includes(path)); + }, [location.pathname]); + + if (!isAuthenticated || !user) return null; + + return ( + + } + fontWeight="medium" + > + 更多 + + + {/* 高频跟踪组 */} + 高频跟踪 + navigate('/community')} + borderRadius="md" + bg={location.pathname.includes('/community') ? 'blue.50' : 'transparent'} + > + + 事件中心 + + HOT + NEW + + + + navigate('/concepts')} + borderRadius="md" + bg={location.pathname.includes('/concepts') ? 'blue.50' : 'transparent'} + > + + 概念中心 + NEW + + + + + + {/* 行情复盘组 */} + 行情复盘 + navigate('/limit-analyse')} + borderRadius="md" + bg={location.pathname.includes('/limit-analyse') ? 'blue.50' : 'transparent'} + > + + 涨停分析 + FREE + + + navigate('/stocks')} + borderRadius="md" + bg={location.pathname.includes('/stocks') ? 'blue.50' : 'transparent'} + > + + 个股中心 + HOT + + + navigate('/trading-simulation')} + borderRadius="md" + bg={location.pathname.includes('/trading-simulation') ? 'blue.50' : 'transparent'} + > + + 模拟盘 + NEW + + + + + + {/* AGENT社群组 */} + AGENT社群 + + 今日热议 + + + 个股社区 + + + + + {/* 联系我们 */} + + 联系我们 + + + + ); +}); + +MoreMenu.displayName = 'MoreMenu'; + +export default MoreMenu; diff --git a/src/components/Navbars/components/Navigation/PersonalCenterMenu.js b/src/components/Navbars/components/Navigation/PersonalCenterMenu.js new file mode 100644 index 00000000..d31382fa --- /dev/null +++ b/src/components/Navbars/components/Navigation/PersonalCenterMenu.js @@ -0,0 +1,102 @@ +// src/components/Navbars/components/Navigation/PersonalCenterMenu.js +// 个人中心下拉菜单 - 仅桌面版显示 + +import React, { memo } from 'react'; +import { + Menu, + MenuButton, + MenuList, + MenuItem, + MenuDivider, + Button, + Box, + Text, + Badge, + useColorModeValue +} from '@chakra-ui/react'; +import { ChevronDownIcon } from '@chakra-ui/icons'; +import { FiHome, FiUser, FiSettings, FiLogOut } from 'react-icons/fi'; +import { FaCrown } from 'react-icons/fa'; +import { useNavigate } from 'react-router-dom'; + +/** + * 个人中心下拉菜单组件 + * 仅在桌面版 (lg+) 显示 + * + * @param {Object} props + * @param {Object} props.user - 用户信息 + * @param {Function} props.handleLogout - 退出登录回调 + */ +const PersonalCenterMenu = memo(({ user, handleLogout }) => { + const navigate = useNavigate(); + const hoverBg = useColorModeValue('gray.100', 'gray.700'); + + // 获取显示名称 + const getDisplayName = () => { + if (user.nickname) return user.nickname; + if (user.username) return user.username; + if (user.email) return user.email.split('@')[0]; + if (user.phone) return user.phone; + return '用户'; + }; + + return ( + + } + _hover={{ bg: hoverBg }} + > + 个人中心 + + + {/* 用户信息区 */} + + {getDisplayName()} + {user.email} + {user.phone && ( + {user.phone} + )} + {user.has_wechat && ( + 微信已绑定 + )} + + + {/* 前往个人中心 */} + } onClick={() => navigate('/home/center')}> + 前往个人中心 + + + + + {/* 账户管理组 */} + } onClick={() => navigate('/home/profile')}> + 个人资料 + + } onClick={() => navigate('/home/settings')}> + 账户设置 + + + + + {/* 功能入口组 */} + } onClick={() => navigate('/home/pages/account/subscription')}> + 订阅管理 + + + + + {/* 退出 */} + } onClick={handleLogout} color="red.500"> + 退出登录 + + + + ); +}); + +PersonalCenterMenu.displayName = 'PersonalCenterMenu'; + +export default PersonalCenterMenu; diff --git a/src/components/Navbars/components/Navigation/index.js b/src/components/Navbars/components/Navigation/index.js new file mode 100644 index 00000000..d9e405d5 --- /dev/null +++ b/src/components/Navbars/components/Navigation/index.js @@ -0,0 +1,6 @@ +// src/components/Navbars/components/Navigation/index.js +// 导航组件统一导出 + +export { default as DesktopNav } from './DesktopNav'; +export { default as MoreMenu } from './MoreMenu'; +export { default as PersonalCenterMenu } from './PersonalCenterMenu';