| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- import debug from 'debug';
- const rpcLogger = debug('frontend:api:rpc');
- import React, { useEffect, useState } from 'react';
- import { UserIcon, EditIcon, ExternalLinkIcon, Loader2Icon } from '@heroicons/react/24/outline';
- import { useParams, useNavigate } from 'react-router-dom';
- import { userClient } from '@/client/api';
- import { useAuth } from '@/client/home/hooks/AuthProvider';
- import type { UserEntity } from '@/server/modules/users/user.entity';
- const UserProfilePage: React.FC = () => {
- const [user, setUser] = useState<UserEntity | null>(null);
- const [loading, setLoading] = useState(true);
- const { id: userId } = useParams<{ id: string }>();
- const id = Number(userId);
- const navigate = useNavigate();
- const { user: currentUser } = useAuth();
- // 获取用户资料
- const fetchUserProfile = async () => {
- if (!id) return;
-
- try {
- setLoading(true);
- rpcLogger('Fetching user profile for id: %s', id);
- const response = await userClient[':id'].$get({
- param: { id }
- });
-
- if (!response.ok) throw new Error('获取用户资料失败');
-
- const userData = await response.json();
- setUser(userData);
- } catch (error) {
- rpcLogger.error('Error fetching user profile:', error);
- } finally {
- setLoading(false);
- }
- };
- 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;
|