前段
Change-Id: I718d4d07ea03c6d2b6bcbd4d426c5d1af2201bf4
diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx
new file mode 100644
index 0000000..941210c
--- /dev/null
+++ b/src/components/Dashboard.jsx
@@ -0,0 +1,940 @@
+import React, { useEffect, useState } from 'react';
+import { useNavigate, useLocation,useParams } from 'react-router-dom';
+// import { getUserInfo } from '../api'; // 保留import但注释掉
+import { createTorrent, getTorrents} from '../api/torrent';
+import './Dashboard.css';
+import { createPost, getPosts } from '../api/helpPost';
+
+
+const Dashboard = ({ onLogout }) => {
+ const location = useLocation();
+ const [userInfo, setUserInfo] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+ const [currentSlide, setCurrentSlide] = useState(0);
+ const navigate = useNavigate();
+ const {tab} = useParams();
+ const [showUploadModal, setShowUploadModal] = useState(false);
+ const [isUploading, setIsUploading] = useState(false);
+ const [uploadData, setUploadData] = useState({
+ name: '',
+ type: '',
+ region: '',
+ subtitle: '',
+ resolution: '', // 新增分辨率字段
+ file: null,
+ description: ''
+ });
+ const [showPostModal, setShowPostModal] = useState(false);
+ const [postTitle, setPostTitle] = useState('');
+ const [postContent, setPostContent] = useState('');
+ const [selectedImage, setSelectedImage] = useState(null);
+ const [helpPosts, setHelpPosts] = useState([]);
+ const [helpLoading, setHelpLoading] = useState(false);
+ const [helpError, setHelpError] = useState(null);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [totalPages, setTotalPages] = useState(1);
+
+ // 添加状态
+ const [torrentPosts, setTorrentPosts] = useState([]);
+ const [torrentLoading, setTorrentLoading] = useState(false);
+ const [torrentError, setTorrentError] = useState(null);
+
+ const [filteredResources, setFilteredResources] = useState(torrentPosts);
+
+ const activeTab = tab || 'announcement'; // 如果没有tab参数,则默认为announcement
+ // 从location.state中初始化状态
+
+
+ const handleTabChange = (tabName) => {
+ navigate(`/dashboard/${tabName}`, {
+ state: {
+ savedFilters: selectedFilters, // 使用新的筛选状态 // 保留现有状态
+ activeTab: tabName // 可选,如果其他组件依赖这个 state
+ }
+ });
+ };
+
+ //公告区
+ const [announcements] = useState([
+ {
+ id: 1,
+ title: '系统维护与更新',
+ content: '2023-10-15 02:00-06:00将进行系统维护升级,期间网站将无法访问。本次更新包含:\n1. 数据库服务器迁移\n2. 安全补丁更新\n3. CDN节点优化\n\n请提前做好下载安排。',
+ author: '系统管理员',
+ date: '2023-10-10',
+ excerpt: '2023-10-15 02:00-06:00将进行系统维护,期间无法访问',
+ category: '系统'
+ },
+ {
+ id: 2,
+ title: '资源上新',
+ content: '最新热门电影《奥本海默》4K REMUX资源已上线,包含:\n- 4K HDR版本 (56.8GB)\n- 1080P标准版 (12.3GB)\n- 中英双语字幕\n\n欢迎下载保种!',
+ author: '资源组',
+ date: '2023-10-08',
+ excerpt: '最新热门电影《奥本海默》4K资源已上线',
+ category: '资源'
+ },
+ {
+ id: 3,
+ title: '积分规则调整',
+ content: '自11月1日起,上传资源积分奖励提升20%,具体规则如下:\n- 上传电影资源:每GB 10积分\n- 上传电视剧资源:每GB 8积分\n- 上传动漫资源:每GB 6积分\n\n感谢大家的支持与贡献!',
+ author: '管理员',
+ date: '2023-10-05',
+ excerpt: '自11月1日起,上传资源积分奖励提升20%',
+ category: '公告'
+ },
+ {
+ id: 4,
+ title: '违规处理公告',
+ content: '用户user123因发布虚假资源已被封禁,相关资源已删除。请大家遵守社区规则,维护良好的分享环境。',
+ author: '管理员',
+ date: '2023-10-03',
+ excerpt: '用户user123因发布虚假资源已被封禁',
+ category: '违规'
+ },
+ {
+ id: 5,
+ title: '节日活动',
+ content: '国庆期间所有资源下载积分减半,活动时间:2023年10月1日至2023年10月7日。',
+ author: '活动组',
+ date: '2023-09-30',
+ excerpt: '国庆期间所有资源下载积分减半',
+ category: '活动'
+ },
+ {
+ id: 6,
+ title: '客户端更新',
+ content: 'PT客户端v2.5.0已发布,修复多个BUG,新增资源搜索功能。请尽快更新到最新版本以获得更好的使用体验。',
+ author: '开发组',
+ date: '2023-09-28',
+ excerpt: 'PT客户端v2.5.0已发布,修复多个BUG',
+ category: '更新'
+ },
+ // 其他公告...
+ ]);
+
+
+ const handleAnnouncementClick = (announcement,e) => {
+ if (!e.target.closest('.exclude-click')) {
+ navigate(`/announcement/${announcement.id}`, {
+ state: {
+ announcement,
+ returnPath: `/dashboard/${activeTab}`,
+ scrollPosition: window.scrollY,
+ activeTab
+ }
+ });
+ }
+ };
+
+
+ //资源区
+
+
+ const handleFileChange = (e) => {
+ setUploadData({...uploadData, file: e.target.files[0]});
+ };
+
+ const handleUploadSubmit = async (e) => {
+ e.preventDefault();
+
+ try {
+ setIsUploading(true);
+ const username = localStorage.getItem('username');
+
+ // 创建种子数据
+ const torrentData = {
+ torrentName: uploadData.name,
+ category: uploadData.type,
+ regin: uploadData.region,
+ resolution: uploadData.resolution,
+ subtitle: uploadData.subtitle,
+ description: uploadData.description,
+ username: username,
+ filePath: "D:\\大学\\大三_下\\实训BT服务器下载\\G2-ptPlatform-backend\\torrents\\[电影天堂www.dytt89.com]两杆大烟枪BD中英双字.mp4.torrent" // 默认路径
+ };
+
+ // 调用API创建种子
+ await createTorrent(
+ torrentData.torrentName,
+ torrentData.description,
+ torrentData.username,
+ torrentData.category,
+ torrentData.regin,
+ torrentData.resolution,
+ torrentData.subtitle,
+ torrentData.filePath
+ );
+
+ // 上传成功处理
+ setShowUploadModal(false);
+ setUploadData({
+ name: '',
+ type: '',
+ region: '',
+ subtitle: '',
+ resolution: '',
+ file: null,
+ description: ''
+ });
+ alert('种子创建成功!');
+
+ // 刷新列表
+ fetchTorrentPosts(currentPage);
+
+ } catch (error) {
+ console.error('创建失败:', error);
+ alert('创建失败: ' + (error.response?.data?.message || error.message));
+ } finally {
+ setIsUploading(false);
+ }
+ };
+
+ const handlePostSubmit = async (e) => {
+ e.preventDefault();
+ try {
+ const username = localStorage.getItem('username');
+ const response = await createPost(
+ postTitle,
+ postContent,
+ username
+ );
+
+ if (response.data.code === 200) {
+ // 刷新帖子列表
+ await fetchHelpPosts();
+ // 重置表单
+ setShowPostModal(false);
+ setPostTitle('');
+ setPostContent('');
+ setSelectedImage(null);
+ } else {
+ setHelpError(response.data.message || '发帖失败');
+ }
+ } catch (err) {
+ setHelpError(err.message || '发帖失败');
+ }
+ };
+
+ // 获取Torrent帖子列表
+ const fetchTorrentPosts = async (page = 1) => {
+ setTorrentLoading(true);
+ try {
+ const response = await getTorrents(page);
+ setTorrentPosts(response.data.data.records);
+ setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条
+ setCurrentPage(page);
+ } catch (err) {
+ setTorrentError(err.message);
+ } finally {
+ setTorrentLoading(false);
+ }
+};
+
+ // 在useEffect中调用
+ useEffect(() => {
+ if (activeTab === 'share') {
+ fetchTorrentPosts();
+ }
+ }, [activeTab]);
+
+ const handleImageUpload = (e) => {
+ setSelectedImage(e.target.files[0]);
+ };
+
+
+
+
+ const fetchHelpPosts = async (page = 1) => {
+ setHelpLoading(true);
+ try {
+ const response = await getPosts(page);
+ if (response.data.code === 200) {
+ setHelpPosts(response.data.data.records);
+ setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条
+ setCurrentPage(page);
+ } else {
+ setHelpError(response.data.message || '获取求助帖失败');
+ }
+ } catch (err) {
+ setHelpError(err.message || '获取求助帖失败');
+ } finally {
+ setHelpLoading(false);
+ }
+ };
+ useEffect(() => {
+ if (activeTab === 'help') {
+ fetchHelpPosts(currentPage);
+ }
+ }, [activeTab, currentPage]); // 添加 currentPage 作为依赖
+
+
+
+
+ // 分类维度配置
+ const filterCategories = {
+ type: {
+ label: '类型',
+ options: {
+ 'all': '全部',
+ '电影': '电影',
+ '电视剧': '电视剧',
+ '动漫': '动漫',
+ '综艺': '综艺'
+ }
+ },
+ subtitle: {
+ label: '字幕',
+ options: {
+ 'all': '全部',
+ 'yes': '有字幕',
+ 'no': '无字幕'
+ }
+ },
+ region: {
+ label: '地区',
+ options: {
+ 'all': '全部',
+ 'cn': '大陆',
+ 'us': '欧美',
+ 'jp': '日本'
+ }
+ }
+ };
+
+ const [selectedFilters, setSelectedFilters] = useState(
+ location.state?.savedFilters ||
+ Object.keys(filterCategories).reduce((acc, category) => {
+ acc[category] = 'all';
+ return acc;
+ }, {})
+ );
+
+
+
+// 处理筛选条件变更
+const handleFilterSelect = (category, value) => {
+ setSelectedFilters(prev => ({
+ ...prev,
+ [category]: prev[category] === value ? null : value // 点击已选中的则取消
+ }));
+};
+
+//应用筛选条件
+const applyFilters = () => {
+ const result = torrentPosts.filter(resource => {
+ return Object.entries(selectedFilters).every(([category, selectedValue]) => {
+ if (selectedValue === 'all') return true;
+ if (category === 'subtitle') {
+ return resource.subtitle === (selectedValue === 'yes');
+ }
+ return resource[category] === selectedValue;
+ });
+ });
+ setFilteredResources(result);
+};
+
+
+ // 恢复滚动位置
+ useEffect(() => {
+ if (location.state?.scrollPosition) {
+ window.scrollTo(0, location.state.scrollPosition);
+ }
+ }, [location.state]);
+
+
+ useEffect(() => {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ navigate('/login');
+ return;
+ }
+
+ /* 保留但注释掉实际的用户信息获取
+ const fetchUserInfo = async () => {
+ try {
+ const response = await getUserInfo(token);
+ if (response.data.code === 200) {
+ setUserInfo(response.data.data);
+ } else {
+ setError('获取用户信息失败');
+ }
+ } catch (err) {
+ setError('获取用户信息失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchUserInfo();
+ */
+
+ // 模拟用户信息
+ setUserInfo({
+ name: localStorage.getItem('username') || '演示用户', // 确保这里读取的是最新值
+ avatar: 'https://via.placeholder.com/40'
+ });
+ setLoading(false);
+ }, [navigate]);
+
+ // 轮播图自动切换效果
+ useEffect(() => {
+ if (activeTab === 'announcement') {
+ const timer = setInterval(() => {
+ setCurrentSlide(prev => (prev + 1) % 3); // 3张轮播图循环
+ }, 3000);
+ return () => clearInterval(timer);
+ }
+ }, [activeTab]);
+
+ useEffect(() => {
+ if (activeTab === 'help') {
+ fetchHelpPosts();
+ }
+ }, [activeTab]);
+
+ const renderContent = () => {
+ switch(activeTab) {
+ case 'announcement':
+ return (
+ <div className="content-area" data-testid="announcement-section">
+ {/* 轮播图区域 */}
+ <div className="carousel-container">
+ <div className={`carousel-slide ${currentSlide === 0 ? 'active' : ''}`}>
+ <div className="carousel-image gray-bg">促销活动1</div>
+ </div>
+ <div className={`carousel-slide ${currentSlide === 1 ? 'active' : ''}`}>
+ <div className="carousel-image gray-bg">促销活动2</div>
+ </div>
+ <div className={`carousel-slide ${currentSlide === 2 ? 'active' : ''}`}>
+ <div className="carousel-image gray-bg">促销活动3</div>
+ </div>
+ <div className="carousel-dots">
+ <span className={`dot ${currentSlide === 0 ? 'active' : ''}`}></span>
+ <span className={`dot ${currentSlide === 1 ? 'active' : ''}`}></span>
+ <span className={`dot ${currentSlide === 2 ? 'active' : ''}`}></span>
+ </div>
+ </div>
+
+ {/* 公告区块区域 */}
+ <div className="announcement-grid">
+ {announcements.map(announcement => (
+ <div
+ key={announcement.id}
+ className="announcement-card"
+ onClick={(e) => handleAnnouncementClick(announcement, e)}
+ >
+ <h3>{announcement.title}</h3>
+ <p>{announcement.excerpt}</p>
+ <div className="announcement-footer exclude-click">
+ <span>{announcement.author}</span>
+ <span>{announcement.date}</span>
+ </div>
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+ case 'share':
+ return (
+ <div className="content-area" data-testid="share-section">
+ {/* 上传按钮 - 添加在筛选区上方 */}
+ <div className="upload-header">
+ <button
+ className="upload-btn"
+ onClick={() => setShowUploadModal(true)}
+ >
+ + 上传种子
+ </button>
+ </div>
+ {/* 分类筛选区 */}
+ <div className="filter-section">
+ {Object.entries(filterCategories).map(([category, config]) => (
+ <div key={category} className="filter-group">
+ <h4>{config.label}:</h4>
+ <div className="filter-options">
+ {Object.entries(config.options).map(([value, label]) => (
+ <button
+ key={value}
+ className={`filter-btn ${
+ selectedFilters[category] === value ? 'active' : ''
+ }`}
+ onClick={() => handleFilterSelect(category, value)}
+ >
+ {label}
+ </button>
+ ))}
+ </div>
+ </div>
+ ))}
+
+ <button
+ className="confirm-btn"
+ onClick={applyFilters}
+ >
+ 确认筛选
+ </button>
+ </div>
+
+ {/* 上传弹窗 */}
+ {showUploadModal && (
+ <div className="modal-overlay">
+ <div className="upload-modal">
+ <h3>上传新种子</h3>
+ <button
+ className="close-btn"
+ onClick={() => setShowUploadModal(false)}
+ >
+ ×
+ </button>
+
+ <form onSubmit={handleUploadSubmit}>
+ <div className="form-group">
+ <label>种子名称</label>
+ <input
+ type="text"
+ value={uploadData.name}
+ onChange={(e) => setUploadData({...uploadData, name: e.target.value})}
+ required
+ />
+ </div>
+
+ <div className="form-group">
+ <label>资源类型</label>
+ <select
+ value={uploadData.type}
+ onChange={(e) => setUploadData({...uploadData, type: e.target.value})}
+ required
+ >
+ <option value="">请选择</option>
+ <option value="电影">电影</option>
+ <option value="电视剧">电视剧</option>
+ <option value="动漫">动漫</option>
+ <option value="综艺">综艺</option>
+ <option value="音乐">音乐</option>
+ </select>
+ </div>
+
+ {/* 新增地区输入框 */}
+ <div className="form-group">
+ <label>地区</label>
+ <input
+ type="text"
+ value={uploadData.region || ''}
+ onChange={(e) => setUploadData({...uploadData, region: e.target.value})}
+ placeholder="例如: 美国, 中国, 日本等"
+ required
+ />
+ </div>
+
+ {/* 添加分辨率下拉框 */}
+ <div className="form-group">
+ <label>分辨率</label>
+ <select
+ value={uploadData.resolution || ''}
+ onChange={(e) => setUploadData({...uploadData, resolution: e.target.value})}
+ required
+ >
+ <option value="">请选择</option>
+ <option value="4K">4K</option>
+ <option value="2K">2K</option>
+ <option value="1080P">1080P</option>
+ <option value="720P">720P</option>
+ <option value="SD">SD</option>
+ <option value="无损音源">无损音源</option>
+ <option value="杜比全景声">杜比全景声</option>
+ <option value="其他">其他</option>
+ </select>
+ </div>
+
+
+
+ {/* 新增字幕语言下拉框 */}
+ <div className="form-group">
+ <label>字幕语言</label>
+ <select
+ value={uploadData.subtitle || ''}
+ onChange={(e) => setUploadData({...uploadData, subtitle: e.target.value})}
+ required
+ >
+ <option value="">请选择</option>
+ <option value="无需字幕">无需字幕</option>
+ <option value="暂无字幕">暂无字幕</option>
+ <option value="自带中文字幕">自带中文字幕</option>
+ <option value="自带双语字幕(含中文)">自带双语字幕(含中文)</option>
+ <option value="附件中文字幕">附件中文字幕</option>
+ <option value="附件双语字幕">附件双语字幕</option>
+ </select>
+ </div>
+
+ <div className="form-group">
+ <label>种子文件</label>
+ <input
+ type="file"
+ accept=".torrent"
+ onChange={handleFileChange}
+
+ />
+ </div>
+
+ <div className="form-group">
+ <label>简介</label>
+ <textarea
+ value={uploadData.description}
+ onChange={(e) => setUploadData({...uploadData, description: e.target.value})}
+ />
+ </div>
+
+ <div className="form-actions">
+ <button
+ type="button"
+ className="cancel-btn"
+ onClick={() => setShowUploadModal(false)}
+ >
+ 取消
+ </button>
+ <button
+ type="submit"
+ className="confirm-btn"
+ disabled={isUploading}
+ >
+ {isUploading ? '上传中...' : '确认上传'}
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+ )}
+
+ <div className="resource-list">
+ {torrentPosts.map(torrent => (
+ <div
+ key={torrent.id}
+ className="resource-item"
+ onClick={() => navigate(`/torrent/${torrent.id}`)}
+ >
+ <div className="resource-poster">
+ <div className="poster-image gray-bg">{torrent.torrentName.charAt(0)}</div>
+ </div>
+ <div className="resource-info">
+ <h3 className="resource-title">{torrent.torrentName}</h3>
+ <p className="resource-meta">
+ {torrent.resolution} | {torrent.region} | {torrent.category}
+ </p>
+ <p className="resource-subtitle">字幕: {torrent.subtitle}</p>
+ </div>
+ <div className="resource-stats">
+ <span className="stat">{torrent.size}</span>
+ <span className="stat">发布者: {torrent.username}</span>
+ </div>
+ <button
+ className="download-btn"
+ onClick={(e) => {
+ e.stopPropagation();
+ // 下载逻辑
+ }}
+ >
+ 立即下载
+ </button>
+ </div>
+ ))}
+ </div>
+
+ {/* 分页控件 */}
+ <div className="pagination">
+ <button
+ onClick={() => fetchTorrentPosts(currentPage - 1)}
+ disabled={currentPage === 1}
+ >
+ 上一页
+ </button>
+
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
+ <button
+ key={page}
+ onClick={() => fetchTorrentPosts(page)}
+ className={currentPage === page ? 'active' : ''}
+ >
+ {page}
+ </button>
+ ))}
+
+ <button
+ onClick={() => fetchTorrentPosts(currentPage + 1)}
+ disabled={currentPage === totalPages}
+ >
+ 下一页
+ </button>
+ </div>
+ </div>
+ );
+ // 在Dashboard.jsx的renderContent函数中修改case 'request'部分
+ case 'request':
+ return (
+ <div className="content-area" data-testid="request-section">
+ {/* 求种区帖子列表 */}
+ <div className="request-list">
+ {[
+ {
+ id: 1,
+ title: '求《药屋少女的呢喃》第二季全集',
+ content: '求1080P带中文字幕版本,最好是内嵌字幕不是外挂的',
+ author: '动漫爱好者',
+ authorAvatar: 'https://via.placeholder.com/40',
+ date: '2023-10-15',
+ likeCount: 24,
+ commentCount: 8
+ },
+ {
+ id: 2,
+ title: '求《奥本海默》IMAX版',
+ content: '最好是原盘或者高码率的版本,谢谢各位大佬',
+ author: '电影收藏家',
+ authorAvatar: 'https://via.placeholder.com/40',
+ date: '2023-10-14',
+ likeCount: 15,
+ commentCount: 5
+ }
+ ].map(post => (
+ <div
+ key={post.id}
+ className="request-post"
+ onClick={() => navigate(`/request/${post.id}`)}
+ >
+ <div className="post-header">
+ <img src={post.authorAvatar} alt={post.author} className="post-avatar" />
+ <div className="post-author">{post.author}</div>
+ <div className="post-date">{post.date}</div>
+ </div>
+ <h3 className="post-title">{post.title}</h3>
+ <p className="post-content">{post.content}</p>
+ <div className="post-stats">
+ <span className="post-likes">👍 {post.likeCount}</span>
+ <span className="post-comments">💬 {post.commentCount}</span>
+ </div>
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+ // 在Dashboard.jsx的renderContent函数中修改case 'help'部分
+ case 'help':
+ return (
+ <div className="content-area" data-testid="help-section">
+ {/* 新增发帖按钮 */}
+ <div className="post-header">
+ <button
+ className="create-post-btn"
+ onClick={() => setShowPostModal(true)}
+ >
+ + 发帖求助
+ </button>
+ </div>
+
+ {/* 加载状态和错误提示 */}
+ {helpLoading && <div className="loading">加载中...</div>}
+ {helpError && <div className="error">{helpError}</div>}
+
+ {/* 求助区帖子列表 */}
+ <div className="help-list">
+ {helpPosts.map(post => (
+ <div
+ key={post.id}
+ className={`help-post ${post.isSolved ? 'solved' : ''}`}
+ onClick={() => navigate(`/help/${post.id}`)}
+ >
+ <div className="post-header">
+ <img
+ src={post.authorAvatar || 'https://via.placeholder.com/40'}
+ alt={post.authorId}
+ className="post-avatar"
+ />
+ <div className="post-author">{post.authorId}</div>
+ <div className="post-date">
+ {new Date(post.createTime).toLocaleDateString()}
+ </div>
+ {post.isSolved && <span className="solved-badge">已解决</span>}
+ </div>
+ <h3 className="post-title">{post.title}</h3>
+ <p className="post-content">{post.content}</p>
+ <div className="post-stats">
+ <span className="post-likes">👍 {post.likeCount || 0}</span>
+ <span className="post-comments">💬 {post.replyCount || 0}</span>
+ </div>
+ </div>
+ ))}
+ </div>
+
+ {/* 在帖子列表后添加分页控件 */}
+ <div className="pagination">
+ <button
+ onClick={() => fetchHelpPosts(currentPage - 1)}
+ disabled={currentPage === 1}
+ >
+ 上一页
+ </button>
+
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
+ <button
+ key={page}
+ onClick={() => fetchHelpPosts(page)}
+ className={currentPage === page ? 'active' : ''}
+ >
+ {page}
+ </button>
+ ))}
+
+ <button
+ onClick={() => fetchHelpPosts(currentPage + 1)}
+ disabled={currentPage === totalPages}
+ >
+ 下一页
+ </button>
+ </div>
+
+ {/* 新增发帖弹窗 */}
+ {showPostModal && (
+ <div className="post-modal-overlay">
+ <div className="post-modal">
+ <h3>发布求助帖</h3>
+ <button
+ className="modal-close-btn"
+ onClick={() => setShowPostModal(false)}
+ >
+ ×
+ </button>
+
+ <form onSubmit={handlePostSubmit}>
+ <div className="form-group">
+ <label>帖子标题</label>
+ <input
+ type="text"
+ value={postTitle}
+ onChange={(e) => setPostTitle(e.target.value)}
+ placeholder="请输入标题"
+ required
+ />
+ </div>
+
+ <div className="form-group">
+ <label>帖子内容</label>
+ <textarea
+ value={postContent}
+ onChange={(e) => setPostContent(e.target.value)}
+ placeholder="详细描述你的问题"
+ required
+ />
+ </div>
+
+ <div className="form-group">
+ <label>上传图片</label>
+ <div className="upload-image-btn">
+ <input
+ type="file"
+ id="image-upload"
+ accept="image/*"
+ onChange={handleImageUpload}
+ style={{ display: 'none' }}
+ />
+ <label htmlFor="image-upload">
+ {selectedImage ? '已选择图片' : '选择图片'}
+ </label>
+ {selectedImage && (
+ <span className="image-name">{selectedImage.name}</span>
+ )}
+ </div>
+ </div>
+
+ <div className="form-actions">
+ <button
+ type="button"
+ className="cancel-btn"
+ onClick={() => setShowPostModal(false)}
+ >
+ 取消
+ </button>
+ <button
+ type="submit"
+ className="submit-btn"
+ >
+ 确认发帖
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+ )}
+ </div>
+ );
+ default:
+ return <div className="content-area" data-testid="default-section">公告区内容</div>;
+ }
+ };
+
+ if (loading) return <div className="loading">加载中...</div>;
+ if (error) return <div className="error">{error}</div>;
+
+ return (
+ <div className="dashboard-container" data-testid="dashboard-container">
+ {/* 顶部栏 */}
+ <div className="top-bar" data-testid="top-bar">
+ {/* 搜索框 */}
+ <div className="search-container">
+ <input
+ type="text"
+ placeholder="搜索种子、用户..."
+ className="search-input"
+ />
+ <button className="search-button">搜索</button>
+ </div>
+
+ <div className="user-info" data-testid="user-info">
+ <img
+ src={userInfo?.avatar || 'https://via.placeholder.com/40'}
+ alt="用户头像"
+ className="user-avatar"
+ onClick={() => navigate('/personal')} // 添加点击跳转
+ style={{ cursor: 'pointer' }} // 添加手型指针
+ />
+ <span className="username">{userInfo?.name || '用户'}</span>
+ <button onClick={onLogout} className="logout-button">退出</button>
+ </div>
+ </div>
+
+ {/* 导航栏 */}
+ {/* handleTabchange函数替换了原本的setactivetab函数 */}
+ <div className="nav-tabs">
+ <button
+ className={`tab-button ${activeTab === 'announcement' ? 'active' : ''}`}
+ onClick={() => handleTabChange('announcement')}
+ >
+ 公告区
+ </button>
+ <button
+ className={`tab-button ${activeTab === 'share' ? 'active' : ''}`}
+ onClick={() => handleTabChange('share')}
+ >
+ 分享区
+ </button>
+ <button
+ className={`tab-button ${activeTab === 'request' ? 'active' : ''}`}
+ onClick={() => handleTabChange('request')}
+ >
+ 求种区
+ </button>
+ <button
+ className={`tab-button ${activeTab === 'help' ? 'active' : ''}`}
+ onClick={() => handleTabChange('help')}
+ >
+ 求助区
+ </button>
+ </div>
+
+ {/* 内容区 */}
+ {renderContent()}
+ </div>
+ );
+};
+
+export default Dashboard;
\ No newline at end of file