diff --git a/src/components/Navbars/components/FeatureMenus/FollowingEventsMenu.js b/src/components/Navbars/components/FeatureMenus/FollowingEventsMenu.js index a062c7c2..8e99fb27 100644 --- a/src/components/Navbars/components/FeatureMenus/FollowingEventsMenu.js +++ b/src/components/Navbars/components/FeatureMenus/FollowingEventsMenu.js @@ -1,7 +1,7 @@ // src/components/Navbars/components/FeatureMenus/FollowingEventsMenu.js // 关注事件下拉菜单组件 -import React, { memo } from 'react'; +import React, { memo, useState } from 'react'; import { Menu, MenuButton, @@ -22,6 +22,7 @@ import { FiCalendar } from 'react-icons/fi'; import { useNavigate } from 'react-router-dom'; import { useFollowingEvents } from '../../../../hooks/useFollowingEvents'; import { getEventDetailUrl } from '@/utils/idEncoder'; +import FavoriteButton from '@/components/FavoriteButton'; /** * 关注事件下拉菜单组件 @@ -30,6 +31,7 @@ import { getEventDetailUrl } from '@/utils/idEncoder'; */ const FollowingEventsMenu = memo(() => { const navigate = useNavigate(); + const [unfollowingId, setUnfollowingId] = useState(null); const { followingEvents, eventsLoading, @@ -40,6 +42,17 @@ const FollowingEventsMenu = memo(() => { handleUnfollowEvent } = useFollowingEvents(); + // 处理取消关注(带 loading 状态) + const handleUnfollow = async (eventId) => { + if (unfollowingId) return; + setUnfollowingId(eventId); + try { + await handleUnfollowEvent(eventId); + } finally { + setUnfollowingId(null); + } + }; + const titleColor = useColorModeValue('gray.600', 'gray.300'); const loadingTextColor = useColorModeValue('gray.500', 'gray.300'); const emptyTextColor = useColorModeValue('gray.500', 'gray.300'); @@ -108,27 +121,6 @@ const FollowingEventsMenu = memo(() => { - {/* 热度 */} - {typeof ev.hot_score === 'number' && ( - = 80 ? 'red' : - (ev.hot_score >= 60 ? 'orange' : 'gray') - } - fontSize="xs" - > - 🔥 {ev.hot_score} - - )} - {/* 关注数 */} - {typeof ev.follower_count === 'number' && ev.follower_count > 0 && ( - - 👥 {ev.follower_count} - - )} {/* 日均涨跌幅 */} {typeof ev.related_avg_chg === 'number' && ( { {ev.related_week_chg.toFixed(2)}% )} - {/* 取消关注按钮 */} + {/* 取消关注按钮 - 使用 FavoriteButton */} { e.preventDefault(); e.stopPropagation(); - handleUnfollowEvent(ev.id); }} > - 取消 + handleUnfollow(ev.id)} + size="sm" + colorScheme="gold" + showTooltip={true} + /> diff --git a/src/views/Profile/components/WatchSidebar/components/FollowingEventsPanel.js b/src/views/Profile/components/WatchSidebar/components/FollowingEventsPanel.js index 16d0a830..3b3cced0 100644 --- a/src/views/Profile/components/WatchSidebar/components/FollowingEventsPanel.js +++ b/src/views/Profile/components/WatchSidebar/components/FollowingEventsPanel.js @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import { Box, Text, VStack, HStack, Icon, Button, ButtonGroup, Badge } from '@chakra-ui/react'; import { Star, Plus, Users, MessageSquare } from 'lucide-react'; import MyCommentsTab from './MyCommentsTab'; +import FavoriteButton from '@/components/FavoriteButton'; const TAB_EVENTS = 'events'; const TAB_COMMENTS = 'comments'; @@ -14,28 +15,31 @@ const FollowingEventsPanel = ({ onCommentClick, onAddEvent, onUnfollow, + hideTitle = false, }) => { const [activeTab, setActiveTab] = useState(TAB_EVENTS); return ( - {/* 标题栏 + Tab 切换 */} - - - - - 事件动态 - + {/* 标题栏 - 可隐藏 */} + {!hideTitle && ( + + + + + 事件动态 + + + - - + )} {/* Tab 切换按钮 */} @@ -110,8 +114,7 @@ const formatChange = (value) => { const EventsTabContent = ({ events, onEventClick, onAddEvent, onUnfollow }) => { const [unfollowingId, setUnfollowingId] = useState(null); - const handleUnfollow = async (e, eventId) => { - e.stopPropagation(); + const handleUnfollow = async (eventId) => { if (unfollowingId) return; setUnfollowingId(eventId); try { @@ -155,8 +158,9 @@ const EventsTabContent = ({ events, onEventClick, onAddEvent, onUnfollow }) => { > {events.map((event) => { - const dailyChg = formatChange(event.daily_avg_chg); - const weeklyChg = formatChange(event.weekly_chg); + // 兼容两种字段名:daily_avg_chg / related_avg_chg, weekly_chg / related_week_chg + const dailyChg = formatChange(event.daily_avg_chg ?? event.related_avg_chg); + const weeklyChg = formatChange(event.weekly_chg ?? event.related_week_chg); const isUnfollowing = unfollowingId === event.id; return ( @@ -181,7 +185,7 @@ const EventsTabContent = ({ events, onEventClick, onAddEvent, onUnfollow }) => { > {event.title} - {/* 底部:日均、周涨、取消 */} + {/* 底部:日均、周涨、取消关注按钮 */} {dailyChg && ( @@ -211,17 +215,16 @@ const EventsTabContent = ({ events, onEventClick, onAddEvent, onUnfollow }) => { )} - handleUnfollow(e, event.id)} - > - {isUnfollowing ? '取消中...' : '取消'} - + e.stopPropagation()}> + handleUnfollow(event.id)} + size="sm" + colorScheme="gold" + showTooltip={true} + /> + ); diff --git a/src/views/Profile/components/WatchSidebar/components/WatchlistPanel.js b/src/views/Profile/components/WatchSidebar/components/WatchlistPanel.js index f148e279..a9a2a0e8 100644 --- a/src/views/Profile/components/WatchSidebar/components/WatchlistPanel.js +++ b/src/views/Profile/components/WatchSidebar/components/WatchlistPanel.js @@ -1,7 +1,21 @@ // 关注股票面板 - 紧凑版 import React, { useState } from 'react'; -import { Box, Text, VStack, HStack, Icon, IconButton, Tooltip } from '@chakra-ui/react'; -import { BarChart2, Plus, X } from 'lucide-react'; +import { Box, Text, VStack, HStack, Icon, Badge } from '@chakra-ui/react'; +import { BarChart2, Plus } from 'lucide-react'; +import FavoriteButton from '@/components/FavoriteButton'; + +/** + * 格式化涨跌幅 + */ +const formatChange = (value) => { + if (value === undefined || value === null) return null; + const num = Number(value); + const isUp = num > 0; + return { + text: `${isUp ? '+' : ''}${num.toFixed(2)}%`, + color: isUp ? '#EF4444' : num < 0 ? '#22C55E' : 'rgba(255, 255, 255, 0.6)', + }; +}; const WatchlistPanel = ({ watchlist = [], @@ -9,11 +23,11 @@ const WatchlistPanel = ({ onStockClick, onAddStock, onUnwatch, + hideTitle = false, }) => { const [removingCode, setRemovingCode] = useState(null); - const handleUnwatch = async (e, stockCode) => { - e.stopPropagation(); + const handleUnwatch = async (stockCode) => { if (removingCode) return; setRemovingCode(stockCode); try { @@ -24,26 +38,28 @@ const WatchlistPanel = ({ }; return ( - {/* 标题 */} - - - - - 关注股票 - - - ({watchlist.length}) - + {/* 标题 - 可隐藏 */} + {!hideTitle && ( + + + + + 关注股票 + + + ({watchlist.length}) + + + - - + )} {/* 股票列表 - 固定高度可滚动 */} {watchlist.length === 0 ? ( @@ -74,7 +90,7 @@ const WatchlistPanel = ({ }, }} > - + {watchlist.map((stock) => { const quote = realtimeQuotes[stock.stock_code]; const changePercent = quote?.change_percent ?? stock.change_percent; @@ -83,58 +99,92 @@ const WatchlistPanel = ({ const isRemoving = removingCode === stock.stock_code; + // 日均涨跌幅和周涨跌幅 + const dailyChg = formatChange(quote?.daily_avg_chg ?? stock.daily_avg_chg); + const weeklyChg = formatChange(quote?.weekly_chg ?? stock.weekly_chg); + return ( - onStockClick?.(stock)} role="group" > - - - {stock.stock_name || stock.stock_code} - - - {stock.stock_code} - - - - - - {quote?.current_price?.toFixed(2) || stock.current_price || '--'} + {/* 第一行:股票名称 + 价格/涨跌幅 + 取消关注按钮 */} + + + + {stock.stock_name || stock.stock_code} - - {changePercent !== undefined && changePercent !== null - ? `${isUp ? '+' : ''}${Number(changePercent).toFixed(2)}%` - : '--'} + + {stock.stock_code} - - } - size="xs" - variant="ghost" - color="rgba(255, 255, 255, 0.3)" - opacity={0} - _groupHover={{ opacity: 1 }} - _hover={{ color: '#EF4444', bg: 'rgba(239, 68, 68, 0.1)' }} - isLoading={isRemoving} - onClick={(e) => handleUnwatch(e, stock.stock_code)} - aria-label="取消关注" - /> - + + + + {quote?.current_price?.toFixed(2) || stock.current_price || '--'} + + + {changePercent !== undefined && changePercent !== null + ? `${isUp ? '+' : ''}${Number(changePercent).toFixed(2)}%` + : '--'} + + + e.stopPropagation()}> + handleUnwatch(stock.stock_code)} + size="sm" + colorScheme="gold" + showTooltip={true} + /> + + - + {/* 第二行:日均、周涨 Badge */} + {(dailyChg || weeklyChg) && ( + + {dailyChg && ( + + 日均 {dailyChg.text} + + )} + {weeklyChg && ( + + 周涨 {weeklyChg.text} + + )} + + )} + ); })}