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