feat: 路由改造

This commit is contained in:
zdl
2025-10-17 18:59:00 +08:00
parent 02bf1ea709
commit bae4d25e24
15 changed files with 496 additions and 227 deletions

View File

@@ -26,6 +26,7 @@ import PageLoader from "components/Loading/PageLoader";
import Admin from "layouts/Admin";
import Auth from "layouts/Auth";
import HomeLayout from "layouts/Home";
import MainLayout from "layouts/MainLayout";
// ⚡ 使用 React.lazy() 实现路由懒加载
// 首屏不需要的组件按需加载,大幅减少初始 JS 包大小
@@ -48,95 +49,113 @@ import { AuthModalProvider } from "contexts/AuthModalContext";
import ProtectedRoute from "components/ProtectedRoute";
import ErrorBoundary from "components/ErrorBoundary";
import AuthModalManager from "components/Auth/AuthModalManager";
import ScrollToTop from "components/ScrollToTop";
function AppContent() {
const { colorMode } = useColorMode();
return (
<Box minH="100vh" bg={colorMode === 'dark' ? 'gray.800' : 'white'}>
{/* ⚡ Suspense 边界:懒加载组件加载时显示 Loading */}
<Suspense fallback={<PageLoader message="页面加载中..." />}>
<Routes>
{/* 首页路由 */}
<Route path="home/*" element={<HomeLayout />} />
{/* 路由切换时自动滚动到顶部 */}
<ScrollToTop />
<Routes>
{/* 带导航栏的主布局 - 所有需要导航栏的页面都在这里 */}
{/* MainLayout 内部有 Suspense确保导航栏始终可见 */}
<Route element={<MainLayout />}>
{/* 首页路由 */}
<Route path="home/*" element={<HomeLayout />} />
{/* Community页面路由 - 需要登录 */}
<Route
path="community"
element={
<ProtectedRoute>
<Community />
</ProtectedRoute>
}
/>
<Route path="forecast-report" element={<ForecastReport />} />
<Route path="Financial" element={<FinancialPanorama />} />
<Route path="company" element={<CompanyIndex />} />
<Route path="company/:code" element={<CompanyIndex />} />
<Route path="market-data" element={<MarketDataView />} />
{/* 概念中心路由 - 需要登录 */}
<Route
path="concepts"
element={
<ProtectedRoute>
<ConceptCenter />
</ProtectedRoute>
}
/>
<Route
path="concept"
element={
<ProtectedRoute>
<ConceptCenter />
</ProtectedRoute>
}
/>
{/* 股票概览页面路由 - 需要登录 */}
<Route
path="stocks"
element={
<ProtectedRoute>
<StockOverview />
</ProtectedRoute>
}
/>
<Route
path="stock-overview"
element={
<ProtectedRoute>
<StockOverview />
</ProtectedRoute>
}
/>
{/* Limit Analyse页面路由 - 需要登录 */}
<Route
path="limit-analyse"
element={
<ProtectedRoute>
<LimitAnalyse />
</ProtectedRoute>
}
/>
{/* 事件详情独立页面路由(不经 Admin 布局) */}
<Route path="event-detail/:eventId" element={<EventDetail />} />
{/* Community页面路由 - 需要登录 */}
<Route
path="community"
element={
<ProtectedRoute>
<Community />
</ProtectedRoute>
}
/>
{/* 模拟盘交易系统路由 - 需登录 */}
<Route
path="trading-simulation"
element={<TradingSimulation />}
/>
{/* 概念中心路由 - 需登录 */}
<Route
path="concepts"
element={
<ProtectedRoute>
<ConceptCenter />
</ProtectedRoute>
}
/>
<Route
path="concept"
element={
<ProtectedRoute>
<ConceptCenter />
</ProtectedRoute>
}
/>
{/* 管理后台路由 - 需要登录 */}
{/* 股票概览页面路由 - 需要登录 */}
<Route
path="stocks"
element={
<ProtectedRoute>
<StockOverview />
</ProtectedRoute>
}
/>
<Route
path="stock-overview"
element={
<ProtectedRoute>
<StockOverview />
</ProtectedRoute>
}
/>
{/* Limit Analyse页面路由 - 需要登录 */}
<Route
path="limit-analyse"
element={
<ProtectedRoute>
<LimitAnalyse />
</ProtectedRoute>
}
/>
{/* 模拟盘交易系统路由 - 需要登录 */}
<Route
path="trading-simulation"
element={
<ProtectedRoute>
<TradingSimulation />
</ProtectedRoute>
}
/>
{/* 事件详情独立页面路由 (不经 Admin 布局) */}
<Route path="event-detail/:eventId" element={<EventDetail />} />
{/* 公司相关页面 */}
<Route path="forecast-report" element={<ForecastReport />} />
<Route path="Financial" element={<FinancialPanorama />} />
<Route path="company" element={<CompanyIndex />} />
<Route path="company/:code" element={<CompanyIndex />} />
<Route path="market-data" element={<MarketDataView />} />
</Route>
{/* 管理后台路由 - 需要登录,不使用 MainLayout */}
{/* 这些路由有自己的加载状态处理 */}
<Route
path="admin/*"
element={
<ProtectedRoute>
<Admin />
</ProtectedRoute>
<Suspense fallback={<PageLoader message="加载中..." />}>
<ProtectedRoute>
<Admin />
</ProtectedRoute>
</Suspense>
}
/>
{/* 认证页面路由 */}
{/* 认证页面路由 - 不使用 MainLayout */}
<Route path="auth/*" element={<Auth />} />
{/* 默认重定向到首页 */}
@@ -145,7 +164,6 @@ function AppContent() {
{/* 404 页面 */}
<Route path="*" element={<Navigate to="/home" replace />} />
</Routes>
</Suspense>
</Box>
);
}

View File

@@ -34,6 +34,7 @@ import { authService } from "../../services/authService";
import AuthHeader from './AuthHeader';
import VerificationCodeInput from './VerificationCodeInput';
import WechatRegister from './WechatRegister';
import { setCurrentUser } from '../../mocks/data/users';
// 统一配置对象
const AUTH_CONFIG = {
@@ -261,6 +262,12 @@ export default function AuthFormContent() {
}
if (response.ok && data.success) {
// ⚡ Mock 模式:先在前端侧写入 localStorage确保时序正确
if (process.env.REACT_APP_ENABLE_MOCK === 'true' && data.user) {
setCurrentUser(data.user);
console.log('[Auth] 前端侧设置当前用户Mock模式:', data.user);
}
// 更新session
await checkSession();

View File

@@ -76,6 +76,14 @@ const SecondaryNav = () => {
{ path: '/stocks', label: '个股中心', badges: [{ text: 'HOT', colorScheme: 'green' }] },
{ path: '/trading-simulation', label: '模拟盘', badges: [{ text: 'NEW', colorScheme: 'red' }] }
]
},
'/trading-simulation': {
title: '行情复盘',
items: [
{ path: '/limit-analyse', label: '涨停分析', badges: [{ text: 'FREE', colorScheme: 'blue' }] },
{ path: '/stocks', label: '个股中心', badges: [{ text: 'HOT', colorScheme: 'green' }] },
{ path: '/trading-simulation', label: '模拟盘', badges: [{ text: 'NEW', colorScheme: 'red' }] }
]
}
};
@@ -181,9 +189,9 @@ const NavItems = ({ isAuthenticated, user }) => {
const location = useLocation();
// 辅助函数:判断导航项是否激活
const isActive = (paths) => {
const isActive = useCallback((paths) => {
return paths.some(path => location.pathname.includes(path));
};
}, [location.pathname]);
if (isAuthenticated && user) {
return (
@@ -203,45 +211,35 @@ const NavItems = ({ isAuthenticated, user }) => {
高频跟踪
</MenuButton>
<MenuList minW="260px" p={2}>
<VStack spacing={1} align="stretch">
<Link
onClick={() => navigate('/community')}
py={2}
px={3}
borderRadius="md"
_hover={{ bg: 'gray.100' }}
cursor="pointer"
bg={location.pathname.includes('/community') ? 'blue.50' : 'transparent'}
borderLeft={location.pathname.includes('/community') ? '3px solid' : 'none'}
borderColor="blue.600"
fontWeight={location.pathname.includes('/community') ? 'bold' : 'normal'}
>
<Flex justify="space-between" align="center">
<Text fontSize="sm">新闻催化分析</Text>
<HStack spacing={1}>
<Badge size="sm" colorScheme="green">HOT</Badge>
<Badge size="sm" colorScheme="red">NEW</Badge>
</HStack>
</Flex>
</Link>
<Link
onClick={() => navigate('/concepts')}
py={2}
px={3}
borderRadius="md"
_hover={{ bg: 'gray.100' }}
cursor="pointer"
bg={location.pathname.includes('/concepts') ? 'blue.50' : 'transparent'}
borderLeft={location.pathname.includes('/concepts') ? '3px solid' : 'none'}
borderColor="blue.600"
fontWeight={location.pathname.includes('/concepts') ? 'bold' : 'normal'}
>
<Flex justify="space-between" align="center">
<Text fontSize="sm">概念中心</Text>
<MenuItem
onClick={() => navigate('/community')}
borderRadius="md"
bg={location.pathname.includes('/community') ? 'blue.50' : 'transparent'}
borderLeft={location.pathname.includes('/community') ? '3px solid' : 'none'}
borderColor="blue.600"
fontWeight={location.pathname.includes('/community') ? 'bold' : 'normal'}
>
<Flex justify="space-between" align="center" w="100%">
<Text fontSize="sm">新闻催化分析</Text>
<HStack spacing={1}>
<Badge size="sm" colorScheme="green">HOT</Badge>
<Badge size="sm" colorScheme="red">NEW</Badge>
</Flex>
</Link>
</VStack>
</HStack>
</Flex>
</MenuItem>
<MenuItem
onClick={() => navigate('/concepts')}
borderRadius="md"
bg={location.pathname.includes('/concepts') ? 'blue.50' : 'transparent'}
borderLeft={location.pathname.includes('/concepts') ? '3px solid' : 'none'}
borderColor="blue.600"
fontWeight={location.pathname.includes('/concepts') ? 'bold' : 'normal'}
>
<Flex justify="space-between" align="center" w="100%">
<Text fontSize="sm">概念中心</Text>
<Badge size="sm" colorScheme="red">NEW</Badge>
</Flex>
</MenuItem>
</MenuList>
</Menu>
@@ -260,59 +258,45 @@ const NavItems = ({ isAuthenticated, user }) => {
行情复盘
</MenuButton>
<MenuList minW="260px" p={2}>
<VStack spacing={1} align="stretch">
<Link
onClick={() => navigate('/limit-analyse')}
py={2}
px={3}
borderRadius="md"
_hover={{ bg: 'gray.100' }}
cursor="pointer"
bg={location.pathname.includes('/limit-analyse') ? 'blue.50' : 'transparent'}
borderLeft={location.pathname.includes('/limit-analyse') ? '3px solid' : 'none'}
borderColor="blue.600"
fontWeight={location.pathname.includes('/limit-analyse') ? 'bold' : 'normal'}
>
<Flex justify="space-between" align="center">
<Text fontSize="sm">涨停分析</Text>
<Badge size="sm" colorScheme="blue">FREE</Badge>
</Flex>
</Link>
<Link
onClick={() => navigate('/stocks')}
py={2}
px={3}
borderRadius="md"
_hover={{ bg: 'gray.100' }}
cursor="pointer"
bg={location.pathname.includes('/stocks') ? 'blue.50' : 'transparent'}
borderLeft={location.pathname.includes('/stocks') ? '3px solid' : 'none'}
borderColor="blue.600"
fontWeight={location.pathname.includes('/stocks') ? 'bold' : 'normal'}
>
<Flex justify="space-between" align="center">
<Text fontSize="sm">个股中心</Text>
<Badge size="sm" colorScheme="green">HOT</Badge>
</Flex>
</Link>
<Link
onClick={() => navigate('/trading-simulation')}
py={2}
px={3}
borderRadius="md"
_hover={{ bg: 'gray.100' }}
cursor="pointer"
bg={location.pathname.includes('/trading-simulation') ? 'blue.50' : 'transparent'}
borderLeft={location.pathname.includes('/trading-simulation') ? '3px solid' : 'none'}
borderColor="blue.600"
fontWeight={location.pathname.includes('/trading-simulation') ? 'bold' : 'normal'}
>
<Flex justify="space-between" align="center">
<Text fontSize="sm">模拟盘</Text>
<Badge size="sm" colorScheme="red">NEW</Badge>
</Flex>
</Link>
</VStack>
<MenuItem
onClick={() => navigate('/limit-analyse')}
borderRadius="md"
bg={location.pathname.includes('/limit-analyse') ? 'blue.50' : 'transparent'}
borderLeft={location.pathname.includes('/limit-analyse') ? '3px solid' : 'none'}
borderColor="blue.600"
fontWeight={location.pathname.includes('/limit-analyse') ? 'bold' : 'normal'}
>
<Flex justify="space-between" align="center" w="100%">
<Text fontSize="sm">涨停分析</Text>
<Badge size="sm" colorScheme="blue">FREE</Badge>
</Flex>
</MenuItem>
<MenuItem
onClick={() => navigate('/stocks')}
borderRadius="md"
bg={location.pathname.includes('/stocks') ? 'blue.50' : 'transparent'}
borderLeft={location.pathname.includes('/stocks') ? '3px solid' : 'none'}
borderColor="blue.600"
fontWeight={location.pathname.includes('/stocks') ? 'bold' : 'normal'}
>
<Flex justify="space-between" align="center" w="100%">
<Text fontSize="sm">个股中心</Text>
<Badge size="sm" colorScheme="green">HOT</Badge>
</Flex>
</MenuItem>
<MenuItem
onClick={() => navigate('/trading-simulation')}
borderRadius="md"
bg={location.pathname.includes('/trading-simulation') ? 'blue.50' : 'transparent'}
borderLeft={location.pathname.includes('/trading-simulation') ? '3px solid' : 'none'}
borderColor="blue.600"
fontWeight={location.pathname.includes('/trading-simulation') ? 'bold' : 'normal'}
>
<Flex justify="space-between" align="center" w="100%">
<Text fontSize="sm">模拟盘</Text>
<Badge size="sm" colorScheme="red">NEW</Badge>
</Flex>
</MenuItem>
</MenuList>
</Menu>
@@ -327,30 +311,20 @@ const NavItems = ({ isAuthenticated, user }) => {
AGENT社群
</MenuButton>
<MenuList minW="300px" p={4}>
<VStack spacing={2} align="stretch">
<Link
py={2}
px={3}
borderRadius="md"
_hover={{}}
cursor="not-allowed"
color="gray.400"
pointerEvents="none"
>
<Text fontSize="sm" color="gray.400">今日热议</Text>
</Link>
<Link
py={2}
px={3}
borderRadius="md"
_hover={{}}
cursor="not-allowed"
color="gray.400"
pointerEvents="none"
>
<Text fontSize="sm" color="gray.400">个股社区</Text>
</Link>
</VStack>
<MenuItem
isDisabled
cursor="not-allowed"
color="gray.400"
>
<Text fontSize="sm" color="gray.400">今日热议</Text>
</MenuItem>
<MenuItem
isDisabled
cursor="not-allowed"
color="gray.400"
>
<Text fontSize="sm" color="gray.400">个股社区</Text>
</MenuItem>
</MenuList>
</Menu>
@@ -373,7 +347,7 @@ const NavItems = ({ isAuthenticated, user }) => {
} else {
return null;
}
}
};
// 计算 API 基础地址(移到组件外部,避免每次 render 重新创建)
const getApiBase = () => (process.env.NODE_ENV === 'production' ? '' : (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001'));

View File

@@ -0,0 +1,27 @@
// src/components/ScrollToTop/index.js
// 路由切换时自动滚动到页面顶部
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
/**
* ScrollToTop - 路由切换时自动滚动到顶部
*
* 使用方式:在 App.js 的 Router 内部添加此组件
*
* @example
* <BrowserRouter>
* <ScrollToTop />
* <Routes>...</Routes>
* </BrowserRouter>
*/
export default function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
// 路径改变时滚动到顶部
window.scrollTo(0, 0);
}, [pathname]);
return null;
}

View File

@@ -3,8 +3,8 @@ import React from "react";
import { Routes, Route } from "react-router-dom";
import { Box } from '@chakra-ui/react';
// 导入导航栏组件 - 使用我们之前修复的版本
import HomeNavbar from "../components/Navbars/HomeNavbar";
// 导航栏已由 MainLayout 提供,此处不再导入
// import HomeNavbar from "../components/Navbars/HomeNavbar";
// 导入页面组件
import HomePage from "views/Home/HomePage";
@@ -25,8 +25,7 @@ import ProtectedRoute from "../components/ProtectedRoute";
export default function Home() {
return (
<Box minH="100vh">
{/* 导航栏 - 这是关键确保HomeNavbar被正确包含 */}
<HomeNavbar />
{/* 导航栏已由 MainLayout 提供,此处不再渲染 */}
{/* 主要内容区域 */}
<Box>

31
src/layouts/MainLayout.js Normal file
View File

@@ -0,0 +1,31 @@
// src/layouts/MainLayout.js
// 主布局组件 - 为所有带导航栏的页面提供统一布局
import React, { Suspense } from "react";
import { Outlet } from "react-router-dom";
import { Box } from '@chakra-ui/react';
import HomeNavbar from "../components/Navbars/HomeNavbar";
import PageLoader from "../components/Loading/PageLoader";
/**
* MainLayout - 带导航栏的主布局
*
* 使用 <Outlet /> 渲染子路由,确保导航栏只渲染一次
* 页面切换时只有 Outlet 内的内容会更新,导航栏保持不变
* Suspense 边界确保导航栏始终可见,只有内容区域显示 loading
*/
export default function MainLayout() {
return (
<Box minH="100vh">
{/* 导航栏 - 在所有页面间共享,不会重新渲染 */}
<HomeNavbar />
{/* 页面内容区域 - 通过 Outlet 渲染当前路由对应的组件 */}
{/* Suspense 只包裹内容区域,导航栏保持可见 */}
<Box>
<Suspense fallback={<PageLoader message="页面加载中..." />}>
<Outlet />
</Suspense>
</Box>
</Box>
);
}

172
src/mocks/data/events.js Normal file
View File

@@ -0,0 +1,172 @@
// Mock 事件相关数据
// Mock 股票池 - 常见的A股股票
const stockPool = [
{ stock_code: '600000.SH', stock_name: '浦发银行', industry: '银行' },
{ stock_code: '600519.SH', stock_name: '贵州茅台', industry: '白酒' },
{ stock_code: '600036.SH', stock_name: '招商银行', industry: '银行' },
{ stock_code: '601318.SH', stock_name: '中国平安', industry: '保险' },
{ stock_code: '600016.SH', stock_name: '民生银行', industry: '银行' },
{ stock_code: '601398.SH', stock_name: '工商银行', industry: '银行' },
{ stock_code: '601288.SH', stock_name: '农业银行', industry: '银行' },
{ stock_code: '601166.SH', stock_name: '兴业银行', industry: '银行' },
{ stock_code: '000001.SZ', stock_name: '平安银行', industry: '银行' },
{ stock_code: '000002.SZ', stock_name: '万科A', industry: '房地产' },
{ stock_code: '000858.SZ', stock_name: '五粮液', industry: '白酒' },
{ stock_code: '000333.SZ', stock_name: '美的集团', industry: '家电' },
{ stock_code: '002415.SZ', stock_name: '海康威视', industry: '安防' },
{ stock_code: '002594.SZ', stock_name: 'BYD比亚迪', industry: '新能源汽车' },
{ stock_code: '300750.SZ', stock_name: '宁德时代', industry: '新能源' },
{ stock_code: '300059.SZ', stock_name: '东方财富', industry: '证券' },
{ stock_code: '601888.SH', stock_name: '中国中免', industry: '免税' },
{ stock_code: '600276.SH', stock_name: '恒瑞医药', industry: '医药' },
{ stock_code: '600887.SH', stock_name: '伊利股份', industry: '乳制品' },
{ stock_code: '601012.SH', stock_name: '隆基绿能', industry: '光伏' },
{ stock_code: '688981.SH', stock_name: '中芯国际', industry: '半导体' },
{ stock_code: '600309.SH', stock_name: '万华化学', industry: '化工' },
{ stock_code: '603259.SH', stock_name: '药明康德', industry: '医药研发' },
{ stock_code: '002475.SZ', stock_name: '立讯精密', industry: '电子' },
{ stock_code: '000063.SZ', stock_name: '中兴通讯', industry: '通信设备' },
];
// 关联描述模板
const relationDescTemplates = [
'主营业务直接相关,预计受事件影响较大',
'产业链上游企业,间接受益',
'产业链下游企业,需求端受影响',
'同行业竞争对手,可能受到间接影响',
'参股该领域相关企业,有一定关联性',
'业务板块涉及相关领域,预计有正面影响',
'控股子公司从事相关业务',
'近期公告布局该领域,潜在受益标的',
'行业龙头企业,市场关注度高',
'技术储备充足,有望抢占市场先机',
'已有成熟产品线,短期内可能受益',
'战略转型方向与事件相关',
];
// 生成随机关联股票数据
export function generateRelatedStocks(eventId, count = 5) {
// 使用事件ID作为随机种子确保相同事件ID返回相同的股票列表
const seed = parseInt(eventId) || 1;
const selectedStocks = [];
// 伪随机选择股票基于事件ID
for (let i = 0; i < Math.min(count, stockPool.length); i++) {
const index = (seed * 17 + i * 13) % stockPool.length;
const stock = stockPool[index];
const descIndex = (seed * 7 + i * 11) % relationDescTemplates.length;
selectedStocks.push({
stock_code: stock.stock_code,
stock_name: stock.stock_name,
relation_desc: relationDescTemplates[descIndex],
industry: stock.industry,
// 可选字段 - 用于前端显示更多信息
relevance_score: Math.max(60, 100 - i * 8), // 相关性评分,递减
impact_level: i < 2 ? 'high' : i < 4 ? 'medium' : 'low', // 影响程度
});
}
return selectedStocks;
}
// Mock 事件相关股票数据映射
// 这里可以为特定事件ID预设特定的股票列表
export const mockEventStocks = {
// 示例事件ID为1的预设股票
'1': [
{
stock_code: '600519.SH',
stock_name: '贵州茅台',
relation_desc: '白酒行业龙头,消费板块受政策影响',
industry: '白酒',
relevance_score: 95,
impact_level: 'high',
},
{
stock_code: '000858.SZ',
stock_name: '五粮液',
relation_desc: '白酒行业第二梯队,同样受消费政策影响',
industry: '白酒',
relevance_score: 90,
impact_level: 'high',
},
{
stock_code: '600887.SH',
stock_name: '伊利股份',
relation_desc: '消费品龙头,受益于消费复苏',
industry: '乳制品',
relevance_score: 75,
impact_level: 'medium',
},
],
// 事件ID为2的预设股票科技类
'2': [
{
stock_code: '002415.SZ',
stock_name: '海康威视',
relation_desc: 'AI视觉领域龙头技术创新受政策支持',
industry: '安防',
relevance_score: 92,
impact_level: 'high',
},
{
stock_code: '000063.SZ',
stock_name: '中兴通讯',
relation_desc: '5G通信设备商基础设施建设受益',
industry: '通信设备',
relevance_score: 88,
impact_level: 'high',
},
{
stock_code: '688981.SH',
stock_name: '中芯国际',
relation_desc: '半导体制造龙头,国产替代核心标的',
industry: '半导体',
relevance_score: 85,
impact_level: 'high',
},
],
// 事件ID为3的预设股票新能源类
'3': [
{
stock_code: '300750.SZ',
stock_name: '宁德时代',
relation_desc: '动力电池龙头,新能源产业链核心',
industry: '新能源',
relevance_score: 98,
impact_level: 'high',
},
{
stock_code: '002594.SZ',
stock_name: 'BYD比亚迪',
relation_desc: '新能源汽车龙头,产业链一体化布局',
industry: '新能源汽车',
relevance_score: 95,
impact_level: 'high',
},
{
stock_code: '601012.SH',
stock_name: '隆基绿能',
relation_desc: '光伏组件龙头,清洁能源受政策支持',
industry: '光伏',
relevance_score: 85,
impact_level: 'medium',
},
],
};
// 获取事件相关股票
export function getEventRelatedStocks(eventId) {
// 优先返回预设的股票列表
if (mockEventStocks[eventId]) {
return mockEventStocks[eventId];
}
// 否则生成随机股票列表3-6只股票
const count = 3 + (parseInt(eventId) % 4);
return generateRelatedStocks(eventId, count);
}

View File

@@ -86,22 +86,33 @@ export function generateWechatSessionId() {
}
// ==================== 当前登录用户状态管理 ====================
// 用于跟踪当前登录的用户Mock 模式下的全局状态
let currentLoggedInUser = null;
// Mock 模式下使用 localStorage 持久化登录状态
// 设置当前登录用户
export function setCurrentUser(user) {
currentLoggedInUser = user;
console.log('[Mock State] 设置当前登录用户:', user);
if (user) {
localStorage.setItem('mock_current_user', JSON.stringify(user));
console.log('[Mock State] 设置当前登录用户:', user);
}
}
// 获取当前登录用户
export function getCurrentUser() {
return currentLoggedInUser;
try {
const stored = localStorage.getItem('mock_current_user');
if (stored) {
const user = JSON.parse(stored);
console.log('[Mock State] 获取当前登录用户:', user);
return user;
}
} catch (error) {
console.error('[Mock State] 解析用户数据失败:', error);
}
return null;
}
// 清除当前登录用户
export function clearCurrentUser() {
currentLoggedInUser = null;
localStorage.removeItem('mock_current_user');
console.log('[Mock State] 清除当前登录用户');
}

View File

@@ -0,0 +1,39 @@
// src/mocks/handlers/event.js
// 事件相关的 Mock API Handlers
import { http, HttpResponse } from 'msw';
import { getEventRelatedStocks } from '../data/events';
// 模拟网络延迟
const delay = (ms = 300) => new Promise(resolve => setTimeout(resolve, ms));
export const eventHandlers = [
// 获取事件相关股票
http.get('/api/events/:eventId/stocks', async ({ params }) => {
await delay(300);
const { eventId } = params;
console.log('[Mock] 获取事件相关股票, eventId:', eventId);
try {
const relatedStocks = getEventRelatedStocks(eventId);
return HttpResponse.json({
success: true,
data: relatedStocks,
message: '获取成功'
});
} catch (error) {
console.error('[Mock] 获取事件相关股票失败:', error);
return HttpResponse.json(
{
success: false,
error: '获取事件相关股票失败',
data: []
},
{ status: 500 }
);
}
}),
];

View File

@@ -4,15 +4,15 @@
import { authHandlers } from './auth';
import { accountHandlers } from './account';
import { simulationHandlers } from './simulation';
import { eventHandlers } from './event';
// 可以在这里添加更多的 handlers
// import { userHandlers } from './user';
// import { eventHandlers } from './event';
export const handlers = [
...authHandlers,
...accountHandlers,
...simulationHandlers,
...eventHandlers,
// ...userHandlers,
// ...eventHandlers,
];

View File

@@ -72,8 +72,7 @@ import ImportanceLegend from './components/ImportanceLegend';
import InvestmentCalendar from './components/InvestmentCalendar';
import { eventService } from '../../services/eventService';
// 导入导航栏组件 (如果需要保留原有的导航栏)
import HomeNavbar from '../../components/Navbars/HomeNavbar';
// 导航栏已由 MainLayout 提供,无需在此导入
const filterLabelMap = {
date_range: v => v ? `日期: ${v}` : '',
@@ -266,8 +265,7 @@ const Community = () => {
return (
<Box minH="100vh" bg={bgColor}>
{/* 导航栏 - 可以保留原有的或改用Chakra UI版本 */}
<HomeNavbar />
{/* 导航栏已由 MainLayout 提供 */}
{/* Midjourney风格英雄区域 */}
<MidjourneyHeroSection />

View File

@@ -83,8 +83,7 @@ import { BsGraphUp, BsLightningFill } from 'react-icons/bs';
import { keyframes } from '@emotion/react';
import ConceptTimelineModal from './ConceptTimelineModal';
import ConceptStatsPanel from './components/ConceptStatsPanel';
// 导入导航栏组件
import HomeNavbar from '../../components/Navbars/HomeNavbar';
// 导航栏已由 MainLayout 提供,无需在此导
// 导入订阅权限管理
import { useSubscription } from '../../hooks/useSubscription';
import SubscriptionUpgradeModal from '../../components/SubscriptionUpgradeModal';
@@ -1080,8 +1079,7 @@ const ConceptCenter = () => {
return (
<Box minH="100vh" bg="gray.50">
{/* 导航栏 */}
<HomeNavbar />
{/* 导航栏已由 MainLayout 提供 */}
{/* Hero Section */}
<Box

View File

@@ -44,8 +44,7 @@ import SectorDetails from './components/SectorDetails';
import { DataAnalysis, StockDetailModal } from './components/DataVisualizationComponents';
import { AdvancedSearch, SearchResultsModal } from './components/SearchComponents';
// 导入导航栏组件
import HomeNavbar from '../../components/Navbars/HomeNavbar';
// 导航栏已由 MainLayout 提供,无需在此导
// 导入高位股统计组件
import HighPositionStocks from './components/HighPositionStocks';
@@ -303,8 +302,7 @@ export default function LimitAnalyse() {
return (
<Box minH="100vh" bg={bgColor}>
{/* 导航栏 */}
<HomeNavbar />
{/* 导航栏已由 MainLayout 提供 */}
{/* 顶部Header */}
<Box bgGradient="linear(to-br, blue.500, purple.600)" color="white" py={8}>

View File

@@ -61,7 +61,8 @@ import { FaChartLine, FaFire, FaRocket, FaBrain, FaCalendarAlt, FaChevronRight,
import { BsGraphUp, BsLightningFill } from 'react-icons/bs';
import { keyframes } from '@emotion/react';
import * as echarts from 'echarts';
import HomeNavbar from '../../components/Navbars/HomeNavbar';
// Navigation bar now provided by MainLayout
// import HomeNavbar from '../../components/Navbars/HomeNavbar';
// 动画定义
const pulseAnimation = keyframes`
@@ -524,8 +525,7 @@ const StockOverview = () => {
return (
<Box minH="100vh" bg={bgColor}>
{/* 导航栏 */}
<HomeNavbar />
{/* 导航栏已由 MainLayout 提供 */}

View File

@@ -45,8 +45,7 @@ import MarginTrading from './components/MarginTrading';
// 导入现有的高质量组件
import LineChart from '../../components/Charts/LineChart';
// 导入导航栏组件
import HomeNavbar from '../../components/Navbars/HomeNavbar';
// 导航栏已由 MainLayout 提供,无需在此导
// 模拟盘账户管理 Hook
import { useTradingAccount } from './hooks/useTradingAccount';
@@ -221,9 +220,7 @@ export default function TradingSimulation() {
// ========== 5. 主要渲染逻辑 ==========
return (
<Box minH="100vh" bg={bgColor}>
{/* 导航栏 */}
<HomeNavbar />
{/* 导航栏已由 MainLayout 提供 */}
<Container maxW="7xl" py={8}>
{!isAuthenticated ? (
<Alert status="warning">