feat(global): HydrateFallback
This commit is contained in:
parent
bf74e99a47
commit
f37be8aded
@ -1,8 +1,8 @@
|
||||
import { Card, Divider, Flex, Select, Space, Statistic, Switch, Typography } from 'antd';
|
||||
import { Card, Divider, Flex, Select, Skeleton, Space, Statistic, Switch, Typography } from 'antd';
|
||||
import type React from 'react';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { clubs } from './clubList';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import type { IEventInfo } from '../../types';
|
||||
|
||||
@ -11,25 +11,25 @@ interface Props {
|
||||
}
|
||||
|
||||
export const GameSelector: React.FC<Props> = props => {
|
||||
const requestEvents = useRequest<IEventInfo[], [string]>(
|
||||
async (clubId: string) => (await fetch(`/api/events/${clubId}`)).json()
|
||||
, { manual: true })
|
||||
const [gameList, setGameList] = useState<(IEventInfo & { finished: boolean })[]>([]);
|
||||
const [isEmpty, setIsEmpty] = useState(false);
|
||||
const [clubId, setClubId] = useState(clubs[0].clubId);
|
||||
const [clubId, setClubId] = useState<string>(clubs[0]?.clubId ?? '');
|
||||
const requestEvents = useRequest<IEventInfo[], []>(
|
||||
async () => {
|
||||
if (!clubId) return [];
|
||||
return (await fetch(`/api/events/${clubId}`)).json()
|
||||
}, { manual: false, refreshDeps: [clubId] })
|
||||
const [showFinished, setShowFinished] = useState(false);
|
||||
const handleClubChange = useCallback(async (clubId: string) => {
|
||||
const list = await requestEvents.runAsync(clubId);
|
||||
const activeList = list.map(e => ({
|
||||
const gameList = useMemo(() => {
|
||||
const activeList = requestEvents.data?.map(e => ({
|
||||
...e,
|
||||
finished: e.info.join('').includes('已结束'),
|
||||
}));
|
||||
setGameList(activeList);
|
||||
setIsEmpty(activeList.filter(e => !e.finished).length === 0);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const clubId = clubs[0].clubId;
|
||||
handleClubChange(clubId);
|
||||
return activeList;
|
||||
}, [requestEvents.data]);
|
||||
const isEmpty = useMemo(() => {
|
||||
return (gameList ?? []).filter(e => !e.finished).length === 0
|
||||
}, [gameList]);
|
||||
const handleClubChange = useCallback(async (id: string) => {
|
||||
setClubId(id);
|
||||
}, []);
|
||||
return (
|
||||
<Space orientation='vertical' style={{ width: '100%' }}>
|
||||
@ -48,12 +48,14 @@ export const GameSelector: React.FC<Props> = props => {
|
||||
/>
|
||||
</Flex>
|
||||
<Divider>{isEmpty && (<Typography.Text type='secondary'>没有未开始的比赛</Typography.Text>)}</Divider>
|
||||
<Flex wrap gap={12} justify='center'>
|
||||
{gameList
|
||||
.filter(e => showFinished || !e.finished)
|
||||
.map(e => <EventCard key={e.matchId} eventInfo={e} onGameClick={props.onGameClick} />)
|
||||
}
|
||||
</Flex>
|
||||
{requestEvents.loading ? <Skeleton.Button active block style={{ height: 300 }} /> : (
|
||||
<Flex wrap gap={12} justify='center'>
|
||||
{gameList
|
||||
?.filter(e => showFinished || !e.finished)
|
||||
?.map(e => <EventCard key={e.matchId} eventInfo={e} onGameClick={props.onGameClick} />)
|
||||
}
|
||||
</Flex>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,4 +3,4 @@ export const clubs = [
|
||||
name: '东华',
|
||||
clubId: '47',
|
||||
},
|
||||
] as const;
|
||||
];
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { Flex, Form, InputNumber, Space, Switch, Typography } from "antd";
|
||||
import { Flex, Form, InputNumber, Switch } from "antd";
|
||||
import { chunk } from 'lodash';
|
||||
import type { BasePlayer } from "../types";
|
||||
import { GroupMember } from "./GroupMember";
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { useRequest } from "ahooks";
|
||||
import { Divider, Flex, Skeleton, Tag, Typography } from "antd";
|
||||
import { EType, type XCXTag } from "../types";
|
||||
import { useEffect } from "react";
|
||||
|
||||
interface Props {
|
||||
uid?: string;
|
||||
|
||||
@ -5,40 +5,65 @@
|
||||
* It is included in `src/index.html`.
|
||||
*/
|
||||
|
||||
import { Component, StrictMode } from "react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import { ConfigProvider, theme } from "antd";
|
||||
import { ConfigProvider, Spin, theme } from "antd";
|
||||
import { createBrowserRouter, RouterProvider } from "react-router";
|
||||
import ProfilePage from "./page/ProfilePage";
|
||||
import EventPage from "./page/EventPage";
|
||||
import type { MatchInfo } from "./types";
|
||||
import { Outlet, useNavigation } from "react-router";
|
||||
|
||||
const elem = document.getElementById("root")!;
|
||||
|
||||
const route = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <App />
|
||||
},
|
||||
{
|
||||
path: '/event/:matchId',
|
||||
loader: async ({ params }) => {
|
||||
const info: MatchInfo = await (await fetch(`/api/match/${params.matchId}`)).json();
|
||||
const members = await (await fetch(`/api/match/${params.matchId}/${info.itemId}`)).json();
|
||||
return { info, members };
|
||||
},
|
||||
Component: EventPage,
|
||||
},
|
||||
{
|
||||
path: '/profile/:uid',
|
||||
loader: async ({ params }) => {
|
||||
return fetch(`/api/user/${params.uid}`);
|
||||
},
|
||||
Component: ProfilePage,
|
||||
element: <Layout />,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
index: true,
|
||||
element: <App />,
|
||||
HydrateFallback: () => <HydrateFallback />
|
||||
},
|
||||
{
|
||||
path: 'event/:matchId',
|
||||
loader: async ({ params }) => {
|
||||
const info: MatchInfo = await (await fetch(`/api/match/${params.matchId}`)).json();
|
||||
const members = await (await fetch(`/api/match/${params.matchId}/${info.itemId}`)).json();
|
||||
return { info, members };
|
||||
},
|
||||
Component: EventPage,
|
||||
HydrateFallback: () => <HydrateFallback />
|
||||
},
|
||||
{
|
||||
path: 'profile/:uid',
|
||||
loader: async ({ params }) => {
|
||||
return fetch(`/api/user/${params.uid}`);
|
||||
},
|
||||
Component: ProfilePage,
|
||||
HydrateFallback: () => <HydrateFallback />
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
function HydrateFallback() {
|
||||
return (
|
||||
<Spin spinning>
|
||||
<div style={{ height: '100vh' }} />
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
function Layout() {
|
||||
const navigation = useNavigation();
|
||||
const loading = navigation.state === 'loading';
|
||||
return loading ? <HydrateFallback /> : <Outlet />
|
||||
}
|
||||
|
||||
const app = (
|
||||
<StrictMode>
|
||||
<ConfigProvider theme={{
|
||||
|
||||
@ -80,6 +80,7 @@ function PlayerList(props: { title: string; names?: string[]; uids?: string[] })
|
||||
|
||||
export default function ProfilePage() {
|
||||
const profile = useLoaderData<XCXProfile | null>();
|
||||
console.debug('profile', profile);
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user