blob: 4756b35b6ea02b10721b88eb9eb85982e9fad1c6 [file] [log] [blame]
刘嘉昕33b9f172025-06-09 17:23:06 +08001import React, { useState, useEffect } from 'react';
2import { useNavigate } from 'react-router-dom';
3import {
4 Table,
5 Button,
6 Modal,
7 Image,
8 message,
9 Spin,
10 Input,
11 Select,
12 Pagination,
13 Space
14} from 'antd';
15import { ExclamationCircleOutlined } from '@ant-design/icons';
16import axios from 'axios';
17
18const { confirm } = Modal;
19const { Option } = Select;
20
21const TorrentManagement = () => {
22 // 状态管理
23 const [torrents, setTorrents] = useState([]);
24 const [isLoading, setIsLoading] = useState(false);
25 const [error, setError] = useState(null);
26 const [selectedTorrentId, setSelectedTorrentId] = useState(null);
27 const [promotionOptions, setPromotionOptions] = useState([
28 { value: 1, label: '上传加倍' },
29 { value: 2, label: '下载减半' },
30 { value: 3, label: '免费下载' },
31 { value: 0, label: '无促销' }
32 ]);
33 const [selectedPromotion, setSelectedPromotion] = useState(null);
34 const [showPromotionWarning, setShowPromotionWarning] = useState(false);
35 const [currentUserId, setCurrentUserId] = useState(null);
36 const [applyPromotionsLoading, setApplyPromotionsLoading] = useState(false);
37 const [usernames, setUsernames] = useState({});
38 const [searchKeyword, setSearchKeyword] = useState('');
39 const [currentPage, setCurrentPage] = useState(1);
40 const [pageSize, setPageSize] = useState(10);
41 const navigate = useNavigate(); // 用于导航到详情页
42
43 // 获取当前用户ID
44 useEffect(() => {
45 const userId = 1; // 示例,实际从认证系统获取
46 setCurrentUserId(userId ? parseInt(userId) : null);
47 }, []);
48
49 // 获取所有种子数据
50 useEffect(() => {
51 fetchAllTorrents();
52 }, [searchKeyword]);
53
54 // 获取所有种子数据的函数
55 const fetchAllTorrents = async () => {
56 setIsLoading(true);
57 setError(null);
58 try {
59 const res = await axios.get('http://localhost:8080/torrent/list');
60 setTorrents(res.data);
61 setCurrentPage(1); // 重置为第一页
62 } catch (err) {
63 console.error('获取种子失败', err);
64 setError('获取种子列表失败,请稍后重试');
65 message.error('获取种子列表失败');
66 } finally {
67 setIsLoading(false);
68 }
69 };
70 console.log('当前种子列表:', torrents);
71
72 // 在组件加载时,批量获取所有 uploader_id 对应的 username
73 useEffect(() => {
74 const fetchUsernames = async () => {
75 if (torrents.length === 0) return;
76
77 const usernamePromises = torrents.map(async (torrent) => {
78 if (torrent.uploader_id && !usernames[torrent.uploader_id]) {
79 try {
80 const response = await fetch(`http://localhost:8080/torrent/${torrent.uploader_id}/username`);
81 if (response.ok) {
82 const username = await response.text();
83 return { [torrent.uploader_id]: username };
84 }
85 } catch (error) {
86 console.error(`Failed to fetch username for uploader_id ${torrent.uploader_id}:`, error);
87 }
88 }
89 return {};
90 });
91
92 const results = await Promise.all(usernamePromises);
93 const mergedUsernames = results.reduce((acc, curr) => ({ ...acc, ...curr }), {});
94 setUsernames((prev) => ({ ...prev, ...mergedUsernames }));
95 };
96
97 fetchUsernames();
98 }, [torrents]);
99
100 // 处理删除种子
101 const handleDeleteTorrent = async (torrentId) => {
102 if (!currentUserId) {
103 message.warning('请先登录');
104 return;
105 }
106
107 confirm({
108 title: '确认删除',
109 icon: <ExclamationCircleOutlined />,
110 content: '确定要删除这个种子吗?此操作不可恢复!',
111 onOk: async () => {
112 try {
113 await axios.delete(`http://localhost:8080/torrent/delete/${torrentId}`, {
114 params: { userid: currentUserId }
115 });
116 setTorrents(torrents.filter(torrent => torrent.torrentid !== torrentId));
117 message.success('种子删除成功');
118 } catch (err) {
119 console.error('删除种子失败', err);
120 if (err.response && err.response.status === 403) {
121 message.error('无权删除此种子');
122 } else {
123 message.error('删除种子失败');
124 }
125 }
126 }
127 });
128 };
129
130 // 搜索种子
131 const handleSearch = async () => {
132 if (!searchKeyword.trim()) {
133 fetchAllTorrents();
134 return;
135 }
136
137 setIsLoading(true);
138 setError(null);
139 try {
140 const res = await axios.get(`http://localhost:8080/torrent/search`, {
141 params: { keyword: searchKeyword },
142 });
143 setTorrents(res.data);
144 setCurrentPage(1); // 搜索后重置为第一页
145 } catch (err) {
146 console.error('搜索失败', err);
147 setError('搜索失败,请稍后重试');
148 message.error('搜索失败');
149 } finally {
150 setIsLoading(false);
151 }
152 };
153
154 // 处理修改促销方式
155 const handlePromotionChange = (torrentId, newPromotion) => {
156 setSelectedTorrentId(torrentId);
157 setSelectedPromotion(newPromotion);
158 setShowPromotionWarning(true);
159 };
160
161 // 确认修改促销方式
162 const confirmPromotionChange = async () => {
163 if (selectedTorrentId && selectedPromotion !== null) {
164 try {
165 await axios.post('http://localhost:8080/torrent/setPromotion', null, {
166 params: {
167 userid: currentUserId,
168 torrentId: selectedTorrentId,
169 promotionId: selectedPromotion
170 }
171 });
172 setTorrents(torrents.map(torrent =>
173 torrent.torrentid === selectedTorrentId
174 ? { ...torrent, promotionid: selectedPromotion }
175 : torrent
176 ));
177 setShowPromotionWarning(false);
178 message.success('促销方式修改成功');
179 } catch (err) {
180 console.error('修改促销方式失败', err);
181 if (err.response && err.response.status === 403) {
182 message.error('无权修改此种子的促销方式');
183 } else {
184 message.error('修改促销方式失败');
185 }
186 }
187 }
188 };
189
190 // 取消修改促销方式
191 const cancelPromotionChange = () => {
192 setShowPromotionWarning(false);
193 setSelectedTorrentId(null);
194 setSelectedPromotion(null);
195 };
196
197 // 触发检查(应用促销规则)
198 const handleApplyPromotions = async () => {
199 if (!currentUserId) {
200 message.warning('请先登录');
201 return;
202 }
203
204 setApplyPromotionsLoading(true);
205 try {
206 const res = await axios.post('http://localhost:8080/torrent/applyPromotions', null, {
207 params: { userid: currentUserId }
208 });
209
210 if (res.data.success) {
211 message.success(res.data.message);
212 fetchAllTorrents(); // 刷新种子列表
213 }
214 } catch (err) {
215 console.error('应用促销规则失败', err);
216 if (err.response && err.response.status === 403) {
217 message.error('无权执行此操作');
218 } else {
219 message.error('应用促销规则失败');
220 }
221 } finally {
222 setApplyPromotionsLoading(false);
223 }
224 };
225
226 // 分页数据计算
227 const getCurrentPageData = () => {
228 const start = (currentPage - 1) * pageSize;
229 const end = start + pageSize;
230 return torrents.slice(start, end);
231 };
232
233 // 页码变化处理
234 const handlePageChange = (page) => {
235 setCurrentPage(page);
236 };
237
238 // 每页条数变化处理
239 const handlePageSizeChange = (current, size) => {
240 setPageSize(size);
241 setCurrentPage(1); // 重置为第一页
242 };
243
244 // 格式化日期
245 const formatDate = (dateString) => {
246 const options = { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' };
247 return new Date(dateString).toLocaleString('zh-CN', options);
248 };
249
250 // 格式化文件大小
251 const formatFileSize = (bytes) => {
252 if (bytes === 0) return '0 Bytes';
253 const k = 1024;
254 const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
255 const i = Math.floor(Math.log(bytes) / Math.log(k));
256 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
257 };
258
259 // 获取促销方式名称
260 const getPromotionName = (promotionId) => {
261 if (promotionId === null) return '无促销';
262 const option = promotionOptions.find(opt => opt.value === promotionId);
263 return option ? option.label : '未知促销';
264 };
265
266 const handleViewDetails = (torrentId) => {
267 navigate(`/admin/${torrentId}`); // 使用已定义的 navigate 变量
268 };
269
270 return (
271 <div className="p-4 max-w-7xl mx-auto">
272 <h1 className="text-2xl font-bold mb-6">种子管理</h1>
273
274 {/* 搜索框 */}
275 <div className="mb-4 flex items-center">
276 <Input
277 placeholder="搜索种子..."
278 value={searchKeyword}
279 onChange={(e) => setSearchKeyword(e.target.value)}
280 style={{ width: 300 }}
281 onPressEnter={handleSearch}
282 />
283 <Button
284 type="primary"
285 onClick={handleSearch}
286 style={{ marginLeft: 8 }}
287 >
288 搜索
289 </Button>
290 </div>
291
292 {/* 右上角按钮 */}
293 <Button
294 type="primary"
295 loading={applyPromotionsLoading}
296 onClick={handleApplyPromotions}
297 style={{ marginBottom: 16 }}
298 >
299 触发检查
300 </Button>
301
302 {/* 加载状态 */}
303 {isLoading && <Spin size="large" style={{ display: 'block', margin: '100px auto' }} />}
304
305 {/* 错误提示 */}
306 {error && <div className="mb-4 p-3 bg-red-100 text-red-700 rounded border border-red-200">{error}</div>}
307
308 {/* 种子列表表格 */}
309 {!isLoading && !error && (
310 <>
311 <Table
312 columns={[
313 {
314 title: '封面',
315 dataIndex: 'coverImagePath',
316 key: 'coverImagePath',
317 render: (text) => text ? (
318 <Image
319 src={text}
320 width={50}
321 height={50}
322 preview={{ maskClosable: true }}
323 />
324 ) : (
325 <div className="w-16 h-16 bg-gray-200 flex items-center justify-center">无封面</div>
326 )
327 },
328 {
329 title: '名称',
330 dataIndex: 'filename',
331 key: 'filename'
332 },
333 {
334 title: '描述',
335 dataIndex: 'description',
336 key: 'description'
337 },
338 {
339 title: '大小',
340 dataIndex: 'torrentSize',
341 key: 'torrentSize',
342 render: (size) => formatFileSize(size)
343 },
344 {
345 title: '上传者',
346 dataIndex: 'uploader_id',
347 key: 'uploader_id',
348 render: (id) => usernames[id] || id
349 },
350 {
351 title: '上传时间',
352 dataIndex: 'uploadTime',
353 key: 'uploadTime',
354 render: (time) => formatDate(time)
355 },
356 {
357 title: '下载次数',
358 dataIndex: 'downloadCount',
359 key: 'downloadCount'
360 },
361 {
362 title: '促销',
363 dataIndex: 'promotionid',
364 key: 'promotionid',
365 render: (id) => getPromotionName(id)
366 },
367 {
368 title: '操作',
369 key: 'action',
370 render: (_, record) => (
371 <Space>
372 <Button
373 danger
374 onClick={() => handleDeleteTorrent(record.torrentid)}
375 loading={isLoading}
376 >
377 删除
378 </Button>
379 <Select
380 value={record.promotionid}
381 onChange={(value) => handlePromotionChange(record.torrentid, value)}
382 style={{ width: 120 }}
383 disabled={isLoading}
384 >
385 {promotionOptions.map(option => (
386 <Option key={option.value} value={option.value}>{option.label}</Option>
387 ))}
388 </Select>
389 <Button
390 type="primary"
391 size="small"
392 onClick={() => handleViewDetails(record.torrentid)} // 使用处理函数
393 >
394 查看详情
395 </Button>
396 </Space>
397 )
398 }
399 ]}
400 dataSource={getCurrentPageData()}
401 rowKey="torrentid"
402 pagination={false}
403 loading={isLoading}
404 />
405
406 {/* 分页控件 */}
407 {torrents.length > 0 && (
408 <div style={{ marginTop: 16, textAlign: 'center' }}>
409 <Pagination
410 current={currentPage}
411 pageSize={pageSize}
412 total={torrents.length}
413 onChange={handlePageChange}
414 onShowSizeChange={handlePageSizeChange}
415 showSizeChanger
416 showTotal={(total) => `共 ${total} 条记录`}
417 pageSizeOptions={['10', '20', '50']}
418 />
419 </div>
420 )}
421 </>
422 )}
423
424 {/* 促销方式修改确认弹窗 */}
425 <Modal
426 title="确认修改促销方式"
427 open={showPromotionWarning}
428 onOk={confirmPromotionChange}
429 onCancel={cancelPromotionChange}
430 okText="确认"
431 cancelText="取消"
432 >
433 <p>
434 您确定要将种子 ID
435 <span className="font-bold">{selectedTorrentId}</span> 的促销方式修改为
436 <span className="font-bold">「{getPromotionName(selectedPromotion)}」</span> 吗?
437 </p>
438 </Modal>
439 </div>
440 );
441};
442
443export default TorrentManagement;