|
|
@@ -0,0 +1,261 @@
|
|
|
+import React, { useEffect, useState } from 'react';
|
|
|
+import { Layout, Card, Avatar, Button, Typography, List, Spin, Tabs, Divider, Badge } from 'antd';
|
|
|
+import { UserOutlined, EditOutlined, HeartOutlined, UserAddOutlined, UserDeleteOutlined } from '@ant-design/icons';
|
|
|
+import { useParams, useNavigate } from 'react-router-dom';
|
|
|
+import { userClient } from '@/client/api';
|
|
|
+import { useAuth } from '@/client/admin/hooks/AuthProvider';
|
|
|
+import type { UserEntity } from '@/server/modules/users/user.entity';
|
|
|
+
|
|
|
+const { Content } = Layout;
|
|
|
+const { Title, Text, Paragraph } = Typography;
|
|
|
+const { TabPane } = Tabs;
|
|
|
+
|
|
|
+const UserProfilePage: React.FC = () => {
|
|
|
+ return null;
|
|
|
+ const [user, setUser] = useState<UserEntity | null>(null);
|
|
|
+ const [loading, setLoading] = useState(true);
|
|
|
+ const [isFollowing, setIsFollowing] = useState(false);
|
|
|
+ const [followerCount, setFollowerCount] = useState(0);
|
|
|
+ const [followingCount, setFollowingCount] = useState(0);
|
|
|
+ const { id } = useParams<{ id: string }>();
|
|
|
+ const navigate = useNavigate();
|
|
|
+ const { user: currentUser } = useAuth();
|
|
|
+
|
|
|
+ // 获取用户资料
|
|
|
+ const fetchUserProfile = async () => {
|
|
|
+ if (!id) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ setLoading(true);
|
|
|
+ const response = await userClient[id].$get();
|
|
|
+
|
|
|
+ if (!response.ok) throw new Error('获取用户资料失败');
|
|
|
+
|
|
|
+ const userData = await response.json();
|
|
|
+ setUser(userData);
|
|
|
+
|
|
|
+ // 获取关注状态
|
|
|
+ if (currentUser && currentUser.id !== Number(id)) {
|
|
|
+ const followStatus = await userClient[currentUser.id].following.$get({
|
|
|
+ query: { userId: id }
|
|
|
+ });
|
|
|
+ setIsFollowing(followStatus.ok);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取关注数量
|
|
|
+ const followers = await userClient[id].followers.$get({ query: { pageSize: 1 } });
|
|
|
+ const following = await userClient[id].following.$get({ query: { pageSize: 1 } });
|
|
|
+
|
|
|
+ setFollowerCount(await followers.json().then(data => data.pagination.total));
|
|
|
+ setFollowingCount(await following.json().then(data => data.pagination.total));
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error fetching user profile:', error);
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 关注/取消关注用户
|
|
|
+ const handleFollowToggle = async () => {
|
|
|
+ if (!currentUser || !id) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (isFollowing) {
|
|
|
+ await userClient[currentUser.id].follow[id].$delete();
|
|
|
+ setFollowerCount(prev => prev - 1);
|
|
|
+ } else {
|
|
|
+ await userClient[currentUser.id].follow.$post({ json: { followingId: Number(id) } });
|
|
|
+ setFollowerCount(prev => prev + 1);
|
|
|
+ }
|
|
|
+ setIsFollowing(!isFollowing);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error toggling follow status:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ fetchUserProfile();
|
|
|
+ }, [id]);
|
|
|
+
|
|
|
+ if (loading) {
|
|
|
+ return (
|
|
|
+ <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '50vh' }}>
|
|
|
+ <Spin size="large" />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!user) {
|
|
|
+ return (
|
|
|
+ <div style={{ textAlign: 'center', padding: '50px' }}>
|
|
|
+ <Title level={3}>用户不存在</Title>
|
|
|
+ <Button onClick={() => navigate('/')}>返回首页</Button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Layout>
|
|
|
+ <Content style={{ padding: '24px', maxWidth: 1200, margin: '0 auto', width: '100%' }}>
|
|
|
+ <Card style={{ marginBottom: 24 }}>
|
|
|
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '24px 0' }}>
|
|
|
+ <Avatar
|
|
|
+ src={user.avatar || <UserOutlined style={{ fontSize: 64 }} />}
|
|
|
+ size={128}
|
|
|
+ style={{ marginBottom: 16 }}
|
|
|
+ />
|
|
|
+ <Title level={2} style={{ margin: 0 }}>{user.nickname || user.username}</Title>
|
|
|
+
|
|
|
+ <div style={{ display: 'flex', margin: '16px 0' }}>
|
|
|
+ <div style={{ textAlign: 'center', margin: '0 24px' }}>
|
|
|
+ <Text strong style={{ fontSize: 24 }}>0</Text>
|
|
|
+ <br />
|
|
|
+ <Text>帖子</Text>
|
|
|
+ </div>
|
|
|
+ <div style={{ textAlign: 'center', margin: '0 24px' }}>
|
|
|
+ <Text strong style={{ fontSize: 24 }}>{followerCount}</Text>
|
|
|
+ <br />
|
|
|
+ <Text>粉丝</Text>
|
|
|
+ </div>
|
|
|
+ <div style={{ textAlign: 'center', margin: '0 24px' }}>
|
|
|
+ <Text strong style={{ fontSize: 24 }}>{followingCount}</Text>
|
|
|
+ <br />
|
|
|
+ <Text>关注</Text>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {currentUser && currentUser.id !== user.id ? (
|
|
|
+ <Button
|
|
|
+ type={isFollowing ? "default" : "primary"}
|
|
|
+ icon={isFollowing ? <UserDeleteOutlined /> : <UserAddOutlined />}
|
|
|
+ onClick={handleFollowToggle}
|
|
|
+ >
|
|
|
+ {isFollowing ? '取消关注' : '关注'}
|
|
|
+ </Button>
|
|
|
+ ) : currentUser && currentUser.id === user.id ? (
|
|
|
+ <Button icon={<EditOutlined />} onClick={() => navigate('/profile/edit')}>
|
|
|
+ 编辑资料
|
|
|
+ </Button>
|
|
|
+ ) : null}
|
|
|
+
|
|
|
+ {user.bio && (
|
|
|
+ <Paragraph style={{ marginTop: 16, maxWidth: 600, textAlign: 'center' }}>
|
|
|
+ {user.bio}
|
|
|
+ </Paragraph>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <div style={{ display: 'flex', alignItems: 'center', marginTop: 8 }}>
|
|
|
+ {user.location && (
|
|
|
+ <Text style={{ marginRight: 16 }}>{user.location}</Text>
|
|
|
+ )}
|
|
|
+ {user.website && (
|
|
|
+ <Text
|
|
|
+ style={{ color: '#1890ff', cursor: 'pointer' }}
|
|
|
+ onClick={() => window.open(user.website, '_blank')}
|
|
|
+ >
|
|
|
+ {user.website}
|
|
|
+ </Text>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ <Tabs defaultActiveKey="posts" style={{ marginBottom: 24 }}>
|
|
|
+ <TabPane tab="帖子" key="posts">
|
|
|
+ <List
|
|
|
+ dataSource={[]} // 这里应该是用户的帖子数据
|
|
|
+ renderItem={item => (
|
|
|
+ <List.Item
|
|
|
+ actions={[
|
|
|
+ <Button icon={<HeartOutlined />}>
|
|
|
+ {item.likesCount > 0 && item.likesCount}
|
|
|
+ </Button>
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <List.Item.Meta
|
|
|
+ description={
|
|
|
+ <>
|
|
|
+ <Paragraph>{item.content}</Paragraph>
|
|
|
+ {item.images?.map((img, idx) => (
|
|
|
+ <img key={idx} src={img} alt={`Post image ${idx}`} style={{ maxWidth: '100%', margin: '8px 0' }} />
|
|
|
+ ))}
|
|
|
+ <Text type="secondary">{new Date(item.createdAt).toLocaleString()}</Text>
|
|
|
+ </>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ </List.Item>
|
|
|
+ )}
|
|
|
+ locale={{ emptyText: '该用户暂无帖子' }}
|
|
|
+ />
|
|
|
+ </TabPane>
|
|
|
+ <TabPane tab="关注" key="following">
|
|
|
+ <List
|
|
|
+ dataSource={[]} // 这里应该是用户关注的人数据
|
|
|
+ renderItem={item => (
|
|
|
+ <List.Item
|
|
|
+ actions={[
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ type={item.isFollowing ? "default" : "primary"}
|
|
|
+ onClick={() => {/* 关注/取消关注逻辑 */}}
|
|
|
+ >
|
|
|
+ {item.isFollowing ? '已关注' : '关注'}
|
|
|
+ </Button>
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <List.Item.Meta
|
|
|
+ avatar={<Avatar src={item.avatar || <UserOutlined />} />}
|
|
|
+ title={
|
|
|
+ <Text
|
|
|
+ style={{ cursor: 'pointer' }}
|
|
|
+ onClick={() => navigate(`/users/${item.id}`)}
|
|
|
+ >
|
|
|
+ {item.nickname || item.username}
|
|
|
+ </Text>
|
|
|
+ }
|
|
|
+ description={item.bio || '暂无简介'}
|
|
|
+ />
|
|
|
+ </List.Item>
|
|
|
+ )}
|
|
|
+ locale={{ emptyText: '该用户暂无关注' }}
|
|
|
+ />
|
|
|
+ </TabPane>
|
|
|
+ <TabPane tab="粉丝" key="followers">
|
|
|
+ <List
|
|
|
+ dataSource={[]} // 这里应该是用户的粉丝数据
|
|
|
+ renderItem={item => (
|
|
|
+ <List.Item
|
|
|
+ actions={[
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ type={item.isFollowing ? "default" : "primary"}
|
|
|
+ onClick={() => {/* 关注/取消关注逻辑 */}}
|
|
|
+ >
|
|
|
+ {item.isFollowing ? '已关注' : '关注'}
|
|
|
+ </Button>
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <List.Item.Meta
|
|
|
+ avatar={<Avatar src={item.avatar || <UserOutlined />} />}
|
|
|
+ title={
|
|
|
+ <Text
|
|
|
+ style={{ cursor: 'pointer' }}
|
|
|
+ onClick={() => navigate(`/users/${item.id}`)}
|
|
|
+ >
|
|
|
+ {item.nickname || item.username}
|
|
|
+ </Text>
|
|
|
+ }
|
|
|
+ description={item.bio || '暂无简介'}
|
|
|
+ />
|
|
|
+ </List.Item>
|
|
|
+ )}
|
|
|
+ locale={{ emptyText: '该用户暂无粉丝' }}
|
|
|
+ />
|
|
|
+ </TabPane>
|
|
|
+ </Tabs>
|
|
|
+ </Content>
|
|
|
+ </Layout>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default UserProfilePage;
|