blob: 4b25a985cfbd36fd44d5088e8cdf593f6397b25a [file] [log] [blame]
import React, { useState, useEffect, useRef } from 'react';
import {
Typography,
Card,
Avatar,
Statistic,
Row,
Col,
Tag,
Progress,
Button,
Divider,
Space,
Tooltip,
message,
Modal,
Form,
Input,
Upload
} from 'antd';
import {
UserOutlined,
EditOutlined,
UploadOutlined,
DownloadOutlined,
TrophyOutlined,
HeartOutlined,
WarningOutlined,
CheckCircleOutlined,
SyncOutlined,
GiftOutlined,
SettingOutlined,
CameraOutlined
} from '@ant-design/icons';
import { useAuth } from '@/features/auth/contexts/AuthContext';
import { updateUserProfile, uploadAvatar } from '@/api/user';
const { Title, Text, Paragraph } = Typography;
const ProfilePage = () => {
const { user, updateUserInfo } = useAuth();
const [loading, setLoading] = useState(false);
const [editModalVisible, setEditModalVisible] = useState(false);
const [form] = Form.useForm();
const hasUpdatedRef = useRef(false); // 用来追踪是否已经更新过用户信息
// PT站统计数据
const [ptStats, setPtStats] = useState({
uploadSize: 0, // GB
downloadSize: 0, // GB
ratio: 0,
points: 0, // 确保初始值为0
userClass: '新用户',
level: 1,
seedingCount: 0,
leechingCount: 0,
completedCount: 0,
invites: 0,
warnings: 0,
hitAndRuns: 0
});
// 页面加载时更新用户信息
useEffect(() => {
const handleUserInfoUpdate = async () => {
if (user?.username && updateUserInfo && !hasUpdatedRef.current) {
console.log('页面加载,正在更新用户信息...');
hasUpdatedRef.current = true; // 标记为已更新
try {
await updateUserInfo(user.username);
console.log('用户信息更新成功');
} catch (error) {
console.error('更新用户信息失败:', error);
hasUpdatedRef.current = false; // 如果失败,重置标记以便重试
}
}
};
handleUserInfoUpdate();
}, [user?.username, updateUserInfo]); // 依赖用户名和更新函数
// 用户信息更新后,从用户数据中提取统计信息
useEffect(() => {
if (user) {
console.log('用户数据变化,更新统计信息:', user);
updateStatsFromUser(user);
}
}, [user]);
// 从用户数据中提取统计信息
const updateStatsFromUser = (userData) => {
if (userData) {
const uploaded = Number(userData.uploaded) || 0;
const downloaded = Number(userData.downloaded) || 0;
const shareRatio = Number(userData.shareRatio) || 0;
const newStats = {
uploadSize: uploaded, // 保存原始字节值
downloadSize: downloaded, // 保存原始字节值
ratio: shareRatio,
points: Number(userData.points) || 0,
userClass: `用户等级`,
level: Number(userData.level) || 1
};
console.log('原始数据:', { uploaded, downloaded, shareRatio });
console.log('更新后的统计数据:', newStats);
setPtStats(newStats);
}
};
// 格式化文件大小
const formatSize = (sizeInBytes) => {
if (sizeInBytes === 0) {
return '0 B';
}
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
const k = 1024;
let bytes = Math.abs(sizeInBytes);
let unitIndex = 0;
// 找到合适的单位
while (bytes >= k && unitIndex < units.length - 1) {
bytes /= k;
unitIndex++;
}
// 根据大小决定小数位数
let decimals = 2;
if (bytes >= 100) {
decimals = 0; // 大于等于100时不显示小数
} else if (bytes >= 10) {
decimals = 1; // 10-99时显示1位小数
}
return `${bytes.toFixed(decimals)} ${units[unitIndex]}`;
};
// 获取分享率颜色
const getRatioColor = (ratio) => {
if (ratio >= 2.0) return '#52c41a'; // 绿色
if (ratio >= 1.0) return '#1890ff'; // 蓝色
if (ratio >= 0.5) return '#faad14'; // 橙色
return '#f5222d'; // 红色
};
// 获取用户等级颜色
const getUserClassColor = (userClass) => {
const classColors = {
'User': 'default',
'Power User': 'blue',
'Elite User': 'purple',
'Crazy User': 'gold',
'Insane User': 'red',
'Veteran User': 'green',
'Extreme User': 'volcano',
'VIP': 'magenta'
};
return classColors[userClass] || 'default';
};
// 显示编辑对话框
const showEditModal = () => {
form.setFieldsValue({
username: user?.username,
email: user?.email
});
setEditModalVisible(true);
};
// 处理编辑提交
const handleEditSubmit = async () => {
try {
const values = await form.validateFields();
setLoading(true);
const response = await updateUserProfile({
username: user.username,
...values
});
if (response && response.data) {
message.success('资料更新成功');
setEditModalVisible(false);
// 资料更新成功后刷新用户信息
try {
await updateUserInfo(user.username);
console.log('资料更新后用户信息已刷新');
} catch (error) {
console.error('资料更新后刷新用户信息失败:', error);
}
} else {
message.error('更新失败,请重试');
}
} catch (error) {
console.error('更新失败:', error);
message.error(error.message || '更新失败,请重试');
} finally {
setLoading(false);
}
};
// 头像上传处理
const handleAvatarUpload = async (file) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('只能上传 JPG/PNG 格式的图片!');
return false;
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('图片大小不能超过 2MB!');
return false;
}
try {
const formData = new FormData();
formData.append('avatar', file);
formData.append('username', user.username);
const response = await uploadAvatar(formData);
if (response && response.data) {
message.success('头像上传成功');
// 头像上传成功后更新用户信息
try {
await updateUserInfo(user.username);
console.log('头像上传后用户信息已更新');
} catch (error) {
console.error('头像上传后更新用户信息失败:', error);
}
} else {
message.error('头像上传失败');
}
} catch (error) {
console.error('头像上传失败:', error);
message.error('头像上传失败');
}
return false; // 阻止默认上传行为
};
// 头像上传配置
const uploadProps = {
name: 'avatar',
showUploadList: false,
beforeUpload: handleAvatarUpload,
};
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<Title level={2}>个人资料</Title>
<Space>
<Button
icon={<SyncOutlined />}
onClick={async () => {
if (!user?.username) {
message.error('用户信息不完整,请重新登录');
return;
}
console.log('手动刷新用户信息...');
setLoading(true);
try {
// 重置标记,允许手动更新
hasUpdatedRef.current = false;
// 调用getUserInfo更新用户信息
await updateUserInfo(user.username);
console.log('用户信息更新成功');
// 统计信息会通过 useEffect 自动更新
message.success('用户信息已更新');
} catch (error) {
console.error('刷新用户信息失败:', error);
message.error('刷新失败: ' + (error.message || '网络错误'));
} finally {
setLoading(false);
}
}}
loading={loading}
>
刷新信息
</Button>
<Button
type="primary"
icon={<EditOutlined />}
onClick={showEditModal}
>
编辑资料
</Button>
</Space>
</div>
<Row gutter={[24, 24]}>
{/* 用户基本信息卡片 */}
<Col xs={24} lg={8}>
<Card>
<div className="text-center">
<div className="relative inline-block">
<Avatar
size={120}
src={user?.avatar}
icon={<UserOutlined />}
className="mb-4"
/>
<Upload {...uploadProps}>
<Button
type="primary"
shape="circle"
icon={<CameraOutlined />}
size="small"
className="absolute bottom-0 right-0"
/>
</Upload>
</div>
<Title level={3} className="mb-2">{user?.username || '用户'}</Title>
<Space direction="vertical" className="w-full">
<Tag
color={getUserClassColor(ptStats.userClass)}
className="text-lg px-3 py-1"
>
用户等级 {user?.level || ptStats.level}
</Tag>
<Text type="secondary">邮箱:{user?.email || '未设置'}</Text>
</Space>
</div>
</Card>
</Col>
{/* PT站统计信息 */}
<Col xs={24} lg={16}>
<Card title={
<Space>
<TrophyOutlined />
<span>PT站统计</span>
</Space>
}>
<Row gutter={[16, 16]}>
{/* 上传下载统计 */}
<Col xs={12} sm={6}>
<Statistic
title="上传量"
value={formatSize(ptStats.uploadSize)}
prefix={<UploadOutlined style={{ color: '#52c41a' }} />}
valueStyle={{ color: '#52c41a' }}
/>
</Col>
<Col xs={12} sm={6}>
<Statistic
title="下载量"
value={formatSize(ptStats.downloadSize)}
prefix={<DownloadOutlined style={{ color: '#1890ff' }} />}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
<Col xs={12} sm={6}>
<Statistic
title="分享率"
value={ptStats.ratio.toFixed(2)}
valueStyle={{ color: getRatioColor(ptStats.ratio) }}
/>
</Col>
<Col xs={12} sm={6}>
<Statistic
title="积分"
value={ptStats.points}
prefix={<HeartOutlined style={{ color: '#eb2f96' }} />}
valueStyle={{ color: '#eb2f96' }}
/>
</Col>
</Row>
</Card>
</Col>
</Row>
{/* 分享率进度条 */}
<Card title="分享率分析">
<Row gutter={[24, 16]}>
<Col xs={24} md={12}>
<div className="mb-4">
<Text strong>当前分享率:{ptStats.ratio.toFixed(2)}</Text>
<Progress
percent={ptStats.ratio >= 999 ? 100 : Math.min(ptStats.ratio * 50, 100)} // 转换为百分比显示
strokeColor={getRatioColor(ptStats.ratio)}
format={() => ptStats.ratio.toFixed(2)}
/>
</div>
<Space wrap>
<Tag color="green">≥2.00 优秀</Tag>
<Tag color="blue">≥1.00 良好</Tag>
<Tag color="orange">≥0.50 及格</Tag>
<Tag color="red">&lt;0.50 需要改善</Tag>
</Space>
</Col>
<Col xs={24} md={12}>
<div className="space-y-2">
<Paragraph>
<Text strong>分享率说明:</Text>
</Paragraph>
<Paragraph type="secondary" className="text-sm">
分享率 = 上传量 ÷ 下载量<br/>
保持良好的分享率有助于维护账号状态<br/>
建议长期做种热门资源提升分享率<br/>
分享率过低可能导致账号受限
</Paragraph>
</div>
</Col>
</Row>
</Card>
{/* 编辑资料对话框 */}
<Modal
title="编辑个人资料"
open={editModalVisible}
onOk={handleEditSubmit}
onCancel={() => setEditModalVisible(false)}
confirmLoading={loading}
okText="保存"
cancelText="取消"
>
<Form form={form} layout="vertical">
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input placeholder="请输入用户名" />
</Form.Item>
<Form.Item
name="email"
label="邮箱"
rules={[
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '请输入有效的邮箱地址' }
]}
>
<Input placeholder="请输入邮箱" />
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default ProfilePage;