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