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 && (
-
- )}
- >
+
) : (
- // 中屏:头像作为下拉菜单,包含所有功能
-
+
)}
{/* 个人中心下拉菜单 - 仅大屏显示 */}
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 (
+ <>
+
+
+ {/* 订阅弹窗 */}
+ {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';