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 { useRequest } from 'ahooks';
import { clubs } from './clubList';
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { IEventInfo } from '../../types';
import { useCallback, useEffect, useState } from 'react';
import dayjs from 'dayjs';
import type { IEventInfo } from '../../types';
interface Props {
onGameClick?: (info: IEventInfo) => void;
@ -14,14 +14,18 @@ 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[]>([]);
const [gameList, setGameList] = useState<(IEventInfo & { finished: boolean })[]>([]);
const [isEmpty, setIsEmpty] = useState(false);
const [clubId, setClubId] = useState(clubs[0].clubId);
const [showFinished, setShowFinished] = useState(false);
const handleClubChange = useCallback(async (clubId: string) => {
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);
setIsEmpty(activeList.length === 0);
setIsEmpty(activeList.filter(e => !e.finished).length === 0);
}, []);
useEffect(() => {
const clubId = clubs[0].clubId;
@ -29,6 +33,11 @@ export const GameSelector: React.FC<Props> = props => {
}, []);
return (
<Space orientation='vertical' style={{ width: '100%' }}>
<Flex vertical gap={12} justify='center'>
<Space style={{ alignItems: 'self-end' }}>
<Typography.Text></Typography.Text>
<Switch checkedChildren='显示' unCheckedChildren='隐藏' checked={showFinished} onChange={e => setShowFinished(e)} />
</Space>
<Select
style={{ width: '100%' }}
placeholder={'请选择俱乐部'}
@ -37,10 +46,25 @@ export const GameSelector: React.FC<Props> = props => {
options={clubs.map(e => ({ label: e.name, value: e.clubId }))}
onChange={handleClubChange}
/>
<Divider />
{isEmpty && (<Typography.Text type='secondary'></Typography.Text>)}
<Spin spinning={requestEvents.loading}>
{gameList.map(e => {
</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>
</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}`);
@ -49,23 +73,21 @@ export const GameSelector: React.FC<Props> = props => {
key={e.matchId}
title={e.title}
hoverable
style={{ width: '100%' }}
onClick={() => props.onGameClick?.(e)}
>
<Typography.Text type='success'>{e.title}</Typography.Text>
<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>
))}
<Statistic.Timer
type='countdown'
value={day.toDate().getTime()}
format='距离比赛开始: DD 天 HH 时 mm 分 ss 秒'
/>
</Card>
);
})}
</Spin>
</Space>
);
)
}

View File

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