homepage/src/App.tsx
2025-02-03 20:30:56 +08:00

109 lines
No EOL
4 KiB
TypeScript

import React, { Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { ThemeProvider } from './contexts/ThemeContext';
import Navbar from './components/Navbar';
import Breadcrumb from './components/Breadcrumb';
import LoadingSpinner from './components/LoadingSpinner';
import iconMap from './utils/iconMap';
import { useQueryClient } from '@tanstack/react-query';
// 懒加载路由组件
const HomePage = React.lazy(() => import('./pages/HomePage'));
const ProjectsPage = React.lazy(() => import('./pages/ProjectsPage'));
const UpdatesPage = React.lazy(() => import('./pages/UpdatesPage'));
const UpdateDetailPage = React.lazy(() => import('./pages/UpdateDetailPage'));
const ContributorsPage = React.lazy(() => import('./pages/ContributorsPage'));
const AboutPage = React.lazy(() => import('./pages/AboutPage'));
function App() {
const { t, i18n } = useTranslation();
const queryClient = useQueryClient();
const socialLinks = t('social.links', { returnObjects: true });
// 在窗口关闭时清除缓存
React.useEffect(() => {
const handleBeforeUnload = () => {
queryClient.clear();
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [queryClient]);
// 构建 Schema.org 结构化数据
const schemaOrgWebsite = {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'STARSET Mirror',
url: 'https://mirror.starset.fans',
description: t('site.description'),
inLanguage: ['zh-Hans', 'en', 'zh-Hant'],
potentialAction: {
'@type': 'SearchAction',
target: 'https://mirror.starset.fans/search?q={search_term_string}',
'query-input': 'required name=search_term_string'
}
};
return (
<ThemeProvider>
<Router>
<div className="min-h-screen bg-white dark:bg-gray-900 transition-colors">
<Helmet>
<script type="application/ld+json">
{JSON.stringify(schemaOrgWebsite)}
</script>
</Helmet>
<Navbar />
<Breadcrumb />
<main className="pt-14" role="main" aria-label={t('aria.mainContent')}>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/projects" element={<ProjectsPage />} />
<Route path="/updates" element={<UpdatesPage />} />
<Route path="/updates/:id" element={<UpdateDetailPage />} />
<Route path="/contributors" element={<ContributorsPage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</Suspense>
</main>
<footer className="bg-gray-900 dark:bg-gray-950 text-white py-6" role="contentinfo">
<div className="container mx-auto px-4">
<div className="flex flex-col items-center">
<p className="text-gray-400 mb-4"> {t('footer.copyright', { currentYear: new Date().getFullYear() })}</p>
<div className="flex space-x-6">
{socialLinks.map((link: any) => {
const Icon = iconMap[link.icon as keyof typeof iconMap];
return (
<a
key={link.name}
href={link.url}
target="_blank"
rel="noopener noreferrer"
className={link.color}
aria-label={link.name}
title={link.name}
>
<Icon className="h-6 w-6" />
</a>
);
})}
</div>
</div>
</div>
</footer>
</div>
</Router>
</ThemeProvider>
);
}
export default App;