blob: 40bd19dd2734444c864321def2e36d30dfd5e345 [file] [log] [blame]
22301080a93bebb2025-05-27 19:48:11 +08001import React, {useEffect, useState} 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';
10 import { getUserInfo, isAdmin } from '../api/auth';
Akane12173a7bb972025-06-01 01:05:27 +080011import { api } from '../api/auth';
Akane121765b61a72025-05-17 13:52:25 +080012
13
22301080a93bebb2025-05-27 19:48:11 +080014const Dashboard = ({onLogout}) => {
15 const location = useLocation();
16 const [userInfo, setUserInfo] = useState(null);
17 const [loading, setLoading] = useState(false);
18 const [error, setError] = useState('');
19 const [currentSlide, setCurrentSlide] = useState(0);
20 const navigate = useNavigate();
21 const {tab} = useParams();
22 const [showUploadModal, setShowUploadModal] = useState(false);
23 const [isUploading, setIsUploading] = useState(false);
24 const [uploadData, setUploadData] = useState({
25 name: '',
26 type: '',
27 region: '',
28 subtitle: '',
29 resolution: '', // 新增分辨率字段
30 file: null,
31 description: ''
32 });
33 const [showPostModal, setShowPostModal] = useState(false);
34 const [postTitle, setPostTitle] = useState('');
35 const [postContent, setPostContent] = useState('');
36 const [selectedImage, setSelectedImage] = useState(null);
37 const [helpPosts, setHelpPosts] = useState([]);
38 const [helpLoading, setHelpLoading] = useState(false);
39 const [helpError, setHelpError] = useState(null);
40 const [currentPage, setCurrentPage] = useState(1);
41 const [totalPages, setTotalPages] = useState(1);
Akane1217d1e9f712025-05-29 14:36:56 +080042 const [likedPosts,setLikedPosts] = useState({});
DREWae420b22025-06-02 14:07:20 +080043 const [announcements, setAnnouncements] = useState([]);
44 const [carouselDiscounts, setCarouselDiscounts] = useState([]);
45 const [requestLoading, setRequestLoading] = useState(false);
46 const [requestError, setRequestError] = useState(null);
47 const [requestPosts, setRequestPosts] = useState([]);
Akane1217d1e9f712025-05-29 14:36:56 +080048
Akane121765b61a72025-05-17 13:52:25 +080049
50 // 添加状态
22301080a93bebb2025-05-27 19:48:11 +080051 const [torrentPosts, setTorrentPosts] = useState([]);
52 const [torrentLoading, setTorrentLoading] = useState(false);
53 const [torrentError, setTorrentError] = useState(null);
Akane121765b61a72025-05-17 13:52:25 +080054
22301080a93bebb2025-05-27 19:48:11 +080055 const [filteredResources, setFilteredResources] = useState(torrentPosts);
56 const [isAdmin, setIsAdmin] = useState(false);
57
DREWae420b22025-06-02 14:07:20 +080058 // 在组件状态中添加
59 const [showDownloadModal, setShowDownloadModal] = useState(false);
60 const [selectedTorrent, setSelectedTorrent] = useState(null);
61 const [downloadProgress, setDownloadProgress] = useState(0);
62 const [isDownloading, setIsDownloading] = useState(false);
63 const [downloadPath, setDownloadPath] = useState('D:/studies/ptPlatform/torrent');
64
Akane12173a7bb972025-06-01 01:05:27 +080065 // 新增搜索状态
66 const [announcementSearch, setAnnouncementSearch] = useState('');
67 const [shareSearch, setShareSearch] = useState('');
68 const [requestSearch, setRequestSearch] = useState('');
69 const [helpSearch, setHelpSearch] = useState('');
70
22301080a93bebb2025-05-27 19:48:11 +080071
72 const activeTab = tab || 'announcement'; // 如果没有tab参数,则默认为announcement
73 // 从location.state中初始化状态
74
75
76 const handleTabChange = (tabName) => {
77 navigate(`/dashboard/${tabName}`, {
78 state: {
79 savedFilters: selectedFilters, // 使用新的筛选状态 // 保留现有状态
80 activeTab: tabName // 可选,如果其他组件依赖这个 state
81 }
82 });
83 };
84
Akane12173a7bb972025-06-01 01:05:27 +080085
DREWae420b22025-06-02 14:07:20 +080086
87 //公告区
88 // 添加获取公告的方法
89 const fetchAnnouncements = async () => {
90 try {
91 const response = await getLatestAnnouncements();
92 setAnnouncements(response.data.data.announcements || []);
93 } catch (error) {
94 console.error('获取公告失败:', error);
95 }
Akane12173a7bb972025-06-01 01:05:27 +080096 };
97
DREWae420b22025-06-02 14:07:20 +080098 useEffect(() => {
99 if (activeTab === 'announcement') {
100 fetchAnnouncements();
101 fetchDiscountsForCarousel();
102 }
103 }, [activeTab]);
Akane12173a7bb972025-06-01 01:05:27 +0800104
DREWae420b22025-06-02 14:07:20 +0800105 const fetchDiscountsForCarousel = async () => {
106 try {
107 const all = await getAllDiscounts();
108 console.log("返回的折扣数据:", all);
109 const now = new Date();
22301080a93bebb2025-05-27 19:48:11 +0800110
DREWae420b22025-06-02 14:07:20 +0800111 // ⚠️ 使用 Date.parse 确保兼容 ISO 格式
112 const ongoing = all.filter(d =>
113 Date.parse(d.startTime) <= now.getTime() && Date.parse(d.endTime) >= now.getTime()
114 );
115
116 const upcoming = all
117 .filter(d => Date.parse(d.startTime) > now.getTime())
118 .sort((a, b) => Date.parse(a.startTime) - Date.parse(b.startTime));
119
120 const selected = [...ongoing.slice(0, 3)];
121
122 while (selected.length < 3 && upcoming.length > 0) {
123 selected.push(upcoming.shift());
124 }
125
126 setCarouselDiscounts(selected);
127 } catch (e) {
128 console.error("获取折扣失败:", e);
129 }
130 };
131
132 // 修改handleAnnouncementClick函数中的state传递,移除不必要的字段
22301080a93bebb2025-05-27 19:48:11 +0800133 const handleAnnouncementClick = (announcement, e) => {
134 if (!e.target.closest('.exclude-click')) {
135 navigate(`/announcement/${announcement.id}`, {
136 state: {
137 announcement,
138 returnPath: `/dashboard/${activeTab}`,
139 scrollPosition: window.scrollY,
140 activeTab
141 }
142 });
Akane121765b61a72025-05-17 13:52:25 +0800143 }
22301080a93bebb2025-05-27 19:48:11 +0800144 };
Akane121765b61a72025-05-17 13:52:25 +0800145
DREWae420b22025-06-02 14:07:20 +0800146
147   // 公告区搜索处理
148    const handleSearchAnnouncement = (e) => {
149        setAnnouncementSearch(e.target.value);
150    };
151
152    // 修改后的搜索函数
153    const handleSearchShare = async () => {
154        try {
155            setTorrentLoading(true);
156            const response = await searchTorrents(shareSearch, 1);
157            if (response.data.code === 200) {
158                setTorrentPosts(response.data.data.records);
159                const total = response.data.data.total;
160                setTotalPages(Math.ceil(total / 5));
161                setCurrentPage(1);
162            } else {
163                setTorrentError(response.data.message || '搜索失败');
164            }
165        } catch (err) {
166            setTorrentError(err.message || '搜索失败');
167        } finally {
168            setTorrentLoading(false);
169        }
170    };
171
172    const handleResetShareSearch = async () => {
173        setShareSearch('');
174        setSelectedFilters(
175            Object.keys(filterCategories).reduce((acc, category) => {
176                acc[category] = 'all';
177                return acc;
178            }, {})
179        );
180        await fetchTorrentPosts(1, true);
181    };
182
183    // 添加搜索函数
184    const handleSearchRequest = async () => {
185        try {
186        setRequestLoading(true);
187        const response = await searchRequestPosts(requestSearch, currentPage);
188        if (response.data.code === 200) {
189            const postsWithCounts = await Promise.all(
190            response.data.data.records.map(async (post) => {
191                try {
192                const detailResponse = await getRequestPostDetail(post.id);
193                if (detailResponse.data.code === 200) {
194                    return {
195                    ...post,
196                    replyCount: detailResponse.data.data.post.replyCount || 0,
197                    isLiked: false
198                    };
199                }
200                return post;
201                } catch (err) {
202                console.error(`获取帖子${post.id}详情失败:`, err);
203                return post;
204                }
205            })
206            );
207            setRequestPosts(postsWithCounts);
208            setTotalPages(Math.ceil(response.data.data.total / 5));
209        } else {
210            setRequestError(response.data.message || '搜索失败');
211        }
212        } catch (err) {
213        setRequestError(err.message || '搜索失败');
214        } finally {
215        setRequestLoading(false);
216        }
217    };
218   
219    // 添加重置搜索函数
220    const handleResetRequestSearch = async () => {
221        setRequestSearch('');
222 await fetchRequestPosts(1); // 重置到第一页
223 };
224
225   // 添加搜索函数
226    const handleSearchHelp = async () => {
227        try {
228        setHelpLoading(true);
229        const response = await searchHelpPosts(helpSearch, currentPage);
230        if (response.data.code === 200) {
231            const postsWithCounts = await Promise.all(
232            response.data.data.records.map(async (post) => {
233                try {
234                const detailResponse = await getHelpPostDetail(post.id);
235                if (detailResponse.data.code === 200) {
236                    return {
237                    ...post,
238                    replyCount: detailResponse.data.data.post.replyCount || 0,
239                    isLiked: false
240                    };
241                }
242                return post;
243                } catch (err) {
244                console.error(`获取帖子${post.id}详情失败:`, err);
245                return post;
246                }
247            })
248            );
249            setHelpPosts(postsWithCounts);
250            setTotalPages(Math.ceil(response.data.data.total / 5));
251        } else {
252            setHelpError(response.data.message || '搜索失败');
253        }
254        } catch (err) {
255        setHelpError(err.message || '搜索失败');
256        } finally {
257        setHelpLoading(false);
258        }
259    };
260   
261    // 添加重置搜索函数
262    const handleResetHelpSearch = async () => {
263        setHelpSearch('');
264 await fetchHelpPosts(1); // 重置到第一页
265 };
266
267
268
Akane121765b61a72025-05-17 13:52:25 +0800269
22301080a93bebb2025-05-27 19:48:11 +0800270 //资源区
271 const handleFileChange = (e) => {
272 setUploadData({...uploadData, file: e.target.files[0]});
273 };
Akane121765b61a72025-05-17 13:52:25 +0800274
22301080a93bebb2025-05-27 19:48:11 +0800275 const handleUploadSubmit = async (e) => {
276 e.preventDefault();
277 try {
278 setIsUploading(true);
Akane121765b61a72025-05-17 13:52:25 +0800279
22301080a93bebb2025-05-27 19:48:11 +0800280 const torrentData = {
281 torrentName: uploadData.name,
282 description: uploadData.description,
283 category: uploadData.type,
284 region: uploadData.region,
285 resolution: uploadData.resolution,
286 subtitle: uploadData.subtitle
287 };
Akane121765b61a72025-05-17 13:52:25 +0800288
DREWae420b22025-06-02 14:07:20 +0800289 await createTorrent(uploadData.file, torrentData, (progress) => {
290 console.log(`上传进度: ${progress}%`);
291 // 这里可以添加进度条更新逻辑
292 });
22301080a93bebb2025-05-27 19:48:11 +0800293
294 // 上传成功处理
295 setShowUploadModal(false);
296 setUploadData({
297 name: '',
298 type: '',
299 region: '',
300 subtitle: '',
301 resolution: '',
302 file: null,
303 description: ''
304 });
305 alert('种子创建成功!');
306
307 // 刷新列表
308 await fetchTorrentPosts(currentPage);
309
310 } catch (error) {
311 console.error('创建失败:', error);
312 alert('创建失败: ' + (error.response?.data?.message || error.message));
313 } finally {
314 setIsUploading(false);
315 }
316 };
317
DREWae420b22025-06-02 14:07:20 +0800318 // 处理下载按钮点击
319 const handleDownloadClick = (torrent, e) => {
320 e.stopPropagation();
321 setSelectedTorrent(torrent);
322 setShowDownloadModal(true);
323 };
324
325 // 执行下载
326 const handleDownload = async () => {
327 if (!selectedTorrent || !downloadPath) return;
328
329 setIsDownloading(true);
330 setDownloadProgress(0);
331
332 try {
333 // 标准化路径
334 const cleanPath = downloadPath
335 .replace(/\\/g, '/') // 统一使用正斜杠
336 .replace(/\/+/g, '/') // 去除多余斜杠
337 .trim();
338
339 // 确保路径以斜杠结尾
340 const finalPath = cleanPath.endsWith('/') ? cleanPath : cleanPath + '/';
341
342 // 发起下载请求
343 await downloadTorrent(selectedTorrent.id, finalPath);
344
345 // 开始轮询进度
346 const interval = setInterval(async () => {
347 try {
348 const res = await getDownloadProgress();
349 const progresses = res.data.progresses;
350
351 if (progresses) {
352 // 使用完整的 torrent 文件路径作为键
353 const torrentPath = selectedTorrent.filePath.replace(/\\/g, '/');
354 const torrentHash = selectedTorrent.hash;
355 // 查找匹配的进度
356 let foundProgress = null;
357 for (const [key, value] of Object.entries(progresses)) {
358 const normalizedKey = key.replace(/\\/g, '/');
359 if (normalizedKey.includes(selectedTorrent.hash) ||
360 normalizedKey.includes(selectedTorrent.torrentName)) {
361 foundProgress = value;
362 break;
363 }
364 }
365 if (foundProgress !== null) {
366 const newProgress = Math.round(foundProgress * 100);
367 setDownloadProgress(newProgress);
368
369 // 检查是否下载完成
370 if (newProgress >= 100) {
371 clearInterval(interval);
372 setIsDownloading(false);
373 message.success('下载完成!');
374 setTimeout(() => setShowDownloadModal(false), 2000);
375 }
376 } else {
377 console.log('当前下载进度:', progresses); // 添加日志
378 }
379 }
380 } catch (error) {
381 console.error('获取进度失败:', error);
382 // 如果获取进度失败但文件已存在,也视为完成
383 const filePath = `${finalPath}${selectedTorrent.torrentName || 'downloaded_file'}`;
384 try {
385 const exists = await checkFileExists(filePath);
386 if (exists) {
387 clearInterval(interval);
388 setDownloadProgress(100);
389 setIsDownloading(false);
390 message.success('下载完成!');
391 setTimeout(() => setShowDownloadModal(false), 2000);
392 }
393 } catch (e) {
394 console.error('文件检查失败:', e);
395 }
396 }
397 }, 2000);
398
399 return () => clearInterval(interval);
400 } catch (error) {
401 setIsDownloading(false);
402 if (error.response && error.response.status === 409) {
403 message.error('分享率不足,无法下载此资源');
404 } else {
405 message.error('下载失败: ' + (error.message || '未知错误'));
406 }
407 }
408 };
409
410 const checkFileExists = async (filePath) => {
411 try {
412 // 这里需要根据您的实际环境实现文件存在性检查
413 // 如果是Electron应用,可以使用Node.js的fs模块
414 // 如果是纯前端,可能需要通过API请求后端检查
415 return true; // 暂时返回true,实际实现需要修改
416 } catch (e) {
417 console.error('检查文件存在性失败:', e);
418 return false;
419 }
420 };
421
422 const handleDeleteTorrent = async (torrentId, e) => {
423 e.stopPropagation(); // 阻止事件冒泡,避免触发资源项的点击事件
424
425 try {
426 // 确认删除
427 if (!window.confirm('确定要删除这个种子吗?此操作不可撤销!')) {
428 return;
429 }
430
431 // 调用删除API
432 await deleteTorrent(torrentId);
433
434 // 删除成功后刷新列表
435 message.success('种子删除成功');
436 await fetchTorrentPosts(currentPage);
437 } catch (error) {
438 console.error('删除种子失败:', error);
439 message.error('删除种子失败: ' + (error.response?.data?.message || error.message));
440 }
441 };
442
443 const handleRequestPostSubmit = async (e) => {
22301080a93bebb2025-05-27 19:48:11 +0800444 e.preventDefault();
445 try {
Akane1217d1e9f712025-05-29 14:36:56 +0800446 const username = localStorage.getItem('username');
DREWae420b22025-06-02 14:07:20 +0800447 const response = await createRequestPost(
448 postTitle,
449 postContent,
450 username,
451 selectedImage
452 );
453
454 if (response.data.code === 200) {
455 // 刷新帖子列表
456
457 await fetchRequestPosts(currentPage);
458 // 重置表单
459 setShowPostModal(false);
460 setPostTitle('');
461 setPostContent('');
462 setSelectedImage(null);
463 } else {
464 setHelpError(response.data.message || '发帖失败');
465 }
466 } catch (err) {
467 setHelpError(err.message || '发帖失败');
468 }
469 };
470
471 const handleHelpPostSubmit = async (e) => {
472 e.preventDefault();
473 try {
474 const username = localStorage.getItem('username');
475 const response = await createHelpPost(
Akane1217d1e9f712025-05-29 14:36:56 +0800476 postTitle,
477 postContent,
478 username,
479 selectedImage
480 );
481
482 if (response.data.code === 200) {
483 // 刷新帖子列表
484 await fetchHelpPosts(currentPage);
DREWae420b22025-06-02 14:07:20 +0800485
Akane1217d1e9f712025-05-29 14:36:56 +0800486 // 重置表单
487 setShowPostModal(false);
488 setPostTitle('');
489 setPostContent('');
490 setSelectedImage(null);
491 } else {
492 setHelpError(response.data.message || '发帖失败');
493 }
22301080a93bebb2025-05-27 19:48:11 +0800494 } catch (err) {
Akane1217d1e9f712025-05-29 14:36:56 +0800495 setHelpError(err.message || '发帖失败');
22301080a93bebb2025-05-27 19:48:11 +0800496 }
Akane1217d1e9f712025-05-29 14:36:56 +0800497 };
Akane121765b61a72025-05-17 13:52:25 +0800498
Akane12173a7bb972025-06-01 01:05:27 +0800499
500const fetchTorrentPosts = async (page = 1, isReset = false) => {
501 setTorrentLoading(true);
502 try {
503 const params = {
504 page,
505 size: 5
506 };
507
508 // 如果有筛选条件且不是重置操作
509 if (!isReset && Object.values(selectedFilters).some(v => v !== 'all')) {
510 if (selectedFilters.type !== 'all') params.category = selectedFilters.type;
511 if (selectedFilters.subtitle !== 'all') params.subtitle = selectedFilters.subtitle;
512 if (selectedFilters.region !== 'all') params.region = selectedFilters.region;
513 if (selectedFilters.resolution !== 'all') params.resolution = selectedFilters.resolution;
22301080a93bebb2025-05-27 19:48:11 +0800514 }
Akane12173a7bb972025-06-01 01:05:27 +0800515
516 const response = (shareSearch && !isReset)
517 ? await searchTorrents(shareSearch, page)
518 : await api.get('http://localhost:8088/torrent', { params });
519
520 if (response.data.code === 200) {
521 setTorrentPosts(response.data.data.records);
522 const total = response.data.data.total;
523 setTotalPages(Math.ceil(total / 5));
524 setCurrentPage(page);
525 } else {
526 setTorrentError(response.data.message);
527 }
528 } catch (err) {
529 setTorrentError(err.message);
530 } finally {
531 setTorrentLoading(false);
532 }
533};
Akane121765b61a72025-05-17 13:52:25 +0800534
22301080a93bebb2025-05-27 19:48:11 +0800535 // 在useEffect中调用
536 useEffect(() => {
537 if (activeTab === 'share') {
538 fetchTorrentPosts();
539 }
540 }, [activeTab]);
Akane121765b61a72025-05-17 13:52:25 +0800541
DREWae420b22025-06-02 14:07:20 +0800542const fetchRequestPosts = async (page = 1) => {
543 setRequestLoading(true);
544 try {
545 const response = await getRequestPosts(page);
546 if (response.data.code === 200) {
547 const postsWithCounts = await Promise.all(
548 response.data.data.records.map(async (post) => {
549 try {
550 const detailResponse = await getRequestPostDetail(post.id);
551 if (detailResponse.data.code === 200) {
552 return {
553 ...post,
554 replyCount: detailResponse.data.data.post.replyCount || 0,
555 isLiked: false // 根据需要添加其他字段
556 };
557 }
558 return post; // 如果获取详情失败,返回原始帖子数据
559 } catch (err) {
560 console.error(`获取帖子${post.id}详情失败:`, err);
561 return post;
562 }
563 })
564 );
565 setRequestPosts(postsWithCounts);
566 setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条
567 setCurrentPage(page);
568 } else {
569 setRequestError(response.data.message || '获取求助帖失败');
570 }
571 } catch (err) {
572 setRequestError(err.message || '获取求助帖失败');
573 } finally {
574 setRequestLoading(false);
575 }
576 };
577
22301080a93bebb2025-05-27 19:48:11 +0800578 const handleImageUpload = (e) => {
579 setSelectedImage(e.target.files[0]);
580 };
Akane121765b61a72025-05-17 13:52:25 +0800581
582
22301080a93bebb2025-05-27 19:48:11 +0800583 const fetchHelpPosts = async (page = 1) => {
584 setHelpLoading(true);
585 try {
DREWae420b22025-06-02 14:07:20 +0800586 const response = await getHelpPosts(page);
Akane1217d1e9f712025-05-29 14:36:56 +0800587 if (response.data.code === 200) {
588 const postsWithCounts = await Promise.all(
589 response.data.data.records.map(async (post) => {
590 try {
DREWae420b22025-06-02 14:07:20 +0800591 const detailResponse = await getHelpPostDetail(post.id);
Akane1217d1e9f712025-05-29 14:36:56 +0800592 if (detailResponse.data.code === 200) {
593 return {
594 ...post,
595 replyCount: detailResponse.data.data.post.replyCount || 0,
596 isLiked: false // 根据需要添加其他字段
597 };
598 }
599 return post; // 如果获取详情失败,返回原始帖子数据
600 } catch (err) {
601 console.error(`获取帖子${post.id}详情失败:`, err);
602 return post;
603 }
604 })
605 );
606 setHelpPosts(postsWithCounts);
607 setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条
608 setCurrentPage(page);
609 } else {
610 setHelpError(response.data.message || '获取求助帖失败');
611 }
22301080a93bebb2025-05-27 19:48:11 +0800612 } catch (err) {
Akane1217d1e9f712025-05-29 14:36:56 +0800613 setHelpError(err.message || '获取求助帖失败');
22301080a93bebb2025-05-27 19:48:11 +0800614 } finally {
Akane1217d1e9f712025-05-29 14:36:56 +0800615 setHelpLoading(false);
22301080a93bebb2025-05-27 19:48:11 +0800616 }
Akane1217d1e9f712025-05-29 14:36:56 +0800617 };
618
619
22301080a93bebb2025-05-27 19:48:11 +0800620 useEffect(() => {
DREWae420b22025-06-02 14:07:20 +0800621 if (activeTab === 'request') {
622 fetchRequestPosts(currentPage);
22301080a93bebb2025-05-27 19:48:11 +0800623 }
624 }, [activeTab, currentPage]); // 添加 currentPage 作为依赖
Akane121765b61a72025-05-17 13:52:25 +0800625
626
Akane12173a7bb972025-06-01 01:05:27 +0800627 // 分类维度配置
22301080a93bebb2025-05-27 19:48:11 +0800628 const filterCategories = {
629 type: {
630 label: '类型',
631 options: {
632 'all': '全部',
633 '电影': '电影',
634 '电视剧': '电视剧',
635 '动漫': '动漫',
Akane12173a7bb972025-06-01 01:05:27 +0800636 '综艺': '综艺',
637 '音乐': '音乐',
638 '其他': '其他'
22301080a93bebb2025-05-27 19:48:11 +0800639 }
640 },
641 subtitle: {
642 label: '字幕',
643 options: {
644 'all': '全部',
Akane12173a7bb972025-06-01 01:05:27 +0800645 '无需字幕': '无需字幕',
646 '暂无字幕': '暂无字幕',
647 '自带中文字幕': '自带中文字幕',
648 '自带双语字幕(含中文)': '自带双语字幕(含中文)',
649 '附件中文字幕': '附件中文字幕',
650 '附件双语字幕': '附件双语字幕'
22301080a93bebb2025-05-27 19:48:11 +0800651 }
652 },
653 region: {
654 label: '地区',
655 options: {
656 'all': '全部',
Akane12173a7bb972025-06-01 01:05:27 +0800657 '中国': '中国',
658 '英国': '英国',
659 '美国': '美国',
660 '日本': '日本',
661 '韩国': '韩国',
662 '其他': '其他'
663 }
664 },
665 resolution: {
666 label: '分辨率',
667 options: {
668 'all': '全部',
669 '4K': '4K',
670 '2K': '2K',
671 '1080P': '1080P',
672 '720P': '720P',
673 'SD': 'SD',
674 '无损音源': '无损音源',
675 '杜比全景声': '杜比全景声',
676 '其他': '其他'
22301080a93bebb2025-05-27 19:48:11 +0800677 }
678 }
679 };
22301080a93bebb2025-05-27 19:48:11 +0800680 const [selectedFilters, setSelectedFilters] = useState(
681 location.state?.savedFilters ||
682 Object.keys(filterCategories).reduce((acc, category) => {
683 acc[category] = 'all';
684 return acc;
685 }, {})
686 );
Akane121765b61a72025-05-17 13:52:25 +0800687
Akane12173a7bb972025-06-01 01:05:27 +0800688 // 处理筛选条件变更
22301080a93bebb2025-05-27 19:48:11 +0800689 const handleFilterSelect = (category, value) => {
690 setSelectedFilters(prev => ({
691 ...prev,
Akane12173a7bb972025-06-01 01:05:27 +0800692 [category]: prev[category] === value ? 'all' : value
22301080a93bebb2025-05-27 19:48:11 +0800693 }));
Akane121765b61a72025-05-17 13:52:25 +0800694 };
695
Akane12173a7bb972025-06-01 01:05:27 +0800696 // 应用筛选条件
697 const applyFilters = async () => {
698 try {
699 setTorrentLoading(true);
700
701 // 构建查询参数
702 const params = {
703 page: 1, // 从第一页开始
704 size: 5
705 };
706
707 // 添加筛选条件
708 if (selectedFilters.type !== 'all') {
709 params.category = selectedFilters.type;
710 }
711 if (selectedFilters.subtitle !== 'all') {
712 params.subtitle = selectedFilters.subtitle;
713 }
714 if (selectedFilters.region !== 'all') {
715 params.region = selectedFilters.region;
716 }
717 if (selectedFilters.resolution !== 'all') {
718 params.resolution = selectedFilters.resolution;
719 }
720
721 // 调用API获取筛选结果
722 const response = await api.get('http://localhost:8088/torrent', { params });
723
724 if (response.data.code === 200) {
725 setTorrentPosts(response.data.data.records);
726 setTotalPages(Math.ceil(response.data.data.total / 5));
727 setCurrentPage(1);
728 } else {
729 setTorrentError(response.data.message || '筛选失败');
730 }
731 } catch (err) {
732 setTorrentError(err.message || '筛选失败');
733 } finally {
734 setTorrentLoading(false);
735 }
22301080a93bebb2025-05-27 19:48:11 +0800736 };
Akane121765b61a72025-05-17 13:52:25 +0800737
Akane121765b61a72025-05-17 13:52:25 +0800738
22301080a93bebb2025-05-27 19:48:11 +0800739 // 恢复滚动位置
740 useEffect(() => {
741 if (location.state?.scrollPosition) {
742 window.scrollTo(0, location.state.scrollPosition);
743 }
744 }, [location.state]);
Akane121765b61a72025-05-17 13:52:25 +0800745
Akane12173a7bb972025-06-01 01:05:27 +0800746 // 在Dashboard.jsx中修改useEffect
747useEffect(() => {
748 const token = localStorage.getItem('token');
749 if (!token) {
750 navigate('/login');
751 return;
752 }
753
754 const fetchUserInfo = async () => {
755 try {
756 setLoading(true);
757 const backendData = await getUserInfo(); // 调用修改后的方法
758 console.log('后端返回的用户数据:', backendData); // 调试用
759
760 setUserInfo({
761 name: backendData.username || '演示用户',
22301080a93bebb2025-05-27 19:48:11 +0800762 avatar: 'https://via.placeholder.com/40',
Akane12173a7bb972025-06-01 01:05:27 +0800763 isAdmin: backendData.authority === 'ADMIN' // 检查 authority 是否为 "ADMIN"
764 });
765 } catch (error) {
766 console.error('获取用户信息失败:', error);
767 setError('获取用户信息失败');
768 } finally {
769 setLoading(false);
770 }
771 };
772
773 fetchUserInfo();
774}, [navigate]);
22301080a93bebb2025-05-27 19:48:11 +0800775
Akane12173a7bb972025-06-01 01:05:27 +0800776
22301080a93bebb2025-05-27 19:48:11 +0800777 useEffect(() => {
778 if (activeTab === 'announcement') {
779 const timer = setInterval(() => {
DREWae420b22025-06-02 14:07:20 +0800780 setCurrentSlide(prev => {
781 const count = carouselDiscounts.length || 1;
782 return (prev + 1) % count;
783 });
22301080a93bebb2025-05-27 19:48:11 +0800784 }, 3000);
785 return () => clearInterval(timer);
786 }
787 }, [activeTab]);
788
789 useEffect(() => {
790 if (activeTab === 'help') {
791 fetchHelpPosts();
792 }
DREWae420b22025-06-02 14:07:20 +0800793 }, [activeTab, currentPage]); // 添加 currentPage 作为依赖
794
795
22301080a93bebb2025-05-27 19:48:11 +0800796
797 const renderContent = () => {
798 switch (activeTab) {
799 case 'announcement':
800 return (
801 <div className="content-area" data-testid="announcement-section">
Akane12173a7bb972025-06-01 01:05:27 +0800802 <div className="section-search-container">
803 <input
804 type="text"
805 placeholder="搜索公告..."
806 value={announcementSearch}
807 onChange={(e) => setAnnouncementSearch(e.target.value)}
808 className="section-search-input"
809 onKeyPress={(e) => e.key === 'Enter' && handleSearchAnnouncement()}
810 />
811 <button
812 className="search-button"
813 onClick={handleSearchAnnouncement}
814 >
815 搜索
816 </button>
817 </div>
22301080a93bebb2025-05-27 19:48:11 +0800818 {/* 轮播图区域 */}
DREWae420b22025-06-02 14:07:20 +0800819 <div className="carousel-container">
820 {carouselDiscounts.length === 0 ? (
821 <div className="carousel-slide active">
822 <div className="carousel-image gray-bg">暂无折扣活动</div>
22301080a93bebb2025-05-27 19:48:11 +0800823 </div>
DREWae420b22025-06-02 14:07:20 +0800824 ) : (
825 carouselDiscounts.map((discount, index) => (
826 <div key={index} className={`carousel-slide ${currentSlide === index ? 'active' : ''}`}>
827 <div className="carousel-image gray-bg">
828 <h3>{discount.type}</h3>
829 <p>{discount.name}</p>
830 <p>{new Date(discount.startTime).toLocaleDateString()} ~ {new Date(discount.endTime).toLocaleDateString()}</p>
831 </div>
22301080a93bebb2025-05-27 19:48:11 +0800832 </div>
DREWae420b22025-06-02 14:07:20 +0800833 ))
834 )}
835 <div className="carousel-dots">
836 {carouselDiscounts.map((_, index) => (
837 <span key={index} className={`dot ${currentSlide === index ? 'active' : ''}`}></span>
838 ))}
22301080a93bebb2025-05-27 19:48:11 +0800839 </div>
DREWae420b22025-06-02 14:07:20 +0800840 </div>
841
842
22301080a93bebb2025-05-27 19:48:11 +0800843
844 {/* 公告区块区域 */}
845 <div className="announcement-grid">
846 {announcements.map(announcement => (
847 <div
848 key={announcement.id}
849 className="announcement-card"
850 onClick={(e) => handleAnnouncementClick(announcement, e)}
851 >
852 <h3>{announcement.title}</h3>
DREWae420b22025-06-02 14:07:20 +0800853 <p>{announcement.content.substring(0, 100)}...</p>
22301080a93bebb2025-05-27 19:48:11 +0800854 <div className="announcement-footer exclude-click">
22301080a93bebb2025-05-27 19:48:11 +0800855 <span>{announcement.date}</span>
856 </div>
857 </div>
858 ))}
859 </div>
Akane121765b61a72025-05-17 13:52:25 +0800860 </div>
22301080a93bebb2025-05-27 19:48:11 +0800861 );
862 case 'share':
863 return (
864 <div className="content-area" data-testid="share-section">
Akane12173a7bb972025-06-01 01:05:27 +0800865 {/* 分享区搜索框 */}
866 <div className="section-search-container">
867 <input
868 type="text"
869 placeholder="搜索资源..."
870 value={shareSearch}
871 onChange={(e) => setShareSearch(e.target.value)}
872 className="section-search-input"
873 onKeyPress={(e) => e.key === 'Enter' && handleSearchShare()}
874 />
875 <button
876 className="search-button"
877 onClick={handleSearchShare}
878 >
879 搜索
880 </button>
881 <button
882 className="reset-button"
883 onClick={handleResetShareSearch} // 使用新的重置函数
884 style={{marginLeft: '10px'}}
885 >
886 重置
887 </button>
888 </div>
889
22301080a93bebb2025-05-27 19:48:11 +0800890 {/* 上传按钮 - 添加在筛选区上方 */}
891 <div className="upload-header">
892 <button
893 className="upload-btn"
894 onClick={() => setShowUploadModal(true)}
895 >
896 上传种子
897 </button>
898 </div>
899 {/* 分类筛选区 */}
900 <div className="filter-section">
901 {Object.entries(filterCategories).map(([category, config]) => (
902 <div key={category} className="filter-group">
903 <h4>{config.label}:</h4>
904 <div className="filter-options">
905 {Object.entries(config.options).map(([value, label]) => (
906 <button
907 key={value}
908 className={`filter-btn ${
909 selectedFilters[category] === value ? 'active' : ''
910 }`}
911 onClick={() => handleFilterSelect(category, value)}
912 >
913 {label}
914 </button>
915 ))}
916 </div>
917 </div>
918 ))}
919
920 <button
921 className="confirm-btn"
922 onClick={applyFilters}
923 >
924 确认筛选
925 </button>
926 </div>
927
928 {/* 上传弹窗 */}
929 {showUploadModal && (
930 <div className="modal-overlay">
931 <div className="upload-modal">
932 <h3>上传新种子</h3>
933 <button
934 className="close-btn"
935 onClick={() => setShowUploadModal(false)}
936 >
937 ×
938 </button>
939
940 <form onSubmit={handleUploadSubmit}>
941 <div className="form-group">
942 <label>种子名称</label>
943 <input
944 type="text"
945 value={uploadData.name}
946 onChange={(e) => setUploadData({...uploadData, name: e.target.value})}
947 required
948 />
949 </div>
950
951 <div className="form-group">
952 <label>资源类型</label>
953 <select
954 value={uploadData.type}
955 onChange={(e) => setUploadData({...uploadData, type: e.target.value})}
956 required
957 >
958 <option value="">请选择</option>
959 <option value="电影">电影</option>
960 <option value="电视剧">电视剧</option>
961 <option value="动漫">动漫</option>
962 <option value="综艺">综艺</option>
963 <option value="音乐">音乐</option>
Akane12173a7bb972025-06-01 01:05:27 +0800964 <option value="其他">其他</option>
22301080a93bebb2025-05-27 19:48:11 +0800965 </select>
966 </div>
967
Akane12173a7bb972025-06-01 01:05:27 +0800968 {/* 修改后的地区下拉框 */}
22301080a93bebb2025-05-27 19:48:11 +0800969 <div className="form-group">
970 <label>地区</label>
Akane12173a7bb972025-06-01 01:05:27 +0800971 <select
22301080a93bebb2025-05-27 19:48:11 +0800972 value={uploadData.region || ''}
973 onChange={(e) => setUploadData({...uploadData, region: e.target.value})}
22301080a93bebb2025-05-27 19:48:11 +0800974 required
Akane12173a7bb972025-06-01 01:05:27 +0800975 >
976 <option value="">请选择</option>
977 <option value="中国">中国</option>
978 <option value="英国">英国</option>
979 <option value="美国">美国</option>
980 <option value="日本">日本</option>
981 <option value="韩国">韩国</option>
982 <option value="其他">其他</option>
983 </select>
22301080a93bebb2025-05-27 19:48:11 +0800984 </div>
985
986 {/* 添加分辨率下拉框 */}
987 <div className="form-group">
988 <label>分辨率</label>
989 <select
990 value={uploadData.resolution || ''}
991 onChange={(e) => setUploadData({
992 ...uploadData,
993 resolution: e.target.value
994 })}
995 required
996 >
997 <option value="">请选择</option>
998 <option value="4K">4K</option>
999 <option value="2K">2K</option>
1000 <option value="1080P">1080P</option>
1001 <option value="720P">720P</option>
1002 <option value="SD">SD</option>
1003 <option value="无损音源">无损音源</option>
1004 <option value="杜比全景声">杜比全景声</option>
1005 <option value="其他">其他</option>
1006 </select>
1007 </div>
1008
1009
1010 {/* 新增字幕语言下拉框 */}
1011 <div className="form-group">
1012 <label>字幕语言</label>
1013 <select
1014 value={uploadData.subtitle || ''}
1015 onChange={(e) => setUploadData({
1016 ...uploadData,
1017 subtitle: e.target.value
1018 })}
1019 required
1020 >
1021 <option value="">请选择</option>
1022 <option value="无需字幕">无需字幕</option>
1023 <option value="暂无字幕">暂无字幕</option>
1024 <option value="自带中文字幕">自带中文字幕</option>
1025 <option value="自带双语字幕(含中文)">自带双语字幕(含中文)</option>
1026 <option value="附件中文字幕">附件中文字幕</option>
1027 <option value="附件双语字幕">附件双语字幕</option>
1028 </select>
1029 </div>
1030
1031 <div className="form-group">
1032 <label>种子文件</label>
1033 <input
1034 type="file"
1035 accept=".torrent"
1036 onChange={handleFileChange}
1037
1038 />
1039 </div>
1040
1041 <div className="form-group">
1042 <label>简介</label>
1043 <textarea
1044 value={uploadData.description}
1045 onChange={(e) => setUploadData({
1046 ...uploadData,
1047 description: e.target.value
1048 })}
1049 />
1050 </div>
1051
1052 <div className="form-actions">
1053 <button
1054 type="button"
1055 className="cancel-btn"
1056 onClick={() => setShowUploadModal(false)}
1057 >
1058 取消
1059 </button>
1060 <button
1061 type="submit"
1062 className="confirm-btn"
1063 disabled={isUploading}
1064 >
1065 {isUploading ? '上传中...' : '确认上传'}
1066 </button>
1067 </div>
1068 </form>
1069 </div>
1070 </div>
Akane121765b61a72025-05-17 13:52:25 +08001071 )}
22301080a93bebb2025-05-27 19:48:11 +08001072
1073 <div className="resource-list">
1074 {torrentPosts.map(torrent => (
1075 <div
1076 key={torrent.id}
1077 className="resource-item"
1078 onClick={() => navigate(`/torrent/${torrent.id}`)}
1079 >
1080 <div className="resource-poster">
1081 <div className="poster-image gray-bg">{torrent.torrentName.charAt(0)}</div>
1082 </div>
1083 <div className="resource-info">
1084 <h3 className="resource-title">{torrent.torrentName}</h3>
1085 <p className="resource-meta">
1086 {torrent.resolution} | {torrent.region} | {torrent.category}
1087 </p>
1088 <p className="resource-subtitle">字幕: {torrent.subtitle}</p>
1089 </div>
1090 <div className="resource-stats">
Akane12173a7bb972025-06-01 01:05:27 +08001091 <span className="stat">{torrent.size}</span>
22301080a93bebb2025-05-27 19:48:11 +08001092 <span className="stat">发布者: {torrent.username}</span>
1093 </div>
1094 <button
1095 className="download-btn"
DREWae420b22025-06-02 14:07:20 +08001096 onClick={(e) => handleDownloadClick(torrent, e)}
22301080a93bebb2025-05-27 19:48:11 +08001097 >
1098 立即下载
1099 </button>
DREWae420b22025-06-02 14:07:20 +08001100 {/* 添加删除按钮 - 只有管理员或发布者可见 */}
1101 {(userInfo?.isAdmin || userInfo?.name === torrent.username) && (
1102 <button
1103 className="delete-btn"
1104 onClick={(e) => handleDeleteTorrent(torrent.id, e)}
1105 >
1106 删除
1107 </button>
1108 )}
22301080a93bebb2025-05-27 19:48:11 +08001109 </div>
1110 ))}
1111 </div>
1112
Akane12173a7bb972025-06-01 01:05:27 +08001113 {totalPages > 1 && (
22301080a93bebb2025-05-27 19:48:11 +08001114 <div className="pagination">
1115 <button
1116 onClick={() => fetchTorrentPosts(currentPage - 1)}
1117 disabled={currentPage === 1}
1118 >
1119 上一页
1120 </button>
1121
1122 {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
1123 <button
1124 key={page}
1125 onClick={() => fetchTorrentPosts(page)}
1126 className={currentPage === page ? 'active' : ''}
1127 >
1128 {page}
1129 </button>
1130 ))}
1131
1132 <button
1133 onClick={() => fetchTorrentPosts(currentPage + 1)}
1134 disabled={currentPage === totalPages}
1135 >
1136 下一页
1137 </button>
1138 </div>
Akane12173a7bb972025-06-01 01:05:27 +08001139 )}
Akane121765b61a72025-05-17 13:52:25 +08001140 </div>
22301080a93bebb2025-05-27 19:48:11 +08001141 );
1142 // 在Dashboard.jsx的renderContent函数中修改case 'request'部分
1143 case 'request':
1144 return (
1145 <div className="content-area" data-testid="request-section">
DREWae420b22025-06-02 14:07:20 +08001146 {/* 求助区搜索框 */}
Akane12173a7bb972025-06-01 01:05:27 +08001147 <div className="section-search-container">
1148 <input
1149 type="text"
DREWae420b22025-06-02 14:07:20 +08001150 placeholder="搜索求助..."
Akane12173a7bb972025-06-01 01:05:27 +08001151 value={requestSearch}
1152 onChange={(e) => setRequestSearch(e.target.value)}
1153 className="section-search-input"
1154 onKeyPress={(e) => e.key === 'Enter' && handleSearchRequest()}
1155 />
1156 <button
1157 className="search-button"
1158 onClick={handleSearchRequest}
1159 >
1160 搜索
1161 </button>
DREWae420b22025-06-02 14:07:20 +08001162 <button
1163 className="reset-button"
1164 onClick={handleResetRequestSearch}
1165 style={{marginLeft: '10px'}}
1166 >
1167 重置
1168 </button>
Akane12173a7bb972025-06-01 01:05:27 +08001169 </div>
DREWae420b22025-06-02 14:07:20 +08001170
1171 {/* 新增发帖按钮 */}
1172 <div className="post-header">
1173 <button
1174 className="create-post-btn"
1175 onClick={() => setShowPostModal(true)}
1176 >
1177 发帖求助
1178 </button>
1179 </div>
1180
1181 {/* 加载状态和错误提示 */}
1182 {requestLoading && <div className="loading">加载中...</div>}
1183 {requestError && <div className="error">{helpError}</div>}
22301080a93bebb2025-05-27 19:48:11 +08001184 {/* 求种区帖子列表 */}
1185 <div className="request-list">
DREWae420b22025-06-02 14:07:20 +08001186 {requestPosts.map(post => (
22301080a93bebb2025-05-27 19:48:11 +08001187 <div
1188 key={post.id}
DREWae420b22025-06-02 14:07:20 +08001189 className={`request-post ${post.isSolved ? 'solved' : ''}`}
22301080a93bebb2025-05-27 19:48:11 +08001190 onClick={() => navigate(`/request/${post.id}`)}
1191 >
1192 <div className="post-header">
DREWae420b22025-06-02 14:07:20 +08001193 <img
1194 src={post.authorAvatar || 'https://via.placeholder.com/40'}
1195 alt={post.authorId}
1196 className="post-avatar"
1197 />
1198 <div className="post-author">{post.authorId}</div>
1199 <div className="post-date">
1200 {new Date(post.createTime).toLocaleDateString()}
1201 </div>
1202 {post.isSolved && <span className="solved-badge">已解决</span>}
22301080a93bebb2025-05-27 19:48:11 +08001203 </div>
1204 <h3 className="post-title">{post.title}</h3>
1205 <p className="post-content">{post.content}</p>
1206 <div className="post-stats">
DREWae420b22025-06-02 14:07:20 +08001207 <span className="post-likes">👍 {post.likeCount || 0}</span>
1208 <span className="post-comments">💬 {post.replyCount || 0}</span>
22301080a93bebb2025-05-27 19:48:11 +08001209 </div>
1210 </div>
1211 ))}
1212 </div>
DREWae420b22025-06-02 14:07:20 +08001213 {/* 在帖子列表后添加分页控件 */}
1214 <div className="pagination">
1215 <button
1216 onClick={() => fetchRequestPosts(currentPage - 1)}
1217 disabled={currentPage === 1}
1218 >
1219 上一页
1220 </button>
1221
1222 {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
1223 <button
1224 key={page}
1225 onClick={() => fetchRequestPosts(page)}
1226 className={currentPage === page ? 'active' : ''}
1227 >
1228 {page}
1229 </button>
1230 ))}
1231
1232 <button
1233 onClick={() => fetchRequestPosts(currentPage + 1)}
1234 disabled={currentPage === totalPages}
1235 >
1236 下一页
1237 </button>
1238 </div>
1239 {/* 新增发帖弹窗 */}
1240 {showPostModal && (
1241 <div className="post-modal-overlay">
1242 <div className="post-modal">
1243 <h3>发布求种帖</h3>
1244 <button
1245 className="modal-close-btn"
1246 onClick={() => setShowPostModal(false)}
1247 >
1248 ×
1249 </button>
1250
1251 <form onSubmit={handleRequestPostSubmit}>
1252 <div className="form-group">
1253 <label>帖子标题</label>
1254 <input
1255 type="text"
1256 value={postTitle}
1257 onChange={(e) => setPostTitle(e.target.value)}
1258 placeholder="请输入标题"
1259 required
1260 />
1261 </div>
1262
1263 <div className="form-group">
1264 <label>帖子内容</label>
1265 <textarea
1266 value={postContent}
1267 onChange={(e) => setPostContent(e.target.value)}
1268 placeholder="详细描述你的问题"
1269 required
1270 />
1271 </div>
1272
1273 <div className="form-group">
1274 <label>上传图片</label>
1275 <div className="upload-image-btn">
1276 <input
1277 type="file"
1278 id="image-upload"
1279 accept="image/*"
1280 onChange={handleImageUpload}
1281 style={{display: 'none'}}
1282 />
1283 <label htmlFor="image-upload">
1284 {selectedImage ? '已选择图片' : '选择图片'}
1285 </label>
1286 {selectedImage && (
1287 <span className="image-name">{selectedImage.name}</span>
1288 )}
1289 </div>
1290 </div>
1291
1292 <div className="form-actions">
1293 <button
1294 type="button"
1295 className="cancel-btn"
1296 onClick={() => setShowPostModal(false)}
1297 >
1298 取消
1299 </button>
1300 <button
1301 type="submit"
1302 className="submit-btn"
1303 >
1304 确认发帖
1305 </button>
1306 </div>
1307 </form>
1308 </div>
1309 </div>
1310 )}
Akane121765b61a72025-05-17 13:52:25 +08001311 </div>
DREWae420b22025-06-02 14:07:20 +08001312
1313
1314
1315
22301080a93bebb2025-05-27 19:48:11 +08001316 );
1317 // 在Dashboard.jsx的renderContent函数中修改case 'help'部分
1318 case 'help':
1319 return (
1320 <div className="content-area" data-testid="help-section">
Akane12173a7bb972025-06-01 01:05:27 +08001321 {/* 求助区搜索框 */}
1322 <div className="section-search-container">
1323 <input
1324 type="text"
1325 placeholder="搜索求助..."
1326 value={helpSearch}
1327 onChange={(e) => setHelpSearch(e.target.value)}
1328 className="section-search-input"
1329 onKeyPress={(e) => e.key === 'Enter' && handleSearchHelp()}
1330 />
1331 <button
1332 className="search-button"
1333 onClick={handleSearchHelp}
1334 >
1335 搜索
1336 </button>
1337 <button
1338 className="reset-button"
1339 onClick={handleResetHelpSearch}
1340 style={{marginLeft: '10px'}}
1341 >
1342 重置
1343 </button>
1344 </div>
1345
22301080a93bebb2025-05-27 19:48:11 +08001346 {/* 新增发帖按钮 */}
1347 <div className="post-header">
1348 <button
1349 className="create-post-btn"
1350 onClick={() => setShowPostModal(true)}
1351 >
1352 发帖求助
1353 </button>
1354 </div>
1355
1356 {/* 加载状态和错误提示 */}
1357 {helpLoading && <div className="loading">加载中...</div>}
1358 {helpError && <div className="error">{helpError}</div>}
1359
1360 {/* 求助区帖子列表 */}
1361 <div className="help-list">
1362 {helpPosts.map(post => (
1363 <div
1364 key={post.id}
1365 className={`help-post ${post.isSolved ? 'solved' : ''}`}
1366 onClick={() => navigate(`/help/${post.id}`)}
1367 >
1368 <div className="post-header">
1369 <img
1370 src={post.authorAvatar || 'https://via.placeholder.com/40'}
1371 alt={post.authorId}
1372 className="post-avatar"
1373 />
1374 <div className="post-author">{post.authorId}</div>
1375 <div className="post-date">
1376 {new Date(post.createTime).toLocaleDateString()}
1377 </div>
1378 {post.isSolved && <span className="solved-badge">已解决</span>}
1379 </div>
1380 <h3 className="post-title">{post.title}</h3>
1381 <p className="post-content">{post.content}</p>
1382 <div className="post-stats">
1383 <span className="post-likes">👍 {post.likeCount || 0}</span>
1384 <span className="post-comments">💬 {post.replyCount || 0}</span>
1385 </div>
1386 </div>
1387 ))}
1388 </div>
1389
1390 {/* 在帖子列表后添加分页控件 */}
1391 <div className="pagination">
1392 <button
1393 onClick={() => fetchHelpPosts(currentPage - 1)}
1394 disabled={currentPage === 1}
1395 >
1396 上一页
1397 </button>
1398
1399 {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
1400 <button
1401 key={page}
1402 onClick={() => fetchHelpPosts(page)}
1403 className={currentPage === page ? 'active' : ''}
1404 >
1405 {page}
1406 </button>
1407 ))}
1408
1409 <button
1410 onClick={() => fetchHelpPosts(currentPage + 1)}
1411 disabled={currentPage === totalPages}
1412 >
1413 下一页
1414 </button>
1415 </div>
1416
1417 {/* 新增发帖弹窗 */}
1418 {showPostModal && (
1419 <div className="post-modal-overlay">
1420 <div className="post-modal">
1421 <h3>发布求助帖</h3>
1422 <button
1423 className="modal-close-btn"
1424 onClick={() => setShowPostModal(false)}
1425 >
1426 ×
1427 </button>
1428
DREWae420b22025-06-02 14:07:20 +08001429 <form onSubmit={handleHelpPostSubmit}>
22301080a93bebb2025-05-27 19:48:11 +08001430 <div className="form-group">
1431 <label>帖子标题</label>
1432 <input
1433 type="text"
1434 value={postTitle}
1435 onChange={(e) => setPostTitle(e.target.value)}
1436 placeholder="请输入标题"
1437 required
1438 />
1439 </div>
1440
1441 <div className="form-group">
1442 <label>帖子内容</label>
1443 <textarea
1444 value={postContent}
1445 onChange={(e) => setPostContent(e.target.value)}
1446 placeholder="详细描述你的问题"
1447 required
1448 />
1449 </div>
1450
1451 <div className="form-group">
1452 <label>上传图片</label>
1453 <div className="upload-image-btn">
1454 <input
1455 type="file"
1456 id="image-upload"
1457 accept="image/*"
1458 onChange={handleImageUpload}
1459 style={{display: 'none'}}
1460 />
1461 <label htmlFor="image-upload">
1462 {selectedImage ? '已选择图片' : '选择图片'}
1463 </label>
1464 {selectedImage && (
1465 <span className="image-name">{selectedImage.name}</span>
1466 )}
1467 </div>
1468 </div>
1469
1470 <div className="form-actions">
1471 <button
1472 type="button"
1473 className="cancel-btn"
1474 onClick={() => setShowPostModal(false)}
1475 >
1476 取消
1477 </button>
1478 <button
1479 type="submit"
1480 className="submit-btn"
1481 >
1482 确认发帖
1483 </button>
1484 </div>
1485 </form>
1486 </div>
1487 </div>
1488 )}
1489 </div>
1490 );
1491 default:
1492 return <div className="content-area" data-testid="default-section">公告区内容</div>;
1493 }
DREWae420b22025-06-02 14:07:20 +08001494
22301080a93bebb2025-05-27 19:48:11 +08001495 };
1496
1497 if (loading) return <div className="loading">加载中...</div>;
1498 if (error) return <div className="error">{error}</div>;
1499
1500 return (
1501 <div className="dashboard-container" data-testid="dashboard-container">
1502 {/* 顶部栏 */}
1503 <div className="top-bar" data-testid="top-bar">
Akane12173a7bb972025-06-01 01:05:27 +08001504 {/* 平台名称替换搜索框 */}
1505 <div className="platform-name">
1506 <h2>PT资源站</h2>
Akane121765b61a72025-05-17 13:52:25 +08001507 </div>
Akane121765b61a72025-05-17 13:52:25 +08001508
22301080a93bebb2025-05-27 19:48:11 +08001509 <div className="user-actions">
1510 {/* 新增管理员按钮 - 只有管理员可见 */}
1511 {userInfo?.isAdmin && (
1512 <button
1513 className="admin-center-button"
1514 onClick={() => navigate('/administer')}
1515 >
1516 管理员中心
1517 </button>
1518 )}
Akane121765b61a72025-05-17 13:52:25 +08001519
22301080a93bebb2025-05-27 19:48:11 +08001520 <div className="user-info" data-testid="user-info">
1521 <img
1522 src={userInfo?.avatar || 'https://via.placeholder.com/40'}
1523 alt="用户头像"
1524 className="user-avatar"
1525 onClick={() => navigate('/personal')}
1526 style={{cursor: 'pointer'}}
1527 />
1528 <span className="username">{userInfo?.name || '用户'}</span>
1529 <button onClick={onLogout} className="logout-button">退出</button>
1530 </div>
1531 </div>
1532 </div>
1533
1534 {/* 导航栏 */}
1535 {/* handleTabchange函数替换了原本的setactivetab函数 */}
1536 <div className="nav-tabs">
1537 <button
1538 className={`tab-button ${activeTab === 'announcement' ? 'active' : ''}`}
1539 onClick={() => handleTabChange('announcement')}
1540 >
1541 公告区
1542 </button>
1543 <button
1544 className={`tab-button ${activeTab === 'share' ? 'active' : ''}`}
1545 onClick={() => handleTabChange('share')}
1546 >
1547 分享区
1548 </button>
1549 <button
1550 className={`tab-button ${activeTab === 'request' ? 'active' : ''}`}
1551 onClick={() => handleTabChange('request')}
1552 >
1553 求种区
1554 </button>
1555 <button
1556 className={`tab-button ${activeTab === 'help' ? 'active' : ''}`}
1557 onClick={() => handleTabChange('help')}
1558 >
1559 求助区
1560 </button>
1561 </div>
1562
1563 {/* 内容区 */}
1564 {renderContent()}
DREWae420b22025-06-02 14:07:20 +08001565 {/* 下载模态框 - 添加在这里 */}
1566 {showDownloadModal && selectedTorrent && (
1567 <div className="modal-overlay">
1568 <div className="download-modal">
1569 <h3>下载 {selectedTorrent.torrentName}</h3>
1570 <button
1571 className="close-btn"
1572 onClick={() => !isDownloading && setShowDownloadModal(false)}
1573 disabled={isDownloading}
1574 >
1575 ×
1576 </button>
1577
1578 <div className="form-group">
1579 <label>下载路径:</label>
1580 <input
1581 type="text"
1582 value={downloadPath}
1583 onChange={(e) => {
1584 // 实时格式化显示
1585 let path = e.target.value
1586 .replace(/\t/g, '')
1587 .replace(/\\/g, '/')
1588 .replace(/\s+/g, ' ');
1589 setDownloadPath(path);
1590 }}
1591 disabled={isDownloading}
1592 placeholder="例如: D:/downloads/"
1593 />
1594 </div>
1595
1596 {isDownloading && (
1597 <div className="progress-container">
1598 <div className="progress-bar" style={{ width: `${downloadProgress}%` }}>
1599 {downloadProgress}%
1600 </div>
1601 </div>
1602 )}
1603
1604 <div className="modal-actions">
1605 <button
1606 onClick={() => !isDownloading && setShowDownloadModal(false)}
1607 disabled={isDownloading}
1608 >
1609 取消
1610 </button>
1611 <button
1612 onClick={handleDownload}
1613 disabled={isDownloading || !downloadPath}
1614 >
1615 {isDownloading ? '下载中...' : '开始下载'}
1616 </button>
1617 </div>
1618 </div>
1619 </div>
1620 )}
Akane121765b61a72025-05-17 13:52:25 +08001621 </div>
22301080a93bebb2025-05-27 19:48:11 +08001622 );
Akane121765b61a72025-05-17 13:52:25 +08001623};
1624
1625export default Dashboard;