fix: 修复导航菜单 hover 触发实现方式
修复之前提交(47f84c5)中使用的无效 trigger="hover" 属性。
Chakra UI Menu 组件不支持 trigger 属性,改用正确的实现方式:
**实现方式:**
- 使用 useDisclosure Hook 管理菜单开关状态
- 为 MenuButton 和 MenuList 添加 onMouseEnter/onMouseLeave 事件
- 这样可以确保鼠标从按钮移到菜单列表时保持打开状态
**修改的组件:**
- DesktopNav.js: 为4个菜单添加独立的 useDisclosure Hook
- MoreMenu.js: 平板版"更多"菜单
- PersonalCenterMenu.js: 个人中心菜单
**技术要点:**
- MenuButton 和 MenuList 都需要 hover 事件处理
- 每个菜单使用独立的 useDisclosure 实例
- 符合 Chakra UI 官方推荐的 hover 菜单实现方式
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,8 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Flex,
|
Flex,
|
||||||
Badge,
|
Badge,
|
||||||
useColorModeValue
|
useColorModeValue,
|
||||||
|
useDisclosure
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
@@ -36,6 +37,12 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
|
|||||||
// 🎯 初始化导航埋点Hook
|
// 🎯 初始化导航埋点Hook
|
||||||
const navEvents = useNavigationEvents({ component: 'top_nav' });
|
const navEvents = useNavigationEvents({ component: 'top_nav' });
|
||||||
|
|
||||||
|
// 🎯 为每个菜单创建独立的 useDisclosure Hook
|
||||||
|
const { isOpen: isHighFreqOpen, onOpen: onHighFreqOpen, onClose: onHighFreqClose } = useDisclosure();
|
||||||
|
const { isOpen: isMarketReviewOpen, onOpen: onMarketReviewOpen, onClose: onMarketReviewClose } = useDisclosure();
|
||||||
|
const { isOpen: isAgentCommunityOpen, onOpen: onAgentCommunityOpen, onClose: onAgentCommunityClose } = useDisclosure();
|
||||||
|
const { isOpen: isContactUsOpen, onOpen: onContactUsOpen, onClose: onContactUsClose } = useDisclosure();
|
||||||
|
|
||||||
// 辅助函数:判断导航项是否激活
|
// 辅助函数:判断导航项是否激活
|
||||||
const isActive = useCallback((paths) => {
|
const isActive = useCallback((paths) => {
|
||||||
return paths.some(path => location.pathname.includes(path));
|
return paths.some(path => location.pathname.includes(path));
|
||||||
@@ -46,7 +53,7 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
|
|||||||
return (
|
return (
|
||||||
<HStack spacing={8}>
|
<HStack spacing={8}>
|
||||||
{/* 高频跟踪 */}
|
{/* 高频跟踪 */}
|
||||||
<Menu trigger="hover">
|
<Menu isOpen={isHighFreqOpen} onClose={onHighFreqClose}>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={Button}
|
as={Button}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -57,10 +64,12 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
|
|||||||
borderBottom={isActive(['/community', '/concepts']) ? '2px solid' : 'none'}
|
borderBottom={isActive(['/community', '/concepts']) ? '2px solid' : 'none'}
|
||||||
borderColor="blue.600"
|
borderColor="blue.600"
|
||||||
_hover={{ bg: isActive(['/community', '/concepts']) ? 'blue.100' : 'gray.50' }}
|
_hover={{ bg: isActive(['/community', '/concepts']) ? 'blue.100' : 'gray.50' }}
|
||||||
|
onMouseEnter={onHighFreqOpen}
|
||||||
|
onMouseLeave={onHighFreqClose}
|
||||||
>
|
>
|
||||||
高频跟踪
|
高频跟踪
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList minW="260px" p={2}>
|
<MenuList minW="260px" p={2} onMouseEnter={onHighFreqOpen} onMouseLeave={onHighFreqClose}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// 🎯 追踪菜单项点击
|
// 🎯 追踪菜单项点击
|
||||||
@@ -102,7 +111,7 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
|
|||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
{/* 行情复盘 */}
|
{/* 行情复盘 */}
|
||||||
<Menu trigger="hover">
|
<Menu isOpen={isMarketReviewOpen} onClose={onMarketReviewClose}>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={Button}
|
as={Button}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -113,10 +122,12 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
|
|||||||
borderBottom={isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? '2px solid' : 'none'}
|
borderBottom={isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? '2px solid' : 'none'}
|
||||||
borderColor="blue.600"
|
borderColor="blue.600"
|
||||||
_hover={{ bg: isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? 'blue.100' : 'gray.50' }}
|
_hover={{ bg: isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? 'blue.100' : 'gray.50' }}
|
||||||
|
onMouseEnter={onMarketReviewOpen}
|
||||||
|
onMouseLeave={onMarketReviewClose}
|
||||||
>
|
>
|
||||||
行情复盘
|
行情复盘
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList minW="260px" p={2}>
|
<MenuList minW="260px" p={2} onMouseEnter={onMarketReviewOpen} onMouseLeave={onMarketReviewClose}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => navigate('/limit-analyse')}
|
onClick={() => navigate('/limit-analyse')}
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
@@ -160,11 +171,17 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
|
|||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
{/* AGENT社群 */}
|
{/* AGENT社群 */}
|
||||||
<Menu trigger="hover">
|
<Menu isOpen={isAgentCommunityOpen} onClose={onAgentCommunityClose}>
|
||||||
<MenuButton as={Button} variant="ghost" rightIcon={<ChevronDownIcon />}>
|
<MenuButton
|
||||||
|
as={Button}
|
||||||
|
variant="ghost"
|
||||||
|
rightIcon={<ChevronDownIcon />}
|
||||||
|
onMouseEnter={onAgentCommunityOpen}
|
||||||
|
onMouseLeave={onAgentCommunityClose}
|
||||||
|
>
|
||||||
AGENT社群
|
AGENT社群
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList minW="300px" p={4}>
|
<MenuList minW="300px" p={4} onMouseEnter={onAgentCommunityOpen} onMouseLeave={onAgentCommunityClose}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
isDisabled
|
isDisabled
|
||||||
cursor="not-allowed"
|
cursor="not-allowed"
|
||||||
@@ -183,11 +200,17 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
|
|||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
{/* 联系我们 */}
|
{/* 联系我们 */}
|
||||||
<Menu trigger="hover">
|
<Menu isOpen={isContactUsOpen} onClose={onContactUsClose}>
|
||||||
<MenuButton as={Button} variant="ghost" rightIcon={<ChevronDownIcon />}>
|
<MenuButton
|
||||||
|
as={Button}
|
||||||
|
variant="ghost"
|
||||||
|
rightIcon={<ChevronDownIcon />}
|
||||||
|
onMouseEnter={onContactUsOpen}
|
||||||
|
onMouseLeave={onContactUsClose}
|
||||||
|
>
|
||||||
联系我们
|
联系我们
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList minW="260px" p={4}>
|
<MenuList minW="260px" p={4} onMouseEnter={onContactUsOpen} onMouseLeave={onContactUsClose}>
|
||||||
<Text fontSize="sm" color={contactTextColor}>敬请期待</Text>
|
<Text fontSize="sm" color={contactTextColor}>敬请期待</Text>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Flex,
|
Flex,
|
||||||
HStack,
|
HStack,
|
||||||
Badge
|
Badge,
|
||||||
|
useDisclosure
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
@@ -29,6 +30,9 @@ const MoreMenu = memo(({ isAuthenticated, user }) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
|
// 🎯 为"更多"菜单创建 useDisclosure Hook
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
// 辅助函数:判断导航项是否激活
|
// 辅助函数:判断导航项是否激活
|
||||||
const isActive = useCallback((paths) => {
|
const isActive = useCallback((paths) => {
|
||||||
return paths.some(path => location.pathname.includes(path));
|
return paths.some(path => location.pathname.includes(path));
|
||||||
@@ -37,16 +41,18 @@ const MoreMenu = memo(({ isAuthenticated, user }) => {
|
|||||||
if (!isAuthenticated || !user) return null;
|
if (!isAuthenticated || !user) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu trigger="hover">
|
<Menu isOpen={isOpen} onClose={onClose}>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={Button}
|
as={Button}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
rightIcon={<ChevronDownIcon />}
|
rightIcon={<ChevronDownIcon />}
|
||||||
fontWeight="medium"
|
fontWeight="medium"
|
||||||
|
onMouseEnter={onOpen}
|
||||||
|
onMouseLeave={onClose}
|
||||||
>
|
>
|
||||||
更多
|
更多
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList minW="300px" p={2}>
|
<MenuList minW="300px" p={2} onMouseEnter={onOpen} onMouseLeave={onClose}>
|
||||||
{/* 高频跟踪组 */}
|
{/* 高频跟踪组 */}
|
||||||
<Text fontSize="xs" fontWeight="bold" px={3} py={2} color="gray.500">高频跟踪</Text>
|
<Text fontSize="xs" fontWeight="bold" px={3} py={2} color="gray.500">高频跟踪</Text>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Text,
|
Text,
|
||||||
Badge,
|
Badge,
|
||||||
useColorModeValue
|
useColorModeValue,
|
||||||
|
useDisclosure
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||||
import { FiHome, FiUser, FiSettings, FiLogOut } from 'react-icons/fi';
|
import { FiHome, FiUser, FiSettings, FiLogOut } from 'react-icons/fi';
|
||||||
@@ -31,6 +32,9 @@ const PersonalCenterMenu = memo(({ user, handleLogout }) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const hoverBg = useColorModeValue('gray.100', 'gray.700');
|
const hoverBg = useColorModeValue('gray.100', 'gray.700');
|
||||||
|
|
||||||
|
// 🎯 为个人中心菜单创建 useDisclosure Hook
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
// 获取显示名称
|
// 获取显示名称
|
||||||
const getDisplayName = () => {
|
const getDisplayName = () => {
|
||||||
if (user.nickname) return user.nickname;
|
if (user.nickname) return user.nickname;
|
||||||
@@ -41,17 +45,19 @@ const PersonalCenterMenu = memo(({ user, handleLogout }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu trigger="hover">
|
<Menu isOpen={isOpen} onClose={onClose}>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={Button}
|
as={Button}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
rightIcon={<ChevronDownIcon />}
|
rightIcon={<ChevronDownIcon />}
|
||||||
_hover={{ bg: hoverBg }}
|
_hover={{ bg: hoverBg }}
|
||||||
|
onMouseEnter={onOpen}
|
||||||
|
onMouseLeave={onClose}
|
||||||
>
|
>
|
||||||
个人中心
|
个人中心
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList>
|
<MenuList onMouseEnter={onOpen} onMouseLeave={onClose}>
|
||||||
{/* 用户信息区 */}
|
{/* 用户信息区 */}
|
||||||
<Box px={3} py={2} borderBottom="1px" borderColor="gray.200">
|
<Box px={3} py={2} borderBottom="1px" borderColor="gray.200">
|
||||||
<Text fontSize="sm" fontWeight="bold">{getDisplayName()}</Text>
|
<Text fontSize="sm" fontWeight="bold">{getDisplayName()}</Text>
|
||||||
|
|||||||
Reference in New Issue
Block a user