102 lines
3.5 KiB
TypeScript
102 lines
3.5 KiB
TypeScript
import { Component, lazy, Suspense, useState, useEffect, type ErrorInfo, type ReactNode } from 'react'
|
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
|
|
import { Button, ConfigProvider, Result } from 'antd'
|
|
import zhCN from 'antd/locale/zh_CN'
|
|
import theme from './theme/anthropic'
|
|
import { BrandProvider, useBrand } from './brand/BrandContext'
|
|
import { verifyToken } from './api/auth'
|
|
import { getToken } from './api/client'
|
|
|
|
const HomePage = lazy(() => import('./viewer/pages/HomePage'))
|
|
const WatchPage = lazy(() => import('./viewer/pages/WatchPage'))
|
|
const AdminLayout = lazy(() => import('./admin/AdminLayout'))
|
|
const LoginPage = lazy(() => import('./admin/LoginPage'))
|
|
const Dashboard = lazy(() => import('./admin/pages/Dashboard'))
|
|
const LiveBoard = lazy(() => import('./admin/pages/LiveBoard'))
|
|
const StreamList = lazy(() => import('./admin/pages/StreamList'))
|
|
const StreamForm = lazy(() => import('./admin/pages/StreamForm'))
|
|
const ReplayEditor = lazy(() => import('./admin/pages/ReplayEditor'))
|
|
const NodeManager = lazy(() => import('./admin/pages/NodeManager'))
|
|
const SettingsPage = lazy(() => import('./admin/pages/Settings'))
|
|
|
|
class PageErrorBoundary extends Component<{ children: ReactNode }, { failed: boolean }> {
|
|
state = { failed: false }
|
|
|
|
static getDerivedStateFromError() {
|
|
return { failed: true }
|
|
}
|
|
|
|
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
console.error('Page render failed', error, info)
|
|
}
|
|
|
|
render() {
|
|
if (this.state.failed) {
|
|
return (
|
|
<Result
|
|
status="error"
|
|
title="页面加载失败"
|
|
subTitle="页面遇到异常,请重新加载后再试。"
|
|
extra={<Button type="primary" onClick={() => window.location.reload()}>重新加载</Button>}
|
|
/>
|
|
)
|
|
}
|
|
return this.props.children
|
|
}
|
|
}
|
|
|
|
function AdminGuard({ children }: { children: React.ReactNode }) {
|
|
const [authed, setAuthed] = useState<boolean | null>(null)
|
|
|
|
useEffect(() => {
|
|
const token = getToken()
|
|
if (!token) { setAuthed(false); return }
|
|
verifyToken().then(setAuthed)
|
|
}, [])
|
|
|
|
if (authed === null) return null
|
|
if (!authed) return <Navigate to="/admin/login" replace />
|
|
return <>{children}</>
|
|
}
|
|
|
|
function AppRoutes() {
|
|
const { brand } = useBrand()
|
|
useEffect(() => { document.title = brand.brand_name }, [brand.brand_name])
|
|
return (
|
|
<BrowserRouter>
|
|
<PageErrorBoundary>
|
|
<Suspense fallback={<div className="route-loading" role="status" aria-label="页面加载中" />}>
|
|
<Routes>
|
|
<Route path="/watch/:id" element={<WatchPage />} />
|
|
<Route path="/admin/login" element={<LoginPage />} />
|
|
<Route path="/admin" element={
|
|
<AdminGuard>
|
|
<AdminLayout />
|
|
</AdminGuard>
|
|
}>
|
|
<Route index element={<Dashboard />} />
|
|
<Route path="board" element={<LiveBoard />} />
|
|
<Route path="streams" element={<StreamList />} />
|
|
<Route path="streams/new" element={<StreamForm />} />
|
|
<Route path="streams/:id/edit" element={<ReplayEditor />} />
|
|
<Route path="nodes" element={<NodeManager />} />
|
|
<Route path="settings" element={<SettingsPage />} />
|
|
</Route>
|
|
<Route path="/" element={<HomePage />} />
|
|
</Routes>
|
|
</Suspense>
|
|
</PageErrorBoundary>
|
|
</BrowserRouter>
|
|
)
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<ConfigProvider theme={theme} locale={zhCN}>
|
|
<BrandProvider>
|
|
<AppRoutes />
|
|
</BrandProvider>
|
|
</ConfigProvider>
|
|
)
|
|
}
|