my-kaiqiuwang/src/page/ProfilePage.tsx
kyuuseiryuu 3b87230173 feat: add favorite player functionality and improve navigation
- components: add FavButton for player favoriting
- store: create useFavPlayerStore for managing favorites
- pages: add FavePlayersPage for displaying favorites
- components: add MenuButtons for navigation controls
- ui: update App and ProfilePage for new features
- meta: update index.html title to "我的开球网"
2026-02-11 16:00:01 +09:00

152 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Link, useLoaderData, useNavigate } from "react-router";
import type { XCXProfile } from "../types/profile";
import { Avatar, Descriptions, Divider, Flex, FloatButton, Image, Typography } from "antd";
import { HomeOutlined } from "@ant-design/icons";
import User from "../components/User";
import React, { useMemo } from "react";
import { ChangeBackground } from "../components/ChangeBackground";
import UserTags from "../components/Tags";
import { GameTable } from "../components/GameTable";
import { useTitle } from "ahooks";
import { FavButton } from "../components/FavButton";
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} />
<Link to={`/event/${honor.eventid}`}>
<Typography.Text>
{honor.subject}
</Typography.Text>
</Link>
</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>
</>
);
}
function PlayerList(props: { title: string; names?: string[]; uids?: string[] }) {
const { names = [], uids = [] } = props;
if (!names.length) return null;
return (
<>
<Typography.Title level={5}>{props.title}</Typography.Title>
<Flex vertical align="center">
{names.map((e, i) => (
<User key={e} name={e} uid={uids?.[i] ?? ''} />
))}
</Flex>
</>
);
}
export default function ProfilePage() {
const { profile, uid } = useLoaderData<{ profile: XCXProfile; uid: string }>();
console.debug('profile', profile);
const navigate = useNavigate();
useTitle(`${profile?.username}${profile?.realname ? ` (${profile?.realname})` : ''} 个人详情`, { restoreOnUnmount: true });
const tags = useMemo<React.ReactNode[]>(() =>
[profile?.province, profile?.sex, profile?.bg ?? '', ...Object.values(profile?.allCities ?? [])]
.filter(Boolean)
, [profile]);
return (
<>
<ChangeBackground url={profile?.realpic} />
<FloatButton icon={<HomeOutlined />} onClick={() => navigate('/')} />
<Flex vertical align="center" style={{ padding: 24 }}>
<Avatar src={profile?.realpic} size={128} />
<Typography.Title level={2}>
<Flex justify="center">
<span>{profile?.username}</span>
<FavButton
user={{
uid,
score: profile.score,
name: profile.username,
realname: profile.realname,
avatar: profile.realpic
}}
/>
</Flex>
</Typography.Title>
<Typography.Text>{profile?.realname}</Typography.Text>
<Typography.Text>{profile?.score}</Typography.Text>
<Typography.Text style={{ textAlign: 'center' }}>
{(tags.length > 0 && tags.reduce((a, b) => (<>{a}<Divider orientation="vertical" />{b}</>)))}
</Typography.Text>
<Divider></Divider>
<Typography.Paragraph>
{profile?.description}
</Typography.Paragraph>
<UserTags uid={uid} />
<Raket profile={profile} />
<Flex wrap gap={24} justify="center">
<Flex vertical align="center">
<PlayerList title="交手前三名" names={profile?.TopPlayerUsernameScore} />
</Flex>
<Flex vertical align="center">
<PlayerList title="战胜前三名" names={profile?.Top3OfBeatUsernameScore} />
</Flex>
<Flex vertical align="center">
<PlayerList title="战胜的男子" names={profile?.Top3ManOfBeatUsernameScore} />
</Flex>
<Flex vertical align="center">
<PlayerList title="战胜的女子" names={profile?.Top3WomanOfBeatUsernameScore} />
</Flex>
</Flex>
<Flex gap={24}>
<Flex vertical align="center">
<PlayerList title="福星" names={profile?.FuXing?.names} uids={profile?.FuXing?.uids} />
</Flex>
<Flex vertical align="center">
<PlayerList title="苦主" names={profile?.KuZhu?.names} uids={profile?.KuZhu?.uids} />
</Flex>
</Flex>
<Honor honors={profile?.honors} />
<GameTable uid={uid} data={profile?.games?.data} />
</Flex>
</>
);
}