feat: bugfix

This commit is contained in:
zdl
2025-10-31 10:33:53 +08:00
parent f05daa3a78
commit 5d8ad5e442
34 changed files with 314 additions and 3579 deletions

View File

@@ -1,53 +0,0 @@
// src/routes/components/RouteContainer.js
// 路由容器组件 - 提供统一的错误边界、加载状态和主题背景
import React, { Suspense } from 'react';
import { Box, useColorMode } from '@chakra-ui/react';
import ErrorBoundary from '@components/ErrorBoundary';
import PageLoader from '@components/Loading/PageLoader';
/**
* RouteContainer - 路由容器组件
*
* 为路由系统提供统一的外层包装,包含:
* 1. 主题感知的背景色(深色/浅色模式)
* 2. Suspense 懒加载边界(显示加载提示)
* 3. ErrorBoundary 错误边界(隔离路由错误)
*
* 这个组件确保:
* - 所有路由页面都有一致的背景色
* - 懒加载组件有统一的加载提示
* - 单个路由的错误不会导致整个应用崩溃
*
* @param {Object} props
* @param {React.ReactNode} props.children - 子组件(通常是 Routes
* @param {string} [props.loadingMessage='加载页面中...'] - 加载提示文本
*
* @example
* <RouteContainer>
* <Routes>
* <Route path="/" element={<Home />} />
* </Routes>
* </RouteContainer>
*/
export function RouteContainer({
children,
loadingMessage = "加载页面中..."
}) {
const { colorMode } = useColorMode();
return (
<Box
minH="100vh"
bg={colorMode === 'dark' ? 'gray.800' : 'white'}
>
{/* Suspense 统一处理懒加载组件的加载状态 */}
<Suspense fallback={<PageLoader message={loadingMessage} />}>
{/* ErrorBoundary 隔离路由错误,防止整个应用崩溃 */}
<ErrorBoundary>
{children}
</ErrorBoundary>
</Suspense>
</Box>
);
}

View File

@@ -1,4 +0,0 @@
// src/routes/components/index.js
// 统一导出所有路由组件
export { RouteContainer } from './RouteContainer';

View File

@@ -1,5 +1,5 @@
// src/routes/constants/index.js
// 统一导出所有路由常量
export { LAYOUT_COMPONENTS } from './layoutComponents';
export { PROTECTION_WRAPPER_MAP } from './protectionWrappers';
export { PROTECTION_MODES } from './protectionModes';

View File

@@ -1,26 +0,0 @@
// src/routes/constants/layoutComponents.js
// 布局组件映射表
import Auth from '@layouts/Auth';
import HomeLayout from '@layouts/Home';
/**
* 特殊布局组件映射表
*
* 用于将字符串标识符映射到实际的组件。
* 这些是非懒加载的布局组件,在 routeConfig.js 中通过字符串引用。
*
* @example
* // 在 routeConfig.js 中:
* {
* path: 'auth/*',
* component: 'Auth', // 字符串标识符
* ...
* }
*
* // 通过 LAYOUT_COMPONENTS['Auth'] 获取实际组件
*/
export const LAYOUT_COMPONENTS = {
Auth,
HomeLayout,
};

View File

@@ -0,0 +1,14 @@
// src/routes/constants/protectionModes.js
// 路由保护模式常量
/**
* 路由保护模式
* - 'modal': 使用 ProtectedRoute (弹窗模式登录)
* - 'redirect': 使用 ProtectedRouteRedirect (跳转模式登录)
* - 'public': 公开访问,无需登录
*/
export const PROTECTION_MODES = {
MODAL: 'modal',
REDIRECT: 'redirect',
PUBLIC: 'public',
};

View File

