App.tsx 所有 React.lazy 改为 safeLazy(loader).catch(...) 保证 chunk load 失败时 element 有 fallback 不会是 undefined. 防 React error #231.
197 lines
10 KiB
TypeScript
197 lines
10 KiB
TypeScript
import React, { useState, useEffect, Suspense } from 'react';
|
|
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
import type { User } from '@/types';
|
|
import { AuthContext, AuthContextType } from '@/contexts/AuthContext';
|
|
import { authAPI } from '@/api';
|
|
|
|
// Layouts(保留为即时加载,构成应用外壳)
|
|
import FrontLayout from '@/components/layout/FrontLayout';
|
|
import AdminLayout from '@/components/layout/AdminLayout';
|
|
|
|
// 路由级代码分割:每个页面独立打包成 chunk,首屏只加载外壳与公共依赖,
|
|
// 访问具体页面时才按需加载对应 chunk,显著加快首屏渲染与路由切换速度。
|
|
// #963: lazy() + .catch() fallback so element never undefined
|
|
const safeLazy = (loader: () => Promise<{ default: React.ComponentType<any> }>) =>
|
|
React.lazy(() =>
|
|
loader().catch((err) => {
|
|
console.error('[qihu-lazy-load-failed]', err);
|
|
return { default: () => (
|
|
React.createElement('div', { style: { padding: 20, color: '#dc2626' } },
|
|
'Chunk load failed: ' + String((err as Error)?.message || err))
|
|
) } as any;
|
|
})
|
|
);
|
|
|
|
const LandingPage = safeLazy(() => import('@/pages/front/LandingPage'));
|
|
const LoginPage = safeLazy(() => import('@/pages/front/LoginPage'));
|
|
const ChatPage = safeLazy(() => import('@/pages/front/ChatPage'));
|
|
const KnowledgePage = safeLazy(() => import('@/pages/front/KnowledgePage'));
|
|
const MarketPage = safeLazy(() => import('@/pages/front/MarketPage'));
|
|
const TokenDashboardPage = safeLazy(() => import('@/pages/front/TokenDashboardPage'));
|
|
const PricingPage = safeLazy(() => import('@/pages/front/PricingPage'));
|
|
const ProfilePage = safeLazy(() => import('@/pages/front/ProfilePage'));
|
|
const NotificationPage = safeLazy(() => import('@/pages/front/NotificationPage'));
|
|
const AssetsPage = safeLazy(() => import('@/pages/front/AssetsPage'));
|
|
const NovelPreviewPage = safeLazy(() => import('@/pages/front/NovelPreviewPage'));
|
|
const AgentCanvas = safeLazy(() => import('@/pages/front/AgentCanvas'));
|
|
const EvolutionDashboard = safeLazy(() => import('@/pages/front/EvolutionDashboard'));
|
|
|
|
const AdminDashboard = safeLazy(() => import('@/pages/admin/Dashboard'));
|
|
const AdminUsers = safeLazy(() => import('@/pages/admin/Users'));
|
|
const AdminModels = safeLazy(() => import('@/pages/admin/Models'));
|
|
const AdminSiteSettings = safeLazy(() => import('@/pages/admin/SiteSettings'));
|
|
const AdminSmsConfig = safeLazy(() => import('@/pages/admin/SmsConfig'));
|
|
const AdminPayment = safeLazy(() => import('@/pages/admin/Payment'));
|
|
const AdminServices = safeLazy(() => import('@/pages/admin/Services'));
|
|
const AdminMembership = safeLazy(() => import('@/pages/admin/Membership'));
|
|
const AdminAds = safeLazy(() => import('@/pages/admin/Ads'));
|
|
const AdminMarket = safeLazy(() => import('@/pages/admin/Market'));
|
|
const AdminMarketProducts = safeLazy(() => import('@/pages/admin/MarketProductsAdmin'));
|
|
const AdminLogs = safeLazy(() => import('@/pages/admin/Logs'));
|
|
const AdminSkills = safeLazy(() => import('@/pages/admin/Skills'));
|
|
const AdminAgents = safeLazy(() => import('@/pages/admin/Agents'));
|
|
const AdminCodeExec = safeLazy(() => import('@/pages/admin/CodeExec'));
|
|
const DistributionBuilder = safeLazy(() => import('@/pages/admin/DistributionBuilder'));
|
|
const ImBots = safeLazy(() => import('@/pages/admin/ImBots'));
|
|
const PrivacySettings = safeLazy(() => import('@/pages/admin/PrivacySettings'));
|
|
const Promotion = safeLazy(() => import('@/pages/admin/Promotion'));
|
|
const Achievement = safeLazy(() => import('@/pages/admin/Achievement'));
|
|
const Tenant = safeLazy(() => import('@/pages/admin/Tenant'));
|
|
const Billing = safeLazy(() => import('@/pages/admin/Billing'));
|
|
const Analytics = safeLazy(() => import('@/pages/admin/Analytics'));
|
|
const AdminKnowledge = safeLazy(() => import('@/pages/admin/Knowledge'));
|
|
const AdminWorkflow = safeLazy(() => import('@/pages/admin/Workflow'));
|
|
const AdminStorage = safeLazy(() => import('@/pages/admin/Storage'));
|
|
const AdminDeveloper = safeLazy(() => import('@/pages/admin/Developer'));
|
|
const AdminDeveloperTools = safeLazy(() => import('@/pages/admin/DeveloperTools'));
|
|
const AdminAutomation = safeLazy(() => import('@/pages/admin/Automation'));
|
|
const AdminMcp = safeLazy(() => import('@/pages/admin/Mcp'));
|
|
const AdminApps = safeLazy(() => import('@/pages/admin/Apps'));
|
|
const AssetCenter = safeLazy(() => import('@/pages/admin/AssetCenter'));
|
|
const UserPortal = safeLazy(() => import('@/pages/UserPortal'));
|
|
const WechatBot = safeLazy(() => import('@/pages/front/WechatBot'));
|
|
const ImBotHub = safeLazy(() => import('@/pages/front/ImBotHub'));
|
|
const AppServices = safeLazy(() => import('@/pages/front/AppServices'));
|
|
const MarketProducts = safeLazy(() => import('@/pages/front/MarketProducts'));
|
|
const MarketProductDetail = safeLazy(() => import('@/pages/front/MarketProductDetail'));
|
|
const MarketProductsAdmin = safeLazy(() => import('@/pages/admin/MarketProductsAdmin'));
|
|
|
|
const pageFallback = <div className="loading-screen">加载中...</div>;
|
|
|
|
const App: React.FC = () => {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
// 初始化:从localStorage恢复登录状态
|
|
useEffect(() => {
|
|
const token = localStorage.getItem('aiflow_token');
|
|
const cachedUser = localStorage.getItem('aiflow_user');
|
|
if (token && cachedUser) {
|
|
try {
|
|
setUser(JSON.parse(cachedUser));
|
|
} catch {
|
|
localStorage.removeItem('aiflow_token');
|
|
localStorage.removeItem('aiflow_user');
|
|
}
|
|
}
|
|
setLoading(false);
|
|
}, []);
|
|
|
|
const login = (u: User) => {
|
|
setUser(u);
|
|
localStorage.setItem('aiflow_user', JSON.stringify(u));
|
|
};
|
|
const logout = () => {
|
|
setUser(null);
|
|
localStorage.removeItem('aiflow_token');
|
|
localStorage.removeItem('aiflow_user');
|
|
};
|
|
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin';
|
|
|
|
if (loading) {
|
|
return <div className="loading-screen">加载中...</div>;
|
|
}
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ user, login, logout, isAdmin }}>
|
|
<Suspense fallback={pageFallback}>
|
|
<Routes>
|
|
{/* Login */}
|
|
<Route path="/login" element={
|
|
user ? (isAdmin ? <Navigate to="/admin" /> : <Navigate to="/chat" />) : <LoginPage />
|
|
} />
|
|
|
|
{/* Root - Landing for guests, redirect for authenticated users */}
|
|
<Route path="/" element={
|
|
user ? (isAdmin ? <Navigate to="/admin" /> : <Navigate to="/chat" />) : <LandingPage />
|
|
} />
|
|
|
|
{/* Front Routes (authenticated only) */}
|
|
<Route path="/" element={<FrontLayout />}>
|
|
<Route path="chat" element={<ChatPage />} />
|
|
<Route path="agent-canvas" element={<AgentCanvas />} />
|
|
<Route path="chat/:id" element={<ChatPage />} />
|
|
<Route path="knowledge" element={<KnowledgePage />} />
|
|
<Route path="market" element={<MarketPage />} />
|
|
<Route path="token" element={<TokenDashboardPage />} />
|
|
<Route path="pricing" element={<PricingPage />} />
|
|
<Route path="profile" element={<ProfilePage />} />
|
|
<Route path="notifications" element={<NotificationPage />} />
|
|
<Route path="assets" element={<AssetsPage />} />
|
|
<Route path="assets/novel/:id" element={<NovelPreviewPage />} />
|
|
<Route path="evolution" element={<EvolutionDashboard />} />
|
|
<Route path="wechat-bot" element={<WechatBot />} />
|
|
<Route path="im-bots" element={<ImBotHub />} />
|
|
<Route path="app-services" element={<AppServices />} />
|
|
<Route path="/marketplace/products" element={<MarketProducts />} />
|
|
<Route path="/marketplace/products/:id" element={<MarketProductDetail />} />
|
|
</Route>
|
|
|
|
{/* Admin Routes */}
|
|
<Route path="/admin" element={isAdmin ? <AdminLayout /> : <Navigate to="/login" replace />}>
|
|
<Route index element={<AdminDashboard />} />
|
|
<Route path="users" element={<AdminUsers />} />
|
|
<Route path="models" element={<AdminModels />} />
|
|
<Route path="settings" element={<AdminSiteSettings />} />
|
|
<Route path="sms" element={<AdminSmsConfig />} />
|
|
<Route path="payment" element={<AdminPayment />} />
|
|
<Route path="services" element={<AdminServices />} />
|
|
<Route path="membership" element={<AdminMembership />} />
|
|
<Route path="ads" element={<AdminAds />} />
|
|
<Route path="market" element={<AdminMarket />} />
|
|
<Route path="market-products" element={<MarketProductsAdmin />} />
|
|
<Route path="logs" element={<AdminLogs />} />
|
|
<Route path="skills" element={<AdminSkills />} />
|
|
<Route path="agents" element={<AdminAgents />} />
|
|
<Route path="code-exec" element={<AdminCodeExec />} />
|
|
<Route path="distributions" element={<DistributionBuilder />} />
|
|
<Route path="im-bots" element={<ImBots />} />
|
|
<Route path="privacy" element={<PrivacySettings />} />
|
|
<Route path="promotion" element={<Promotion />} />
|
|
<Route path="achievement" element={<Achievement />} />
|
|
<Route path="tenant" element={<Tenant />} />
|
|
<Route path="billing" element={<Billing />} />
|
|
<Route path="analytics" element={<Analytics />} />
|
|
<Route path="knowledge" element={<AdminKnowledge />} />
|
|
<Route path="workflow" element={<AdminWorkflow />} />
|
|
<Route path="storage" element={<AdminStorage />} />
|
|
<Route path="developer" element={<AdminDeveloper />} />
|
|
<Route path="developer-tools" element={<AdminDeveloperTools />} />
|
|
<Route path="automation" element={<AdminAutomation />} />
|
|
<Route path="mcp" element={<AdminMcp />} />
|
|
<Route path="apps" element={<AdminApps />} />
|
|
<Route path="assets" element={<AssetCenter />} />
|
|
<Route path="portal" element={<UserPortal />} />
|
|
</Route>
|
|
|
|
{/* 公开前台首页:不受登录态拦截,供后台「访问前台」直接打开 */}
|
|
<Route path="/home" element={<LandingPage />} />
|
|
<Route path="*" element={<Navigate to="/" />} />
|
|
</Routes>
|
|
</Suspense>
|
|
</AuthContext.Provider>
|
|
);
|
|
};
|
|
|
|
export default App;
|