feat: remove web-push dependency and add event summary links to game table

- Remove web-push and related sub-dependencies (asn1.js, http_ece, etc.) from package.json and bun.lock
- Update AppBar z-index to 8 for proper layering
- Enhance GameTable component:
  - Add Skeleton loading states for event names
  - Introduce EventName component with Link to event detail pages
  - Fix column widths and add row-span logic for event grouping
  - Enable horizontal scrolling for better mobile view
- Add /api/match-summary/:matchId endpoint for frontend event name fetching
- Rename getEventInfo to getMatchSummary in KaiqiuService for consistency
This commit is contained in:
kyuuseiryuu 2026-03-26 11:52:25 +09:00
parent 53b7f9928e
commit f1ca5cda75
7 changed files with 64 additions and 27 deletions

View File

@ -25,7 +25,6 @@
"react-dom": "^19",
"react-router": "^7.13.0",
"styled-components": "^6.3.8",
"web-push": "^3.6.7",
"zustand": "^5.0.10",
},
"devDependencies": {
@ -411,8 +410,6 @@
"arrify": ["arrify@2.0.1", "", {}, "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="],
"asn1.js": ["asn1.js@5.4.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "safer-buffer": "^2.1.0" } }, "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA=="],
"async-retry": ["async-retry@1.3.3", "", { "dependencies": { "retry": "0.13.1" } }, "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
@ -423,8 +420,6 @@
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
"bn.js": ["bn.js@4.12.3", "", {}, "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
@ -623,8 +618,6 @@
"http-status-codes": ["http-status-codes@2.3.0", "", {}, "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="],
"http_ece": ["http_ece@1.2.0", "", {}, "sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA=="],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
@ -719,10 +712,6 @@
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"mysql2": ["mysql2@3.15.3", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg=="],
@ -905,8 +894,6 @@
"valibot": ["valibot@1.2.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg=="],
"web-push": ["web-push@3.6.7", "", { "dependencies": { "asn1.js": "^5.3.0", "http_ece": "1.2.0", "https-proxy-agent": "^7.0.0", "jws": "^4.0.0", "minimist": "^1.2.5" }, "bin": { "web-push": "src/cli.js" } }, "sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A=="],
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="],

View File

@ -29,7 +29,6 @@
"react-dom": "^19",
"react-router": "^7.13.0",
"styled-components": "^6.3.8",
"web-push": "^3.6.7",
"zustand": "^5.0.10"
},
"devDependencies": {

View File

@ -10,7 +10,7 @@ const StyledContainer = styled.div`
left: -2px;
width: calc(100vw + 4px);
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3);
z-index: 1;
z-index: 8;
background: #181818;
font-size: 16px;
padding: 8px;

View File

@ -1,15 +1,17 @@
import { Button, Divider, Flex, Table as AntTable } from "antd";
import { Button, Divider, Flex, Table as AntTable, Skeleton } from "antd";
import type { GamesData } from "../types";
import User from "./User";
import { useMemo, useState } from "react";
import { useRequest } from "ahooks";
import styled from "styled-components";
import { Link } from "react-router";
const Table = styled(AntTable<GamesData>)`
width: 100%;
max-width: 400px;
.ant-table, .ant-table-footer {
background: transparent;
min-width: 320px;
max-width: 600px;
.ant-table-content {
--ant-table-row-hover-bg: var(--ant-color-bg-container);
}
`;
@ -22,7 +24,7 @@ function toTeams(data: GamesData) {
const {
uid1, uid11, username1, username11,
uid2, uid22, username2, username22,
result1, result2, ascore1, score1, score2,
// result1, result2, ascore1, score1, score2,
} = data;
const isDoubles = Boolean(username11);
const team1 = [{ uid: uid1, name: username1 }];
@ -58,7 +60,7 @@ export function GameTable(props: Props) {
pagination={false}
dataSource={displayData}
size="small"
style={{ width: '100%', maxWidth: 400, background: 'transparent' }}
scroll={{ x: 400 }}
footer={() => (
<Flex justify="center">
<Button
@ -69,8 +71,9 @@ export function GameTable(props: Props) {
</Flex>
)}
>
<Table.Column align="center" dataIndex={''} render={(_, __, i) => i + 1} />
<Table.Column width={60} fixed="left" align="center" dataIndex={''} render={(_, __, i) => i + 1} />
<Table.Column
width={100}
dataIndex='gameid'
align="center"
render={((_, record: GamesData) => {
@ -84,6 +87,7 @@ export function GameTable(props: Props) {
})}
/>
<Table.Column
width={100}
dataIndex='gameid'
align="center"
render={((_, record: GamesData) => {
@ -97,6 +101,7 @@ export function GameTable(props: Props) {
})}
/>
<Table.Column
width={40}
align="center"
dataIndex={'result1'}
render={(result1, { result2, uid1 }: GamesData) => {
@ -105,12 +110,33 @@ export function GameTable(props: Props) {
}}
/>
<Table.Column
width={60}
align="center"
dataIndex={'score1'}
render={(score1, { uid1, score2 }: GamesData) => {
return props.uid === uid1 ? score1 : score2;
}}
/>
<Table.Column dataIndex={'dateline'} />
<Table.Column
align="center"
// fixed='right'
width={120}
dataIndex={'dateline'}
onCell={(data, index) => {
const firstIndex = displayData.findIndex(e => e.dateline === data.dateline);
const size = displayData.filter(e => e.dateline === data.dateline).length;
const show = firstIndex === index;
if (show) {
return { rowSpan: size, style: { borderTop: '1px solid #f0f0f' } };
}
return { rowSpan: 0 };
}}
render={(dateline: string, { eventid }: GamesData) => {
return (
<EventName id={eventid} />
);
}}
/>
</Table>
</>
);
@ -125,3 +151,22 @@ function PlayerTeam(props: {
</Flex>
);
}
function EventName(props: { id: string }) {
const req = useRequest<string, []>(async () => {
return fetch(`/api/match-summary/${props.id}`)
.then(res => res.json())
.then(data => data.title ?? '-');
}, { refreshDeps: [props.id], debounceWait: 300 });
return (
<Skeleton loading={req.loading}>
<Link
type="text"
to={`/event/${props.id}`}
target="_blank"
>
{req.data}
</Link>
</Skeleton>
);
}

View File

@ -219,6 +219,12 @@ const server = Bun.serve({
});
},
},
"/api/match-summary/:matchId": {
async GET(req) {
const data = await KaiqiuService.getMatchSummary(req.params.matchId);
return Response.json(data);
}
},
"/api/match/:matchId": {
async GET(req) {
const data = await KaiqiuService.getMatchDetail(req.params.matchId);

View File

@ -50,7 +50,7 @@ export class EventSubscribeService {
}).then(v => v.map(e => e.event_id));
const events = [];
for (const eid of eids) {
const info = await KaiqiuService.getEventInfo(eid)
const info = await KaiqiuService.getMatchSummary(eid)
console.debug(
'Getting event info for %s - %s, should check: %s',
info.title, info.eventId, !info.isFinished && !info.isProcessing,

View File

@ -98,7 +98,7 @@ export class KaiqiuService {
location,
nums,
see,
} = await this.getEventInfo(matchId);
} = await this.getMatchSummary(matchId);
const event: IEventInfo = {
title,
info: [`比赛时间:${startDate}`, place, [see, nums].join(' ')],
@ -133,7 +133,7 @@ export class KaiqiuService {
return { lng, lat };
}
public static async getEventInfo(eventId: string, force?: boolean): Promise<MatchSummary> {
public static async getMatchSummary(eventId: string, force?: boolean): Promise<MatchSummary> {
// https://kaiqiuwang.cc/home/space-event-id-175775.html
const key = `my-kaiqiuwang:event-info:${eventId}`;
const eventURL = `${this.#baseURL}/home/space-event-id-${eventId}.html`;
@ -183,7 +183,7 @@ export class KaiqiuService {
const url = `${this.#baseURL}/home/space.php?do=event&id=${eventId}&view=member&status=2`;
const key = `my-kaiqiuwang:match-detail:${eventId}`;
let html = await redis.get(key) ?? '';
const info = await this.getEventInfo(eventId);
const info = await this.getMatchSummary(eventId);
if (!html || html.includes('连接超时') || force) {
html = await fetch(url, { headers: htmlRequestHeaders }).then(res => res.text() || '');
if (info.isFinished) {