feat(Profile)
- add react-router
This commit is contained in:
parent
7ca9c46ba6
commit
2f82a4edf5
384
__test__/data/profile.json
Normal file
384
__test__/data/profile.json
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "获得用户高级信息",
|
||||||
|
"time": "1769560179",
|
||||||
|
"data": {
|
||||||
|
"uid": "73276",
|
||||||
|
"username": "武藏野",
|
||||||
|
"Top3ManOfBeat": "12332,379345,562751,",
|
||||||
|
"Top3ManOfBeatUsernameScore": [
|
||||||
|
"陳允文(1889)",
|
||||||
|
"邵博文(1854)",
|
||||||
|
"张磊(1845)"
|
||||||
|
],
|
||||||
|
"Top3WomanOfBeat": "100252,100748,452471,",
|
||||||
|
"Top3WomanOfPlay": "0",
|
||||||
|
"Top3OfBeat": "379345,562751,452471,",
|
||||||
|
"Top3WomanOfBeatUsernameScore": [
|
||||||
|
"惠宇晨(1965)",
|
||||||
|
"楡原al(1815)",
|
||||||
|
"三島悠華(1766)"
|
||||||
|
],
|
||||||
|
"TopPlayer": "107828,428006,427831,",
|
||||||
|
"TopPlayerUsernameScore": [
|
||||||
|
"陈永航(2150)",
|
||||||
|
"金田優陽(2058)",
|
||||||
|
"李冠远(2006)"
|
||||||
|
],
|
||||||
|
"OftenPlayer": "铁板李(7),中河(9),cldws(10),大国(12),滨町张磊(16),chen5274(16),",
|
||||||
|
"maxConsWin": "11",
|
||||||
|
"maxConsWinLastGameId": "10193043",
|
||||||
|
"allCities": [
|
||||||
|
"日本"
|
||||||
|
],
|
||||||
|
"win": "153",
|
||||||
|
"lose": "89",
|
||||||
|
"total": "242",
|
||||||
|
"dateline": "1455960573",
|
||||||
|
"province": "上海",
|
||||||
|
"city": "浦东新",
|
||||||
|
"brand": "银河",
|
||||||
|
"if_event_uid": "0",
|
||||||
|
"score": "1711",
|
||||||
|
"goldNum": "2",
|
||||||
|
"cityNum": "1",
|
||||||
|
"path": "",
|
||||||
|
"pathNum": "0",
|
||||||
|
"champion": "",
|
||||||
|
"orgTimes": "0",
|
||||||
|
"ly_event_times": "6",
|
||||||
|
"ly_event_games": "49",
|
||||||
|
"ly_event_rank": "0",
|
||||||
|
"ly_max_inc": "35",
|
||||||
|
"ly_max_dis": "15.78",
|
||||||
|
"ly_beatit": "惠惠惠yc(1965)",
|
||||||
|
"ly_shopid": "2135",
|
||||||
|
"sleep": "0",
|
||||||
|
"pathWords": "",
|
||||||
|
"fuxing": "张成成,张成成,1666,22992,4012390,3胜0负 胜率:100% 胜负局:9/1;cldws,吴庶,1631,71669,14807559,10胜0负 胜率:100% 胜负局:28/8",
|
||||||
|
"kuzhu": "邵仁爱,邵仁爱,1781,68186,13745808,0胜4负 胜率:0% 胜负局:3/11;卡斯柏,汪志浩,1893,89518,5038808,0胜4负 胜率:0% 胜负局:1/11;萍姐,尹艳萍,1897,381359,5846361,0胜3负 胜率:0% 胜负局:2/9",
|
||||||
|
"rank": "53424",
|
||||||
|
"ifHonorShow": "1",
|
||||||
|
"locationupdatetime": "0",
|
||||||
|
"resideupdatetime": "0",
|
||||||
|
"ifManage": 0,
|
||||||
|
"age": 62,
|
||||||
|
"maxscore": "1828",
|
||||||
|
"maxScoreTheYear": "1819",
|
||||||
|
"beat": "惠宇晨(1965)",
|
||||||
|
"rate": "87.18%",
|
||||||
|
"Top3OfBeatUsernameScore": [
|
||||||
|
"惠宇晨(1965)",
|
||||||
|
"陳允文(1889)",
|
||||||
|
"邵博文(1854)"
|
||||||
|
],
|
||||||
|
"KuZhu": {
|
||||||
|
"names": [
|
||||||
|
"邵仁爱(1781)",
|
||||||
|
"汪志浩(1893)",
|
||||||
|
"尹艳萍(1897)"
|
||||||
|
],
|
||||||
|
"uids": [
|
||||||
|
"68186",
|
||||||
|
"89518",
|
||||||
|
"381359"
|
||||||
|
],
|
||||||
|
"gameids": [
|
||||||
|
"13745808",
|
||||||
|
"5038808",
|
||||||
|
"5846361"
|
||||||
|
],
|
||||||
|
"winlose": [
|
||||||
|
"0胜4负 胜率:0% 胜负局:3/11",
|
||||||
|
"0胜4负 胜率:0% 胜负局:1/11",
|
||||||
|
"0胜3负 胜率:0% 胜负局:2/9"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"FuXing": {
|
||||||
|
"names": [
|
||||||
|
"张成成(1666)",
|
||||||
|
"吴庶(1631)"
|
||||||
|
],
|
||||||
|
"uids": [
|
||||||
|
"22992",
|
||||||
|
"71669"
|
||||||
|
],
|
||||||
|
"gameids": [
|
||||||
|
"4012390",
|
||||||
|
"14807559"
|
||||||
|
],
|
||||||
|
"winlose": [
|
||||||
|
"3胜0负 胜率:100% 胜负局:9/1",
|
||||||
|
"10胜0负 胜率:100% 胜负局:28/8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lastUpdate": "2026年01月22日",
|
||||||
|
"realpic": "https://oss.kaiqiu.cc/avatar/000/07/32/76_avatar_big.jpg",
|
||||||
|
"realname": "lihua",
|
||||||
|
"qiupai": "蝴蝶Butterfly",
|
||||||
|
"qiupaitype": "普里莫拉茨碳",
|
||||||
|
"fanshou": "蝴蝶BUTTERFLY",
|
||||||
|
"fanshoutype": "Tenergy 05 FX",
|
||||||
|
"zhengshou": "蝴蝶BUTTERFLY",
|
||||||
|
"zhengshoutype": "Flarestorm Ⅱ",
|
||||||
|
"resideprovince": "上海 浦东新",
|
||||||
|
"sex": "男",
|
||||||
|
"bg": "业余选手",
|
||||||
|
"scope": "全国",
|
||||||
|
"description": "lihua 于 2016年02月20日 这一天开启了开球网ChinaTT积分赛的神奇之旅,他是开球网的第 73276 位忠实用户。lihua共进行了 242 盘单打比赛,其中获胜 153 盘,失利 89 盘。lihua的积分超过了全国 87.18% 的乒乓球选手,应是业余准高手,小圈子里前几名的水平,对乒乓球有一定的理解。lihua曾战胜的最高分选手:惠宇晨(1965)",
|
||||||
|
"ifHonor": 1,
|
||||||
|
"honors": [
|
||||||
|
{
|
||||||
|
"uid": "73276",
|
||||||
|
"hid": "450465",
|
||||||
|
"eventid": "70476",
|
||||||
|
"itemid": "6034415",
|
||||||
|
"honor": "https://kaiqiuwang.cc/home/image/icon/bronze_medal16.png",
|
||||||
|
"subject": "2024年12月22日 东华乒乓球俱乐部2024年1季军",
|
||||||
|
"posttime": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "73276",
|
||||||
|
"hid": "274549",
|
||||||
|
"eventid": "47712",
|
||||||
|
"itemid": "38822",
|
||||||
|
"honor": "https://kaiqiuwang.cc/home/image/icon/gold_medal16.png",
|
||||||
|
"subject": "2024年6月15日 东华乒乓球俱乐部2024年6月冠军",
|
||||||
|
"posttime": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "73276",
|
||||||
|
"hid": "26332",
|
||||||
|
"eventid": "10049",
|
||||||
|
"itemid": "0",
|
||||||
|
"honor": "https://kaiqiuwang.cc/home/image/icon/silver_medal16.png",
|
||||||
|
"subject": "东华乒乓球俱乐部2018年2月份积分争霸赛亚军",
|
||||||
|
"posttime": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "73276",
|
||||||
|
"hid": "25609",
|
||||||
|
"eventid": "9708",
|
||||||
|
"itemid": "0",
|
||||||
|
"honor": "https://kaiqiuwang.cc/home/image/icon/gold_medal16.png",
|
||||||
|
"subject": "东华乒乓球俱乐部2017年12月份积分争霸赛冠军",
|
||||||
|
"posttime": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "73276",
|
||||||
|
"hid": "16388",
|
||||||
|
"eventid": "6078",
|
||||||
|
"itemid": "0",
|
||||||
|
"honor": "https://kaiqiuwang.cc/home/image/icon/bronze_medal16.png",
|
||||||
|
"subject": "东华乒乓球俱乐部2016年4月份积分赛季军",
|
||||||
|
"posttime": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"games": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"gameid": "20730731",
|
||||||
|
"uid1": "73276",
|
||||||
|
"uid2": "666599",
|
||||||
|
"uid11": "0",
|
||||||
|
"uid22": "0",
|
||||||
|
"username1": "武藏野",
|
||||||
|
"username2": "庞程万里",
|
||||||
|
"username11": "",
|
||||||
|
"username22": "",
|
||||||
|
"result1": "3",
|
||||||
|
"result2": "0",
|
||||||
|
"score1": "+16",
|
||||||
|
"score2": "-16",
|
||||||
|
"eventid": "157007",
|
||||||
|
"ascore1": "1711",
|
||||||
|
"dateline": "2026-01-17",
|
||||||
|
"groupid": "708465111",
|
||||||
|
"flag": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gameid": "20730724",
|
||||||
|
"uid1": "73276",
|
||||||
|
"uid2": "563021",
|
||||||
|
"uid11": "0",
|
||||||
|
"uid22": "0",
|
||||||
|
"username1": "武藏野",
|
||||||
|
"username2": "Tracy_god",
|
||||||
|
"username11": "",
|
||||||
|
"username22": "",
|
||||||
|
"result1": "0",
|
||||||
|
"result2": "3",
|
||||||
|
"score1": "-7",
|
||||||
|
"score2": "7",
|
||||||
|
"eventid": "157007",
|
||||||
|
"ascore1": "1695",
|
||||||
|
"dateline": "2026-01-17",
|
||||||
|
"groupid": "708465111",
|
||||||
|
"flag": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gameid": "20730720",
|
||||||
|
"uid1": "73276",
|
||||||
|
"uid2": "72155",
|
||||||
|
"uid11": "0",
|
||||||
|
"uid22": "0",
|
||||||
|
"username1": "武藏野",
|
||||||
|
"username2": "大国",
|
||||||
|
"username11": "",
|
||||||
|
"username22": "",
|
||||||
|
"result1": "1",
|
||||||
|
"result2": "3",
|
||||||
|
"score1": "-2",
|
||||||
|
"score2": "2",
|
||||||
|
"eventid": "157007",
|
||||||
|
"ascore1": "1866",
|
||||||
|
"dateline": "2026-01-17",
|
||||||
|
"groupid": "708465111",
|
||||||
|
"flag": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gameid": "20730718",
|
||||||
|
"uid1": "73276",
|
||||||
|
"uid2": "58891",
|
||||||
|
"uid11": "0",
|
||||||
|
"uid22": "0",
|
||||||
|
"username1": "武藏野",
|
||||||
|
"username2": "阿迪73",
|
||||||
|
"username11": "",
|
||||||
|
"username22": "",
|
||||||
|
"result1": "2",
|
||||||
|
"result2": "3",
|
||||||
|
"score1": "-10",
|
||||||
|
"score2": "10",
|
||||||
|
"eventid": "157007",
|
||||||
|
"ascore1": "1708",
|
||||||
|
"dateline": "2026-01-17",
|
||||||
|
"groupid": "708465111",
|
||||||
|
"flag": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gameid": "20730715",
|
||||||
|
"uid1": "73276",
|
||||||
|
"uid2": "68186",
|
||||||
|
"uid11": "0",
|
||||||
|
"uid22": "0",
|
||||||
|
"username1": "武藏野",
|
||||||
|
"username2": "邵仁爱",
|
||||||
|
"username11": "",
|
||||||
|
"username22": "",
|
||||||
|
"result1": "1",
|
||||||
|
"result2": "3",
|
||||||
|
"score1": "-5",
|
||||||
|
"score2": "5",
|
||||||
|
"eventid": "157007",
|
||||||
|
"ascore1": "1787",
|
||||||
|
"dateline": "2026-01-17",
|
||||||
|
"groupid": "708465111",
|
||||||
|
"flag": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gameid": "20730713",
|
||||||
|
"uid1": "73276",
|
||||||
|
"uid2": "376886",
|
||||||
|
"uid11": "0",
|
||||||
|
"uid22": "0",
|
||||||
|
"username1": "武藏野",
|
||||||
|
"username2": "中河",
|
||||||
|
"username11": "",
|
||||||
|
"username22": "",
|
||||||
|
"result1": "1",
|
||||||
|
"result2": "3",
|
||||||
|
"score1": "-6",
|
||||||
|
"score2": "6",
|
||||||
|
"eventid": "157007",
|
||||||
|
"ascore1": "1770",
|
||||||
|
"dateline": "2026-01-17",
|
||||||
|
"groupid": "708465111",
|
||||||
|
"flag": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gameid": "17112521",
|
||||||
|
"uid1": "73276",
|
||||||
|
"uid2": "434868",
|
||||||
|
"uid11": "0",
|
||||||
|
"uid22": "0",
|
||||||
|
"username1": "武藏野",
|
||||||
|
"username2": "薛高远",
|
||||||
|
"username11": "",
|
||||||
|
"username22": "",
|
||||||
|
"result1": "0",
|
||||||
|
"result2": "3",
|
||||||
|
"score1": "-13",
|
||||||
|
"score2": "13",
|
||||||
|
"eventid": "117651",
|
||||||
|
"ascore1": "1692",
|
||||||
|
"dateline": "2025-08-24",
|
||||||
|
"groupid": "-1",
|
||||||
|
"flag": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gameid": "17070397",
|
||||||
|
"uid1": "73276",
|
||||||
|
"uid2": "439944",
|
||||||
|
"uid11": "0",
|
||||||
|
"uid22": "0",
|
||||||
|
"username1": "武藏野",
|
||||||
|
"username2": "周诗博",
|
||||||
|
"username11": "",
|
||||||
|
"username22": "",
|
||||||
|
"result1": "0",
|
||||||
|
"result2": "3",
|
||||||
|
"score1": "-13",
|
||||||
|
"score2": "13",
|
||||||
|
"eventid": "117651",
|
||||||
|
"ascore1": "1738",
|
||||||
|
"dateline": "2025-08-24",
|
||||||
|
"groupid": "703644614",
|
||||||
|
"flag": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gameid": "17070393",
|
||||||
|
"uid1": "73276",
|
||||||
|
"uid2": "90032",
|
||||||
|
"uid11": "0",
|
||||||
|
"uid22": "0",
|
||||||
|
"username1": "武藏野",
|
||||||
|
"username2": "骏骏",
|
||||||
|
"username11": "",
|
||||||
|
"username22": "",
|
||||||
|
"result1": "3",
|
||||||
|
"result2": "1",
|
||||||
|
"score1": "+4",
|
||||||
|
"score2": "-4",
|
||||||
|
"eventid": "117651",
|
||||||
|
"ascore1": "1751",
|
||||||
|
"dateline": "2025-08-24",
|
||||||
|
"groupid": "703644614",
|
||||||
|
"flag": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gameid": "17070389",
|
||||||
|
"uid1": "73276",
|
||||||
|
"uid2": "318418",
|
||||||
|
"uid11": "0",
|
||||||
|
"uid22": "0",
|
||||||
|
"username1": "武藏野",
|
||||||
|
"username2": "SYF777",
|
||||||
|
"username11": "",
|
||||||
|
"username22": "",
|
||||||
|
"result1": "3",
|
||||||
|
"result2": "2",
|
||||||
|
"score1": "+2",
|
||||||
|
"score2": "-2",
|
||||||
|
"eventid": "117651",
|
||||||
|
"ascore1": "1747",
|
||||||
|
"dateline": "2025-08-24",
|
||||||
|
"groupid": "703644614",
|
||||||
|
"flag": "0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"latest_headtohead_gameid": "19767882",
|
||||||
|
"hasFollowed": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,6 +21,7 @@ test('event content not empty', () => {
|
|||||||
expect(itemId).toBe(item_id);
|
expect(itemId).toBe(item_id);
|
||||||
expect(players.length).toBeGreaterThan(0);
|
expect(players.length).toBeGreaterThan(0);
|
||||||
console.log(players);
|
console.log(players);
|
||||||
|
expect(players[0]?.uid).not.toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("group", () => {
|
test("group", () => {
|
||||||
|
|||||||
12
__test__/xcxapi.test.ts
Normal file
12
__test__/xcxapi.test.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { test, expect } from 'bun:test';
|
||||||
|
import { getAdvProfile } from '../src/utils';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
test('Test profile', async () => {
|
||||||
|
const uid = '73276';
|
||||||
|
const profile = await getAdvProfile(uid);
|
||||||
|
console.log(profile)
|
||||||
|
expect(profile).not.toBe(null)
|
||||||
|
fs.writeFileSync(path.resolve(__dirname, 'data', 'profile.json'), JSON.stringify(profile, null, 2));
|
||||||
|
});
|
||||||
10
bun.lock
10
bun.lock
@ -12,6 +12,8 @@
|
|||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"react-dom": "^19",
|
"react-dom": "^19",
|
||||||
|
"react-router": "^7.13.0",
|
||||||
|
"zustand": "^5.0.10",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
@ -156,6 +158,8 @@
|
|||||||
|
|
||||||
"compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="],
|
"compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="],
|
||||||
|
|
||||||
|
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||||
|
|
||||||
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
|
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
|
||||||
|
|
||||||
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
|
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
|
||||||
@ -206,6 +210,8 @@
|
|||||||
|
|
||||||
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||||
|
|
||||||
|
"react-router": ["react-router@7.13.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw=="],
|
||||||
|
|
||||||
"resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="],
|
"resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="],
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
@ -216,6 +222,8 @@
|
|||||||
|
|
||||||
"scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="],
|
"scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="],
|
||||||
|
|
||||||
|
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
|
||||||
|
|
||||||
"string-convert": ["string-convert@0.2.1", "", {}, "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="],
|
"string-convert": ["string-convert@0.2.1", "", {}, "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="],
|
||||||
|
|
||||||
"stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="],
|
"stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="],
|
||||||
@ -232,6 +240,8 @@
|
|||||||
|
|
||||||
"whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
|
"whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
|
||||||
|
|
||||||
|
"zustand": ["zustand@5.0.10", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg=="],
|
||||||
|
|
||||||
"htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
"htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||||
|
|
||||||
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||||
|
|||||||
@ -15,7 +15,9 @@
|
|||||||
"cheerio": "^1.2.0",
|
"cheerio": "^1.2.0",
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"react-dom": "^19"
|
"react-dom": "^19",
|
||||||
|
"react-router": "^7.13.0",
|
||||||
|
"zustand": "^5.0.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
|||||||
22
src/App.tsx
22
src/App.tsx
@ -1,29 +1,19 @@
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback } from "react";
|
||||||
import { ClubSelector } from "./components/GameSelector";
|
import { ClubSelector } from "./components/GameSelector";
|
||||||
|
import type { IEventInfo } from "./types";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import type { IEventInfo, Player } from "./types";
|
|
||||||
import { Drawer, Tabs } from "antd";
|
|
||||||
import { PlayerList } from "./components/PlayerList";
|
|
||||||
import { GamePanel } from "./components/GamePanel";
|
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const [game, setGame] = useState<IEventInfo>();
|
const navigate = useNavigate();
|
||||||
const handleGameClick = useCallback(async (game: IEventInfo) => {
|
const handleGameClick = useCallback(async (game: IEventInfo) => {
|
||||||
setGame(game);
|
navigate(`/event/${game.matchId}`);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<h1>开球网比赛分组预测</h1>
|
<h1>开球网比赛分组预测</h1>
|
||||||
<ClubSelector onGameClick={handleGameClick} />
|
<ClubSelector onGameClick={handleGameClick} />
|
||||||
<Drawer
|
|
||||||
placement="bottom"
|
|
||||||
title={game?.title}
|
|
||||||
open={Boolean(game)}
|
|
||||||
onClose={() => setGame(undefined)}
|
|
||||||
size={'calc(100vh - 100px)'}
|
|
||||||
>
|
|
||||||
<GamePanel game={game} />
|
|
||||||
</Drawer>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import type { IEventInfo, MatchInfo } from "../types";
|
import type { IEventInfo, MatchInfo, Player } from "../types";
|
||||||
import { useRequest } from "ahooks";
|
import { useRequest } from "ahooks";
|
||||||
import { Spin, Tabs } from "antd";
|
import { Spin, Tabs } from "antd";
|
||||||
import { PlayerList } from "./PlayerList";
|
import { PlayerList } from "./PlayerList";
|
||||||
@ -7,34 +7,30 @@ import { GroupingPrediction } from "./GroupingPrediction";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
game?: IEventInfo;
|
title: string;
|
||||||
|
players?: Player[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GamePanel: React.FC<Props> = props => {
|
export const GamePanel: React.FC<Props> = props => {
|
||||||
const fetchPlayers = useRequest<MatchInfo | null, []>(async () => {
|
|
||||||
if (!props.game) return null;
|
|
||||||
const info: MatchInfo = await (await fetch(`/api/match/${props.game.matchId}`)).json();
|
|
||||||
return info;
|
|
||||||
}, { refreshDeps: [props] });
|
|
||||||
const sneckMode = useMemo(() => {
|
const sneckMode = useMemo(() => {
|
||||||
return !!props.game?.title?.includes('争霸赛');
|
return !!props.title?.includes('争霸赛');
|
||||||
}, [props.game]);
|
}, [props.title]);
|
||||||
return (
|
return (
|
||||||
<Spin spinning={fetchPlayers.loading}>
|
<>
|
||||||
<Tabs
|
<Tabs
|
||||||
items={fetchPlayers.loading ? [] : [
|
items={[
|
||||||
{
|
{
|
||||||
key: 'groups',
|
key: 'groups',
|
||||||
label: '分组预测',
|
label: '分组预测',
|
||||||
children: <GroupingPrediction sneckMode={sneckMode} players={fetchPlayers.data?.players} />
|
children: <GroupingPrediction sneckMode={sneckMode} players={props.players} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'players',
|
key: 'players',
|
||||||
label: '成员列表',
|
label: '成员列表',
|
||||||
children: <PlayerList players={fetchPlayers.data?.players} />
|
children: <PlayerList players={props.players} />
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Spin>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@ import { clubs } from './clubList';
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useRequest } from 'ahooks';
|
import { useRequest } from 'ahooks';
|
||||||
import { GlobalOutlined } from '@ant-design/icons';
|
import { GlobalOutlined } from '@ant-design/icons';
|
||||||
import type { IEventInfo } from '../../types';
|
import type { IEventInfo } from '../..';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onGameClick?: (info: IEventInfo) => void;
|
onGameClick?: (info: IEventInfo) => void;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Card, Space, Table, Typography } from "antd";
|
import { Card, Table } from "antd";
|
||||||
import type { Player } from "../types";
|
import type { Player } from "../types";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import User from "./User";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
index: number;
|
index: number;
|
||||||
@ -27,7 +28,7 @@ export const GroupMember: React.FC<Props> = props => {
|
|||||||
columns={[
|
columns={[
|
||||||
{ dataIndex: '_', render: (_, __, i) => `(${i + 1})` },
|
{ dataIndex: '_', render: (_, __, i) => `(${i + 1})` },
|
||||||
{ dataIndex: 'index' },
|
{ dataIndex: 'index' },
|
||||||
{ dataIndex: 'name' },
|
{ dataIndex: 'name', render: (name, { uid }) => <User name={name} uid={uid} /> },
|
||||||
{ dataIndex: 'score' },
|
{ dataIndex: 'score' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export const GroupingPrediction: React.FC<Props> = props => {
|
|||||||
}, [grouped, groupLen, maxPlayerSize]);
|
}, [grouped, groupLen, maxPlayerSize]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form layout='horizontal'>
|
<Flex gap={10} wrap>
|
||||||
<Form.Item label={'取人数'}>
|
<Form.Item label={'取人数'}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
value={maxPlayerSize}
|
value={maxPlayerSize}
|
||||||
@ -52,7 +52,7 @@ export const GroupingPrediction: React.FC<Props> = props => {
|
|||||||
<Form.Item label="蛇形分组">
|
<Form.Item label="蛇形分组">
|
||||||
<Switch checked={sneckMode} onChange={setSneckMode} />
|
<Switch checked={sneckMode} onChange={setSneckMode} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Flex>
|
||||||
<Flex gap='middle' wrap align="center" justify="center">
|
<Flex gap='middle' wrap align="center" justify="center">
|
||||||
<React.Fragment key={'normal'}>
|
<React.Fragment key={'normal'}>
|
||||||
{ !sneckMode && grouped.map((p, i) => <GroupMember key={i} players={p} index={i} />)}
|
{ !sneckMode && grouped.map((p, i) => <GroupMember key={i} players={p} index={i} />)}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import type { Player } from "../types";
|
import type { Player } from "../types";
|
||||||
import { Avatar, Table } from "antd";
|
import { Avatar, Table } from "antd";
|
||||||
|
import User from "./User";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
@ -17,7 +18,13 @@ export const PlayerList: React.FC<Props> = props => {
|
|||||||
>
|
>
|
||||||
<Table.Column width={32} dataIndex={'_'} align="center" render={(_, __, i) => `${i + 1}`} />
|
<Table.Column width={32} dataIndex={'_'} align="center" render={(_, __, i) => `${i + 1}`} />
|
||||||
<Table.Column width={32} dataIndex={'avatar'} align="center" render={src => <Avatar src={src} />} />
|
<Table.Column width={32} dataIndex={'avatar'} align="center" render={src => <Avatar src={src} />} />
|
||||||
<Table.Column width={200} title="姓名" align="center" dataIndex={'name'} />
|
<Table.Column
|
||||||
|
width={200}
|
||||||
|
title="姓名"
|
||||||
|
align="center"
|
||||||
|
dataIndex={'name'}
|
||||||
|
render={(name, { uid }) => <User name={name} uid={uid} />}
|
||||||
|
/>
|
||||||
<Table.Column width={200} dataIndex={'score'} title="积分" sorter={{
|
<Table.Column width={200} dataIndex={'score'} title="积分" sorter={{
|
||||||
compare: ({ score: a }: Player, { score: b}: Player) => a - b,
|
compare: ({ score: a }: Player, { score: b}: Player) => a - b,
|
||||||
}} />
|
}} />
|
||||||
|
|||||||
11
src/components/User.tsx
Normal file
11
src/components/User.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Link } from "react-router";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string;
|
||||||
|
uid: string;
|
||||||
|
}
|
||||||
|
export default function User(props: Props) {
|
||||||
|
return (
|
||||||
|
<Link to={`/profile/${props.uid}`}>{props.name}</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -5,18 +5,45 @@
|
|||||||
* It is included in `src/index.html`.
|
* It is included in `src/index.html`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { StrictMode } from "react";
|
import { Component, StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { App } from "./App";
|
import { App } from "./App";
|
||||||
import { ConfigProvider, theme } from "antd";
|
import { ConfigProvider, theme } from "antd";
|
||||||
|
import { createBrowserRouter, RouterProvider } from "react-router";
|
||||||
|
import ProfilePage from "./page/ProfilePage";
|
||||||
|
import EventPage from "./page/EventPage";
|
||||||
|
import type { MatchInfo } from "./types";
|
||||||
|
|
||||||
const elem = document.getElementById("root")!;
|
const elem = document.getElementById("root")!;
|
||||||
|
|
||||||
|
const route = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <App />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/event/:matchId',
|
||||||
|
loader: async ({ params }) => {
|
||||||
|
const info: MatchInfo = await (await fetch(`/api/match/${params.matchId}`)).json();
|
||||||
|
return info;
|
||||||
|
},
|
||||||
|
Component: EventPage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/profile/:uid',
|
||||||
|
loader: async ({ params }) => {
|
||||||
|
return fetch(`/api/user/${params.uid}`);
|
||||||
|
},
|
||||||
|
Component: ProfilePage,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const app = (
|
const app = (
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<ConfigProvider theme={{
|
<ConfigProvider theme={{
|
||||||
algorithm: theme.darkAlgorithm,
|
algorithm: theme.darkAlgorithm,
|
||||||
}}>
|
}}>
|
||||||
<App />
|
<RouterProvider router={route} />
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,7 +6,8 @@
|
|||||||
background-color: #242424;
|
background-color: #242424;
|
||||||
}
|
}
|
||||||
#root {
|
#root {
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
|
max-width: 100vw;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { serve } from "bun";
|
import { serve } from "bun";
|
||||||
import index from "./index.html";
|
import index from "./index.html";
|
||||||
import { getMatchInfo, listEvent } from "./utils";
|
import { getAdvProfile, getMatchInfo, listEvent } from "./utils";
|
||||||
|
|
||||||
const server = serve({
|
const server = serve({
|
||||||
port: process.env.PORT || 3000,
|
port: process.env.PORT || 3000,
|
||||||
@ -19,6 +19,13 @@ const server = serve({
|
|||||||
return Response.json(data);
|
return Response.json(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/user/:uid": {
|
||||||
|
async GET(req) {
|
||||||
|
const uid = req.params.uid;
|
||||||
|
const profile = await getAdvProfile(uid);
|
||||||
|
return Response.json(profile);
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
development: process.env.NODE_ENV !== "production" && {
|
development: process.env.NODE_ENV !== "production" && {
|
||||||
|
|||||||
12
src/page/EventPage.tsx
Normal file
12
src/page/EventPage.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { useLoaderData, useNavigate } from "react-router";
|
||||||
|
import { GamePanel } from "../components/GamePanel";
|
||||||
|
import type { MatchInfo } from "../types";
|
||||||
|
|
||||||
|
export default function EventPage() {
|
||||||
|
const game = useLoaderData<MatchInfo>();
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%', padding: 10, boxSizing: 'border-box' }}>
|
||||||
|
<GamePanel title={game.title} players={game.players} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
72
src/page/ProfilePage.tsx
Normal file
72
src/page/ProfilePage.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { useLoaderData } from "react-router";
|
||||||
|
import type { XCXProfile } from "../types/profile";
|
||||||
|
import { Avatar, Descriptions, Divider, Flex, Image, Typography } from "antd";
|
||||||
|
|
||||||
|
function Honor(props: { honors?: XCXProfile['honors'] }) {
|
||||||
|
if (!props.honors?.length) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Divider>荣誉</Divider>
|
||||||
|
<Flex vertical gap={12}>
|
||||||
|
{props.honors?.map(honor => {
|
||||||
|
return (
|
||||||
|
<Flex key={honor.hid} gap={12} style={{ width: '100%' }}>
|
||||||
|
<Image style={{ mixBlendMode: 'multiply' }} src={honor.honor} />
|
||||||
|
<Typography.Text>{honor.subject}</Typography.Text>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Raket(props: { profile?: XCXProfile | null }) {
|
||||||
|
const { qiupaitype, zhengshoutype, fanshoutype, qiupai, zhengshou, fanshou } = props.profile || {};
|
||||||
|
if ([qiupaitype, zhengshoutype, fanshoutype].every(e => !e)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Divider>装备</Divider>
|
||||||
|
<Descriptions>
|
||||||
|
<Descriptions.Item label="底板">
|
||||||
|
{qiupaitype}
|
||||||
|
<Typography.Text type="secondary" style={{ marginLeft: 4 }}>({qiupai})</Typography.Text>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="正手">
|
||||||
|
{zhengshoutype}
|
||||||
|
<Typography.Text type="secondary" style={{ marginLeft: 4 }}>({zhengshou})</Typography.Text>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="反手">
|
||||||
|
{fanshoutype}
|
||||||
|
<Typography.Text type="secondary" style={{ marginLeft: 4 }}>({fanshou})</Typography.Text>
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProfilePage() {
|
||||||
|
const profile = useLoaderData<XCXProfile | null>();
|
||||||
|
return (
|
||||||
|
<Flex vertical align="center" style={{ padding: 24 }}>
|
||||||
|
<Avatar src={profile?.realpic} size={128} />
|
||||||
|
<Typography.Title level={2}>{profile?.username}</Typography.Title>
|
||||||
|
<Typography.Text>姓名:{profile?.realname}</Typography.Text>
|
||||||
|
<Typography.Text>积分:{profile?.score}</Typography.Text>
|
||||||
|
<Typography.Text>
|
||||||
|
{
|
||||||
|
[profile?.province, profile?.sex, profile?.bg, profile?.scope, ...profile?.allCities ?? []]
|
||||||
|
.filter(Boolean).join(' | ')
|
||||||
|
}
|
||||||
|
</Typography.Text>
|
||||||
|
<Divider>简介</Divider>
|
||||||
|
<Typography.Paragraph>
|
||||||
|
{profile?.description}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
<Raket profile={profile} />
|
||||||
|
<Honor honors={profile?.honors} />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
src/store/useGameStore.ts
Normal file
18
src/store/useGameStore.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { type IEventInfo } from '../types';
|
||||||
|
|
||||||
|
interface StoreType {
|
||||||
|
eventInfo?: IEventInfo;
|
||||||
|
setEventInfo: (info?: IEventInfo) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const useGameStore = create<StoreType>((set) => {
|
||||||
|
return {
|
||||||
|
setEventInfo: (info) => {
|
||||||
|
set({ eventInfo: info });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useGameStore;
|
||||||
@ -5,14 +5,16 @@ export interface IEventInfo {
|
|||||||
matchId: string;
|
matchId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MatchInfo {
|
||||||
|
itemId: string;
|
||||||
|
title: string;
|
||||||
|
players: Player[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface Player {
|
export interface Player {
|
||||||
name: string;
|
name: string;
|
||||||
score: number;
|
score: number;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
info: string;
|
info: string;
|
||||||
|
uid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MatchInfo {
|
|
||||||
itemId: string;
|
|
||||||
players: Player[];
|
|
||||||
}
|
|
||||||
116
src/types/profile.ts
Normal file
116
src/types/profile.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
export interface XCXProfile {
|
||||||
|
uid: string;
|
||||||
|
username: string;
|
||||||
|
Top3ManOfBeat: string;
|
||||||
|
Top3ManOfBeatUsernameScore: string[];
|
||||||
|
Top3WomanOfBeat: string;
|
||||||
|
Top3WomanOfPlay: string;
|
||||||
|
Top3OfBeat: string;
|
||||||
|
Top3WomanOfBeatUsernameScore: string[];
|
||||||
|
TopPlayer: string;
|
||||||
|
TopPlayerUsernameScore: string[];
|
||||||
|
OftenPlayer: string;
|
||||||
|
maxConsWin: string;
|
||||||
|
maxConsWinLastGameId: string;
|
||||||
|
allCities: string[];
|
||||||
|
win: string;
|
||||||
|
lose: string;
|
||||||
|
total: string;
|
||||||
|
dateline: string;
|
||||||
|
province: string;
|
||||||
|
city: string;
|
||||||
|
brand: string;
|
||||||
|
if_event_uid: string;
|
||||||
|
score: string;
|
||||||
|
goldNum: string;
|
||||||
|
cityNum: string;
|
||||||
|
path: string;
|
||||||
|
pathNum: string;
|
||||||
|
champion: string;
|
||||||
|
orgTimes: string;
|
||||||
|
ly_event_times: string;
|
||||||
|
ly_event_games: string;
|
||||||
|
ly_event_rank: string;
|
||||||
|
ly_max_inc: string;
|
||||||
|
ly_max_dis: string;
|
||||||
|
ly_beatit: string;
|
||||||
|
ly_shopid: string;
|
||||||
|
sleep: string;
|
||||||
|
pathWords: string;
|
||||||
|
fuxing: string;
|
||||||
|
kuzhu: string;
|
||||||
|
rank: string;
|
||||||
|
ifHonorShow: string;
|
||||||
|
locationupdatetime: string;
|
||||||
|
resideupdatetime: string;
|
||||||
|
ifManage: number;
|
||||||
|
age: number;
|
||||||
|
maxscore: string;
|
||||||
|
maxScoreTheYear: string;
|
||||||
|
beat: string;
|
||||||
|
rate: string;
|
||||||
|
Top3OfBeatUsernameScore: string[];
|
||||||
|
KuZhu: KuZhu;
|
||||||
|
FuXing: KuZhu;
|
||||||
|
lastUpdate: string;
|
||||||
|
realpic: string;
|
||||||
|
realname: string;
|
||||||
|
qiupai: string;
|
||||||
|
qiupaitype: string;
|
||||||
|
fanshou: string;
|
||||||
|
fanshoutype: string;
|
||||||
|
zhengshou: string;
|
||||||
|
zhengshoutype: string;
|
||||||
|
resideprovince: string;
|
||||||
|
sex: string;
|
||||||
|
bg: string;
|
||||||
|
scope: string;
|
||||||
|
description: string;
|
||||||
|
ifHonor: number;
|
||||||
|
honors: Honor[];
|
||||||
|
games: Games;
|
||||||
|
hasFollowed: number;
|
||||||
|
followed_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Games {
|
||||||
|
data: GamesData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GamesData {
|
||||||
|
gameid: string;
|
||||||
|
uid1: string;
|
||||||
|
uid2: string;
|
||||||
|
uid11: string;
|
||||||
|
uid22: string;
|
||||||
|
username1: string;
|
||||||
|
username2: string;
|
||||||
|
username11: string;
|
||||||
|
username22: string;
|
||||||
|
result1: string;
|
||||||
|
result2: string;
|
||||||
|
score1: string;
|
||||||
|
score2: string;
|
||||||
|
eventid: string;
|
||||||
|
ascore1: string;
|
||||||
|
dateline: string;
|
||||||
|
groupid: string;
|
||||||
|
flag: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KuZhu {
|
||||||
|
names: string[];
|
||||||
|
uids: string[];
|
||||||
|
gameids: string[];
|
||||||
|
winlose: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Honor {
|
||||||
|
uid: string;
|
||||||
|
hid: string;
|
||||||
|
eventid: string;
|
||||||
|
itemid: string;
|
||||||
|
honor: string;
|
||||||
|
subject: string;
|
||||||
|
posttime: string;
|
||||||
|
}
|
||||||
39
src/utils.ts
39
src/utils.ts
@ -4,8 +4,28 @@ import * as cheerio from "cheerio";
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { chunk } from 'lodash';
|
import { chunk } from 'lodash';
|
||||||
|
import type { XCXProfile } from "./types/profile";
|
||||||
|
|
||||||
const BASE_URL = `https://kaiqiuwang.cc`;
|
const BASE_URL = `https://kaiqiuwang.cc`;
|
||||||
|
const XCX_BASE_URL = `${BASE_URL}/xcx/public/index.php`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token:
|
||||||
|
XX-Device-Type: wxapp
|
||||||
|
content-type: application/json
|
||||||
|
Accept-Encoding: gzip,compress,br,deflate
|
||||||
|
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.68(0x1800442a) NetType/WIFI Language/zh_CN
|
||||||
|
Referer: https://servicewechat.com/wxff09fb0e92aa456a/464/page-frame.html
|
||||||
|
*/
|
||||||
|
const xcxDefaultHeaders = {
|
||||||
|
'token': 'e72b91bb-a690-44fe-9274-6a4a251f611b',
|
||||||
|
'XX-Device-Type': 'wxapp',
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'Accept-Encoding': 'gzip,compress,br,deflate',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.68(0x1800442a) NetType/WIFI Language/zh_CN',
|
||||||
|
'Referer': 'https://servicewechat.com/wxff09fb0e92aa456a/464/page-frame.html',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param tagid 俱乐部 ID
|
* @param tagid 俱乐部 ID
|
||||||
*/
|
*/
|
||||||
@ -62,6 +82,7 @@ export async function fetchEventContentHTML(matchId: string) {
|
|||||||
|
|
||||||
export function parseEventInfo(html: string) {
|
export function parseEventInfo(html: string) {
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('h2.title a').text();
|
||||||
const itemHref = $('.sub_menu a.active').attr('href') ?? '';
|
const itemHref = $('.sub_menu a.active').attr('href') ?? '';
|
||||||
const itemId = /\S+item_id=(\d+)$/.exec(itemHref)?.[1] ?? '';
|
const itemId = /\S+item_id=(\d+)$/.exec(itemHref)?.[1] ?? '';
|
||||||
const players: Player[] = [];
|
const players: Player[] = [];
|
||||||
@ -69,12 +90,14 @@ export function parseEventInfo(html: string) {
|
|||||||
for (const player of playersEl) {
|
for (const player of playersEl) {
|
||||||
const img = $(player).find('.image img').attr('src') ?? '';
|
const img = $(player).find('.image img').attr('src') ?? '';
|
||||||
const name = $(player).find('h6').text().trim();
|
const name = $(player).find('h6').text().trim();
|
||||||
|
const uid = /space-(?<uid>\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 info = $(player).find('p:nth-of-type(2)').text().replace(/\s/g, '');
|
||||||
const score = Number(/^.*?\b(\d+)\b/.exec(info)?.[1]);
|
const score = Number(/^.*?\b(\d+)\b/.exec(info)?.[1]);
|
||||||
players.push({ name, avatar: img, score, info });
|
players.push({ name, avatar: img, score, info, uid });
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
itemId,
|
itemId,
|
||||||
|
title,
|
||||||
players,
|
players,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,4 +131,18 @@ export function sneckGroup(size: number, groupLen: number) {
|
|||||||
newGroups.push(group);
|
newGroups.push(group);
|
||||||
}
|
}
|
||||||
return newGroups;
|
return newGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAdvProfile(uid: string) {
|
||||||
|
// return JSON.parse(fs.readFileSync(
|
||||||
|
// path.resolve(__dirname, '..', '__test__', 'data', 'profile.json'),
|
||||||
|
// ).toString()).data;
|
||||||
|
if (!/^\d+$/.test(uid)) return null;
|
||||||
|
if (!uid) return null;
|
||||||
|
const resp = await fetch(`${XCX_BASE_URL}/api/User/adv_profile?uid=${uid}`, {
|
||||||
|
headers: xcxDefaultHeaders,
|
||||||
|
});
|
||||||
|
const data = await resp.json();
|
||||||
|
if (data.code !== 1) return null;
|
||||||
|
return data.data as XCXProfile;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user