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}
+
+ )}
+
+ )}
+
);
})}