更新登录
Change-Id: I0b2663f4291f573625222995b09a99ac53c2a081
diff --git a/src/pages/Torrent/torrentAudit.tsx b/src/pages/Torrent/torrentAudit.tsx
index e90af3c..3e6a45b 100644
--- a/src/pages/Torrent/torrentAudit.tsx
+++ b/src/pages/Torrent/torrentAudit.tsx
@@ -2,7 +2,7 @@
import { useParams,useSearchParams, useNavigate } from 'react-router-dom';
import { Card, Button, Spin, Tag, message, Input } from 'antd';
import { ArrowLeftOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons';
-import { getTorrentInfo,auditTorrent,downloadTorrent } from '../../services/bt/index';
+import { getTorrentInfo,auditTorrent,downloadTorrent,getCategories } from '../../services/bt/index';
// 状态映射
@@ -17,7 +17,7 @@
const TorrentAudit: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
-
+ const [categories, setCategories] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [torrent, setTorrent] = useState<any>(null);
const [auditLoading, setAuditLoading] = useState(false);
@@ -33,6 +33,7 @@
getTorrentInfo({ id })
.then(res => setTorrent(res.data))
.finally(() => setLoading(false));
+ getCategories().then((res) => setCategories(res.data || []));
}, [id, navigate]);
const handleDownload = async () => {
try {
@@ -68,7 +69,10 @@
setAuditLoading(false);
}
};
-
+const getCategoryName = (catId: number) => {
+ console.log('categories', catId, categories);
+ return categories.find((c) => c.id === catId)?.name || '未知分类';
+};
return (
<div style={{ minHeight: '100vh', background: '#f4f8ff', padding: 32 }}>
<div style={{ maxWidth: 800, margin: '0 auto' }}>
@@ -101,7 +105,7 @@
<>
<h2>{torrent.title}</h2>
<div style={{ marginBottom: 8 }}>
- <Tag color="blue">{torrent.categoryName || '未知分类'}</Tag>
+ <Tag color="blue">{getCategoryName(torrent.category) || '未知分类'}</Tag>
{torrent.tags?.map((tag: string) => (
<Tag key={tag}>{tag}</Tag>
))}
@@ -132,6 +136,7 @@
borderRadius: 8,
marginBottom: 24,
minHeight: 80,
+ color: '#333',
}}
dangerouslySetInnerHTML={{ __html: torrent.description || '' }}
/>
diff --git a/src/pages/Torrent/torrentDetail.tsx b/src/pages/Torrent/torrentDetail.tsx
index 7e194a9..e27be12 100644
--- a/src/pages/Torrent/torrentDetail.tsx
+++ b/src/pages/Torrent/torrentDetail.tsx
@@ -285,6 +285,17 @@
<div style={{ color: '#cbd5e1', marginBottom: 8 }}>
上传者:{torrent?.owner} | 上传时间:{torrent?.createdAt}
</div>
+ <div
+ style={{
+ background: '#f6f8fa',
+ padding: 16,
+ borderRadius: 8,
+ marginBottom: 24,
+ minHeight: 80,
+ color: '#333',
+ }}
+ dangerouslySetInnerHTML={{ __html: torrent.description || '' }}
+ />
<Button
type="primary"
icon={<DownloadOutlined />}
diff --git a/src/pages/Torrent/torrentListMy.tsx b/src/pages/Torrent/torrentListMy.tsx
new file mode 100644
index 0000000..2f336ac
--- /dev/null
+++ b/src/pages/Torrent/torrentListMy.tsx
@@ -0,0 +1,505 @@
+import React, { useEffect, useState } from "react";
+import { styled } from "@mui/material/styles";
+import axios from "axios";
+import {
+ Box,
+ Button,
+ Card,
+ CardContent,
+ Chip,
+ CircularProgress,
+ Container,
+ MenuItem,
+ Pagination,
+ Select,
+ Typography,
+ TextField,
+ InputAdornment,
+ IconButton,
+} from "@mui/material";
+import { getCategories, getMyTorrentList,searchTorrents } from "../../services/bt/index";
+
+// 优化后的星空背景
+const StarBg = styled("div")({
+ minHeight: "100vh",
+ width: "auto",
+ background: "radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%)",
+ overflow: "auto",
+ position: "relative",
+ top: 0,
+ left: 0,
+ "&:before, &:after": {
+ content: '""',
+ position: "absolute",
+ top: 0,
+ left: 0,
+ width: "100%",
+ height: "100%",
+ pointerEvents: "none",
+ },
+ "&:before": {
+ background: `
+ radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,0.8), rgba(255,255,255,0)),
+ radial-gradient(1px 1px at 40% 70%, rgba(255,255,255,0.8), rgba(255,255,255,0)),
+ radial-gradient(1.5px 1.5px at 60% 20%, rgba(255,255,255,0.9), rgba(255,255,255,0)),
+ radial-gradient(1.5px 1.5px at 80% 90%, rgba(255,255,255,0.9), rgba(255,255,255,0))
+ `,
+ backgroundRepeat: "repeat",
+ backgroundSize: "200px 200px",
+ animation: "twinkle 10s infinite ease-in-out",
+ },
+ "&:after": {
+ background: `
+ radial-gradient(1px 1px at 70% 40%, rgba(255,255,255,0.7), rgba(255,255,255,0)),
+ radial-gradient(1.2px 1.2px at 10% 80%, rgba(255,255,255,0.7), rgba(255,255,255,0)),
+ radial-gradient(1.5px 1.5px at 30% 60%, rgba(255,255,255,0.8), rgba(255,255,255,0))
+ `,
+ backgroundRepeat: "repeat",
+ backgroundSize: "300px 300px",
+ animation: "twinkle 15s infinite 5s ease-in-out",
+ },
+ "@keyframes twinkle": {
+ "0%, 100%": { opacity: 0.3 },
+ "50%": { opacity: 0.8 },
+ }
+});
+
+// 分类标签栏
+const CategoryBar = styled(Box)({
+ display: "flex",
+ gap: 12,
+ alignItems: "center",
+ marginBottom: 24,
+ flexWrap: "wrap",
+});
+
+// 种子卡片
+const TorrentCard = styled(Card)({
+ background: "rgba(30, 41, 59, 0.85)",
+ color: "#fff",
+ borderRadius: 16,
+ boxShadow: "0 4px 24px 0 rgba(0,0,0,0.4)",
+ border: "1px solid #334155",
+ transition: "transform 0.2s",
+ "&:hover": {
+ transform: "scale(1.025)",
+ boxShadow: "0 8px 32px 0 #0ea5e9",
+ },
+});
+
+const sortOptions = [
+ { label: "最新", value: { sortField: "create_time", sortDirection: "desc" } },
+ { label: "下载量", value: { sortField: "completions", sortDirection: "desc" } },
+ { label: "推荐", value: { sortField: "seeders", sortDirection: "desc" } },
+];
+
+const statusMap: Record<number, string> = {
+ 0: '审核中',
+ 1: '已发布',
+ 2: '审核不通过',
+ 3: '已上架修改重审中',
+ 10: '已下架',
+};
+
+const PAGE_SIZE = 20;
+
+const TorrentListPage: React.FC = () => {
+ const [categories, setCategories] = useState<
+ { id: number; name: string; remark?: string | null; type?: number | null }[]
+ >([]);
+ const [selectedCat, setSelectedCat] = useState<number | null>(null);
+ const [sort, setSort] = useState(sortOptions[0].value);
+ const [torrents, setTorrents] = useState<any[]>([]);
+ const [loading, setLoading] = useState(false);
+ const [page, setPage] = useState(1);
+ const [total, setTotal] = useState(0);
+
+ // 搜索相关
+ const [searchKeyword, setSearchKeyword] = useState("");
+ const [searching, setSearching] = useState(false);
+ const [isSearchMode, setIsSearchMode] = useState(false);
+
+ // 获取分类
+ useEffect(() => {
+ getCategories().then((res) => {
+ setCategories(res.data || []);
+ });
+ }, []);
+
+ // 获取种子列表
+ useEffect(() => {
+ if (isSearchMode) return;
+ setLoading(true);
+ getMyTorrentList()
+ .then((res) => {
+ setTorrents(res.datarow || []);
+ setTotal(res.data.page?.size || 0);
+ })
+ .finally(() => setLoading(false));
+ }, [selectedCat, sort, page, isSearchMode]);
+
+ // 搜索
+ const handleSearch = async () => {
+ if (!searchKeyword.trim()) {
+ setIsSearchMode(false);
+ setPage(1);
+ return;
+ }
+ setSearching(true);
+ setIsSearchMode(true);
+ try {
+ const res = await searchTorrents({
+ titleKeyword: searchKeyword.trim(),
+ category: selectedCat !== null ? selectedCat : undefined,
+ sortField: sort.sortField,
+ sortDirection: sort.sortDirection,
+ pageNum: page,
+ pageSize: PAGE_SIZE,
+ });
+ setTorrents((res.records || []).map((r: any) => ({
+ ...r.torrent,
+ ownerInfo: r.ownerInfo,
+ })));
+ setTotal(res.total || 0);
+ } finally {
+ setSearching(false);
+ }
+ };
+
+ // 搜索模式下翻页
+ useEffect(() => {
+ if (!isSearchMode) return;
+ handleSearch();
+ // eslint-disable-next-line
+ }, [page, sort, selectedCat]);
+
+ // 切换分类/排序时退出搜索模式
+ useEffect(() => {
+ setIsSearchMode(false);
+ setSearchKeyword("");
+ setPage(1);
+ }, [selectedCat, sort]);
+
+ return (
+ <StarBg>
+ <Container maxWidth="lg" sx={{ pt: 6, pb: 6, position: "relative" }}>
+ <Typography
+ variant="h3"
+ sx={{
+ color: "#fff",
+ fontWeight: 700,
+ mb: 3,
+ letterSpacing: 2,
+ textShadow: "0 2px 16px #0ea5e9",
+ }}
+ >
+ 我的种子
+ </Typography>
+ 搜索框
+ <Box
+ sx={{
+ mb: 4,
+ display: "flex",
+ alignItems: "center",
+ gap: 2,
+ background: "rgba(30,41,59,0.7)",
+ borderRadius: 3,
+ px: 2,
+ py: 1.5,
+ boxShadow: "0 2px 12px 0 #0ea5e950",
+ border: "1px solid #334155",
+ maxWidth: 700,
+ mx: { xs: "auto", md: 0 },
+ }}
+ >
+ <TextField
+ variant="outlined"
+ size="small"
+ placeholder="🔍 搜索种子标题"
+ value={searchKeyword}
+ onChange={(e) => setSearchKeyword(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ setPage(1);
+ handleSearch();
+ }
+ }}
+ sx={{
+ background: "rgba(17,24,39,0.95)",
+ borderRadius: 2,
+ input: {
+ color: "#fff",
+ fontSize: 17,
+ fontWeight: 500,
+ letterSpacing: 1,
+ px: 1,
+ },
+ width: 320,
+ ".MuiOutlinedInput-notchedOutline": { border: 0 },
+ boxShadow: "0 1px 4px 0 #0ea5e930",
+ }}
+ InputProps={{
+ endAdornment: (
+ <InputAdornment position="end">
+ <IconButton
+ onClick={() => {
+ setPage(1);
+ handleSearch();
+ }}
+ edge="end"
+ sx={{
+ color: "#0ea5e9",
+ bgcolor: "#1e293b",
+ borderRadius: 2,
+ "&:hover": { bgcolor: "#0ea5e9", color: "#fff" },
+ transition: "all 0.2s",
+ }}
+ >
+ <svg width="22" height="22" fill="none" viewBox="0 0 24 24">
+ <circle cx="11" cy="11" r="7" stroke="currentColor" strokeWidth="2"/>
+ <path stroke="currentColor" strokeWidth="2" strokeLinecap="round" d="M20 20l-3.5-3.5"/>
+ </svg>
+ </IconButton>
+ </InputAdornment>
+ ),
+ }}
+ />
+ {isSearchMode && (
+ <Button
+ variant="outlined"
+ color="secondary"
+ onClick={() => {
+ setIsSearchMode(false);
+ setSearchKeyword("");
+ setPage(1);
+ }}
+ sx={{
+ ml: 1,
+ color: "#fff",
+ borderColor: "#64748b",
+ borderRadius: 2,
+ fontWeight: 600,
+ letterSpacing: 1,
+ px: 2,
+ py: 0.5,
+ background: "rgba(51,65,85,0.7)",
+ "&:hover": {
+ borderColor: "#0ea5e9",
+ background: "#0ea5e9",
+ color: "#fff",
+ },
+ transition: "all 0.2s",
+ }}
+ >
+ 清除搜索
+ </Button>
+ )}
+ <Box sx={{ flex: 1 }} />
+ <Button
+ variant="contained"
+ color="primary"
+ onClick={() => window.open("/torrent-upload", "_self")}
+ sx={{
+ borderRadius: 2,
+ fontWeight: 700,
+ letterSpacing: 1,
+ px: 3,
+ py: 1,
+ boxShadow: "0 2px 8px 0 #0ea5e950",
+ background: "#0ea5e9",
+ color: "#fff",
+ "&:hover": {
+ background: "#38bdf8",
+ },
+ transition: "all 0.2s",
+ }}
+ >
+ 分享资源
+ </Button>
+ </Box>
+ <CategoryBar>
+ <Chip
+ label="全部"
+ color={selectedCat === null ? "primary" : "default"}
+ onClick={() => {
+ setSelectedCat(null);
+ setPage(1);
+ }}
+ sx={{
+ fontWeight: 600,
+ fontSize: 16,
+ color: selectedCat === null ? undefined : "#fff",
+ }}
+ />
+ {categories.map((cat) => (
+ <Chip
+ key={cat.id}
+ label={cat.name}
+ color={selectedCat === cat.id ? "primary" : "default"}
+ onClick={() => {
+ setSelectedCat(cat.id);
+ setPage(1);
+ }}
+ sx={{
+ fontWeight: 600,
+ fontSize: 16,
+ color: selectedCat === cat.id ? undefined : "#fff",
+ }}
+ />
+ ))}
+ <Box sx={{ flex: 1 }} />
+ <Select
+ size="small"
+ value={JSON.stringify(sort)}
+ onChange={(e) => {
+ setSort(JSON.parse(e.target.value));
+ setPage(1);
+ }}
+ sx={{
+ color: "#fff",
+ background: "#1e293b",
+ borderRadius: 2,
+ ".MuiOutlinedInput-notchedOutline": { border: 0 },
+ minWidth: 120,
+ }}
+ >
+ {sortOptions.map((opt) => (
+ <MenuItem key={opt.label} value={JSON.stringify(opt.value)}>
+ {opt.label}
+ </MenuItem>
+ ))}
+ </Select>
+ </CategoryBar>
+ {(loading || searching) ? (
+ <Box sx={{ display: "flex", justifyContent: "center", mt: 8 }}>
+ <CircularProgress color="info" />
+ </Box>
+ ) : (
+ <>
+ <Box
+ sx={{
+ display: "flex",
+ flexWrap: "wrap",
+ gap: 3,
+ justifyContent: { xs: "center", md: "flex-start" },
+ }}
+ >
+ {torrents.map((torrent) => (
+ <Box
+ key={torrent.id}
+ sx={{
+ flex: "1 1 320px",
+ maxWidth: { xs: "100%", sm: "48%", md: "32%" },
+ minWidth: 300,
+ mb: 3,
+ display: "flex",
+ cursor: "pointer",
+ }}
+ onClick={() => window.open(`/torrent-detail/${torrent.id}`, "_self")}
+ >
+ <TorrentCard sx={{ width: "100%" }}>
+ <CardContent>
+ <Typography
+ variant="h6"
+ sx={{
+ color: "#38bdf8",
+ fontWeight: 700,
+ mb: 1,
+ textOverflow: "ellipsis",
+ overflow: "hidden",
+ whiteSpace: "nowrap",
+ }}
+ title={torrent.title}
+ >
+ {torrent.title}
+ </Typography>
+ <Box
+ sx={{
+ display: "flex",
+ gap: 1,
+ alignItems: "center",
+ mb: 1,
+ flexWrap: "wrap",
+ }}
+ >
+ <Chip
+ size="small"
+ label={
+ categories.find((c) => c.id === torrent.category)?.name ||
+ "未知"
+ }
+ sx={{
+ background: "#0ea5e9",
+ color: "#fff",
+ fontWeight: 600,
+ }}
+ />
+ {torrent.free === "1" && (
+ <Chip
+ size="small"
+ label="促销"
+ sx={{
+ background: "#fbbf24",
+ color: "#1e293b",
+ fontWeight: 600,
+ }}
+ />
+ )}
+ {/* 状态标签 */}
+ {typeof torrent.status !== "undefined" && (
+ <Chip
+ size="small"
+ label={statusMap[torrent.status] || "未知状态"}
+ sx={{
+ background: "#64748b",
+ color: "#fff",
+ fontWeight: 600,
+ }}
+ />
+ )}
+ </Box>
+ <Typography variant="body2" sx={{ color: "#cbd5e1", mb: 1 }}>
+ 上传时间:{torrent.createTime}
+ </Typography>
+ <Box sx={{ display: "flex", gap: 2, mt: 1 }}>
+ <Typography variant="body2" sx={{ color: "#38bdf8" }}>
+ 做种:{torrent.seeders}
+ </Typography>
+ <Typography variant="body2" sx={{ color: "#f472b6" }}>
+ 下载量:{torrent.completions}
+ </Typography>
+ <Typography variant="body2" sx={{ color: "#fbbf24" }}>
+ 下载中:{torrent.leechers}
+ </Typography>
+ </Box>
+ </CardContent>
+ </TorrentCard>
+ </Box>
+ ))}
+ </Box>
+ <Box sx={{ display: "flex", justifyContent: "center", mt: 4 }}>
+ <Pagination
+ count={Math.ceil(total / PAGE_SIZE)}
+ page={page}
+ onChange={(_, v) => setPage(v)}
+ color="primary"
+ sx={{
+ ".MuiPaginationItem-root": {
+ color: "#fff",
+ background: "#1e293b",
+ border: "1px solid #334155",
+ },
+ ".Mui-selected": {
+ background: "#0ea5e9 !important",
+ },
+ }}
+ />
+ </Box>
+ </>
+ )}
+ </Container>
+ </StarBg>
+ );
+};
+
+export default TorrentListPage;
\ No newline at end of file