320 lines
12 KiB
JavaScript
Executable File
320 lines
12 KiB
JavaScript
Executable File
/**
|
||
=========================================================
|
||
Vision UI PRO React - v1.0.0
|
||
=========================================================
|
||
Product Page: https://www.creative-tim.com/product/vision-ui-dashboard-pro-react
|
||
Copyright 2021 Creative Tim (https://www.creative-tim.com/)
|
||
Design and Coded by Simmmple & Creative Tim
|
||
=========================================================
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Visionware.
|
||
*/
|
||
|
||
import React, { Suspense, useEffect } from "react";
|
||
import { ChakraProvider } from '@chakra-ui/react';
|
||
import { Routes, Route, Navigate } from "react-router-dom";
|
||
|
||
// Chakra imports
|
||
import { Box, useColorMode } from '@chakra-ui/react';
|
||
|
||
// Core Components
|
||
import theme from "theme/theme.js";
|
||
|
||
// Loading Component
|
||
import PageLoader from "components/Loading/PageLoader";
|
||
|
||
// Layouts - 保持同步导入(需要立即加载)
|
||
import Auth from "layouts/Auth";
|
||
import HomeLayout from "layouts/Home";
|
||
import MainLayout from "layouts/MainLayout";
|
||
|
||
// ⚡ 使用 React.lazy() 实现路由懒加载
|
||
// 首屏不需要的组件按需加载,大幅减少初始 JS 包大小
|
||
const Community = React.lazy(() => import("views/Community"));
|
||
const LimitAnalyse = React.lazy(() => import("views/LimitAnalyse"));
|
||
const ForecastReport = React.lazy(() => import("views/Company/ForecastReport"));
|
||
const ConceptCenter = React.lazy(() => import("views/Concept"));
|
||
const FinancialPanorama = React.lazy(() => import("views/Company/FinancialPanorama"));
|
||
const CompanyIndex = React.lazy(() => import("views/Company"));
|
||
const MarketDataView = React.lazy(() => import("views/Company/MarketDataView"));
|
||
const StockOverview = React.lazy(() => import("views/StockOverview"));
|
||
const EventDetail = React.lazy(() => import("views/EventDetail"));
|
||
const TradingSimulation = React.lazy(() => import("views/TradingSimulation"));
|
||
|
||
// Contexts
|
||
import { AuthProvider } from "contexts/AuthContext";
|
||
import { AuthModalProvider } from "contexts/AuthModalContext";
|
||
import { NotificationProvider, useNotification } from "contexts/NotificationContext";
|
||
import { IndustryProvider } from "contexts/IndustryContext";
|
||
|
||
// Components
|
||
import ProtectedRoute from "components/ProtectedRoute";
|
||
import ProtectedRouteRedirect from "components/ProtectedRouteRedirect";
|
||
import ErrorBoundary from "components/ErrorBoundary";
|
||
import AuthModalManager from "components/Auth/AuthModalManager";
|
||
import NotificationContainer from "components/NotificationContainer";
|
||
import ConnectionStatusBar from "components/ConnectionStatusBar";
|
||
import NotificationTestTool from "components/NotificationTestTool";
|
||
import ScrollToTop from "components/ScrollToTop";
|
||
import { logger } from "utils/logger";
|
||
|
||
/**
|
||
* ConnectionStatusBar 包装组件
|
||
* 需要在 NotificationProvider 内部使用,所以单独提取
|
||
*/
|
||
function ConnectionStatusBarWrapper() {
|
||
const { connectionStatus, reconnectAttempt, maxReconnectAttempts, retryConnection } = useNotification();
|
||
const [isDismissed, setIsDismissed] = React.useState(false);
|
||
|
||
// 监听连接状态变化
|
||
React.useEffect(() => {
|
||
// 重连成功后,清除 dismissed 状态
|
||
if (connectionStatus === 'connected' && isDismissed) {
|
||
setIsDismissed(false);
|
||
// 从 localStorage 清除 dismissed 标记
|
||
localStorage.removeItem('connection_status_dismissed');
|
||
}
|
||
|
||
// 从 localStorage 恢复 dismissed 状态
|
||
if (connectionStatus !== 'connected' && !isDismissed) {
|
||
const dismissed = localStorage.getItem('connection_status_dismissed');
|
||
if (dismissed === 'true') {
|
||
setIsDismissed(true);
|
||
}
|
||
}
|
||
}, [connectionStatus, isDismissed]);
|
||
|
||
const handleClose = () => {
|
||
// 用户手动关闭,保存到 localStorage
|
||
setIsDismissed(true);
|
||
localStorage.setItem('connection_status_dismissed', 'true');
|
||
logger.info('App', 'Connection status bar dismissed by user');
|
||
};
|
||
|
||
return (
|
||
<ConnectionStatusBar
|
||
status={connectionStatus}
|
||
reconnectAttempt={reconnectAttempt}
|
||
maxReconnectAttempts={maxReconnectAttempts}
|
||
onRetry={retryConnection}
|
||
onClose={handleClose}
|
||
isDismissed={isDismissed}
|
||
/>
|
||
);
|
||
}
|
||
|
||
function AppContent() {
|
||
const { colorMode } = useColorMode();
|
||
|
||
return (
|
||
<Box minH="100vh" bg={colorMode === 'dark' ? 'gray.800' : 'white'}>
|
||
{/* Socket 连接状态条 */}
|
||
<ConnectionStatusBarWrapper />
|
||
|
||
{/* 路由切换时自动滚动到顶部 */}
|
||
<ScrollToTop />
|
||
<Routes>
|
||
{/* 带导航栏的主布局 - 所有需要导航栏的页面都在这里 */}
|
||
{/* MainLayout 内部有 Suspense,确保导航栏始终可见 */}
|
||
<Route element={<MainLayout />}>
|
||
{/* 首页路由 */}
|
||
<Route path="home/*" element={<HomeLayout />} />
|
||
|
||
{/* Community页面路由 - 需要登录 */}
|
||
<Route
|
||
path="community"
|
||
element={
|
||
<ProtectedRoute>
|
||
<Community />
|
||
</ProtectedRoute>
|
||
}
|
||
/>
|
||
|
||
{/* 概念中心路由 - 需要登录 */}
|
||
<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>
|
||
}
|
||
/>
|
||
|
||
{/* 事件详情独立页面路由 - 需要登录(跳转模式) */}
|
||
<Route
|
||
path="event-detail/:eventId"
|
||
element={
|
||
<ProtectedRouteRedirect>
|
||
<EventDetail />
|
||
</ProtectedRouteRedirect>
|
||
}
|
||
/>
|
||
|
||
{/* 公司相关页面 */}
|
||
{/* 财报预测 - 需要登录(跳转模式) */}
|
||
<Route
|
||
path="forecast-report"
|
||
element={
|
||
<ProtectedRouteRedirect>
|
||
<ForecastReport />
|
||
</ProtectedRouteRedirect>
|
||
}
|
||
/>
|
||
|
||
{/* 财务全景 - 需要登录(弹窗模式) */}
|
||
<Route
|
||
path="Financial"
|
||
element={
|
||
<ProtectedRoute>
|
||
<FinancialPanorama />
|
||
</ProtectedRoute>
|
||
}
|
||
/>
|
||
|
||
{/* 公司页面 - 需要登录(弹窗模式) */}
|
||
<Route
|
||
path="company"
|
||
element={
|
||
<ProtectedRoute>
|
||
<CompanyIndex />
|
||
</ProtectedRoute>
|
||
}
|
||
/>
|
||
|
||
{/* 公司详情 - 需要登录(跳转模式) */}
|
||
<Route
|
||
path="company/:code"
|
||
element={
|
||
<ProtectedRouteRedirect>
|
||
<CompanyIndex />
|
||
</ProtectedRouteRedirect>
|
||
}
|
||
/>
|
||
|
||
{/* 市场数据 - 需要登录(弹窗模式) */}
|
||
<Route
|
||
path="market-data"
|
||
element={
|
||
<ProtectedRoute>
|
||
<MarketDataView />
|
||
</ProtectedRoute>
|
||
}
|
||
/>
|
||
</Route>
|
||
|
||
{/* 认证页面路由 - 不使用 MainLayout */}
|
||
<Route path="auth/*" element={<Auth />} />
|
||
|
||
{/* 默认重定向到首页 */}
|
||
<Route path="/" element={<Navigate to="/home" replace />} />
|
||
|
||
{/* 404 页面 */}
|
||
<Route path="*" element={<Navigate to="/home" replace />} />
|
||
</Routes>
|
||
</Box>
|
||
);
|
||
}
|
||
|
||
export default function App() {
|
||
// 全局错误处理:捕获未处理的 Promise rejection
|
||
useEffect(() => {
|
||
const handleUnhandledRejection = (event) => {
|
||
logger.error('App', 'unhandledRejection', event.reason instanceof Error ? event.reason : new Error(String(event.reason)), {
|
||
promise: event.promise
|
||
});
|
||
// 阻止默认的错误处理(防止崩溃)
|
||
event.preventDefault();
|
||
};
|
||
|
||
const handleError = (event) => {
|
||
logger.error('App', 'globalError', event.error || new Error(event.message), {
|
||
filename: event.filename,
|
||
lineno: event.lineno,
|
||
colno: event.colno
|
||
});
|
||
// 阻止默认的错误处理(防止崩溃)
|
||
event.preventDefault();
|
||
};
|
||
|
||
window.addEventListener('unhandledrejection', handleUnhandledRejection);
|
||
window.addEventListener('error', handleError);
|
||
|
||
return () => {
|
||
window.removeEventListener('unhandledrejection', handleUnhandledRejection);
|
||
window.removeEventListener('error', handleError);
|
||
};
|
||
}, []);
|
||
|
||
return (
|
||
<ChakraProvider
|
||
theme={theme}
|
||
toastOptions={{
|
||
defaultOptions: {
|
||
position: 'top',
|
||
duration: 3000,
|
||
isClosable: true,
|
||
}
|
||
}}
|
||
>
|
||
<ErrorBoundary>
|
||
<NotificationProvider>
|
||
<AuthProvider>
|
||
<AuthModalProvider>
|
||
<IndustryProvider>
|
||
<AppContent />
|
||
<AuthModalManager />
|
||
<NotificationContainer />
|
||
<NotificationTestTool />
|
||
</IndustryProvider>
|
||
</AuthModalProvider>
|
||
</AuthProvider>
|
||
</NotificationProvider>
|
||
</ErrorBoundary>
|
||
</ChakraProvider>
|
||
);
|
||
} |