blob: 97a01cccc192448ae81ea82430be2a52db4bac7a [file] [log] [blame]
22301080a93bebb2025-05-27 19:48:11 +08001import React, {useEffect, useState} from 'react';
2import {useNavigate, useLocation, useParams} from 'react-router-dom';
3// import { getUserInfo } from '../api/auth';
4import {createTorrent, getTorrents} from '../api/torrent';
Akane121765b61a72025-05-17 13:52:25 +08005import './Dashboard.css';
22301080a93bebb2025-05-27 19:48:11 +08006import {createPost, getPosts} from '../api/helpPost';
Akane121765b61a72025-05-17 13:52:25 +08007
8
22301080a93bebb2025-05-27 19:48:11 +08009const Dashboard = ({onLogout}) => {
10 const location = useLocation();
11 const [userInfo, setUserInfo] = useState(null);
12 const [loading, setLoading] = useState(false);
13 const [error, setError] = useState('');
14 const [currentSlide, setCurrentSlide] = useState(0);
15 const navigate = useNavigate();
16 const {tab} = useParams();
17 const [showUploadModal, setShowUploadModal] = useState(false);
18 const [isUploading, setIsUploading] = useState(false);
19 const [uploadData, setUploadData] = useState({
20 name: '',
21 type: '',
22 region: '',
23 subtitle: '',
24 resolution: '', // 新增分辨率字段
25 file: null,
26 description: ''
27 });
28 const [showPostModal, setShowPostModal] = useState(false);
29 const [postTitle, setPostTitle] = useState('');
30 const [postContent, setPostContent] = useState('');
31 const [selectedImage, setSelectedImage] = useState(null);
32 const [helpPosts, setHelpPosts] = useState([]);
33 const [helpLoading, setHelpLoading] = useState(false);
34 const [helpError, setHelpError] = useState(null);
35 const [currentPage, setCurrentPage] = useState(1);
36 const [totalPages, setTotalPages] = useState(1);
Akane121765b61a72025-05-17 13:52:25 +080037
38 // 添加状态
22301080a93bebb2025-05-27 19:48:11 +080039 const [torrentPosts, setTorrentPosts] = useState([]);
40 const [torrentLoading, setTorrentLoading] = useState(false);
41 const [torrentError, setTorrentError] = useState(null);
Akane121765b61a72025-05-17 13:52:25 +080042
22301080a93bebb2025-05-27 19:48:11 +080043 const [filteredResources, setFilteredResources] = useState(torrentPosts);
44 const [isAdmin, setIsAdmin] = useState(false);
45
46
47 const activeTab = tab || 'announcement'; // 如果没有tab参数,则默认为announcement
48 // 从location.state中初始化状态
49
50
51 const handleTabChange = (tabName) => {
52 navigate(`/dashboard/${tabName}`, {
53 state: {
54 savedFilters: selectedFilters, // 使用新的筛选状态 // 保留现有状态
55 activeTab: tabName // 可选,如果其他组件依赖这个 state
56 }
57 });
58 };
59
60 //公告区
61 const [announcements] = useState([
62 {
63 id: 1,
64 title: '系统维护与更新',
65 content: '2023-10-15 02:00-06:00将进行系统维护升级,期间网站将无法访问。本次更新包含:\n1. 数据库服务器迁移\n2. 安全补丁更新\n3. CDN节点优化\n\n请提前做好下载安排。',
66 author: '系统管理员',
67 date: '2023-10-10',
68 excerpt: '2023-10-15 02:00-06:00将进行系统维护,期间无法访问',
69 category: '系统'
70 },
71 {
72 id: 2,
73 title: '资源上新',
74 content: '最新热门电影《奥本海默》4K REMUX资源已上线,包含:\n- 4K HDR版本 (56.8GB)\n- 1080P标准版 (12.3GB)\n- 中英双语字幕\n\n欢迎下载保种!',
75 author: '资源组',
76 date: '2023-10-08',
77 excerpt: '最新热门电影《奥本海默》4K资源已上线',
78 category: '资源'
79 },
80 {
81 id: 3,
82 title: '积分规则调整',
83 content: '自11月1日起,上传资源积分奖励提升20%,具体规则如下:\n- 上传电影资源:每GB 10积分\n- 上传电视剧资源:每GB 8积分\n- 上传动漫资源:每GB 6积分\n\n感谢大家的支持与贡献!',
84 author: '管理员',
85 date: '2023-10-05',
86 excerpt: '自11月1日起,上传资源积分奖励提升20%',
87 category: '公告'
88 },
89 {
90 id: 4,
91 title: '违规处理公告',
92 content: '用户user123因发布虚假资源已被封禁,相关资源已删除。请大家遵守社区规则,维护良好的分享环境。',
93 author: '管理员',
94 date: '2023-10-03',
95 excerpt: '用户user123因发布虚假资源已被封禁',
96 category: '违规'
97 },
98 {
99 id: 5,
100 title: '节日活动',
101 content: '国庆期间所有资源下载积分减半,活动时间:2023年10月1日至2023年10月7日。',
102 author: '活动组',
103 date: '2023-09-30',
104 excerpt: '国庆期间所有资源下载积分减半',
105 category: '活动'
106 },
107 {
108 id: 6,
109 title: '客户端更新',
110 content: 'PT客户端v2.5.0已发布,修复多个BUG,新增资源搜索功能。请尽快更新到最新版本以获得更好的使用体验。',
111 author: '开发组',
112 date: '2023-09-28',
113 excerpt: 'PT客户端v2.5.0已发布,修复多个BUG',
114 category: '更新'
115 },
116 // 其他公告...
117 ]);
118
119
120 const handleAnnouncementClick = (announcement, e) => {
121 if (!e.target.closest('.exclude-click')) {
122 navigate(`/announcement/${announcement.id}`, {
123 state: {
124 announcement,
125 returnPath: `/dashboard/${activeTab}`,
126 scrollPosition: window.scrollY,
127 activeTab
128 }
129 });
Akane121765b61a72025-05-17 13:52:25 +0800130 }
22301080a93bebb2025-05-27 19:48:11 +0800131 };
Akane121765b61a72025-05-17 13:52:25 +0800132
133
22301080a93bebb2025-05-27 19:48:11 +0800134 //资源区
135 const handleFileChange = (e) => {
136 setUploadData({...uploadData, file: e.target.files[0]});
137 };
Akane121765b61a72025-05-17 13:52:25 +0800138
22301080a93bebb2025-05-27 19:48:11 +0800139 const handleUploadSubmit = async (e) => {
140 e.preventDefault();
141 try {
142 setIsUploading(true);
Akane121765b61a72025-05-17 13:52:25 +0800143
22301080a93bebb2025-05-27 19:48:11 +0800144 const torrentData = {
145 torrentName: uploadData.name,
146 description: uploadData.description,
147 category: uploadData.type,
148 region: uploadData.region,
149 resolution: uploadData.resolution,
150 subtitle: uploadData.subtitle
151 };
Akane121765b61a72025-05-17 13:52:25 +0800152
22301080a93bebb2025-05-27 19:48:11 +0800153 await createTorrent(torrentData, uploadData.file);
154
155 // 上传成功处理
156 setShowUploadModal(false);
157 setUploadData({
158 name: '',
159 type: '',
160 region: '',
161 subtitle: '',
162 resolution: '',
163 file: null,
164 description: ''
165 });
166 alert('种子创建成功!');
167
168 // 刷新列表
169 await fetchTorrentPosts(currentPage);
170
171 } catch (error) {
172 console.error('创建失败:', error);
173 alert('创建失败: ' + (error.response?.data?.message || error.message));
174 } finally {
175 setIsUploading(false);
176 }
177 };
178
179 const handlePostSubmit = async (e) => {
180 e.preventDefault();
181 try {
182 const username = localStorage.getItem('username');
183 const response = await createPost(
184 postTitle,
185 postContent,
186 username
187 );
188
189 if (response.data.code === 200) {
190 // 刷新帖子列表
191 await fetchHelpPosts();
192 // 重置表单
193 setShowPostModal(false);
194 setPostTitle('');
195 setPostContent('');
196 setSelectedImage(null);
197 } else {
198 setHelpError(response.data.message || '发帖失败');
199 }
200 } catch (err) {
201 setHelpError(err.message || '发帖失败');
202 }
203 };
Akane121765b61a72025-05-17 13:52:25 +0800204
205 // 获取Torrent帖子列表
22301080a93bebb2025-05-27 19:48:11 +0800206 const fetchTorrentPosts = async (page = 1) => {
207 setTorrentLoading(true);
208 try {
209 const response = await getTorrents(page);
210 setTorrentPosts(response.data.data.records);
211 setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条
212 setCurrentPage(page);
213 } catch (err) {
214 setTorrentError(err.message);
215 } finally {
216 setTorrentLoading(false);
217 }
218 };
Akane121765b61a72025-05-17 13:52:25 +0800219
22301080a93bebb2025-05-27 19:48:11 +0800220 // 在useEffect中调用
221 useEffect(() => {
222 if (activeTab === 'share') {
223 fetchTorrentPosts();
224 }
225 }, [activeTab]);
Akane121765b61a72025-05-17 13:52:25 +0800226
22301080a93bebb2025-05-27 19:48:11 +0800227 const handleImageUpload = (e) => {
228 setSelectedImage(e.target.files[0]);
229 };
Akane121765b61a72025-05-17 13:52:25 +0800230
231
22301080a93bebb2025-05-27 19:48:11 +0800232 const fetchHelpPosts = async (page = 1) => {
233 setHelpLoading(true);
234 try {
235 const response = await getPosts(page);
236 if (response.data.code === 200) {
237 setHelpPosts(response.data.data.records);
238 setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条
239 setCurrentPage(page);
240 } else {
241 setHelpError(response.data.message || '获取求助帖失败');
242 }
243 } catch (err) {
244 setHelpError(err.message || '获取求助帖失败');
245 } finally {
246 setHelpLoading(false);
247 }
248 };
249 useEffect(() => {
250 if (activeTab === 'help') {
251 fetchHelpPosts(currentPage);
252 }
253 }, [activeTab, currentPage]); // 添加 currentPage 作为依赖
Akane121765b61a72025-05-17 13:52:25 +0800254
255
22301080a93bebb2025-05-27 19:48:11 +0800256 // 分类维度配置
257 const filterCategories = {
258 type: {
259 label: '类型',
260 options: {
261 'all': '全部',
262 '电影': '电影',
263 '电视剧': '电视剧',
264 '动漫': '动漫',
265 '综艺': '综艺'
266 }
267 },
268 subtitle: {
269 label: '字幕',
270 options: {
271 'all': '全部',
272 'yes': '有字幕',
273 'no': '无字幕'
274 }
275 },
276 region: {
277 label: '地区',
278 options: {
279 'all': '全部',
280 'cn': '大陆',
281 'us': '欧美',
282 'jp': '日本'
283 }
284 }
285 };
Akane121765b61a72025-05-17 13:52:25 +0800286
22301080a93bebb2025-05-27 19:48:11 +0800287 const [selectedFilters, setSelectedFilters] = useState(
288 location.state?.savedFilters ||
289 Object.keys(filterCategories).reduce((acc, category) => {
290 acc[category] = 'all';
291 return acc;
292 }, {})
293 );
Akane121765b61a72025-05-17 13:52:25 +0800294
295
296// 处理筛选条件变更
22301080a93bebb2025-05-27 19:48:11 +0800297 const handleFilterSelect = (category, value) => {
298 setSelectedFilters(prev => ({
299 ...prev,
300 [category]: prev[category] === value ? null : value // 点击已选中的则取消
301 }));
Akane121765b61a72025-05-17 13:52:25 +0800302 };
303
22301080a93bebb2025-05-27 19:48:11 +0800304//应用筛选条件
305 const applyFilters = () => {
306 const result = torrentPosts.filter(resource => {
307 return Object.entries(selectedFilters).every(([category, selectedValue]) => {
308 if (selectedValue === 'all') return true;
309 if (category === 'subtitle') {
310 return resource.subtitle === (selectedValue === 'yes');
Akane121765b61a72025-05-17 13:52:25 +0800311 }
22301080a93bebb2025-05-27 19:48:11 +0800312 return resource[category] === selectedValue;
313 });
314 });
315 setFilteredResources(result);
316 };
Akane121765b61a72025-05-17 13:52:25 +0800317
Akane121765b61a72025-05-17 13:52:25 +0800318
22301080a93bebb2025-05-27 19:48:11 +0800319 // 恢复滚动位置
320 useEffect(() => {
321 if (location.state?.scrollPosition) {
322 window.scrollTo(0, location.state.scrollPosition);
323 }
324 }, [location.state]);
Akane121765b61a72025-05-17 13:52:25 +0800325
22301080a93bebb2025-05-27 19:48:11 +0800326
327 useEffect(() => {
328 const token = localStorage.getItem('token');
329 if (!token) {
330 navigate('/login');
331 return;
332 }
333
334 /* 保留但注释掉实际的用户信息获取
335     const fetchUserInfo = async () => {
336       try {
337         const response = await getUserInfo(token);
338         if (response.data.code === 200) {
339           setUserInfo(response.data.data);
340         } else {
341           setError('获取用户信息失败');
342         }
343       } catch (err) {
344         setError('获取用户信息失败');
345       } finally {
346         setLoading(false);
347       }
348     };
349
350     fetchUserInfo();
351     */
352
353 // 模拟用户信息
354 setUserInfo({
355 name: localStorage.getItem('username') || '演示用户', // 确保这里读取的是最新值
356 avatar: 'https://via.placeholder.com/40',
357 isAdmin: true
358 });
359 setLoading(false);
360 }, [navigate]);
361
362 // 轮播图自动切换效果
363 useEffect(() => {
364 if (activeTab === 'announcement') {
365 const timer = setInterval(() => {
366 setCurrentSlide(prev => (prev + 1) % 3); // 3张轮播图循环
367 }, 3000);
368 return () => clearInterval(timer);
369 }
370 }, [activeTab]);
371
372 useEffect(() => {
373 if (activeTab === 'help') {
374 fetchHelpPosts();
375 }
376 }, [activeTab]);
377
378 const renderContent = () => {
379 switch (activeTab) {
380 case 'announcement':
381 return (
382 <div className="content-area" data-testid="announcement-section">
383 {/* 轮播图区域 */}
384 <div className="carousel-container">
385 <div className={`carousel-slide ${currentSlide === 0 ? 'active' : ''}`}>
386 <div className="carousel-image gray-bg">促销活动1</div>
387 </div>
388 <div className={`carousel-slide ${currentSlide === 1 ? 'active' : ''}`}>
389 <div className="carousel-image gray-bg">促销活动2</div>
390 </div>
391 <div className={`carousel-slide ${currentSlide === 2 ? 'active' : ''}`}>
392 <div className="carousel-image gray-bg">促销活动3</div>
393 </div>
394 <div className="carousel-dots">
395 <span className={`dot ${currentSlide === 0 ? 'active' : ''}`}></span>
396 <span className={`dot ${currentSlide === 1 ? 'active' : ''}`}></span>
397 <span className={`dot ${currentSlide === 2 ? 'active' : ''}`}></span>
398 </div>
399 </div>
400
401 {/* 公告区块区域 */}
402 <div className="announcement-grid">
403 {announcements.map(announcement => (
404 <div
405 key={announcement.id}
406 className="announcement-card"
407 onClick={(e) => handleAnnouncementClick(announcement, e)}
408 >
409 <h3>{announcement.title}</h3>
410 <p>{announcement.excerpt}</p>
411 <div className="announcement-footer exclude-click">
412 <span>{announcement.author}</span>
413 <span>{announcement.date}</span>
414 </div>
415 </div>
416 ))}
417 </div>
Akane121765b61a72025-05-17 13:52:25 +0800418 </div>
22301080a93bebb2025-05-27 19:48:11 +0800419 );
420 case 'share':
421 return (
422 <div className="content-area" data-testid="share-section">
423 {/* 上传按钮 - 添加在筛选区上方 */}
424 <div className="upload-header">
425 <button
426 className="upload-btn"
427 onClick={() => setShowUploadModal(true)}
428 >
429 上传种子
430 </button>
431 </div>
432 {/* 分类筛选区 */}
433 <div className="filter-section">
434 {Object.entries(filterCategories).map(([category, config]) => (
435 <div key={category} className="filter-group">
436 <h4>{config.label}:</h4>
437 <div className="filter-options">
438 {Object.entries(config.options).map(([value, label]) => (
439 <button
440 key={value}
441 className={`filter-btn ${
442 selectedFilters[category] === value ? 'active' : ''
443 }`}
444 onClick={() => handleFilterSelect(category, value)}
445 >
446 {label}
447 </button>
448 ))}
449 </div>
450 </div>
451 ))}
452
453 <button
454 className="confirm-btn"
455 onClick={applyFilters}
456 >
457 确认筛选
458 </button>
459 </div>
460
461 {/* 上传弹窗 */}
462 {showUploadModal && (
463 <div className="modal-overlay">
464 <div className="upload-modal">
465 <h3>上传新种子</h3>
466 <button
467 className="close-btn"
468 onClick={() => setShowUploadModal(false)}
469 >
470 ×
471 </button>
472
473 <form onSubmit={handleUploadSubmit}>
474 <div className="form-group">
475 <label>种子名称</label>
476 <input
477 type="text"
478 value={uploadData.name}
479 onChange={(e) => setUploadData({...uploadData, name: e.target.value})}
480 required
481 />
482 </div>
483
484 <div className="form-group">
485 <label>资源类型</label>
486 <select
487 value={uploadData.type}
488 onChange={(e) => setUploadData({...uploadData, type: e.target.value})}
489 required
490 >
491 <option value="">请选择</option>
492 <option value="电影">电影</option>
493 <option value="电视剧">电视剧</option>
494 <option value="动漫">动漫</option>
495 <option value="综艺">综艺</option>
496 <option value="音乐">音乐</option>
497 </select>
498 </div>
499
500 {/* 新增地区输入框 */}
501 <div className="form-group">
502 <label>地区</label>
503 <input
504 type="text"
505 value={uploadData.region || ''}
506 onChange={(e) => setUploadData({...uploadData, region: e.target.value})}
507 placeholder="例如: 美国, 中国, 日本等"
508 required
509 />
510 </div>
511
512 {/* 添加分辨率下拉框 */}
513 <div className="form-group">
514 <label>分辨率</label>
515 <select
516 value={uploadData.resolution || ''}
517 onChange={(e) => setUploadData({
518 ...uploadData,
519 resolution: e.target.value
520 })}
521 required
522 >
523 <option value="">请选择</option>
524 <option value="4K">4K</option>
525 <option value="2K">2K</option>
526 <option value="1080P">1080P</option>
527 <option value="720P">720P</option>
528 <option value="SD">SD</option>
529 <option value="无损音源">无损音源</option>
530 <option value="杜比全景声">杜比全景声</option>
531 <option value="其他">其他</option>
532 </select>
533 </div>
534
535
536 {/* 新增字幕语言下拉框 */}
537 <div className="form-group">
538 <label>字幕语言</label>
539 <select
540 value={uploadData.subtitle || ''}
541 onChange={(e) => setUploadData({
542 ...uploadData,
543 subtitle: e.target.value
544 })}
545 required
546 >
547 <option value="">请选择</option>
548 <option value="无需字幕">无需字幕</option>
549 <option value="暂无字幕">暂无字幕</option>
550 <option value="自带中文字幕">自带中文字幕</option>
551 <option value="自带双语字幕(含中文)">自带双语字幕(含中文)</option>
552 <option value="附件中文字幕">附件中文字幕</option>
553 <option value="附件双语字幕">附件双语字幕</option>
554 </select>
555 </div>
556
557 <div className="form-group">
558 <label>种子文件</label>
559 <input
560 type="file"
561 accept=".torrent"
562 onChange={handleFileChange}
563
564 />
565 </div>
566
567 <div className="form-group">
568 <label>简介</label>
569 <textarea
570 value={uploadData.description}
571 onChange={(e) => setUploadData({
572 ...uploadData,
573 description: e.target.value
574 })}
575 />
576 </div>
577
578 <div className="form-actions">
579 <button
580 type="button"
581 className="cancel-btn"
582 onClick={() => setShowUploadModal(false)}
583 >
584 取消
585 </button>
586 <button
587 type="submit"
588 className="confirm-btn"
589 disabled={isUploading}
590 >
591 {isUploading ? '上传中...' : '确认上传'}
592 </button>
593 </div>
594 </form>
595 </div>
596 </div>
Akane121765b61a72025-05-17 13:52:25 +0800597 )}
22301080a93bebb2025-05-27 19:48:11 +0800598
599 <div className="resource-list">
600 {torrentPosts.map(torrent => (
601 <div
602 key={torrent.id}
603 className="resource-item"
604 onClick={() => navigate(`/torrent/${torrent.id}`)}
605 >
606 <div className="resource-poster">
607 <div className="poster-image gray-bg">{torrent.torrentName.charAt(0)}</div>
608 </div>
609 <div className="resource-info">
610 <h3 className="resource-title">{torrent.torrentName}</h3>
611 <p className="resource-meta">
612 {torrent.resolution} | {torrent.region} | {torrent.category}
613 </p>
614 <p className="resource-subtitle">字幕: {torrent.subtitle}</p>
615 </div>
616 <div className="resource-stats">
617 <span className="stat">{torrent.size}</span>
618 <span className="stat">发布者: {torrent.username}</span>
619 </div>
620 <button
621 className="download-btn"
622 onClick={(e) => {
623 e.stopPropagation();
624 // 下载逻辑
625 }}
626 >
627 立即下载
628 </button>
629 </div>
630 ))}
631 </div>
632
633 {/* 分页控件 */}
634 <div className="pagination">
635 <button
636 onClick={() => fetchTorrentPosts(currentPage - 1)}
637 disabled={currentPage === 1}
638 >
639 上一页
640 </button>
641
642 {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
643 <button
644 key={page}
645 onClick={() => fetchTorrentPosts(page)}
646 className={currentPage === page ? 'active' : ''}
647 >
648 {page}
649 </button>
650 ))}
651
652 <button
653 onClick={() => fetchTorrentPosts(currentPage + 1)}
654 disabled={currentPage === totalPages}
655 >
656 下一页
657 </button>
658 </div>
Akane121765b61a72025-05-17 13:52:25 +0800659 </div>
22301080a93bebb2025-05-27 19:48:11 +0800660 );
661 // 在Dashboard.jsx的renderContent函数中修改case 'request'部分
662 case 'request':
663 return (
664 <div className="content-area" data-testid="request-section">
665 {/* 求种区帖子列表 */}
666 <div className="request-list">
667 {[
668 {
669 id: 1,
670 title: '求《药屋少女的呢喃》第二季全集',
671 content: '求1080P带中文字幕版本,最好是内嵌字幕不是外挂的',
672 author: '动漫爱好者',
673 authorAvatar: 'https://via.placeholder.com/40',
674 date: '2023-10-15',
675 likeCount: 24,
676 commentCount: 8
677 },
678 {
679 id: 2,
680 title: '求《奥本海默》IMAX版',
681 content: '最好是原盘或者高码率的版本,谢谢各位大佬',
682 author: '电影收藏家',
683 authorAvatar: 'https://via.placeholder.com/40',
684 date: '2023-10-14',
685 likeCount: 15,
686 commentCount: 5
687 }
688 ].map(post => (
689 <div
690 key={post.id}
691 className="request-post"
692 onClick={() => navigate(`/request/${post.id}`)}
693 >
694 <div className="post-header">
695 <img src={post.authorAvatar} alt={post.author} className="post-avatar"/>
696 <div className="post-author">{post.author}</div>
697 <div className="post-date">{post.date}</div>
698 </div>
699 <h3 className="post-title">{post.title}</h3>
700 <p className="post-content">{post.content}</p>
701 <div className="post-stats">
702 <span className="post-likes">👍 {post.likeCount}</span>
703 <span className="post-comments">💬 {post.commentCount}</span>
704 </div>
705 </div>
706 ))}
707 </div>
Akane121765b61a72025-05-17 13:52:25 +0800708 </div>
22301080a93bebb2025-05-27 19:48:11 +0800709 );
710 // 在Dashboard.jsx的renderContent函数中修改case 'help'部分
711 case 'help':
712 return (
713 <div className="content-area" data-testid="help-section">
714 {/* 新增发帖按钮 */}
715 <div className="post-header">
716 <button
717 className="create-post-btn"
718 onClick={() => setShowPostModal(true)}
719 >
720 发帖求助
721 </button>
722 </div>
723
724 {/* 加载状态和错误提示 */}
725 {helpLoading && <div className="loading">加载中...</div>}
726 {helpError && <div className="error">{helpError}</div>}
727
728 {/* 求助区帖子列表 */}
729 <div className="help-list">
730 {helpPosts.map(post => (
731 <div
732 key={post.id}
733 className={`help-post ${post.isSolved ? 'solved' : ''}`}
734 onClick={() => navigate(`/help/${post.id}`)}
735 >
736 <div className="post-header">
737 <img
738 src={post.authorAvatar || 'https://via.placeholder.com/40'}
739 alt={post.authorId}
740 className="post-avatar"
741 />
742 <div className="post-author">{post.authorId}</div>
743 <div className="post-date">
744 {new Date(post.createTime).toLocaleDateString()}
745 </div>
746 {post.isSolved && <span className="solved-badge">已解决</span>}
747 </div>
748 <h3 className="post-title">{post.title}</h3>
749 <p className="post-content">{post.content}</p>
750 <div className="post-stats">
751 <span className="post-likes">👍 {post.likeCount || 0}</span>
752 <span className="post-comments">💬 {post.replyCount || 0}</span>
753 </div>
754 </div>
755 ))}
756 </div>
757
758 {/* 在帖子列表后添加分页控件 */}
759 <div className="pagination">
760 <button
761 onClick={() => fetchHelpPosts(currentPage - 1)}
762 disabled={currentPage === 1}
763 >
764 上一页
765 </button>
766
767 {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
768 <button
769 key={page}
770 onClick={() => fetchHelpPosts(page)}
771 className={currentPage === page ? 'active' : ''}
772 >
773 {page}
774 </button>
775 ))}
776
777 <button
778 onClick={() => fetchHelpPosts(currentPage + 1)}
779 disabled={currentPage === totalPages}
780 >
781 下一页
782 </button>
783 </div>
784
785 {/* 新增发帖弹窗 */}
786 {showPostModal && (
787 <div className="post-modal-overlay">
788 <div className="post-modal">
789 <h3>发布求助帖</h3>
790 <button
791 className="modal-close-btn"
792 onClick={() => setShowPostModal(false)}
793 >
794 ×
795 </button>
796
797 <form onSubmit={handlePostSubmit}>
798 <div className="form-group">
799 <label>帖子标题</label>
800 <input
801 type="text"
802 value={postTitle}
803 onChange={(e) => setPostTitle(e.target.value)}
804 placeholder="请输入标题"
805 required
806 />
807 </div>
808
809 <div className="form-group">
810 <label>帖子内容</label>
811 <textarea
812 value={postContent}
813 onChange={(e) => setPostContent(e.target.value)}
814 placeholder="详细描述你的问题"
815 required
816 />
817 </div>
818
819 <div className="form-group">
820 <label>上传图片</label>
821 <div className="upload-image-btn">
822 <input
823 type="file"
824 id="image-upload"
825 accept="image/*"
826 onChange={handleImageUpload}
827 style={{display: 'none'}}
828 />
829 <label htmlFor="image-upload">
830 {selectedImage ? '已选择图片' : '选择图片'}
831 </label>
832 {selectedImage && (
833 <span className="image-name">{selectedImage.name}</span>
834 )}
835 </div>
836 </div>
837
838 <div className="form-actions">
839 <button
840 type="button"
841 className="cancel-btn"
842 onClick={() => setShowPostModal(false)}
843 >
844 取消
845 </button>
846 <button
847 type="submit"
848 className="submit-btn"
849 >
850 确认发帖
851 </button>
852 </div>
853 </form>
854 </div>
855 </div>
856 )}
857 </div>
858 );
859 default:
860 return <div className="content-area" data-testid="default-section">公告区内容</div>;
861 }
862 };
863
864 if (loading) return <div className="loading">加载中...</div>;
865 if (error) return <div className="error">{error}</div>;
866
867 return (
868 <div className="dashboard-container" data-testid="dashboard-container">
869 {/* 顶部栏 */}
870 <div className="top-bar" data-testid="top-bar">
871 {/* 搜索框 */}
872 <div className="search-container">
873 <input
874 type="text"
875 placeholder="搜索种子、用户..."
876 className="search-input"
877 />
878 <button className="search-button">搜索</button>
Akane121765b61a72025-05-17 13:52:25 +0800879 </div>
Akane121765b61a72025-05-17 13:52:25 +0800880
22301080a93bebb2025-05-27 19:48:11 +0800881 <div className="user-actions">
882 {/* 新增管理员按钮 - 只有管理员可见 */}
883 {userInfo?.isAdmin && (
884 <button
885 className="admin-center-button"
886 onClick={() => navigate('/administer')}
887 >
888 管理员中心
889 </button>
890 )}
Akane121765b61a72025-05-17 13:52:25 +0800891
22301080a93bebb2025-05-27 19:48:11 +0800892 <div className="user-info" data-testid="user-info">
893 <img
894 src={userInfo?.avatar || 'https://via.placeholder.com/40'}
895 alt="用户头像"
896 className="user-avatar"
897 onClick={() => navigate('/personal')}
898 style={{cursor: 'pointer'}}
899 />
900 <span className="username">{userInfo?.name || '用户'}</span>
901 <button onClick={onLogout} className="logout-button">退出</button>
902 </div>
903 </div>
904 </div>
905
906 {/* 导航栏 */}
907 {/* handleTabchange函数替换了原本的setactivetab函数 */}
908 <div className="nav-tabs">
909 <button
910 className={`tab-button ${activeTab === 'announcement' ? 'active' : ''}`}
911 onClick={() => handleTabChange('announcement')}
912 >
913 公告区
914 </button>
915 <button
916 className={`tab-button ${activeTab === 'share' ? 'active' : ''}`}
917 onClick={() => handleTabChange('share')}
918 >
919 分享区
920 </button>
921 <button
922 className={`tab-button ${activeTab === 'request' ? 'active' : ''}`}
923 onClick={() => handleTabChange('request')}
924 >
925 求种区
926 </button>
927 <button
928 className={`tab-button ${activeTab === 'help' ? 'active' : ''}`}
929 onClick={() => handleTabChange('help')}
930 >
931 求助区
932 </button>
933 </div>
934
935 {/* 内容区 */}
936 {renderContent()}
Akane121765b61a72025-05-17 13:52:25 +0800937 </div>
22301080a93bebb2025-05-27 19:48:11 +0800938 );
Akane121765b61a72025-05-17 13:52:25 +0800939};
940
941export default Dashboard;