blob: 6cb5a413ad097aeb0390daea5e99f2226fc124cf [file] [log] [blame]
Jiarenxiang36728482025-06-07 21:51:26 +08001import React, { useEffect, useState } from 'react';
2import { PlusOutlined, UploadOutlined } from '@ant-design/icons';
3import {
4Form,
5Input,
6Button,
7Select,
8Upload,
9message,
10Tag,
11Space,
12Typography,
13Modal,
14} from 'antd';
15import{
16 getCategories,
17 addTorrent,
18 uploadTorrentFile
19} from '../../services/bt/index';
20
21const { Option } = Select;
22const { TextArea } = Input;
23const { Title } = Typography;
24
25const TorrentUpload: React.FC = () => {
26const [categories, setCategories] = useState<{ id: number; name: string }[]>([]);
27const [tagOptions, setTagOptions] = useState<string[]>([]);
28const [customTags, setCustomTags] = useState<string[]>([]);
29const [fileList, setFileList] = useState<any[]>([]);
30const [uploading, setUploading] = useState(false);
31const [form] = Form.useForm();
32const [tagInputVisible, setTagInputVisible] = useState(false);
33const [tagInputValue, setTagInputValue] = useState('');
34
35useEffect(() => {
36 getCategories().then((res) => {
37 if (Array.isArray(res.data)) {
38 setCategories(res.data);
39 setTagOptions(res.data.map((cat: { name: string }) => cat.name));
40 }
41 });
42}, []);
43
44const handleTagClose = (removedTag: string) => {
45 setCustomTags(customTags.filter(tag => tag !== removedTag));
46};
47
48const showTagInput = () => setTagInputVisible(true);
49
50const handleTagInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
51 setTagInputValue(e.target.value);
52};
53
54const handleTagInputConfirm = () => {
55 if (
56 tagInputValue &&
57 !customTags.includes(tagInputValue) &&
58 !tagOptions.includes(tagInputValue)
59 ) {
60 setCustomTags([...customTags, tagInputValue]);
61 }
62 setTagInputVisible(false);
63 setTagInputValue('');
64};
65
66const beforeUpload = (file: File) => {
67 const isTorrent = file.name.endsWith('.torrent');
68 if (!isTorrent) {
69 message.error('只能上传.torrent文件');
70 }
71 setFileList([file]);
72 return false;
73};
74
75const handleSubmit = async (values: any) => {
76 if (fileList.length === 0) {
77 message.error('请上传.torrent文件');
78 return;
79 }
80 setUploading(true);
81 try {
82 // 1. 添加种子基本信息
83 const addRes = await addTorrent({
84 ...values,
85 category: Number(values.category),
86 description: values.description,
87 name: values.name,
88 title: values.title,
89 subheading: values.subheading || '',
90 remark: values.remark || '',
91 });
92 if (!addRes?.id) {
93 throw new Error('种子信息添加失败');
94 }
95 // 2. 上传.torrent文件
96 await uploadTorrentFile(fileList[0], addRes.id);
97
98 message.success('种子上传成功');
99 form.resetFields();
100 setFileList([]);
101 setCustomTags([]);
102 } catch (err: any) {
103 message.error(err.message || '上传失败');
104 } finally {
105 setUploading(false);
106 }
107};
108
109return (
110 <div
111 style={{
112 width: '100%',
113 margin: '64px auto',
114 padding: '56px 64px 48px 64px',
115 background: 'linear-gradient(135deg, #232526 0%, #414345 100%)',
116 borderRadius: 24,
117 boxShadow: '0 16px 48px 0 rgba(31, 38, 135, 0.18)',
118 color: '#fff',
119 position: 'relative',
120 overflow: 'hidden',
121 }}
122 >
123 <div
124 style={{
125 position: 'absolute',
126 top: -100,
127 right: -100,
128 width: 260,
129 height: 260,
130 background: 'radial-gradient(circle, #667eea55 0%, transparent 80%)',
131 zIndex: 0,
132 }}
133 />
134 <div
135 style={{
136 position: 'absolute',
137 bottom: -80,
138 left: -80,
139 width: 200,
140 height: 200,
141 background: 'radial-gradient(circle, #764ba255 0%, transparent 80%)',
142 zIndex: 0,
143 }}
144 />
145 <Title
146 level={2}
147 style={{
148 color: '#fff',
149 textAlign: 'center',
150 marginBottom: 48,
151 letterSpacing: 2,
152 fontWeight: 700,
153 zIndex: 1,
154 position: 'relative',
155 fontSize: 32,
156 }}
157 >
158 星空PT - 上传资源
159 </Title>
160 <Form
161 form={form}
162 layout="horizontal"
163 labelCol={{ span: 5 }}
164 wrapperCol={{ span: 16 }}
165 onFinish={handleSubmit}
166 initialValues={{ anonymous: 0 }}
167 style={{ zIndex: 1, position: 'relative' }}
168 >
169 <Form.Item
170 label={<span style={{ fontWeight: 500 }}>主标题</span>}
171 name="title"
172 rules={[{ required: true, message: '请输入主标题' }]}
173 >
174 <Input
175 placeholder="请输入主标题"
176 size="large"
177 style={{
178 background: 'rgba(255,255,255,0.06)',
179 border: '1px solid #333',
180 color: '#fff',
181 }}
182 />
183 </Form.Item>
184 <Form.Item label={<span style={{ fontWeight: 500 }}>副标题</span>} name="subheading">
185 <Input
186 placeholder="可选,副标题"
187 size="large"
188 style={{
189 background: 'rgba(255,255,255,0.06)',
190 border: '1px solid #333',
191 color: '#fff',
192 }}
193 />
194 </Form.Item>
195 <Form.Item
196 label={<span style={{ fontWeight: 500 }}>种子名称</span>}
197 name="name"
198 rules={[{ required: true, message: '请输入种子名称' }]}
199 >
200 <Input
201 placeholder="请输入种子名称"
202 size="large"
203 style={{
204 background: 'rgba(255,255,255,0.06)',
205 border: '1px solid #333',
206 color: '#fff',
207 }}
208 />
209 </Form.Item>
210 <Form.Item
211 label={<span style={{ fontWeight: 500 }}>分类</span>}
212 name="category"
213 rules={[{ required: true, message: '请选择分类' }]}
214 >
215 <Select
216 placeholder="请选择分类"
217 size="large"
218 dropdownStyle={{ background: '#232526', color: '#fff' }}
219 style={{
220 background: 'rgba(255,255,255,0.06)',
221 border: '1px solid #333',
222 color: '#fff',
223 }}
224 >
225 {categories.map((cat) => (
226 <Option key={cat.id} value={cat.id}>
227 {cat.name}
228 </Option>
229 ))}
230 </Select>
231 </Form.Item>
232 <Form.Item label={<span style={{ fontWeight: 500 }}>标签</span>}>
233 <Space wrap>
234 {customTags.map((tag) => (
235 <Tag
236 key={tag}
237 closable
238 color="geekblue"
239 onClose={() => handleTagClose(tag)}
240 style={{
241 marginBottom: 4,
242 fontSize: 15,
243 padding: '4px 12px',
244 borderRadius: 8,
245 }}
246 >
247 {tag}
248 </Tag>
249 ))}
250 {tagInputVisible ? (
251 <Input
252 size="small"
253 style={{
254 width: 120,
255 background: 'rgba(255,255,255,0.08)',
256 color: '#fff',
257 border: '1px solid #333',
258 }}
259 value={tagInputValue}
260 onChange={handleTagInputChange}
261 onBlur={handleTagInputConfirm}
262 onPressEnter={handleTagInputConfirm}
263 autoFocus
264 />
265 ) : (
266 <Tag
267 onClick={showTagInput}
268 style={{
269 background: 'rgba(255,255,255,0.08)',
270 border: '1px dashed #1890ff',
271 cursor: 'pointer',
272 color: '#1890ff',
273 fontSize: 15,
274 borderRadius: 8,
275 padding: '4px 12px',
276 }}
277 >
278 <PlusOutlined /> 自定义标签
279 </Tag>
280 )}
281 </Space>
282 </Form.Item>
283 <Form.Item
284 label={<span style={{ fontWeight: 500 }}>描述</span>}
285 name="description"
286 rules={[{ required: true, message: '请输入描述' }]}
287 >
288 <TextArea
289 rows={6}
290 placeholder="请输入种子描述,支持Markdown"
291 style={{
292 background: 'rgba(255,255,255,0.06)',
293 border: '1px solid #333',
294 color: '#fff',
295 fontSize: 15,
296 }}
297 />
298 </Form.Item>
299 <Form.Item label={<span style={{ fontWeight: 500 }}>备注</span>} name="remark">
300 <Input
301 placeholder="可选,备注信息"
302 size="large"
303 style={{
304 background: 'rgba(255,255,255,0.06)',
305 border: '1px solid #333',
306 color: '#fff',
307 }}
308 />
309 </Form.Item>
310 <Form.Item
311 label={<span style={{ fontWeight: 500 }}>匿名上传</span>}
312 name="anonymous"
313 valuePropName="checked"
314 >
315 <Select
316 size="large"
317 style={{
318 background: 'rgba(255,255,255,0.06)',
319 border: '1px solid #333',
320 color: '#fff',
321 }}
322 >
323 <Option value={0}>否</Option>
324 <Option value={1}>是</Option>
325 </Select>
326 </Form.Item>
327 <Form.Item
328 label={<span style={{ fontWeight: 500 }}>上传.torrent文件</span>}
329 required
330 >
331 <Upload
332 beforeUpload={beforeUpload}
333 fileList={fileList}
334 onRemove={() => setFileList([])}
335 accept=".torrent"
336 maxCount={1}
337 showUploadList={{ showRemoveIcon: true }}
338 customRequest={() => {}}
339 >
340 <Button
341 icon={<UploadOutlined />}
342 size="large"
343 style={{
344 background: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
345 border: 'none',
346 color: '#fff',
347 fontWeight: 500,
348 }}
349 >
350 选择.torrent文件
351 </Button>
352 </Upload>
353 </Form.Item>
354 <Form.Item wrapperCol={{ span: 16, offset: 5 }}>
355 <Button
356 type="primary"
357 htmlType="submit"
358 loading={uploading}
359 block
360 size="large"
361 style={{
362 background: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
363 border: 'none',
364 fontWeight: 600,
365 letterSpacing: 2,
366 fontSize: 18,
367 marginTop: 8,
368 boxShadow: '0 4px 16px 0 rgba(118,75,162,0.15)',
369 }}
370 >
371 上传
372 </Button>
373 </Form.Item>
374 </Form>
375 </div>
376);
377};
378
379export default TorrentUpload;