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