feat(WatchSidebar): 面板 UI 优化,添加日均周涨展示

- WatchlistPanel: 添加 hideTitle 支持,新增日均/周涨 Badge 展示
- FollowingEventsPanel: 添加 hideTitle 支持,兼容 related_avg_chg 字段
- FollowingEventsMenu: 使用 FavoriteButton 替代文字按钮
- 统一卡片样式,与关注事件面板保持一致

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-23 20:09:44 +08:00
parent 073fba5c57
commit 9156da410d
3 changed files with 172 additions and 129 deletions

View File

@@ -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 (
<Box>
{/* 标题栏 + Tab 切换 */}
<HStack justify="space-between" mb={2}>
<HStack spacing={1}>
<Icon as={Star} boxSize={3.5} color="rgba(234, 179, 8, 0.9)" />
<Text fontSize="xs" fontWeight="bold" color="rgba(255, 255, 255, 0.9)">
事件动态
</Text>
{/* 标题栏 - 可隐藏 */}
{!hideTitle && (
<HStack justify="space-between" mb={2}>
<HStack spacing={1}>
<Icon as={Star} boxSize={3.5} color="rgba(234, 179, 8, 0.9)" />
<Text fontSize="xs" fontWeight="bold" color="rgba(255, 255, 255, 0.9)">
事件动态
</Text>
</HStack>
<Icon
as={Plus}
boxSize={3.5}
color="rgba(255, 255, 255, 0.5)"
cursor="pointer"
_hover={{ color: 'rgba(212, 175, 55, 0.9)' }}
onClick={onAddEvent}
/>
</HStack>
<Icon
as={Plus}
boxSize={3.5}
color="rgba(255, 255, 255, 0.5)"
cursor="pointer"
_hover={{ color: 'rgba(212, 175, 55, 0.9)' }}
onClick={onAddEvent}
/>
</HStack>
)}
{/* Tab 切换按钮 */}
<ButtonGroup size="xs" isAttached variant="outline" mb={2} w="100%">
@@ -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 }) => {
>
<VStack spacing={1.5} align="stretch">
{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}
</Text>
{/* 底部:日均、周涨、取消 */}
{/* 底部:日均、周涨、取消关注按钮 */}
<HStack justify="space-between" fontSize="10px" flexWrap="wrap" gap={1}>
<HStack spacing={1.5}>
{dailyChg && (
@@ -211,17 +215,16 @@ const EventsTabContent = ({ events, onEventClick, onAddEvent, onUnfollow }) => {
</Badge>
)}
</HStack>
<Text
color="rgba(255, 255, 255, 0.4)"
fontSize="9px"
cursor="pointer"
opacity={0}
_groupHover={{ opacity: 1 }}
_hover={{ color: '#EF4444' }}
onClick={(e) => handleUnfollow(e, event.id)}
>
{isUnfollowing ? '取消中...' : '取消'}
</Text>
<Box onClick={(e) => e.stopPropagation()}>
<FavoriteButton
isFavorite={true}
isLoading={isUnfollowing}
onClick={() => handleUnfollow(event.id)}
size="sm"
colorScheme="gold"
showTooltip={true}
/>
</Box>
</HStack>
</Box>
);

View File

@@ -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 (
<Box>
{/* 标题 */}
<HStack justify="space-between" mb={2}>
<HStack spacing={1}>
<Icon as={BarChart2} boxSize={3.5} color="rgba(59, 130, 246, 0.9)" />
<Text fontSize="xs" fontWeight="bold" color="rgba(255, 255, 255, 0.9)">
关注股票
</Text>
<Text fontSize="xs" color="rgba(255, 255, 255, 0.5)">
({watchlist.length})
</Text>
{/* 标题 - 可隐藏 */}
{!hideTitle && (
<HStack justify="space-between" mb={2}>
<HStack spacing={1}>
<Icon as={BarChart2} boxSize={3.5} color="rgba(59, 130, 246, 0.9)" />
<Text fontSize="xs" fontWeight="bold" color="rgba(255, 255, 255, 0.9)">
关注股票
</Text>
<Text fontSize="xs" color="rgba(255, 255, 255, 0.5)">
({watchlist.length})
</Text>
</HStack>
<Icon
as={Plus}
boxSize={3.5}
color="rgba(255, 255, 255, 0.5)"
cursor="pointer"
_hover={{ color: 'rgba(212, 175, 55, 0.9)' }}
onClick={onAddStock}
/>
</HStack>
<Icon
as={Plus}
boxSize={3.5}
color="rgba(255, 255, 255, 0.5)"
cursor="pointer"
_hover={{ color: 'rgba(212, 175, 55, 0.9)' }}
onClick={onAddStock}
/>
</HStack>
)}
{/* 股票列表 - 固定高度可滚动 */}
{watchlist.length === 0 ? (
@@ -74,7 +90,7 @@ const WatchlistPanel = ({
},
}}
>
<VStack spacing={1} align="stretch">
<VStack spacing={1.5} align="stretch">
{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 (
<HStack
<Box
key={stock.stock_code}
py={1.5}
py={2}
px={2}
justify="space-between"
cursor="pointer"
borderRadius="md"
_hover={{ bg: 'rgba(255, 255, 255, 0.05)' }}
bg="rgba(37, 37, 64, 0.3)"
_hover={{ bg: 'rgba(37, 37, 64, 0.6)' }}
onClick={() => onStockClick?.(stock)}
role="group"
>
<VStack align="start" spacing={0} flex={1} minW={0}>
<Text
fontSize="xs"
fontWeight="medium"
color="rgba(255, 255, 255, 0.9)"
noOfLines={1}
>
{stock.stock_name || stock.stock_code}
</Text>
<Text fontSize="10px" color="rgba(255, 255, 255, 0.4)">
{stock.stock_code}
</Text>
</VStack>
<HStack spacing={1}>
<VStack align="end" spacing={0}>
<Text fontSize="xs" fontWeight="bold" color={changeColor}>
{quote?.current_price?.toFixed(2) || stock.current_price || '--'}
{/* 第一行:股票名称 + 价格/涨跌幅 + 取消关注按钮 */}
<HStack justify="space-between" mb={1.5}>
<VStack align="start" spacing={0} flex={1} minW={0}>
<Text
fontSize="xs"
fontWeight="medium"
color="rgba(255, 255, 255, 0.9)"
noOfLines={1}
>
{stock.stock_name || stock.stock_code}
</Text>
<Text fontSize="10px" color={changeColor}>
{changePercent !== undefined && changePercent !== null
? `${isUp ? '+' : ''}${Number(changePercent).toFixed(2)}%`
: '--'}
<Text fontSize="10px" color="rgba(255, 255, 255, 0.4)">
{stock.stock_code}
</Text>
</VStack>
<Tooltip label="取消关注" placement="top" hasArrow>
<IconButton
icon={<Icon as={X} boxSize={3} />}
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="取消关注"
/>
</Tooltip>
<HStack spacing={1}>
<VStack align="end" spacing={0}>
<Text fontSize="xs" fontWeight="bold" color={changeColor}>
{quote?.current_price?.toFixed(2) || stock.current_price || '--'}
</Text>
<Text fontSize="10px" color={changeColor}>
{changePercent !== undefined && changePercent !== null
? `${isUp ? '+' : ''}${Number(changePercent).toFixed(2)}%`
: '--'}
</Text>
</VStack>
<Box onClick={(e) => e.stopPropagation()}>
<FavoriteButton
isFavorite={true}
isLoading={isRemoving}
onClick={() => handleUnwatch(stock.stock_code)}
size="sm"
colorScheme="gold"
showTooltip={true}
/>
</Box>
</HStack>
</HStack>
</HStack>
{/* 第二行:日均、周涨 Badge */}
{(dailyChg || weeklyChg) && (
<HStack spacing={1.5} fontSize="10px">
{dailyChg && (
<Badge
bg="rgba(255, 255, 255, 0.08)"
color={dailyChg.color}
fontSize="9px"
fontWeight="medium"
px={1.5}
py={0.5}
borderRadius="sm"
>
日均 {dailyChg.text}
</Badge>
)}
{weeklyChg && (
<Badge
bg="rgba(255, 255, 255, 0.08)"
color={weeklyChg.color}
fontSize="9px"
fontWeight="medium"
px={1.5}
py={0.5}
borderRadius="sm"
>
周涨 {weeklyChg.text}
</Badge>
)}
</HStack>
)}
</Box>
);
})}
</VStack>