增加活动页面

Change-Id: I63cf84e250d16b2af401f335562fcadd3be13170
diff --git a/react-ui/src/pages/Activity/index.tsx b/react-ui/src/pages/Activity/index.tsx
new file mode 100644
index 0000000..936b88a
--- /dev/null
+++ b/react-ui/src/pages/Activity/index.tsx
@@ -0,0 +1,318 @@
+// index.tsx - 活动中心页面
+
+import React, { useState, useEffect, useRef } from 'react';
+import {
+    Card,
+    Button,
+    Tag,
+    message,
+    Spin,
+    Empty,
+    List,
+    Space,
+    Typography,
+    Upload
+} from 'antd';
+import {
+    GiftOutlined,
+    UploadOutlined,
+    DownloadOutlined,
+    FireOutlined,
+    CheckCircleOutlined,
+    ClockCircleOutlined
+} from '@ant-design/icons';
+import { PageContainer } from '@ant-design/pro-components';
+import { getActivityList, participateActivity, formatDateTime } from './service';
+import { uploadTorrent, downloadTorrent } from '../Torrent/service';
+import type { SysActivity } from './data';
+
+const { Title, Text } = Typography;
+
+const ActivityPage: React.FC = () => {
+    const [activities, setActivities] = useState<SysActivity[]>([]);
+    const [loading, setLoading] = useState(false);
+    const [participatingIds, setParticipatingIds] = useState<Set<number>>(new Set());
+    const [uploading, setUploading] = useState(false);
+    const [downloading, setDownloading] = useState<number | null>(null);
+    const fileInputRef = useRef<HTMLInputElement>(null);
+
+    // 获取活动列表
+    const fetchActivities = async () => {
+        setLoading(true);
+        try {
+            const res = await getActivityList({ pageNum: 1, pageSize: 20 });
+            console.log('活动列表响应:', res);
+            if (res.code === 0) {
+                // 修正:使用 res.data.list 而不是 res.rows
+                setActivities(res.data.list);
+                console.log('活动数据:', res.data.list);
+                console.log('第一个活动:', res.data.list[0]);
+            } else {
+                message.error(res.msg || '获取活动列表失败');
+            }
+        } catch (error) {
+            console.error('获取活动列表异常:', error);
+            message.error('网络异常,请稍后重试');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    // 获取排行榜
+    // const fetchLeaderboard = async () => {
+    //     try {
+    //         const res = await getLeaderboard({ pageNum: 1, pageSize: 50 });
+    //         if (res.code === 0) {
+    //             setLeaderboard(res.data.list);
+    //         } else {
+    //             message.error(res.msg || '获取排行榜失败');
+    //         }
+    //     } catch (error) {
+    //         console.error('获取排行榜失败', error);
+    //         message.error('获取排行榜异常,请稍后重试');
+    //     }
+    // };
+
+    // 处理文件上传
+    const handleUpload = async (file: File, activity: SysActivity) => {
+        setUploading(true);
+        try {
+            const res = await uploadTorrent(file);
+            if (res.code === 0) {
+                message.success('上传成功!');
+                // 上传成功后自动参与活动
+                await handleParticipate(activity.activityId);
+            } else {
+                message.error(res.msg || '上传失败');
+            }
+        } catch (error) {
+            console.error('上传异常:', error);
+            message.error('上传异常,请稍后重试');
+        } finally {
+            setUploading(false);
+        }
+    };
+
+    // 处理种子下载
+    const handleDownload = async (activity: SysActivity) => {
+        setDownloading(activity.activityId);
+        try {
+            const torrentId = activity.conditionValue; // 种子ID存储在conditionValue中
+            const blob = await downloadTorrent(Number(torrentId));
+
+            // 创建下载链接
+            const url = window.URL.createObjectURL(blob);
+            const link = document.createElement('a');
+            link.href = url;
+            link.download = `torrent_${torrentId}.torrent`;
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+            window.URL.revokeObjectURL(url);
+
+            message.success('下载成功');
+            // 下载成功后自动参与活动
+            await handleParticipate(activity.activityId);
+        } catch (error) {
+            console.error('下载异常:', error);
+            message.error('下载异常,请稍后重试');
+        } finally {
+            setDownloading(null);
+        }
+    };
+
+    // 触发文件选择
+    const triggerFileUpload = (activity: SysActivity) => {
+        const input = document.createElement('input');
+        input.type = 'file';
+        input.accept = '.torrent';
+        input.onchange = (e) => {
+            const file = (e.target as HTMLInputElement).files?.[0];
+            if (file) {
+                handleUpload(file, activity);
+            }
+        };
+        input.click();
+    };
+
+    // 参与活动
+    const handleParticipate = async (activityId: number) => {
+        // 添加到参与中的活动ID集合
+        setParticipatingIds(prev => new Set(prev).add(activityId));
+
+        try {
+            const res = await participateActivity(activityId);
+            if (res.code === 0) {
+                message.success('参与成功!');
+                fetchActivities();
+                // 已删除排行榜刷新
+            } else {
+                message.error(res.msg || '参与失败');
+            }
+        } catch (error) {
+            console.error('参与活动异常:', error);
+            message.error('网络异常,请稍后重试');
+        } finally {
+            // 从参与中的活动ID集合中移除
+            setParticipatingIds(prev => {
+                const newSet = new Set(prev);
+                newSet.delete(activityId);
+                return newSet;
+            });
+        }
+    };
+
+    useEffect(() => {
+        fetchActivities();
+        // 已删除排行榜获取
+    }, []);
+
+    // 渲染活动类型图标(可点击)
+    const renderActivityIcon = (activity: SysActivity) => {
+        if (activity.status === 0) {
+            // 活动已结束,图标不可点击
+            return activity.activityType === 'UPLOAD' ?
+                <UploadOutlined style={{ fontSize: 20, color: '#d9d9d9' }} /> :
+                <DownloadOutlined style={{ fontSize: 20, color: '#d9d9d9' }} />;
+        }
+
+        if (activity.activityType === 'UPLOAD') {
+            return (
+                <UploadOutlined
+                    style={{
+                        fontSize: 20,
+                        color: '#1890ff',
+                        cursor: 'pointer'
+                    }}
+                    title="点击上传种子文件"
+                    onClick={() => triggerFileUpload(activity)}
+                />
+            );
+        } else {
+            return (
+                <DownloadOutlined
+                    style={{
+                        fontSize: 20,
+                        color: '#52c41a',
+                        cursor: 'pointer'
+                    }}
+                    title="点击下载种子"
+                    onClick={() => handleDownload(activity)}
+                />
+            );
+        }
+    };
+
+    // 渲染状态标签
+    const renderStatusTag = (status: number) => {
+        return status === 1 ? (
+            <Tag icon={<FireOutlined />} color="success">进行中</Tag>
+        ) : (
+            <Tag icon={<CheckCircleOutlined />} color="default">已结束</Tag>
+        );
+    };
+
+    // 格式化条件显示
+    const formatCondition = (activity: SysActivity) => {
+        if (activity.activityType === 'UPLOAD') {
+            return `上传 ${activity.conditionValue}`;
+        }
+        return '完成下载任务';
+    };
+
+    // 格式化时间显示
+    const formatTimeRange = (startTime: string, endTime: string) => {
+        try {
+            // 处理 ISO 8601 格式的时间
+            const start = formatDateTime(startTime);
+            const end = formatDateTime(endTime);
+
+            // 只显示日期部分
+            const startDate = start.split(' ')[0];
+            const endDate = end.split(' ')[0];
+
+            return `${startDate} ~ ${endDate}`;
+        } catch (error) {
+            console.error('时间格式化错误:', error);
+            // 降级处理:如果是老格式,直接使用
+            return `${startTime.split(' ')[0]} ~ ${endTime.split(' ')[0]}`;
+        }
+    };
+
+    // 排行榜相关代码已删除
+
+    return (
+        <PageContainer title="活动中心">
+            <Spin spinning={loading}>
+                <List
+                    grid={{ gutter: 16, xs: 1, sm: 2, md: 2, lg: 3, xl: 3 }}
+                    dataSource={activities}
+                    locale={{ emptyText: <Empty description="暂无活动" /> }}
+                    renderItem={(activity) => (
+                        <List.Item>
+                            <Card
+                                hoverable
+                                actions={[
+                                    <Button
+                                        key="participate"
+                                        type="primary"
+                                        disabled={activity.status === 0}
+                                        loading={
+                                            participatingIds.has(activity.activityId) ||
+                                            uploading ||
+                                            downloading === activity.activityId
+                                        }
+                                        onClick={() => handleParticipate(activity.activityId)}
+                                    >
+                                        {activity.status === 1 ? '确认参与' : '已结束'}
+                                    </Button>
+                                ]}
+                            >
+                                <Space direction="vertical" size={8} style={{ width: '100%' }}>
+                                    <Space style={{ width: '100%', justifyContent: 'space-between' }}>
+                                        {renderActivityIcon(activity)}
+                                        {renderStatusTag(activity.status)}
+                                    </Space>
+
+                                    <Title level={5} style={{ margin: 0 }}>
+                                        {activity.activityName}
+                                    </Title>
+
+                                    <Text type="secondary">
+                                        {formatCondition(activity)}
+                                    </Text>
+
+                                    <Space>
+                                        <GiftOutlined style={{ color: '#faad14' }} />
+                                        <Text strong style={{ color: '#faad14' }}>
+                                            {activity.rewardBonus} 积分
+                                        </Text>
+                                    </Space>
+
+                                    <Space size={4} style={{ fontSize: 12 }}>
+                                        <ClockCircleOutlined />
+                                        <Text type="secondary">
+                                            {formatTimeRange(activity.startTime, activity.endTime)}
+                                        </Text>
+                                    </Space>
+
+                                    {/* 操作提示 */}
+                                    {activity.status === 1 && (
+                                        <Text type="secondary" style={{ fontSize: 12 }}>
+                                            {activity.activityType === 'UPLOAD'
+                                                ? '点击上传图标选择种子文件'
+                                                : '点击下载图标获取种子'
+                                            }
+                                        </Text>
+                                    )}
+                                </Space>
+                            </Card>
+                        </List.Item>
+                    )}
+                />
+            </Spin>
+        </PageContainer>
+    );
+};
+
+export default ActivityPage;
\ No newline at end of file