@@ -3,7 +3,7 @@
import ProtectedRoute from '@components/ProtectedRoute';
import ProtectedRouteRedirect from '@components/ProtectedRouteRedirect';
import { PROTECTION_MODES } from '../routeConfig';
import { PROTECTION_MODES } from './protectionModes';
/**
* 保护模式包装器映射表

115
src/routes/homeRoutes.js Normal file
View File

@@ -0,0 +1,115 @@
// src/routes/homeRoutes.js
// Home 模块子路由配置
import { lazyComponents } from './lazy-components';
import { PROTECTION_MODES } from './constants/protectionModes';
/**
* Home 模块的子路由配置
* 这些路由将作为 /home/* 的嵌套路由
*
* 注意:
* - 使用相对路径(不带前导斜杠)
* - 空字符串 '' 表示索引路由,匹配 /home
* - 这些路由将通过 Outlet 渲染到父路由中
*/
export const homeRoutes = [
// 首页 - /home
{
path: '',
component: lazyComponents.HomePage,
protection: PROTECTION_MODES.PUBLIC,
meta: {
title: '首页',
description: '价值前沿首页'
}
},
// 个人中心 - /home/center
{
path: 'center',
component: lazyComponents.CenterDashboard,
protection: PROTECTION_MODES.MODAL,
meta: {
title: '个人中心',
description: '用户个人中心'
}
},
// 个人资料 - /home/profile
{
path: 'profile',
component: lazyComponents.ProfilePage,
protection: PROTECTION_MODES.MODAL,
meta: {
title: '个人资料',
description: '用户个人资料'
}
},
// 账户设置 - /home/settings
{
path: 'settings',
component: lazyComponents.SettingsPage,
protection: PROTECTION_MODES.MODAL,
meta: {
title: '账户设置',
description: '用户账户设置'
}
},
// 订阅管理 - /home/pages/account/subscription
{
path: 'pages/account/subscription',
component: lazyComponents.Subscription,
protection: PROTECTION_MODES.MODAL,
meta: {
title: '订阅管理',
description: '管理订阅服务'
}
},
// 隐私政策 - /home/privacy-policy
{
path: 'privacy-policy',
component: lazyComponents.PrivacyPolicy,
protection: PROTECTION_MODES.PUBLIC,
meta: {
title: '隐私政策',
description: '隐私保护政策'
}
},
// 用户协议 - /home/user-agreement
{
path: 'user-agreement',
component: lazyComponents.UserAgreement,
protection: PROTECTION_MODES.PUBLIC,
meta: {
title: '用户协议',
description: '用户使用协议'
}
},
// 微信授权回调 - /home/wechat-callback
{
path: 'wechat-callback',
component: lazyComponents.WechatCallback,
protection: PROTECTION_MODES.PUBLIC,
meta: {
title: '微信授权',
description: '微信授权回调页面'
}
},
// 回退路由 - 匹配任何未定义的 /home/* 路径
{
path: '*',
component: lazyComponents.HomePage,
protection: PROTECTION_MODES.PUBLIC,
meta: {
title: '首页',
description: '价值前沿首页'
}
},
];

View File

