blob: e2f7f8eae3974a5af266a261d1481b57503de642 [file] [log] [blame]
ZBD7e88c222025-05-07 21:07:12 +08001import React, { useState, useEffect } from 'react';
2import {
ZBDff4d40a2025-05-27 17:05:20 +08003 Box, Container, Typography, Paper,
ZBD4b0e05a2025-06-08 18:11:26 +08004 List, ListItem, ListItemText, Chip, Stack, CircularProgress, Alert, Button
ZBD7e88c222025-05-07 21:07:12 +08005} from '@mui/material';
ZBDff4d40a2025-05-27 17:05:20 +08006import { Link } from 'react-router-dom'; // 假设你已经配置了 React Router
7
ZBDff4d40a2025-05-27 17:05:20 +08008// 模拟从API获取数据
9const fetchTorrents = () => {
10 return new Promise((resolve) => {
11 setTimeout(() => {
12 resolve([
13 {
14 id: 1,
15 title: 'Ubuntu ISO 镜像',
16 tags: ['Linux', '操作系统', 'ISO'],
17 size: '2.1 GB',
18 uploader: 'admin',
19 description: '最新的 Ubuntu Desktop 长期支持版。适用于开发者和普通用户。'
20 },
21 {
22 id: 2,
23 title: '开源电影素材包 - Blender "Sintel"',
24 tags: ['视频', '素材', '开源', 'Blender'],
25 size: '800 MB',
26 uploader: 'user001',
27 description: '来自 Blender Foundation 的开源电影 "Sintel" 的原始素材,可用于学习和创作。'
28 },
29 {
30 id: 3,
31 title: 'React 学习资料大全',
32 tags: ['React', 'JavaScript', '前端开发', '教程'],
33 size: '150 MB',
34 uploader: 'dev_guru',
35 description: '包含 React 官方文档、优秀教程链接和项目示例。'
36 }
37 ]);
38 }, 1500); // 模拟网络延迟
39 });
40};
41
ZBD7e88c222025-05-07 21:07:12 +080042
43function Home() {
44 const [torrents, setTorrents] = useState([]);
ZBDff4d40a2025-05-27 17:05:20 +080045 const [loading, setLoading] = useState(true);
46 const [error, setError] = useState(null);
ZBD7e88c222025-05-07 21:07:12 +080047
48 useEffect(() => {
ZBDff4d40a2025-05-27 17:05:20 +080049 setLoading(true);
50 fetchTorrents()
51 .then(data => {
52 setTorrents(data);
53 setLoading(false);
54 })
55 .catch(err => {
56 console.error("Failed to fetch torrents:", err);
57 setError("加载资源失败,请稍后再试。");
58 setLoading(false);
59 });
ZBD7e88c222025-05-07 21:07:12 +080060 }, []);
61
62 return (
ZBDff4d40a2025-05-27 17:05:20 +080063 <Box sx={{ minHeight: '100vh', py: 4, background: 'linear-gradient(135deg, #2c3e50, #4ca1af)', color: 'white' }}>
ZBD4b0e05a2025-06-08 18:11:26 +080064 <Container maxWidth="md" sx={{ position: 'relative', zIndex: 10 }}>
ZBDff4d40a2025-05-27 17:05:20 +080065 <Typography variant="h4" sx={{ textAlign: 'center', mb: 3, fontWeight: 'bold' }}>
66 🌐 首页 · Mini-Tracker
ZBD7e88c222025-05-07 21:07:12 +080067 </Typography>
68
ZBDff4d40a2025-05-27 17:05:20 +080069 <Paper sx={{ position: 'relative', zIndex: 20, padding: '2rem', backgroundColor: 'rgba(30, 30, 30, 0.9)', borderRadius: '12px' }}>
70 <Typography variant="h6" gutterBottom sx={{ mb: 2, color: '#eee' }}>
71 最新种子资源
72 </Typography>
73
74 {loading && (
75 <Box sx={{ display: 'flex', justifyContent: 'center', my: 3 }}>
76 <CircularProgress color="inherit" />
77 </Box>
78 )}
79
80 {error && (
81 <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>
82 )}
83
84 {!loading && !error && (
85 <List sx={{ '& .MuiListItem-root:hover': { backgroundColor: 'rgba(255, 255, 255, 0.05)' }}}>
86 {torrents.map(torrent => (
87 <ListItem
88 key={torrent.id}
89 component={Link} // react-router-dom Link
90 to={`/detail/${torrent.id}`} // 假设的详情页路由
ZBD4b0e05a2025-06-08 18:11:26 +080091 sx={{
92 color: 'white',
ZBDff4d40a2025-05-27 17:05:20 +080093 textDecoration: 'none',
ZBD4b0e05a2025-06-08 18:11:26 +080094 mb: 1.5,
95 p: 1.5,
ZBDff4d40a2025-05-27 17:05:20 +080096 borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
97 '&:last-child': {
ZBD4b0e05a2025-06-08 18:11:26 +080098 borderBottom: 'none'
ZBDff4d40a2025-05-27 17:05:20 +080099 },
ZBD4b0e05a2025-06-08 18:11:26 +0800100 display: 'flex',
101 flexDirection: { xs: 'column', sm: 'row' },
ZBDff4d40a2025-05-27 17:05:20 +0800102 alignItems: { xs: 'flex-start', sm: 'center' }
103 }}
ZBD4b0e05a2025-06-08 18:11:26 +0800104 divider={false}
ZBDff4d40a2025-05-27 17:05:20 +0800105 >
106 <ListItemText
107 primary={torrent.title}
108 secondary={`大小: ${torrent.size} · 上传者: ${torrent.uploader}`}
ZBD4b0e05a2025-06-08 18:11:26 +0800109 primaryTypographyProps={{
110 variant: 'h6',
111 component: 'div',
112 sx: { mb: 0.5, color: '#f5f5f5', fontWeight: 500 }
113 }}
ZBDff4d40a2025-05-27 17:05:20 +0800114 secondaryTypographyProps={{ sx: { color: '#bbb', fontSize: '0.85rem' } }}
ZBD4b0e05a2025-06-08 18:11:26 +0800115 sx={{ flexGrow: 1, mb: { xs: 1, sm: 0 }, mr: { sm: 2 } }}
ZBDff4d40a2025-05-27 17:05:20 +0800116 />
ZBD4b0e05a2025-06-08 18:11:26 +0800117
118 <Stack direction="row" spacing={1} sx={{ flexWrap: 'wrap', gap: 0.5 }}>
ZBDff4d40a2025-05-27 17:05:20 +0800119 {torrent.tags.map(tag => (
120 <Chip
121 key={tag}
122 label={tag}
123 size="small"
ZBD4b0e05a2025-06-08 18:11:26 +0800124 sx={{
125 backgroundColor: 'rgba(255, 255, 255, 0.2)',
ZBDff4d40a2025-05-27 17:05:20 +0800126 color: 'white',
127 cursor: 'pointer',
128 '&:hover': {
129 backgroundColor: 'rgba(255, 255, 255, 0.3)',
130 }
131 }}
132 />
ZBD7e88c222025-05-07 21:07:12 +0800133 ))}
ZBDff4d40a2025-05-27 17:05:20 +0800134 </Stack>
ZBD4b0e05a2025-06-08 18:11:26 +0800135
136 {/* ——— 在此处添加“下载”按钮 —— */}
137 <Button
138 variant="outlined"
139 component="a"
140 href={`http://localhost:8080/api/downloads/${torrent.id}/${encodeURIComponent(torrent.title)}.torrent`}
141 download={`${torrent.title}.torrent`}
142 sx={{
143 ml: { xs: 0, sm: 2 }, // 小屏幕时居中;大屏幕时与右边距留空
144 mt: { xs: 1, sm: 0 }, // 小屏时在下方,留一点间距
145 color: '#4caf50',
146 borderColor: '#4caf50',
147 fontSize: '0.85rem',
148 '&:hover': {
149 backgroundColor: 'rgba(76, 175, 80, 0.15)',
150 borderColor: '#388e3c'
151 }
152 }}
153 >
154 下载
155 </Button>
156 {/* ——— 下载按钮结束 ——— */}
157
ZBDff4d40a2025-05-27 17:05:20 +0800158 </ListItem>
ZBD7e88c222025-05-07 21:07:12 +0800159 ))}
ZBDff4d40a2025-05-27 17:05:20 +0800160 </List>
161 )}
ZBD7e88c222025-05-07 21:07:12 +0800162 </Paper>
163 </Container>
164
ZBD4b0e05a2025-06-08 18:11:26 +0800165 {/* 背景泡泡动画复用 */}
ZBDff4d40a2025-05-27 17:05:20 +0800166 <Box className="bubbles" sx={{
167 pointerEvents: 'none',
168 position: 'fixed',
169 top: 0,
170 left: 0,
171 width: '100%',
172 height: '100%',
173 overflow: 'hidden',
ZBD4b0e05a2025-06-08 18:11:26 +0800174 zIndex: 1,
ZBDff4d40a2025-05-27 17:05:20 +0800175 }}>
ZBD7e88c222025-05-07 21:07:12 +0800176 {[...Array(40)].map((_, i) => (
ZBDff4d40a2025-05-27 17:05:20 +0800177 <Box
178 key={i}
179 className="bubble"
180 sx={{
181 position: 'absolute',
ZBD4b0e05a2025-06-08 18:11:26 +0800182 bottom: '-150px',
183 background: `hsla(${Math.random() * 360}, 70%, 80%, 0.15)`,
ZBDff4d40a2025-05-27 17:05:20 +0800184 borderRadius: '50%',
185 animation: 'rise 20s infinite ease-in',
ZBD4b0e05a2025-06-08 18:11:26 +0800186 width: `${Math.random() * 25 + 10}px`,
ZBDff4d40a2025-05-27 17:05:20 +0800187 height: `${Math.random() * 25 + 10}px`,
188 left: `${Math.random() * 100}%`,
ZBD4b0e05a2025-06-08 18:11:26 +0800189 animationDuration: `${15 + Math.random() * 20}s`,
190 animationDelay: `${Math.random() * 10}s`,
191 opacity: 0,
ZBDff4d40a2025-05-27 17:05:20 +0800192 }}
193 />
ZBD7e88c222025-05-07 21:07:12 +0800194 ))}
ZBDff4d40a2025-05-27 17:05:20 +0800195 </Box>
ZBDff4d40a2025-05-27 17:05:20 +0800196 <style>
197 {`
ZBD4b0e05a2025-06-08 18:11:26 +0800198 body {
ZBDff4d40a2025-05-27 17:05:20 +0800199 margin: 0;
ZBD4b0e05a2025-06-08 18:11:26 +0800200 font-family: 'Roboto', sans-serif;
ZBDff4d40a2025-05-27 17:05:20 +0800201 }
ZBD7e88c222025-05-07 21:07:12 +0800202
ZBDff4d40a2025-05-27 17:05:20 +0800203 @keyframes rise {
204 0% {
205 transform: translateY(0) scale(0.8);
ZBD4b0e05a2025-06-08 18:11:26 +0800206 opacity: 0;
ZBDff4d40a2025-05-27 17:05:20 +0800207 }
208 10% {
ZBD4b0e05a2025-06-08 18:11:26 +0800209 opacity: 1;
ZBDff4d40a2025-05-27 17:05:20 +0800210 }
211 90% {
ZBD4b0e05a2025-06-08 18:11:26 +0800212 opacity: 1;
ZBDff4d40a2025-05-27 17:05:20 +0800213 }
214 100% {
ZBD4b0e05a2025-06-08 18:11:26 +0800215 transform: translateY(-130vh) scale(0.3);
216 opacity: 0;
ZBDff4d40a2025-05-27 17:05:20 +0800217 }
218 }
ZBDff4d40a2025-05-27 17:05:20 +0800219 `}
220 </style>
ZBD7e88c222025-05-07 21:07:12 +0800221 </Box>
222 );
223}
224
225export default Home;