From 56003039bd60274c7b514777c9513db956e6f90a Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Thu, 30 Oct 2025 18:27:55 +0800 Subject: [PATCH] =?UTF-8?q?fix(UserMenu):=20=E4=BF=AE=E5=A4=8D=20Phase=203?= =?UTF-8?q?=20=E9=87=8D=E6=9E=84=E5=BC=95=E5=85=A5=E7=9A=84=E5=A4=B4?= =?UTF-8?q?=E5=83=8F=20UI=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **问题描述** Phase 3 重构提取用户菜单组件时,引入了多个 UI 和交互问题: 1. ❌ 皇冠 UI 改变:右上角 FaCrown → 左上角 Emoji 2. ❌ Hover 效果消失:平板版头像无 hover 3. ❌ Tooltip 内容丢失:简化版内容 → 原始丰富内容 4. ❌ Tooltip 不显示:Chakra UI ref 传递问题 5. ⚠️ React 警告:forwardRef 缺失 **修复内容** ### 1. UserAvatar.js (101行 → 76行, -25行) **恢复原始皇冠设计**: - 删除自定义 CrownIcon(FaCrown + 渐变背景) - 改用 CrownTooltip.js 原始实现(👑/💎 Emoji) - 位置:右上角 → 左上角 - 交互:无 → 有 scale(1.2) hover **修复 Hover 效果**: ```diff - _hover={onClick ? { ...defaultHoverStyle, ...hoverStyle } : undefined} + _hover={{ ...defaultHoverStyle, ...hoverStyle }} ``` - 移除 onClick 依赖,头像始终可交互 **添加 forwardRef**: ```diff - const UserAvatar = memo(({ user, subscriptionInfo, ... }) => { + const UserAvatar = forwardRef(({ user, subscriptionInfo, ... }, ref) => { + return ``` - 支持 Tooltip 和 MenuButton 传递 ref - 消除 React 控制台警告 ### 2. DesktopUserMenu.js (93行 → 65行, -28行) **恢复原始 TooltipContent**: ```diff - const TooltipContent = memo(({ subscriptionInfo }) => { - return getSubscriptionBadgeText(); // 纯文本 - }); + import { TooltipContent } from '../../../Subscription/CrownTooltip'; ``` - 恢复丰富 UI:VStack + Divider + 状态图标 + 剩余天数 - 支持紧急提醒(< 7天)和警告(< 30天) **修复 Tooltip 显示**: ```diff + + ``` - 添加 span 包裹层确保 ref 和事件正确传递 - Chakra UI 官方推荐做法 **修复验证** - ✅ 桌面版:皇冠在左上角(👑/💎),Tooltip 显示丰富内容 - ✅ 平板版:头像有 hover 效果,下拉菜单正常 - ✅ 控制台:无 forwardRef 警告 **测试场景** 1. 免费用户:无皇冠,Tooltip 显示升级提示 2. Pro/Max 用户:显示皇冠,Tooltip 显示剩余天数 3. < 7天到期:红色紧急提示 4. 已过期:显示续费提示 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../components/UserMenu/DesktopUserMenu.js | 45 +++------------- .../Navbars/components/UserMenu/UserAvatar.js | 52 +++++-------------- 2 files changed, 21 insertions(+), 76 deletions(-) diff --git a/src/components/Navbars/components/UserMenu/DesktopUserMenu.js b/src/components/Navbars/components/UserMenu/DesktopUserMenu.js index ffd98434..7c9c1aee 100644 --- a/src/components/Navbars/components/UserMenu/DesktopUserMenu.js +++ b/src/components/Navbars/components/UserMenu/DesktopUserMenu.js @@ -5,40 +5,9 @@ import React, { memo } from 'react'; import { Tooltip, useColorModeValue } from '@chakra-ui/react'; import UserAvatar from './UserAvatar'; import SubscriptionModal from '../../../Subscription/SubscriptionModal'; +import { TooltipContent } from '../../../Subscription/CrownTooltip'; 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+) 显示,头像点击打开订阅弹窗 @@ -70,11 +39,13 @@ const DesktopUserMenu = memo(({ user }) => { boxShadow="lg" p={3} > - + + + {isSubscriptionModalOpen && ( diff --git a/src/components/Navbars/components/UserMenu/UserAvatar.js b/src/components/Navbars/components/UserMenu/UserAvatar.js index 45338ea3..0ac08e3d 100644 --- a/src/components/Navbars/components/UserMenu/UserAvatar.js +++ b/src/components/Navbars/components/UserMenu/UserAvatar.js @@ -1,41 +1,9 @@ // src/components/Navbars/components/UserMenu/UserAvatar.js // 用户头像组件 - 带皇冠图标和订阅边框 -import React, { memo } from 'react'; +import React, { memo, forwardRef } 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'; +import { CrownIcon } from '../../../Subscription/CrownTooltip'; /** * 用户头像组件 @@ -47,14 +15,15 @@ CrownIcon.displayName = 'CrownIcon'; * @param {string} props.size - 头像大小 (默认 'sm') * @param {Function} props.onClick - 点击回调 * @param {Object} props.hoverStyle - 悬停样式 + * @param {React.Ref} ref - 用于 Tooltip 和 MenuButton 的 ref */ -const UserAvatar = memo(({ +const UserAvatar = forwardRef(({ user, subscriptionInfo, size = 'sm', onClick, hoverStyle = {} -}) => { +}, ref) => { // 获取显示名称 const getDisplayName = () => { if (user.nickname) return user.nickname; @@ -71,7 +40,7 @@ const UserAvatar = memo(({ return 'transparent'; }; - // 默认悬停样式 + // 默认悬停样式 - 头像始终可交互(在 Tooltip 或 MenuButton 中) const defaultHoverStyle = { transform: 'scale(1.05)', boxShadow: subscriptionInfo.type !== 'free' @@ -80,7 +49,12 @@ const UserAvatar = memo(({ }; return ( - +