reset the rebase code
Change-Id: I50bb95e75cd5679667fb6e4053e0d1e60e28f6a5
diff --git a/src/pages/Torrent/torrentDetail.tsx b/src/pages/Torrent/torrentDetail.tsx
new file mode 100644
index 0000000..d841b04
--- /dev/null
+++ b/src/pages/Torrent/torrentDetail.tsx
@@ -0,0 +1,334 @@
+import React, { useEffect, useState } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { Button, Input, message, Spin, Tag, Card, Avatar, List, Pagination } from 'antd';
+import { DownloadOutlined, ArrowLeftOutlined, SendOutlined } from '@ant-design/icons';
+import {
+ getTorrentInfo,
+ downloadTorrent,
+ addComment,
+ getComments,
+ getCategories,
+} from '../../services/bt/index';
+
+const PAGE_SIZE = 10;
+
+const TorrentDetail: React.FC = () => {
+const { id } = useParams<{ id: string }>();
+const navigate = useNavigate();
+
+const [loading, setLoading] = useState(true);
+const [torrent, setTorrent] = useState<any>(null);
+const [categories, setCategories] = useState<any[]>([]);
+const [comments, setComments] = useState<any[]>([]);
+const [commentLoading, setCommentLoading] = useState(false);
+const [comment, setComment] = useState('');
+const [commentsTotal, setCommentsTotal] = useState(0);
+const [pageNum, setPageNum] = useState(1);
+
+useEffect(() => {
+ setLoading(true);
+ getTorrentInfo({ id: id! })
+ .then((res) => setTorrent(res.data))
+ .finally(() => setLoading(false));
+ getCategories().then((res) => setCategories(res.data || []));
+}, [id]);
+
+useEffect(() => {
+ fetchComments(pageNum);
+ // eslint-disable-next-line
+}, [id, pageNum]);
+
+const fetchComments = (page: number) => {
+ setCommentLoading(true);
+ getComments({ torrentId: Number(id), pageNum: page, pageSize: PAGE_SIZE })
+ .then((res) => {0
+ setComments(res.data?.list || []);
+ setCommentsTotal(res.data?.total || 0);
+ })
+ .finally(() => setCommentLoading(false));
+};
+
+const handleDownload = async () => {
+ try {
+ const res = await downloadTorrent({ id: id! });
+ const blob = new Blob([res], { type: 'application/x-bittorrent' });
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `${torrent?.title || 'torrent'}.torrent`;
+ a.click();
+ window.URL.revokeObjectURL(url);
+ } catch {
+ message.error('下载失败');
+ }
+};
+
+const handleAddComment = async () => {
+ if (!comment.trim()) return;
+ await addComment({ torrentId: Number(id), comment });
+ setComment('');
+ fetchComments(1);
+ setPageNum(1);
+ message.success('评论成功');
+};
+
+const getCategoryName = (catId: number) => {
+ return categories.find((c) => c.id === catId)?.name || '未知分类';
+};
+
+const statusMap: Record<number, string> = {
+ 0: '候选中',
+ 1: '已发布',
+ 2: '审核不通过',
+ 3: '已上架修改重审中',
+ 10: '已下架',
+};
+
+// 星球主题样式
+const planetBg = {
+ background: 'radial-gradient(circle at 60% 40%, #2b6cb0 0%, #1a202c 100%)',
+ minHeight: '100vh',
+ padding: '32px 0',
+};
+
+return (
+ <div style={planetBg}>
+ <div style={{ maxWidth: 900, margin: '0 auto', background: 'rgba(255,255,255,0.08)', borderRadius: 16, boxShadow: '0 8px 32px rgba(0,0,0,0.2)', padding: 24 }}>
+ <Button
+ icon={<ArrowLeftOutlined />}
+ type="link"
+ onClick={() => navigate(-1)}
+ style={{ color: '#fff', marginBottom: 16 }}
+ >
+ 返回
+ </Button>
+ {loading ? (
+ <Spin size="large" />
+ ) : (
+ <>
+ {torrent?.status !== 1 ? (
+ <div style={{ color: '#fff', fontSize: 24, textAlign: 'center', padding: '80px 0' }}>
+ 当前状态:{statusMap[torrent?.status] || '未知状态'}
+ </div>
+ ) : (
+ <>
+ <div style={{ display: 'flex', alignItems: 'center', gap: 24 }}>
+ <Avatar
+ size={96}
+ src={torrent?.cover || 'https://img.icons8.com/color/96/planet.png'}
+ style={{ boxShadow: '0 0 24px #4299e1' }}
+ />
+ <div>
+ <h1 style={{ color: '#fff', fontSize: 32, marginBottom: 8 }}>
+ {torrent?.title}
+ </h1>
+ <div style={{ marginBottom: 8 }}>
+ <Tag color="geekblue">{getCategoryName(torrent?.categoryId)}</Tag>
+ {torrent?.tags?.map((tag: string) => (
+ <Tag key={tag} color="blue">{tag}</Tag>
+ ))}
+ </div>
+ <div style={{ color: '#cbd5e1', marginBottom: 8 }}>
+ 上传者:{torrent?.owner} | 上传时间:{torrent?.createdAt}
+ </div>
+ <Button
+ type="primary"
+ icon={<DownloadOutlined />}
+ onClick={handleDownload}
+ style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none' }}
+ >
+ 下载种子
+ </Button>
+ </div>
+ </div>
+ <Card
+ style={{
+ marginTop: 32,
+ background: 'rgba(255,255,255,0.12)',
+ border: 'none',
+ borderRadius: 12,
+ color: '#fff',
+ }}
+ title={<span style={{ color: '#fff' }}>种子详情</span>}
+ >
+ <div dangerouslySetInnerHTML={{ __html: torrent?.description || '' }} />
+ <div style={{ marginTop: 16, color: '#cbd5e1' }}>
+ <span>文件大小:{torrent?.size}</span>
+ <span style={{ marginLeft: 24 }}>做种人数:{torrent?.seeders}</span>
+ <span style={{ marginLeft: 24 }}>下载人数:{torrent?.leechers}</span>
+ <span style={{ marginLeft: 24 }}>完成次数:{torrent?.completed}</span>
+ </div>
+ </Card>
+ <Card
+ style={{
+ marginTop: 32,
+ background: 'rgba(255,255,255,0.10)',
+ border: 'none',
+ borderRadius: 12,
+ color: '#fff',
+ }}
+ title={<span style={{ color: '#fff' }}>星球评论</span>}
+ >
+ <List
+ loading={commentLoading}
+ dataSource={comments}
+ locale={{ emptyText: '暂无评论' }}
+ renderItem={(item: any) => {
+ // 只渲染顶级评论(pid为null)
+ if (item.pid !== null) return null;
+ const [expanded, setExpanded] = useState(false);
+ const [replyContent, setReplyContent] = useState('');
+ const [replying, setReplying] = useState(false);
+
+ const handleReply = async () => {
+ if (!replyContent.trim()) return;
+ setReplying(true);
+ await addComment({ torrentId: Number(id), comment: replyContent, pid: item.id });
+ setReplyContent('');
+ setReplying(false);
+ fetchComments(pageNum);
+ message.success('回复成功');
+ };
+
+ return (
+ <List.Item
+ style={{
+ alignItems: 'flex-start',
+ border: 'none',
+ background: 'transparent',
+ padding: '20px 0',
+ borderBottom: '1px solid rgba(255,255,255,0.08)',
+ }}
+ >
+ <List.Item.Meta
+ avatar={
+ <Avatar
+ src={item.avatar ? item.avatar.startsWith('http') ? item.avatar : `${item.avatar}` : 'https://img.icons8.com/color/48/planet.png'}
+ size={48}
+ style={{ boxShadow: '0 2px 8px #4299e1' }}
+ />
+ }
+ title={
+ <span style={{ color: '#fff', fontWeight: 500 }}>
+ {item.username || '匿名用户'}
+ </span>
+ }
+ description={
+ <span style={{ color: '#cbd5e1', fontSize: 16 }}>
+ {item.comment}
+ <span style={{ marginLeft: 16, fontSize: 12, color: '#a0aec0' }}>
+ {item.createTime}
+ </span>
+ <Button
+ type="link"
+ size="small"
+ style={{ marginLeft: 16, color: '#4299e1' }}
+ onClick={() => setExpanded((v) => !v)}
+ >
+ {expanded ? '收起回复' : `展开回复${item.children?.length ? ` (${item.children.length})` : ''}`}
+ </Button>
+ </span>
+ }
+ />
+ {expanded && (
+ <div style={{ width: '100%', marginTop: 12, marginLeft: 56 }}>
+ <List
+ dataSource={item.children}
+ itemLayout="horizontal"
+ locale={{ emptyText: '暂无子评论' }}
+ renderItem={(child: any) => (
+ <List.Item
+ style={{
+ border: 'none',
+ background: 'transparent',
+ padding: '12px 0 0 0',
+ marginLeft: 0,
+ }}
+ >
+ <List.Item.Meta
+ avatar={
+ <Avatar
+ src={child.avatar ? child.avatar.startsWith('http') ? child.avatar : `${child.avatar}` : 'https://img.icons8.com/color/48/planet.png'}
+ size={36}
+ style={{ boxShadow: '0 1px 4px #805ad5' }}
+ />
+ }
+ title={
+ <span style={{ color: '#c3bfff', fontWeight: 500, fontSize: 15 }}>
+ {child.username || '匿名用户'}
+ </span>
+ }
+ description={
+ <span style={{ color: '#e0e7ef', fontSize: 15 }}>
+ {child.comment}
+ <span style={{ marginLeft: 12, fontSize: 12, color: '#a0aec0' }}>
+ {child.createTime}
+ </span>
+ </span>
+ }
+ />
+ </List.Item>
+ )}
+ />
+ <div style={{ display: 'flex', marginTop: 12, gap: 8 }}>
+ <Input.TextArea
+ value={replyContent}
+ onChange={e => setReplyContent(e.target.value)}
+ placeholder="回复该评论"
+ autoSize={{ minRows: 1, maxRows: 3 }}
+ style={{ background: 'rgba(255,255,255,0.15)', color: '#fff', border: 'none' }}
+ />
+ <Button
+ type="primary"
+ icon={<SendOutlined />}
+ loading={replying}
+ onClick={handleReply}
+ disabled={!replyContent.trim()}
+ style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none', height: 40 }}
+ >
+ 发送
+ </Button>
+ </div>
+ </div>
+ )}
+ </List.Item>
+ );
+ }}
+ />
+ <Pagination
+ style={{ marginTop: 16, textAlign: 'right' }}
+ current={pageNum}
+ pageSize={PAGE_SIZE}
+ total={commentsTotal}
+ onChange={setPageNum}
+ showSizeChanger={false}
+ />
+ <div style={{ display: 'flex', marginTop: 24, gap: 8 }}>
+ <Input.TextArea
+ value={comment}
+ onChange={e => setComment(e.target.value)}
+ placeholder="在星球上留下你的评论吧~"
+ autoSize={{ minRows: 2, maxRows: 4 }}
+ style={{ background: 'rgba(255,255,255,0.15)', color: '#fff', border: 'none' }}
+ />
+ <Button
+ type="primary"
+ icon={<SendOutlined />}
+ onClick={handleAddComment}
+ disabled={!comment.trim()}
+ style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none', height: 48 }}
+ >
+ 发送
+ </Button>
+ </div>
+ </Card>
+ </>
+ )}
+ </>
+ )}
+ </div>
+ </div>
+);
+};
+
+export default TorrentDetail;
\ No newline at end of file
diff --git a/src/pages/Torrent/torrentList.tsx b/src/pages/Torrent/torrentList.tsx
new file mode 100644
index 0000000..b881aea
--- /dev/null
+++ b/src/pages/Torrent/torrentList.tsx
@@ -0,0 +1,325 @@
+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,
+} from "@mui/material";
+import { getCategories, getTorrentList } 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,
+
+
+
+ // 使用CSS动画实现星空
+ "&: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: "createTime", sortDirection: "desc" } },
+{ label: "下载量", value: { sortField: "completions", sortDirection: "desc" } },
+{ label: "推荐", value: { sortField: "seeders", sortDirection: "desc" } },
+];
+
+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);
+
+ // 获取分类
+ useEffect(() => {
+ getCategories().then((res) => {
+ // 直接使用返回的data数组
+ setCategories(res.data || []);
+ });
+ }, []);
+
+ // 获取种子列表
+ useEffect(() => {
+ setLoading(true);
+ getTorrentList({
+ category: selectedCat !== null ? String(selectedCat) : undefined,
+ sortField: sort.sortField,
+ sortDirection: sort.sortDirection,
+ pageNum: page,
+ pageSize: PAGE_SIZE,
+ })
+ .then((res) => {
+ // 直接使用返回的data数组和page对象
+ setTorrents(res.data || []);
+ setTotal(res.page?.total || 0);
+ })
+ .finally(() => setLoading(false));
+ }, [selectedCat, sort, page]);
+
+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",
+ }}
+ >
+ ThunderHub 星空PT种子广场
+ </Typography>
+ <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 ? (
+ <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,
+ }}
+ />
+ )}
+ </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
diff --git a/src/pages/Torrent/torrentUpload.tsx b/src/pages/Torrent/torrentUpload.tsx
new file mode 100644
index 0000000..6cb5a41
--- /dev/null
+++ b/src/pages/Torrent/torrentUpload.tsx
@@ -0,0 +1,379 @@
+import React, { useEffect, useState } from 'react';
+import { PlusOutlined, UploadOutlined } from '@ant-design/icons';
+import {
+Form,
+Input,
+Button,
+Select,
+Upload,
+message,
+Tag,
+Space,
+Typography,
+Modal,
+} from 'antd';
+import{
+ getCategories,
+ addTorrent,
+ uploadTorrentFile
+} from '../../services/bt/index';
+
+const { Option } = Select;
+const { TextArea } = Input;
+const { Title } = Typography;
+
+const TorrentUpload: React.FC = () => {
+const [categories, setCategories] = useState<{ id: number; name: string }[]>([]);
+const [tagOptions, setTagOptions] = useState<string[]>([]);
+const [customTags, setCustomTags] = useState<string[]>([]);
+const [fileList, setFileList] = useState<any[]>([]);
+const [uploading, setUploading] = useState(false);
+const [form] = Form.useForm();
+const [tagInputVisible, setTagInputVisible] = useState(false);
+const [tagInputValue, setTagInputValue] = useState('');
+
+useEffect(() => {
+ getCategories().then((res) => {
+ if (Array.isArray(res.data)) {
+ setCategories(res.data);
+ setTagOptions(res.data.map((cat: { name: string }) => cat.name));
+ }
+ });
+}, []);
+
+const handleTagClose = (removedTag: string) => {
+ setCustomTags(customTags.filter(tag => tag !== removedTag));
+};
+
+const showTagInput = () => setTagInputVisible(true);
+
+const handleTagInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ setTagInputValue(e.target.value);
+};
+
+const handleTagInputConfirm = () => {
+ if (
+ tagInputValue &&
+ !customTags.includes(tagInputValue) &&
+ !tagOptions.includes(tagInputValue)
+ ) {
+ setCustomTags([...customTags, tagInputValue]);
+ }
+ setTagInputVisible(false);
+ setTagInputValue('');
+};
+
+const beforeUpload = (file: File) => {
+ const isTorrent = file.name.endsWith('.torrent');
+ if (!isTorrent) {
+ message.error('只能上传.torrent文件');
+ }
+ setFileList([file]);
+ return false;
+};
+
+const handleSubmit = async (values: any) => {
+ if (fileList.length === 0) {
+ message.error('请上传.torrent文件');
+ return;
+ }
+ setUploading(true);
+ try {
+ // 1. 添加种子基本信息
+ const addRes = await addTorrent({
+ ...values,
+ category: Number(values.category),
+ description: values.description,
+ name: values.name,
+ title: values.title,
+ subheading: values.subheading || '',
+ remark: values.remark || '',
+ });
+ if (!addRes?.id) {
+ throw new Error('种子信息添加失败');
+ }
+ // 2. 上传.torrent文件
+ await uploadTorrentFile(fileList[0], addRes.id);
+
+ message.success('种子上传成功');
+ form.resetFields();
+ setFileList([]);
+ setCustomTags([]);
+ } catch (err: any) {
+ message.error(err.message || '上传失败');
+ } finally {
+ setUploading(false);
+ }
+};
+
+return (
+ <div
+ style={{
+ width: '100%',
+ margin: '64px auto',
+ padding: '56px 64px 48px 64px',
+ background: 'linear-gradient(135deg, #232526 0%, #414345 100%)',
+ borderRadius: 24,
+ boxShadow: '0 16px 48px 0 rgba(31, 38, 135, 0.18)',
+ color: '#fff',
+ position: 'relative',
+ overflow: 'hidden',
+ }}
+ >
+ <div
+ style={{
+ position: 'absolute',
+ top: -100,
+ right: -100,
+ width: 260,
+ height: 260,
+ background: 'radial-gradient(circle, #667eea55 0%, transparent 80%)',
+ zIndex: 0,
+ }}
+ />
+ <div
+ style={{
+ position: 'absolute',
+ bottom: -80,
+ left: -80,
+ width: 200,
+ height: 200,
+ background: 'radial-gradient(circle, #764ba255 0%, transparent 80%)',
+ zIndex: 0,
+ }}
+ />
+ <Title
+ level={2}
+ style={{
+ color: '#fff',
+ textAlign: 'center',
+ marginBottom: 48,
+ letterSpacing: 2,
+ fontWeight: 700,
+ zIndex: 1,
+ position: 'relative',
+ fontSize: 32,
+ }}
+ >
+ 星空PT - 上传资源
+ </Title>
+ <Form
+ form={form}
+ layout="horizontal"
+ labelCol={{ span: 5 }}
+ wrapperCol={{ span: 16 }}
+ onFinish={handleSubmit}
+ initialValues={{ anonymous: 0 }}
+ style={{ zIndex: 1, position: 'relative' }}
+ >
+ <Form.Item
+ label={<span style={{ fontWeight: 500 }}>主标题</span>}
+ name="title"
+ rules={[{ required: true, message: '请输入主标题' }]}
+ >
+ <Input
+ placeholder="请输入主标题"
+ size="large"
+ style={{
+ background: 'rgba(255,255,255,0.06)',
+ border: '1px solid #333',
+ color: '#fff',
+ }}
+ />
+ </Form.Item>
+ <Form.Item label={<span style={{ fontWeight: 500 }}>副标题</span>} name="subheading">
+ <Input
+ placeholder="可选,副标题"
+ size="large"
+ style={{
+ background: 'rgba(255,255,255,0.06)',
+ border: '1px solid #333',
+ color: '#fff',
+ }}
+ />
+ </Form.Item>
+ <Form.Item
+ label={<span style={{ fontWeight: 500 }}>种子名称</span>}
+ name="name"
+ rules={[{ required: true, message: '请输入种子名称' }]}
+ >
+ <Input
+ placeholder="请输入种子名称"
+ size="large"
+ style={{
+ background: 'rgba(255,255,255,0.06)',
+ border: '1px solid #333',
+ color: '#fff',
+ }}
+ />
+ </Form.Item>
+ <Form.Item
+ label={<span style={{ fontWeight: 500 }}>分类</span>}
+ name="category"
+ rules={[{ required: true, message: '请选择分类' }]}
+ >
+ <Select
+ placeholder="请选择分类"
+ size="large"
+ dropdownStyle={{ background: '#232526', color: '#fff' }}
+ style={{
+ background: 'rgba(255,255,255,0.06)',
+ border: '1px solid #333',
+ color: '#fff',
+ }}
+ >
+ {categories.map((cat) => (
+ <Option key={cat.id} value={cat.id}>
+ {cat.name}
+ </Option>
+ ))}
+ </Select>
+ </Form.Item>
+ <Form.Item label={<span style={{ fontWeight: 500 }}>标签</span>}>
+ <Space wrap>
+ {customTags.map((tag) => (
+ <Tag
+ key={tag}
+ closable
+ color="geekblue"
+ onClose={() => handleTagClose(tag)}
+ style={{
+ marginBottom: 4,
+ fontSize: 15,
+ padding: '4px 12px',
+ borderRadius: 8,
+ }}
+ >
+ {tag}
+ </Tag>
+ ))}
+ {tagInputVisible ? (
+ <Input
+ size="small"
+ style={{
+ width: 120,
+ background: 'rgba(255,255,255,0.08)',
+ color: '#fff',
+ border: '1px solid #333',
+ }}
+ value={tagInputValue}
+ onChange={handleTagInputChange}
+ onBlur={handleTagInputConfirm}
+ onPressEnter={handleTagInputConfirm}
+ autoFocus
+ />
+ ) : (
+ <Tag
+ onClick={showTagInput}
+ style={{
+ background: 'rgba(255,255,255,0.08)',
+ border: '1px dashed #1890ff',
+ cursor: 'pointer',
+ color: '#1890ff',
+ fontSize: 15,
+ borderRadius: 8,
+ padding: '4px 12px',
+ }}
+ >
+ <PlusOutlined /> 自定义标签
+ </Tag>
+ )}
+ </Space>
+ </Form.Item>
+ <Form.Item
+ label={<span style={{ fontWeight: 500 }}>描述</span>}
+ name="description"
+ rules={[{ required: true, message: '请输入描述' }]}
+ >
+ <TextArea
+ rows={6}
+ placeholder="请输入种子描述,支持Markdown"
+ style={{
+ background: 'rgba(255,255,255,0.06)',
+ border: '1px solid #333',
+ color: '#fff',
+ fontSize: 15,
+ }}
+ />
+ </Form.Item>
+ <Form.Item label={<span style={{ fontWeight: 500 }}>备注</span>} name="remark">
+ <Input
+ placeholder="可选,备注信息"
+ size="large"
+ style={{
+ background: 'rgba(255,255,255,0.06)',
+ border: '1px solid #333',
+ color: '#fff',
+ }}
+ />
+ </Form.Item>
+ <Form.Item
+ label={<span style={{ fontWeight: 500 }}>匿名上传</span>}
+ name="anonymous"
+ valuePropName="checked"
+ >
+ <Select
+ size="large"
+ style={{
+ background: 'rgba(255,255,255,0.06)',
+ border: '1px solid #333',
+ color: '#fff',
+ }}
+ >
+ <Option value={0}>否</Option>
+ <Option value={1}>是</Option>
+ </Select>
+ </Form.Item>
+ <Form.Item
+ label={<span style={{ fontWeight: 500 }}>上传.torrent文件</span>}
+ required
+ >
+ <Upload
+ beforeUpload={beforeUpload}
+ fileList={fileList}
+ onRemove={() => setFileList([])}
+ accept=".torrent"
+ maxCount={1}
+ showUploadList={{ showRemoveIcon: true }}
+ customRequest={() => {}}
+ >
+ <Button
+ icon={<UploadOutlined />}
+ size="large"
+ style={{
+ background: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
+ border: 'none',
+ color: '#fff',
+ fontWeight: 500,
+ }}
+ >
+ 选择.torrent文件
+ </Button>
+ </Upload>
+ </Form.Item>
+ <Form.Item wrapperCol={{ span: 16, offset: 5 }}>
+ <Button
+ type="primary"
+ htmlType="submit"
+ loading={uploading}
+ block
+ size="large"
+ style={{
+ background: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
+ border: 'none',
+ fontWeight: 600,
+ letterSpacing: 2,
+ fontSize: 18,
+ marginTop: 8,
+ boxShadow: '0 4px 16px 0 rgba(118,75,162,0.15)',
+ }}
+ >
+ 上传
+ </Button>
+ </Form.Item>
+ </Form>
+ </div>
+);
+};
+
+export default TorrentUpload;
\ No newline at end of file