Files
d8d-admin-mobile-starter-pu…/client/mobile/mobile_app.tsx

312 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { 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',
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();