blob: bdbdb0aaa9e51d06460ccc3ee443bf5f6bb5298c [file] [log] [blame]
ybtbac75f22025-06-08 22:31:15 +08001import React, { useState, useEffect } from 'react';
2import {
3 Typography,
4 Card,
5 Avatar,
6 Statistic,
7 Row,
8 Col,
9 Tag,
10 Progress,
11 Button,
12 Divider,
13 Space,
14 Tooltip,
15 message,
16 Modal,
17 Form,
18 Input,
19 Upload
20} from 'antd';
21import {
22 UserOutlined,
23 EditOutlined,
24 UploadOutlined,
25 DownloadOutlined,
26 TrophyOutlined,
27 HeartOutlined,
28 WarningOutlined,
29 CheckCircleOutlined,
30 SyncOutlined,
31 GiftOutlined,
32 SettingOutlined,
33 CameraOutlined
34} from '@ant-design/icons';
35import { useAuth } from '@/features/auth/contexts/AuthContext';
36import { getUserStats, updateUserProfile, uploadAvatar } from '@/api/user';
ybtda5978b2025-05-31 15:58:05 +080037
ybtbac75f22025-06-08 22:31:15 +080038const { Title, Text, Paragraph } = Typography;
ybtda5978b2025-05-31 15:58:05 +080039
ybtbac75f22025-06-08 22:31:15 +080040const ProfilePage = () => {
41 const { user } = useAuth();
42 const [loading, setLoading] = useState(false);
43 const [editModalVisible, setEditModalVisible] = useState(false);
44 const [form] = Form.useForm();
45
46 // PT站统计数据
47 const [ptStats, setPtStats] = useState({
48 uploadSize: 157.89, // GB
49 downloadSize: 89.32, // GB
50 ratio: 1.77,
51 points: 12580,
52 userClass: 'Power User',
53 seedingCount: 12,
54 leechingCount: 2,
55 completedCount: 156,
56 invites: 3,
57 warnings: 0,
58 hitAndRuns: 0,
59 joinDate: '2023-05-15',
60 lastActive: '2024-12-28 15:30:00'
61 });
62
63 // 获取用户统计信息
64 useEffect(() => {
65 if (user?.username) {
66 fetchUserStats();
67 }
68 }, [user]);
69
70 const fetchUserStats = async () => {
71 try {
72 setLoading(true);
73 const response = await getUserStats(user.username);
74 if (response && response.data) {
75 setPtStats(prevStats => ({
76 ...prevStats,
77 ...response.data
78 }));
79 }
80 } catch (error) {
81 console.error('获取用户统计信息失败:', error);
82 // 使用默认数据,不显示错误信息
83 } finally {
84 setLoading(false);
85 }
86 };
87
88 // 格式化文件大小
89 const formatSize = (sizeInGB) => {
90 if (sizeInGB >= 1024) {
91 return `${(sizeInGB / 1024).toFixed(2)} TB`;
92 }
93 return `${sizeInGB.toFixed(2)} GB`;
94 };
95
96 // 获取分享率颜色
97 const getRatioColor = (ratio) => {
98 if (ratio >= 2.0) return '#52c41a'; // 绿色
99 if (ratio >= 1.0) return '#1890ff'; // 蓝色
100 if (ratio >= 0.5) return '#faad14'; // 橙色
101 return '#f5222d'; // 红色
102 };
103
104 // 获取用户等级颜色
105 const getUserClassColor = (userClass) => {
106 const classColors = {
107 'User': 'default',
108 'Power User': 'blue',
109 'Elite User': 'purple',
110 'Crazy User': 'gold',
111 'Insane User': 'red',
112 'Veteran User': 'green',
113 'Extreme User': 'volcano',
114 'VIP': 'magenta'
115 };
116 return classColors[userClass] || 'default';
117 };
118
119 // 显示编辑对话框
120 const showEditModal = () => {
121 form.setFieldsValue({
122 username: user?.username,
123 email: user?.email
124 });
125 setEditModalVisible(true);
126 };
127
128 // 处理编辑提交
129 const handleEditSubmit = async () => {
130 try {
131 const values = await form.validateFields();
132 setLoading(true);
133
134 const response = await updateUserProfile({
135 username: user.username,
136 ...values
137 });
138
139 if (response && response.data) {
140 message.success('资料更新成功');
141 setEditModalVisible(false);
142 // 可以触发AuthContext的用户信息更新
143 } else {
144 message.error('更新失败,请重试');
145 }
146
147 } catch (error) {
148 console.error('更新失败:', error);
149 message.error(error.message || '更新失败,请重试');
150 } finally {
151 setLoading(false);
152 }
153 };
154
155 // 头像上传处理
156 const handleAvatarUpload = async (file) => {
157 const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
158 if (!isJpgOrPng) {
159 message.error('只能上传 JPG/PNG 格式的图片!');
160 return false;
161 }
162 const isLt2M = file.size / 1024 / 1024 < 2;
163 if (!isLt2M) {
164 message.error('图片大小不能超过 2MB!');
165 return false;
166 }
167
168 try {
169 const formData = new FormData();
170 formData.append('avatar', file);
171 formData.append('username', user.username);
172
173 const response = await uploadAvatar(formData);
174 if (response && response.data) {
175 message.success('头像上传成功');
176 // 可以触发AuthContext的用户信息更新或重新获取用户信息
177 } else {
178 message.error('头像上传失败');
179 }
180 } catch (error) {
181 console.error('头像上传失败:', error);
182 message.error('头像上传失败');
183 }
ybtda5978b2025-05-31 15:58:05 +0800184
ybtbac75f22025-06-08 22:31:15 +0800185 return false; // 阻止默认上传行为
186 };
187
188 // 头像上传配置
189 const uploadProps = {
190 name: 'avatar',
191 showUploadList: false,
192 beforeUpload: handleAvatarUpload,
193 };
194
195 return (
196 <div className="space-y-6">
197 <div className="flex justify-between items-center">
198 <Title level={2}>个人资料</Title>
199 <Button
200 type="primary"
201 icon={<EditOutlined />}
202 onClick={showEditModal}
203 >
204 编辑资料
205 </Button>
206 </div>
207
208 <Row gutter={[24, 24]}>
209 {/* 用户基本信息卡片 */}
210 <Col xs={24} lg={8}>
211 <Card>
212 <div className="text-center">
213 <div className="relative inline-block">
214 <Avatar
215 size={120}
216 src={user?.avatar}
217 icon={<UserOutlined />}
218 className="mb-4"
219 />
220 <Upload {...uploadProps}>
221 <Button
222 type="primary"
223 shape="circle"
224 icon={<CameraOutlined />}
225 size="small"
226 className="absolute bottom-0 right-0"
227 />
228 </Upload>
229 </div>
230
231 <Title level={3} className="mb-2">{user?.username || '用户'}</Title>
232
233 <Space direction="vertical" className="w-full">
234 <Tag
235 color={getUserClassColor(ptStats.userClass)}
236 className="text-lg px-3 py-1"
237 >
238 {ptStats.userClass}
239 </Tag>
240
241 <Text type="secondary">邮箱:{user?.email || '未设置'}</Text>
242 <Text type="secondary">注册时间:{ptStats.joinDate}</Text>
243 <Text type="secondary">最后活跃:{ptStats.lastActive}</Text>
244 </Space>
245 </div>
246 </Card>
247 </Col>
248
249 {/* PT站统计信息 */}
250 <Col xs={24} lg={16}>
251 <Card title={
252 <Space>
253 <TrophyOutlined />
254 <span>PT站统计</span>
255 </Space>
256 }>
257 <Row gutter={[16, 16]}>
258 {/* 上传下载统计 */}
259 <Col xs={12} sm={6}>
260 <Statistic
261 title="上传量"
262 value={formatSize(ptStats.uploadSize)}
263 prefix={<UploadOutlined style={{ color: '#52c41a' }} />}
264 valueStyle={{ color: '#52c41a' }}
265 />
266 </Col>
267 <Col xs={12} sm={6}>
268 <Statistic
269 title="下载量"
270 value={formatSize(ptStats.downloadSize)}
271 prefix={<DownloadOutlined style={{ color: '#1890ff' }} />}
272 valueStyle={{ color: '#1890ff' }}
273 />
274 </Col>
275 <Col xs={12} sm={6}>
276 <Statistic
277 title="分享率"
278 value={ptStats.ratio}
279 precision={2}
280 valueStyle={{ color: getRatioColor(ptStats.ratio) }}
281 />
282 </Col>
283 <Col xs={12} sm={6}>
284 <Statistic
285 title="积分"
286 value={ptStats.points}
287 prefix={<HeartOutlined style={{ color: '#eb2f96' }} />}
288 valueStyle={{ color: '#eb2f96' }}
289 />
290 </Col>
291 </Row>
292 </Card>
293 </Col>
294 </Row>
295
296 {/* 分享率进度条 */}
297 <Card title="分享率分析">
298 <Row gutter={[24, 16]}>
299 <Col xs={24} md={12}>
300 <div className="mb-4">
301 <Text strong>当前分享率:{ptStats.ratio}</Text>
302 <Progress
303 percent={Math.min(ptStats.ratio * 50, 100)} // 转换为百分比显示
304 strokeColor={getRatioColor(ptStats.ratio)}
305 format={() => ptStats.ratio}
306 />
307 </div>
308 <Space wrap>
309 <Tag color="green">≥2.0 优秀</Tag>
310 <Tag color="blue">≥1.0 良好</Tag>
311 <Tag color="orange">≥0.5 及格</Tag>
312 <Tag color="red">&lt;0.5 需要改善</Tag>
313 </Space>
314 </Col>
315 <Col xs={24} md={12}>
316 <div className="space-y-2">
317 <Paragraph>
318 <Text strong>分享率说明:</Text>
319 </Paragraph>
320 <Paragraph type="secondary" className="text-sm">
321 分享率 = 上传量 ÷ 下载量<br/>
322 保持良好的分享率有助于维护账号状态<br/>
323 建议长期做种热门资源提升分享率<br/>
324 分享率过低可能导致账号受限
325 </Paragraph>
326 </div>
327 </Col>
328 </Row>
329 </Card>
330
331 {/* 编辑资料对话框 */}
332 <Modal
333 title="编辑个人资料"
334 open={editModalVisible}
335 onOk={handleEditSubmit}
336 onCancel={() => setEditModalVisible(false)}
337 confirmLoading={loading}
338 okText="保存"
339 cancelText="取消"
340 >
341 <Form form={form} layout="vertical">
342 <Form.Item
343 name="username"
344 label="用户名"
345 rules={[{ required: true, message: '请输入用户名' }]}
346 >
347 <Input placeholder="请输入用户名" />
348 </Form.Item>
349 <Form.Item
350 name="email"
351 label="邮箱"
352 rules={[
353 { required: true, message: '请输入邮箱' },
354 { type: 'email', message: '请输入有效的邮箱地址' }
355 ]}
356 >
357 <Input placeholder="请输入邮箱" />
358 </Form.Item>
359 </Form>
360 </Modal>
ybtda5978b2025-05-31 15:58:05 +0800361 </div>
ybtbac75f22025-06-08 22:31:15 +0800362 );
363};
ybtda5978b2025-05-31 15:58:05 +0800364
365export default ProfilePage;