refactor(Center): 大幅简化,移除侧边栏逻辑
- 移除 WatchSidebar 相关代码(已移至全局 GlobalSidebar) - 移除数据加载逻辑(由 GlobalSidebarContext 统一管理) - 移除 useAuth、useLocation、useNavigate 等依赖 - 保留核心功能:MarketDashboard、ForumCenter、InvestmentPlanningCenter - 代码从 ~260 行精简至 ~40 行 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5,261 +5,38 @@
|
|||||||
* 功能:自选股监控、关注事件、投资规划等
|
* 功能:自选股监控、关注事件、投资规划等
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
import React from 'react';
|
||||||
import { logger } from '@/utils/logger';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { getApiBase } from '@/utils/apiConfig';
|
|
||||||
import { useDashboardEvents } from '@/hooks/useDashboardEvents';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Flex,
|
|
||||||
Text,
|
|
||||||
VStack,
|
|
||||||
useToast,
|
|
||||||
Spinner,
|
|
||||||
Center,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { useCenterColors } from './hooks';
|
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
|
||||||
import InvestmentPlanningCenter from './components/InvestmentPlanningCenter';
|
import InvestmentPlanningCenter from './components/InvestmentPlanningCenter';
|
||||||
import { getEventDetailUrl } from '@/utils/idEncoder';
|
|
||||||
import MarketDashboard from '@views/Profile/components/MarketDashboard';
|
import MarketDashboard from '@views/Profile/components/MarketDashboard';
|
||||||
import ForumCenter from '@views/Profile/components/ForumCenter';
|
import ForumCenter from '@views/Profile/components/ForumCenter';
|
||||||
import WatchSidebar from '@views/Profile/components/WatchSidebar';
|
|
||||||
import { THEME } from '@views/Profile/components/MarketDashboard/constants';
|
import { THEME } from '@views/Profile/components/MarketDashboard/constants';
|
||||||
|
|
||||||
import type {
|
|
||||||
WatchlistItem,
|
|
||||||
RealtimeQuotesMap,
|
|
||||||
FollowingEvent,
|
|
||||||
EventComment,
|
|
||||||
WatchlistApiResponse,
|
|
||||||
RealtimeQuotesApiResponse,
|
|
||||||
FollowingEventsApiResponse,
|
|
||||||
EventCommentsApiResponse,
|
|
||||||
DashboardEventsResult,
|
|
||||||
} from '@/types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CenterDashboard 组件
|
* CenterDashboard 组件
|
||||||
* 个人中心仪表板主页面
|
* 个人中心仪表板主页面
|
||||||
|
*
|
||||||
|
* 注意:右侧 WatchSidebar 已移至全局 GlobalSidebar(在 MainLayout 中渲染)
|
||||||
*/
|
*/
|
||||||
const CenterDashboard: React.FC = () => {
|
const CenterDashboard: React.FC = () => {
|
||||||
const { user } = useAuth();
|
|
||||||
const location = useLocation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
// 提取 userId 为独立变量(优化依赖项)
|
|
||||||
const userId = user?.id;
|
|
||||||
|
|
||||||
// 初始化 Dashboard 埋点 Hook(类型断言为 DashboardEventsResult)
|
|
||||||
const dashboardEvents = useDashboardEvents({
|
|
||||||
pageType: 'center',
|
|
||||||
navigate
|
|
||||||
}) as DashboardEventsResult;
|
|
||||||
|
|
||||||
// 颜色主题(使用 useCenterColors 封装,避免 7 次 useColorModeValue 调用)
|
|
||||||
const { secondaryText } = useCenterColors();
|
|
||||||
|
|
||||||
// 数据状态
|
|
||||||
const [watchlist, setWatchlist] = useState<WatchlistItem[]>([]);
|
|
||||||
const [realtimeQuotes, setRealtimeQuotes] = useState<RealtimeQuotesMap>({});
|
|
||||||
const [followingEvents, setFollowingEvents] = useState<FollowingEvent[]>([]);
|
|
||||||
const [eventComments, setEventComments] = useState<EventComment[]>([]);
|
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
|
||||||
const [quotesLoading, setQuotesLoading] = useState<boolean>(false);
|
|
||||||
|
|
||||||
// 使用 ref 跟踪是否已经加载过数据(首次加载标记)
|
|
||||||
const hasLoadedRef = useRef<boolean>(false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载实时行情
|
|
||||||
*/
|
|
||||||
const loadRealtimeQuotes = useCallback(async (): Promise<void> => {
|
|
||||||
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: RealtimeQuotesApiResponse = await response.json();
|
|
||||||
if (data.success) {
|
|
||||||
const quotesMap: RealtimeQuotesMap = {};
|
|
||||||
data.data.forEach(item => {
|
|
||||||
quotesMap[item.stock_code] = item;
|
|
||||||
});
|
|
||||||
setRealtimeQuotes(quotesMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Center', 'loadRealtimeQuotes', error, {
|
|
||||||
userId,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setQuotesLoading(false);
|
|
||||||
}
|
|
||||||
}, [userId]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载所有数据(自选股、关注事件、评论)
|
|
||||||
*/
|
|
||||||
const loadData = useCallback(async (): Promise<void> => {
|
|
||||||
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: WatchlistApiResponse = await w.json();
|
|
||||||
const je: FollowingEventsApiResponse = await e.json();
|
|
||||||
const jc: EventCommentsApiResponse = 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, loadRealtimeQuotes, dashboardEvents]);
|
|
||||||
|
|
||||||
// 首次加载和页面可见性变化时加载数据
|
|
||||||
useEffect(() => {
|
|
||||||
const isOnCenterPage = location.pathname.includes('/home/center');
|
|
||||||
|
|
||||||
// 首次进入页面且有用户时加载数据
|
|
||||||
if (user && isOnCenterPage && !hasLoadedRef.current) {
|
|
||||||
console.log('[Center] 🚀 首次加载数据');
|
|
||||||
hasLoadedRef.current = true;
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
const onVis = (): void => {
|
|
||||||
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 (
|
|
||||||
<Center h="60vh">
|
|
||||||
<VStack spacing={4}>
|
|
||||||
<Spinner size="xl" color="blue.500" thickness="4px" />
|
|
||||||
<Text color={secondaryText}>加载个人中心数据...</Text>
|
|
||||||
</VStack>
|
|
||||||
</Center>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box bg={THEME.bg.primary} minH="100vh" overflowX="hidden">
|
<Box bg={THEME.bg.primary} minH="100vh" overflowX="hidden">
|
||||||
<Box px={{ base: 3, md: 4 }} py={{ base: 4, md: 6 }} maxW="container.xl" mx="auto">
|
<Box px={{ base: 3, md: 4 }} py={{ base: 4, md: 6 }} maxW="container.xl" mx="auto">
|
||||||
{/* 左右布局:左侧自适应,右侧固定200px */}
|
{/* 市场概览仪表盘 */}
|
||||||
<Flex gap={4}>
|
<Box mb={4}>
|
||||||
{/* 左侧主内容区 */}
|
<MarketDashboard />
|
||||||
<Box flex={1} minW={0}>
|
</Box>
|
||||||
{/* 市场概览仪表盘 */}
|
|
||||||
<Box mb={4}>
|
|
||||||
<MarketDashboard />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 价值论坛 / 互动中心 */}
|
{/* 价值论坛 / 互动中心 */}
|
||||||
<Box mb={4}>
|
<Box mb={4}>
|
||||||
<ForumCenter />
|
<ForumCenter />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 投资规划中心(整合了日历、计划、复盘,应用 FUI 毛玻璃风格) */}
|
{/* 投资规划中心(整合了日历、计划、复盘,应用 FUI 毛玻璃风格) */}
|
||||||
<Box>
|
<Box>
|
||||||
<InvestmentPlanningCenter />
|
<InvestmentPlanningCenter />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 右侧固定宽度侧边栏 */}
|
|
||||||
<Box
|
|
||||||
w={{ base: '100%', md: '300px' }}
|
|
||||||
flexShrink={0}
|
|
||||||
display={{ base: 'none', md: 'block' }}
|
|
||||||
position="sticky"
|
|
||||||
top="80px"
|
|
||||||
alignSelf="flex-start"
|
|
||||||
>
|
|
||||||
<WatchSidebar
|
|
||||||
watchlist={watchlist}
|
|
||||||
realtimeQuotes={realtimeQuotes}
|
|
||||||
followingEvents={followingEvents}
|
|
||||||
eventComments={eventComments}
|
|
||||||
onStockClick={(stock: WatchlistItem) => navigate(`/company/${stock.stock_code}`)}
|
|
||||||
onEventClick={(event: FollowingEvent) => navigate(getEventDetailUrl(event.id))}
|
|
||||||
onCommentClick={(comment: EventComment) => navigate(getEventDetailUrl(comment.event_id))}
|
|
||||||
onAddStock={() => navigate('/stocks')}
|
|
||||||
onAddEvent={() => navigate('/community')}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user