feat(global): find user
This commit is contained in:
parent
4dd3daf909
commit
bbc6a592f4
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"postman.settings.dotenv-detection-notification-visibility": false
|
||||
}
|
||||
@ -3,6 +3,7 @@ import { ClubSelector } from "./components/GameSelector";
|
||||
import type { IEventInfo } from "./types";
|
||||
import { useNavigate } from "react-router";
|
||||
import "./index.css";
|
||||
import { FindUserButton } from "./components/FindUser";
|
||||
|
||||
export function App() {
|
||||
const navigate = useNavigate();
|
||||
@ -14,6 +15,7 @@ export function App() {
|
||||
<div className="app">
|
||||
<h1>开球网比赛分组预测</h1>
|
||||
<ClubSelector onGameClick={handleGameClick} />
|
||||
<FindUserButton />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
70
src/components/FindUser/index.tsx
Normal file
70
src/components/FindUser/index.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { SearchOutlined } from "@ant-design/icons";
|
||||
import { useLocalStorageState, useRequest } from "ahooks";
|
||||
import { FloatButton, Drawer, Input, Table } from "antd";
|
||||
import { useState } from "react";
|
||||
import type { XCXFindUser, XCXFindUserResp } from "../../types";
|
||||
import User from "../User";
|
||||
import { SEX } from "../../utils";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export function FindUserButton() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [searchKey, setSearchKey] = useLocalStorageState<string>('findUser:searchKey');
|
||||
const findUserReq = useRequest(async (page: number = 1) => {
|
||||
const findOutUsers: XCXFindUserResp = await (await fetch(`/api/user/find?page=${page}&key=${searchKey}`)).json();
|
||||
return findOutUsers;
|
||||
}, { manual: true, refreshDeps: [searchKey], cacheKey: 'findUser:result' });
|
||||
return (
|
||||
<>
|
||||
<FloatButton
|
||||
icon={<SearchOutlined />}
|
||||
onClick={() => setOpen(true)}
|
||||
/>
|
||||
<Drawer
|
||||
placement="bottom"
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
loading={findUserReq.loading}
|
||||
size="80vh"
|
||||
>
|
||||
<Input.Search
|
||||
allowClear
|
||||
size="large"
|
||||
placeholder="输入昵称或姓名查找"
|
||||
value={searchKey}
|
||||
onChange={e => setSearchKey(e.target.value)}
|
||||
onSearch={async () => findUserReq.runAsync()}
|
||||
/>
|
||||
<Table
|
||||
size="small"
|
||||
rowKey={e => e.uid}
|
||||
dataSource={findUserReq.data?.data}
|
||||
pagination={{
|
||||
total: findUserReq.data?.total,
|
||||
current: findUserReq.data?.current_page,
|
||||
pageSize: findUserReq.data?.per_page,
|
||||
onChange(page) {
|
||||
findUserReq.run(page);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Table.Column
|
||||
align="center"
|
||||
title="昵称/姓名"
|
||||
dataIndex={'username2'}
|
||||
render={(username2, { realname, uid }: XCXFindUser) => (
|
||||
<User
|
||||
uid={uid}
|
||||
name={`${username2}${(realname && username2 !== realname) ? `(${realname})` : ''}`}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Table.Column align="center" title="积分" dataIndex={'score'} />
|
||||
<Table.Column align="center" title="地区" dataIndex={'resideprovince'} />
|
||||
<Table.Column align="center" title="性别" dataIndex={'sex'} render={sex => SEX[sex]} />
|
||||
<Table.Column align="center" title="年龄" dataIndex={'birthyear'} render={year => dayjs().diff(dayjs(year), 'year')} />
|
||||
</Table>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -70,6 +70,19 @@ export function GameTable(props: Props) {
|
||||
)}
|
||||
>
|
||||
<Table.Column align="center" dataIndex={''} render={(_, __, i) => i + 1} />
|
||||
<Table.Column
|
||||
dataIndex='gameid'
|
||||
align="center"
|
||||
render={((_, record: GamesData) => {
|
||||
const { uid1 } = record;
|
||||
const { team1, team2 } = toTeams(record);
|
||||
return (
|
||||
<Flex gap={48} justify="center">
|
||||
<PlayerTeam idNames={props.uid === uid1 ? team1 : team2} />
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
/>
|
||||
<Table.Column
|
||||
dataIndex='gameid'
|
||||
align="center"
|
||||
|
||||
@ -5,7 +5,7 @@ interface Props {
|
||||
uid?: string;
|
||||
}
|
||||
export default function User(props: Props) {
|
||||
if (!props.uid) return <span>{props.name}</span>
|
||||
if (!Number(props.uid)) return <span>{props.name}</span>
|
||||
return (
|
||||
<Link to={`/profile/${props.uid}`}>{props.name}</Link>
|
||||
);
|
||||
|
||||
@ -34,6 +34,15 @@ const server = serve({
|
||||
return Response.json(data);
|
||||
}
|
||||
},
|
||||
"/api/user/find": {
|
||||
async GET(req) {
|
||||
const searchParams = new URL(req.url).searchParams;
|
||||
const key = searchParams.get('key') ?? '';
|
||||
const page = Number(searchParams.get('page'));
|
||||
const users = await xcxApi.findUser(key, page);
|
||||
return Response.json(users);
|
||||
}
|
||||
},
|
||||
"/api/user/:uid": {
|
||||
async GET(req) {
|
||||
const uid = req.params.uid;
|
||||
|
||||
@ -67,13 +67,14 @@ function Raket(props: { profile?: XCXProfile | null }) {
|
||||
}
|
||||
|
||||
function PlayerList(props: { title: string; names?: string[]; uids?: string[] }) {
|
||||
if (!props.names?.length) return null;
|
||||
const { names = [], uids = [] } = props;
|
||||
if (!names.length) return null;
|
||||
return (
|
||||
<>
|
||||
<Typography.Title level={5}>{props.title}</Typography.Title>
|
||||
<Flex vertical align="center">
|
||||
{props.names.map((e, i) => (
|
||||
<User key={e} name={e} uid={props.uids?.[i] ?? ''} />
|
||||
{names.map((e, i) => (
|
||||
<User key={e} name={e} uid={uids?.[i] ?? ''} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
@ -96,7 +97,7 @@ export default function ProfilePage() {
|
||||
<Typography.Text>积分:{profile?.score}</Typography.Text>
|
||||
<Typography.Text style={{ textAlign: 'center' }}>
|
||||
{
|
||||
([profile?.province, profile?.sex, profile?.bg ?? '', ...(profile?.allCities ?? [])] as React.ReactNode[])
|
||||
([profile?.province, profile?.sex, profile?.bg ?? '', ...Object.values(profile?.allCities ?? [])] as React.ReactNode[])
|
||||
.filter(Boolean)
|
||||
.reduce((a, b) => (<>{a}<Divider orientation="vertical" />{b}</>))
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { GamesData, XCXMember, XCXProfile, XCXTag } from "../types";
|
||||
import type { GamesData, XCXFindUser, XCXFindUserResp, XCXMember, XCXProfile, XCXTag } from "../types";
|
||||
import { BASE_URL } from "../utils";
|
||||
|
||||
const XCX_BASE_URL = `${BASE_URL}/xcx/public/index.php`;
|
||||
@ -51,4 +51,9 @@ export class XCXAPI {
|
||||
const url = `/api/User/getGames?uid=${uid}&page=${page}&size=50`;
|
||||
return (await this.#fetch<{ data: GamesData[] }>(url))?.data;
|
||||
}
|
||||
async findUser(key: string = '', page: number = 1) {
|
||||
if (!key) return;
|
||||
const url = `/api/user/lists?page=${page}&key=${key}`;
|
||||
return (await this.#fetch<XCXFindUserResp>(url));
|
||||
}
|
||||
}
|
||||
@ -35,3 +35,24 @@ export interface XCXMember extends BasePlayer {
|
||||
number: number;
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface XCXFindUser extends BasePlayer {
|
||||
name: never;
|
||||
username2: string;
|
||||
realname: string;
|
||||
residecity: string;
|
||||
resideprovince: string;
|
||||
minscore: string;
|
||||
maxscore: string;
|
||||
birthyear: string;
|
||||
sex: string;
|
||||
th?: string;
|
||||
}
|
||||
|
||||
export interface XCXFindUserResp {
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
data: XCXFindUser[];
|
||||
}
|
||||
@ -115,3 +115,8 @@ export function sneckGroup(size: number, groupLen: number) {
|
||||
}
|
||||
return newGroups;
|
||||
}
|
||||
|
||||
export enum SEX {
|
||||
'男' = 1,
|
||||
'女' = 2,
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user