diff --git a/src/layouts/MainLayout.js b/src/layouts/MainLayout.js index 372b34ed..dbb64f09 100644 --- a/src/layouts/MainLayout.js +++ b/src/layouts/MainLayout.js @@ -30,12 +30,12 @@ const MemoizedAppFooter = memo(AppFooter); */ export default function MainLayout() { return ( - + {/* 导航栏 - 在所有页面间共享,memo 后不会在路由切换时重新渲染 */} {/* 页面内容区域 - flex: 1 占据剩余空间,包含错误边界、懒加载 */} - + }> diff --git a/src/mocks/handlers/event.js b/src/mocks/handlers/event.js index 1fe1e1c8..a328d079 100644 --- a/src/mocks/handlers/event.js +++ b/src/mocks/handlers/event.js @@ -255,6 +255,48 @@ export const eventHandlers = [ // ==================== 事件详情相关 ==================== + // 获取事件详情 + http.get('/api/events/:eventId', async ({ params }) => { + await delay(200); + + const { eventId } = params; + + console.log('[Mock] 获取事件详情, eventId:', eventId); + + try { + // 返回模拟的事件详情数据 + return HttpResponse.json({ + success: true, + data: { + id: parseInt(eventId), + title: `测试事件 ${eventId} - 重大政策发布`, + description: '这是一个模拟的事件描述,用于开发测试。该事件涉及重要政策变化,可能对相关板块产生显著影响。建议关注后续发展动态。', + importance: ['S', 'A', 'B', 'C'][Math.floor(Math.random() * 4)], + created_at: new Date().toISOString(), + trading_date: new Date().toISOString().split('T')[0], + event_type: ['政策', '财报', '行业', '宏观'][Math.floor(Math.random() * 4)], + related_avg_chg: parseFloat((Math.random() * 10 - 5).toFixed(2)), + follower_count: Math.floor(Math.random() * 500) + 50, + view_count: Math.floor(Math.random() * 5000) + 100, + is_following: false, + post_count: Math.floor(Math.random() * 50), + expectation_surprise_score: parseFloat((Math.random() * 100).toFixed(1)), + }, + message: '获取成功' + }); + } catch (error) { + console.error('[Mock] 获取事件详情失败:', error); + return HttpResponse.json( + { + success: false, + error: '获取事件详情失败', + data: null + }, + { status: 500 } + ); + } + }), + // 获取事件相关股票 http.get('/api/events/:eventId/stocks', async ({ params }) => { await delay(300); diff --git a/src/mocks/handlers/stock.js b/src/mocks/handlers/stock.js index f7825a9b..eb3ef037 100644 --- a/src/mocks/handlers/stock.js +++ b/src/mocks/handlers/stock.js @@ -318,4 +318,74 @@ export const stockHandlers = [ ); } }), + + // 获取股票报价(批量) + http.post('/api/stock/quotes', async ({ request }) => { + await delay(200); + + try { + const body = await request.json(); + const { codes, event_time } = body; + + console.log('[Mock Stock] 获取股票报价:', { + stockCount: codes?.length, + eventTime: event_time + }); + + if (!codes || !Array.isArray(codes) || codes.length === 0) { + return HttpResponse.json( + { success: false, error: '股票代码列表不能为空' }, + { status: 400 } + ); + } + + // 生成股票列表用于查找名称 + const stockList = generateStockList(); + const stockMap = {}; + stockList.forEach(s => { + stockMap[s.code] = s.name; + }); + + // 为每只股票生成报价数据 + const quotesData = {}; + codes.forEach(stockCode => { + // 生成基础价格(10-200之间) + const basePrice = parseFloat((Math.random() * 190 + 10).toFixed(2)); + // 涨跌幅(-10% 到 +10%) + const changePercent = parseFloat((Math.random() * 20 - 10).toFixed(2)); + // 涨跌额 + const change = parseFloat((basePrice * changePercent / 100).toFixed(2)); + // 昨收 + const prevClose = parseFloat((basePrice - change).toFixed(2)); + + quotesData[stockCode] = { + code: stockCode, + name: stockMap[stockCode] || `股票${stockCode}`, + price: basePrice, + change: change, + change_percent: changePercent, + prev_close: prevClose, + open: parseFloat((prevClose * (1 + (Math.random() * 0.02 - 0.01))).toFixed(2)), + high: parseFloat((basePrice * (1 + Math.random() * 0.05)).toFixed(2)), + low: parseFloat((basePrice * (1 - Math.random() * 0.05)).toFixed(2)), + volume: Math.floor(Math.random() * 100000000), + amount: parseFloat((Math.random() * 10000000000).toFixed(2)), + market: stockCode.startsWith('6') ? 'SH' : 'SZ', + update_time: new Date().toISOString() + }; + }); + + return HttpResponse.json({ + success: true, + data: quotesData, + message: '获取成功' + }); + } catch (error) { + console.error('[Mock Stock] 获取股票报价失败:', error); + return HttpResponse.json( + { success: false, error: '获取股票报价失败' }, + { status: 500 } + ); + } + }), ]; diff --git a/src/theme/theme.js b/src/theme/theme.js index e8d34aff..48471c58 100755 --- a/src/theme/theme.js +++ b/src/theme/theme.js @@ -27,6 +27,18 @@ import { MainPanelComponent } from "./additions/layout/MainPanel"; import { PanelContentComponent } from "./additions/layout/PanelContent"; import { PanelContainerComponent } from "./additions/layout/PanelContainer"; // import { mode } from "@chakra-ui/theme-tools"; + +// Container 组件样式覆盖 - 移除默认背景色 +const ContainerComponent = { + components: { + Container: { + baseStyle: { + bg: "1A202C", + }, + }, + }, +}; + export default extendTheme( { breakpoints }, // Breakpoints globalStyles, @@ -37,5 +49,6 @@ export default extendTheme( CardComponent, // Card component MainPanelComponent, // Main Panel component PanelContentComponent, // Panel Content component - PanelContainerComponent // Panel Container component + PanelContainerComponent, // Panel Container component + ContainerComponent // Container 背景透明 ); diff --git a/src/views/EventDetail/index.js b/src/views/EventDetail/index.js index 3e56a4f1..381b851d 100644 --- a/src/views/EventDetail/index.js +++ b/src/views/EventDetail/index.js @@ -1,899 +1,112 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { useParams, useLocation, useSearchParams } from 'react-router-dom'; -import { decodeEventId } from '@/utils/idEncoder'; +/** + * EventDetail - 事件详情页面 + * 使用 DynamicNewsDetailPanel 组件展示事件详情 + */ + +import React, { useState, useEffect } from 'react'; +import { useParams, useSearchParams } from 'react-router-dom'; import { - Box, - Container, - VStack, - HStack, - Spinner, - Alert, - AlertIcon, - AlertTitle, - AlertDescription, - Flex, - useColorModeValue, - Grid, - GridItem, - Icon, - Text, - Badge, - Divider, - useDisclosure, - Button, - Heading, - Stat, - StatLabel, - StatNumber, - StatHelpText, - SimpleGrid, - Tabs, - TabList, - TabPanels, - Tab, - TabPanel, - Textarea, - Avatar, - IconButton, - Input, - Collapse, - Center, - useToast, - Skeleton, + Box, + Container, + Spinner, + Center, + Alert, + AlertIcon, + AlertTitle, + AlertDescription, + Text, } from '@chakra-ui/react'; -import { FiLock } from 'react-icons/fi'; -import { - FiTrendingUp, - FiActivity, - FiMessageSquare, - FiClock, - FiBarChart2, - FiLink, - FiZap, - FiGlobe, - FiHeart, - FiTrash2, - FiChevronDown, - FiChevronUp, -} from 'react-icons/fi'; -import { FaHeart, FaRegHeart, FaComment } from 'react-icons/fa'; -import { format } from 'date-fns'; -import { zhCN } from 'date-fns/locale'; - -// 导入新建的业务组件 -import EventHeader from './components/EventHeader'; -import RelatedConcepts from './components/RelatedConcepts'; -import HistoricalEvents from './components/HistoricalEvents'; -import RelatedStocks from './components/RelatedStocks'; -// Navigation bar now provided by MainLayout -// import HomeNavbar from '../../components/Navbars/HomeNavbar'; -import SubscriptionUpgradeModal from '../../components/SubscriptionUpgradeModal'; -import { useAuth } from '../../contexts/AuthContext'; -import { useSubscription } from '../../hooks/useSubscription'; -import TransmissionChainAnalysis from './components/TransmissionChainAnalysis'; - -// 导入你的 Flask API 服务 -import { eventService } from '../../services/eventService'; -import { debugEventService } from '../../utils/debugEventService'; -import { logger } from '../../utils/logger'; -import { useEventDetailEvents } from './hooks/useEventDetailEvents'; - -// 临时调试代码 - 生产环境测试后请删除 -if (typeof window !== 'undefined') { - logger.debug('EventDetail', '调试 eventService'); - debugEventService(); -} - -// 统计卡片组件 - 更简洁的设计 -const StatCard = ({ icon, label, value, color }) => { - const bg = useColorModeValue('white', 'gray.800'); - const borderColor = useColorModeValue('gray.200', 'gray.700'); - const iconColor = useColorModeValue(`${color}.500`, `${color}.300`); - - return ( - - - - - {label} - {value} - - - - ); -}; - -// 帖子组件 -const PostItem = ({ post, onRefresh, eventEvents }) => { - const [showComments, setShowComments] = useState(false); - const [comments, setComments] = useState([]); - const [newComment, setNewComment] = useState(''); - const [isLoading, setIsLoading] = useState(false); - const [liked, setLiked] = useState(post.liked || false); - const [likesCount, setLikesCount] = useState(post.likes_count || 0); - const toast = useToast(); - const { user } = useAuth(); - const bg = useColorModeValue('white', 'gray.800'); - const borderColor = useColorModeValue('gray.200', 'gray.700'); - - const loadComments = async () => { - if (!showComments) { - setShowComments(true); - setIsLoading(true); - try { - const result = await eventService.getPostComments(post.id); - if (result.success) { - setComments(result.data); - } - } catch (error) { - logger.error('PostItem', 'loadComments', error, { postId: post.id }); - } finally { - setIsLoading(false); - } - } else { - setShowComments(false); - } - }; - - const handleLike = async () => { - try { - const result = await eventService.likePost(post.id); - if (result.success) { - const newLikedState = result.liked; - setLiked(newLikedState); - setLikesCount(result.likes_count); - - // 🎯 追踪评论点赞 - if (eventEvents && eventEvents.trackCommentLiked) { - eventEvents.trackCommentLiked(post.id, newLikedState); - } - } - } catch (error) { - toast({ - title: '操作失败', - status: 'error', - duration: 2000, - }); - } - }; - - const handleAddComment = async () => { - if (!newComment.trim()) return; - - try { - const result = await eventService.addPostComment(post.id, { - content: newComment, - }); - - if (result.success) { - // 🎯 追踪添加评论 - if (eventEvents && eventEvents.trackCommentAdded) { - eventEvents.trackCommentAdded( - result.data?.id || post.id, - newComment.length - ); - } - - toast({ - title: '评论发表成功', - status: 'success', - duration: 2000, - }); - setNewComment(''); - // 重新加载评论 - const commentsResult = await eventService.getPostComments(post.id); - if (commentsResult.success) { - setComments(commentsResult.data); - } - } - } catch (error) { - toast({ - title: '评论失败', - status: 'error', - duration: 2000, - }); - } - }; - - const handleDelete = async () => { - if (window.confirm('确定要删除这个帖子吗?')) { - try { - const result = await eventService.deletePost(post.id); - if (result.success) { - // 🎯 追踪删除评论 - if (eventEvents && eventEvents.trackCommentDeleted) { - eventEvents.trackCommentDeleted(post.id); - } - - toast({ - title: '删除成功', - status: 'success', - duration: 2000, - }); - onRefresh(); - } - } catch (error) { - toast({ - title: '删除失败', - status: 'error', - duration: 2000, - }); - } - } - }; - - return ( - - {/* 帖子头部 */} - - - - - {post.user?.username || '匿名用户'} - - {format(new Date(post.created_at), 'yyyy-MM-dd HH:mm', { locale: zhCN })} - - - - } - variant="ghost" - size="sm" - onClick={handleDelete} - /> - - - {/* 帖子内容 */} - {post.title && ( - - {post.title} - - )} - - {post.content} - - - {/* 操作栏 */} - - - - - - {/* 评论区 */} - - - {/* 评论输入 */} - -