stud-manager / App.tsx
dvc890's picture
Update App.tsx
f15b284 verified
raw
history blame
8.86 kB
import React, { useState, useEffect, Suspense } from 'react';
import { Sidebar } from './components/Sidebar';
import { Header } from './components/Header';
import { Login } from './pages/Login';
import { User, UserRole } from './types';
import { api } from './services/api';
import { AlertTriangle, Loader2 } from 'lucide-react';
// --- Lazy Load Helper with Retry Strategy ---
// This wrapper catches chunk loading errors (common after deployments) and reloads the page once.
const lazyRetry = (importFn: () => Promise<any>, name: string): React.LazyExoticComponent<React.ComponentType<any>> => {
return React.lazy(async () => {
try {
const module = await importFn();
return { default: module[name] };
} catch (error: any) {
console.error(`Failed to load ${name}:`, error);
// Identify if it's a fetch/chunk loading error
const isLoadError = error.message?.includes('fetch') || error.message?.includes('chunk') || error.message?.includes('dynamically imported');
if (isLoadError) {
const storageKey = `retry_load_${name}`;
const hasRetried = sessionStorage.getItem(storageKey);
if (!hasRetried) {
console.warn(`Chunk load failed for ${name}. Reloading page to fetch new chunks...`);
sessionStorage.setItem(storageKey, 'true');
window.location.reload();
// Return a pending promise to pause React rendering while page reloads
return new Promise(() => {});
} else {
// If we already reloaded and it still fails, cleanup and throw
sessionStorage.removeItem(storageKey);
}
}
throw error;
}
});
};
// Lazy load pages using the robust retry wrapper
const Dashboard = lazyRetry(() => import('./pages/Dashboard'), 'Dashboard');
const StudentList = lazyRetry(() => import('./pages/StudentList'), 'StudentList');
const CourseList = lazyRetry(() => import('./pages/CourseList'), 'CourseList');
const ScoreList = lazyRetry(() => import('./pages/ScoreList'), 'ScoreList');
const ClassList = lazyRetry(() => import('./pages/ClassList'), 'ClassList');
const Settings = lazyRetry(() => import('./pages/Settings'), 'Settings');
const Reports = lazyRetry(() => import('./pages/Reports'), 'Reports');
const SubjectList = lazyRetry(() => import('./pages/SubjectList'), 'SubjectList');
const UserList = lazyRetry(() => import('./pages/UserList'), 'UserList');
const SchoolList = lazyRetry(() => import('./pages/SchoolList'), 'SchoolList');
const Games = lazyRetry(() => import('./pages/Games'), 'Games');
const AttendancePage = lazyRetry(() => import('./pages/Attendance'), 'AttendancePage');
const Profile = lazyRetry(() => import('./pages/Profile'), 'Profile');
const WishesAndFeedback = lazyRetry(() => import('./pages/WishesAndFeedback'), 'WishesAndFeedback');
const AIAssistant = lazyRetry(() => import('./pages/AIAssistant'), 'AIAssistant');
class ErrorBoundary extends React.Component<{children: React.ReactNode}, {hasError: boolean, error: Error | null}> {
constructor(props: any) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error("Uncaught error:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
<div className="bg-white p-8 rounded-xl shadow-lg max-w-lg w-full text-center">
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-red-100 mb-4">
<AlertTriangle className="h-8 w-8 text-red-600" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">哎呀,页面出错了</h2>
<p className="text-gray-500 mb-6">系统遇到了一些非预期的问题。通常这可能是网络波动或缓存问题导致的。</p>
<div className="bg-gray-50 p-4 rounded text-left text-xs text-red-500 overflow-auto max-h-40 mb-6 border border-red-100 font-mono">
{this.state.error?.message || 'Unknown Error'}
</div>
<button onClick={() => { sessionStorage.clear(); window.location.reload(); }} className="w-full px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium">刷新页面重试</button>
<p className="mt-3 text-xs text-gray-400">如果问题持续存在,请尝试清除浏览器缓存。</p>
</div>
</div>
);
}
return this.props.children;
}
}
const PageLoading = () => (
<div className="flex h-full w-full items-center justify-center min-h-[400px]">
<div className="flex flex-col items-center space-y-3">
<Loader2 className="h-8 w-8 animate-spin text-blue-600" />
<p className="text-sm text-gray-400">资源加载中...</p>
</div>
</div>
);
const AppContent: React.FC = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [currentUser, setCurrentUser] = useState<User | null>(null);
const [currentView, setCurrentView] = useState('dashboard');
const [loading, setLoading] = useState(true);
const [sidebarOpen, setSidebarOpen] = useState(false);
useEffect(() => {
const initApp = async () => {
api.init();
let storedUser = api.auth.getCurrentUser();
// Refresh session from server to get latest role/class info
if (storedUser) {
try {
const refreshedUser = await api.auth.refreshSession();
if (refreshedUser) storedUser = refreshedUser;
} catch (e) {
console.warn('Session refresh failed');
}
setCurrentUser(storedUser);
setIsAuthenticated(true);
}
setLoading(false);
};
initApp();
}, []);
const handleLogin = (user: User) => {
setCurrentUser(user);
setIsAuthenticated(true);
setCurrentView('dashboard');
};
const handleLogout = () => {
api.auth.logout();
setIsAuthenticated(false);
setCurrentUser(null);
};
const renderContent = () => {
switch (currentView) {
case 'dashboard': return <Dashboard onNavigate={(view: string) => setCurrentView(view)} />;
case 'students': return <StudentList />;
case 'classes': return <ClassList />;
case 'courses': return <CourseList />;
case 'grades': return <ScoreList />;
case 'settings': return <Settings />;
case 'reports': return <Reports />;
case 'subjects': return <SubjectList />;
case 'users': return <UserList />;
case 'schools': return <SchoolList />;
case 'games': return <Games />;
case 'attendance': return <AttendancePage />;
case 'wishes': return <WishesAndFeedback />;
case 'ai-assistant': return <AIAssistant />;
case 'profile': return <Profile />;
default: return <Dashboard onNavigate={(view: string) => setCurrentView(view)} />;
}
};
const viewTitles: Record<string, string> = {
dashboard: '工作台',
students: '学生档案管理',
classes: '班级管理',
courses: '课程安排',
grades: '成绩管理',
settings: '系统设置',
reports: '统计报表',
subjects: '学科设置',
users: '用户权限管理',
schools: '学校维度管理',
games: '互动教学中心',
attendance: '考勤管理',
wishes: '心愿与反馈',
'ai-assistant': 'AI 智能助教',
profile: '个人中心'
};
if (loading) return <div className="min-h-screen flex items-center justify-center bg-gray-50 text-blue-600">加载中...</div>;
if (!isAuthenticated) return <Login onLogin={handleLogin} />;
return (
<div className="flex h-screen bg-gray-50 overflow-hidden">
<Sidebar
currentView={currentView}
onChangeView={(view) => { setCurrentView(view); setSidebarOpen(false); }}
userRole={(currentUser?.role as UserRole) || UserRole.USER}
onLogout={handleLogout}
isOpen={sidebarOpen}
onClose={() => setSidebarOpen(false)}
/>
<div className="flex-1 flex flex-col w-full">
<Header user={currentUser!} title={viewTitles[currentView] || '智慧校园'} onMenuClick={() => setSidebarOpen(true)}/>
<main className="flex-1 overflow-x-hidden overflow-y-auto bg-gray-50 p-4 md:p-6 w-full">
<div className="max-w-7xl mx-auto w-full h-full">
<Suspense fallback={<PageLoading />}>{renderContent()}</Suspense>
</div>
</main>
</div>
</div>
);
};
const App: React.FC = () => {
return (
<ErrorBoundary>
<AppContent />
</ErrorBoundary>
);
};
export default App;