diff --git a/src/components/Navbars/HomeNavbar.js b/src/components/Navbars/HomeNavbar.js index f9f22d5a..b86e68f7 100644 --- a/src/components/Navbars/HomeNavbar.js +++ b/src/components/Navbars/HomeNavbar.js @@ -31,7 +31,6 @@ import { useColorMode, useColorModeValue, useToast, - Tooltip, Modal, ModalOverlay, ModalContent, @@ -48,8 +47,6 @@ import { useAuthModal } from '../../hooks/useAuthModal'; import { logger } from '../../utils/logger'; import { getApiBase } from '../../utils/apiConfig'; import SubscriptionButton from '../Subscription/SubscriptionButton'; -import SubscriptionModal from '../Subscription/SubscriptionModal'; -import { CrownIcon, TooltipContent } from '../Subscription/CrownTooltip'; import { useNavigationEvents } from '../../hooks/useNavigationEvents'; // Phase 1 优化: 提取的子组件 @@ -60,6 +57,9 @@ import CalendarButton from './components/CalendarButton'; // Phase 2 优化: 使用 Redux 管理订阅数据 import { useSubscription } from '../../hooks/useSubscription'; +// Phase 3 优化: 提取的用户菜单组件 +import { DesktopUserMenu, TabletUserMenu } from './components/UserMenu'; + /** 二级导航栏组件 - 显示当前一级菜单下的所有二级菜单项 */ const SecondaryNav = ({ showCompletenessAlert }) => { const navigate = useNavigate(); @@ -1069,156 +1069,16 @@ export default function HomeNavbar() { )} - {/* 头像区域 - 响应式 */} + {/* 头像区域 - 响应式 (Phase 3 优化) */} {isDesktop ? ( - // 大屏:头像点击打开订阅弹窗 - <> - } - placement="bottom" - hasArrow - bg={useColorModeValue('white', 'gray.800')} - borderRadius="lg" - border="1px solid" - borderColor={useColorModeValue('gray.200', 'gray.600')} - boxShadow="lg" - p={3} - > - - - - - - {isSubscriptionModalOpen && ( - - )} - + ) : ( - // 中屏:头像作为下拉菜单,包含所有功能 - - - - - - - - - {/* 用户信息区 */} - - {getDisplayName()} - {user.email} - {user.phone && ( - {user.phone} - )} - {user.has_wechat && ( - 微信已绑定 - )} - - - {/* 订阅管理 */} - } onClick={openSubscriptionModal}> - - 订阅管理 - - {subscriptionInfo.type === 'max' ? 'MAX' : - subscriptionInfo.type === 'pro' ? 'PRO' : '免费版'} - - - - - {isSubscriptionModalOpen && ( - - )} - - - - {/* 投资日历 */} - } onClick={() => navigate('/community')}> - 投资日历 - - - {/* 自选股 */} - } onClick={() => navigate('/home/center')}> - - 我的自选股 - {watchlistQuotes && watchlistQuotes.length > 0 && ( - {watchlistQuotes.length} - )} - - - - {/* 自选事件 */} - } onClick={() => navigate('/home/center')}> - - 我的自选事件 - {followingEvents && followingEvents.length > 0 && ( - {followingEvents.length} - )} - - - - - - {/* 个人中心 */} - } onClick={() => navigate('/home/center')}> - 个人中心 - - } onClick={() => navigate('/home/profile')}> - 个人资料 - - } onClick={() => navigate('/home/settings')}> - 账户设置 - - - - - {/* 退出登录 */} - } onClick={handleLogout} color="red.500"> - 退出登录 - - - + )} {/* 个人中心下拉菜单 - 仅大屏显示 */} diff --git a/src/components/Navbars/components/UserMenu/DesktopUserMenu.js b/src/components/Navbars/components/UserMenu/DesktopUserMenu.js new file mode 100644 index 00000000..ffd98434 --- /dev/null +++ b/src/components/Navbars/components/UserMenu/DesktopUserMenu.js @@ -0,0 +1,93 @@ +// src/components/Navbars/components/UserMenu/DesktopUserMenu.js +// 桌面版用户菜单 - 头像 + Tooltip + 订阅弹窗 + +import React, { memo } from 'react'; +import { Tooltip, useColorModeValue } from '@chakra-ui/react'; +import UserAvatar from './UserAvatar'; +import SubscriptionModal from '../../../Subscription/SubscriptionModal'; +import { useSubscription } from '../../../../hooks/useSubscription'; + +/** + * Tooltip 内容组件 + * 显示用户订阅信息和剩余天数 + */ +const TooltipContent = memo(({ subscriptionInfo }) => { + const getSubscriptionBadgeText = () => { + if (!subscriptionInfo || !subscriptionInfo.type) { + return '免费版'; + } + + const type = subscriptionInfo.type.toLowerCase(); + + switch (type) { + case 'max': + return subscriptionInfo.is_active + ? `Max版 (剩余 ${subscriptionInfo.days_left || 0} 天)` + : 'Max版 (已过期)'; + case 'pro': + return subscriptionInfo.is_active + ? `Pro版 (剩余 ${subscriptionInfo.days_left || 0} 天)` + : 'Pro版 (已过期)'; + case 'free': + default: + return '免费版 (点击升级)'; + } + }; + + return getSubscriptionBadgeText(); +}); + +TooltipContent.displayName = 'TooltipContent'; + +/** + * 桌面版用户菜单组件 + * 大屏幕 (md+) 显示,头像点击打开订阅弹窗 + * + * @param {Object} props + * @param {Object} props.user - 用户信息 + */ +const DesktopUserMenu = memo(({ user }) => { + const { + subscriptionInfo, + isSubscriptionModalOpen, + openSubscriptionModal, + closeSubscriptionModal + } = useSubscription(); + + const tooltipBg = useColorModeValue('white', 'gray.800'); + const tooltipBorderColor = useColorModeValue('gray.200', 'gray.600'); + + return ( + <> + } + placement="bottom" + hasArrow + bg={tooltipBg} + borderRadius="lg" + border="1px solid" + borderColor={tooltipBorderColor} + boxShadow="lg" + p={3} + > + + + + {isSubscriptionModalOpen && ( + + )} + + ); +}); + +DesktopUserMenu.displayName = 'DesktopUserMenu'; + +export default DesktopUserMenu; diff --git a/src/components/Navbars/components/UserMenu/TabletUserMenu.js b/src/components/Navbars/components/UserMenu/TabletUserMenu.js new file mode 100644 index 00000000..9857138f --- /dev/null +++ b/src/components/Navbars/components/UserMenu/TabletUserMenu.js @@ -0,0 +1,166 @@ +// src/components/Navbars/components/UserMenu/TabletUserMenu.js +// 平板版用户菜单 - 头像作为下拉菜单,包含所有功能 + +import React, { memo } from 'react'; +import { + Menu, + MenuButton, + MenuList, + MenuItem, + MenuDivider, + Box, + Text, + Badge, + Flex, + useColorModeValue +} from '@chakra-ui/react'; +import { FiStar, FiCalendar, FiUser, FiSettings, FiHome, FiLogOut } from 'react-icons/fi'; +import { FaCrown } from 'react-icons/fa'; +import { useNavigate } from 'react-router-dom'; +import UserAvatar from './UserAvatar'; +import SubscriptionModal from '../../../Subscription/SubscriptionModal'; +import { useSubscription } from '../../../../hooks/useSubscription'; + +/** + * 平板版用户菜单组件 + * 中屏幕 (sm-md) 显示,头像作为下拉菜单,包含所有功能 + * + * @param {Object} props + * @param {Object} props.user - 用户信息 + * @param {Function} props.handleLogout - 退出登录回调 + * @param {Array} props.watchlistQuotes - 自选股列表 + * @param {Array} props.followingEvents - 自选事件列表 + */ +const TabletUserMenu = memo(({ + user, + handleLogout, + watchlistQuotes, + followingEvents +}) => { + const navigate = useNavigate(); + const { + subscriptionInfo, + isSubscriptionModalOpen, + openSubscriptionModal, + closeSubscriptionModal + } = useSubscription(); + + const borderColor = useColorModeValue('gray.200', 'gray.600'); + + // 获取显示名称 + 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 '用户'; + }; + + // 获取订阅标签 + const getSubscriptionBadge = () => { + if (subscriptionInfo.type === 'max') return 'MAX'; + if (subscriptionInfo.type === 'pro') return 'PRO'; + return '免费版'; + }; + + // 获取订阅标签颜色 + const getSubscriptionBadgeColor = () => { + return subscriptionInfo.type === 'free' ? 'gray' : 'purple'; + }; + + return ( + <> + + + + + + {/* 用户信息区 */} + + {getDisplayName()} + {user.email} + {user.phone && ( + {user.phone} + )} + {user.has_wechat && ( + 微信已绑定 + )} + + + {/* 订阅管理 */} + } onClick={openSubscriptionModal}> + + 订阅管理 + + {getSubscriptionBadge()} + + + + + + + {/* 投资日历 */} + } onClick={() => navigate('/community')}> + 投资日历 + + + {/* 自选股 */} + } onClick={() => navigate('/home/center')}> + + 我的自选股 + {watchlistQuotes && watchlistQuotes.length > 0 && ( + {watchlistQuotes.length} + )} + + + + {/* 自选事件 */} + } onClick={() => navigate('/home/center')}> + + 我的自选事件 + {followingEvents && followingEvents.length > 0 && ( + {followingEvents.length} + )} + + + + + + {/* 个人中心 */} + } onClick={() => navigate('/home/center')}> + 个人中心 + + } onClick={() => navigate('/home/profile')}> + 个人资料 + + } onClick={() => navigate('/home/settings')}> + 账户设置 + + + + + {/* 退出登录 */} + } onClick={handleLogout} color="red.500"> + 退出登录 + + + + + {/* 订阅弹窗 */} + {isSubscriptionModalOpen && ( + + )} + + ); +}); + +TabletUserMenu.displayName = 'TabletUserMenu'; + +export default TabletUserMenu; diff --git a/src/components/Navbars/components/UserMenu/UserAvatar.js b/src/components/Navbars/components/UserMenu/UserAvatar.js new file mode 100644 index 00000000..45338ea3 --- /dev/null +++ b/src/components/Navbars/components/UserMenu/UserAvatar.js @@ -0,0 +1,101 @@ +// src/components/Navbars/components/UserMenu/UserAvatar.js +// 用户头像组件 - 带皇冠图标和订阅边框 + +import React, { memo } from 'react'; +import { Box, Avatar } from '@chakra-ui/react'; +import { FaCrown } from 'react-icons/fa'; + +/** + * 皇冠图标组件 + * @param {Object} props.subscriptionInfo - 订阅信息 + */ +const CrownIcon = memo(({ subscriptionInfo }) => { + if (!subscriptionInfo || subscriptionInfo.type === 'free') { + return null; + } + + const crownColor = subscriptionInfo.type === 'max' + ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' + : '#667eea'; + + return ( + + + + ); +}); + +CrownIcon.displayName = 'CrownIcon'; + +/** + * 用户头像组件 + * 包含皇冠图标和订阅边框样式 + * + * @param {Object} props + * @param {Object} props.user - 用户信息 + * @param {Object} props.subscriptionInfo - 订阅信息 + * @param {string} props.size - 头像大小 (默认 'sm') + * @param {Function} props.onClick - 点击回调 + * @param {Object} props.hoverStyle - 悬停样式 + */ +const UserAvatar = memo(({ + user, + subscriptionInfo, + size = 'sm', + onClick, + hoverStyle = {} +}) => { + // 获取显示名称 + 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 '用户'; + }; + + // 边框颜色 + const getBorderColor = () => { + if (subscriptionInfo.type === 'max') return '#667eea'; + if (subscriptionInfo.type === 'pro') return '#667eea'; + return 'transparent'; + }; + + // 默认悬停样式 + const defaultHoverStyle = { + transform: 'scale(1.05)', + boxShadow: subscriptionInfo.type !== 'free' + ? '0 4px 12px rgba(102, 126, 234, 0.4)' + : 'md', + }; + + return ( + + + + + ); +}); + +UserAvatar.displayName = 'UserAvatar'; + +export default UserAvatar; diff --git a/src/components/Navbars/components/UserMenu/index.js b/src/components/Navbars/components/UserMenu/index.js new file mode 100644 index 00000000..51fe6956 --- /dev/null +++ b/src/components/Navbars/components/UserMenu/index.js @@ -0,0 +1,6 @@ +// src/components/Navbars/components/UserMenu/index.js +// 用户菜单组件统一导出 + +export { default as UserAvatar } from './UserAvatar'; +export { default as DesktopUserMenu } from './DesktopUserMenu'; +export { default as TabletUserMenu } from './TabletUserMenu';