blob: b881aeaa6d471f81b4f555cacca56ca2066a8a94 [file] [log] [blame]
Jiarenxiang36728482025-06-07 21:51:26 +08001import React, { useEffect, useState } from "react";
2import { styled } from "@mui/material/styles";
3import axios from "axios";
4import {
5 Box,
6 Button,
7 Card,
8 CardContent,
9 Chip,
10 CircularProgress,
11 Container,
12 MenuItem,
13 Pagination,
14 Select,
15 Typography,
16} from "@mui/material";
17import { getCategories, getTorrentList } from "../../services/bt/index";
18// 优化后的星空背景
19const StarBg = styled("div")({
20 minHeight: "100vh",
21 width: "auto",
22 background: "radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%)",
23 overflow: "auto",
24 position: "relative",
25 top: 0,
26 left: 0,
27
28
29
30 // 使用CSS动画实现星空
31 "&:before, &:after": {
32 content: '""',
33 position: "absolute",
34 top: 0,
35 left: 0,
36 width: "100%",
37 height: "100%",
38 pointerEvents: "none",
39 },
40 "&:before": {
41 background: `
42 radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,0.8), rgba(255,255,255,0)),
43 radial-gradient(1px 1px at 40% 70%, rgba(255,255,255,0.8), rgba(255,255,255,0)),
44 radial-gradient(1.5px 1.5px at 60% 20%, rgba(255,255,255,0.9), rgba(255,255,255,0)),
45 radial-gradient(1.5px 1.5px at 80% 90%, rgba(255,255,255,0.9), rgba(255,255,255,0))
46 `,
47 backgroundRepeat: "repeat",
48 backgroundSize: "200px 200px",
49 animation: "twinkle 10s infinite ease-in-out",
50 },
51 "&:after": {
52 background: `
53 radial-gradient(1px 1px at 70% 40%, rgba(255,255,255,0.7), rgba(255,255,255,0)),
54 radial-gradient(1.2px 1.2px at 10% 80%, rgba(255,255,255,0.7), rgba(255,255,255,0)),
55 radial-gradient(1.5px 1.5px at 30% 60%, rgba(255,255,255,0.8), rgba(255,255,255,0))
56 `,
57 backgroundRepeat: "repeat",
58 backgroundSize: "300px 300px",
59 animation: "twinkle 15s infinite 5s ease-in-out",
60 },
61
62 "@keyframes twinkle": {
63 "0%, 100%": { opacity: 0.3 },
64 "50%": { opacity: 0.8 },
65 }
66});
67
68// 分类标签栏
69const CategoryBar = styled(Box)({
70display: "flex",
71gap: 12,
72alignItems: "center",
73marginBottom: 24,
74flexWrap: "wrap",
75});
76
77// 种子卡片
78const TorrentCard = styled(Card)({
79background: "rgba(30, 41, 59, 0.85)",
80color: "#fff",
81borderRadius: 16,
82boxShadow: "0 4px 24px 0 rgba(0,0,0,0.4)",
83border: "1px solid #334155",
84transition: "transform 0.2s",
85"&:hover": {
86 transform: "scale(1.025)",
87 boxShadow: "0 8px 32px 0 #0ea5e9",
88},
89});
90
91const sortOptions = [
92{ label: "最新", value: { sortField: "createTime", sortDirection: "desc" } },
93{ label: "下载量", value: { sortField: "completions", sortDirection: "desc" } },
94{ label: "推荐", value: { sortField: "seeders", sortDirection: "desc" } },
95];
96
97const PAGE_SIZE = 20;
98
99const TorrentListPage: React.FC = () => {
100 const [categories, setCategories] = useState<
101 { id: number; name: string; remark?: string | null; type?: number | null }[]
102 >([]);
103 const [selectedCat, setSelectedCat] = useState<number | null>(null);
104 const [sort, setSort] = useState(sortOptions[0].value);
105 const [torrents, setTorrents] = useState<any[]>([]);
106 const [loading, setLoading] = useState(false);
107 const [page, setPage] = useState(1);
108 const [total, setTotal] = useState(0);
109
110 // 获取分类
111 useEffect(() => {
112 getCategories().then((res) => {
113 // 直接使用返回的data数组
114 setCategories(res.data || []);
115 });
116 }, []);
117
118 // 获取种子列表
119 useEffect(() => {
120 setLoading(true);
121 getTorrentList({
122 category: selectedCat !== null ? String(selectedCat) : undefined,
123 sortField: sort.sortField,
124 sortDirection: sort.sortDirection,
125 pageNum: page,
126 pageSize: PAGE_SIZE,
127 })
128 .then((res) => {
129 // 直接使用返回的data数组和page对象
130 setTorrents(res.data || []);
131 setTotal(res.page?.total || 0);
132 })
133 .finally(() => setLoading(false));
134 }, [selectedCat, sort, page]);
135
136return (
137 <StarBg>
138 <Container maxWidth="lg" sx={{ pt: 6, pb: 6, position: "relative" }}>
139 {/* 原有内容 */}
140 <Typography
141 variant="h3"
142 sx={{
143 color: "#fff",
144 fontWeight: 700,
145 mb: 3,
146 letterSpacing: 2,
147 textShadow: "0 2px 16px #0ea5e9",
148 }}
149 >
150 ThunderHub 星空PT种子广场
151 </Typography>
152 <CategoryBar>
153 <Chip
154 label="全部"
155 color={selectedCat === null ? "primary" : "default"}
156 onClick={() => {
157 setSelectedCat(null);
158 setPage(1);
159 }}
160 sx={{
161 fontWeight: 600,
162 fontSize: 16,
163 color: selectedCat === null ? undefined : "#fff",
164 }}
165 />
166 {categories.map((cat) => (
167 <Chip
168 key={cat.id}
169 label={cat.name}
170 color={selectedCat === cat.id ? "primary" : "default"}
171 onClick={() => {
172 setSelectedCat(cat.id);
173 setPage(1);
174 }}
175 sx={{
176 fontWeight: 600,
177 fontSize: 16,
178 color: selectedCat === cat.id ? undefined : "#fff",
179 }}
180 />
181 ))}
182 <Box sx={{ flex: 1 }} />
183 <Select
184 size="small"
185 value={JSON.stringify(sort)}
186 onChange={(e) => {
187 setSort(JSON.parse(e.target.value));
188 setPage(1);
189 }}
190 sx={{
191 color: "#fff",
192 background: "#1e293b",
193 borderRadius: 2,
194 ".MuiOutlinedInput-notchedOutline": { border: 0 },
195 minWidth: 120,
196 }}
197 >
198 {sortOptions.map((opt) => (
199 <MenuItem key={opt.label} value={JSON.stringify(opt.value)}>
200 {opt.label}
201 </MenuItem>
202 ))}
203 </Select>
204 </CategoryBar>
205 {loading ? (
206 <Box sx={{ display: "flex", justifyContent: "center", mt: 8 }}>
207 <CircularProgress color="info" />
208 </Box>
209 ) : (
210 <>
211 <Box
212 sx={{
213 display: "flex",
214 flexWrap: "wrap",
215 gap: 3,
216 justifyContent: { xs: "center", md: "flex-start" },
217 }}
218 >
219 {torrents.map((torrent) => (
220 <Box
221 key={torrent.id}
222 sx={{
223 flex: "1 1 320px",
224 maxWidth: { xs: "100%", sm: "48%", md: "32%" },
225 minWidth: 300,
226 mb: 3,
227 display: "flex",
228 cursor: "pointer",
229 }}
230 onClick={() => window.open(`/torrent-detail/${torrent.id}`, "_self")}
231 >
232 <TorrentCard sx={{ width: "100%" }}>
233 <CardContent>
234 <Typography
235 variant="h6"
236 sx={{
237 color: "#38bdf8",
238 fontWeight: 700,
239 mb: 1,
240 textOverflow: "ellipsis",
241 overflow: "hidden",
242 whiteSpace: "nowrap",
243 }}
244 title={torrent.title}
245 >
246 {torrent.title}
247 </Typography>
248 <Box
249 sx={{
250 display: "flex",
251 gap: 1,
252 alignItems: "center",
253 mb: 1,
254 flexWrap: "wrap",
255 }}
256 >
257 <Chip
258 size="small"
259 label={
260 categories.find((c) => c.id === torrent.category)?.name ||
261 "未知"
262 }
263 sx={{
264 background: "#0ea5e9",
265 color: "#fff",
266 fontWeight: 600,
267 }}
268 />
269 {torrent.free === "1" && (
270 <Chip
271 size="small"
272 label="促销"
273 sx={{
274 background: "#fbbf24",
275 color: "#1e293b",
276 fontWeight: 600,
277 }}
278 />
279 )}
280 </Box>
281 <Typography variant="body2" sx={{ color: "#cbd5e1", mb: 1 }}>
282 上传时间:{torrent.createTime}
283 </Typography>
284 <Box sx={{ display: "flex", gap: 2, mt: 1 }}>
285 <Typography variant="body2" sx={{ color: "#38bdf8" }}>
286 做种:{torrent.seeders}
287 </Typography>
288 <Typography variant="body2" sx={{ color: "#f472b6" }}>
289 下载量:{torrent.completions}
290 </Typography>
291 <Typography variant="body2" sx={{ color: "#fbbf24" }}>
292 下载中:{torrent.leechers}
293 </Typography>
294 </Box>
295 </CardContent>
296 </TorrentCard>
297 </Box>
298 ))}
299 </Box>
300 <Box sx={{ display: "flex", justifyContent: "center", mt: 4 }}>
301 <Pagination
302 count={Math.ceil(total / PAGE_SIZE)}
303 page={page}
304 onChange={(_, v) => setPage(v)}
305 color="primary"
306 sx={{
307 ".MuiPaginationItem-root": {
308 color: "#fff",
309 background: "#1e293b",
310 border: "1px solid #334155",
311 },
312 ".Mui-selected": {
313 background: "#0ea5e9 !important",
314 },
315 }}
316 />
317 </Box>
318 </>
319 )}
320 </Container>
321 </StarBg>
322);
323};
324
325export default TorrentListPage;