// src/views/Dashboard/Center.js import React, { useEffect, useState, useCallback } from 'react'; import { logger } from '../../utils/logger'; import { getApiBase } from '../../utils/apiConfig'; import { useDashboardEvents } from '../../hooks/useDashboardEvents'; import { Box, Flex, Grid, SimpleGrid, Stack, Text, Badge, Button, VStack, HStack, Card, CardHeader, CardBody, Heading, useColorModeValue, Icon, IconButton, Stat, StatLabel, StatNumber, StatHelpText, StatArrow, Divider, Tag, TagLabel, TagLeftIcon, Wrap, WrapItem, Avatar, Tooltip, Progress, useToast, LinkBox, LinkOverlay, Spinner, Center, Image, } from '@chakra-ui/react'; import { useAuth } from '../../contexts/AuthContext'; import { useLocation, useNavigate, Link } from 'react-router-dom'; import { FiTrendingUp, FiEye, FiMessageSquare, FiThumbsUp, FiClock, FiCalendar, FiRefreshCw, FiTrash2, FiExternalLink, FiPlus, FiBarChart2, FiStar, FiActivity, FiAlertCircle, FiUsers, } from 'react-icons/fi'; import InvestmentPlanningCenter from './components/InvestmentPlanningCenter'; import { getEventDetailUrl } from '@/utils/idEncoder'; import MarketDashboard from '@views/Profile/components/MarketDashboard'; import StrategyCenter from '@views/Profile/components/StrategyCenter'; import ForumCenter from '@views/Profile/components/ForumCenter'; import WatchSidebar from '@views/Profile/components/WatchSidebar'; import { THEME } from '@views/Profile/components/MarketDashboard/constants'; export default function CenterDashboard() { const { user } = useAuth(); const location = useLocation(); const navigate = useNavigate(); const toast = useToast(); // ⚡ 提取 userId 为独立变量 const userId = user?.id; // 🎯 初始化Dashboard埋点Hook const dashboardEvents = useDashboardEvents({ pageType: 'center', navigate }); // 颜色主题 const textColor = useColorModeValue('gray.700', 'white'); const borderColor = useColorModeValue('gray.200', 'gray.600'); const bgColor = useColorModeValue('white', 'gray.800'); const hoverBg = useColorModeValue('gray.50', 'gray.700'); const secondaryText = useColorModeValue('gray.600', 'gray.400'); const cardBg = useColorModeValue('white', 'gray.800'); const sectionBg = useColorModeValue('gray.50', 'gray.900'); const [watchlist, setWatchlist] = useState([]); const [realtimeQuotes, setRealtimeQuotes] = useState({}); const [followingEvents, setFollowingEvents] = useState([]); const [eventComments, setEventComments] = useState([]); const [loading, setLoading] = useState(true); const [quotesLoading, setQuotesLoading] = useState(false); const loadData = useCallback(async () => { try { const base = getApiBase(); const ts = Date.now(); const [w, e, c] = await Promise.all([ fetch(base + `/api/account/watchlist?_=${ts}`, { credentials: 'include', cache: 'no-store' }), fetch(base + `/api/account/events/following?_=${ts}`, { credentials: 'include', cache: 'no-store' }), fetch(base + `/api/account/events/posts?_=${ts}`, { credentials: 'include', cache: 'no-store' }), ]); const jw = await w.json(); const je = await e.json(); const jc = await c.json(); if (jw.success) { const watchlistData = Array.isArray(jw.data) ? jw.data : []; setWatchlist(watchlistData); // 🎯 追踪自选股列表查看 if (watchlistData.length > 0) { dashboardEvents.trackWatchlistViewed(watchlistData.length, true); } // 加载实时行情 if (jw.data && jw.data.length > 0) { loadRealtimeQuotes(); } } if (je.success) { const eventsData = Array.isArray(je.data) ? je.data : []; setFollowingEvents(eventsData); // 🎯 追踪关注的事件列表查看 dashboardEvents.trackFollowingEventsViewed(eventsData.length); } if (jc.success) { const commentsData = Array.isArray(jc.data) ? jc.data : []; setEventComments(commentsData); // 🎯 追踪评论列表查看 dashboardEvents.trackCommentsViewed(commentsData.length); } } catch (err) { logger.error('Center', 'loadData', err, { userId, timestamp: new Date().toISOString() }); } finally { setLoading(false); } }, [userId]); // ⚡ 使用 userId 而不是 user?.id // 加载实时行情 const loadRealtimeQuotes = useCallback(async () => { try { setQuotesLoading(true); const base = getApiBase(); const response = await fetch(base + '/api/account/watchlist/realtime', { credentials: 'include', cache: 'no-store' }); if (response.ok) { const data = await response.json(); if (data.success) { const quotesMap = {}; data.data.forEach(item => { quotesMap[item.stock_code] = item; }); setRealtimeQuotes(quotesMap); } } } catch (error) { logger.error('Center', 'loadRealtimeQuotes', error, { userId: user?.id, watchlistLength: watchlist.length }); } finally { setQuotesLoading(false); } }, []); // 格式化日期 const formatDate = (dateString) => { if (!dateString) return ''; const date = new Date(dateString); const now = new Date(); const diffTime = Math.abs(now - date); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); if (diffDays < 1) { const diffHours = Math.ceil(diffTime / (1000 * 60 * 60)); if (diffHours < 1) { const diffMinutes = Math.ceil(diffTime / (1000 * 60)); return `${diffMinutes}分钟前`; } return `${diffHours}小时前`; } else if (diffDays < 7) { return `${diffDays}天前`; } else { return date.toLocaleDateString('zh-CN'); } }; // 格式化数字 const formatNumber = (num) => { if (!num) return '0'; if (num >= 10000) { return (num / 10000).toFixed(1) + 'w'; } else if (num >= 1000) { return (num / 1000).toFixed(1) + 'k'; } return num.toString(); }; // 获取事件热度颜色 const getHeatColor = (score) => { if (score >= 80) return 'red'; if (score >= 60) return 'orange'; if (score >= 40) return 'yellow'; return 'green'; }; // 🔧 使用 ref 跟踪是否已经加载过数据(首次加载标记) const hasLoadedRef = React.useRef(false); useEffect(() => { const isOnCenterPage = location.pathname.includes('/home/center'); // 首次进入页面且有用户时加载数据 if (user && isOnCenterPage && !hasLoadedRef.current) { console.log('[Center] 🚀 首次加载数据'); hasLoadedRef.current = true; loadData(); } const onVis = () => { if (document.visibilityState === 'visible' && location.pathname.includes('/home/center')) { console.log('[Center] 👁️ visibilitychange 触发 loadData'); loadData(); } }; document.addEventListener('visibilitychange', onVis); return () => document.removeEventListener('visibilitychange', onVis); }, [userId, location.pathname, loadData, user]); // 当用户登出再登入(userId 变化)时,重置加载标记 useEffect(() => { if (!user) { hasLoadedRef.current = false; } }, [user]); // 定时刷新实时行情(每分钟一次) useEffect(() => { if (watchlist.length > 0) { const interval = setInterval(() => { loadRealtimeQuotes(); }, 60000); // 60秒刷新一次 return () => clearInterval(interval); } }, [watchlist.length, loadRealtimeQuotes]); // 渲染加载状态 if (loading) { return (