feat(global): Skeleton
This commit is contained in:
parent
bf74e99a47
commit
6924cc873b
@ -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";
|
||||
|
||||
@ -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> = 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 block active style={{ height: 200 }} /> : (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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<BasePlayer[]>(() => {
|
||||
return members.map(e => ({ ...e, name: e.realname } as BasePlayer))
|
||||
}, [members]);
|
||||
return (
|
||||
<div style={{ width: '100%', padding: 10, boxSizing: 'border-box' }}>
|
||||
<Typography.Title level={3}>
|
||||
<HomeOutlined style={{ marginRight: 4 }} onClick={() => navigate('/')}/>
|
||||
{game.title}
|
||||
</Typography.Title>
|
||||
<GamePanel members={basePlayers} title={game.title} players={players} />
|
||||
{fetchData.loading ? (<Skeleton.Button block />) : (
|
||||
<Typography.Title level={3}>
|
||||
<HomeOutlined style={{ marginRight: 4 }} onClick={() => navigate('/')}/>
|
||||
{game?.title}
|
||||
</Typography.Title>
|
||||
)}
|
||||
{fetchData.loading ? <Skeleton.Button active block size="large" style={{ height: 400, marginTop: 40 }} /> : (
|
||||
<GamePanel members={basePlayers} title={game?.title ?? ''} players={players} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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<XCXProfile | null>();
|
||||
const uid = useLoaderData<string | null>();
|
||||
const fetchData = useRequest<XCXProfile | undefined, []>(async () => {
|
||||
return (await fetch(`/api/user/${uid}`)).json();
|
||||
}, { refreshDeps: [uid] });
|
||||
const loading = fetchData.loading;
|
||||
const profile = fetchData.data;
|
||||
const navigate = useNavigate();
|
||||
const tags = useMemo<React.ReactNode[]>(() =>
|
||||
[profile?.province, profile?.sex, profile?.bg ?? '', ...(profile?.allCities ?? [])]
|
||||
?.filter(Boolean), []);
|
||||
return (
|
||||
<>
|
||||
<ChangeBackground url={profile?.realpic} />
|
||||
<FloatButton icon={<HomeOutlined />} onClick={() => navigate('/')} />
|
||||
<Flex vertical align="center" style={{ padding: 24 }}>
|
||||
<Avatar src={profile?.realpic} size={128} />
|
||||
<Typography.Title level={2}>{profile?.username}</Typography.Title>
|
||||
<Typography.Text>姓名:{profile?.realname}</Typography.Text>
|
||||
<Typography.Text>积分:{profile?.score}</Typography.Text>
|
||||
<Typography.Text style={{ textAlign: 'center' }}>
|
||||
{
|
||||
([profile?.province, profile?.sex, profile?.bg ?? '', ...(profile?.allCities ?? [])] as React.ReactNode[])
|
||||
.filter(Boolean)
|
||||
.reduce((a, b) => (<>{a}<Divider orientation="vertical" />{b}</>))
|
||||
}
|
||||
</Typography.Text>
|
||||
{ loading ? <Skeleton.Avatar active size={128} /> : (
|
||||
<Avatar src={profile?.realpic} size={128} />
|
||||
) }
|
||||
<Skeleton active loading={loading}>
|
||||
<Typography.Title level={2}>{profile?.username}</Typography.Title>
|
||||
<Typography.Text>姓名:{profile?.realname}</Typography.Text>
|
||||
<Typography.Text>积分:{profile?.score}</Typography.Text>
|
||||
<Typography.Text style={{ textAlign: 'center' }}>
|
||||
{ tags.length ? tags.reduce((a, b) => (<>{a}<Divider orientation="vertical" />{b}</>)) : null }
|
||||
</Typography.Text>
|
||||
</Skeleton>
|
||||
<Divider>简介</Divider>
|
||||
<Typography.Paragraph>
|
||||
{profile?.description}
|
||||
</Typography.Paragraph>
|
||||
<Skeleton active loading={loading}>
|
||||
<Typography.Paragraph>
|
||||
{profile?.description}
|
||||
</Typography.Paragraph>
|
||||
</Skeleton>
|
||||
<UserTags uid={profile?.uid} />
|
||||
<Raket profile={profile} />
|
||||
<Flex wrap gap={24} justify="center">
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="交手前三名" names={profile?.TopPlayerUsernameScore} />
|
||||
<Skeleton active loading={loading}>
|
||||
<Raket profile={profile} />
|
||||
<Flex wrap gap={24} justify="center">
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="交手前三名" names={profile?.TopPlayerUsernameScore} />
|
||||
</Flex>
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="战胜前三名" names={profile?.Top3OfBeatUsernameScore} />
|
||||
</Flex>
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="战胜的男子" names={profile?.Top3ManOfBeatUsernameScore} />
|
||||
</Flex>
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="战胜的女子" names={profile?.Top3WomanOfBeatUsernameScore} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="战胜前三名" names={profile?.Top3OfBeatUsernameScore} />
|
||||
<Flex gap={24}>
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="福星" names={profile?.FuXing?.names} uids={profile?.FuXing?.uids} />
|
||||
</Flex>
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="苦主" names={profile?.KuZhu?.names} uids={profile?.KuZhu?.uids} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="战胜的男子" names={profile?.Top3ManOfBeatUsernameScore} />
|
||||
</Flex>
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="战胜的女子" names={profile?.Top3WomanOfBeatUsernameScore} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex gap={24}>
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="福星" names={profile?.FuXing?.names} uids={profile?.FuXing?.uids} />
|
||||
</Flex>
|
||||
<Flex vertical align="center">
|
||||
<PlayerList title="苦主" names={profile?.KuZhu?.names} uids={profile?.KuZhu?.uids} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Honor honors={profile?.honors} />
|
||||
<Honor honors={profile?.honors} />
|
||||
</Skeleton>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user