From 9156da410dc1d0f43b166534889da7cf0e6fca58 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 23 Dec 2025 20:09:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(WatchSidebar):=20=E9=9D=A2=E6=9D=BF=20UI?= =?UTF-8?q?=20=E4=BC=98=E5=8C=96=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=97=A5?= =?UTF-8?q?=E5=9D=87=E5=91=A8=E6=B6=A8=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - WatchlistPanel: 添加 hideTitle 支持,新增日均/周涨 Badge 展示 - FollowingEventsPanel: 添加 hideTitle 支持,兼容 related_avg_chg 字段 - FollowingEventsMenu: 使用 FavoriteButton 替代文字按钮 - 统一卡片样式,与关注事件面板保持一致 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../FeatureMenus/FollowingEventsMenu.js | 56 +++--- .../components/FollowingEventsPanel.js | 67 +++---- .../WatchSidebar/components/WatchlistPanel.js | 178 +++++++++++------- 3 files changed, 172 insertions(+), 129 deletions(-) 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} + + )} + + )} + ); })}