import React, { useState, useEffect } from 'react';
import { useParams, useLocation } 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,
Link,
} 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';
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';
// 临时调试代码 - 生产环境测试后请删除
if (typeof window !== 'undefined') {
console.log('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 }) => {
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) {
console.error('Failed to load comments:', error);
} finally {
setIsLoading(false);
}
} else {
setShowComments(false);
}
};
const handleLike = async () => {
try {
const result = await eventService.likePost(post.id);
if (result.success) {
setLiked(result.liked);
setLikesCount(result.likes_count);
}
} 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) {
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) {
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}
{/* 操作栏 */}
: }
color={liked ? 'red.500' : 'gray.500'}
onClick={handleLike}
>
{likesCount}
}
rightIcon={showComments ? : }
onClick={loadComments}
>
{post.comments_count || 0} 评论
{/* 评论区 */}
{/* 评论输入 */}
{/* 评论列表 */}
{isLoading ? (
) : (
{comments.map((comment) => (
{comment.user?.username || '匿名用户'}
{format(new Date(comment.created_at), 'MM-dd HH:mm')}
{comment.content}
))}
{comments.length === 0 && (
暂无评论
)}
)}
);
};
const EventDetail = () => {
const { eventId } = useParams();
const location = useLocation();
const bgColor = useColorModeValue('gray.50', 'gray.900');
const toast = useToast();
// 用户认证和权限控制
const { user } = useAuth();
const { hasFeatureAccess, getUpgradeRecommendation } = useSubscription();
// State hooks
const [eventData, setEventData] = useState(null);
const [relatedStocks, setRelatedStocks] = useState([]);
const [relatedConcepts, setRelatedConcepts] = useState([]);
const [historicalEvents, setHistoricalEvents] = useState([]);
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [postsLoading, setPostsLoading] = useState(false);
const [error, setError] = useState(null);
const [activeTab, setActiveTab] = useState(0);
const [newPostContent, setNewPostContent] = useState('');
const [newPostTitle, setNewPostTitle] = useState('');
const [submitting, setSubmitting] = useState(false);
const [upgradeModal, setUpgradeModal] = useState({ isOpen: false, feature: '功能', required: 'pro' });
// 从URL路径中提取eventId(处理多种URL格式)
const getEventIdFromPath = () => {
const pathParts = location.pathname.split('/');
const lastPart = pathParts[pathParts.length - 1];
const secondLastPart = pathParts[pathParts.length - 2];
if (!isNaN(lastPart) && lastPart) {
return lastPart;
}
if (!isNaN(secondLastPart) && secondLastPart) {
return secondLastPart;
}
return eventId;
};
const actualEventId = getEventIdFromPath();
const loadEventData = async () => {
try {
setLoading(true);
setError(null);
// 加载基本事件信息(免费用户也可以访问)
const eventResponse = await eventService.getEventDetail(actualEventId);
setEventData(eventResponse.data);
// 总是尝试加载相关股票(权限在组件内部检查)
try {
const stocksResponse = await eventService.getRelatedStocks(actualEventId);
setRelatedStocks(stocksResponse.data || []);
} catch (e) {
console.warn('加载相关股票失败:', e);
setRelatedStocks([]);
}
// 根据权限决定是否加载相关概念
if (hasFeatureAccess('related_concepts')) {
try {
const conceptsResponse = await eventService.getRelatedConcepts(actualEventId);
setRelatedConcepts(conceptsResponse.data || []);
} catch (e) {
console.warn('加载相关概念失败:', e);
}
}
// 历史事件所有用户都可以访问,但免费用户只看到前2条
try {
const eventsResponse = await eventService.getHistoricalEvents(actualEventId);
setHistoricalEvents(eventsResponse.data || []);
} catch (e) {
console.warn('历史事件加载失败', e);
}
} catch (err) {
console.error('Error loading event data:', err);
setError(err.message || '加载事件数据失败');
} finally {
setLoading(false);
}
};
const refetchStocks = async () => {
if (!hasFeatureAccess('related_stocks')) return;
try {
const stocksResponse = await eventService.getRelatedStocks(actualEventId);
setRelatedStocks(stocksResponse.data);
} catch (err) {
console.error('重新获取股票数据失败:', err);
}
};
const handleFollowToggle = async () => {
try {
await eventService.toggleFollow(actualEventId, eventData.is_following);
setEventData(prev => ({
...prev,
is_following: !prev.is_following,
follower_count: prev.is_following
? prev.follower_count - 1
: prev.follower_count + 1
}));
} catch (err) {
console.error('关注操作失败:', err);
}
};
// 加载帖子列表
const loadPosts = async () => {
setPostsLoading(true);
try {
const result = await eventService.getPosts(actualEventId);
if (result.success) {
setPosts(result.data || []);
}
} catch (err) {
console.error('加载帖子失败:', err);
} finally {
setPostsLoading(false);
}
};
// 创建新帖子
const handleCreatePost = async () => {
if (!newPostContent.trim()) return;
setSubmitting(true);
try {
const result = await eventService.createPost(actualEventId, {
title: newPostTitle.trim(),
content: newPostContent.trim(),
content_type: 'text',
});
if (result.success) {
toast({
title: '帖子发布成功',
status: 'success',
duration: 2000,
});
setNewPostContent('');
setNewPostTitle('');
loadPosts();
// 更新帖子数
setEventData(prev => ({
...prev,
post_count: (prev.post_count || 0) + 1
}));
}
} catch (err) {
toast({
title: '发布失败',
description: err.message,
status: 'error',
duration: 3000,
});
} finally {
setSubmitting(false);
}
};
// Effect hook - must be called after all state hooks
useEffect(() => {
if (actualEventId) {
loadEventData();
loadPosts();
} else {
setError('无效的事件ID');
setLoading(false);
}
}, [actualEventId, location.pathname]);
// 加载状态
if (loading) {
return (
{[1, 2, 3, 4].map((i) => (
))}
);
}
// 错误状态
if (error) {
return (
加载失败
{error}
{actualEventId && (
事件ID: {actualEventId}
)}
);
}
// 主要内容
return (
{/* 事件基本信息 */}
{/* 统计数据 */}
{/* 主要内容标签页 */}
相关标的
{!hasFeatureAccess('related_stocks') && (
)}
相关概念
{!hasFeatureAccess('related_concepts') && (
)}
历史事件
传导链分析
{!hasFeatureAccess('transmission_chain') && (
)}
讨论区
{/* 相关标的标签页 */}
{!hasFeatureAccess('related_stocks') ? (
该功能为Pro专享,请升级订阅后查看相关标的。
) : (
)}
{/* 相关概念标签页 */}
{!hasFeatureAccess('related_concepts') ? (
该功能为Pro专享,请升级订阅后查看相关概念。
) : (
)}
{/* 历史事件标签页 */}
{!hasFeatureAccess('historical_events_full') && historicalEvents.length > 0 && (
免费版仅展示前2条历史事件,
可查看全部。
)}
{/* 传导链分析标签页 */}
{!hasFeatureAccess('transmission_chain') ? (
传导链分析为Max专享,请升级订阅后查看。
) : (
)}
{/* 讨论区标签页 */}
{/* 发布新帖子 */}
{user && (
setNewPostTitle(e.target.value)}
/>
)}
{/* 帖子列表 */}
{postsLoading ? (
{[1, 2, 3].map((i) => (
))}
) : posts.length > 0 ? (
posts.map((post) => (
))
) : (
还没有讨论,来发布第一个帖子吧!
)}
{/* Footer区域 */}
© 2024 价值前沿. 保留所有权利.
京公网安备11010802046286号
京ICP备2025107343号-1
{/* 升级弹窗 */}
setUpgradeModal({ isOpen: false, feature: '功能', required: 'pro' })}
requiredLevel={upgradeModal.required}
featureName={upgradeModal.feature}
currentLevel={user?.subscription_type || 'free'}
/>
);
};
export default EventDetail;