{event.creator?.username || 'Anonymous'}
diff --git a/src/views/Community/hooks/useEventFilters.js b/src/views/Community/hooks/useEventFilters.js
index be2ff8ae..51206708 100644
--- a/src/views/Community/hooks/useEventFilters.js
+++ b/src/views/Community/hooks/useEventFilters.js
@@ -6,6 +6,7 @@ import { useSearchParams } from 'react-router-dom';
import { logger } from '../../../utils/logger';
import { usePostHogTrack } from '../../../hooks/usePostHogRedux';
import { RETENTION_EVENTS } from '../../../lib/constants';
+import { getEventDetailUrl } from '@/utils/idEncoder';
/**
* 事件筛选逻辑 Hook
@@ -145,7 +146,7 @@ export const useEventFilters = ({ navigate, onEventClick, eventTimelineRef } = {
});
if (navigate) {
- navigate(`/event-detail/${eventId}`);
+ navigate(getEventDetailUrl(eventId));
}
}, [navigate, track]);
diff --git a/src/views/Concept/ConceptTimelineModal.js b/src/views/Concept/ConceptTimelineModal.js
index ac271ee7..f002838a 100644
--- a/src/views/Concept/ConceptTimelineModal.js
+++ b/src/views/Concept/ConceptTimelineModal.js
@@ -31,6 +31,7 @@ import {
useDisclosure,
SimpleGrid,
Tooltip,
+ useBreakpointValue,
} from '@chakra-ui/react';
import {
ChevronDownIcon,
@@ -111,6 +112,9 @@ const ConceptTimelineModal = ({
const [selectedNews, setSelectedNews] = useState(null);
const [isNewsModalOpen, setIsNewsModalOpen] = useState(false);
+ // 响应式配置
+ const isMobile = useBreakpointValue({ base: true, md: false }, { fallback: 'md' });
+
// 辅助函数:格式化日期显示(包含年份)
const formatDateDisplay = (dateStr) => {
const date = new Date(dateStr);
@@ -602,37 +606,41 @@ const ConceptTimelineModal = ({
onClose={onClose}
size="full"
scrollBehavior="inside"
+ isCentered
>
-
+
-
+
{conceptName} - 历史时间轴
最近100天
@@ -640,20 +648,29 @@ const ConceptTimelineModal = ({
🔥 Max版功能
-
+
) : timelineData.length > 0 ? (
-
- {/* 图例说明 */}
-
+
+ {/* 图例说明 - H5端保持一行 */}
+
-
- 📰 新闻
+
+ 📰 新闻
-
- 📊 研报
+
+ 📊 研报
-
- 上涨
+
+ 上涨
-
- 下跌
+
+ 下跌
- 🔥
- 涨3%+
+ 🔥
+ 涨3%+
{/* FullCalendar 日历组件 */}
@@ -882,32 +924,11 @@ const ConceptTimelineModal = ({
)}
{/* 风险提示 */}
-
+
-
-
-
)}
diff --git a/src/views/Concept/index.js b/src/views/Concept/index.js
index cfcb9464..ad159e46 100644
--- a/src/views/Concept/index.js
+++ b/src/views/Concept/index.js
@@ -78,6 +78,7 @@ import {
MenuList,
MenuItem,
Collapse,
+ useBreakpointValue,
} from '@chakra-ui/react';
import { SearchIcon, ViewIcon, CalendarIcon, ExternalLinkIcon, StarIcon, ChevronDownIcon, InfoIcon, CloseIcon, ChevronRightIcon } from '@chakra-ui/icons';
import { FaThLarge, FaList, FaTags, FaChartLine, FaRobot, FaTable, FaHistory, FaBrain, FaLightbulb, FaRocket, FaShieldAlt, FaCalendarAlt, FaArrowUp, FaArrowDown, FaNewspaper, FaFileAlt, FaExpand, FaCompress, FaClock, FaLock } from 'react-icons/fa';
@@ -85,6 +86,8 @@ import { BsGraphUp, BsLightningFill } from 'react-icons/bs';
import { keyframes } from '@emotion/react';
import ConceptTimelineModal from './ConceptTimelineModal';
import ConceptStatsPanel from './components/ConceptStatsPanel';
+import ConceptStocksModal from '@components/ConceptStocksModal';
+import TradeDatePicker from '@components/TradeDatePicker';
// 导航栏已由 MainLayout 提供,无需在此导入
// 导入订阅权限管理
import { useSubscription } from '../../hooks/useSubscription';
@@ -527,109 +530,6 @@ const ConceptCenter = () => {
return `https://valuefrontier.cn/company?scode=${seccode}`;
};
- // 渲染动态表格列
- const renderStockTable = () => {
- if (!selectedConceptStocks || selectedConceptStocks.length === 0) {
- return 暂无相关股票数据;
- }
-
- const allFields = new Set();
- selectedConceptStocks.forEach(stock => {
- Object.keys(stock).forEach(key => allFields.add(key));
- });
-
- // 定义固定的列顺序,包含新增的现价和涨跌幅列
- const orderedFields = ['stock_name', 'stock_code', 'current_price', 'change_percent'];
- allFields.forEach(field => {
- if (!orderedFields.includes(field)) {
- orderedFields.push(field);
- }
- });
-
- return (
-
- {loadingStockData && (
-
-
-
- 正在获取行情数据...
-
-
- )}
-
-
-
-
-
- {orderedFields.map(field => (
- |
- {field === 'stock_name' ? '股票名称' :
- field === 'stock_code' ? '股票代码' :
- field === 'current_price' ? '现价' :
- field === 'change_percent' ? '当日涨跌幅' : field}
- |
- ))}
-
-
-
- {selectedConceptStocks.map((stock, idx) => {
- const marketData = stockMarketData[stock.stock_code];
- const companyLink = generateCompanyLink(stock.stock_code);
-
- return (
-
- {orderedFields.map(field => {
- let cellContent = stock[field] || '-';
- let cellProps = {};
-
- // 处理特殊字段
- if (field === 'current_price') {
- cellContent = marketData ? formatPrice(marketData.close) : (loadingStockData ? : '-');
- } else if (field === 'change_percent') {
- if (marketData) {
- cellContent = formatStockChangePercent(marketData.change_percent);
- cellProps.color = `${getStockChangeColor(marketData.change_percent)}.500`;
- cellProps.fontWeight = 'bold';
- } else {
- cellContent = loadingStockData ? : '-';
- }
- } else if (field === 'stock_name' || field === 'stock_code') {
- // 添加超链接
- cellContent = (
-
- {stock[field] || '-'}
-
- );
- }
-
- return (
- |
- {cellContent}
- |
- );
- })}
-
- );
- })}
-
-
-
-
- );
- };
-
// 格式化添加日期显示
const formatAddedDate = (concept) => {
// 优先使用 created_at 或 added_date 字段
@@ -672,6 +572,10 @@ const ConceptCenter = () => {
const changePercent = concept.price_info?.avg_change_pct;
const changeColor = getChangeColor(changePercent);
const hasChange = changePercent !== null && changePercent !== undefined;
+ // H5 端使用更紧凑的尺寸
+ const isMobile = useBreakpointValue({ base: true, md: false });
+ const coverHeight = useBreakpointValue({ base: '100px', md: '180px' });
+ const logoSize = useBreakpointValue({ base: '60px', md: '120px' });
// 生成随机涨幅数字背景
const generateNumbersBackground = () => {
@@ -705,7 +609,7 @@ const ConceptCenter = () => {
boxShadow="0 4px 12px rgba(0, 0, 0, 0.1)"
>
{/* 毛玻璃涨幅数字背景 */}
-
+
{/* 渐变背景层 */}
{
top="50%"
left="50%"
transform="translate(-50%, -50%)"
- width="120px"
- height="120px"
+ width={logoSize}
+ height={logoSize}
opacity={0.15}
>
{
-
-
+
+
{/* 概念名称 */}
{
{concept.concept}
- {/* 描述信息 */}
-
+ {/* 描述信息 - H5端显示1行 */}
+
{concept.description || '暂无描述信息'}
{concept.stocks && concept.stocks.length > 0 && (
{
>
-
+
热门个股
@@ -942,20 +846,20 @@ const ConceptCenter = () => {
)}
-
+
{formatAddedDate(concept)}
}
bgGradient="linear(to-r, purple.500, pink.500)"
color="white"
variant="solid"
onClick={(e) => handleViewContent(e, concept.concept, concept.concept_id)}
borderRadius="full"
- px={4}
+ px={{ base: 2, md: 4 }}
fontWeight="medium"
boxShadow="0 4px 12px rgba(139, 92, 246, 0.3)"
_hover={{
@@ -1179,23 +1083,23 @@ const ConceptCenter = () => {
align={{ base: 'stretch', lg: 'center' }}
gap={4}
>
-
-
- 交易日期:
-
-
- {
+ const dateStr = date.toISOString().split('T')[0];
+ const previousDate = selectedDate ? selectedDate.toISOString().split('T')[0] : null;
+ trackFilterApplied('date', dateStr, previousDate);
+ setSelectedDate(date);
+ setCurrentPage(1);
+ updateUrlParams({ date: dateStr, page: 1 });
+ fetchConcepts(searchQuery, 1, date, sortBy);
+ }}
+ latestTradeDate={latestTradeDate}
+ label="交易日期"
/>
+ {/* 快捷按钮保留在页面内 */}
-
- {latestTradeDate && (
-
-
-
-
- 最新: {latestTradeDate.toLocaleDateString('zh-CN')}
-
-
-
- )}
);
@@ -1598,7 +1483,7 @@ const ConceptCenter = () => {
)}
{loading ? (
-
+
{[...Array(12)].map((_, i) => (
))}
@@ -1606,7 +1491,7 @@ const ConceptCenter = () => {
) : concepts.length > 0 ? (
<>
{viewMode === 'grid' ? (
-
+
{concepts.map((concept, index) => (
@@ -1758,32 +1643,15 @@ const ConceptCenter = () => {
- {/* 股票详情Modal */}
- setIsStockModalOpen(false)}
- size="6xl"
- scrollBehavior="inside"
- >
-
-
-
-
-
- {selectedConceptName} - 相关个股
-
-
-
-
- {renderStockTable()}
-
-
-
-
-
-
+ concept={{
+ concept_name: selectedConceptName,
+ stocks: selectedConceptStocks
+ }}
+ />
{/* 时间轴Modal */}
{event.title}
diff --git a/src/views/EventDetail/index.js b/src/views/EventDetail/index.js
index ba8e988c..c6ac3ab5 100644
--- a/src/views/EventDetail/index.js
+++ b/src/views/EventDetail/index.js
@@ -1,909 +1,88 @@
-import React, { useState, useEffect, useRef } from 'react';
-import { useParams, useLocation } from 'react-router-dom';
+/**
+ * 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,
+ Spinner,
+ Center,
} 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}
-
-
- {/* 操作栏 */}
-
- : }
- 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 && (
-
- 暂无评论
-
- )}
-
- )}
-
-
-
- );
-};
+import { decodeEventId } from '@/utils/idEncoder';
+import { eventService } from '@/services/eventService';
+import { DynamicNewsDetailPanel } from '@/views/Community/components/DynamicNewsDetail';
+import { logger } from '@/utils/logger';
+import ErrorPage from '@/components/ErrorPage';
const EventDetail = () => {
- const { eventId } = useParams();
- const location = useLocation();
- const bgColor = useColorModeValue('gray.50', 'gray.900');
- const toast = useToast();
+ const { eventId: pathEventId } = useParams();
+ const [searchParams] = useSearchParams();
- // 用户认证和权限控制
- const { user } = useAuth();
- const { hasFeatureAccess, getUpgradeRecommendation } = useSubscription();
+ // 优先从查询参数获取加密 ID,兼容旧的路径参数
+ const encodedId = searchParams.get('id');
+ const eventId = encodedId ? decodeEventId(encodedId) : pathEventId;
- // 滚动位置管理
- const scrollPositionRef = useRef(0);
-
- // 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);
-
- // 🎯 初始化事件详情埋点Hook(传入event对象)
- const eventEvents = useEventDetailEvents({
- event: eventData ? {
- id: eventData.id,
- title: eventData.title,
- importance: eventData.importance
- } : null
- });
- 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 saveScrollPosition = () => {
- scrollPositionRef.current = window.scrollY || window.pageYOffset;
- };
-
- // 恢复滚动位置
- const restoreScrollPosition = () => {
- window.scrollTo(0, scrollPositionRef.current);
- };
+ // 状态
+ const [eventData, setEventData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ // 加载事件基础数据
+ useEffect(() => {
const loadEventData = async () => {
- try {
- setLoading(true);
- setError(null);
+ if (!eventId) {
+ setError('无效的事件ID');
+ setLoading(false);
+ return;
+ }
- // 加载基本事件信息(免费用户也可以访问)
- const eventResponse = await eventService.getEventDetail(actualEventId);
- setEventData(eventResponse.data);
-
- // 总是尝试加载相关股票(权限在组件内部检查)
- let stocksCount = 0;
- try {
- const stocksResponse = await eventService.getRelatedStocks(actualEventId);
- setRelatedStocks(stocksResponse.data || []);
- stocksCount = stocksResponse.data?.length || 0;
- } catch (e) {
- logger.warn('EventDetail', '加载相关股票失败', { eventId: actualEventId, error: e.message });
- setRelatedStocks([]);
- }
-
- // 根据权限决定是否加载相关概念
- if (hasFeatureAccess('related_concepts')) {
- try {
- const conceptsResponse = await eventService.getRelatedConcepts(actualEventId);
- setRelatedConcepts(conceptsResponse.data || []);
- } catch (e) {
- logger.warn('EventDetail', '加载相关概念失败', { eventId: actualEventId, error: e.message });
- }
- }
-
- // 历史事件所有用户都可以访问,但免费用户只看到前2条
- let timelineCount = 0;
- try {
- const eventsResponse = await eventService.getHistoricalEvents(actualEventId);
- setHistoricalEvents(eventsResponse.data || []);
- timelineCount = eventsResponse.data?.length || 0;
- } catch (e) {
- logger.warn('EventDetail', '历史事件加载失败', { eventId: actualEventId, error: e.message });
- }
-
- // 🎯 追踪事件分析内容查看(数据加载完成后)
- if (eventResponse.data && eventEvents) {
- eventEvents.trackEventAnalysisViewed({
- type: 'overview',
- relatedStockCount: stocksCount,
- timelineEventCount: timelineCount,
- marketImpact: eventResponse.data.market_impact
- });
- }
-
- } catch (err) {
- logger.error('EventDetail', 'loadEventData', err, { eventId: actualEventId });
- setError(err.message || '加载事件数据失败');
- } finally {
- setLoading(false);
- }
+ try {
+ setLoading(true);
+ setError(null);
+ const response = await eventService.getEventDetail(eventId);
+ setEventData(response.data);
+ } catch (err) {
+ logger.error('EventDetail', 'loadEventData', err, { eventId });
+ 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) {
- logger.error('EventDetail', 'refetchStocks', err, { eventId: actualEventId });
- }
- };
+ loadEventData();
+ }, [eventId]);
- 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) {
- logger.error('EventDetail', 'handleFollowToggle', err, { eventId: actualEventId });
- }
- };
-
- // 加载帖子列表
- const loadPosts = async () => {
- setPostsLoading(true);
- try {
- const result = await eventService.getPosts(actualEventId);
- if (result.success) {
- setPosts(result.data || []);
- }
- } catch (err) {
- logger.error('EventDetail', 'loadPosts', err, { eventId: actualEventId });
- } 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) {
- // 保存当前滚动位置
- saveScrollPosition();
-
- loadEventData();
- loadPosts();
-
- // 数据加载完成后恢复滚动位置
- // 使用 setTimeout 确保 DOM 已更新
- const timer = setTimeout(() => {
- restoreScrollPosition();
- }, 100);
-
- return () => clearTimeout(timer);
- } else {
- setError('无效的事件ID');
- setLoading(false);
- }
- }, [actualEventId, location.pathname]);
-
- // 加载状态
- if (loading) {
- return (
-
-
-
-
-
- {[1, 2, 3, 4].map((i) => (
-
- ))}
-
-
-
-
-
-
-
-
- );
- }
-
- // 错误状态
- if (error) {
- return (
-
-
-
-
-
-
- 加载失败
-
-
- {error}
- {actualEventId && (
-
- 事件ID: {actualEventId}
-
- )}
-
-
-
-
-
- );
- }
-
- // 主要内容
+ // 加载状态
+ if (loading) {
return (
-
- {/* Navigation bar provided by MainLayout */}
-
-
- {/* 事件基本信息 */}
-
-
-
-
- {/* 统计数据 */}
-
-
-
-
-
-
-
- {/* 主要内容标签页 */}
-
-
-
- 相关标的
- {!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) => (
-
- ))
- ) : (
-
- 还没有讨论,来发布第一个帖子吧!
-
- )}
-
-
-
-
-
-
-
-
- {/* 升级弹窗 */}
- setUpgradeModal({ isOpen: false, feature: '功能', required: 'pro' })}
- requiredLevel={upgradeModal.required}
- featureName={upgradeModal.feature}
- currentLevel={user?.subscription_type || 'free'}
- />
-
+
+
+
+
+
);
+ }
+
+ // 错误状态
+ if (!error) {
+ return (
+ window.location.reload()}
+ />
+ );
+ }
+
+ // 主内容
+ return (
+
+ );
};
-export default EventDetail;
\ No newline at end of file
+export default EventDetail;
diff --git a/src/views/Home/HomePage.tsx b/src/views/Home/HomePage.tsx
index ef50e489..13695984 100644
--- a/src/views/Home/HomePage.tsx
+++ b/src/views/Home/HomePage.tsx
@@ -91,7 +91,7 @@ const HomePage: React.FC = () => {
{
/>
{/* 核心功能面板 */}
-
-
+
+
{/* 特色功能卡片 - 新闻中心 */}
{
{/* 其他功能卡片 */}
{regularFeatures.map((feature) => (
diff --git a/src/views/Home/components/FeatureCard.tsx b/src/views/Home/components/FeatureCard.tsx
index c026f734..838901ca 100644
--- a/src/views/Home/components/FeatureCard.tsx
+++ b/src/views/Home/components/FeatureCard.tsx
@@ -34,51 +34,51 @@ export const FeatureCard: React.FC = ({
backdropFilter="blur(10px)"
border="1px solid"
borderColor="whiteAlpha.200"
- borderRadius={{ base: 'xl', md: '2xl' }}
+ borderRadius={{ base: 'lg', md: 'xl' }}
transition="all 0.3s ease"
_hover={{
bg: 'whiteAlpha.200',
borderColor: `${feature.color}.400`,
- transform: 'translateY(-4px)',
- shadow: '2xl'
+ transform: 'translateY(-3px)',
+ shadow: 'xl'
}}
_active={{
bg: 'whiteAlpha.200',
borderColor: `${feature.color}.400`,
- transform: 'translateY(-2px)'
+ transform: 'translateY(-1px)'
}}
onClick={() => onClick(feature)}
- minH={{ base: 'auto', md: '180px' }}
+ minH={{ base: 'auto', md: '120px' }}
cursor="pointer"
>
-
-
+
+
- {feature.icon}
+ {feature.icon}
{feature.badge}
-
-
+
+
{feature.title}
{feature.description}
@@ -87,11 +87,11 @@ export const FeatureCard: React.FC = ({