feat(api): 重构 API 调用并优化用户认证流程
- 新增 auth、forum 和 user API 文件夹,重新组织 API 调用结构
- 重构 AuthContext,使用新 API 进行用户认证和信息管理
- 更新 AdminPanel、ForumPage 等组件,使用新的 API 调用
- 删除旧的 authApi.js 文件,清理冗余代码
- 优化用户登录、注册和登出流程,改进错误处理和用户提示
Change-Id: If664193e1bf30036c197f164edc5b10df75f1331
diff --git a/src/features/profile/pages/ProfilePage.jsx b/src/features/profile/pages/ProfilePage.jsx
index 79fbfe4..bdbdb0a 100644
--- a/src/features/profile/pages/ProfilePage.jsx
+++ b/src/features/profile/pages/ProfilePage.jsx
@@ -1,19 +1,365 @@
-import React from 'react';
-import { Typography, Button, Empty } from 'antd';
+import React, { useState, useEffect } 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 { getUserStats, updateUserProfile, uploadAvatar } from '@/api/user';
-const { Title } = Typography;
+const { Title, Text, Paragraph } = Typography;
-const ProfilePage = () => (
- <div className="space-y-6">
- <Title level={2}>个人资料</Title>
+const ProfilePage = () => {
+ const { user } = useAuth();
+ const [loading, setLoading] = useState(false);
+ const [editModalVisible, setEditModalVisible] = useState(false);
+ const [form] = Form.useForm();
+
+ // PT站统计数据
+ const [ptStats, setPtStats] = useState({
+ uploadSize: 157.89, // GB
+ downloadSize: 89.32, // GB
+ ratio: 1.77,
+ points: 12580,
+ userClass: 'Power User',
+ seedingCount: 12,
+ leechingCount: 2,
+ completedCount: 156,
+ invites: 3,
+ warnings: 0,
+ hitAndRuns: 0,
+ joinDate: '2023-05-15',
+ lastActive: '2024-12-28 15:30:00'
+ });
+
+ // 获取用户统计信息
+ useEffect(() => {
+ if (user?.username) {
+ fetchUserStats();
+ }
+ }, [user]);
+
+ const fetchUserStats = async () => {
+ try {
+ setLoading(true);
+ const response = await getUserStats(user.username);
+ if (response && response.data) {
+ setPtStats(prevStats => ({
+ ...prevStats,
+ ...response.data
+ }));
+ }
+ } catch (error) {
+ console.error('获取用户统计信息失败:', error);
+ // 使用默认数据,不显示错误信息
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 格式化文件大小
+ const formatSize = (sizeInGB) => {
+ if (sizeInGB >= 1024) {
+ return `${(sizeInGB / 1024).toFixed(2)} TB`;
+ }
+ return `${sizeInGB.toFixed(2)} GB`;
+ };
+
+ // 获取分享率颜色
+ 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);
+ // 可以触发AuthContext的用户信息更新
+ } 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('头像上传成功');
+ // 可以触发AuthContext的用户信息更新或重新获取用户信息
+ } else {
+ message.error('头像上传失败');
+ }
+ } catch (error) {
+ console.error('头像上传失败:', error);
+ message.error('头像上传失败');
+ }
- <div className="text-center py-12">
- <Empty description="用户资料页面正在开发中" />
- <Button type="primary" className="mt-4">
- 编辑资料
- </Button>
+ 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>
+ <Button
+ type="primary"
+ icon={<EditOutlined />}
+ onClick={showEditModal}
+ >
+ 编辑资料
+ </Button>
+ </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"
+ >
+ {ptStats.userClass}
+ </Tag>
+
+ <Text type="secondary">邮箱:{user?.email || '未设置'}</Text>
+ <Text type="secondary">注册时间:{ptStats.joinDate}</Text>
+ <Text type="secondary">最后活跃:{ptStats.lastActive}</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}
+ precision={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}</Text>
+ <Progress
+ percent={Math.min(ptStats.ratio * 50, 100)} // 转换为百分比显示
+ strokeColor={getRatioColor(ptStats.ratio)}
+ format={() => ptStats.ratio}
+ />
+ </div>
+ <Space wrap>
+ <Tag color="green">≥2.0 优秀</Tag>
+ <Tag color="blue">≥1.0 良好</Tag>
+ <Tag color="orange">≥0.5 及格</Tag>
+ <Tag color="red"><0.5 需要改善</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>
- </div>
-);
+ );
+};
export default ProfilePage;
\ No newline at end of file