BirdNETM | 8bcf85f | 2025-06-09 16:07:21 +0800 | [diff] [blame] | 1 | // index.tsx - 活动中心页面 |
| 2 | |
| 3 | import React, { useState, useEffect, useRef } from 'react'; |
| 4 | import { |
| 5 | Card, |
| 6 | Button, |
| 7 | Tag, |
| 8 | message, |
| 9 | Spin, |
| 10 | Empty, |
| 11 | List, |
| 12 | Space, |
| 13 | Typography, |
| 14 | Upload |
| 15 | } from 'antd'; |
| 16 | import { |
| 17 | GiftOutlined, |
| 18 | UploadOutlined, |
| 19 | DownloadOutlined, |
| 20 | FireOutlined, |
| 21 | CheckCircleOutlined, |
| 22 | ClockCircleOutlined |
| 23 | } from '@ant-design/icons'; |
| 24 | import { PageContainer } from '@ant-design/pro-components'; |
| 25 | import { getActivityList, participateActivity, formatDateTime } from './service'; |
| 26 | import { uploadTorrent, downloadTorrent } from '../Torrent/service'; |
| 27 | import type { SysActivity } from './data'; |
| 28 | |
| 29 | const { Title, Text } = Typography; |
| 30 | |
| 31 | const ActivityPage: React.FC = () => { |
| 32 | const [activities, setActivities] = useState<SysActivity[]>([]); |
| 33 | const [loading, setLoading] = useState(false); |
| 34 | const [participatingIds, setParticipatingIds] = useState<Set<number>>(new Set()); |
| 35 | const [uploading, setUploading] = useState(false); |
| 36 | const [downloading, setDownloading] = useState<number | null>(null); |
| 37 | const fileInputRef = useRef<HTMLInputElement>(null); |
| 38 | |
| 39 | // 获取活动列表 |
| 40 | const fetchActivities = async () => { |
| 41 | setLoading(true); |
| 42 | try { |
| 43 | const res = await getActivityList({ pageNum: 1, pageSize: 20 }); |
| 44 | console.log('活动列表响应:', res); |
| 45 | if (res.code === 0) { |
| 46 | // 修正:使用 res.data.list 而不是 res.rows |
| 47 | setActivities(res.data.list); |
| 48 | console.log('活动数据:', res.data.list); |
| 49 | console.log('第一个活动:', res.data.list[0]); |
| 50 | } else { |
| 51 | message.error(res.msg || '获取活动列表失败'); |
| 52 | } |
| 53 | } catch (error) { |
| 54 | console.error('获取活动列表异常:', error); |
| 55 | message.error('网络异常,请稍后重试'); |
| 56 | } finally { |
| 57 | setLoading(false); |
| 58 | } |
| 59 | }; |
| 60 | |
| 61 | // 获取排行榜 |
| 62 | // const fetchLeaderboard = async () => { |
| 63 | // try { |
| 64 | // const res = await getLeaderboard({ pageNum: 1, pageSize: 50 }); |
| 65 | // if (res.code === 0) { |
| 66 | // setLeaderboard(res.data.list); |
| 67 | // } else { |
| 68 | // message.error(res.msg || '获取排行榜失败'); |
| 69 | // } |
| 70 | // } catch (error) { |
| 71 | // console.error('获取排行榜失败', error); |
| 72 | // message.error('获取排行榜异常,请稍后重试'); |
| 73 | // } |
| 74 | // }; |
| 75 | |
| 76 | // 处理文件上传 |
| 77 | const handleUpload = async (file: File, activity: SysActivity) => { |
| 78 | setUploading(true); |
| 79 | try { |
| 80 | const res = await uploadTorrent(file); |
| 81 | if (res.code === 0) { |
| 82 | message.success('上传成功!'); |
| 83 | // 上传成功后自动参与活动 |
| 84 | await handleParticipate(activity.activityId); |
| 85 | } else { |
| 86 | message.error(res.msg || '上传失败'); |
| 87 | } |
| 88 | } catch (error) { |
| 89 | console.error('上传异常:', error); |
| 90 | message.error('上传异常,请稍后重试'); |
| 91 | } finally { |
| 92 | setUploading(false); |
| 93 | } |
| 94 | }; |
| 95 | |
| 96 | // 处理种子下载 |
| 97 | const handleDownload = async (activity: SysActivity) => { |
| 98 | setDownloading(activity.activityId); |
| 99 | try { |
| 100 | const torrentId = activity.conditionValue; // 种子ID存储在conditionValue中 |
| 101 | const blob = await downloadTorrent(Number(torrentId)); |
| 102 | |
| 103 | // 创建下载链接 |
| 104 | const url = window.URL.createObjectURL(blob); |
| 105 | const link = document.createElement('a'); |
| 106 | link.href = url; |
| 107 | link.download = `torrent_${torrentId}.torrent`; |
| 108 | document.body.appendChild(link); |
| 109 | link.click(); |
| 110 | document.body.removeChild(link); |
| 111 | window.URL.revokeObjectURL(url); |
| 112 | |
| 113 | message.success('下载成功'); |
| 114 | // 下载成功后自动参与活动 |
| 115 | await handleParticipate(activity.activityId); |
| 116 | } catch (error) { |
| 117 | console.error('下载异常:', error); |
| 118 | message.error('下载异常,请稍后重试'); |
| 119 | } finally { |
| 120 | setDownloading(null); |
| 121 | } |
| 122 | }; |
| 123 | |
| 124 | // 触发文件选择 |
| 125 | const triggerFileUpload = (activity: SysActivity) => { |
| 126 | const input = document.createElement('input'); |
| 127 | input.type = 'file'; |
| 128 | input.accept = '.torrent'; |
| 129 | input.onchange = (e) => { |
| 130 | const file = (e.target as HTMLInputElement).files?.[0]; |
| 131 | if (file) { |
| 132 | handleUpload(file, activity); |
| 133 | } |
| 134 | }; |
| 135 | input.click(); |
| 136 | }; |
| 137 | |
| 138 | // 参与活动 |
| 139 | const handleParticipate = async (activityId: number) => { |
| 140 | // 添加到参与中的活动ID集合 |
| 141 | setParticipatingIds(prev => new Set(prev).add(activityId)); |
| 142 | |
| 143 | try { |
| 144 | const res = await participateActivity(activityId); |
| 145 | if (res.code === 0) { |
| 146 | message.success('参与成功!'); |
| 147 | fetchActivities(); |
| 148 | // 已删除排行榜刷新 |
| 149 | } else { |
| 150 | message.error(res.msg || '参与失败'); |
| 151 | } |
| 152 | } catch (error) { |
| 153 | console.error('参与活动异常:', error); |
| 154 | message.error('网络异常,请稍后重试'); |
| 155 | } finally { |
| 156 | // 从参与中的活动ID集合中移除 |
| 157 | setParticipatingIds(prev => { |
| 158 | const newSet = new Set(prev); |
| 159 | newSet.delete(activityId); |
| 160 | return newSet; |
| 161 | }); |
| 162 | } |
| 163 | }; |
| 164 | |
| 165 | useEffect(() => { |
| 166 | fetchActivities(); |
| 167 | // 已删除排行榜获取 |
| 168 | }, []); |
| 169 | |
| 170 | // 渲染活动类型图标(可点击) |
| 171 | const renderActivityIcon = (activity: SysActivity) => { |
| 172 | if (activity.status === 0) { |
| 173 | // 活动已结束,图标不可点击 |
| 174 | return activity.activityType === 'UPLOAD' ? |
| 175 | <UploadOutlined style={{ fontSize: 20, color: '#d9d9d9' }} /> : |
| 176 | <DownloadOutlined style={{ fontSize: 20, color: '#d9d9d9' }} />; |
| 177 | } |
| 178 | |
| 179 | if (activity.activityType === 'UPLOAD') { |
| 180 | return ( |
| 181 | <UploadOutlined |
| 182 | style={{ |
| 183 | fontSize: 20, |
| 184 | color: '#1890ff', |
| 185 | cursor: 'pointer' |
| 186 | }} |
| 187 | title="点击上传种子文件" |
| 188 | onClick={() => triggerFileUpload(activity)} |
| 189 | /> |
| 190 | ); |
| 191 | } else { |
| 192 | return ( |
| 193 | <DownloadOutlined |
| 194 | style={{ |
| 195 | fontSize: 20, |
| 196 | color: '#52c41a', |
| 197 | cursor: 'pointer' |
| 198 | }} |
| 199 | title="点击下载种子" |
| 200 | onClick={() => handleDownload(activity)} |
| 201 | /> |
| 202 | ); |
| 203 | } |
| 204 | }; |
| 205 | |
| 206 | // 渲染状态标签 |
| 207 | const renderStatusTag = (status: number) => { |
| 208 | return status === 1 ? ( |
| 209 | <Tag icon={<FireOutlined />} color="success">进行中</Tag> |
| 210 | ) : ( |
| 211 | <Tag icon={<CheckCircleOutlined />} color="default">已结束</Tag> |
| 212 | ); |
| 213 | }; |
| 214 | |
| 215 | // 格式化条件显示 |
| 216 | const formatCondition = (activity: SysActivity) => { |
| 217 | if (activity.activityType === 'UPLOAD') { |
| 218 | return `上传 ${activity.conditionValue}`; |
| 219 | } |
| 220 | return '完成下载任务'; |
| 221 | }; |
| 222 | |
| 223 | // 格式化时间显示 |
| 224 | const formatTimeRange = (startTime: string, endTime: string) => { |
| 225 | try { |
| 226 | // 处理 ISO 8601 格式的时间 |
| 227 | const start = formatDateTime(startTime); |
| 228 | const end = formatDateTime(endTime); |
| 229 | |
| 230 | // 只显示日期部分 |
| 231 | const startDate = start.split(' ')[0]; |
| 232 | const endDate = end.split(' ')[0]; |
| 233 | |
| 234 | return `${startDate} ~ ${endDate}`; |
| 235 | } catch (error) { |
| 236 | console.error('时间格式化错误:', error); |
| 237 | // 降级处理:如果是老格式,直接使用 |
| 238 | return `${startTime.split(' ')[0]} ~ ${endTime.split(' ')[0]}`; |
| 239 | } |
| 240 | }; |
| 241 | |
| 242 | // 排行榜相关代码已删除 |
| 243 | |
| 244 | return ( |
| 245 | <PageContainer title="活动中心"> |
| 246 | <Spin spinning={loading}> |
| 247 | <List |
| 248 | grid={{ gutter: 16, xs: 1, sm: 2, md: 2, lg: 3, xl: 3 }} |
| 249 | dataSource={activities} |
| 250 | locale={{ emptyText: <Empty description="暂无活动" /> }} |
| 251 | renderItem={(activity) => ( |
| 252 | <List.Item> |
| 253 | <Card |
| 254 | hoverable |
| 255 | actions={[ |
| 256 | <Button |
| 257 | key="participate" |
| 258 | type="primary" |
| 259 | disabled={activity.status === 0} |
| 260 | loading={ |
| 261 | participatingIds.has(activity.activityId) || |
| 262 | uploading || |
| 263 | downloading === activity.activityId |
| 264 | } |
| 265 | onClick={() => handleParticipate(activity.activityId)} |
| 266 | > |
| 267 | {activity.status === 1 ? '确认参与' : '已结束'} |
| 268 | </Button> |
| 269 | ]} |
| 270 | > |
| 271 | <Space direction="vertical" size={8} style={{ width: '100%' }}> |
| 272 | <Space style={{ width: '100%', justifyContent: 'space-between' }}> |
| 273 | {renderActivityIcon(activity)} |
| 274 | {renderStatusTag(activity.status)} |
| 275 | </Space> |
| 276 | |
| 277 | <Title level={5} style={{ margin: 0 }}> |
| 278 | {activity.activityName} |
| 279 | </Title> |
| 280 | |
| 281 | <Text type="secondary"> |
| 282 | {formatCondition(activity)} |
| 283 | </Text> |
| 284 | |
| 285 | <Space> |
| 286 | <GiftOutlined style={{ color: '#faad14' }} /> |
| 287 | <Text strong style={{ color: '#faad14' }}> |
| 288 | {activity.rewardBonus} 积分 |
| 289 | </Text> |
| 290 | </Space> |
| 291 | |
| 292 | <Space size={4} style={{ fontSize: 12 }}> |
| 293 | <ClockCircleOutlined /> |
| 294 | <Text type="secondary"> |
| 295 | {formatTimeRange(activity.startTime, activity.endTime)} |
| 296 | </Text> |
| 297 | </Space> |
| 298 | |
| 299 | {/* 操作提示 */} |
| 300 | {activity.status === 1 && ( |
| 301 | <Text type="secondary" style={{ fontSize: 12 }}> |
| 302 | {activity.activityType === 'UPLOAD' |
| 303 | ? '点击上传图标选择种子文件' |
| 304 | : '点击下载图标获取种子' |
| 305 | } |
| 306 | </Text> |
| 307 | )} |
| 308 | </Space> |
| 309 | </Card> |
| 310 | </List.Item> |
| 311 | )} |
| 312 | /> |
| 313 | </Spin> |
| 314 | </PageContainer> |
| 315 | ); |
| 316 | }; |
| 317 | |
| 318 | export default ActivityPage; |