feat(club-management): add club follow/unfollow functionality

Introduce a follow/unfollow feature for clubs in the ClubSummary component.
Users can now toggle their subscription status to a specific club, with
the state persisted locally using zustand.

Changes include:
- Added 'Follow' and 'Unfollow' buttons with corresponding star icons (StarOutlined/StarFilled).
- Updated ClubStore to manage a simplified list of clubs (id and name only).
- Refactored Data type in ClubSearchTable to use Pick<ClubInfo, 'id' | 'name'>.
- Fixed club name in GameSelector clubList to '东华乒乓球俱乐部'.
- Excluded follow buttons for club ID '47' (Donghua).

The implementation ensures a consistent UI state across the application
and persists user preferences.
This commit is contained in:
kyuuseiryuu 2026-03-26 13:44:32 +09:00
parent f1ca5cda75
commit 5927861af7
4 changed files with 31 additions and 8 deletions

View File

@ -19,7 +19,7 @@ enum ClubType {
,
}
type Data = { clubs: ClubInfo[], total: number, page: number; };
type Data = { clubs: Pick<ClubInfo, 'id' | 'name'>[], total: number, page: number; };
const initData = { clubs: [], total: 0, page: 1 };

View File

@ -5,7 +5,8 @@ import { useRequest } from "ahooks";
import type { ClubDetail } from "../types";
import { MapType, openWebMapRaw } from "../utils/front";
import type { ItemType } from "antd/es/menu/interface";
import { NotificationOutlined, PushpinOutlined } from "@ant-design/icons";
import { NotificationOutlined, PushpinOutlined, StarFilled, StarOutlined } from "@ant-design/icons";
import { useClubStore } from "../store/useClubStore";
interface Props {
clubId: string;
@ -43,6 +44,7 @@ export const ClubSummary = (props: Props) => {
},
]
}, [info]);
const store = useClubStore();
if (requestClubSummary.data === null) return null;
return (
<div style={{ width: '100%' }}>
@ -52,7 +54,29 @@ export const ClubSummary = (props: Props) => {
<Flex vertical align="center" justify="center">
<ChangeBackground url={info?.img} />
<Avatar src={info?.img} size={80} />
<Typography.Title level={2}>{info?.name}</Typography.Title>
<Typography.Title level={3}>
<Flex gap={12}>
{info?.name}
{props.clubId === '47' ? null : (store.isFav(info?.id ?? '')) ? (
<Button
icon={<StarFilled style={{ color: 'yellow' }} />}
onClick={() => store.unFav(info?.id ?? '')}
>
</Button>
) : (
<Button
icon={<StarOutlined />}
onClick={() => store.add({
id: info?.id ?? '',
name: info?.name ?? '',
})}
>
</Button>
)}
</Flex>
</Typography.Title>
<Flex gap={12}>
{noArticle ? null : (
<Button icon={<NotificationOutlined />} onClick={() => setIsArticleOpen(true)}></Button>

View File

@ -1,6 +1,6 @@
export const clubs = [
{
name: '东华',
name: '东华乒乓球俱乐部',
clubId: '47',
},
];

View File

@ -1,17 +1,16 @@
import { create } from "zustand";
import { persist, createJSONStorage } from 'zustand/middleware';
import type { ClubInfo } from "../types";
interface Store {
clubs: ClubInfo[];
add(club: ClubInfo): void;
clubs: { id: string; name: string }[];
add(club: { id: string; name: string }): void;
unFav(id: string): void;
isFav(id: string): boolean;
}
export const useClubStore = create(persist<Store>((set, get) => {
return {
clubs: [],
add: (club: ClubInfo) => {
add: (club: { id: string; name: string }) => {
set({ clubs: [...get().clubs, club] });
},
unFav: (id: string) => {