diff --git a/src/components/GamePanel.tsx b/src/components/GamePanel.tsx index e5d7010..c3044bd 100644 --- a/src/components/GamePanel.tsx +++ b/src/components/GamePanel.tsx @@ -1,5 +1,5 @@ import type React from "react"; -import type { Player } from "../types"; +import type { BasePlayer, Player } from "../types"; import { Tabs } from "antd"; import { PlayerList } from "./PlayerList"; import { GroupingPrediction } from "./GroupingPrediction"; @@ -8,6 +8,7 @@ import { useMemo } from "react"; interface Props { title: string; players?: Player[]; + members?: BasePlayer[]; } export const GamePanel: React.FC = props => { @@ -21,7 +22,7 @@ export const GamePanel: React.FC = props => { { key: 'groups', label: '分组预测', - children: + children: }, { key: 'players', diff --git a/src/components/GroupMember.tsx b/src/components/GroupMember.tsx index 6fa8f8f..96be686 100644 --- a/src/components/GroupMember.tsx +++ b/src/components/GroupMember.tsx @@ -1,11 +1,11 @@ import { Card, Table } from "antd"; -import type { Player } from "../types"; +import type { BasePlayer } from "../types"; import { useMemo } from "react"; import User from "./User"; interface Props { index: number; - players?: (Player & { id: string })[]; + players?: (BasePlayer & { id: string })[]; } export const GroupMember: React.FC = props => { diff --git a/src/components/GroupingPrediction.tsx b/src/components/GroupingPrediction.tsx index 9779cdb..566fe1b 100644 --- a/src/components/GroupingPrediction.tsx +++ b/src/components/GroupingPrediction.tsx @@ -1,20 +1,23 @@ import React, { useMemo, useState } from "react"; import { Flex, Form, InputNumber, Space, Switch, Typography } from "antd"; import { chunk } from 'lodash'; -import type { Player } from "../types"; +import type { BasePlayer } from "../types"; import { GroupMember } from "./GroupMember"; import { sneckGroup } from "../utils"; interface Props { - players?: Player[]; + players?: BasePlayer[]; sneckMode: boolean; } -type CustomPlayer = (Player & { index: number; id: string; }); +type CustomPlayer = (BasePlayer & { index: number; id: string; }); export const GroupingPrediction: React.FC = props => { const [maxPlayerSize, setMaxPlayerSize] = useState(48); const players: CustomPlayer[] = useMemo(() => { - return props.players?.slice(0, maxPlayerSize)?.map((e, i) => ({ ...e, index: i + 1, id: `${i}-${e.name}-${e.score}` })) ?? []; + return props.players + ?.slice(0, maxPlayerSize) + ?.sort((a, b) => Number(b.score) - Number(a.score)) + ?.map((e, i) => ({ ...e, index: i + 1, id: `${i}-${e.name}-${e.score}` })) ?? []; }, [props.players, maxPlayerSize]); const [groupLen, setGroupLen] = useState(6); const [sneckMode, setSneckMode] = useState(props.sneckMode); diff --git a/src/components/PlayerList.tsx b/src/components/PlayerList.tsx index b02aea3..69706db 100644 --- a/src/components/PlayerList.tsx +++ b/src/components/PlayerList.tsx @@ -21,7 +21,7 @@ export const PlayerList: React.FC = props => { placement: ['topCenter', 'bottomCenter'], }} > - `${i + 1}`} /> + } /> = props => { render={(name, { uid }) => } /> a - b, + compare: ({ score: a }: Player, { score: b}: Player) => +a - +b, }} /> diff --git a/src/frontend.tsx b/src/frontend.tsx index 41b5c92..166c3d3 100644 --- a/src/frontend.tsx +++ b/src/frontend.tsx @@ -25,7 +25,8 @@ const route = createBrowserRouter([ path: '/event/:matchId', loader: async ({ params }) => { const info: MatchInfo = await (await fetch(`/api/match/${params.matchId}`)).json(); - return info; + const members = await (await fetch(`/api/match/${params.matchId}/${info.itemId}`)).json(); + return { info, members }; }, Component: EventPage, }, diff --git a/src/index.tsx b/src/index.tsx index 9c49a74..0dea370 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,6 @@ import { serve } from "bun"; import index from "./index.html"; -import { getAdvProfile, getMatchInfo, getPlayerTags, listEvent } from "./utils"; +import { getAdvProfile, getMatchInfo, getMemberDetail, getPlayerTags, listEvent } from "./utils"; const server = serve({ port: process.env.PORT || 3000, @@ -19,6 +19,13 @@ const server = serve({ return Response.json(data); } }, + "/api/match/:matchId/:itemId": { + async GET(req) { + const { matchId, itemId } = req.params; + const data = await getMemberDetail(matchId, itemId); + return Response.json(data); + } + }, "/api/user/:uid": { async GET(req) { const uid = req.params.uid; diff --git a/src/page/EventPage.tsx b/src/page/EventPage.tsx index f69f024..929e716 100644 --- a/src/page/EventPage.tsx +++ b/src/page/EventPage.tsx @@ -1,19 +1,32 @@ import { useLoaderData, useNavigate } from "react-router"; import { GamePanel } from "../components/GamePanel"; -import type { MatchInfo } from "../types"; +import type { BasePlayer, MatchInfo, XCXMember } from "../types"; import { Typography } from "antd"; import { HomeOutlined } from "@ant-design/icons"; +import { useMemo } from "react"; export default function EventPage() { - const game = useLoaderData(); + const { + info: game, + members + } = useLoaderData<{ info: MatchInfo, members: XCXMember[] }>(); 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)); + }, [game, map]); + const basePlayers = useMemo(() => { + return members.map(e => ({ ...e, name: e.realname } as BasePlayer)) + }, [members]); return (
navigate('/')}/> {game.title} - +
); } \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index c79e2cd..1175e0e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -13,10 +13,22 @@ export interface MatchInfo { players: Player[]; } -export interface Player { +export interface BasePlayer { + uid: string; name: string; - score: number; + score: string; +} + +export interface Player extends BasePlayer { avatar: string; info: string; - uid: string; } + +export interface XCXMember extends BasePlayer { + uid: string; + score: string; + realname: string; + name: string; + number: number; + age: string; +} \ No newline at end of file diff --git a/src/types/tag.ts b/src/types/tag.ts index 2af4055..dc6c2e6 100644 --- a/src/types/tag.ts +++ b/src/types/tag.ts @@ -9,4 +9,4 @@ export interface XCXTag { etype: EType; ename: string; count: string; -} \ No newline at end of file +} diff --git a/src/utils.ts b/src/utils.ts index f633f31..f8a3fba 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ // import { fetch } from 'bun'; -import type { IEventInfo, Player } from "./types"; +import type { IEventInfo, Player, XCXMember } from "./types"; import * as cheerio from "cheerio"; import fs from 'fs'; import path from 'path'; @@ -93,7 +93,7 @@ export function parseEventInfo(html: string) { const name = $(player).find('h6').text().trim(); const uid = /space-(?\d+).html/.exec($(player).find('h6 a').attr('href') ?? '')?.groups?.uid ?? ''; const info = $(player).find('p:nth-of-type(2)').text().replace(/\s/g, ''); - const score = Number(/^.*?\b(\d+)\b/.exec(info)?.[1]); + const score = /^.*?\b(\d+)\b/.exec(info)?.[1] ?? ''; players.push({ name, avatar: img, score, info, uid }); } return { @@ -160,4 +160,13 @@ export async function getPlayerTags(uid: string) { const data = await resp.json(); if (data.code !== 1) return null; return (data.data as XCXTag[]).filter(e => Number(e.count) > 0); +} + +export async function getMemberDetail(matchId: string, itemId: string) { + const resp = await fetch(`${XCX_BASE_URL}/api/enter/get_member_detail?id=${itemId}&match_id=${matchId}`, { + headers: xcxDefaultHeaders, + }); + const data = await resp.json(); + if (data.code !== 1) return null; + return (data.data.list as XCXMember[]); } \ No newline at end of file