- Refactor ClubEventList to support persistent page state in sessionStorage and dynamic toggle for finished events. - Move pagination logic to useRunOnce hook for initialization, ensuring correct state restoration. - Update ClubSearchTable to support "My Favorites" tab, using useRef to cache search results for different club types. - Enhance EventCard with precise time formatting (HH:mm:ss) and dynamic countdown logic using dayjs timezone. - Persist club selection in GameSelector using local storage and integrate ClubSummary component. - Fix geo data handling in KaiqiuService, ensuring coordinates are correctly passed to iCalendar generator. - Remove unused imports and simplify component structure across affected files.
137 lines
4.5 KiB
TypeScript
137 lines
4.5 KiB
TypeScript
import { useRequest } from "ahooks";
|
|
import { Avatar, Button, Flex, Input, Radio, Space, Table } from "antd";
|
|
import { useCallback, useRef, useState } from "react";
|
|
import type { ClubInfo } from "../types";
|
|
import { EyeOutlined, StarFilled, StarOutlined } from "@ant-design/icons";
|
|
import { useClubStore } from "../store/useClubStore";
|
|
|
|
|
|
type ClickType = 'VIEW' | 'FAV';
|
|
|
|
interface Props {
|
|
handleClick?: (type: ClickType, club: ClubInfo) => void;
|
|
}
|
|
|
|
enum ClubType {
|
|
积分俱乐部,
|
|
俱乐部,
|
|
我的收藏,
|
|
}
|
|
|
|
type Data = { clubs: ClubInfo[], total: number, page: number; };
|
|
|
|
const initData = { clubs: [], total: 0, page: 1 };
|
|
|
|
export const ClubSearchTable = (props: Props) => {
|
|
const [searchKey, setSearchKey] = useState('');
|
|
const [datasource, setDatasource] = useState<Data>(initData);
|
|
const dataRef = useRef<Partial<Record<ClubType, Data>>>({});
|
|
const searchClub = useRequest<Data, [string, number, ClubType]>(async (
|
|
searchKey: string,
|
|
page = 1,
|
|
clubType = ClubType.积分俱乐部,
|
|
) => {
|
|
const resp: Data = await fetch(
|
|
`/api/club/find?key=${searchKey}&page=${page}${clubType ? '&normalClub=1' : ''}`
|
|
).then(e => e.json());
|
|
dataRef.current[clubType] = {
|
|
...resp,
|
|
page,
|
|
};
|
|
return resp;
|
|
}, { manual: true });
|
|
const clubStore = useClubStore(store => store);
|
|
const [clubType, setClubType] = useState<ClubType>(ClubType.积分俱乐部);
|
|
const handleSearch = useCallback(async (searchKey: string, clubType: ClubType, page = 1) => {
|
|
switch (clubType) {
|
|
case ClubType.积分俱乐部:
|
|
case ClubType.俱乐部: {
|
|
const resp = await searchClub.runAsync(searchKey, page, clubType);
|
|
setDatasource({ ...resp, page });
|
|
break;
|
|
}
|
|
}
|
|
}, []);
|
|
const handleTypeChange = useCallback(async (type: ClubType) => {
|
|
setClubType(type);
|
|
switch (type) {
|
|
case ClubType.积分俱乐部:
|
|
case ClubType.俱乐部: {
|
|
if (!searchKey) {
|
|
setDatasource(dataRef.current[type] ?? initData);
|
|
break;
|
|
}
|
|
await handleSearch(searchKey, type);
|
|
break;
|
|
}
|
|
case ClubType.我的收藏:
|
|
setDatasource({ clubs: clubStore.clubs, total: clubStore.clubs.length, page: 1 });
|
|
break;
|
|
default: break;
|
|
}
|
|
}, [searchKey, searchClub, clubStore]);
|
|
return (
|
|
<Flex vertical gap={12} justify="center" align="center">
|
|
<Radio.Group
|
|
optionType="button"
|
|
value={clubType}
|
|
onChange={e => handleTypeChange(e.target.value)}
|
|
options={[
|
|
{ label: '积分俱乐部', value: ClubType.积分俱乐部 },
|
|
{ label: '俱乐部', value: ClubType.俱乐部 },
|
|
{ label: '我的收藏', value: ClubType.我的收藏 },
|
|
]}
|
|
/>
|
|
{clubType !== ClubType.我的收藏 ? (
|
|
<Input.Search
|
|
allowClear
|
|
onSearch={e => {
|
|
setSearchKey(e);
|
|
handleSearch(e, clubType);
|
|
}}
|
|
onPressEnter={() => handleSearch(searchKey, clubType)}
|
|
value={searchKey}
|
|
onChange={e => setSearchKey(e.target.value)}
|
|
/>
|
|
) : null}
|
|
<Table
|
|
rowKey={e => e.id}
|
|
loading={searchClub.loading}
|
|
dataSource={datasource.clubs}
|
|
style={{ width: '100%' }}
|
|
scroll={{ x: 400 }}
|
|
pagination={clubType !== ClubType.我的收藏 ? {
|
|
current: datasource.page,
|
|
showSizeChanger: false,
|
|
total: datasource.total,
|
|
onChange: (page) => {
|
|
handleSearch(searchKey, clubType, page);
|
|
}
|
|
} : false}
|
|
>
|
|
<Table.Column dataIndex="name" width={140} fixed="left" render={(name, { img }) => {
|
|
return (
|
|
<Space>
|
|
<Avatar src={img} size={32} />
|
|
<span>{name}</span>
|
|
</Space>
|
|
);
|
|
}} />
|
|
<Table.Column dataIndex="area" width={80} />
|
|
<Table.Column dataIndex="members" width={80} render={num => `${num} 人`} />
|
|
<Table.Column dataIndex="id" align="center" width={200} render={(id, record: ClubInfo) => {
|
|
return (
|
|
<Space.Compact size="small">
|
|
<Button icon={
|
|
useClubStore.getState().isFav(id)
|
|
? <StarFilled style={{ color: 'yellow' }} />
|
|
: <StarOutlined />
|
|
} onClick={() => props.handleClick?.('FAV', record)}>关注</Button>
|
|
<Button icon={<EyeOutlined />} onClick={() => props.handleClick?.('VIEW', record)}>查看</Button>
|
|
</Space.Compact>
|
|
);
|
|
}} />
|
|
</Table>
|
|
</Flex>
|
|
);
|
|
} |