318 lines
8.4 KiB
TypeScript
318 lines
8.4 KiB
TypeScript
import React, { useEffect } from 'react';
|
||
import { createRoot } from 'react-dom/client';
|
||
import {
|
||
createBrowserRouter,
|
||
RouterProvider,
|
||
Outlet,
|
||
Navigate,
|
||
useLocation,
|
||
useNavigate,
|
||
Link,
|
||
useRouteError
|
||
} from 'react-router';
|
||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||
import dayjs from 'dayjs';
|
||
import 'dayjs/locale/zh-cn';
|
||
import { AuthProvider, ThemeProvider, useAuth } from './hooks.tsx';
|
||
import HomePage from './pages_index.tsx';
|
||
import LoginPage from './pages_login.tsx';
|
||
import RegisterPage from './pages_register.tsx';
|
||
import { GlobalConfig } from "../share/types.ts";
|
||
import { ExclamationTriangleIcon, HomeIcon, BellIcon, UserIcon } from '@heroicons/react/24/outline';
|
||
import { NotificationsPage } from './pages_messages.tsx';
|
||
// 设置中文语言
|
||
dayjs.locale('zh-cn');
|
||
|
||
// 声明全局配置对象类型
|
||
declare global {
|
||
interface Window {
|
||
CONFIG?: GlobalConfig;
|
||
}
|
||
}
|
||
|
||
|
||
// 创建QueryClient实例
|
||
const queryClient = new QueryClient();
|
||
|
||
// 添加全局CSS(使用TailwindCSS的类)
|
||
const injectGlobalStyles = () => {
|
||
const style = document.createElement('style');
|
||
style.innerHTML = `
|
||
:root {
|
||
--primary-color: #3B82F6;
|
||
--background-color: #F9FAFB;
|
||
--text-color: #111827;
|
||
--border-radius: 8px;
|
||
--font-size: 16px;
|
||
}
|
||
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||
background-color: var(--background-color);
|
||
color: var(--text-color);
|
||
font-size: var(--font-size);
|
||
line-height: 1.5;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
.line-clamp-1 {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 1;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.line-clamp-2 {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.line-clamp-3 {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 3;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 暗色模式支持 */
|
||
.dark {
|
||
color-scheme: dark;
|
||
}
|
||
|
||
.dark body {
|
||
background-color: #121212;
|
||
color: #E5E7EB;
|
||
}
|
||
|
||
/* 滚动条美化 */
|
||
::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: #BFDBFE;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: #93C5FD;
|
||
}
|
||
|
||
/* 移动端点击高亮颜色 */
|
||
* {
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
};
|
||
|
||
// 授权路由守卫
|
||
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||
const { isAuthenticated, isLoading } = useAuth();
|
||
const location = useLocation();
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className="flex items-center justify-center min-h-screen">
|
||
<div className="w-12 h-12 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!isAuthenticated) {
|
||
return <Navigate to="/mobile/login" state={{ from: location }} replace />;
|
||
}
|
||
|
||
return <>{children}</>;
|
||
};
|
||
|
||
// 页面组件
|
||
const PageNotFound = () => (
|
||
<div className="flex flex-col items-center justify-center min-h-screen p-6 text-center">
|
||
<div className="text-6xl font-bold text-blue-600 mb-4">404</div>
|
||
<h1 className="text-2xl font-medium mb-2">页面不存在</h1>
|
||
<p className="text-gray-500 mb-6">您访问的页面不存在或已被移除</p>
|
||
<a
|
||
href="/mobile"
|
||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||
>
|
||
返回首页
|
||
</a>
|
||
</div>
|
||
);
|
||
|
||
// 添加错误页面组件
|
||
const ErrorPage = () => {
|
||
const error = useRouteError() as any;
|
||
const errorMessage = error?.statusText || error?.message || '未知错误';
|
||
|
||
return (
|
||
<div className="flex flex-col items-center justify-center min-h-screen p-6 text-center bg-gray-50">
|
||
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8">
|
||
<ExclamationTriangleIcon className="h-16 w-16 text-red-600 mx-auto mb-4" />
|
||
<h1 className="text-2xl font-medium mb-2">出错了</h1>
|
||
<div className="text-gray-500 mb-4">抱歉,页面加载过程中发生错误</div>
|
||
|
||
<div className="bg-red-50 border border-red-200 rounded-md p-4 mb-6">
|
||
<p className="text-red-700 text-sm font-medium">错误信息:</p>
|
||
<p className="text-red-600 mt-1 text-sm break-all">{errorMessage}</p>
|
||
{error?.stack && (
|
||
<details className="mt-2">
|
||
<summary className="text-red-700 text-sm cursor-pointer">查看详细信息</summary>
|
||
<pre className="mt-2 text-xs text-red-600 overflow-auto p-2 bg-red-50 rounded">
|
||
{error.stack}
|
||
</pre>
|
||
</details>
|
||
)}
|
||
</div>
|
||
|
||
<a
|
||
href="/mobile"
|
||
className="inline-flex items-center justify-center px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors w-full"
|
||
>
|
||
返回首页
|
||
</a>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
import ProfilePage from './pages_profile.tsx'
|
||
import SettingsPage from './pages_settings.tsx'
|
||
|
||
// 移动端布局组件 - 包含底部导航
|
||
const MobileLayout = () => {
|
||
const location = useLocation();
|
||
|
||
return (
|
||
<div className="flex flex-col min-h-screen">
|
||
<div className="flex-1 pb-16">
|
||
<Outlet />
|
||
</div>
|
||
|
||
{/* 底部导航栏 */}
|
||
<nav className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg">
|
||
<div className="flex justify-around">
|
||
<Link
|
||
to="/mobile"
|
||
className={`flex flex-col items-center py-2 px-4 ${
|
||
location.pathname === '/mobile' ? 'text-blue-600' : 'text-gray-500'
|
||
}`}
|
||
>
|
||
<HomeIcon className="w-6 h-6 mb-1" />
|
||
<span className="text-xs">首页</span>
|
||
</Link>
|
||
<Link
|
||
to="/mobile/notifications"
|
||
className={`flex flex-col items-center py-2 px-4 ${
|
||
location.pathname === '/mobile/notifications' ? 'text-blue-600' : 'text-gray-500'
|
||
}`}
|
||
>
|
||
<BellIcon className="w-6 h-6 mb-1" />
|
||
<span className="text-xs">通知</span>
|
||
</Link>
|
||
<Link
|
||
to="/mobile/profile"
|
||
className={`flex flex-col items-center py-2 px-4 ${
|
||
location.pathname === '/mobile/profile' ? 'text-blue-600' : 'text-gray-500'
|
||
}`}
|
||
>
|
||
<UserIcon className="w-6 h-6 mb-1" />
|
||
<span className="text-xs">我的</span>
|
||
</Link>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// 主应用组件
|
||
const App = () => {
|
||
// 创建路由器配置
|
||
const router = createBrowserRouter([
|
||
{
|
||
path: '/',
|
||
element: <Navigate to="/mobile" replace />,
|
||
errorElement: <ErrorPage />
|
||
},
|
||
{
|
||
path: '/mobile/login',
|
||
element: <LoginPage />,
|
||
errorElement: <ErrorPage />
|
||
},
|
||
{
|
||
path: '/mobile/register',
|
||
element: <RegisterPage />,
|
||
errorElement: <ErrorPage />
|
||
},
|
||
{
|
||
path: '/mobile',
|
||
element: (
|
||
<ProtectedRoute>
|
||
<MobileLayout />
|
||
</ProtectedRoute>
|
||
),
|
||
errorElement: <ErrorPage />,
|
||
children: [
|
||
{
|
||
index: true,
|
||
element: <HomePage />
|
||
},
|
||
{
|
||
path: 'profile',
|
||
element: <ProfilePage />
|
||
},
|
||
{
|
||
path: 'notifications',
|
||
element: <NotificationsPage />
|
||
},
|
||
{
|
||
path: 'settings',
|
||
element: <SettingsPage />
|
||
}
|
||
]
|
||
},
|
||
{
|
||
path: '*',
|
||
element: <PageNotFound />
|
||
}
|
||
]);
|
||
|
||
return <RouterProvider router={router} />;
|
||
};
|
||
|
||
// 渲染应用到DOM
|
||
const initApp = () => {
|
||
// 注入全局样式
|
||
injectGlobalStyles();
|
||
|
||
// 渲染应用
|
||
const root = createRoot(document.getElementById('root') as HTMLElement);
|
||
root.render(
|
||
<QueryClientProvider client={queryClient}>
|
||
<ThemeProvider>
|
||
<AuthProvider>
|
||
<App />
|
||
</AuthProvider>
|
||
</ThemeProvider>
|
||
</QueryClientProvider>
|
||
);
|
||
};
|
||
|
||
// 初始化应用
|
||
initApp();
|