);
}
return this.props.children;
}
}
const AppContent: React.FC = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [currentUser, setCurrentUser] = useState(null);
const [currentView, setCurrentView] = useState('dashboard');
const [loading, setLoading] = useState(true);
const [sidebarOpen, setSidebarOpen] = useState(false);
// --- Auto-Update Logic ---
useEffect(() => {
// 1. Force reload page when Service Worker updates (controller change)
// This happens automatically due to 'registerType: autoUpdate' in vite config,
// but the window needs to reload to use the new SW.
const handleControllerChange = () => {
console.log("🔄 App updated, reloading...");
window.location.reload();
};
// 2. Check for updates on visibility change (e.g. user comes back to tab)
const checkForUpdates = () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.update();
}).catch(() => {});
}
};
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('controllerchange', handleControllerChange);
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
checkForUpdates();
}
});
// Initial check
checkForUpdates();
}
return () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.removeEventListener('controllerchange', handleControllerChange);
}
};
}, []);
useEffect(() => {
const initApp = async () => {
api.init();
let storedUser = api.auth.getCurrentUser();
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 setCurrentView(view)} />;
case 'students': return ;
case 'classes': return ;
case 'courses': return ;
case 'grades': return ;
case 'settings': return ;
case 'reports': return ;
case 'subjects': return ;
case 'users': return ;
case 'schools': return ;
case 'games': return ;
case 'attendance': return ;
case 'wishes': return ;
case 'ai-assistant': return ;
case 'profile': return ;
case 'my-class': return ;
default: return setCurrentView(view)} />;
}
};
const viewTitles: Record = {
dashboard: '工作台',
students: '学生档案管理',
classes: '班级管理',
courses: '课程安排',
grades: '成绩管理',
settings: '系统设置',
reports: '统计报表',
subjects: '学科设置',
users: '用户权限管理',
schools: '学校维度管理',
games: '互动教学中心',
attendance: '考勤管理',
wishes: '心愿与反馈',
'ai-assistant': 'AI 智能助教',
profile: '个人中心',
'my-class': '我的班级 (班主任)'
};
if (loading) return
加载中...
;
if (!isAuthenticated) return ;
const showLiveAssistant = currentUser && (currentUser.role === UserRole.ADMIN || currentUser.aiAccess);
// Layout Logic:
// For 'ai-assistant' and 'games', we want full height without internal padding from Main,
// so the component can manage its own scroll/flex layout (sticky headers/footers).
// For other pages, we use the standard scrolling Main container.
const isAppLikePage = currentView === 'ai-assistant' || currentView === 'games';
const mainClasses = isAppLikePage
? "flex-1 overflow-hidden relative bg-slate-50 w-full"
: "flex-1 overflow-x-hidden overflow-y-auto bg-gray-50 p-4 md:p-6 w-full relative";
return (
// Fixed inset-0 prevents body scroll, everything happens inside