@@ -10,9 +10,8 @@ import { getMainLayoutRoutes, getStandaloneRoutes } from './routeConfig';
// 布局组件
import MainLayout from '@layouts/MainLayout';
// 路由工具和组件
// 路由工具
import { renderRoute } from './utils';
import { RouteContainer } from './components';
/**
* AppRoutes - 应用路由组件
@@ -31,7 +30,11 @@ import { RouteContainer } from './components';
* 目录结构:
* - constants/ - 常量配置(布局映射、保护包装器)
* - utils/ - 工具函数renderRoute, wrapWithProtection
* - components/ - 路由组件RouteContainer
* - components/ - 路由相关组件
*
* 注意:
* - Suspense/ErrorBoundary 由 PageTransitionWrapper 统一处理
* - 全屏容器由 MainLayout 提供minH="100vh"
*/
export function AppRoutes() {
// 🎯 性能优化:使用 useMemo 缓存路由计算结果
@@ -39,23 +42,21 @@ export function AppRoutes() {
const standaloneRoutes = useMemo(() => getStandaloneRoutes(), []);
return (
<RouteContainer>
<Routes>
{/* 主布局路由 - 带导航栏和页脚 */}
<Route element={<MainLayout />}>
{mainLayoutRoutes.map(renderRoute)}
</Route>
<Routes>
{/* 主布局路由 - 带导航栏和页脚 */}
<Route element={<MainLayout />}>
{mainLayoutRoutes.map(renderRoute)}
</Route>
{/* 独立路由 - 无布局(如登录页)*/}
{standaloneRoutes.map(renderRoute)}
{/* 独立路由 - 无布局(如登录页)*/}
{standaloneRoutes.map(renderRoute)}
{/* 默认路由 - 重定向到首页 */}
<Route path="/" element={<Navigate to="/home" replace />} />
{/* 默认路由 - 重定向到首页 */}
<Route path="/" element={<Navigate to="/home" replace />} />
{/* 404 页面 - 捕获所有未匹配的路由 */}
<Route path="*" element={<Navigate to="/home" replace />} />
</Routes>
</RouteContainer>
{/* 404 页面 - 捕获所有未匹配的路由 */}
<Route path="*" element={<Navigate to="/home" replace />} />
</Routes>
);
}

View File

@@ -8,6 +8,16 @@ import React from 'react';
* 使用 React.lazy() 实现路由懒加载,大幅减少初始 JS 包大小
*/
export const lazyComponents = {
// Home 模块
HomePage: React.lazy(() => import('../views/Home/HomePage')),
CenterDashboard: React.lazy(() => import('../views/Dashboard/Center')),
ProfilePage: React.lazy(() => import('../views/Profile/ProfilePage')),
SettingsPage: React.lazy(() => import('../views/Settings/SettingsPage')),
Subscription: React.lazy(() => import('../views/Pages/Account/Subscription')),
PrivacyPolicy: React.lazy(() => import('../views/Pages/PrivacyPolicy')),
UserAgreement: React.lazy(() => import('../views/Pages/UserAgreement')),
WechatCallback: React.lazy(() => import('../views/Pages/WechatCallback')),
// 社区/内容模块
Community: React.lazy(() => import('../views/Community')),
ConceptCenter: React.lazy(() => import('../views/Concept')),
@@ -31,6 +41,14 @@ export const lazyComponents = {
* 按需导出单个组件(可选)
*/
export const {
HomePage,
CenterDashboard,
ProfilePage,
SettingsPage,
Subscription,
PrivacyPolicy,
UserAgreement,
WechatCallback,
Community,
ConceptCenter,
StockOverview,

View File

@@ -2,35 +2,30 @@
// 声明式路由配置
import { lazyComponents } from './lazy-components';
import { homeRoutes } from './homeRoutes';
import { PROTECTION_MODES } from './constants/protectionModes';
/**
* 路由保护模式
* - 'modal': 使用 ProtectedRoute (弹窗模式登录)
* - 'redirect': 使用 ProtectedRouteRedirect (跳转模式登录)
* - 'public': 公开访问,无需登录
*/
export const PROTECTION_MODES = {
MODAL: 'modal',
REDIRECT: 'redirect',
PUBLIC: 'public',
};
// 重新导出 PROTECTION_MODES 以保持向后兼容
export { PROTECTION_MODES };
/**
* 路由配置
* 每个路由对象包含:
* - path: 路由路径
* - component: 组件(从 lazyComponents 引用)
* - component: 组件(从 lazyComponents 引用,或设为 null 使用 Outlet)
* - protection: 保护模式 (modal/redirect/public)
* - layout: 布局类型 (main/auth/none)
* - children: 子路由配置数组(可选,用于嵌套路由)
* - meta: 路由元数据(可选,用于面包屑、标题等)
*/
export const routeConfig = [
// ==================== 首页 ====================
{
path: 'home/*',
component: 'HomeLayout', // 非懒加载,直接在 App.js 导入
path: 'home',
component: null, // 使用 Outlet 渲染子路由
protection: PROTECTION_MODES.PUBLIC,
layout: 'main',
children: homeRoutes, // 子路由配置
meta: {
title: '首页',
description: '价值前沿首页'
@@ -107,7 +102,7 @@ export const routeConfig = [
{
path: 'forecast-report',
component: lazyComponents.ForecastReport,
protection: PROTECTION_MODES.REDIRECT,
protection: PROTECTION_MODES.MODAL,
layout: 'main',
meta: {
title: '财报预测',
@@ -115,7 +110,7 @@ export const routeConfig = [
}
},
{
path: 'Financial',
path: 'financial',
component: lazyComponents.FinancialPanorama,
protection: PROTECTION_MODES.MODAL,
layout: 'main',
@@ -154,18 +149,6 @@ export const routeConfig = [
description: '实时市场数据'
}
},
// ==================== 认证模块 ====================
{
path: 'auth/*',
component: 'Auth', // 非懒加载,直接在 App.js 导入
protection: PROTECTION_MODES.PUBLIC,
layout: 'none',
meta: {
title: '登录/注册',
description: '用户认证'
}
},
];
/**

View File

@@ -2,8 +2,7 @@
// 路由渲染工具函数
import React from 'react';
import { Route } from 'react-router-dom';
import { LAYOUT_COMPONENTS } from '../constants';
import { Route, Outlet } from 'react-router-dom';
import { wrapWithProtection } from './wrapWithProtection';
/**
@@ -11,14 +10,16 @@ import { wrapWithProtection } from './wrapWithProtection';
*
* 根据路由配置项生成 React Router 的 Route 组件。
* 处理以下逻辑:
* 1. 解析组件(特殊布局组件 vs 懒加载组件)
* 1. 解析组件(懒加载组件 or Outlet
* 2. 应用路由保护(根据 protection 字段)
* 3. 生成唯一 key
* 3. 处理嵌套路由children 数组)
* 4. 生成唯一 key
*
* @param {Object} routeItem - 路由配置项(来自 routeConfig.js
* @param {string} routeItem.path - 路由路径
* @param {React.ComponentType|string} routeItem.component - 组件或组件标识符
* @param {React.ComponentType|null} routeItem.component - 懒加载组件或 nullnull 表示使用 Outlet
* @param {string} routeItem.protection - 保护模式 (modal/redirect/public)
* @param {Array} [routeItem.children] - 子路由配置数组(可选)
* @param {number} index - 路由索引,用于生成唯一 key
*
* @returns {React.ReactElement} Route 组件
@@ -27,19 +28,41 @@ import { wrapWithProtection } from './wrapWithProtection';
* // 使用示例
* const routes = [
* { path: 'community', component: CommunityComponent, protection: 'modal' },
* { path: 'auth/*', component: 'Auth', protection: 'public' },
* { path: 'home', component: null, protection: 'public', children: [...] },
* ];
*
* routes.map((route, index) => renderRoute(route, index));
*/
export function renderRoute(routeItem, index) {
const { path, component, protection } = routeItem;
const { path, component, protection, children } = routeItem;
// 解析组件:
// - 如果是字符串(如 'Auth', 'HomeLayout'),从 LAYOUT_COMPONENTS 映射表查找
// - 如果是 null使用 <Outlet /> 用于嵌套路由
// - 否则直接使用(懒加载组件)
const Component = LAYOUT_COMPONENTS[component] || component;
let Component;
let isOutletRoute = false;
if (component === null) {
Component = Outlet; // 用于嵌套路由
isOutletRoute = true;
} else {
Component = component; // 直接使用懒加载组件
}
// 如果有子路由,递归渲染
if (children && children.length > 0) {
return (
<Route
key={`${path}-${index}`}
path={path}
element={isOutletRoute ? <Outlet /> : wrapWithProtection(Component, protection)}
>
{children.map((childRoute, childIndex) => renderRoute(childRoute, childIndex))}
</Route>
);
}
// 没有子路由,渲染单个路由
return (
<Route
key={`${path}-${index}`}