From 6924cc873bde0160134c6c733cb67ff4819c66f8 Mon Sep 17 00:00:00 2001 From: kyuuseiryuu Date: Fri, 30 Jan 2026 01:10:14 +0900 Subject: [PATCH] feat(global): Skeleton --- src/components/GamePanel.tsx | 2 +- src/components/GameSelector/GameSelector.tsx | 16 ++-- src/frontend.tsx | 6 +- src/page/EventPage.tsx | 34 +++++--- src/page/ProfilePage.tsx | 91 +++++++++++--------- 5 files changed, 86 insertions(+), 63 deletions(-) diff --git a/src/components/GamePanel.tsx b/src/components/GamePanel.tsx index c3044bd..e05050b 100644 --- a/src/components/GamePanel.tsx +++ b/src/components/GamePanel.tsx @@ -1,6 +1,6 @@ import type React from "react"; import type { BasePlayer, Player } from "../types"; -import { Tabs } from "antd"; +import { Skeleton, Spin, Tabs } from "antd"; import { PlayerList } from "./PlayerList"; import { GroupingPrediction } from "./GroupingPrediction"; import { useMemo } from "react"; diff --git a/src/components/GameSelector/GameSelector.tsx b/src/components/GameSelector/GameSelector.tsx index 4f5f56d..e3dfa9a 100644 --- a/src/components/GameSelector/GameSelector.tsx +++ b/src/components/GameSelector/GameSelector.tsx @@ -1,4 +1,4 @@ -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'; @@ -48,12 +48,14 @@ export const GameSelector: React.FC = props => { /> {isEmpty && (没有未开始的比赛)} - - {gameList - .filter(e => showFinished || !e.finished) - .map(e => ) - } - + {requestEvents.loading ? : ( + + {gameList + .filter(e => showFinished || !e.finished) + .map(e => ) + } + + )} ); } diff --git a/src/frontend.tsx b/src/frontend.tsx index 166c3d3..6f4771d 100644 --- a/src/frontend.tsx +++ b/src/frontend.tsx @@ -24,16 +24,14 @@ const route = createBrowserRouter([ { 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 }; + return { matchId: params.matchId }; }, Component: EventPage, }, { path: '/profile/:uid', loader: async ({ params }) => { - return fetch(`/api/user/${params.uid}`); + return params.uid; }, Component: ProfilePage, }, diff --git a/src/page/EventPage.tsx b/src/page/EventPage.tsx index 929e716..3a69534 100644 --- a/src/page/EventPage.tsx +++ b/src/page/EventPage.tsx @@ -1,32 +1,42 @@ import { useLoaderData, useNavigate } from "react-router"; import { GamePanel } from "../components/GamePanel"; import type { BasePlayer, MatchInfo, XCXMember } from "../types"; -import { Typography } from "antd"; +import { Skeleton, Typography } from "antd"; import { HomeOutlined } from "@ant-design/icons"; import { useMemo } from "react"; +import { useRequest } from "ahooks"; export default function EventPage() { const { - info: game, - members - } = useLoaderData<{ info: MatchInfo, members: XCXMember[] }>(); + matchId, + } = useLoaderData<{ matchId: string }>(); + const fetchData = useRequest<{ info: MatchInfo, members: XCXMember[] }, []>(async () => { + const info: MatchInfo = await (await fetch(`/api/match/${matchId}`)).json(); + const members = await (await fetch(`/api/match/${matchId}/${info.itemId}`)).json(); + return { info, members }; + }, { refreshDeps: [matchId]}); + const { info: game, members = [] } = useMemo<{ info?: MatchInfo, members?: XCXMember[] }>(() => fetchData.data ?? {}, [fetchData]); const navigate = useNavigate(); const map = useMemo(() => Object.fromEntries(members.map(e => [e.uid, e])), [members]); const players = useMemo(() => { - return game.players - .map(e => ({ ...e, name: map[e.uid]?.realname ?? e.name, number: map[e.uid]?.number ?? NaN })) - .sort(((a, b) => +a.number - +b.number)); + return game?.players + ?.map(e => ({ ...e, name: map[e.uid]?.realname ?? e.name, number: map[e.uid]?.number ?? NaN })) + ?.sort(((a, b) => +a.number - +b.number)); }, [game, map]); const basePlayers = useMemo(() => { return members.map(e => ({ ...e, name: e.realname } as BasePlayer)) }, [members]); return (
- - navigate('/')}/> - {game.title} - - + {fetchData.loading ? () : ( + + navigate('/')}/> + {game?.title} + + )} + {fetchData.loading ? : ( + + )}
); } \ No newline at end of file diff --git a/src/page/ProfilePage.tsx b/src/page/ProfilePage.tsx index 0ff0f3a..1bb4d5b 100644 --- a/src/page/ProfilePage.tsx +++ b/src/page/ProfilePage.tsx @@ -1,12 +1,13 @@ import { Link, useLoaderData, useNavigate } from "react-router"; import type { XCXProfile } from "../types/profile"; -import { Avatar, Descriptions, Divider, Flex, FloatButton, Image, Typography } from "antd"; +import { Skeleton, Avatar, Descriptions, Divider, Flex, FloatButton, Image, Typography } from "antd"; import { HomeOutlined } from "@ant-design/icons"; import User from "../components/User"; -import React from "react"; +import React, { useMemo } from "react"; import { ChangeBackground } from "../components/ChangeBackground"; import UserTags from "../components/Tags"; +import { useRequest } from "ahooks"; function Honor(props: { honors?: XCXProfile['honors'] }) { if (!props.honors?.length) return null; @@ -79,53 +80,65 @@ function PlayerList(props: { title: string; names?: string[]; uids?: string[] }) } export default function ProfilePage() { - const profile = useLoaderData(); + const uid = useLoaderData(); + const fetchData = useRequest(async () => { + return (await fetch(`/api/user/${uid}`)).json(); + }, { refreshDeps: [uid] }); + const loading = fetchData.loading; + const profile = fetchData.data; const navigate = useNavigate(); + const tags = useMemo(() => + [profile?.province, profile?.sex, profile?.bg ?? '', ...(profile?.allCities ?? [])] + ?.filter(Boolean), []); return ( <> } onClick={() => navigate('/')} /> - - {profile?.username} - 姓名:{profile?.realname} - 积分:{profile?.score} - - { - ([profile?.province, profile?.sex, profile?.bg ?? '', ...(profile?.allCities ?? [])] as React.ReactNode[]) - .filter(Boolean) - .reduce((a, b) => (<>{a}{b})) - } - + { loading ? : ( + + ) } + + {profile?.username} + 姓名:{profile?.realname} + 积分:{profile?.score} + + { tags.length ? tags.reduce((a, b) => (<>{a}{b})) : null } + + 简介 - - {profile?.description} - + + + {profile?.description} + + - - - - + + + + + + + + + + + + + + + - - + + + + + + + - - - - - - - - - - - - - - - - + + );