feat(GlobalSidebar): 收起状态添加 Popover 悬浮弹窗
- 收起状态点击图标显示悬浮弹窗,无需展开侧边栏 - 添加关注股票、关注事件、热门板块三个 Popover 面板 - 展开状态添加独立标题栏 [>] 工具栏 - 移除收起按钮的 Tooltip 提示 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
* GlobalSidebar - 全局右侧工具栏
|
* GlobalSidebar - 全局右侧工具栏
|
||||||
*
|
*
|
||||||
* 可收起/展开的侧边栏,包含关注股票和事件动态
|
* 可收起/展开的侧边栏,包含关注股票和事件动态
|
||||||
|
* 收起时点击图标显示悬浮弹窗
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -10,98 +11,231 @@ import {
|
|||||||
VStack,
|
VStack,
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
Tooltip,
|
|
||||||
Badge,
|
Badge,
|
||||||
Spinner,
|
Spinner,
|
||||||
Center,
|
Center,
|
||||||
|
Popover,
|
||||||
|
PopoverTrigger,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverHeader,
|
||||||
|
PopoverCloseButton,
|
||||||
|
Text,
|
||||||
|
HStack,
|
||||||
|
Portal,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ChevronLeft, ChevronRight, BarChart2, Star } from 'lucide-react';
|
import { ChevronLeft, ChevronRight, BarChart2, Star, TrendingUp } from 'lucide-react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useGlobalSidebar } from '@/contexts/GlobalSidebarContext';
|
import { useGlobalSidebar } from '@/contexts/GlobalSidebarContext';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
import { getEventDetailUrl } from '@/utils/idEncoder';
|
import { getEventDetailUrl } from '@/utils/idEncoder';
|
||||||
import WatchSidebar from '@views/Profile/components/WatchSidebar';
|
import WatchSidebar from '@views/Profile/components/WatchSidebar';
|
||||||
|
import { WatchlistPanel, FollowingEventsPanel } from '@views/Profile/components/WatchSidebar/components';
|
||||||
|
import HotSectorsRanking from '@views/Profile/components/MarketDashboard/components/atoms/HotSectorsRanking';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收起状态下的图标菜单
|
* 收起状态下的图标菜单(带悬浮弹窗)
|
||||||
*/
|
*/
|
||||||
const CollapsedMenu = ({ watchlist, followingEvents, onToggle }) => {
|
const CollapsedMenu = ({
|
||||||
|
watchlist,
|
||||||
|
realtimeQuotes,
|
||||||
|
followingEvents,
|
||||||
|
eventComments,
|
||||||
|
onToggle,
|
||||||
|
onStockClick,
|
||||||
|
onEventClick,
|
||||||
|
onCommentClick,
|
||||||
|
onAddStock,
|
||||||
|
onAddEvent,
|
||||||
|
onUnwatch,
|
||||||
|
onUnfollow,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<VStack spacing={4} py={4} align="center">
|
<VStack spacing={4} py={4} align="center">
|
||||||
{/* 展开按钮 */}
|
{/* 展开按钮 */}
|
||||||
<Tooltip label="展开工具栏" placement="left">
|
<HStack spacing={1} w="100%" justify="center" cursor="pointer" onClick={onToggle} _hover={{ bg: 'rgba(255, 255, 255, 0.05)' }} py={1} borderRadius="md">
|
||||||
<IconButton
|
<Icon as={ChevronLeft} boxSize={4} color="rgba(255, 255, 255, 0.6)" />
|
||||||
icon={<Icon as={ChevronLeft} />}
|
<Text fontSize="10px" color="rgba(255, 255, 255, 0.5)">
|
||||||
size="sm"
|
展开
|
||||||
variant="ghost"
|
</Text>
|
||||||
color="rgba(255, 255, 255, 0.6)"
|
</HStack>
|
||||||
_hover={{ color: 'rgba(212, 175, 55, 0.9)', bg: 'rgba(255, 255, 255, 0.05)' }}
|
|
||||||
onClick={onToggle}
|
|
||||||
aria-label="展开工具栏"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* 关注股票图标 */}
|
{/* 关注股票 - 悬浮弹窗 */}
|
||||||
<Tooltip label={`关注股票 (${watchlist.length})`} placement="left">
|
<Popover placement="left-start" trigger="click" isLazy>
|
||||||
<Box
|
<PopoverTrigger>
|
||||||
position="relative"
|
<VStack
|
||||||
cursor="pointer"
|
spacing={1}
|
||||||
p={2}
|
align="center"
|
||||||
borderRadius="md"
|
cursor="pointer"
|
||||||
_hover={{ bg: 'rgba(255, 255, 255, 0.05)' }}
|
p={2}
|
||||||
onClick={onToggle}
|
borderRadius="md"
|
||||||
>
|
_hover={{ bg: 'rgba(255, 255, 255, 0.05)' }}
|
||||||
<Icon as={BarChart2} boxSize={5} color="rgba(59, 130, 246, 0.9)" />
|
position="relative"
|
||||||
{watchlist.length > 0 && (
|
>
|
||||||
<Badge
|
<Box position="relative">
|
||||||
position="absolute"
|
<Icon as={BarChart2} boxSize={5} color="rgba(59, 130, 246, 0.9)" />
|
||||||
top="-2px"
|
{watchlist.length > 0 && (
|
||||||
right="-2px"
|
<Badge
|
||||||
colorScheme="red"
|
position="absolute"
|
||||||
fontSize="9px"
|
top="-4px"
|
||||||
minW="16px"
|
right="-8px"
|
||||||
h="16px"
|
colorScheme="red"
|
||||||
borderRadius="full"
|
fontSize="9px"
|
||||||
display="flex"
|
minW="16px"
|
||||||
alignItems="center"
|
h="16px"
|
||||||
justifyContent="center"
|
borderRadius="full"
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
{watchlist.length > 99 ? '99+' : watchlist.length}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Text fontSize="10px" color="rgba(255, 255, 255, 0.6)" whiteSpace="nowrap">
|
||||||
|
关注股票
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<Portal>
|
||||||
|
<PopoverContent
|
||||||
|
w="300px"
|
||||||
|
bg="rgba(26, 32, 44, 0.95)"
|
||||||
|
borderColor="rgba(255, 255, 255, 0.1)"
|
||||||
|
boxShadow="0 8px 32px rgba(0, 0, 0, 0.4)"
|
||||||
|
_focus={{ outline: 'none' }}
|
||||||
|
>
|
||||||
|
<PopoverHeader
|
||||||
|
borderBottomColor="rgba(255, 255, 255, 0.1)"
|
||||||
|
py={2}
|
||||||
|
px={3}
|
||||||
>
|
>
|
||||||
{watchlist.length > 99 ? '99+' : watchlist.length}
|
<HStack spacing={2}>
|
||||||
</Badge>
|
<Icon as={BarChart2} boxSize={4} color="rgba(59, 130, 246, 0.9)" />
|
||||||
)}
|
<Text fontSize="sm" fontWeight="bold" color="rgba(255, 255, 255, 0.9)">
|
||||||
</Box>
|
关注股票 ({watchlist.length})
|
||||||
</Tooltip>
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverCloseButton color="rgba(255, 255, 255, 0.5)" />
|
||||||
|
<PopoverBody p={2}>
|
||||||
|
<WatchlistPanel
|
||||||
|
watchlist={watchlist}
|
||||||
|
realtimeQuotes={realtimeQuotes}
|
||||||
|
onStockClick={onStockClick}
|
||||||
|
onAddStock={onAddStock}
|
||||||
|
onUnwatch={onUnwatch}
|
||||||
|
/>
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</Portal>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
{/* 事件动态图标 */}
|
{/* 事件动态 - 悬浮弹窗 */}
|
||||||
<Tooltip label={`事件动态 (${followingEvents.length})`} placement="left">
|
<Popover placement="left-start" trigger="click" isLazy>
|
||||||
<Box
|
<PopoverTrigger>
|
||||||
position="relative"
|
<VStack
|
||||||
cursor="pointer"
|
spacing={1}
|
||||||
p={2}
|
align="center"
|
||||||
borderRadius="md"
|
cursor="pointer"
|
||||||
_hover={{ bg: 'rgba(255, 255, 255, 0.05)' }}
|
p={2}
|
||||||
onClick={onToggle}
|
borderRadius="md"
|
||||||
>
|
_hover={{ bg: 'rgba(255, 255, 255, 0.05)' }}
|
||||||
<Icon as={Star} boxSize={5} color="rgba(234, 179, 8, 0.9)" />
|
position="relative"
|
||||||
{followingEvents.length > 0 && (
|
>
|
||||||
<Badge
|
<Box position="relative">
|
||||||
position="absolute"
|
<Icon as={Star} boxSize={5} color="rgba(234, 179, 8, 0.9)" />
|
||||||
top="-2px"
|
{followingEvents.length > 0 && (
|
||||||
right="-2px"
|
<Badge
|
||||||
colorScheme="yellow"
|
position="absolute"
|
||||||
fontSize="9px"
|
top="-4px"
|
||||||
minW="16px"
|
right="-8px"
|
||||||
h="16px"
|
colorScheme="yellow"
|
||||||
borderRadius="full"
|
fontSize="9px"
|
||||||
display="flex"
|
minW="16px"
|
||||||
alignItems="center"
|
h="16px"
|
||||||
justifyContent="center"
|
borderRadius="full"
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
{followingEvents.length > 99 ? '99+' : followingEvents.length}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Text fontSize="10px" color="rgba(255, 255, 255, 0.6)" whiteSpace="nowrap">
|
||||||
|
关注事件
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<Portal>
|
||||||
|
<PopoverContent
|
||||||
|
w="300px"
|
||||||
|
bg="rgba(26, 32, 44, 0.95)"
|
||||||
|
borderColor="rgba(255, 255, 255, 0.1)"
|
||||||
|
boxShadow="0 8px 32px rgba(0, 0, 0, 0.4)"
|
||||||
|
_focus={{ outline: 'none' }}
|
||||||
|
>
|
||||||
|
<PopoverHeader
|
||||||
|
borderBottomColor="rgba(255, 255, 255, 0.1)"
|
||||||
|
py={2}
|
||||||
|
px={3}
|
||||||
>
|
>
|
||||||
{followingEvents.length > 99 ? '99+' : followingEvents.length}
|
<HStack spacing={2}>
|
||||||
</Badge>
|
<Icon as={Star} boxSize={4} color="rgba(234, 179, 8, 0.9)" />
|
||||||
)}
|
<Text fontSize="sm" fontWeight="bold" color="rgba(255, 255, 255, 0.9)">
|
||||||
</Box>
|
事件动态
|
||||||
</Tooltip>
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverCloseButton color="rgba(255, 255, 255, 0.5)" />
|
||||||
|
<PopoverBody p={2}>
|
||||||
|
<FollowingEventsPanel
|
||||||
|
events={followingEvents}
|
||||||
|
eventComments={eventComments}
|
||||||
|
onEventClick={onEventClick}
|
||||||
|
onCommentClick={onCommentClick}
|
||||||
|
onAddEvent={onAddEvent}
|
||||||
|
onUnfollow={onUnfollow}
|
||||||
|
/>
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</Portal>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
{/* 热门板块 - 悬浮弹窗 */}
|
||||||
|
<Popover placement="left-start" trigger="click" isLazy>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<VStack
|
||||||
|
spacing={1}
|
||||||
|
align="center"
|
||||||
|
cursor="pointer"
|
||||||
|
p={2}
|
||||||
|
borderRadius="md"
|
||||||
|
_hover={{ bg: 'rgba(255, 255, 255, 0.05)' }}
|
||||||
|
position="relative"
|
||||||
|
>
|
||||||
|
<Icon as={TrendingUp} boxSize={5} color="rgba(34, 197, 94, 0.9)" />
|
||||||
|
<Text fontSize="10px" color="rgba(255, 255, 255, 0.6)" whiteSpace="nowrap">
|
||||||
|
热门板块
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<Portal>
|
||||||
|
<PopoverContent
|
||||||
|
w="280px"
|
||||||
|
bg="rgba(26, 32, 44, 0.95)"
|
||||||
|
borderColor="rgba(255, 255, 255, 0.1)"
|
||||||
|
boxShadow="0 8px 32px rgba(0, 0, 0, 0.4)"
|
||||||
|
_focus={{ outline: 'none' }}
|
||||||
|
>
|
||||||
|
<PopoverCloseButton color="rgba(255, 255, 255, 0.5)" />
|
||||||
|
<PopoverBody p={2}>
|
||||||
|
<HotSectorsRanking title="热门板块" />
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</Portal>
|
||||||
|
</Popover>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -132,7 +266,7 @@ const GlobalSidebar = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
w={isOpen ? '300px' : '48px'}
|
w={isOpen ? '300px' : '72px'}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
transition="width 0.2s ease-in-out"
|
transition="width 0.2s ease-in-out"
|
||||||
display={{ base: 'none', md: 'block' }}
|
display={{ base: 'none', md: 'block' }}
|
||||||
@@ -149,24 +283,30 @@ const GlobalSidebar = () => {
|
|||||||
|
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
/* 展开状态 */
|
/* 展开状态 */
|
||||||
<Box h="100%" overflowY="auto" position="relative">
|
<Box h="100%" display="flex" flexDirection="column">
|
||||||
{/* 收起按钮 */}
|
{/* 标题栏 - 收起按钮 + 标题 */}
|
||||||
<Box position="absolute" top={2} left={2} zIndex={2}>
|
<HStack
|
||||||
<Tooltip label="收起工具栏" placement="right">
|
px={3}
|
||||||
<IconButton
|
py={2}
|
||||||
icon={<Icon as={ChevronRight} />}
|
borderBottom="1px solid rgba(255, 255, 255, 0.05)"
|
||||||
size="xs"
|
flexShrink={0}
|
||||||
variant="ghost"
|
>
|
||||||
color="rgba(255, 255, 255, 0.4)"
|
<IconButton
|
||||||
_hover={{ color: 'rgba(212, 175, 55, 0.9)', bg: 'rgba(255, 255, 255, 0.05)' }}
|
icon={<Icon as={ChevronRight} />}
|
||||||
onClick={toggle}
|
size="xs"
|
||||||
aria-label="收起工具栏"
|
variant="ghost"
|
||||||
/>
|
color="rgba(255, 255, 255, 0.5)"
|
||||||
</Tooltip>
|
_hover={{ color: 'rgba(212, 175, 55, 0.9)', bg: 'rgba(255, 255, 255, 0.05)' }}
|
||||||
</Box>
|
onClick={toggle}
|
||||||
|
aria-label="收起工具栏"
|
||||||
|
/>
|
||||||
|
<Text fontSize="sm" fontWeight="medium" color="rgba(255, 255, 255, 0.7)">
|
||||||
|
工具栏
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
{/* WatchSidebar 内容 */}
|
{/* WatchSidebar 内容 */}
|
||||||
<Box pt={2} px={2}>
|
<Box flex="1" overflowY="auto" pt={2} px={2}>
|
||||||
<WatchSidebar
|
<WatchSidebar
|
||||||
watchlist={watchlist}
|
watchlist={watchlist}
|
||||||
realtimeQuotes={realtimeQuotes}
|
realtimeQuotes={realtimeQuotes}
|
||||||
@@ -183,11 +323,20 @@ const GlobalSidebar = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
/* 收起状态 */
|
/* 收起状态 - 点击图标显示悬浮弹窗 */
|
||||||
<CollapsedMenu
|
<CollapsedMenu
|
||||||
watchlist={watchlist}
|
watchlist={watchlist}
|
||||||
|
realtimeQuotes={realtimeQuotes}
|
||||||
followingEvents={followingEvents}
|
followingEvents={followingEvents}
|
||||||
|
eventComments={eventComments}
|
||||||
onToggle={toggle}
|
onToggle={toggle}
|
||||||
|
onStockClick={(stock) => navigate(`/company/${stock.stock_code}`)}
|
||||||
|
onEventClick={(event) => navigate(getEventDetailUrl(event.id))}
|
||||||
|
onCommentClick={(comment) => navigate(getEventDetailUrl(comment.event_id))}
|
||||||
|
onAddStock={() => navigate('/stocks')}
|
||||||
|
onAddEvent={() => navigate('/community')}
|
||||||
|
onUnwatch={unwatchStock}
|
||||||
|
onUnfollow={unfollowEvent}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user