blob: f8f5dd378351dbcfc1db86e78939c7065a7ded0a [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';
3import {createTorrent, getTorrents, searchTorrents} from '../api/torrent';
Akane121765b61a72025-05-17 13:52:25 +08004import './Dashboard.css';
Akane12173a7bb972025-06-01 01:05:27 +08005import {createPost, getPosts, getPostDetail, searchPosts} from '../api/helpPost';
6import { getUserInfo, isAdmin } from '../api/auth';
7import { api } from '../api/auth';
Akane121765b61a72025-05-17 13:52:25 +08008
9
22301080a93bebb2025-05-27 19:48:11 +080010const Dashboard = ({onLogout}) => {
11 const location = useLocation();
12 const [userInfo, setUserInfo] = useState(null);
13 const [loading, setLoading] = useState(false);
14 const [error, setError] = useState('');
15 const [currentSlide, setCurrentSlide] = useState(0);
16 const navigate = useNavigate();
17 const {tab} = useParams();
18 const [showUploadModal, setShowUploadModal] = useState(false);
19 const [isUploading, setIsUploading] = useState(false);
20 const [uploadData, setUploadData] = useState({
21 name: '',
22 type: '',
23 region: '',
24 subtitle: '',
25 resolution: '', // 新增分辨率字段
26 file: null,
27 description: ''
28 });
29 const [showPostModal, setShowPostModal] = useState(false);
30 const [postTitle, setPostTitle] = useState('');
31 const [postContent, setPostContent] = useState('');
32 const [selectedImage, setSelectedImage] = useState(null);
33 const [helpPosts, setHelpPosts] = useState([]);
34 const [helpLoading, setHelpLoading] = useState(false);
35 const [helpError, setHelpError] = useState(null);
36 const [currentPage, setCurrentPage] = useState(1);
37 const [totalPages, setTotalPages] = useState(1);
Akane1217d1e9f712025-05-29 14:36:56 +080038 const [likedPosts,setLikedPosts] = useState({});
39
Akane121765b61a72025-05-17 13:52:25 +080040
41 // 添加状态
22301080a93bebb2025-05-27 19:48:11 +080042 const [torrentPosts, setTorrentPosts] = useState([]);
43 const [torrentLoading, setTorrentLoading] = useState(false);
44 const [torrentError, setTorrentError] = useState(null);
Akane121765b61a72025-05-17 13:52:25 +080045
22301080a93bebb2025-05-27 19:48:11 +080046 const [filteredResources, setFilteredResources] = useState(torrentPosts);
47 const [isAdmin, setIsAdmin] = useState(false);
48
Akane12173a7bb972025-06-01 01:05:27 +080049 // 新增搜索状态
50 const [announcementSearch, setAnnouncementSearch] = useState('');
51 const [shareSearch, setShareSearch] = useState('');
52 const [requestSearch, setRequestSearch] = useState('');
53 const [helpSearch, setHelpSearch] = useState('');
54
22301080a93bebb2025-05-27 19:48:11 +080055
56 const activeTab = tab || 'announcement'; // 如果没有tab参数,则默认为announcement
57 // 从location.state中初始化状态
58
59
60 const handleTabChange = (tabName) => {
61 navigate(`/dashboard/${tabName}`, {
62 state: {
63 savedFilters: selectedFilters, // 使用新的筛选状态 // 保留现有状态
64 activeTab: tabName // 可选,如果其他组件依赖这个 state
65 }
66 });
67 };
68
69 //公告区
70 const [announcements] = useState([
71 {
72 id: 1,
73 title: '系统维护与更新',
74 content: '2023-10-15 02:00-06:00将进行系统维护升级,期间网站将无法访问。本次更新包含:\n1. 数据库服务器迁移\n2. 安全补丁更新\n3. CDN节点优化\n\n请提前做好下载安排。',
75 author: '系统管理员',
76 date: '2023-10-10',
77 excerpt: '2023-10-15 02:00-06:00将进行系统维护,期间无法访问',
78 category: '系统'
79 },
80 {
81 id: 2,
82 title: '资源上新',
83 content: '最新热门电影《奥本海默》4K REMUX资源已上线,包含:\n- 4K HDR版本 (56.8GB)\n- 1080P标准版 (12.3GB)\n- 中英双语字幕\n\n欢迎下载保种!',
84 author: '资源组',
85 date: '2023-10-08',
86 excerpt: '最新热门电影《奥本海默》4K资源已上线',
87 category: '资源'
88 },
89 {
90 id: 3,
91 title: '积分规则调整',
92 content: '自11月1日起,上传资源积分奖励提升20%,具体规则如下:\n- 上传电影资源:每GB 10积分\n- 上传电视剧资源:每GB 8积分\n- 上传动漫资源:每GB 6积分\n\n感谢大家的支持与贡献!',
93 author: '管理员',
94 date: '2023-10-05',
95 excerpt: '自11月1日起,上传资源积分奖励提升20%',
96 category: '公告'
97 },
98 {
99 id: 4,
100 title: '违规处理公告',
101 content: '用户user123因发布虚假资源已被封禁,相关资源已删除。请大家遵守社区规则,维护良好的分享环境。',
102 author: '管理员',
103 date: '2023-10-03',
104 excerpt: '用户user123因发布虚假资源已被封禁',
105 category: '违规'
106 },
107 {
108 id: 5,
109 title: '节日活动',
110 content: '国庆期间所有资源下载积分减半,活动时间:2023年10月1日至2023年10月7日。',
111 author: '活动组',
112 date: '2023-09-30',
113 excerpt: '国庆期间所有资源下载积分减半',
114 category: '活动'
115 },
116 {
117 id: 6,
118 title: '客户端更新',
119 content: 'PT客户端v2.5.0已发布,修复多个BUG,新增资源搜索功能。请尽快更新到最新版本以获得更好的使用体验。',
120 author: '开发组',
121 date: '2023-09-28',
122 excerpt: 'PT客户端v2.5.0已发布,修复多个BUG',
123 category: '更新'
124 },
125 // 其他公告...
126 ]);
127
Akane12173a7bb972025-06-01 01:05:27 +0800128 // 公告区搜索处理
129 const handleSearchAnnouncement = (e) => {
130 setAnnouncementSearch(e.target.value);
131 };
132
133 // 修改后的搜索函数
134 const handleSearchShare = async () => {
135 try {
136 setTorrentLoading(true);
137 const response = await searchTorrents(shareSearch, 1);
138 if (response.data.code === 200) {
139 setTorrentPosts(response.data.data.records);
140 const total = response.data.data.total;
141 setTotalPages(Math.ceil(total / 5));
142 setCurrentPage(1);
143 } else {
144 setTorrentError(response.data.message || '搜索失败');
145 }
146 } catch (err) {
147 setTorrentError(err.message || '搜索失败');
148 } finally {
149 setTorrentLoading(false);
150 }
151 };
152
153 const handleResetShareSearch = async () => {
154 setShareSearch('');
155 setSelectedFilters(
156 Object.keys(filterCategories).reduce((acc, category) => {
157 acc[category] = 'all';
158 return acc;
159 }, {})
160 );
161 await fetchTorrentPosts(1, true);
162 };
163
164 // 求种区搜索处理
165 const handleSearchRequest = (e) => {
166 setRequestSearch(e.target.value);
167 };
168
169 // 添加搜索函数
170 const handleSearchHelp = async () => {
171 try {
172 setHelpLoading(true);
173 const response = await searchPosts(helpSearch, currentPage);
174 if (response.data.code === 200) {
175 const postsWithCounts = await Promise.all(
176 response.data.data.records.map(async (post) => {
177 try {
178 const detailResponse = await getPostDetail(post.id);
179 if (detailResponse.data.code === 200) {
180 return {
181 ...post,
182 replyCount: detailResponse.data.data.post.replyCount || 0,
183 isLiked: false
184 };
185 }
186 return post;
187 } catch (err) {
188 console.error(`获取帖子${post.id}详情失败:`, err);
189 return post;
190 }
191 })
192 );
193 setHelpPosts(postsWithCounts);
194 setTotalPages(Math.ceil(response.data.data.total / 5));
195 } else {
196 setHelpError(response.data.message || '搜索失败');
197 }
198 } catch (err) {
199 setHelpError(err.message || '搜索失败');
200 } finally {
201 setHelpLoading(false);
202 }
203 };
204
205 // 添加重置搜索函数
206 const handleResetHelpSearch = async () => {
207 setHelpSearch('');
208 await fetchHelpPosts(1); // 重置到第一页
209 };
210
211
22301080a93bebb2025-05-27 19:48:11 +0800212
213 const handleAnnouncementClick = (announcement, e) => {
214 if (!e.target.closest('.exclude-click')) {
215 navigate(`/announcement/${announcement.id}`, {
216 state: {
217 announcement,
218 returnPath: `/dashboard/${activeTab}`,
219 scrollPosition: window.scrollY,
220 activeTab
221 }
222 });
Akane121765b61a72025-05-17 13:52:25 +0800223 }
22301080a93bebb2025-05-27 19:48:11 +0800224 };
Akane121765b61a72025-05-17 13:52:25 +0800225
226
22301080a93bebb2025-05-27 19:48:11 +0800227 //资源区
228 const handleFileChange = (e) => {
229 setUploadData({...uploadData, file: e.target.files[0]});
230 };
Akane121765b61a72025-05-17 13:52:25 +0800231
22301080a93bebb2025-05-27 19:48:11 +0800232 const handleUploadSubmit = async (e) => {
233 e.preventDefault();
234 try {
235 setIsUploading(true);
Akane121765b61a72025-05-17 13:52:25 +0800236
22301080a93bebb2025-05-27 19:48:11 +0800237 const torrentData = {
238 torrentName: uploadData.name,
239 description: uploadData.description,
240 category: uploadData.type,
241 region: uploadData.region,
242 resolution: uploadData.resolution,
243 subtitle: uploadData.subtitle
244 };
Akane121765b61a72025-05-17 13:52:25 +0800245
22301080a93bebb2025-05-27 19:48:11 +0800246 await createTorrent(torrentData, uploadData.file);
247
248 // 上传成功处理
249 setShowUploadModal(false);
250 setUploadData({
251 name: '',
252 type: '',
253 region: '',
254 subtitle: '',
255 resolution: '',
256 file: null,
257 description: ''
258 });
259 alert('种子创建成功!');
260
261 // 刷新列表
262 await fetchTorrentPosts(currentPage);
263
264 } catch (error) {
265 console.error('创建失败:', error);
266 alert('创建失败: ' + (error.response?.data?.message || error.message));
267 } finally {
268 setIsUploading(false);
269 }
270 };
271
272 const handlePostSubmit = async (e) => {
273 e.preventDefault();
274 try {
Akane1217d1e9f712025-05-29 14:36:56 +0800275 const username = localStorage.getItem('username');
276 const response = await createPost(
277 postTitle,
278 postContent,
279 username,
280 selectedImage
281 );
282
283 if (response.data.code === 200) {
284 // 刷新帖子列表
285 await fetchHelpPosts(currentPage);
286 // 重置表单
287 setShowPostModal(false);
288 setPostTitle('');
289 setPostContent('');
290 setSelectedImage(null);
291 } else {
292 setHelpError(response.data.message || '发帖失败');
293 }
22301080a93bebb2025-05-27 19:48:11 +0800294 } catch (err) {
Akane1217d1e9f712025-05-29 14:36:56 +0800295 setHelpError(err.message || '发帖失败');
22301080a93bebb2025-05-27 19:48:11 +0800296 }
Akane1217d1e9f712025-05-29 14:36:56 +0800297 };
Akane121765b61a72025-05-17 13:52:25 +0800298
Akane12173a7bb972025-06-01 01:05:27 +0800299
300const fetchTorrentPosts = async (page = 1, isReset = false) => {
301 setTorrentLoading(true);
302 try {
303 const params = {
304 page,
305 size: 5
306 };
307
308 // 如果有筛选条件且不是重置操作
309 if (!isReset && Object.values(selectedFilters).some(v => v !== 'all')) {
310 if (selectedFilters.type !== 'all') params.category = selectedFilters.type;
311 if (selectedFilters.subtitle !== 'all') params.subtitle = selectedFilters.subtitle;
312 if (selectedFilters.region !== 'all') params.region = selectedFilters.region;
313 if (selectedFilters.resolution !== 'all') params.resolution = selectedFilters.resolution;
22301080a93bebb2025-05-27 19:48:11 +0800314 }
Akane12173a7bb972025-06-01 01:05:27 +0800315
316 const response = (shareSearch && !isReset)
317 ? await searchTorrents(shareSearch, page)
318 : await api.get('http://localhost:8088/torrent', { params });
319
320 if (response.data.code === 200) {
321 setTorrentPosts(response.data.data.records);
322 const total = response.data.data.total;
323 setTotalPages(Math.ceil(total / 5));
324 setCurrentPage(page);
325 } else {
326 setTorrentError(response.data.message);
327 }
328 } catch (err) {
329 setTorrentError(err.message);
330 } finally {
331 setTorrentLoading(false);
332 }
333};
Akane121765b61a72025-05-17 13:52:25 +0800334
22301080a93bebb2025-05-27 19:48:11 +0800335 // 在useEffect中调用
336 useEffect(() => {
337 if (activeTab === 'share') {
338 fetchTorrentPosts();
339 }
340 }, [activeTab]);
Akane121765b61a72025-05-17 13:52:25 +0800341
22301080a93bebb2025-05-27 19:48:11 +0800342 const handleImageUpload = (e) => {
343 setSelectedImage(e.target.files[0]);
344 };
Akane121765b61a72025-05-17 13:52:25 +0800345
346
22301080a93bebb2025-05-27 19:48:11 +0800347 const fetchHelpPosts = async (page = 1) => {
348 setHelpLoading(true);
349 try {
Akane1217d1e9f712025-05-29 14:36:56 +0800350 const response = await getPosts(page);
351 if (response.data.code === 200) {
352 const postsWithCounts = await Promise.all(
353 response.data.data.records.map(async (post) => {
354 try {
355 const detailResponse = await getPostDetail(post.id);
356 if (detailResponse.data.code === 200) {
357 return {
358 ...post,
359 replyCount: detailResponse.data.data.post.replyCount || 0,
360 isLiked: false // 根据需要添加其他字段
361 };
362 }
363 return post; // 如果获取详情失败,返回原始帖子数据
364 } catch (err) {
365 console.error(`获取帖子${post.id}详情失败:`, err);
366 return post;
367 }
368 })
369 );
370 setHelpPosts(postsWithCounts);
371 setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条
372 setCurrentPage(page);
373 } else {
374 setHelpError(response.data.message || '获取求助帖失败');
375 }
22301080a93bebb2025-05-27 19:48:11 +0800376 } catch (err) {
Akane1217d1e9f712025-05-29 14:36:56 +0800377 setHelpError(err.message || '获取求助帖失败');
22301080a93bebb2025-05-27 19:48:11 +0800378 } finally {
Akane1217d1e9f712025-05-29 14:36:56 +0800379 setHelpLoading(false);
22301080a93bebb2025-05-27 19:48:11 +0800380 }
Akane1217d1e9f712025-05-29 14:36:56 +0800381 };
382
383
22301080a93bebb2025-05-27 19:48:11 +0800384 useEffect(() => {
385 if (activeTab === 'help') {
386 fetchHelpPosts(currentPage);
387 }
388 }, [activeTab, currentPage]); // 添加 currentPage 作为依赖
Akane121765b61a72025-05-17 13:52:25 +0800389
390
Akane12173a7bb972025-06-01 01:05:27 +0800391 // 分类维度配置
22301080a93bebb2025-05-27 19:48:11 +0800392 const filterCategories = {
393 type: {
394 label: '类型',
395 options: {
396 'all': '全部',
397 '电影': '电影',
398 '电视剧': '电视剧',
399 '动漫': '动漫',
Akane12173a7bb972025-06-01 01:05:27 +0800400 '综艺': '综艺',
401 '音乐': '音乐',
402 '其他': '其他'
22301080a93bebb2025-05-27 19:48:11 +0800403 }
404 },
405 subtitle: {
406 label: '字幕',
407 options: {
408 'all': '全部',
Akane12173a7bb972025-06-01 01:05:27 +0800409 '无需字幕': '无需字幕',
410 '暂无字幕': '暂无字幕',
411 '自带中文字幕': '自带中文字幕',
412 '自带双语字幕(含中文)': '自带双语字幕(含中文)',
413 '附件中文字幕': '附件中文字幕',
414 '附件双语字幕': '附件双语字幕'
22301080a93bebb2025-05-27 19:48:11 +0800415 }
416 },
417 region: {
418 label: '地区',
419 options: {
420 'all': '全部',
Akane12173a7bb972025-06-01 01:05:27 +0800421 '中国': '中国',
422 '英国': '英国',
423 '美国': '美国',
424 '日本': '日本',
425 '韩国': '韩国',
426 '其他': '其他'
427 }
428 },
429 resolution: {
430 label: '分辨率',
431 options: {
432 'all': '全部',
433 '4K': '4K',
434 '2K': '2K',
435 '1080P': '1080P',
436 '720P': '720P',
437 'SD': 'SD',
438 '无损音源': '无损音源',
439 '杜比全景声': '杜比全景声',
440 '其他': '其他'
22301080a93bebb2025-05-27 19:48:11 +0800441 }
442 }
443 };
22301080a93bebb2025-05-27 19:48:11 +0800444 const [selectedFilters, setSelectedFilters] = useState(
445 location.state?.savedFilters ||
446 Object.keys(filterCategories).reduce((acc, category) => {
447 acc[category] = 'all';
448 return acc;
449 }, {})
450 );
Akane121765b61a72025-05-17 13:52:25 +0800451
Akane12173a7bb972025-06-01 01:05:27 +0800452 // 处理筛选条件变更
22301080a93bebb2025-05-27 19:48:11 +0800453 const handleFilterSelect = (category, value) => {
454 setSelectedFilters(prev => ({
455 ...prev,
Akane12173a7bb972025-06-01 01:05:27 +0800456 [category]: prev[category] === value ? 'all' : value
22301080a93bebb2025-05-27 19:48:11 +0800457 }));
Akane121765b61a72025-05-17 13:52:25 +0800458 };
459
Akane12173a7bb972025-06-01 01:05:27 +0800460 // 应用筛选条件
461 const applyFilters = async () => {
462 try {
463 setTorrentLoading(true);
464
465 // 构建查询参数
466 const params = {
467 page: 1, // 从第一页开始
468 size: 5
469 };
470
471 // 添加筛选条件
472 if (selectedFilters.type !== 'all') {
473 params.category = selectedFilters.type;
474 }
475 if (selectedFilters.subtitle !== 'all') {
476 params.subtitle = selectedFilters.subtitle;
477 }
478 if (selectedFilters.region !== 'all') {
479 params.region = selectedFilters.region;
480 }
481 if (selectedFilters.resolution !== 'all') {
482 params.resolution = selectedFilters.resolution;
483 }
484
485 // 调用API获取筛选结果
486 const response = await api.get('http://localhost:8088/torrent', { params });
487
488 if (response.data.code === 200) {
489 setTorrentPosts(response.data.data.records);
490 setTotalPages(Math.ceil(response.data.data.total / 5));
491 setCurrentPage(1);
492 } else {
493 setTorrentError(response.data.message || '筛选失败');
494 }
495 } catch (err) {
496 setTorrentError(err.message || '筛选失败');
497 } finally {
498 setTorrentLoading(false);
499 }
22301080a93bebb2025-05-27 19:48:11 +0800500 };
Akane121765b61a72025-05-17 13:52:25 +0800501
Akane121765b61a72025-05-17 13:52:25 +0800502
22301080a93bebb2025-05-27 19:48:11 +0800503 // 恢复滚动位置
504 useEffect(() => {
505 if (location.state?.scrollPosition) {
506 window.scrollTo(0, location.state.scrollPosition);
507 }
508 }, [location.state]);
Akane121765b61a72025-05-17 13:52:25 +0800509
Akane12173a7bb972025-06-01 01:05:27 +0800510 // 在Dashboard.jsx中修改useEffect
511useEffect(() => {
512 const token = localStorage.getItem('token');
513 if (!token) {
514 navigate('/login');
515 return;
516 }
517
518 const fetchUserInfo = async () => {
519 try {
520 setLoading(true);
521 const backendData = await getUserInfo(); // 调用修改后的方法
522 console.log('后端返回的用户数据:', backendData); // 调试用
523
524 setUserInfo({
525 name: backendData.username || '演示用户',
22301080a93bebb2025-05-27 19:48:11 +0800526 avatar: 'https://via.placeholder.com/40',
Akane12173a7bb972025-06-01 01:05:27 +0800527 isAdmin: backendData.authority === 'ADMIN' // 检查 authority 是否为 "ADMIN"
528 });
529 } catch (error) {
530 console.error('获取用户信息失败:', error);
531 setError('获取用户信息失败');
532 } finally {
533 setLoading(false);
534 }
535 };
536
537 fetchUserInfo();
538}, [navigate]);
22301080a93bebb2025-05-27 19:48:11 +0800539
Akane12173a7bb972025-06-01 01:05:27 +0800540
22301080a93bebb2025-05-27 19:48:11 +0800541 useEffect(() => {
542 if (activeTab === 'announcement') {
543 const timer = setInterval(() => {
544 setCurrentSlide(prev => (prev + 1) % 3); // 3张轮播图循环
545 }, 3000);
546 return () => clearInterval(timer);
547 }
548 }, [activeTab]);
549
550 useEffect(() => {
551 if (activeTab === 'help') {
552 fetchHelpPosts();
553 }
554 }, [activeTab]);
555
556 const renderContent = () => {
557 switch (activeTab) {
558 case 'announcement':
559 return (
560 <div className="content-area" data-testid="announcement-section">
Akane12173a7bb972025-06-01 01:05:27 +0800561 <div className="section-search-container">
562 <input
563 type="text"
564 placeholder="搜索公告..."
565 value={announcementSearch}
566 onChange={(e) => setAnnouncementSearch(e.target.value)}
567 className="section-search-input"
568 onKeyPress={(e) => e.key === 'Enter' && handleSearchAnnouncement()}
569 />
570 <button
571 className="search-button"
572 onClick={handleSearchAnnouncement}
573 >
574 搜索
575 </button>
576 </div>
22301080a93bebb2025-05-27 19:48:11 +0800577 {/* 轮播图区域 */}
578 <div className="carousel-container">
579 <div className={`carousel-slide ${currentSlide === 0 ? 'active' : ''}`}>
580 <div className="carousel-image gray-bg">促销活动1</div>
581 </div>
582 <div className={`carousel-slide ${currentSlide === 1 ? 'active' : ''}`}>
583 <div className="carousel-image gray-bg">促销活动2</div>
584 </div>
585 <div className={`carousel-slide ${currentSlide === 2 ? 'active' : ''}`}>
586 <div className="carousel-image gray-bg">促销活动3</div>
587 </div>
588 <div className="carousel-dots">
589 <span className={`dot ${currentSlide === 0 ? 'active' : ''}`}></span>
590 <span className={`dot ${currentSlide === 1 ? 'active' : ''}`}></span>
591 <span className={`dot ${currentSlide === 2 ? 'active' : ''}`}></span>
592 </div>
593 </div>
594
595 {/* 公告区块区域 */}
596 <div className="announcement-grid">
597 {announcements.map(announcement => (
598 <div
599 key={announcement.id}
600 className="announcement-card"
601 onClick={(e) => handleAnnouncementClick(announcement, e)}
602 >
603 <h3>{announcement.title}</h3>
604 <p>{announcement.excerpt}</p>
605 <div className="announcement-footer exclude-click">
606 <span>{announcement.author}</span>
607 <span>{announcement.date}</span>
608 </div>
609 </div>
610 ))}
611 </div>
Akane121765b61a72025-05-17 13:52:25 +0800612 </div>
22301080a93bebb2025-05-27 19:48:11 +0800613 );
614 case 'share':
615 return (
616 <div className="content-area" data-testid="share-section">
Akane12173a7bb972025-06-01 01:05:27 +0800617 {/* 分享区搜索框 */}
618 <div className="section-search-container">
619 <input
620 type="text"
621 placeholder="搜索资源..."
622 value={shareSearch}
623 onChange={(e) => setShareSearch(e.target.value)}
624 className="section-search-input"
625 onKeyPress={(e) => e.key === 'Enter' && handleSearchShare()}
626 />
627 <button
628 className="search-button"
629 onClick={handleSearchShare}
630 >
631 搜索
632 </button>
633 <button
634 className="reset-button"
635 onClick={handleResetShareSearch} // 使用新的重置函数
636 style={{marginLeft: '10px'}}
637 >
638 重置
639 </button>
640 </div>
641
22301080a93bebb2025-05-27 19:48:11 +0800642 {/* 上传按钮 - 添加在筛选区上方 */}
643 <div className="upload-header">
644 <button
645 className="upload-btn"
646 onClick={() => setShowUploadModal(true)}
647 >
648 上传种子
649 </button>
650 </div>
651 {/* 分类筛选区 */}
652 <div className="filter-section">
653 {Object.entries(filterCategories).map(([category, config]) => (
654 <div key={category} className="filter-group">
655 <h4>{config.label}:</h4>
656 <div className="filter-options">
657 {Object.entries(config.options).map(([value, label]) => (
658 <button
659 key={value}
660 className={`filter-btn ${
661 selectedFilters[category] === value ? 'active' : ''
662 }`}
663 onClick={() => handleFilterSelect(category, value)}
664 >
665 {label}
666 </button>
667 ))}
668 </div>
669 </div>
670 ))}
671
672 <button
673 className="confirm-btn"
674 onClick={applyFilters}
675 >
676 确认筛选
677 </button>
678 </div>
679
680 {/* 上传弹窗 */}
681 {showUploadModal && (
682 <div className="modal-overlay">
683 <div className="upload-modal">
684 <h3>上传新种子</h3>
685 <button
686 className="close-btn"
687 onClick={() => setShowUploadModal(false)}
688 >
689 ×
690 </button>
691
692 <form onSubmit={handleUploadSubmit}>
693 <div className="form-group">
694 <label>种子名称</label>
695 <input
696 type="text"
697 value={uploadData.name}
698 onChange={(e) => setUploadData({...uploadData, name: e.target.value})}
699 required
700 />
701 </div>
702
703 <div className="form-group">
704 <label>资源类型</label>
705 <select
706 value={uploadData.type}
707 onChange={(e) => setUploadData({...uploadData, type: e.target.value})}
708 required
709 >
710 <option value="">请选择</option>
711 <option value="电影">电影</option>
712 <option value="电视剧">电视剧</option>
713 <option value="动漫">动漫</option>
714 <option value="综艺">综艺</option>
715 <option value="音乐">音乐</option>
Akane12173a7bb972025-06-01 01:05:27 +0800716 <option value="其他">其他</option>
22301080a93bebb2025-05-27 19:48:11 +0800717 </select>
718 </div>
719
Akane12173a7bb972025-06-01 01:05:27 +0800720 {/* 修改后的地区下拉框 */}
22301080a93bebb2025-05-27 19:48:11 +0800721 <div className="form-group">
722 <label>地区</label>
Akane12173a7bb972025-06-01 01:05:27 +0800723 <select
22301080a93bebb2025-05-27 19:48:11 +0800724 value={uploadData.region || ''}
725 onChange={(e) => setUploadData({...uploadData, region: e.target.value})}
22301080a93bebb2025-05-27 19:48:11 +0800726 required
Akane12173a7bb972025-06-01 01:05:27 +0800727 >
728 <option value="">请选择</option>
729 <option value="中国">中国</option>
730 <option value="英国">英国</option>
731 <option value="美国">美国</option>
732 <option value="日本">日本</option>
733 <option value="韩国">韩国</option>
734 <option value="其他">其他</option>
735 </select>
22301080a93bebb2025-05-27 19:48:11 +0800736 </div>
737
738 {/* 添加分辨率下拉框 */}
739 <div className="form-group">
740 <label>分辨率</label>
741 <select
742 value={uploadData.resolution || ''}
743 onChange={(e) => setUploadData({
744 ...uploadData,
745 resolution: e.target.value
746 })}
747 required
748 >
749 <option value="">请选择</option>
750 <option value="4K">4K</option>
751 <option value="2K">2K</option>
752 <option value="1080P">1080P</option>
753 <option value="720P">720P</option>
754 <option value="SD">SD</option>
755 <option value="无损音源">无损音源</option>
756 <option value="杜比全景声">杜比全景声</option>
757 <option value="其他">其他</option>
758 </select>
759 </div>
760
761
762 {/* 新增字幕语言下拉框 */}
763 <div className="form-group">
764 <label>字幕语言</label>
765 <select
766 value={uploadData.subtitle || ''}
767 onChange={(e) => setUploadData({
768 ...uploadData,
769 subtitle: e.target.value
770 })}
771 required
772 >
773 <option value="">请选择</option>
774 <option value="无需字幕">无需字幕</option>
775 <option value="暂无字幕">暂无字幕</option>
776 <option value="自带中文字幕">自带中文字幕</option>
777 <option value="自带双语字幕(含中文)">自带双语字幕(含中文)</option>
778 <option value="附件中文字幕">附件中文字幕</option>
779 <option value="附件双语字幕">附件双语字幕</option>
780 </select>
781 </div>
782
783 <div className="form-group">
784 <label>种子文件</label>
785 <input
786 type="file"
787 accept=".torrent"
788 onChange={handleFileChange}
789
790 />
791 </div>
792
793 <div className="form-group">
794 <label>简介</label>
795 <textarea
796 value={uploadData.description}
797 onChange={(e) => setUploadData({
798 ...uploadData,
799 description: e.target.value
800 })}
801 />
802 </div>
803
804 <div className="form-actions">
805 <button
806 type="button"
807 className="cancel-btn"
808 onClick={() => setShowUploadModal(false)}
809 >
810 取消
811 </button>
812 <button
813 type="submit"
814 className="confirm-btn"
815 disabled={isUploading}
816 >
817 {isUploading ? '上传中...' : '确认上传'}
818 </button>
819 </div>
820 </form>
821 </div>
822 </div>
Akane121765b61a72025-05-17 13:52:25 +0800823 )}
22301080a93bebb2025-05-27 19:48:11 +0800824
825 <div className="resource-list">
826 {torrentPosts.map(torrent => (
827 <div
828 key={torrent.id}
829 className="resource-item"
830 onClick={() => navigate(`/torrent/${torrent.id}`)}
831 >
832 <div className="resource-poster">
833 <div className="poster-image gray-bg">{torrent.torrentName.charAt(0)}</div>
834 </div>
835 <div className="resource-info">
836 <h3 className="resource-title">{torrent.torrentName}</h3>
837 <p className="resource-meta">
838 {torrent.resolution} | {torrent.region} | {torrent.category}
839 </p>
840 <p className="resource-subtitle">字幕: {torrent.subtitle}</p>
841 </div>
842 <div className="resource-stats">
Akane12173a7bb972025-06-01 01:05:27 +0800843 <span className="stat">{torrent.size}</span>
22301080a93bebb2025-05-27 19:48:11 +0800844 <span className="stat">发布者: {torrent.username}</span>
845 </div>
846 <button
847 className="download-btn"
848 onClick={(e) => {
849 e.stopPropagation();
850 // 下载逻辑
851 }}
852 >
853 立即下载
854 </button>
855 </div>
856 ))}
857 </div>
858
Akane12173a7bb972025-06-01 01:05:27 +0800859 {totalPages > 1 && (
22301080a93bebb2025-05-27 19:48:11 +0800860 <div className="pagination">
861 <button
862 onClick={() => fetchTorrentPosts(currentPage - 1)}
863 disabled={currentPage === 1}
864 >
865 上一页
866 </button>
867
868 {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
869 <button
870 key={page}
871 onClick={() => fetchTorrentPosts(page)}
872 className={currentPage === page ? 'active' : ''}
873 >
874 {page}
875 </button>
876 ))}
877
878 <button
879 onClick={() => fetchTorrentPosts(currentPage + 1)}
880 disabled={currentPage === totalPages}
881 >
882 下一页
883 </button>
884 </div>
Akane12173a7bb972025-06-01 01:05:27 +0800885 )}
Akane121765b61a72025-05-17 13:52:25 +0800886 </div>
22301080a93bebb2025-05-27 19:48:11 +0800887 );
888 // 在Dashboard.jsx的renderContent函数中修改case 'request'部分
889 case 'request':
890 return (
891 <div className="content-area" data-testid="request-section">
Akane12173a7bb972025-06-01 01:05:27 +0800892 {/* 求种区搜索框 */}
893 <div className="section-search-container">
894 <input
895 type="text"
896 placeholder="搜索求种..."
897 value={requestSearch}
898 onChange={(e) => setRequestSearch(e.target.value)}
899 className="section-search-input"
900 onKeyPress={(e) => e.key === 'Enter' && handleSearchRequest()}
901 />
902 <button
903 className="search-button"
904 onClick={handleSearchRequest}
905 >
906 搜索
907 </button>
908 </div>
22301080a93bebb2025-05-27 19:48:11 +0800909 {/* 求种区帖子列表 */}
910 <div className="request-list">
911 {[
912 {
913 id: 1,
914 title: '求《药屋少女的呢喃》第二季全集',
915 content: '求1080P带中文字幕版本,最好是内嵌字幕不是外挂的',
916 author: '动漫爱好者',
917 authorAvatar: 'https://via.placeholder.com/40',
918 date: '2023-10-15',
919 likeCount: 24,
920 commentCount: 8
921 },
922 {
923 id: 2,
924 title: '求《奥本海默》IMAX版',
925 content: '最好是原盘或者高码率的版本,谢谢各位大佬',
926 author: '电影收藏家',
927 authorAvatar: 'https://via.placeholder.com/40',
928 date: '2023-10-14',
929 likeCount: 15,
930 commentCount: 5
931 }
932 ].map(post => (
933 <div
934 key={post.id}
935 className="request-post"
936 onClick={() => navigate(`/request/${post.id}`)}
937 >
938 <div className="post-header">
939 <img src={post.authorAvatar} alt={post.author} className="post-avatar"/>
940 <div className="post-author">{post.author}</div>
941 <div className="post-date">{post.date}</div>
942 </div>
943 <h3 className="post-title">{post.title}</h3>
944 <p className="post-content">{post.content}</p>
945 <div className="post-stats">
946 <span className="post-likes">👍 {post.likeCount}</span>
947 <span className="post-comments">💬 {post.commentCount}</span>
948 </div>
949 </div>
950 ))}
951 </div>
Akane121765b61a72025-05-17 13:52:25 +0800952 </div>
22301080a93bebb2025-05-27 19:48:11 +0800953 );
954 // 在Dashboard.jsx的renderContent函数中修改case 'help'部分
955 case 'help':
956 return (
957 <div className="content-area" data-testid="help-section">
Akane12173a7bb972025-06-01 01:05:27 +0800958 {/* 求助区搜索框 */}
959 <div className="section-search-container">
960 <input
961 type="text"
962 placeholder="搜索求助..."
963 value={helpSearch}
964 onChange={(e) => setHelpSearch(e.target.value)}
965 className="section-search-input"
966 onKeyPress={(e) => e.key === 'Enter' && handleSearchHelp()}
967 />
968 <button
969 className="search-button"
970 onClick={handleSearchHelp}
971 >
972 搜索
973 </button>
974 <button
975 className="reset-button"
976 onClick={handleResetHelpSearch}
977 style={{marginLeft: '10px'}}
978 >
979 重置
980 </button>
981 </div>
982
22301080a93bebb2025-05-27 19:48:11 +0800983 {/* 新增发帖按钮 */}
984 <div className="post-header">
985 <button
986 className="create-post-btn"
987 onClick={() => setShowPostModal(true)}
988 >
989 发帖求助
990 </button>
991 </div>
992
993 {/* 加载状态和错误提示 */}
994 {helpLoading && <div className="loading">加载中...</div>}
995 {helpError && <div className="error">{helpError}</div>}
996
997 {/* 求助区帖子列表 */}
998 <div className="help-list">
999 {helpPosts.map(post => (
1000 <div
1001 key={post.id}
1002 className={`help-post ${post.isSolved ? 'solved' : ''}`}
1003 onClick={() => navigate(`/help/${post.id}`)}
1004 >
1005 <div className="post-header">
1006 <img
1007 src={post.authorAvatar || 'https://via.placeholder.com/40'}
1008 alt={post.authorId}
1009 className="post-avatar"
1010 />
1011 <div className="post-author">{post.authorId}</div>
1012 <div className="post-date">
1013 {new Date(post.createTime).toLocaleDateString()}
1014 </div>
1015 {post.isSolved && <span className="solved-badge">已解决</span>}
1016 </div>
1017 <h3 className="post-title">{post.title}</h3>
1018 <p className="post-content">{post.content}</p>
1019 <div className="post-stats">
1020 <span className="post-likes">👍 {post.likeCount || 0}</span>
1021 <span className="post-comments">💬 {post.replyCount || 0}</span>
1022 </div>
1023 </div>
1024 ))}
1025 </div>
1026
1027 {/* 在帖子列表后添加分页控件 */}
1028 <div className="pagination">
1029 <button
1030 onClick={() => fetchHelpPosts(currentPage - 1)}
1031 disabled={currentPage === 1}
1032 >
1033 上一页
1034 </button>
1035
1036 {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
1037 <button
1038 key={page}
1039 onClick={() => fetchHelpPosts(page)}
1040 className={currentPage === page ? 'active' : ''}
1041 >
1042 {page}
1043 </button>
1044 ))}
1045
1046 <button
1047 onClick={() => fetchHelpPosts(currentPage + 1)}
1048 disabled={currentPage === totalPages}
1049 >
1050 下一页
1051 </button>
1052 </div>
1053
1054 {/* 新增发帖弹窗 */}
1055 {showPostModal && (
1056 <div className="post-modal-overlay">
1057 <div className="post-modal">
1058 <h3>发布求助帖</h3>
1059 <button
1060 className="modal-close-btn"
1061 onClick={() => setShowPostModal(false)}
1062 >
1063 ×
1064 </button>
1065
1066 <form onSubmit={handlePostSubmit}>
1067 <div className="form-group">
1068 <label>帖子标题</label>
1069 <input
1070 type="text"
1071 value={postTitle}
1072 onChange={(e) => setPostTitle(e.target.value)}
1073 placeholder="请输入标题"
1074 required
1075 />
1076 </div>
1077
1078 <div className="form-group">
1079 <label>帖子内容</label>
1080 <textarea
1081 value={postContent}
1082 onChange={(e) => setPostContent(e.target.value)}
1083 placeholder="详细描述你的问题"
1084 required
1085 />
1086 </div>
1087
1088 <div className="form-group">
1089 <label>上传图片</label>
1090 <div className="upload-image-btn">
1091 <input
1092 type="file"
1093 id="image-upload"
1094 accept="image/*"
1095 onChange={handleImageUpload}
1096 style={{display: 'none'}}
1097 />
1098 <label htmlFor="image-upload">
1099 {selectedImage ? '已选择图片' : '选择图片'}
1100 </label>
1101 {selectedImage && (
1102 <span className="image-name">{selectedImage.name}</span>
1103 )}
1104 </div>
1105 </div>
1106
1107 <div className="form-actions">
1108 <button
1109 type="button"
1110 className="cancel-btn"
1111 onClick={() => setShowPostModal(false)}
1112 >
1113 取消
1114 </button>
1115 <button
1116 type="submit"
1117 className="submit-btn"
1118 >
1119 确认发帖
1120 </button>
1121 </div>
1122 </form>
1123 </div>
1124 </div>
1125 )}
1126 </div>
1127 );
1128 default:
1129 return <div className="content-area" data-testid="default-section">公告区内容</div>;
1130 }
1131 };
1132
1133 if (loading) return <div className="loading">加载中...</div>;
1134 if (error) return <div className="error">{error}</div>;
1135
1136 return (
1137 <div className="dashboard-container" data-testid="dashboard-container">
1138 {/* 顶部栏 */}
1139 <div className="top-bar" data-testid="top-bar">
Akane12173a7bb972025-06-01 01:05:27 +08001140 {/* 平台名称替换搜索框 */}
1141 <div className="platform-name">
1142 <h2>PT资源站</h2>
Akane121765b61a72025-05-17 13:52:25 +08001143 </div>
Akane121765b61a72025-05-17 13:52:25 +08001144
22301080a93bebb2025-05-27 19:48:11 +08001145 <div className="user-actions">
1146 {/* 新增管理员按钮 - 只有管理员可见 */}
1147 {userInfo?.isAdmin && (
1148 <button
1149 className="admin-center-button"
1150 onClick={() => navigate('/administer')}
1151 >
1152 管理员中心
1153 </button>
1154 )}
Akane121765b61a72025-05-17 13:52:25 +08001155
22301080a93bebb2025-05-27 19:48:11 +08001156 <div className="user-info" data-testid="user-info">
1157 <img
1158 src={userInfo?.avatar || 'https://via.placeholder.com/40'}
1159 alt="用户头像"
1160 className="user-avatar"
1161 onClick={() => navigate('/personal')}
1162 style={{cursor: 'pointer'}}
1163 />
1164 <span className="username">{userInfo?.name || '用户'}</span>
1165 <button onClick={onLogout} className="logout-button">退出</button>
1166 </div>
1167 </div>
1168 </div>
1169
1170 {/* 导航栏 */}
1171 {/* handleTabchange函数替换了原本的setactivetab函数 */}
1172 <div className="nav-tabs">
1173 <button
1174 className={`tab-button ${activeTab === 'announcement' ? 'active' : ''}`}
1175 onClick={() => handleTabChange('announcement')}
1176 >
1177 公告区
1178 </button>
1179 <button
1180 className={`tab-button ${activeTab === 'share' ? 'active' : ''}`}
1181 onClick={() => handleTabChange('share')}
1182 >
1183 分享区
1184 </button>
1185 <button
1186 className={`tab-button ${activeTab === 'request' ? 'active' : ''}`}
1187 onClick={() => handleTabChange('request')}
1188 >
1189 求种区
1190 </button>
1191 <button
1192 className={`tab-button ${activeTab === 'help' ? 'active' : ''}`}
1193 onClick={() => handleTabChange('help')}
1194 >
1195 求助区
1196 </button>
1197 </div>
1198
1199 {/* 内容区 */}
1200 {renderContent()}
Akane121765b61a72025-05-17 13:52:25 +08001201 </div>
22301080a93bebb2025-05-27 19:48:11 +08001202 );
Akane121765b61a72025-05-17 13:52:25 +08001203};
1204
1205export default Dashboard;