refactor(navbar): 导航栏布局重构
- 新增 MySpaceButton 组件 - 删除 PersonalCenterMenu,功能合并到 DesktopUserMenu - 桌面端布局:[我的空间] | [头像][用户名]
This commit is contained in:
@@ -32,10 +32,14 @@ const CalendarButton = memo(() => {
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
variant="solid"
|
||||
borderRadius="full"
|
||||
variant="ghost"
|
||||
color="gray.700"
|
||||
fontWeight="medium"
|
||||
leftIcon={<Calendar size={16} />}
|
||||
_hover={{
|
||||
bg: 'gray.100',
|
||||
color: 'gray.900',
|
||||
}}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
>
|
||||
投资日历
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
// Navbar 右侧功能区组件
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { HStack, IconButton, Box } from '@chakra-ui/react';
|
||||
import { HStack, IconButton, Box, Divider } from '@chakra-ui/react';
|
||||
import { Menu } from 'lucide-react';
|
||||
// import ThemeToggleButton from '../ThemeToggleButton'; // ❌ 已删除 - 不再支持深色模式切换
|
||||
import LoginButton from '../LoginButton';
|
||||
import CalendarButton from '../CalendarButton';
|
||||
// import CalendarButton from '../CalendarButton'; // 暂时注释
|
||||
import { DesktopUserMenu, TabletUserMenu } from '../UserMenu';
|
||||
import { PersonalCenterMenu, MoreMenu } from '../Navigation';
|
||||
import { MySpaceButton, MoreMenu } from '../Navigation';
|
||||
|
||||
/**
|
||||
* Navbar 右侧功能区组件
|
||||
@@ -48,35 +47,38 @@ const NavbarActions = memo(({
|
||||
) : isAuthenticated && user ? (
|
||||
// 已登录状态 - 用户菜单 + 功能菜单排列
|
||||
<HStack spacing={{ base: 2, md: 3 }}>
|
||||
{/* 投资日历 - 仅大屏显示 */}
|
||||
{isDesktop && <CalendarButton />}
|
||||
{/* 投资日历 - 暂时注释 */}
|
||||
{/* {isDesktop && <CalendarButton />} */}
|
||||
|
||||
{/* 头像区域 - 响应式 */}
|
||||
{/* 桌面端布局:[我的空间] | [头像][用户名] */}
|
||||
{isDesktop ? (
|
||||
<DesktopUserMenu user={user} />
|
||||
) : (
|
||||
<TabletUserMenu
|
||||
user={user}
|
||||
handleLogout={handleLogout}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 头像右侧的菜单 - 响应式(互斥逻辑,确保只渲染一个) */}
|
||||
{isDesktop ? (
|
||||
// 桌面端:个人中心下拉菜单
|
||||
<PersonalCenterMenu user={user} handleLogout={handleLogout} />
|
||||
<>
|
||||
<MySpaceButton />
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
h="24px"
|
||||
borderColor="gray.300"
|
||||
/>
|
||||
<DesktopUserMenu user={user} handleLogout={handleLogout} />
|
||||
</>
|
||||
) : isTablet ? (
|
||||
// 平板端:MoreMenu 下拉菜单
|
||||
<MoreMenu isAuthenticated={isAuthenticated} user={user} />
|
||||
// 平板端:头像 + MoreMenu
|
||||
<>
|
||||
<TabletUserMenu user={user} handleLogout={handleLogout} />
|
||||
<MoreMenu isAuthenticated={isAuthenticated} user={user} />
|
||||
</>
|
||||
) : (
|
||||
// 移动端:汉堡菜单(打开抽屉)
|
||||
<IconButton
|
||||
icon={<Menu size={20} />}
|
||||
variant="ghost"
|
||||
onClick={onMenuOpen}
|
||||
aria-label="打开菜单"
|
||||
size="md"
|
||||
/>
|
||||
// 移动端:头像 + 汉堡菜单
|
||||
<>
|
||||
<TabletUserMenu user={user} handleLogout={handleLogout} />
|
||||
<IconButton
|
||||
icon={<Menu size={20} />}
|
||||
variant="ghost"
|
||||
onClick={onMenuOpen}
|
||||
aria-label="打开菜单"
|
||||
size="md"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
) : (
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// src/components/Navbars/components/Navigation/MySpaceButton.js
|
||||
// 「我的空间」独立跳转按钮 - 点击直接跳转至个人中心
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* 「我的空间」独立按钮组件
|
||||
* 点击直接跳转至个人中心页面
|
||||
*
|
||||
* 黑金主题配色:
|
||||
* - 默认:金色文字 (#CC9C00)
|
||||
* - hover:浅金背景 (#FFF9E6) + 深金文字 (#997500)
|
||||
*/
|
||||
const MySpaceButton = memo(() => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
color="gray.700"
|
||||
fontWeight="medium"
|
||||
_hover={{
|
||||
bg: 'gray.100',
|
||||
color: 'gray.900',
|
||||
}}
|
||||
onClick={() => navigate('/home/center')}
|
||||
>
|
||||
我的主页
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
MySpaceButton.displayName = 'MySpaceButton';
|
||||
|
||||
export default MySpaceButton;
|
||||
@@ -1,116 +0,0 @@
|
||||
// src/components/Navbars/components/Navigation/PersonalCenterMenu.js
|
||||
// 个人中心下拉菜单 - 仅桌面版显示
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Button,
|
||||
Box,
|
||||
Text,
|
||||
Badge,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { ChevronDown, Home, User, Settings, LogOut, Crown } from 'lucide-react';
|
||||
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();
|
||||
|
||||
// 🎯 为个人中心菜单创建 useDisclosure Hook
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
// 获取显示名称
|
||||
const getDisplayName = () => {
|
||||
if (user.nickname) return user.nickname;
|
||||
if (user.username) return user.username;
|
||||
if (user.email) return user.email.split('@')[0];
|
||||
if (typeof user.phone === 'string' && user.phone) return user.phone;
|
||||
return '用户';
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu isOpen={isOpen} onClose={onClose}>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
variant="solid"
|
||||
borderRadius="full"
|
||||
rightIcon={<ChevronDown size={16} />}
|
||||
onMouseEnter={onOpen}
|
||||
onMouseLeave={onClose}
|
||||
>
|
||||
个人中心
|
||||
</MenuButton>
|
||||
<MenuList onMouseEnter={onOpen}>
|
||||
{/* 用户信息区 */}
|
||||
<Box px={3} py={2} borderBottom="1px" borderColor="gray.200">
|
||||
<Text fontSize="sm" fontWeight="bold">{getDisplayName()}</Text>
|
||||
{typeof user.phone === 'string' && user.phone && (
|
||||
<Text fontSize="xs" color="gray.500">{user.phone}</Text>
|
||||
)}
|
||||
{user.has_wechat && (
|
||||
<Badge size="sm" colorScheme="green" mt={1}>微信已绑定</Badge>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* 前往个人中心 */}
|
||||
<MenuItem icon={<Home size={16} />} onClick={() => {
|
||||
onClose(); // 先关闭菜单
|
||||
navigate('/home/center');
|
||||
}}>
|
||||
前往个人中心
|
||||
</MenuItem>
|
||||
|
||||
<MenuDivider />
|
||||
|
||||
{/* 账户管理组 */}
|
||||
<MenuItem icon={<User size={16} />} onClick={() => {
|
||||
onClose(); // 先关闭菜单
|
||||
navigate('/home/profile');
|
||||
}}>
|
||||
个人资料
|
||||
</MenuItem>
|
||||
<MenuItem icon={<Settings size={16} />} onClick={() => {
|
||||
onClose(); // 先关闭菜单
|
||||
navigate('/home/settings');
|
||||
}}>
|
||||
账户设置
|
||||
</MenuItem>
|
||||
|
||||
<MenuDivider />
|
||||
|
||||
{/* 功能入口组 */}
|
||||
<MenuItem icon={<Crown size={16} />} onClick={() => {
|
||||
onClose(); // 先关闭菜单
|
||||
navigate('/home/pages/account/subscription');
|
||||
}}>
|
||||
订阅管理
|
||||
</MenuItem>
|
||||
|
||||
<MenuDivider />
|
||||
|
||||
{/* 退出 */}
|
||||
<MenuItem icon={<LogOut size={16} />} onClick={handleLogout} color="red.500">
|
||||
退出登录
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
PersonalCenterMenu.displayName = 'PersonalCenterMenu';
|
||||
|
||||
export default PersonalCenterMenu;
|
||||
@@ -3,4 +3,5 @@
|
||||
|
||||
export { default as DesktopNav } from './DesktopNav';
|
||||
export { default as MoreMenu } from './MoreMenu';
|
||||
export { default as PersonalCenterMenu } from './PersonalCenterMenu';
|
||||
export { default as MySpaceButton } from './MySpaceButton';
|
||||
// PersonalCenterMenu 已废弃,功能合并到 DesktopUserMenu
|
||||
|
||||
@@ -1,71 +1,234 @@
|
||||
// src/components/Navbars/components/UserMenu/DesktopUserMenu.js
|
||||
// 桌面版用户菜单 - 头像点击跳转到订阅页面
|
||||
// 桌面版用户菜单 - 头像+用户名组合,点击展开综合下拉面板
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverArrow,
|
||||
useColorModeValue
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Box,
|
||||
HStack,
|
||||
VStack,
|
||||
Text,
|
||||
Button,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { Settings, LogOut } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import UserAvatar from './UserAvatar';
|
||||
import { TooltipContent } from '../../../Subscription/CrownTooltip';
|
||||
import { useSubscription } from '../../../../hooks/useSubscription';
|
||||
|
||||
/**
|
||||
* 桌面版用户菜单组件
|
||||
* 大屏幕 (md+) 显示,头像点击跳转到订阅页面
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Object} props.user - 用户信息
|
||||
* 会员权益条组件
|
||||
* 金色渐变背景,单行显示会员类型和到期时间
|
||||
*/
|
||||
const DesktopUserMenu = memo(({ user }) => {
|
||||
const MembershipBar = memo(({ subscriptionInfo, onClose }) => {
|
||||
const navigate = useNavigate();
|
||||
const { subscriptionInfo } = useSubscription();
|
||||
const { type, days_left } = subscriptionInfo;
|
||||
|
||||
const popoverBg = useColorModeValue('white', 'gray.800');
|
||||
const popoverBorderColor = useColorModeValue('gray.200', 'gray.600');
|
||||
const getMemberText = () => {
|
||||
if (type === 'free') return '基础版';
|
||||
if (type === 'pro') return 'Pro会员';
|
||||
return 'Max会员';
|
||||
};
|
||||
|
||||
const handleAvatarClick = () => {
|
||||
const getMemberIcon = () => {
|
||||
if (type === 'free') return '✨';
|
||||
if (type === 'pro') return '💎';
|
||||
return '👑';
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
onClose();
|
||||
navigate('/home/pages/account/subscription');
|
||||
};
|
||||
|
||||
// 金色渐变背景
|
||||
const gradientBg = type === 'free'
|
||||
? 'linear(to-r, gray.100, gray.200)'
|
||||
: 'linear(to-r, #F6E5A3, #D4AF37)';
|
||||
|
||||
return (
|
||||
<Popover
|
||||
trigger="hover"
|
||||
placement="bottom-end"
|
||||
openDelay={100}
|
||||
closeDelay={200}
|
||||
gutter={8}
|
||||
<Box
|
||||
px={4}
|
||||
py={2.5}
|
||||
bgGradient={gradientBg}
|
||||
cursor="pointer"
|
||||
onClick={handleClick}
|
||||
_hover={{ opacity: 0.9 }}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<span>
|
||||
<HStack justify="space-between">
|
||||
<HStack spacing={1}>
|
||||
<Text fontSize="sm">{getMemberIcon()}</Text>
|
||||
<Text fontSize="sm" fontWeight="600" color={type === 'free' ? 'gray.700' : 'gray.800'}>
|
||||
{getMemberText()}
|
||||
{type !== 'free' && (
|
||||
<Text as="span" fontWeight="normal" color="gray.700">
|
||||
{' '}· {days_left}天后到期
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</HStack>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="outline"
|
||||
borderColor={type === 'free' ? 'gray.400' : 'gray.700'}
|
||||
color={type === 'free' ? 'gray.600' : 'gray.800'}
|
||||
bg="transparent"
|
||||
_hover={{ bg: 'whiteAlpha.500' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleClick();
|
||||
}}
|
||||
>
|
||||
{type === 'free' ? '升级会员' : '管理订阅'}
|
||||
</Button>
|
||||
</HStack>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
MembershipBar.displayName = 'MembershipBar';
|
||||
|
||||
/**
|
||||
* 桌面版用户菜单组件
|
||||
* 头像+用户名组合(去掉箭头),点击展开综合下拉面板
|
||||
*
|
||||
* 布局: [头像][用户名]
|
||||
* 交互: hover 时显示浅色圆角背景,点击展开面板
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Object} props.user - 用户信息
|
||||
* @param {Function} props.handleLogout - 退出登录回调
|
||||
*/
|
||||
const DesktopUserMenu = memo(({ user, handleLogout }) => {
|
||||
const navigate = useNavigate();
|
||||
const { subscriptionInfo } = useSubscription();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
// 获取显示名称(含手机号脱敏逻辑)
|
||||
const getDisplayName = () => {
|
||||
// 1. 优先显示昵称
|
||||
if (user.nickname) return user.nickname;
|
||||
// 2. 其次显示用户名
|
||||
if (user.username) return user.username;
|
||||
// 3. 手机号脱敏
|
||||
if (typeof user.phone === 'string' && user.phone) {
|
||||
return user.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
|
||||
}
|
||||
// 4. 默认显示
|
||||
return '股票新用户';
|
||||
};
|
||||
|
||||
// 跳转到我的空间
|
||||
const handleNavigateToMySpace = () => {
|
||||
onClose();
|
||||
navigate('/home/center');
|
||||
};
|
||||
|
||||
// 跳转到账户设置
|
||||
const handleNavigateToSettings = () => {
|
||||
onClose();
|
||||
navigate('/home/settings');
|
||||
};
|
||||
|
||||
// 退出登录
|
||||
const handleLogoutClick = () => {
|
||||
onClose();
|
||||
handleLogout();
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu isOpen={isOpen} onClose={onClose}>
|
||||
<MenuButton
|
||||
as={Box}
|
||||
px={2}
|
||||
py={1}
|
||||
borderRadius="full"
|
||||
cursor="pointer"
|
||||
onClick={onOpen}
|
||||
transition="all 0.2s"
|
||||
_hover={{
|
||||
bg: 'rgba(0, 0, 0, 0.05)',
|
||||
}}
|
||||
>
|
||||
{/* 使用 HStack 明确实现水平布局 */}
|
||||
<HStack spacing={2}>
|
||||
<UserAvatar
|
||||
user={user}
|
||||
subscriptionInfo={subscriptionInfo}
|
||||
onClick={handleAvatarClick}
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
bg={popoverBg}
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor={popoverBorderColor}
|
||||
boxShadow="lg"
|
||||
p={3}
|
||||
w="auto"
|
||||
_focus={{ outline: 'none' }}
|
||||
>
|
||||
<PopoverArrow bg={popoverBg} />
|
||||
<TooltipContent
|
||||
subscriptionInfo={subscriptionInfo}
|
||||
onNavigate={handleAvatarClick}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight="medium"
|
||||
maxW="80px"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
color="gray.700"
|
||||
>
|
||||
{getDisplayName()}
|
||||
</Text>
|
||||
</HStack>
|
||||
</MenuButton>
|
||||
|
||||
<MenuList minW="280px" py={0} overflow="hidden" borderRadius="lg">
|
||||
{/* 顶部:用户信息区 - 深色背景 + 头像 + 用户名 */}
|
||||
<Box
|
||||
px={4}
|
||||
py={4}
|
||||
bg="gray.800"
|
||||
cursor="pointer"
|
||||
_hover={{ bg: 'gray.700' }}
|
||||
onClick={handleNavigateToMySpace}
|
||||
>
|
||||
<HStack spacing={3}>
|
||||
<UserAvatar
|
||||
user={user}
|
||||
subscriptionInfo={subscriptionInfo}
|
||||
size="md"
|
||||
/>
|
||||
<VStack align="start" spacing={0}>
|
||||
<Text fontWeight="bold" fontSize="md" color="white">
|
||||
{getDisplayName()}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="gray.400">
|
||||
ID: {user.phone || user.id || '---'}
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
{/* 会员权益条 - 金色渐变背景 */}
|
||||
<MembershipBar subscriptionInfo={subscriptionInfo} onClose={onClose} />
|
||||
|
||||
<MenuDivider my={0} />
|
||||
|
||||
{/* 列表区:快捷功能 */}
|
||||
<MenuItem
|
||||
icon={<Settings size={16} />}
|
||||
onClick={handleNavigateToSettings}
|
||||
py={3}
|
||||
>
|
||||
账户设置
|
||||
</MenuItem>
|
||||
|
||||
<MenuDivider my={0} />
|
||||
|
||||
{/* 底部:退出登录 */}
|
||||
<MenuItem
|
||||
icon={<LogOut size={16} />}
|
||||
color="red.500"
|
||||
onClick={handleLogoutClick}
|
||||
py={3}
|
||||
>
|
||||
退出登录
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user