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-dom": "^19",
"react-router": "^7.13.0", "react-router": "^7.13.0",
"styled-components": "^6.3.8", "styled-components": "^6.3.8",
"web-push": "^3.6.7",
"zustand": "^5.0.10", "zustand": "^5.0.10",
}, },
"devDependencies": { "devDependencies": {
@ -411,8 +410,6 @@
"arrify": ["arrify@2.0.1", "", {}, "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="], "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=="], "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=="], "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=="], "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=="], "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=="], "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-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=="], "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=="], "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=="], "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=="], "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=="], "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=="], "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-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="],

View File

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

View File

@ -10,7 +10,7 @@ const StyledContainer = styled.div`
left: -2px; left: -2px;
width: calc(100vw + 4px); width: calc(100vw + 4px);
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3); box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3);
z-index: 1; z-index: 8;
background: #181818; background: #181818;
font-size: 16px; font-size: 16px;
padding: 8px; 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 type { GamesData } from "../types";
import User from "./User"; import User from "./User";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { useRequest } from "ahooks"; import { useRequest } from "ahooks";
import styled from "styled-components"; import styled from "styled-components";
import { Link } from "react-router";
const Table = styled(AntTable<GamesData>)` const Table = styled(AntTable<GamesData>)`
width: 100%; width: 100%;
max-width: 400px; min-width: 320px;
.ant-table, .ant-table-footer { max-width: 600px;
background: transparent; .ant-table-content {
--ant-table-row-hover-bg: var(--ant-color-bg-container);
} }
`; `;
@ -22,7 +24,7 @@ function toTeams(data: GamesData) {
const { const {
uid1, uid11, username1, username11, uid1, uid11, username1, username11,
uid2, uid22, username2, username22, uid2, uid22, username2, username22,
result1, result2, ascore1, score1, score2, // result1, result2, ascore1, score1, score2,
} = data; } = data;
const isDoubles = Boolean(username11); const isDoubles = Boolean(username11);
const team1 = [{ uid: uid1, name: username1 }]; const team1 = [{ uid: uid1, name: username1 }];
@ -58,7 +60,7 @@ export function GameTable(props: Props) {
pagination={false} pagination={false}
dataSource={displayData} dataSource={displayData}
size="small" size="small"
style={{ width: '100%', maxWidth: 400, background: 'transparent' }} scroll={{ x: 400 }}
footer={() => ( footer={() => (
<Flex justify="center"> <Flex justify="center">
<Button <Button
@ -69,8 +71,9 @@ export function GameTable(props: Props) {
</Flex> </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 <Table.Column
width={100}
dataIndex='gameid' dataIndex='gameid'
align="center" align="center"
render={((_, record: GamesData) => { render={((_, record: GamesData) => {
@ -84,6 +87,7 @@ export function GameTable(props: Props) {
})} })}
/> />
<Table.Column <Table.Column
width={100}
dataIndex='gameid' dataIndex='gameid'
align="center" align="center"
render={((_, record: GamesData) => { render={((_, record: GamesData) => {
@ -97,6 +101,7 @@ export function GameTable(props: Props) {
})} })}
/> />
<Table.Column <Table.Column
width={40}
align="center" align="center"
dataIndex={'result1'} dataIndex={'result1'}
render={(result1, { result2, uid1 }: GamesData) => { render={(result1, { result2, uid1 }: GamesData) => {
@ -105,12 +110,33 @@ export function GameTable(props: Props) {
}} }}
/> />
<Table.Column <Table.Column
width={60}
align="center"
dataIndex={'score1'} dataIndex={'score1'}
render={(score1, { uid1, score2 }: GamesData) => { render={(score1, { uid1, score2 }: GamesData) => {
return props.uid === uid1 ? score1 : score2; 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> </Table>
</> </>
); );
@ -125,3 +151,22 @@ function PlayerTeam(props: {
</Flex> </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": { "/api/match/:matchId": {
async GET(req) { async GET(req) {
const data = await KaiqiuService.getMatchDetail(req.params.matchId); 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)); }).then(v => v.map(e => e.event_id));
const events = []; const events = [];
for (const eid of eids) { for (const eid of eids) {
const info = await KaiqiuService.getEventInfo(eid) const info = await KaiqiuService.getMatchSummary(eid)
console.debug( console.debug(
'Getting event info for %s - %s, should check: %s', 'Getting event info for %s - %s, should check: %s',
info.title, info.eventId, !info.isFinished && !info.isProcessing, info.title, info.eventId, !info.isFinished && !info.isProcessing,

View File

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