feat(GameSelector): Add button to view game details and update parsing logic
This commit is contained in:
parent
6f1f8044dd
commit
c239b8bf40
@ -1,10 +1,11 @@
|
|||||||
import { Card, Divider, Flex, Select, Skeleton, Space, Statistic, Switch, Typography } from 'antd';
|
import { Button, Card, Divider, Flex, Select, Skeleton, 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, useMemo, useState } from 'react';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import type { IEventInfo } from '../../types';
|
import type { IEventInfo } from '../../types';
|
||||||
|
import { EyeOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onGameClick?: (info: IEventInfo) => void;
|
onGameClick?: (info: IEventInfo) => void;
|
||||||
@ -21,7 +22,7 @@ export const GameSelector: React.FC<Props> = props => {
|
|||||||
const gameList = useMemo(() => {
|
const gameList = useMemo(() => {
|
||||||
const activeList = requestEvents.data?.map(e => ({
|
const activeList = requestEvents.data?.map(e => ({
|
||||||
...e,
|
...e,
|
||||||
finished: e.info.join('').includes('已结束'),
|
finished: dayjs(e.startDate).isBefore(dayjs()),
|
||||||
}));
|
}));
|
||||||
return activeList;
|
return activeList;
|
||||||
}, [requestEvents.data]);
|
}, [requestEvents.data]);
|
||||||
@ -76,16 +77,21 @@ interface EventCardProps {
|
|||||||
|
|
||||||
function EventCard(props: EventCardProps) {
|
function EventCard(props: EventCardProps) {
|
||||||
const { eventInfo: e } = props;
|
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 day = dayjs(e.startDate);
|
||||||
const hm = /(?<hm>\d+:\d+)\b/.exec(e.info.join('\n'))?.groups?.hm ?? '10:00';
|
|
||||||
const day = dayjs(`${y}-${M}-${D} ${hm}`);
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={e.matchId}
|
key={e.matchId}
|
||||||
title={e.title}
|
title={e.title}
|
||||||
hoverable
|
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
onClick={() => props.onGameClick?.(e)}
|
onClick={() => props.onGameClick?.(e)}
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
>
|
||||||
|
查看
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Typography.Text type={e.finished ? undefined : 'success'}>{e.title}</Typography.Text>
|
<Typography.Text type={e.finished ? undefined : 'success'}>{e.title}</Typography.Text>
|
||||||
<Statistic.Timer
|
<Statistic.Timer
|
||||||
|
|||||||
@ -3,4 +3,8 @@ export const clubs = [
|
|||||||
name: '东华',
|
name: '东华',
|
||||||
clubId: '47',
|
clubId: '47',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: '飞酷乒乓球',
|
||||||
|
clubId: '7841',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { Flex, Form, InputNumber, Segmented, Switch } from "antd";
|
import { Flex, Form, InputNumber, Segmented, Switch } from "antd";
|
||||||
import { chunk } from 'lodash';
|
import { chunk } from 'lodash';
|
||||||
import type { BasePlayer } from "../types";
|
import type { BasePlayer } from "../types";
|
||||||
@ -46,6 +46,16 @@ export const GroupingPrediction: React.FC<Props> = props => {
|
|||||||
?.map((e, i) => ({ ...e, index: i + 1, id: `${i}-${e.name}-${e.score}` })) ?? [];
|
?.map((e, i) => ({ ...e, index: i + 1, id: `${i}-${e.name}-${e.score}` })) ?? [];
|
||||||
}, [refactoredPlayers, maxPlayerSize]);
|
}, [refactoredPlayers, maxPlayerSize]);
|
||||||
const [groupLen, setGroupLen] = useState(6);
|
const [groupLen, setGroupLen] = useState(6);
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.players) {
|
||||||
|
if (players.length < 48) {
|
||||||
|
setMaxPlayerSize(players.length);
|
||||||
|
}
|
||||||
|
if (players.length < 12) {
|
||||||
|
setGroupLen(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [props.players?.length]);
|
||||||
const [sneckMode, setSneckMode] = useState(props.sneckMode);
|
const [sneckMode, setSneckMode] = useState(props.sneckMode);
|
||||||
const chunkSize = useMemo(() => Math.floor((players.length ?? 0) / groupLen) || 1, [players, groupLen]);
|
const chunkSize = useMemo(() => Math.floor((players.length ?? 0) / groupLen) || 1, [players, groupLen]);
|
||||||
const grouped = useMemo(() => {
|
const grouped = useMemo(() => {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export interface IEventInfo {
|
|||||||
info: string[];
|
info: string[];
|
||||||
url: string;
|
url: string;
|
||||||
matchId: string;
|
matchId: string;
|
||||||
|
startDate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MatchInfo {
|
export interface MatchInfo {
|
||||||
|
|||||||
@ -43,13 +43,13 @@ const htmlRequestHeaders = {
|
|||||||
* @param tagid 俱乐部 ID
|
* @param tagid 俱乐部 ID
|
||||||
*/
|
*/
|
||||||
export async function listEvent(tagid: string): Promise<IEventInfo[]> {
|
export async function listEvent(tagid: string): Promise<IEventInfo[]> {
|
||||||
const key = `my-kaiqiuwang:evnet:${tagid}`;
|
const key = `my-kaiqiuwang:club-events:${tagid}`;
|
||||||
let html = await redis.get(key).catch(() => '');
|
let html = await redis.get(key).catch(() => '');
|
||||||
if (!html) {
|
if (!html) {
|
||||||
html = await fetchEventListHTML(tagid);
|
html = await fetchEventListHTML(tagid);
|
||||||
redis.setex(key, 60 * 60 * REDIS_CACHE_HOUR, html);
|
redis.setex(key, 60 * 60 * REDIS_CACHE_HOUR, html);
|
||||||
}
|
}
|
||||||
return parseEventList(html);
|
return await parseEventList(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,21 +62,33 @@ export async function fetchEventListHTML(tagid: string) {
|
|||||||
return resp.text() ?? '';
|
return resp.text() ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseEventList(html: string) {
|
export async function parseEventList(html: string) {
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
const blockList = $('div.event_list > ol > li');
|
const blockList = $('div.event_list > ol > li');
|
||||||
const list: IEventInfo[] = [];
|
const list: IEventInfo[] = [];
|
||||||
for (const block of blockList) {
|
for (const block of blockList) {
|
||||||
const titleEl = $(block).find('.event_title');
|
const titleEl = $(block).find('.event_title');
|
||||||
const title = titleEl.text();
|
const title = titleEl.text();
|
||||||
const url = $(titleEl).find('a').attr('href') ?? '';
|
const eventPath = $(titleEl).find('a').attr('href') ?? '';
|
||||||
const startDate = $(block).find('ul li:nth-of-type(1)').text().replace('比赛开始: \\t', '').trim();
|
|
||||||
const place = $(block).find('ul li:nth-of-type(2)').text().replace('比赛地点: \\t', '').trim();
|
const place = $(block).find('ul li:nth-of-type(2)').text().replace('比赛地点: \\t', '').trim();
|
||||||
|
const eventURL = `${BASE_URL}/home/${eventPath}`;
|
||||||
|
const matchId = /\S+-(\d+).html$/.exec(eventPath)?.[1] ?? '';
|
||||||
|
let eventPage = await redis.get(`my-kaiqiuwang:match:${matchId}`) ?? '';
|
||||||
|
if (!eventPage) {
|
||||||
|
eventPage = await fetch(eventURL, { headers: htmlRequestHeaders }).then(res => res.text() ?? '');
|
||||||
|
await redis.setex(`my-kaiqiuwang:match:${matchId}`, 60 * 60 * 10, eventPage)
|
||||||
|
}
|
||||||
|
const $eventPage = cheerio.load(eventPage);
|
||||||
|
const eventContent = $eventPage('.event_content').text().replace(/(\r|\n)/g, ',').split(',').filter(Boolean).join(' ');
|
||||||
|
const { y, M, D, H, m} = /比赛开始:.*?(?<y>\d{4})年(?<M>\d{2})月(?<D>\d{2})日 \w+ (?<H>\d{2}):(?<m>\d{2})/
|
||||||
|
.exec(eventContent)?.groups ?? {};
|
||||||
|
const startDate = y ? `${y}-${M}-${D} ${H}:${m}` : '';
|
||||||
const event: IEventInfo = {
|
const event: IEventInfo = {
|
||||||
title,
|
title,
|
||||||
info: [startDate, place],
|
info: [`比赛时间:${startDate}`, place],
|
||||||
url: `${BASE_URL}/home/${url}`,
|
url: eventURL,
|
||||||
matchId: /\S+-(\d+).html$/.exec(url)?.[1] ?? '',
|
startDate,
|
||||||
|
matchId,
|
||||||
}
|
}
|
||||||
list.push(event);
|
list.push(event);
|
||||||
}
|
}
|
||||||
@ -117,7 +129,7 @@ export function parseEventInfo(html: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getMatchInfo(matchId: string) {
|
export async function getMatchInfo(matchId: string) {
|
||||||
const key = `my-kaiqiuwang:matchinfo:${matchId}`;
|
const key = `my-kaiqiuwang:match-member:${matchId}`;
|
||||||
let html = await redis.get(key).catch(() => '');
|
let html = await redis.get(key).catch(() => '');
|
||||||
if (!html) {
|
if (!html) {
|
||||||
html = await fetchEventContentHTML(matchId);
|
html = await fetchEventContentHTML(matchId);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user