blob: 434d65bfa8829e8f06885880d527f4b5dd7fdec0 [file] [log] [blame]
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,
deleteTorrent,
getTracker,
} 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?.data) {
throw new Error('种子信息添加失败');
}
// 2. 上传.torrent文件
let upRes = await uploadTorrentFile(fileList[0], addRes.data);
console.log('上传结果:', upRes);
if (upRes.code == 500) {
throw new Error(upRes.msg);
await deleteTorrent( addRes.data);
}else{
message.success('种子上传成功');
form.resetFields();
setFileList([]);
setCustomTags([]);
// 获取并显示 trackerUrl
const trackerRes = await getTracker();
if (trackerRes.data) {
const trackerUrl = trackerRes.data;
Modal.info({
title: 'Tracker 服务器',
content: (
<div style={{ marginTop: 12, color: '#232946', wordBreak: 'break-all' }}>
{trackerUrl}
<br />
<span style={{ color: '#667eea', fontWeight: 500 }}>
&nbsp;torrentId={addRes.data}
</span>
</div>
),
width: 480,
okText: '关闭',
});
}
}
} catch (err: any) {
message.error(err.message || '上传失败');
} finally {
setUploading(false);
}
};
return (
<div
style={{
minHeight: '100vh',
background: 'radial-gradient(ellipse at 50% 30%, #232946 60%, #0f1021 100%)',
position: 'relative',
overflow: 'hidden',
padding: '0 0 80px 0',
}}
>
{/* 星空背景装饰 */}
<svg
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
zIndex: 0,
pointerEvents: 'none',
}}
>
{/* 随机星星 */}
{Array.from({ length: 120 }).map((_, i) => (
<circle
key={i}
cx={Math.random() * window.innerWidth}
cy={Math.random() * window.innerHeight}
r={Math.random() * 1.2 + 0.2}
fill="#fff"
opacity={Math.random() * 0.7 + 0.3}
/>
))}
{/* 银河带 */}
<ellipse
cx={window.innerWidth / 2}
cy={window.innerHeight / 2.5}
rx={window.innerWidth / 2.2}
ry={80}
fill="url(#milkyway)"
opacity="0.18"
/>
<defs>
<radialGradient id="milkyway" cx="50%" cy="50%" r="100%">
<stop offset="0%" stopColor="#fff" stopOpacity="0.8" />
<stop offset="100%" stopColor="#232946" stopOpacity="0" />
</radialGradient>
</defs>
</svg>
<div
style={{
maxWidth: 600,
margin: '0 auto',
marginTop: 80,
background: 'rgba(30,34,60,0.92)',
borderRadius: 24,
boxShadow: '0 8px 32px 0 rgba(31,38,135,0.25)',
padding: '48px 36px 32px 36px',
position: 'relative',
zIndex: 1,
border: '1.5px solid #3a3f5c',
backdropFilter: 'blur(2px)',
}}
>
<div style={{ textAlign: 'center', marginBottom: 24 }}>
<svg width="64" height="64" viewBox="0 0 64 64">
<defs>
<radialGradient id="star" cx="50%" cy="50%" r="50%">
<stop offset="0%" stopColor="#fffbe6" />
<stop offset="100%" stopColor="#667eea" />
</radialGradient>
</defs>
<circle cx="32" cy="32" r="28" fill="url(#star)" opacity="0.7" />
<polygon
points="32,12 36,28 52,28 38,36 42,52 32,42 22,52 26,36 12,28 28,28"
fill="#fff"
opacity="0.9"
/>
</svg>
<Title
level={2}
style={{
color: '#fff',
margin: '12px 0 0 0',
letterSpacing: 2,
fontWeight: 700,
fontSize: 30,
textShadow: '0 2px 12px #667eea55',
}}
>
星空PT · 种子上传
</Title>
<div style={{ color: '#bfcfff', fontSize: 16, marginTop: 4, letterSpacing: 1 }}>
分享你的资源,点亮星空
</div>
</div>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{ anonymous: 0 }}
style={{ zIndex: 1, position: 'relative' }}
>
<Form.Item
label={<span style={{ color: '#bfcfff', fontWeight: 500 }}>主标题</span>}
name="title"
rules={[{ required: true, message: '请输入主标题' }]}
>
<Input
placeholder="请输入主标题"
size="large"
style={{
background: 'rgba(255,255,255,0.06)',
border: '1px solid #3a3f5c',
color: '#fff',
}}
/>
</Form.Item>
<Form.Item
label={<span style={{ color: '#bfcfff', fontWeight: 500 }}>副标题</span>}
name="subheading"
>
<Input
placeholder="可选,副标题"
size="large"
style={{
background: 'rgba(255,255,255,0.06)',
border: '1px solid #3a3f5c',
color: '#fff',
}}
/>
</Form.Item>
<Form.Item
label={<span style={{ color: '#bfcfff', fontWeight: 500 }}>种子名称</span>}
name="name"
rules={[{ required: true, message: '请输入种子名称' }]}
>
<Input
placeholder="请输入种子名称"
size="large"
style={{
background: 'rgba(255,255,255,0.06)',
border: '1px solid #3a3f5c',
color: '#fff',
}}
/>
</Form.Item>
<Form.Item
label={<span style={{ color: '#bfcfff', fontWeight: 500 }}>分类</span>}
name="category"
rules={[{ required: true, message: '请选择分类' }]}
>
<Select
placeholder="请选择分类"
size="large"
dropdownStyle={{ background: '#232946', color: '#fff' }}
style={{
background: 'rgba(255,255,255,0.06)',
border: '1px solid #3a3f5c',
color: '#fff',
}}
>
{categories.map((cat) => (
<Option
key={cat.id}
value={cat.id}
style={{ color: '#fff', background: '#232946' }}
>
{cat.name}
</Option>
))}
</Select>
</Form.Item>
<Form.Item label={<span style={{ color: '#bfcfff', 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,
background: 'rgba(102,126,234,0.18)',
border: '1px solid #667eea',
color: '#fff',
}}
>
{tag}
</Tag>
))}
{tagInputVisible ? (
<Input
size="small"
style={{
width: 120,
background: 'rgba(255,255,255,0.08)',
color: '#fff',
border: '1px solid #667eea',
}}
value={tagInputValue}
onChange={handleTagInputChange}
onBlur={handleTagInputConfirm}
onPressEnter={handleTagInputConfirm}
autoFocus
/>
) : (
<Tag
onClick={showTagInput}
style={{
background: 'rgba(102,126,234,0.10)',
border: '1px dashed #667eea',
cursor: 'pointer',
color: '#bfcfff',
fontSize: 15,
borderRadius: 8,
padding: '4px 12px',
}}
>
<PlusOutlined /> 自定义标签
</Tag>
)}
</Space>
</Form.Item>
<Form.Item
label={<span style={{ color: '#bfcfff', 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 #3a3f5c',
color: '#fff',
fontSize: 15,
}}
/>
</Form.Item>
<Form.Item
label={<span style={{ color: '#bfcfff', fontWeight: 500 }}>备注</span>}
name="remark"
>
<Input
placeholder="可选,备注信息"
size="large"
style={{
background: 'rgba(255,255,255,0.06)',
border: '1px solid #3a3f5c',
color: '#fff',
}}
/>
</Form.Item>
<Form.Item
label={<span style={{ color: '#bfcfff', fontWeight: 500 }}>匿名上传</span>}
name="anonymous"
valuePropName="checked"
>
<Select
size="large"
style={{
background: 'rgba(255,255,255,0.06)',
border: '1px solid #3a3f5c',
color: '#fff',
}}
>
<Option value={0}>否</Option>
<Option value={1}>是</Option>
</Select>
</Form.Item>
<Form.Item
label={<span style={{ color: '#bfcfff', 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,
boxShadow: '0 2px 8px #667eea33',
}}
>
选择.torrent文件
</Button>
</Upload>
</Form.Item>
<Form.Item>
<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>
<Button
type="default"
block
style={{
marginTop: 16,
background: 'rgba(102,126,234,0.10)',
border: '1px solid #667eea',
color: '#bfcfff',
fontWeight: 500,
letterSpacing: 1,
}}
onClick={async () => {
try {
const res = await getTracker();
if (res.data) {
Modal.info({
title: 'Tracker 服务器',
content: (
<div style={{ marginTop: 12, color: '#232946' }}>
{res.data}
</div>
),
width: 480,
okText: '关闭',
});
} else {
message.info('暂无可用的 Tracker 服务器');
}
} catch (err) {
message.error('获取 Tracker 服务器失败');
}
}}
>
查看 Tracker 服务器
</Button>
</Form.Item>
</Form>
</div>
<div
style={{
position: 'fixed',
bottom: 16,
width: '100%',
textAlign: 'center',
color: '#bfcfff99',
fontSize: 14,
letterSpacing: 1,
zIndex: 2,
pointerEvents: 'none',
textShadow: '0 2px 8px #232946',
}}
>
星空PT · 让每一份分享都如星辰般闪耀
</div>
</div>
);
};
export default TorrentUpload;