fix(UserMenu): 修复 Phase 3 重构引入的头像 UI 问题
**问题描述** 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 <Box ref={ref} ...> ``` - 支持 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 <Tooltip ...> + <span> <UserAvatar ... /> + </span> </Tooltip> ``` - 添加 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 <noreply@anthropic.com>
This commit is contained in:
@@ -5,40 +5,9 @@ import React, { memo } from 'react';
|
|||||||
import { Tooltip, useColorModeValue } from '@chakra-ui/react';
|
import { Tooltip, useColorModeValue } from '@chakra-ui/react';
|
||||||
import UserAvatar from './UserAvatar';
|
import UserAvatar from './UserAvatar';
|
||||||
import SubscriptionModal from '../../../Subscription/SubscriptionModal';
|
import SubscriptionModal from '../../../Subscription/SubscriptionModal';
|
||||||
|
import { TooltipContent } from '../../../Subscription/CrownTooltip';
|
||||||
import { useSubscription } from '../../../../hooks/useSubscription';
|
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+) 显示,头像点击打开订阅弹窗
|
* 大屏幕 (md+) 显示,头像点击打开订阅弹窗
|
||||||
@@ -70,11 +39,13 @@ const DesktopUserMenu = memo(({ user }) => {
|
|||||||
boxShadow="lg"
|
boxShadow="lg"
|
||||||
p={3}
|
p={3}
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
user={user}
|
user={user}
|
||||||
subscriptionInfo={subscriptionInfo}
|
subscriptionInfo={subscriptionInfo}
|
||||||
onClick={openSubscriptionModal}
|
onClick={openSubscriptionModal}
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{isSubscriptionModalOpen && (
|
{isSubscriptionModalOpen && (
|
||||||
|
|||||||
@@ -1,41 +1,9 @@
|
|||||||
// src/components/Navbars/components/UserMenu/UserAvatar.js
|
// 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 { Box, Avatar } from '@chakra-ui/react';
|
||||||
import { FaCrown } from 'react-icons/fa';
|
import { CrownIcon } from '../../../Subscription/CrownTooltip';
|
||||||
|
|
||||||
/**
|
|
||||||
* 皇冠图标组件
|
|
||||||
* @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 (
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
top="-4px"
|
|
||||||
right="-4px"
|
|
||||||
zIndex={2}
|
|
||||||
fontSize="14px"
|
|
||||||
background={crownColor}
|
|
||||||
borderRadius="full"
|
|
||||||
p="3px"
|
|
||||||
boxShadow="0 2px 8px rgba(102, 126, 234, 0.4)"
|
|
||||||
>
|
|
||||||
<FaCrown color="white" />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
CrownIcon.displayName = 'CrownIcon';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户头像组件
|
* 用户头像组件
|
||||||
@@ -47,14 +15,15 @@ CrownIcon.displayName = 'CrownIcon';
|
|||||||
* @param {string} props.size - 头像大小 (默认 'sm')
|
* @param {string} props.size - 头像大小 (默认 'sm')
|
||||||
* @param {Function} props.onClick - 点击回调
|
* @param {Function} props.onClick - 点击回调
|
||||||
* @param {Object} props.hoverStyle - 悬停样式
|
* @param {Object} props.hoverStyle - 悬停样式
|
||||||
|
* @param {React.Ref} ref - 用于 Tooltip 和 MenuButton 的 ref
|
||||||
*/
|
*/
|
||||||
const UserAvatar = memo(({
|
const UserAvatar = forwardRef(({
|
||||||
user,
|
user,
|
||||||
subscriptionInfo,
|
subscriptionInfo,
|
||||||
size = 'sm',
|
size = 'sm',
|
||||||
onClick,
|
onClick,
|
||||||
hoverStyle = {}
|
hoverStyle = {}
|
||||||
}) => {
|
}, ref) => {
|
||||||
// 获取显示名称
|
// 获取显示名称
|
||||||
const getDisplayName = () => {
|
const getDisplayName = () => {
|
||||||
if (user.nickname) return user.nickname;
|
if (user.nickname) return user.nickname;
|
||||||
@@ -71,7 +40,7 @@ const UserAvatar = memo(({
|
|||||||
return 'transparent';
|
return 'transparent';
|
||||||
};
|
};
|
||||||
|
|
||||||
// 默认悬停样式
|
// 默认悬停样式 - 头像始终可交互(在 Tooltip 或 MenuButton 中)
|
||||||
const defaultHoverStyle = {
|
const defaultHoverStyle = {
|
||||||
transform: 'scale(1.05)',
|
transform: 'scale(1.05)',
|
||||||
boxShadow: subscriptionInfo.type !== 'free'
|
boxShadow: subscriptionInfo.type !== 'free'
|
||||||
@@ -80,7 +49,12 @@ const UserAvatar = memo(({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position="relative" cursor={onClick ? 'pointer' : 'default'} onClick={onClick}>
|
<Box
|
||||||
|
ref={ref}
|
||||||
|
position="relative"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
<CrownIcon subscriptionInfo={subscriptionInfo} />
|
<CrownIcon subscriptionInfo={subscriptionInfo} />
|
||||||
<Avatar
|
<Avatar
|
||||||
size={size}
|
size={size}
|
||||||
@@ -89,7 +63,7 @@ const UserAvatar = memo(({
|
|||||||
bg="blue.500"
|
bg="blue.500"
|
||||||
border={subscriptionInfo.type !== 'free' ? '2.5px solid' : 'none'}
|
border={subscriptionInfo.type !== 'free' ? '2.5px solid' : 'none'}
|
||||||
borderColor={getBorderColor()}
|
borderColor={getBorderColor()}
|
||||||
_hover={onClick ? { ...defaultHoverStyle, ...hoverStyle } : undefined}
|
_hover={{ ...defaultHoverStyle, ...hoverStyle }}
|
||||||
transition="all 0.2s"
|
transition="all 0.2s"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user