blob: 3476ecafaab3c248d740ea09a51e41b0b88128d6 [file] [log] [blame]
DREW311a1a42025-06-07 21:19:03 +08001import React, {useEffect, useState, useRef} from 'react';
Akane12173a7bb972025-06-01 01:05:27 +08002import {useNavigate, useLocation, useParams} from 'react-router-dom';
DREWae420b22025-06-02 14:07:20 +08003import {createTorrent, getTorrents, downloadTorrent, getDownloadProgress, deleteTorrent, searchTorrents} from '../api/torrent';
Akane121765b61a72025-05-17 13:52:25 +08004import './Dashboard.css';
DREWae420b22025-06-02 14:07:20 +08005import {createHelpPost, getHelpPosts, getHelpPostDetail, searchHelpPosts} from '../api/helpPost';
6import {createRequestPost, getRequestPosts, getRequestPostDetail, searchRequestPosts} from '../api/requestPost';
7import { message } from 'antd'; // 用于显示提示消息
8import { getAnnouncements,getLatestAnnouncements,getAnnouncementDetail } from '../api/announcement';
9import { getAllDiscounts } from '../api/administer';
DREW7c7c6a02025-06-05 19:58:55 +080010import { getUserInfo, isAdmin } from '../api/auth';
Akane12173a7bb972025-06-01 01:05:27 +080011import { api } from '../api/auth';
DREW7c7c6a02025-06-05 19:58:55 +080012import { getRecommendations, markRecommendationShown, markRecommendationClicked } from '../api/recommend';
13
Akane121765b61a72025-05-17 13:52:25 +080014
15
22301080a93bebb2025-05-27 19:48:11 +080016const Dashboard = ({onLogout}) => {
17 const location = useLocation();
18 const [userInfo, setUserInfo] = useState(null);
19 const [loading, setLoading] = useState(false);
20 const [error, setError] = useState('');
21 const [currentSlide, setCurrentSlide] = useState(0);
22 const navigate = useNavigate();
23 const {tab} = useParams();
24 const [showUploadModal, setShowUploadModal] = useState(false);
25 const [isUploading, setIsUploading] = useState(false);
26 const [uploadData, setUploadData] = useState({
27 name: '',
28 type: '',
29 region: '',
30 subtitle: '',
31 resolution: '', // 新增分辨率字段
32 file: null,
33 description: ''
34 });
35 const [showPostModal, setShowPostModal] = useState(false);
36 const [postTitle, setPostTitle] = useState('');
37 const [postContent, setPostContent] = useState('');
38 const [selectedImage, setSelectedImage] = useState(null);
39 const [helpPosts, setHelpPosts] = useState([]);
40 const [helpLoading, setHelpLoading] = useState(false);
41 const [helpError, setHelpError] = useState(null);
42 const [currentPage, setCurrentPage] = useState(1);
43 const [totalPages, setTotalPages] = useState(1);
Akane1217d1e9f712025-05-29 14:36:56 +080044 const [likedPosts,setLikedPosts] = useState({});
DREWae420b22025-06-02 14:07:20 +080045 const [announcements, setAnnouncements] = useState([]);
46 const [carouselDiscounts, setCarouselDiscounts] = useState([]);
47 const [requestLoading, setRequestLoading] = useState(false);
48 const [requestError, setRequestError] = useState(null);
49 const [requestPosts, setRequestPosts] = useState([]);
Akane1217d1e9f712025-05-29 14:36:56 +080050
Akane121765b61a72025-05-17 13:52:25 +080051
52 // 添加状态
22301080a93bebb2025-05-27 19:48:11 +080053 const [torrentPosts, setTorrentPosts] = useState([]);
54 const [torrentLoading, setTorrentLoading] = useState(false);
55 const [torrentError, setTorrentError] = useState(null);
Akane121765b61a72025-05-17 13:52:25 +080056
22301080a93bebb2025-05-27 19:48:11 +080057 const [filteredResources, setFilteredResources] = useState(torrentPosts);
58 const [isAdmin, setIsAdmin] = useState(false);
59
DREWae420b22025-06-02 14:07:20 +080060 // 在组件状态中添加
61 const [showDownloadModal, setShowDownloadModal] = useState(false);
62 const [selectedTorrent, setSelectedTorrent] = useState(null);
63 const [downloadProgress, setDownloadProgress] = useState(0);
64 const [isDownloading, setIsDownloading] = useState(false);
65 const [downloadPath, setDownloadPath] = useState('D:/studies/ptPlatform/torrent');
DREW311a1a42025-06-07 21:19:03 +080066 // const timerRef = useRef(null);
DREWae420b22025-06-02 14:07:20 +080067
Akane12173a7bb972025-06-01 01:05:27 +080068 // 新增搜索状态
69 const [announcementSearch, setAnnouncementSearch] = useState('');
70 const [shareSearch, setShareSearch] = useState('');
71 const [requestSearch, setRequestSearch] = useState('');
72 const [helpSearch, setHelpSearch] = useState('');
DREW7c7c6a02025-06-05 19:58:55 +080073 const [recommendations, setRecommendations] = useState([]);
74 const [recommendLoading, setRecommendLoading] = useState(false);
75 const [recommendError, setRecommendError] = useState(null);
DREW5b1883e2025-06-07 10:41:32 +080076 const [isRecommend, setIsRecommend] = useState(true);
77 const [needNewRecommend, setNeedNewRecommend] = useState(false);
78
DREW7c7c6a02025-06-05 19:58:55 +080079
Akane12173a7bb972025-06-01 01:05:27 +080080
22301080a93bebb2025-05-27 19:48:11 +080081
82 const activeTab = tab || 'announcement'; // 如果没有tab参数,则默认为announcement
83 // 从location.state中初始化状态
84
85
86 const handleTabChange = (tabName) => {
87 navigate(`/dashboard/${tabName}`, {
88 state: {
89 savedFilters: selectedFilters, // 使用新的筛选状态 // 保留现有状态
90 activeTab: tabName // 可选,如果其他组件依赖这个 state
91 }
92 });
93 };
94
Akane12173a7bb972025-06-01 01:05:27 +080095
DREWae420b22025-06-02 14:07:20 +080096
97 //公告区
98 // 添加获取公告的方法
99 const fetchAnnouncements = async () => {
100 try {
101 const response = await getLatestAnnouncements();
102 setAnnouncements(response.data.data.announcements || []);
103 } catch (error) {
104 console.error('获取公告失败:', error);
105 }
Akane12173a7bb972025-06-01 01:05:27 +0800106 };
107
DREW5b1883e2025-06-07 10:41:32 +0800108 // 修改 useEffect 钩子
DREWae420b22025-06-02 14:07:20 +0800109 useEffect(() => {
110 if (activeTab === 'announcement') {
111 fetchAnnouncements();
112 fetchDiscountsForCarousel();
DREW5b1883e2025-06-07 10:41:32 +0800113
114 // 只在需要时获取推荐
115 if (isRecommend || needNewRecommend) {
116 fetchRecommendations();
117 }
DREWae420b22025-06-02 14:07:20 +0800118 }
DREW5b1883e2025-06-07 10:41:32 +0800119 }, [activeTab, isRecommend, needNewRecommend]);
120
121 // 添加刷新推荐的功能
122 const handleRefreshRecommendations = () => {
123 setNeedNewRecommend(true);
124 };
Akane12173a7bb972025-06-01 01:05:27 +0800125
DREWae420b22025-06-02 14:07:20 +0800126 const fetchDiscountsForCarousel = async () => {
127 try {
128 const all = await getAllDiscounts();
DREW5b1883e2025-06-07 10:41:32 +0800129 // console.log("返回的折扣数据:", all);
DREWae420b22025-06-02 14:07:20 +0800130 const now = new Date();
22301080a93bebb2025-05-27 19:48:11 +0800131
DREWae420b22025-06-02 14:07:20 +0800132 // ⚠️ 使用 Date.parse 确保兼容 ISO 格式
133 const ongoing = all.filter(d =>
134 Date.parse(d.startTime) <= now.getTime() && Date.parse(d.endTime) >= now.getTime()
135 );
136
137 const upcoming = all
138 .filter(d => Date.parse(d.startTime) > now.getTime())
139 .sort((a, b) => Date.parse(a.startTime) - Date.parse(b.startTime));
140
141 const selected = [...ongoing.slice(0, 3)];
142
143 while (selected.length < 3 && upcoming.length > 0) {
144 selected.push(upcoming.shift());
145 }
146
147 setCarouselDiscounts(selected);
148 } catch (e) {
149 console.error("获取折扣失败:", e);
150 }
151 };
152
153 // 修改handleAnnouncementClick函数中的state传递,移除不必要的字段
22301080a93bebb2025-05-27 19:48:11 +0800154 const handleAnnouncementClick = (announcement, e) => {
155 if (!e.target.closest('.exclude-click')) {
156 navigate(`/announcement/${announcement.id}`, {
157 state: {
158 announcement,
159 returnPath: `/dashboard/${activeTab}`,
160 scrollPosition: window.scrollY,
161 activeTab
162 }
163 });
Akane121765b61a72025-05-17 13:52:25 +0800164 }
22301080a93bebb2025-05-27 19:48:11 +0800165 };
Akane121765b61a72025-05-17 13:52:25 +0800166
DREW7c7c6a02025-06-05 19:58:55 +0800167 const handleRecommendClick = async (torrent) => {
168 try {
169 // 标记为已点击
170 await markRecommendationClicked(torrent.id);
171
172 // 导航到种子详情页
173 navigate(`/torrent/${torrent.id}`, {
174 state: {
175 fromTab: 'announcement',
176 scrollPosition: window.scrollY
177 }
178 });
179 } catch (error) {
180 console.error('标记推荐点击失败:', error);
181 }
182 };
183
DREWae420b22025-06-02 14:07:20 +0800184
185   // 公告区搜索处理
186    const handleSearchAnnouncement = (e) => {
187        setAnnouncementSearch(e.target.value);
188    };
189
190    // 修改后的搜索函数
191    const handleSearchShare = async () => {
192        try {
193            setTorrentLoading(true);
194            const response = await searchTorrents(shareSearch, 1);
195            if (response.data.code === 200) {
196                setTorrentPosts(response.data.data.records);
197                const total = response.data.data.total;
198                setTotalPages(Math.ceil(total / 5));
199                setCurrentPage(1);
200            } else {
201                setTorrentError(response.data.message || '搜索失败');
202            }
203        } catch (err) {
204            setTorrentError(err.message || '搜索失败');
205        } finally {
206            setTorrentLoading(false);
207        }
208    };
209
210    const handleResetShareSearch = async () => {
211        setShareSearch('');
212        setSelectedFilters(
213            Object.keys(filterCategories).reduce((acc, category) => {
214                acc[category] = 'all';
215                return acc;
216            }, {})
217        );
218        await fetchTorrentPosts(1, true);
219    };
220
221    // 添加搜索函数
222    const handleSearchRequest = async () => {
223        try {
224        setRequestLoading(true);
225        const response = await searchRequestPosts(requestSearch, currentPage);
226        if (response.data.code === 200) {
227            const postsWithCounts = await Promise.all(
228            response.data.data.records.map(async (post) => {
229                try {
230                const detailResponse = await getRequestPostDetail(post.id);
231                if (detailResponse.data.code === 200) {
232                    return {
233                    ...post,
234                    replyCount: detailResponse.data.data.post.replyCount || 0,
235                    isLiked: false
236                    };
237                }
238                return post;
239                } catch (err) {
240                console.error(`获取帖子${post.id}详情失败:`, err);
241                return post;
242                }
243            })
244            );
245            setRequestPosts(postsWithCounts);
246            setTotalPages(Math.ceil(response.data.data.total / 5));
247        } else {
248            setRequestError(response.data.message || '搜索失败');
249        }
250        } catch (err) {
251        setRequestError(err.message || '搜索失败');
252        } finally {
253        setRequestLoading(false);
254        }
255    };
256   
257    // 添加重置搜索函数
258    const handleResetRequestSearch = async () => {
259        setRequestSearch('');
260 await fetchRequestPosts(1); // 重置到第一页
261 };
262
263   // 添加搜索函数
264    const handleSearchHelp = async () => {
265        try {
266        setHelpLoading(true);
267        const response = await searchHelpPosts(helpSearch, currentPage);
268        if (response.data.code === 200) {
269            const postsWithCounts = await Promise.all(
270            response.data.data.records.map(async (post) => {
271                try {
272                const detailResponse = await getHelpPostDetail(post.id);
273                if (detailResponse.data.code === 200) {
274                    return {
275                    ...post,
276                    replyCount: detailResponse.data.data.post.replyCount || 0,
277                    isLiked: false
278                    };
279                }
280                return post;
281                } catch (err) {
282                console.error(`获取帖子${post.id}详情失败:`, err);
283                return post;
284                }
285            })
286            );
287            setHelpPosts(postsWithCounts);
288            setTotalPages(Math.ceil(response.data.data.total / 5));
289        } else {
290            setHelpError(response.data.message || '搜索失败');
291        }
292        } catch (err) {
293        setHelpError(err.message || '搜索失败');
294        } finally {
295        setHelpLoading(false);
296        }
297    };
298   
299    // 添加重置搜索函数
300    const handleResetHelpSearch = async () => {
301        setHelpSearch('');
302 await fetchHelpPosts(1); // 重置到第一页
303 };
304
305
306
Akane121765b61a72025-05-17 13:52:25 +0800307
22301080a93bebb2025-05-27 19:48:11 +0800308 //资源区
309 const handleFileChange = (e) => {
310 setUploadData({...uploadData, file: e.target.files[0]});
311 };
Akane121765b61a72025-05-17 13:52:25 +0800312
22301080a93bebb2025-05-27 19:48:11 +0800313 const handleUploadSubmit = async (e) => {
314 e.preventDefault();
315 try {
316 setIsUploading(true);
Akane121765b61a72025-05-17 13:52:25 +0800317
22301080a93bebb2025-05-27 19:48:11 +0800318 const torrentData = {
319 torrentName: uploadData.name,
320 description: uploadData.description,
321 category: uploadData.type,
322 region: uploadData.region,
323 resolution: uploadData.resolution,
324 subtitle: uploadData.subtitle
325 };
Akane121765b61a72025-05-17 13:52:25 +0800326
DREWae420b22025-06-02 14:07:20 +0800327 await createTorrent(uploadData.file, torrentData, (progress) => {
328 console.log(`上传进度: ${progress}%`);
329 // 这里可以添加进度条更新逻辑
330 });
22301080a93bebb2025-05-27 19:48:11 +0800331
332 // 上传成功处理
333 setShowUploadModal(false);
334 setUploadData({
335 name: '',
336 type: '',
337 region: '',
338 subtitle: '',
339 resolution: '',
340 file: null,
341 description: ''
342 });
343 alert('种子创建成功!');
344
345 // 刷新列表
346 await fetchTorrentPosts(currentPage);
347
348 } catch (error) {
349 console.error('创建失败:', error);
350 alert('创建失败: ' + (error.response?.data?.message || error.message));
351 } finally {
352 setIsUploading(false);
353 }
354 };
355
DREWae420b22025-06-02 14:07:20 +0800356 // 处理下载按钮点击
357 const handleDownloadClick = (torrent, e) => {
358 e.stopPropagation();
359 setSelectedTorrent(torrent);
360 setShowDownloadModal(true);
361 };
362
363 // 执行下载
DREW311a1a42025-06-07 21:19:03 +0800364 // const handleDownload = async () => {
365 // if (!selectedTorrent || !downloadPath) return;
DREWae420b22025-06-02 14:07:20 +0800366
DREW311a1a42025-06-07 21:19:03 +0800367 // setIsDownloading(true);
368 // setDownloadProgress(0);
DREWae420b22025-06-02 14:07:20 +0800369
DREW311a1a42025-06-07 21:19:03 +0800370 // try {
371 // // 发起下载请求
372 // await downloadTorrent(selectedTorrent.id, downloadPath);
373 // message.success("下载任务已提交");
374 // // 开始轮询进度
375 // const interval = setInterval(async () => {
376 // try {
377 // const res = await getDownloadProgress();
378 // const progresses = res.data.progresses;
DREWae420b22025-06-02 14:07:20 +0800379
DREW311a1a42025-06-07 21:19:03 +0800380 // if (progresses) {
381 // // 使用完整的 torrent 文件路径作为键
382 // const torrentPath = selectedTorrent.downloadPath.replace(/\\/g, '/');
383 // const torrentHash = selectedTorrent.hash;
384 // // 查找匹配的进度
385 // let foundProgress = null;
386 // for (const [key, value] of Object.entries(progresses)) {
387 // const normalizedKey = key.replace(/\\/g, '/');
388 // if (normalizedKey.includes(selectedTorrent.hash) ||
389 // normalizedKey.includes(selectedTorrent.torrentName)) {
390 // foundProgress = value;
391 // break;
392 // }
393 // }
394 // if (foundProgress !== null) {
395 // const newProgress = Math.round(foundProgress * 100);
396 // setDownloadProgress(newProgress);
DREWae420b22025-06-02 14:07:20 +0800397
DREW311a1a42025-06-07 21:19:03 +0800398 // // 检查是否下载完成
399 // if (newProgress >= 100) {
400 // clearInterval(interval);
401 // setIsDownloading(false);
402 // message.success('下载完成!');
403 // setTimeout(() => setShowDownloadModal(false), 2000);
404 // }
405 // } else {
406 // console.log('当前下载进度:', progresses); // 添加日志
407 // }
408 // }
409 // } catch (error) {
410 // console.error('获取进度失败:', error);
411 // // 如果获取进度失败但文件已存在,也视为完成
412 // const filePath = `${downloadPath}${selectedTorrent.torrentName || 'downloaded_file'}`;
413 // try {
414 // const exists = await checkFileExists(filePath);
415 // if (exists) {
416 // clearInterval(interval);
417 // setDownloadProgress(100);
418 // setIsDownloading(false);
419 // message.success('下载完成!');
420 // setTimeout(() => setShowDownloadModal(false), 2000);
421 // }
422 // } catch (e) {
423 // console.error('文件检查失败:', e);
424 // }
425 // }
426 // }, 2000);
DREWae420b22025-06-02 14:07:20 +0800427
DREW311a1a42025-06-07 21:19:03 +0800428 // return () => clearInterval(interval);
429 // } catch (error) {
430 // setIsDownloading(false);
431 // if (error.response && error.response.status === 409) {
432 // message.error('分享率不足,无法下载此资源');
433 // } else {
434 // message.error('下载失败: ' + (error.message || '未知错误'));
435 // }
436 // }
437 // };
438
439 const handleDownload = async () => {
440 if (!selectedTorrent || !downloadPath) return;
441
442 setIsDownloading(true);
443 // startPollingProgress();
444
445 try {
446 // 发起下载请求
447 await downloadTorrent(selectedTorrent.id, downloadPath);
448 message.success("下载任务已提交");
449 // setTimeout(() => setShowDownloadModal(false), 2000);
450 } catch (error) {
451 if (error.response && error.response.status === 409) {
452 message.error('分享率不足,无法下载此资源');
453 } else {
454 message.error('下载失败: ' + (error.message || '未知错误'));
455 }
456 }finally{
457 setIsDownloading(false);
DREWae420b22025-06-02 14:07:20 +0800458 }
DREWae420b22025-06-02 14:07:20 +0800459 };
DREW311a1a42025-06-07 21:19:03 +0800460
461
462 // const startPollingProgress = () => {
463 // timerRef.current = setInterval(async () => {
464 // try {
465 // const res = await getDownloadProgress();
466 // if (res.code === 200 && res.data.progresses) {
467 // const taskProgress = res.data.progresses[selectedTorrent?.id];
468 // if (taskProgress !== undefined) {
469 // const percent = Math.floor(taskProgress * 100);
470 // setDownloadProgress(percent);
471
472 // if (percent >= 100) {
473 // clearInterval(timerRef.current);
474 // message.success("下载完成");
475 // setTimeout(() => {
476 // setIsDownloading(false);
477 // setShowDownloadModal(false);
478 // }, 1500);
479 // }
480 // }
481 // }
482 // } catch (err) {
483 // console.error("获取进度失败", err);
484 // }
485 // }, 1000);
486 // };
487
488 // const stopPollingProgress = () => {
489 // clearInterval(timerRef.current);
490 // };
491
492 // useEffect(() => {
493 // return () => {
494 // stopPollingProgress();
495 // };
496 // }, []);
DREWae420b22025-06-02 14:07:20 +0800497
498 const checkFileExists = async (filePath) => {
499 try {
500 // 这里需要根据您的实际环境实现文件存在性检查
501 // 如果是Electron应用,可以使用Node.js的fs模块
502 // 如果是纯前端,可能需要通过API请求后端检查
503 return true; // 暂时返回true,实际实现需要修改
504 } catch (e) {
505 console.error('检查文件存在性失败:', e);
506 return false;
507 }
508 };
509
510 const handleDeleteTorrent = async (torrentId, e) => {
511 e.stopPropagation(); // 阻止事件冒泡,避免触发资源项的点击事件
512
513 try {
514 // 确认删除
515 if (!window.confirm('确定要删除这个种子吗?此操作不可撤销!')) {
516 return;
517 }
518
519 // 调用删除API
520 await deleteTorrent(torrentId);
521
522 // 删除成功后刷新列表
523 message.success('种子删除成功');
524 await fetchTorrentPosts(currentPage);
525 } catch (error) {
526 console.error('删除种子失败:', error);
527 message.error('删除种子失败: ' + (error.response?.data?.message || error.message));
528 }
529 };
530
531 const handleRequestPostSubmit = async (e) => {
22301080a93bebb2025-05-27 19:48:11 +0800532 e.preventDefault();
533 try {
Akane1217d1e9f712025-05-29 14:36:56 +0800534 const username = localStorage.getItem('username');
DREWae420b22025-06-02 14:07:20 +0800535 const response = await createRequestPost(
536 postTitle,
537 postContent,
538 username,
539 selectedImage
540 );
541
542 if (response.data.code === 200) {
543 // 刷新帖子列表
544
545 await fetchRequestPosts(currentPage);
546 // 重置表单
547 setShowPostModal(false);
548 setPostTitle('');
549 setPostContent('');
550 setSelectedImage(null);
551 } else {
552 setHelpError(response.data.message || '发帖失败');
553 }
554 } catch (err) {
555 setHelpError(err.message || '发帖失败');
556 }
557 };
558
559 const handleHelpPostSubmit = async (e) => {
560 e.preventDefault();
561 try {
562 const username = localStorage.getItem('username');
563 const response = await createHelpPost(
Akane1217d1e9f712025-05-29 14:36:56 +0800564 postTitle,
565 postContent,
566 username,
567 selectedImage
568 );
569
570 if (response.data.code === 200) {
571 // 刷新帖子列表
572 await fetchHelpPosts(currentPage);
DREWae420b22025-06-02 14:07:20 +0800573
Akane1217d1e9f712025-05-29 14:36:56 +0800574 // 重置表单
575 setShowPostModal(false);
576 setPostTitle('');
577 setPostContent('');
578 setSelectedImage(null);
579 } else {
580 setHelpError(response.data.message || '发帖失败');
581 }
22301080a93bebb2025-05-27 19:48:11 +0800582 } catch (err) {
Akane1217d1e9f712025-05-29 14:36:56 +0800583 setHelpError(err.message || '发帖失败');
22301080a93bebb2025-05-27 19:48:11 +0800584 }
Akane1217d1e9f712025-05-29 14:36:56 +0800585 };
Akane121765b61a72025-05-17 13:52:25 +0800586
Akane12173a7bb972025-06-01 01:05:27 +0800587
588const fetchTorrentPosts = async (page = 1, isReset = false) => {
589 setTorrentLoading(true);
590 try {
591 const params = {
592 page,
593 size: 5
594 };
595
596 // 如果有筛选条件且不是重置操作
597 if (!isReset && Object.values(selectedFilters).some(v => v !== 'all')) {
598 if (selectedFilters.type !== 'all') params.category = selectedFilters.type;
599 if (selectedFilters.subtitle !== 'all') params.subtitle = selectedFilters.subtitle;
600 if (selectedFilters.region !== 'all') params.region = selectedFilters.region;
601 if (selectedFilters.resolution !== 'all') params.resolution = selectedFilters.resolution;
22301080a93bebb2025-05-27 19:48:11 +0800602 }
Akane12173a7bb972025-06-01 01:05:27 +0800603
604 const response = (shareSearch && !isReset)
605 ? await searchTorrents(shareSearch, page)
DREW7c7c6a02025-06-05 19:58:55 +0800606 : await api.get('/torrent', { params });
Akane12173a7bb972025-06-01 01:05:27 +0800607
608 if (response.data.code === 200) {
609 setTorrentPosts(response.data.data.records);
610 const total = response.data.data.total;
611 setTotalPages(Math.ceil(total / 5));
612 setCurrentPage(page);
613 } else {
614 setTorrentError(response.data.message);
615 }
616 } catch (err) {
617 setTorrentError(err.message);
618 } finally {
619 setTorrentLoading(false);
620 }
621};
Akane121765b61a72025-05-17 13:52:25 +0800622
22301080a93bebb2025-05-27 19:48:11 +0800623 // 在useEffect中调用
624 useEffect(() => {
625 if (activeTab === 'share') {
626 fetchTorrentPosts();
627 }
628 }, [activeTab]);
Akane121765b61a72025-05-17 13:52:25 +0800629
DREWae420b22025-06-02 14:07:20 +0800630const fetchRequestPosts = async (page = 1) => {
631 setRequestLoading(true);
632 try {
633 const response = await getRequestPosts(page);
634 if (response.data.code === 200) {
635 const postsWithCounts = await Promise.all(
636 response.data.data.records.map(async (post) => {
637 try {
638 const detailResponse = await getRequestPostDetail(post.id);
639 if (detailResponse.data.code === 200) {
640 return {
641 ...post,
642 replyCount: detailResponse.data.data.post.replyCount || 0,
643 isLiked: false // 根据需要添加其他字段
644 };
645 }
646 return post; // 如果获取详情失败,返回原始帖子数据
647 } catch (err) {
648 console.error(`获取帖子${post.id}详情失败:`, err);
649 return post;
650 }
651 })
652 );
653 setRequestPosts(postsWithCounts);
654 setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条
655 setCurrentPage(page);
656 } else {
657 setRequestError(response.data.message || '获取求助帖失败');
658 }
659 } catch (err) {
660 setRequestError(err.message || '获取求助帖失败');
661 } finally {
662 setRequestLoading(false);
663 }
664 };
665
22301080a93bebb2025-05-27 19:48:11 +0800666 const handleImageUpload = (e) => {
667 setSelectedImage(e.target.files[0]);
668 };
Akane121765b61a72025-05-17 13:52:25 +0800669
670
22301080a93bebb2025-05-27 19:48:11 +0800671 const fetchHelpPosts = async (page = 1) => {
672 setHelpLoading(true);
673 try {
DREWae420b22025-06-02 14:07:20 +0800674 const response = await getHelpPosts(page);
Akane1217d1e9f712025-05-29 14:36:56 +0800675 if (response.data.code === 200) {
676 const postsWithCounts = await Promise.all(
677 response.data.data.records.map(async (post) => {
678 try {
DREWae420b22025-06-02 14:07:20 +0800679 const detailResponse = await getHelpPostDetail(post.id);
Akane1217d1e9f712025-05-29 14:36:56 +0800680 if (detailResponse.data.code === 200) {
681 return {
682 ...post,
683 replyCount: detailResponse.data.data.post.replyCount || 0,
684 isLiked: false // 根据需要添加其他字段
685 };
686 }
687 return post; // 如果获取详情失败,返回原始帖子数据
688 } catch (err) {
689 console.error(`获取帖子${post.id}详情失败:`, err);
690 return post;
691 }
692 })
693 );
694 setHelpPosts(postsWithCounts);
695 setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条
696 setCurrentPage(page);
697 } else {
698 setHelpError(response.data.message || '获取求助帖失败');
699 }
22301080a93bebb2025-05-27 19:48:11 +0800700 } catch (err) {
Akane1217d1e9f712025-05-29 14:36:56 +0800701 setHelpError(err.message || '获取求助帖失败');
22301080a93bebb2025-05-27 19:48:11 +0800702 } finally {
Akane1217d1e9f712025-05-29 14:36:56 +0800703 setHelpLoading(false);
22301080a93bebb2025-05-27 19:48:11 +0800704 }
DREW7c7c6a02025-06-05 19:58:55 +0800705 };
706
DREW5b1883e2025-06-07 10:41:32 +0800707 // 修改 fetchRecommendations 函数
708const fetchRecommendations = async () => {
709 try {
710 setRecommendLoading(true);
711 const response = await getRecommendations(5);
712
713 if (Array.isArray(response)) {
714 setRecommendations(response);
DREW7c7c6a02025-06-05 19:58:55 +0800715
DREW5b1883e2025-06-07 10:41:32 +0800716 if (response.length === 0) {
717 console.warn('暂无推荐内容');
718 } else {
719 // 标记这些推荐为已显示
720 response.forEach(torrent => {
721 markRecommendationShown(torrent.id);
722 });
DREW7c7c6a02025-06-05 19:58:55 +0800723 }
DREW5b1883e2025-06-07 10:41:32 +0800724
725 // 获取成功后重置标志
726 setIsRecommend(false);
727 setNeedNewRecommend(false);
728 } else {
729 console.warn('返回格式异常:', response);
730 setRecommendations([]);
DREW7c7c6a02025-06-05 19:58:55 +0800731 }
DREW5b1883e2025-06-07 10:41:32 +0800732 } catch (error) {
733 setRecommendError(error.message || '获取推荐失败');
734 } finally {
735 setRecommendLoading(false);
736 }
737};
Akane1217d1e9f712025-05-29 14:36:56 +0800738
739
22301080a93bebb2025-05-27 19:48:11 +0800740 useEffect(() => {
DREWae420b22025-06-02 14:07:20 +0800741 if (activeTab === 'request') {
742 fetchRequestPosts(currentPage);
22301080a93bebb2025-05-27 19:48:11 +0800743 }
744 }, [activeTab, currentPage]); // 添加 currentPage 作为依赖
Akane121765b61a72025-05-17 13:52:25 +0800745
746
Akane12173a7bb972025-06-01 01:05:27 +0800747 // 分类维度配置
22301080a93bebb2025-05-27 19:48:11 +0800748 const filterCategories = {
749 type: {
750 label: '类型',
751 options: {
752 'all': '全部',
753 '电影': '电影',
754 '电视剧': '电视剧',
755 '动漫': '动漫',
Akane12173a7bb972025-06-01 01:05:27 +0800756 '综艺': '综艺',
757 '音乐': '音乐',
758 '其他': '其他'
22301080a93bebb2025-05-27 19:48:11 +0800759 }
760 },
761 subtitle: {
762 label: '字幕',
763 options: {
764 'all': '全部',
Akane12173a7bb972025-06-01 01:05:27 +0800765 '无需字幕': '无需字幕',
766 '暂无字幕': '暂无字幕',
767 '自带中文字幕': '自带中文字幕',
768 '自带双语字幕(含中文)': '自带双语字幕(含中文)',
769 '附件中文字幕': '附件中文字幕',
770 '附件双语字幕': '附件双语字幕'
22301080a93bebb2025-05-27 19:48:11 +0800771 }
772 },
773 region: {
774 label: '地区',
775 options: {
776 'all': '全部',
Akane12173a7bb972025-06-01 01:05:27 +0800777 '中国': '中国',
778 '英国': '英国',
779 '美国': '美国',
780 '日本': '日本',
781 '韩国': '韩国',
782 '其他': '其他'
783 }
784 },
785 resolution: {
786 label: '分辨率',
787 options: {
788 'all': '全部',
789 '4K': '4K',
790 '2K': '2K',
791 '1080P': '1080P',
792 '720P': '720P',
793 'SD': 'SD',
794 '无损音源': '无损音源',
795 '杜比全景声': '杜比全景声',
796 '其他': '其他'
22301080a93bebb2025-05-27 19:48:11 +0800797 }
798 }
799 };
22301080a93bebb2025-05-27 19:48:11 +0800800 const [selectedFilters, setSelectedFilters] = useState(
801 location.state?.savedFilters ||
802 Object.keys(filterCategories).reduce((acc, category) => {
803 acc[category] = 'all';
804 return acc;
805 }, {})
806 );
Akane121765b61a72025-05-17 13:52:25 +0800807
Akane12173a7bb972025-06-01 01:05:27 +0800808 // 处理筛选条件变更
22301080a93bebb2025-05-27 19:48:11 +0800809 const handleFilterSelect = (category, value) => {
810 setSelectedFilters(prev => ({
811 ...prev,
Akane12173a7bb972025-06-01 01:05:27 +0800812 [category]: prev[category] === value ? 'all' : value
22301080a93bebb2025-05-27 19:48:11 +0800813 }));
Akane121765b61a72025-05-17 13:52:25 +0800814 };
815
Akane12173a7bb972025-06-01 01:05:27 +0800816 // 应用筛选条件
817 const applyFilters = async () => {
818 try {
819 setTorrentLoading(true);
820
821 // 构建查询参数
822 const params = {
823 page: 1, // 从第一页开始
824 size: 5
825 };
826
827 // 添加筛选条件
828 if (selectedFilters.type !== 'all') {
829 params.category = selectedFilters.type;
830 }
831 if (selectedFilters.subtitle !== 'all') {
832 params.subtitle = selectedFilters.subtitle;
833 }
834 if (selectedFilters.region !== 'all') {
835 params.region = selectedFilters.region;
836 }
837 if (selectedFilters.resolution !== 'all') {
838 params.resolution = selectedFilters.resolution;
839 }
840
841 // 调用API获取筛选结果
DREW7c7c6a02025-06-05 19:58:55 +0800842 const response = await api.get('/torrent', { params });
Akane12173a7bb972025-06-01 01:05:27 +0800843
844 if (response.data.code === 200) {
845 setTorrentPosts(response.data.data.records);
846 setTotalPages(Math.ceil(response.data.data.total / 5));
847 setCurrentPage(1);
848 } else {
849 setTorrentError(response.data.message || '筛选失败');
850 }
851 } catch (err) {
852 setTorrentError(err.message || '筛选失败');
853 } finally {
854 setTorrentLoading(false);
855 }
22301080a93bebb2025-05-27 19:48:11 +0800856 };
Akane121765b61a72025-05-17 13:52:25 +0800857
Akane121765b61a72025-05-17 13:52:25 +0800858
22301080a93bebb2025-05-27 19:48:11 +0800859 // 恢复滚动位置
860 useEffect(() => {
861 if (location.state?.scrollPosition) {
862 window.scrollTo(0, location.state.scrollPosition);
863 }
864 }, [location.state]);
Akane121765b61a72025-05-17 13:52:25 +0800865
Akane12173a7bb972025-06-01 01:05:27 +0800866 // 在Dashboard.jsx中修改useEffect
867useEffect(() => {
868 const token = localStorage.getItem('token');
869 if (!token) {
870 navigate('/login');
871 return;
872 }
873
874 const fetchUserInfo = async () => {
875 try {
876 setLoading(true);
877 const backendData = await getUserInfo(); // 调用修改后的方法
DREW5b1883e2025-06-07 10:41:32 +0800878 // console.log('后端返回的用户数据:', backendData); // 调试用
Akane12173a7bb972025-06-01 01:05:27 +0800879
880 setUserInfo({
881 name: backendData.username || '演示用户',
22301080a93bebb2025-05-27 19:48:11 +0800882 avatar: 'https://via.placeholder.com/40',
Akane12173a7bb972025-06-01 01:05:27 +0800883 isAdmin: backendData.authority === 'ADMIN' // 检查 authority 是否为 "ADMIN"
884 });
885 } catch (error) {
886 console.error('获取用户信息失败:', error);
887 setError('获取用户信息失败');
888 } finally {
889 setLoading(false);
890 }
891 };
892
893 fetchUserInfo();
894}, [navigate]);
22301080a93bebb2025-05-27 19:48:11 +0800895
Akane12173a7bb972025-06-01 01:05:27 +0800896
22301080a93bebb2025-05-27 19:48:11 +0800897 useEffect(() => {
898 if (activeTab === 'announcement') {
899 const timer = setInterval(() => {
DREWae420b22025-06-02 14:07:20 +0800900 setCurrentSlide(prev => {
901 const count = carouselDiscounts.length || 1;
902 return (prev + 1) % count;
903 });
22301080a93bebb2025-05-27 19:48:11 +0800904 }, 3000);
905 return () => clearInterval(timer);
906 }
907 }, [activeTab]);
908
909 useEffect(() => {
910 if (activeTab === 'help') {
911 fetchHelpPosts();
912 }
DREWae420b22025-06-02 14:07:20 +0800913 }, [activeTab, currentPage]); // 添加 currentPage 作为依赖
914
915
22301080a93bebb2025-05-27 19:48:11 +0800916
917 const renderContent = () => {
918 switch (activeTab) {
919 case 'announcement':
920 return (
921 <div className="content-area" data-testid="announcement-section">
Akane12173a7bb972025-06-01 01:05:27 +0800922 <div className="section-search-container">
923 <input
924 type="text"
925 placeholder="搜索公告..."
926 value={announcementSearch}
927 onChange={(e) => setAnnouncementSearch(e.target.value)}
928 className="section-search-input"
929 onKeyPress={(e) => e.key === 'Enter' && handleSearchAnnouncement()}
930 />
931 <button
932 className="search-button"
933 onClick={handleSearchAnnouncement}
934 >
935 搜索
936 </button>
937 </div>
22301080a93bebb2025-05-27 19:48:11 +0800938 {/* 轮播图区域 */}
DREWae420b22025-06-02 14:07:20 +0800939 <div className="carousel-container">
940 {carouselDiscounts.length === 0 ? (
941 <div className="carousel-slide active">
942 <div className="carousel-image gray-bg">暂无折扣活动</div>
22301080a93bebb2025-05-27 19:48:11 +0800943 </div>
DREWae420b22025-06-02 14:07:20 +0800944 ) : (
945 carouselDiscounts.map((discount, index) => (
946 <div key={index} className={`carousel-slide ${currentSlide === index ? 'active' : ''}`}>
947 <div className="carousel-image gray-bg">
948 <h3>{discount.type}</h3>
949 <p>{discount.name}</p>
950 <p>{new Date(discount.startTime).toLocaleDateString()} ~ {new Date(discount.endTime).toLocaleDateString()}</p>
951 </div>
22301080a93bebb2025-05-27 19:48:11 +0800952 </div>
DREWae420b22025-06-02 14:07:20 +0800953 ))
954 )}
955 <div className="carousel-dots">
956 {carouselDiscounts.map((_, index) => (
957 <span key={index} className={`dot ${currentSlide === index ? 'active' : ''}`}></span>
958 ))}
22301080a93bebb2025-05-27 19:48:11 +0800959 </div>
DREWae420b22025-06-02 14:07:20 +0800960 </div>
961
962
22301080a93bebb2025-05-27 19:48:11 +0800963
964 {/* 公告区块区域 */}
965 <div className="announcement-grid">
966 {announcements.map(announcement => (
967 <div
968 key={announcement.id}
969 className="announcement-card"
970 onClick={(e) => handleAnnouncementClick(announcement, e)}
971 >
972 <h3>{announcement.title}</h3>
DREWae420b22025-06-02 14:07:20 +0800973 <p>{announcement.content.substring(0, 100)}...</p>
22301080a93bebb2025-05-27 19:48:11 +0800974 <div className="announcement-footer exclude-click">
22301080a93bebb2025-05-27 19:48:11 +0800975 <span>{announcement.date}</span>
976 </div>
977 </div>
978 ))}
979 </div>
DREW7c7c6a02025-06-05 19:58:55 +0800980 {/* 新增的为你推荐区域 */}
981 <div className="recommend-section">
DREW5b1883e2025-06-07 10:41:32 +0800982 <div className="section-header">
983 <h2 className="section-title">为你推荐</h2>
984 <button
985 className="refresh-btn"
986 onClick={handleRefreshRecommendations}
987 disabled={recommendLoading}
988 >
989 {recommendLoading ? '刷新中...' : '刷新推荐'}
990 </button>
991 </div>
DREW7c7c6a02025-06-05 19:58:55 +0800992
993 {recommendLoading && <div className="loading">加载推荐中...</div>}
994 {recommendError && <div className="error">{recommendError}</div>}
DREW5b1883e2025-06-07 10:41:32 +0800995
DREW7c7c6a02025-06-05 19:58:55 +0800996 <div className="recommend-grid">
997 {recommendations.map(torrent => (
998 <div
999 key={torrent.id}
1000 className="recommend-card"
1001 onClick={() => handleRecommendClick(torrent)}
1002 >
1003 <div className="card-poster">
1004 <div className="poster-image gray-bg">
1005 {torrent.torrentName.charAt(0)}
1006 </div>
1007 </div>
1008 <div className="card-info">
1009 <h3 className="card-title">{torrent.torrentName}</h3>
1010 <p className="card-meta">
1011 {torrent.resolution} | {torrent.region} | {torrent.category}
1012 </p>
1013 <p className="card-subtitle">字幕: {torrent.subtitle}</p>
1014 </div>
1015 <div className="card-stats">
1016 <span className="stat">👍 {torrent.likeCount || 0}</span>
1017 </div>
1018 </div>
1019 ))}
1020
1021 {!recommendLoading && recommendations.length === 0 && (
1022 <div className="no-recommendations">
1023 暂无推荐,请先下载一些资源以获取个性化推荐
1024 </div>
1025 )}
1026 </div>
1027 </div>
Akane121765b61a72025-05-17 13:52:25 +08001028 </div>
22301080a93bebb2025-05-27 19:48:11 +08001029 );
1030 case 'share':
1031 return (
1032 <div className="content-area" data-testid="share-section">
Akane12173a7bb972025-06-01 01:05:27 +08001033 {/* 分享区搜索框 */}
1034 <div className="section-search-container">
1035 <input
1036 type="text"
1037 placeholder="搜索资源..."
1038 value={shareSearch}
1039 onChange={(e) => setShareSearch(e.target.value)}
1040 className="section-search-input"
1041 onKeyPress={(e) => e.key === 'Enter' && handleSearchShare()}
1042 />
1043 <button
1044 className="search-button"
1045 onClick={handleSearchShare}
1046 >
1047 搜索
1048 </button>
1049 <button
1050 className="reset-button"
1051 onClick={handleResetShareSearch} // 使用新的重置函数
1052 style={{marginLeft: '10px'}}
1053 >
1054 重置
1055 </button>
1056 </div>
1057
22301080a93bebb2025-05-27 19:48:11 +08001058 {/* 上传按钮 - 添加在筛选区上方 */}
1059 <div className="upload-header">
1060 <button
1061 className="upload-btn"
1062 onClick={() => setShowUploadModal(true)}
1063 >
1064 上传种子
1065 </button>
1066 </div>
1067 {/* 分类筛选区 */}
1068 <div className="filter-section">
1069 {Object.entries(filterCategories).map(([category, config]) => (
1070 <div key={category} className="filter-group">
1071 <h4>{config.label}:</h4>
1072 <div className="filter-options">
1073 {Object.entries(config.options).map(([value, label]) => (
1074 <button
1075 key={value}
1076 className={`filter-btn ${
1077 selectedFilters[category] === value ? 'active' : ''
1078 }`}
1079 onClick={() => handleFilterSelect(category, value)}
1080 >
1081 {label}
1082 </button>
1083 ))}
1084 </div>
1085 </div>
1086 ))}
1087
1088 <button
1089 className="confirm-btn"
1090 onClick={applyFilters}
1091 >
1092 确认筛选
1093 </button>
1094 </div>
1095
1096 {/* 上传弹窗 */}
1097 {showUploadModal && (
1098 <div className="modal-overlay">
1099 <div className="upload-modal">
1100 <h3>上传新种子</h3>
1101 <button
1102 className="close-btn"
1103 onClick={() => setShowUploadModal(false)}
1104 >
1105 ×
1106 </button>
1107
1108 <form onSubmit={handleUploadSubmit}>
1109 <div className="form-group">
1110 <label>种子名称</label>
1111 <input
1112 type="text"
1113 value={uploadData.name}
1114 onChange={(e) => setUploadData({...uploadData, name: e.target.value})}
1115 required
1116 />
1117 </div>
1118
1119 <div className="form-group">
1120 <label>资源类型</label>
1121 <select
1122 value={uploadData.type}
1123 onChange={(e) => setUploadData({...uploadData, type: e.target.value})}
1124 required
1125 >
1126 <option value="">请选择</option>
1127 <option value="电影">电影</option>
1128 <option value="电视剧">电视剧</option>
1129 <option value="动漫">动漫</option>
1130 <option value="综艺">综艺</option>
1131 <option value="音乐">音乐</option>
Akane12173a7bb972025-06-01 01:05:27 +08001132 <option value="其他">其他</option>
22301080a93bebb2025-05-27 19:48:11 +08001133 </select>
1134 </div>
1135
Akane12173a7bb972025-06-01 01:05:27 +08001136 {/* 修改后的地区下拉框 */}
22301080a93bebb2025-05-27 19:48:11 +08001137 <div className="form-group">
1138 <label>地区</label>
Akane12173a7bb972025-06-01 01:05:27 +08001139 <select
22301080a93bebb2025-05-27 19:48:11 +08001140 value={uploadData.region || ''}
1141 onChange={(e) => setUploadData({...uploadData, region: e.target.value})}
22301080a93bebb2025-05-27 19:48:11 +08001142 required
Akane12173a7bb972025-06-01 01:05:27 +08001143 >
1144 <option value="">请选择</option>
1145 <option value="中国">中国</option>
1146 <option value="英国">英国</option>
1147 <option value="美国">美国</option>
1148 <option value="日本">日本</option>
1149 <option value="韩国">韩国</option>
1150 <option value="其他">其他</option>
1151 </select>
22301080a93bebb2025-05-27 19:48:11 +08001152 </div>
1153
1154 {/* 添加分辨率下拉框 */}
1155 <div className="form-group">
1156 <label>分辨率</label>
1157 <select
1158 value={uploadData.resolution || ''}
1159 onChange={(e) => setUploadData({
1160 ...uploadData,
1161 resolution: e.target.value
1162 })}
1163 required
1164 >
1165 <option value="">请选择</option>
1166 <option value="4K">4K</option>
1167 <option value="2K">2K</option>
1168 <option value="1080P">1080P</option>
1169 <option value="720P">720P</option>
1170 <option value="SD">SD</option>
1171 <option value="无损音源">无损音源</option>
1172 <option value="杜比全景声">杜比全景声</option>
1173 <option value="其他">其他</option>
1174 </select>
1175 </div>
1176
1177
1178 {/* 新增字幕语言下拉框 */}
1179 <div className="form-group">
1180 <label>字幕语言</label>
1181 <select
1182 value={uploadData.subtitle || ''}
1183 onChange={(e) => setUploadData({
1184 ...uploadData,
1185 subtitle: e.target.value
1186 })}
1187 required
1188 >
1189 <option value="">请选择</option>
1190 <option value="无需字幕">无需字幕</option>
1191 <option value="暂无字幕">暂无字幕</option>
1192 <option value="自带中文字幕">自带中文字幕</option>
1193 <option value="自带双语字幕(含中文)">自带双语字幕(含中文)</option>
1194 <option value="附件中文字幕">附件中文字幕</option>
1195 <option value="附件双语字幕">附件双语字幕</option>
1196 </select>
1197 </div>
1198
1199 <div className="form-group">
1200 <label>种子文件</label>
1201 <input
1202 type="file"
1203 accept=".torrent"
1204 onChange={handleFileChange}
1205
1206 />
1207 </div>
1208
1209 <div className="form-group">
1210 <label>简介</label>
1211 <textarea
1212 value={uploadData.description}
1213 onChange={(e) => setUploadData({
1214 ...uploadData,
1215 description: e.target.value
1216 })}
1217 />
1218 </div>
1219
1220 <div className="form-actions">
1221 <button
1222 type="button"
1223 className="cancel-btn"
1224 onClick={() => setShowUploadModal(false)}
1225 >
1226 取消
1227 </button>
1228 <button
1229 type="submit"
1230 className="confirm-btn"
1231 disabled={isUploading}
1232 >
1233 {isUploading ? '上传中...' : '确认上传'}
1234 </button>
1235 </div>
1236 </form>
1237 </div>
1238 </div>
Akane121765b61a72025-05-17 13:52:25 +08001239 )}
22301080a93bebb2025-05-27 19:48:11 +08001240
1241 <div className="resource-list">
1242 {torrentPosts.map(torrent => (
1243 <div
1244 key={torrent.id}
1245 className="resource-item"
1246 onClick={() => navigate(`/torrent/${torrent.id}`)}
1247 >
1248 <div className="resource-poster">
1249 <div className="poster-image gray-bg">{torrent.torrentName.charAt(0)}</div>
1250 </div>
1251 <div className="resource-info">
1252 <h3 className="resource-title">{torrent.torrentName}</h3>
1253 <p className="resource-meta">
1254 {torrent.resolution} | {torrent.region} | {torrent.category}
1255 </p>
1256 <p className="resource-subtitle">字幕: {torrent.subtitle}</p>
1257 </div>
1258 <div className="resource-stats">
Akane12173a7bb972025-06-01 01:05:27 +08001259 <span className="stat">{torrent.size}</span>
22301080a93bebb2025-05-27 19:48:11 +08001260 <span className="stat">发布者: {torrent.username}</span>
1261 </div>
1262 <button
1263 className="download-btn"
DREWae420b22025-06-02 14:07:20 +08001264 onClick={(e) => handleDownloadClick(torrent, e)}
22301080a93bebb2025-05-27 19:48:11 +08001265 >
1266 立即下载
1267 </button>
DREWae420b22025-06-02 14:07:20 +08001268 {/* 添加删除按钮 - 只有管理员或发布者可见 */}
1269 {(userInfo?.isAdmin || userInfo?.name === torrent.username) && (
1270 <button
1271 className="delete-btn"
1272 onClick={(e) => handleDeleteTorrent(torrent.id, e)}
1273 >
1274 删除
1275 </button>
1276 )}
22301080a93bebb2025-05-27 19:48:11 +08001277 </div>
1278 ))}
1279 </div>
1280
Akane12173a7bb972025-06-01 01:05:27 +08001281 {totalPages > 1 && (
22301080a93bebb2025-05-27 19:48:11 +08001282 <div className="pagination">
1283 <button
1284 onClick={() => fetchTorrentPosts(currentPage - 1)}
1285 disabled={currentPage === 1}
1286 >
1287 上一页
1288 </button>
1289
1290 {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
1291 <button
1292 key={page}
1293 onClick={() => fetchTorrentPosts(page)}
1294 className={currentPage === page ? 'active' : ''}
1295 >
1296 {page}
1297 </button>
1298 ))}
1299
1300 <button
1301 onClick={() => fetchTorrentPosts(currentPage + 1)}
1302 disabled={currentPage === totalPages}
1303 >
1304 下一页
1305 </button>
1306 </div>
Akane12173a7bb972025-06-01 01:05:27 +08001307 )}
Akane121765b61a72025-05-17 13:52:25 +08001308 </div>
22301080a93bebb2025-05-27 19:48:11 +08001309 );
1310 // 在Dashboard.jsx的renderContent函数中修改case 'request'部分
1311 case 'request':
1312 return (
1313 <div className="content-area" data-testid="request-section">
DREW5b1883e2025-06-07 10:41:32 +08001314 {/* 求种区搜索框 */}
Akane12173a7bb972025-06-01 01:05:27 +08001315 <div className="section-search-container">
1316 <input
1317 type="text"
DREW5b1883e2025-06-07 10:41:32 +08001318 placeholder="搜索求种..."
Akane12173a7bb972025-06-01 01:05:27 +08001319 value={requestSearch}
1320 onChange={(e) => setRequestSearch(e.target.value)}
1321 className="section-search-input"
1322 onKeyPress={(e) => e.key === 'Enter' && handleSearchRequest()}
1323 />
1324 <button
1325 className="search-button"
1326 onClick={handleSearchRequest}
1327 >
1328 搜索
1329 </button>
DREWae420b22025-06-02 14:07:20 +08001330 <button
1331 className="reset-button"
1332 onClick={handleResetRequestSearch}
1333 style={{marginLeft: '10px'}}
1334 >
1335 重置
1336 </button>
Akane12173a7bb972025-06-01 01:05:27 +08001337 </div>
DREWae420b22025-06-02 14:07:20 +08001338
1339 {/* 新增发帖按钮 */}
1340 <div className="post-header">
1341 <button
1342 className="create-post-btn"
1343 onClick={() => setShowPostModal(true)}
1344 >
1345 发帖求助
1346 </button>
1347 </div>
1348
1349 {/* 加载状态和错误提示 */}
1350 {requestLoading && <div className="loading">加载中...</div>}
1351 {requestError && <div className="error">{helpError}</div>}
22301080a93bebb2025-05-27 19:48:11 +08001352 {/* 求种区帖子列表 */}
1353 <div className="request-list">
DREWae420b22025-06-02 14:07:20 +08001354 {requestPosts.map(post => (
22301080a93bebb2025-05-27 19:48:11 +08001355 <div
1356 key={post.id}
DREWae420b22025-06-02 14:07:20 +08001357 className={`request-post ${post.isSolved ? 'solved' : ''}`}
22301080a93bebb2025-05-27 19:48:11 +08001358 onClick={() => navigate(`/request/${post.id}`)}
1359 >
1360 <div className="post-header">
DREWae420b22025-06-02 14:07:20 +08001361 <img
1362 src={post.authorAvatar || 'https://via.placeholder.com/40'}
1363 alt={post.authorId}
1364 className="post-avatar"
1365 />
1366 <div className="post-author">{post.authorId}</div>
1367 <div className="post-date">
1368 {new Date(post.createTime).toLocaleDateString()}
1369 </div>
1370 {post.isSolved && <span className="solved-badge">已解决</span>}
22301080a93bebb2025-05-27 19:48:11 +08001371 </div>
1372 <h3 className="post-title">{post.title}</h3>
1373 <p className="post-content">{post.content}</p>
1374 <div className="post-stats">
DREWae420b22025-06-02 14:07:20 +08001375 <span className="post-likes">👍 {post.likeCount || 0}</span>
1376 <span className="post-comments">💬 {post.replyCount || 0}</span>
22301080a93bebb2025-05-27 19:48:11 +08001377 </div>
1378 </div>
1379 ))}
1380 </div>
DREWae420b22025-06-02 14:07:20 +08001381 {/* 在帖子列表后添加分页控件 */}
1382 <div className="pagination">
1383 <button
1384 onClick={() => fetchRequestPosts(currentPage - 1)}
1385 disabled={currentPage === 1}
1386 >
1387 上一页
1388 </button>
1389
1390 {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
1391 <button
1392 key={page}
1393 onClick={() => fetchRequestPosts(page)}
1394 className={currentPage === page ? 'active' : ''}
1395 >
1396 {page}
1397 </button>
1398 ))}
1399
1400 <button
1401 onClick={() => fetchRequestPosts(currentPage + 1)}
1402 disabled={currentPage === totalPages}
1403 >
1404 下一页
1405 </button>
1406 </div>
1407 {/* 新增发帖弹窗 */}
1408 {showPostModal && (
1409 <div className="post-modal-overlay">
1410 <div className="post-modal">
1411 <h3>发布求种帖</h3>
1412 <button
1413 className="modal-close-btn"
1414 onClick={() => setShowPostModal(false)}
1415 >
1416 ×
1417 </button>
1418
1419 <form onSubmit={handleRequestPostSubmit}>
1420 <div className="form-group">
1421 <label>帖子标题</label>
1422 <input
1423 type="text"
1424 value={postTitle}
1425 onChange={(e) => setPostTitle(e.target.value)}
1426 placeholder="请输入标题"
1427 required
1428 />
1429 </div>
1430
1431 <div className="form-group">
1432 <label>帖子内容</label>
1433 <textarea
1434 value={postContent}
1435 onChange={(e) => setPostContent(e.target.value)}
1436 placeholder="详细描述你的问题"
1437 required
1438 />
1439 </div>
1440
1441 <div className="form-group">
1442 <label>上传图片</label>
1443 <div className="upload-image-btn">
1444 <input
1445 type="file"
1446 id="image-upload"
1447 accept="image/*"
1448 onChange={handleImageUpload}
1449 style={{display: 'none'}}
1450 />
1451 <label htmlFor="image-upload">
1452 {selectedImage ? '已选择图片' : '选择图片'}
1453 </label>
1454 {selectedImage && (
1455 <span className="image-name">{selectedImage.name}</span>
1456 )}
1457 </div>
1458 </div>
1459
1460 <div className="form-actions">
1461 <button
1462 type="button"
1463 className="cancel-btn"
1464 onClick={() => setShowPostModal(false)}
1465 >
1466 取消
1467 </button>
1468 <button
1469 type="submit"
1470 className="submit-btn"
1471 >
1472 确认发帖
1473 </button>
1474 </div>
1475 </form>
1476 </div>
1477 </div>
1478 )}
Akane121765b61a72025-05-17 13:52:25 +08001479 </div>
DREWae420b22025-06-02 14:07:20 +08001480
1481
1482
1483
22301080a93bebb2025-05-27 19:48:11 +08001484 );
1485 // 在Dashboard.jsx的renderContent函数中修改case 'help'部分
1486 case 'help':
1487 return (
1488 <div className="content-area" data-testid="help-section">
Akane12173a7bb972025-06-01 01:05:27 +08001489 {/* 求助区搜索框 */}
1490 <div className="section-search-container">
1491 <input
1492 type="text"
1493 placeholder="搜索求助..."
1494 value={helpSearch}
1495 onChange={(e) => setHelpSearch(e.target.value)}
1496 className="section-search-input"
1497 onKeyPress={(e) => e.key === 'Enter' && handleSearchHelp()}
1498 />
1499 <button
1500 className="search-button"
1501 onClick={handleSearchHelp}
1502 >
1503 搜索
1504 </button>
1505 <button
1506 className="reset-button"
1507 onClick={handleResetHelpSearch}
1508 style={{marginLeft: '10px'}}
1509 >
1510 重置
1511 </button>
1512 </div>
1513
22301080a93bebb2025-05-27 19:48:11 +08001514 {/* 新增发帖按钮 */}
1515 <div className="post-header">
1516 <button
1517 className="create-post-btn"
1518 onClick={() => setShowPostModal(true)}
1519 >
1520 发帖求助
1521 </button>
1522 </div>
1523
1524 {/* 加载状态和错误提示 */}
1525 {helpLoading && <div className="loading">加载中...</div>}
1526 {helpError && <div className="error">{helpError}</div>}
1527
1528 {/* 求助区帖子列表 */}
1529 <div className="help-list">
1530 {helpPosts.map(post => (
1531 <div
1532 key={post.id}
1533 className={`help-post ${post.isSolved ? 'solved' : ''}`}
1534 onClick={() => navigate(`/help/${post.id}`)}
1535 >
1536 <div className="post-header">
1537 <img
1538 src={post.authorAvatar || 'https://via.placeholder.com/40'}
1539 alt={post.authorId}
1540 className="post-avatar"
1541 />
1542 <div className="post-author">{post.authorId}</div>
1543 <div className="post-date">
1544 {new Date(post.createTime).toLocaleDateString()}
1545 </div>
1546 {post.isSolved && <span className="solved-badge">已解决</span>}
1547 </div>
1548 <h3 className="post-title">{post.title}</h3>
1549 <p className="post-content">{post.content}</p>
1550 <div className="post-stats">
1551 <span className="post-likes">👍 {post.likeCount || 0}</span>
1552 <span className="post-comments">💬 {post.replyCount || 0}</span>
1553 </div>
1554 </div>
1555 ))}
1556 </div>
1557
1558 {/* 在帖子列表后添加分页控件 */}
1559 <div className="pagination">
1560 <button
1561 onClick={() => fetchHelpPosts(currentPage - 1)}
1562 disabled={currentPage === 1}
1563 >
1564 上一页
1565 </button>
1566
1567 {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
1568 <button
1569 key={page}
1570 onClick={() => fetchHelpPosts(page)}
1571 className={currentPage === page ? 'active' : ''}
1572 >
1573 {page}
1574 </button>
1575 ))}
1576
1577 <button
1578 onClick={() => fetchHelpPosts(currentPage + 1)}
1579 disabled={currentPage === totalPages}
1580 >
1581 下一页
1582 </button>
1583 </div>
1584
1585 {/* 新增发帖弹窗 */}
1586 {showPostModal && (
1587 <div className="post-modal-overlay">
1588 <div className="post-modal">
1589 <h3>发布求助帖</h3>
1590 <button
1591 className="modal-close-btn"
1592 onClick={() => setShowPostModal(false)}
1593 >
1594 ×
1595 </button>
1596
DREWae420b22025-06-02 14:07:20 +08001597 <form onSubmit={handleHelpPostSubmit}>
22301080a93bebb2025-05-27 19:48:11 +08001598 <div className="form-group">
1599 <label>帖子标题</label>
1600 <input
1601 type="text"
1602 value={postTitle}
1603 onChange={(e) => setPostTitle(e.target.value)}
1604 placeholder="请输入标题"
1605 required
1606 />
1607 </div>
1608
1609 <div className="form-group">
1610 <label>帖子内容</label>
1611 <textarea
1612 value={postContent}
1613 onChange={(e) => setPostContent(e.target.value)}
1614 placeholder="详细描述你的问题"
1615 required
1616 />
1617 </div>
1618
1619 <div className="form-group">
1620 <label>上传图片</label>
1621 <div className="upload-image-btn">
1622 <input
1623 type="file"
1624 id="image-upload"
1625 accept="image/*"
1626 onChange={handleImageUpload}
1627 style={{display: 'none'}}
1628 />
1629 <label htmlFor="image-upload">
1630 {selectedImage ? '已选择图片' : '选择图片'}
1631 </label>
1632 {selectedImage && (
1633 <span className="image-name">{selectedImage.name}</span>
1634 )}
1635 </div>
1636 </div>
1637
1638 <div className="form-actions">
1639 <button
1640 type="button"
1641 className="cancel-btn"
1642 onClick={() => setShowPostModal(false)}
1643 >
1644 取消
1645 </button>
1646 <button
1647 type="submit"
1648 className="submit-btn"
1649 >
1650 确认发帖
1651 </button>
1652 </div>
1653 </form>
1654 </div>
1655 </div>
1656 )}
1657 </div>
1658 );
1659 default:
1660 return <div className="content-area" data-testid="default-section">公告区内容</div>;
1661 }
DREWae420b22025-06-02 14:07:20 +08001662
22301080a93bebb2025-05-27 19:48:11 +08001663 };
1664
1665 if (loading) return <div className="loading">加载中...</div>;
1666 if (error) return <div className="error">{error}</div>;
1667
1668 return (
1669 <div className="dashboard-container" data-testid="dashboard-container">
1670 {/* 顶部栏 */}
1671 <div className="top-bar" data-testid="top-bar">
Akane12173a7bb972025-06-01 01:05:27 +08001672 {/* 平台名称替换搜索框 */}
1673 <div className="platform-name">
1674 <h2>PT资源站</h2>
Akane121765b61a72025-05-17 13:52:25 +08001675 </div>
Akane121765b61a72025-05-17 13:52:25 +08001676
22301080a93bebb2025-05-27 19:48:11 +08001677 <div className="user-actions">
1678 {/* 新增管理员按钮 - 只有管理员可见 */}
1679 {userInfo?.isAdmin && (
1680 <button
1681 className="admin-center-button"
1682 onClick={() => navigate('/administer')}
1683 >
1684 管理员中心
1685 </button>
1686 )}
Akane121765b61a72025-05-17 13:52:25 +08001687
22301080a93bebb2025-05-27 19:48:11 +08001688 <div className="user-info" data-testid="user-info">
1689 <img
1690 src={userInfo?.avatar || 'https://via.placeholder.com/40'}
1691 alt="用户头像"
1692 className="user-avatar"
1693 onClick={() => navigate('/personal')}
1694 style={{cursor: 'pointer'}}
1695 />
1696 <span className="username">{userInfo?.name || '用户'}</span>
1697 <button onClick={onLogout} className="logout-button">退出</button>
1698 </div>
1699 </div>
1700 </div>
1701
1702 {/* 导航栏 */}
1703 {/* handleTabchange函数替换了原本的setactivetab函数 */}
1704 <div className="nav-tabs">
1705 <button
1706 className={`tab-button ${activeTab === 'announcement' ? 'active' : ''}`}
1707 onClick={() => handleTabChange('announcement')}
1708 >
1709 公告区
1710 </button>
1711 <button
1712 className={`tab-button ${activeTab === 'share' ? 'active' : ''}`}
1713 onClick={() => handleTabChange('share')}
1714 >
1715 分享区
1716 </button>
1717 <button
1718 className={`tab-button ${activeTab === 'request' ? 'active' : ''}`}
1719 onClick={() => handleTabChange('request')}
1720 >
1721 求种区
1722 </button>
1723 <button
1724 className={`tab-button ${activeTab === 'help' ? 'active' : ''}`}
1725 onClick={() => handleTabChange('help')}
1726 >
1727 求助区
1728 </button>
1729 </div>
1730
1731 {/* 内容区 */}
1732 {renderContent()}
DREWae420b22025-06-02 14:07:20 +08001733 {/* 下载模态框 - 添加在这里 */}
1734 {showDownloadModal && selectedTorrent && (
1735 <div className="modal-overlay">
1736 <div className="download-modal">
1737 <h3>下载 {selectedTorrent.torrentName}</h3>
1738 <button
1739 className="close-btn"
1740 onClick={() => !isDownloading && setShowDownloadModal(false)}
1741 disabled={isDownloading}
1742 >
1743 ×
1744 </button>
1745
1746 <div className="form-group">
1747 <label>下载路径:</label>
1748 <input
1749 type="text"
1750 value={downloadPath}
1751 onChange={(e) => {
1752 // 实时格式化显示
1753 let path = e.target.value
1754 .replace(/\t/g, '')
1755 .replace(/\\/g, '/')
1756 .replace(/\s+/g, ' ');
1757 setDownloadPath(path);
1758 }}
1759 disabled={isDownloading}
1760 placeholder="例如: D:/downloads/"
1761 />
1762 </div>
1763
1764 {isDownloading && (
1765 <div className="progress-container">
1766 <div className="progress-bar" style={{ width: `${downloadProgress}%` }}>
1767 {downloadProgress}%
1768 </div>
1769 </div>
1770 )}
1771
1772 <div className="modal-actions">
1773 <button
1774 onClick={() => !isDownloading && setShowDownloadModal(false)}
1775 disabled={isDownloading}
1776 >
1777 取消
1778 </button>
1779 <button
1780 onClick={handleDownload}
1781 disabled={isDownloading || !downloadPath}
1782 >
1783 {isDownloading ? '下载中...' : '开始下载'}
1784 </button>
1785 </div>
1786 </div>
1787 </div>
1788 )}
Akane121765b61a72025-05-17 13:52:25 +08001789 </div>
22301080a93bebb2025-05-27 19:48:11 +08001790 );
Akane121765b61a72025-05-17 13:52:25 +08001791};
1792
1793export default Dashboard;