blob: 6b945240fccdfc5236d2d061a251a04d3c564f3e [file] [log] [blame]
import React, { useState } from "react";
import {
Typography,
Tabs,
Form,
Input,
Upload,
Button,
Select,
DatePicker,
InputNumber,
Radio,
Checkbox,
Collapse,
Alert,
Divider,
Space,
message,
} from "antd";
import {
UploadOutlined,
InboxOutlined,
InfoCircleOutlined,
} from "@ant-design/icons";
import { uploadTorrent } from "../../../api/torrents";
import { useAuth } from "../../auth/contexts/AuthContext";
const { Title, Paragraph, Text } = Typography;
const { TabPane } = Tabs;
const { Option } = Select;
const { TextArea } = Input;
const { Panel } = Collapse;
const { Dragger } = Upload;
// 通用的上传组件配置
const uploadProps = {
name: "torrent",
multiple: false,
accept: ".torrent",
maxCount: 1,
beforeUpload(file) {
const isTorrent = file.type === "application/x-bittorrent" || file.name.endsWith(".torrent");
if (!isTorrent) {
message.error("您只能上传 .torrent 文件!");
return Upload.LIST_IGNORE;
}
// 阻止自动上传,我们将在表单提交时手动处理
return false;
},
};
// 注意事项内容
const NoticeContent = () => (
<div className="space-y-4">
<Alert
message="请仔细阅读以下注意事项"
description="上传种子前,请确保您了解并同意以下规则,否则您的种子可能会被删除或您的账号可能会被处罚。"
type="info"
showIcon
/>
<Collapse defaultActiveKey={["1"]}>
<Panel header="一般规则" key="1">
<ul className="list-disc pl-8 space-y-2">
<li>您上传的资源必须是合法的,不得上传侵犯版权的内容。</li>
<li>禁止上传含有恶意代码的文件,所有上传内容会经过安全检查。</li>
<li>资源标题必须清晰描述内容,不得使用误导性标题。</li>
<li>请确保您提供的信息准确完整,包括分类、标签、简介等。</li>
<li>如发现重复资源,管理员有权合并或删除。</li>
<li>新用户需要达到一定等级后才能上传资源,具体请参考用户等级规则。</li>
</ul>
</Panel>
<Panel header="做种要求" key="2">
<ul className="list-disc pl-8 space-y-2">
<li>上传者必须保证做种至少 7 天,或直到至少有 3 个其他用户完成下载。</li>
<li>对于大型资源({'>'}20GB),请确保您有足够的上传带宽。</li>
<li>对于稀有资源,请尽可能长期做种,这将获得额外的积分奖励。</li>
<li>故意停止做种可能导致警告或账号处罚。</li>
</ul>
</Panel>
<Panel header="奖励政策" key="3">
<ul className="list-disc pl-8 space-y-2">
<li>首发资源将获得额外的积分奖励。</li>
<li>高质量的资源(高分辨率、完整音轨等)将获得质量加成。</li>
<li>稀有、珍贵的资源会获得特殊奖励。</li>
<li>长期保持良好的做种习惯将提高您的用户级别。</li>
</ul>
</Panel>
</Collapse>
</div>
);
// 通用版规组件
const CategoryRules = ({ category }) => {
const rules = {
movie: "电影资源上传规则...",
tv: "剧集资源上传规则...",
music: "音乐资源上传规则...",
anime: "动漫资源上传规则...",
game: "游戏资源上传规则...",
variety: "综艺资源上传规则...",
sports: "体育资源上传规则...",
software: "软件资源上传规则...",
learning: "学习资源上传规则...",
documentary: "纪录片资源上传规则...",
other: "其他资源上传规则...",
};
return (
<Collapse className="mb-6">
<Panel header={`${category}资源版规`} key="1">
<Paragraph>{rules[category] || "暂无特定规则"}</Paragraph>
</Panel>
</Collapse>
);
};
// 基础表单组件
const BaseFormItems = ({ category }) => (
<>
<Form.Item
name="torrentFile"
label="种子文件"
rules={[{ required: true, message: "请上传种子文件" }]}
>
<Dragger {...uploadProps}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">点击或拖拽种子文件到此区域上传</p>
<p className="ant-upload-hint">
支持单个.torrent文件上传,请确保种子文件有效
</p>
</Dragger>
</Form.Item>
<Form.Item
name="title"
label="标题"
>
<Input placeholder="请输入完整、准确的资源标题" />
</Form.Item>
<Form.Item
name="chineseName"
label="中文名"
>
<Input placeholder="请输入资源中文名称" />
</Form.Item>
<Form.Item
name="englishName"
label="英文名/原名"
>
<Input placeholder="请输入资源英文名称或原名" />
</Form.Item>
<Form.Item
name="year"
label="年份"
>
<DatePicker picker="year" placeholder="选择年份" />
</Form.Item>
<Form.Item
name="region"
label="地区"
>
<Select placeholder="请选择地区">
<Option value="china">中国大陆</Option>
<Option value="hongkong">中国香港</Option>
<Option value="taiwan">中国台湾</Option>
<Option value="japan">日本</Option>
<Option value="korea">韩国</Option>
<Option value="usa">美国</Option>
<Option value="uk">英国</Option>
<Option value="france">法国</Option>
<Option value="germany">德国</Option>
<Option value="italy">意大利</Option>
<Option value="spain">西班牙</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="language"
label="语言"
>
<Select placeholder="请选择语言" mode="multiple">
<Option value="chinese">中文</Option>
<Option value="english">英语</Option>
<Option value="japanese">日语</Option>
<Option value="korean">韩语</Option>
<Option value="french">法语</Option>
<Option value="german">德语</Option>
<Option value="italian">意大利语</Option>
<Option value="spanish">西班牙语</Option>
<Option value="russian">俄语</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="hasSubtitle"
valuePropName="checked"
>
<Checkbox>包含中文字幕</Checkbox>
</Form.Item>
<Form.Item
name="description"
label="资源简介"
rules={[{ required: true, message: "请输入资源简介" }]}
>
<TextArea rows={6} placeholder="请详细描述资源内容、特点等信息" />
</Form.Item>
<Form.Item
name="mediaInfo"
label="MediaInfo"
>
<TextArea rows={4} placeholder="请粘贴MediaInfo信息(如有)" />
</Form.Item>
<Form.Item
name="screenshots"
label="截图"
>
<Upload
listType="picture-card"
action="/api/upload-screenshot"
accept=".jpg,.jpeg,.png,.webp"
multiple
>
<div>
<UploadOutlined />
<div style={{ marginTop: 8 }}>上传截图</div>
</div>
</Upload>
</Form.Item>
</>
);
// 电影特定表单项
const MovieFormItems = () => (
<>
<Form.Item
name="director"
label="导演"
>
<Input placeholder="请输入导演名称,多个导演请用逗号分隔" />
</Form.Item>
<Form.Item
name="actors"
label="主演"
>
<Input placeholder="请输入主演名称,多个主演请用逗号分隔" />
</Form.Item>
<Form.Item
name="movieType"
label="电影类型"
>
<Select placeholder="请选择电影类型" mode="multiple">
<Option value="action">动作</Option>
<Option value="comedy">喜剧</Option>
<Option value="romance">爱情</Option>
<Option value="sci-fi">科幻</Option>
<Option value="horror">恐怖</Option>
<Option value="drama">剧情</Option>
<Option value="war">战争</Option>
<Option value="documentary">纪录</Option>
<Option value="animation">动画</Option>
<Option value="thriller">惊悚</Option>
<Option value="crime">犯罪</Option>
<Option value="fantasy">奇幻</Option>
<Option value="adventure">冒险</Option>
<Option value="history">历史</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="resolution"
label="分辨率"
>
<Select placeholder="请选择分辨率">
<Option value="4K">4K</Option>
<Option value="2K">2K</Option>
<Option value="1080p">1080p</Option>
<Option value="720p">720p</Option>
<Option value="480p">480p</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="source"
label="片源"
>
<Select placeholder="请选择片源">
<Option value="bluray">蓝光原盘</Option>
<Option value="remux">蓝光重灌</Option>
<Option value="encode">压制</Option>
<Option value="web-dl">WEB-DL</Option>
<Option value="dvd">DVD</Option>
<Option value="hdtv">HDTV</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="duration"
label="片长(分钟)"
>
<InputNumber min={1} placeholder="请输入片长" />
</Form.Item>
<Form.Item
name="imdbId"
label="IMDb ID"
>
<Input placeholder="例如:tt0111161" />
</Form.Item>
<Form.Item
name="doubanId"
label="豆瓣ID"
>
<Input placeholder="例如:1292052" />
</Form.Item>
</>
);
// 剧集特定表单项
const TVFormItems = () => (
<>
<Form.Item
name="season"
label="季数"
>
<InputNumber min={1} placeholder="第几季" />
</Form.Item>
<Form.Item
name="episode"
label="集数"
>
<Input placeholder="例如:1-12、全集、SP" />
</Form.Item>
<Form.Item
name="isComplete"
valuePropName="checked"
>
<Checkbox>已完结</Checkbox>
</Form.Item>
<Form.Item
name="director"
label="导演"
>
<Input placeholder="请输入导演名称,多个导演请用逗号分隔" />
</Form.Item>
<Form.Item
name="actors"
label="主演"
>
<Input placeholder="请输入主演名称,多个主演请用逗号分隔" />
</Form.Item>
<Form.Item
name="tvType"
label="剧集类型"
rules={[{ required: true, message: "请选择剧集类型" }]}
>
<Select placeholder="请选择剧集类型" mode="multiple">
<Option value="action">动作</Option>
<Option value="comedy">喜剧</Option>
<Option value="romance">爱情</Option>
<Option value="sci-fi">科幻</Option>
<Option value="horror">恐怖</Option>
<Option value="drama">剧情</Option>
<Option value="crime">犯罪</Option>
<Option value="fantasy">奇幻</Option>
<Option value="adventure">冒险</Option>
<Option value="mystery">悬疑</Option>
<Option value="thriller">惊悚</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="resolution"
label="分辨率"
rules={[{ required: true, message: "请选择分辨率" }]}
>
<Select placeholder="请选择分辨率">
<Option value="4K">4K</Option>
<Option value="2K">2K</Option>
<Option value="1080p">1080p</Option>
<Option value="720p">720p</Option>
<Option value="480p">480p</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="source"
label="片源"
rules={[{ required: true, message: "请选择片源" }]}
>
<Select placeholder="请选择片源">
<Option value="bluray">蓝光原盘</Option>
<Option value="remux">蓝光重灌</Option>
<Option value="encode">压制</Option>
<Option value="web-dl">WEB-DL</Option>
<Option value="dvd">DVD</Option>
<Option value="hdtv">HDTV</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="imdbId"
label="IMDb ID"
>
<Input placeholder="例如:tt0111161" />
</Form.Item>
<Form.Item
name="doubanId"
label="豆瓣ID"
>
<Input placeholder="例如:1292052" />
</Form.Item>
</>
);
// 音乐特定表单项
const MusicFormItems = () => (
<>
<Form.Item
name="artist"
label="艺术家"
rules={[{ required: true, message: "请输入艺术家名称" }]}
>
<Input placeholder="请输入艺术家名称" />
</Form.Item>
<Form.Item
name="album"
label="专辑名称"
>
<Input placeholder="请输入专辑名称(如果是专辑)" />
</Form.Item>
<Form.Item
name="musicType"
label="音乐类型"
rules={[{ required: true, message: "请选择音乐类型" }]}
>
<Select placeholder="请选择音乐类型" mode="multiple">
<Option value="pop">流行</Option>
<Option value="rock">摇滚</Option>
<Option value="electronic">电子</Option>
<Option value="folk">民谣</Option>
<Option value="hip-hop">嘻哈</Option>
<Option value="classical">古典</Option>
<Option value="jazz">爵士</Option>
<Option value="soundtrack">原声带</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="format"
label="音频格式"
rules={[{ required: true, message: "请选择音频格式" }]}
>
<Select placeholder="请选择音频格式">
<Option value="flac">FLAC</Option>
<Option value="ape">APE</Option>
<Option value="wav">WAV</Option>
<Option value="mp3">MP3</Option>
<Option value="aac">AAC</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="bitrate"
label="比特率"
>
<Select placeholder="请选择比特率">
<Option value="lossless">无损</Option>
<Option value="320">320Kbps</Option>
<Option value="256">256Kbps</Option>
<Option value="192">192Kbps</Option>
<Option value="128">128Kbps</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="tracks"
label="曲目数"
>
<InputNumber min={1} placeholder="请输入曲目数量" />
</Form.Item>
</>
);
// 动漫特定表单项
const AnimeFormItems = () => (
<>
<Form.Item
name="season"
label="季数"
>
<InputNumber min={1} placeholder="第几季" />
</Form.Item>
<Form.Item
name="episode"
label="集数"
>
<Input placeholder="例如:1-12、全集、SP、剧场版" />
</Form.Item>
<Form.Item
name="isComplete"
valuePropName="checked"
>
<Checkbox>已完结</Checkbox>
</Form.Item>
<Form.Item
name="animeType"
label="动漫类型"
rules={[{ required: true, message: "请选择动漫类型" }]}
>
<Select placeholder="请选择动漫类型" mode="multiple">
<Option value="tv">TV版</Option>
<Option value="movie">剧场版</Option>
<Option value="ova">OVA</Option>
<Option value="special">特别篇</Option>
</Select>
</Form.Item>
<Form.Item
name="resolution"
label="分辨率"
rules={[{ required: true, message: "请选择分辨率" }]}
>
<Select placeholder="请选择分辨率">
<Option value="4K">4K</Option>
<Option value="2K">2K</Option>
<Option value="1080p">1080p</Option>
<Option value="720p">720p</Option>
<Option value="480p">480p</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="source"
label="片源"
rules={[{ required: true, message: "请选择片源" }]}
>
<Select placeholder="请选择片源">
<Option value="bluray">蓝光原盘</Option>
<Option value="remux">蓝光重灌</Option>
<Option value="encode">压制</Option>
<Option value="web-dl">WEB-DL</Option>
<Option value="dvd">DVD</Option>
<Option value="hdtv">HDTV</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="subTeam"
label="字幕组"
>
<Input placeholder="请输入字幕组名称" />
</Form.Item>
</>
);
// 游戏特定表单项
const GameFormItems = () => (
<>
<Form.Item
name="platform"
label="平台"
rules={[{ required: true, message: "请选择游戏平台" }]}
>
<Select placeholder="请选择游戏平台" mode="multiple">
<Option value="pc">PC</Option>
<Option value="ps5">PS5</Option>
<Option value="ps4">PS4</Option>
<Option value="ps3">PS3</Option>
<Option value="xbox-series">Xbox Series X/S</Option>
<Option value="xbox-one">Xbox One</Option>
<Option value="switch">Nintendo Switch</Option>
<Option value="ns">Nintendo Switch</Option>
<Option value="3ds">Nintendo 3DS</Option>
<Option value="mobile">手机</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="gameType"
label="游戏类型"
rules={[{ required: true, message: "请选择游戏类型" }]}
>
<Select placeholder="请选择游戏类型" mode="multiple">
<Option value="rpg">角色扮演</Option>
<Option value="action">动作</Option>
<Option value="fps">第一人称射击</Option>
<Option value="tps">第三人称射击</Option>
<Option value="strategy">策略</Option>
<Option value="simulation">模拟</Option>
<Option value="adventure">冒险</Option>
<Option value="sports">体育</Option>
<Option value="racing">竞速</Option>
<Option value="fighting">格斗</Option>
<Option value="other">其他</Option>
</Select>
</Form.Item>
<Form.Item
name="version"
label="版本"
>
<Input placeholder="请输入游戏版本,例如:V1.02.3" />
</Form.Item>
<Form.Item
name="developer"
label="开发商"
>
<Input placeholder="请输入游戏开发商" />
</Form.Item>
<Form.Item
name="publisher"
label="发行商"
>
<Input placeholder="请输入游戏发行商" />
</Form.Item>
<Form.Item
name="language"
label="语言"
rules={[{ required: true, message: "请选择游戏语言" }]}
>
<Select placeholder="请选择游戏语言" mode="multiple">
<Option value="chinese">中文</Option>
<Option value="english">英文</Option>
<Option value="japanese">日文</Option>
<Option value="korean">韩文</Option>
<Option value="french">法文</Option>
<Option value="german">德文</Option>
<Option value="russian">俄文</Option>
<Option value="multi">多语言</Option>
</Select>
</Form.Item>
<Form.Item
name="hasCrack"
valuePropName="checked"
>
<Checkbox>已破解/免安装</Checkbox>
</Form.Item>
</>
);
// 其他类型特定的表单项(简化)
const OtherFormItems = ({ category }) => {
if (category === "variety") {
return (
<>
<Form.Item name="host" label="主持人">
<Input placeholder="请输入主持人名称" />
</Form.Item>
<Form.Item name="episode" label="期数">
<Input placeholder="例如:第1-12期、20200101" />
</Form.Item>
</>
);
}
if (category === "sports") {
return (
<>
<Form.Item name="league" label="联赛/赛事名称">
<Input placeholder="请输入联赛或赛事名称" />
</Form.Item>
<Form.Item name="match" label="比赛">
<Input placeholder="例如:队伍A vs 队伍B" />
</Form.Item>
</>
);
}
if (category === "software") {
return (
<>
<Form.Item name="version" label="版本">
<Input placeholder="请输入软件版本" />
</Form.Item>
<Form.Item name="os" label="操作系统">
<Select placeholder="请选择操作系统" mode="multiple">
<Option value="windows">Windows</Option>
<Option value="macos">macOS</Option>
<Option value="linux">Linux</Option>
<Option value="android">Android</Option>
<Option value="ios">iOS</Option>
</Select>
</Form.Item>
</>
);
}
if (category === "learning") {
return (
<>
<Form.Item name="subject" label="学科/主题">
<Input placeholder="请输入学科或主题" />
</Form.Item>
<Form.Item name="level" label="难度级别">
<Select placeholder="请选择难度级别">
<Option value="beginner">入门</Option>
<Option value="intermediate">中级</Option>
<Option value="advanced">高级</Option>
</Select>
</Form.Item>
</>
);
}
if (category === "documentary") {
return (
<>
<Form.Item name="docType" label="纪录片类型">
<Select placeholder="请选择纪录片类型" mode="multiple">
<Option value="nature">自然</Option>
<Option value="history">历史</Option>
<Option value="science">科学</Option>
<Option value="culture">人文</Option>
<Option value="society">社会</Option>
<Option value="biography">传记</Option>
</Select>
</Form.Item>
<Form.Item name="episodes" label="集数">
<Input placeholder="例如:共8集" />
</Form.Item>
</>
);
}
return null;
};
// 主组件
const UploadTorrentPage = () => {
const [form] = Form.useForm();
const [activeCategory, setActiveCategory] = useState("notice");
const { user } = useAuth();
const onFinish = async (values) => {
try {
// 创建 FormData 对象
const formData = new FormData();
// * 添加用户名
const username = user?.username || user?.uid;
if (!username) {
message.error('用户信息不完整,请重新登录');
return;
}
formData.append('username', username);
// 添加描述信息
formData.append('description', values.description || '');
// 添加种子文件
if (values.torrentFile && values.torrentFile.fileList && values.torrentFile.fileList.length > 0) {
const torrentFile = values.torrentFile.fileList[0].originFileObj;
formData.append('torrent', torrentFile);
} else {
message.error('请选择种子文件');
return;
}
console.log('username', formData.get('username'));
console.log('description', formData.get('description'));
console.log('torrent', formData.get('torrent'));
// 调用上传接口
const response = await uploadTorrent(formData);
if (response && response.message === "Resource published successfully") {
message.success('种子上传成功!');
form.resetFields(); // 重置表单
} else {
message.error('上传失败,请重试');
}
} catch (error) {
console.error('上传种子时出错:', error);
message.error('上传失败:' + (error.message || '网络错误,请重试'));
}
};
const renderFormByCategory = (category) => {
if (category === "notice") {
return <NoticeContent />;
}
return (
<Form
form={form}
layout="vertical"
onFinish={onFinish}
scrollToFirstError
>
<CategoryRules category={category} />
<BaseFormItems category={category} />
{/* 根据类别渲染特定的表单项 */}
{category === "movie" && <MovieFormItems />}
{category === "tv" && <TVFormItems />}
{category === "music" && <MusicFormItems />}
{category === "anime" && <AnimeFormItems />}
{category === "game" && <GameFormItems />}
{["variety", "sports", "software", "learning", "documentary", "other"].includes(category) && (
<OtherFormItems category={category} />
)}
<Divider />
<Form.Item>
<Space>
<Button type="primary" htmlType="submit">
提交
</Button>
<Button onClick={() => form.resetFields()}>重置</Button>
</Space>
</Form.Item>
</Form>
);
};
return (
<div className="space-y-6">
<Title level={2}>发布种子</Title>
<Tabs activeKey={activeCategory} onChange={setActiveCategory} type="card">
<TabPane tab="注意事项" key="notice" />
<TabPane tab="电影" key="movie" />
<TabPane tab="剧集" key="tv" />
<TabPane tab="音乐" key="music" />
<TabPane tab="动漫" key="anime" />
<TabPane tab="游戏" key="game" />
<TabPane tab="综艺" key="variety" />
<TabPane tab="体育" key="sports" />
<TabPane tab="软件" key="software" />
<TabPane tab="学习" key="learning" />
<TabPane tab="纪录片" key="documentary" />
<TabPane tab="其他" key="other" />
</Tabs>
<div>{renderFormByCategory(activeCategory)}</div>
</div>
);
};
export default UploadTorrentPage;