feat(index)

This commit is contained in:
kyuuseiryuu 2026-01-28 16:02:45 +09:00
parent 0f33f63459
commit 7df7b70516
3 changed files with 74 additions and 51 deletions

View File

@ -0,0 +1,7 @@
import { createGlobalStyle } from "styled-components";
export const ChangeBackground = createGlobalStyle<{ url?: string }>`
body:before {
${({ url }) => url ? `background: url(${url});` : ''}
}
`

View File

@ -1,10 +1,10 @@
import { Card, Divider, Select, Space, Spin, Statistic, Typography } from 'antd'; import { Card, Divider, Flex, Select, Space, Statistic, Switch, Typography } from 'antd';
import type React from 'react'; import type React from 'react';
import { useRequest } from 'ahooks'; import { useRequest } from 'ahooks';
import { clubs } from './clubList'; import { clubs } from './clubList';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import type { IEventInfo } from '../../types';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { IEventInfo } from '../../types';
interface Props { interface Props {
onGameClick?: (info: IEventInfo) => void; onGameClick?: (info: IEventInfo) => void;
@ -14,14 +14,18 @@ export const GameSelector: React.FC<Props> = props => {
const requestEvents = useRequest<IEventInfo[], [string]>( const requestEvents = useRequest<IEventInfo[], [string]>(
async (clubId: string) => (await fetch(`/api/events/${clubId}`)).json() async (clubId: string) => (await fetch(`/api/events/${clubId}`)).json()
, { manual: true }) , { manual: true })
const [gameList, setGameList] = useState<IEventInfo[]>([]); const [gameList, setGameList] = useState<(IEventInfo & { finished: boolean })[]>([]);
const [isEmpty, setIsEmpty] = useState(false); const [isEmpty, setIsEmpty] = useState(false);
const [clubId, setClubId] = useState(clubs[0].clubId); const [clubId, setClubId] = useState(clubs[0].clubId);
const [showFinished, setShowFinished] = useState(false);
const handleClubChange = useCallback(async (clubId: string) => { const handleClubChange = useCallback(async (clubId: string) => {
const list = await requestEvents.runAsync(clubId); const list = await requestEvents.runAsync(clubId);
const activeList = list.filter(e => !e.info.join('').includes('已结束')); const activeList = list.map(e => ({
...e,
finished: e.info.join('').includes('已结束'),
}));
setGameList(activeList); setGameList(activeList);
setIsEmpty(activeList.length === 0); setIsEmpty(activeList.filter(e => !e.finished).length === 0);
}, []); }, []);
useEffect(() => { useEffect(() => {
const clubId = clubs[0].clubId; const clubId = clubs[0].clubId;
@ -29,43 +33,61 @@ export const GameSelector: React.FC<Props> = props => {
}, []); }, []);
return ( return (
<Space orientation='vertical' style={{ width: '100%' }}> <Space orientation='vertical' style={{ width: '100%' }}>
<Select <Flex vertical gap={12} justify='center'>
style={{ width: '100%' }} <Space style={{ alignItems: 'self-end' }}>
placeholder={'请选择俱乐部'} <Typography.Text></Typography.Text>
size='large' <Switch checkedChildren='显示' unCheckedChildren='隐藏' checked={showFinished} onChange={e => setShowFinished(e)} />
value={clubId} </Space>
options={clubs.map(e => ({ label: e.name, value: e.clubId }))} <Select
onChange={handleClubChange} style={{ width: '100%' }}
/> placeholder={'请选择俱乐部'}
<Divider /> size='large'
{isEmpty && (<Typography.Text type='secondary'></Typography.Text>)} value={clubId}
<Spin spinning={requestEvents.loading}> options={clubs.map(e => ({ label: e.name, value: e.clubId }))}
{gameList.map(e => { onChange={handleClubChange}
const { y, M, D } = /^(?<y>\d{4})年(?<M>\d+)月(?<D>\d+)日/.exec(e.title)?.groups ?? {} as { y: string; M: string; D: string}; />
const hm = /(?<hm>\d+:\d+)\b/.exec(e.info.join('\n'))?.groups?.hm ?? '10:00'; </Flex>
const day = dayjs(`${y}-${M}-${D} ${hm}`); <Divider>{isEmpty && (<Typography.Text type='secondary'></Typography.Text>)}</Divider>
return ( <Flex wrap gap={12} justify='center'>
<Card {gameList
key={e.matchId} .filter(e => showFinished || !e.finished)
title={e.title} .map(e => <EventCard key={e.matchId} eventInfo={e} onGameClick={props.onGameClick} />)
hoverable }
onClick={() => props.onGameClick?.(e)} </Flex>
>
<Typography.Text type='success'>{e.title}</Typography.Text>
{e.info.map(e => (
<div key={e}>
<Typography.Text type='secondary'>{e}</Typography.Text>
</div>
))}
<Statistic.Timer
type='countdown'
value={day.toDate().getTime()}
format='距离比赛开始: DD 天 HH 时 mm 分 ss 秒'
/>
</Card>
);
})}
</Spin>
</Space> </Space>
); );
} }
interface EventCardProps {
eventInfo: IEventInfo & { finished: boolean };
onGameClick?: (info: IEventInfo) => void;
}
function EventCard(props: EventCardProps) {
const { eventInfo: e } = props;
const { y, M, D } = /^(?<y>\d{4})年(?<M>\d+)月(?<D>\d+)日/.exec(e.title)?.groups ?? {} as { y: string; M: string; D: string};
const hm = /(?<hm>\d+:\d+)\b/.exec(e.info.join('\n'))?.groups?.hm ?? '10:00';
const day = dayjs(`${y}-${M}-${D} ${hm}`);
return (
<Card
key={e.matchId}
title={e.title}
hoverable
style={{ width: '100%' }}
onClick={() => props.onGameClick?.(e)}
>
<Typography.Text type={e.finished ? undefined : 'success'}>{e.title}</Typography.Text>
<Statistic.Timer
type={e.finished ? 'countup' : 'countdown'}
value={day.toDate().getTime()}
format={`距离比赛${e.finished ? '结束' : '开始'}: DD 天${e.finished ? '' : ' HH 时'}`}
styles={{ content: e.finished ? { color: 'gray' } : {} }}
/>
{e.info.map(e => (
<div key={e}>
<Typography.Text type='secondary'>{e}</Typography.Text>
</div>
))}
</Card>
)
}

View File

@ -5,13 +5,7 @@ import { HomeOutlined } from "@ant-design/icons";
import User from "../components/User"; import User from "../components/User";
import React from "react"; import React from "react";
import { createGlobalStyle } from "styled-components"; import { ChangeBackground } from "../components/ChangeBackground";
const BodyBG = createGlobalStyle<{ url?: string }>`
body:before {
${({ url }) => url ? `background: url(${url});` : ''}
}
`
function Honor(props: { honors?: XCXProfile['honors'] }) { function Honor(props: { honors?: XCXProfile['honors'] }) {
if (!props.honors?.length) return null; if (!props.honors?.length) return null;
@ -81,7 +75,7 @@ export default function ProfilePage() {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<> <>
<BodyBG url={profile?.realpic} /> <ChangeBackground url={profile?.realpic} />
<FloatButton icon={<HomeOutlined />} onClick={() => navigate('/')} /> <FloatButton icon={<HomeOutlined />} onClick={() => navigate('/')} />
<Flex vertical align="center" style={{ padding: 24 }}> <Flex vertical align="center" style={{ padding: 24 }}>
<Avatar src={profile?.realpic} size={128} /> <Avatar src={profile?.realpic} size={128} />