完成上传下载连接,公告管理与详情页面,求种区页面,轮播图折扣显示,修改部分bug
Change-Id: I86fc294e32911cb3426a8b16f90aca371f975c11
diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx
index f8f5dd3..40bd19d 100644
--- a/src/components/Dashboard.jsx
+++ b/src/components/Dashboard.jsx
@@ -1,9 +1,13 @@
import React, {useEffect, useState} from 'react';
import {useNavigate, useLocation, useParams} from 'react-router-dom';
-import {createTorrent, getTorrents, searchTorrents} from '../api/torrent';
+import {createTorrent, getTorrents, downloadTorrent, getDownloadProgress, deleteTorrent, searchTorrents} from '../api/torrent';
import './Dashboard.css';
-import {createPost, getPosts, getPostDetail, searchPosts} from '../api/helpPost';
-import { getUserInfo, isAdmin } from '../api/auth';
+import {createHelpPost, getHelpPosts, getHelpPostDetail, searchHelpPosts} from '../api/helpPost';
+import {createRequestPost, getRequestPosts, getRequestPostDetail, searchRequestPosts} from '../api/requestPost';
+import { message } from 'antd'; // 用于显示提示消息
+import { getAnnouncements,getLatestAnnouncements,getAnnouncementDetail } from '../api/announcement';
+import { getAllDiscounts } from '../api/administer';
+ import { getUserInfo, isAdmin } from '../api/auth';
import { api } from '../api/auth';
@@ -36,6 +40,11 @@
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [likedPosts,setLikedPosts] = useState({});
+ const [announcements, setAnnouncements] = useState([]);
+ const [carouselDiscounts, setCarouselDiscounts] = useState([]);
+ const [requestLoading, setRequestLoading] = useState(false);
+ const [requestError, setRequestError] = useState(null);
+ const [requestPosts, setRequestPosts] = useState([]);
// 添加状态
@@ -46,6 +55,13 @@
const [filteredResources, setFilteredResources] = useState(torrentPosts);
const [isAdmin, setIsAdmin] = useState(false);
+ // 在组件状态中添加
+ const [showDownloadModal, setShowDownloadModal] = useState(false);
+ const [selectedTorrent, setSelectedTorrent] = useState(null);
+ const [downloadProgress, setDownloadProgress] = useState(0);
+ const [isDownloading, setIsDownloading] = useState(false);
+ const [downloadPath, setDownloadPath] = useState('D:/studies/ptPlatform/torrent');
+
// 新增搜索状态
const [announcementSearch, setAnnouncementSearch] = useState('');
const [shareSearch, setShareSearch] = useState('');
@@ -66,150 +82,54 @@
});
};
- //公告区
- 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 handleSearchAnnouncement = (e) => {
- setAnnouncementSearch(e.target.value);
- };
-
- // 修改后的搜索函数
- const handleSearchShare = async () => {
- try {
- setTorrentLoading(true);
- const response = await searchTorrents(shareSearch, 1);
- if (response.data.code === 200) {
- setTorrentPosts(response.data.data.records);
- const total = response.data.data.total;
- setTotalPages(Math.ceil(total / 5));
- setCurrentPage(1);
- } else {
- setTorrentError(response.data.message || '搜索失败');
- }
- } catch (err) {
- setTorrentError(err.message || '搜索失败');
- } finally {
- setTorrentLoading(false);
- }
- };
-
- const handleResetShareSearch = async () => {
- setShareSearch('');
- setSelectedFilters(
- Object.keys(filterCategories).reduce((acc, category) => {
- acc[category] = 'all';
- return acc;
- }, {})
- );
- await fetchTorrentPosts(1, true);
- };
-
- // 求种区搜索处理
- const handleSearchRequest = (e) => {
- setRequestSearch(e.target.value);
- };
-
- // 添加搜索函数
- const handleSearchHelp = async () => {
- try {
- setHelpLoading(true);
- const response = await searchPosts(helpSearch, currentPage);
- if (response.data.code === 200) {
- const postsWithCounts = await Promise.all(
- response.data.data.records.map(async (post) => {
- try {
- const detailResponse = await getPostDetail(post.id);
- if (detailResponse.data.code === 200) {
- return {
- ...post,
- replyCount: detailResponse.data.data.post.replyCount || 0,
- isLiked: false
- };
- }
- return post;
- } catch (err) {
- console.error(`获取帖子${post.id}详情失败:`, err);
- return post;
- }
- })
- );
- setHelpPosts(postsWithCounts);
- setTotalPages(Math.ceil(response.data.data.total / 5));
- } else {
- setHelpError(response.data.message || '搜索失败');
- }
- } catch (err) {
- setHelpError(err.message || '搜索失败');
- } finally {
- setHelpLoading(false);
- }
- };
- // 添加重置搜索函数
- const handleResetHelpSearch = async () => {
- setHelpSearch('');
- await fetchHelpPosts(1); // 重置到第一页
+
+ //公告区
+ // 添加获取公告的方法
+ const fetchAnnouncements = async () => {
+ try {
+ const response = await getLatestAnnouncements();
+ setAnnouncements(response.data.data.announcements || []);
+ } catch (error) {
+ console.error('获取公告失败:', error);
+ }
};
+ useEffect(() => {
+ if (activeTab === 'announcement') {
+ fetchAnnouncements();
+ fetchDiscountsForCarousel();
+ }
+ }, [activeTab]);
+ const fetchDiscountsForCarousel = async () => {
+ try {
+ const all = await getAllDiscounts();
+ console.log("返回的折扣数据:", all);
+ const now = new Date();
+ // ⚠️ 使用 Date.parse 确保兼容 ISO 格式
+ const ongoing = all.filter(d =>
+ Date.parse(d.startTime) <= now.getTime() && Date.parse(d.endTime) >= now.getTime()
+ );
+
+ const upcoming = all
+ .filter(d => Date.parse(d.startTime) > now.getTime())
+ .sort((a, b) => Date.parse(a.startTime) - Date.parse(b.startTime));
+
+ const selected = [...ongoing.slice(0, 3)];
+
+ while (selected.length < 3 && upcoming.length > 0) {
+ selected.push(upcoming.shift());
+ }
+
+ setCarouselDiscounts(selected);
+ } catch (e) {
+ console.error("获取折扣失败:", e);
+ }
+ };
+
+ // 修改handleAnnouncementClick函数中的state传递,移除不必要的字段
const handleAnnouncementClick = (announcement, e) => {
if (!e.target.closest('.exclude-click')) {
navigate(`/announcement/${announcement.id}`, {
@@ -223,6 +143,129 @@
}
};
+
+ // 公告区搜索处理
+ const handleSearchAnnouncement = (e) => {
+ setAnnouncementSearch(e.target.value);
+ };
+
+ // 修改后的搜索函数
+ const handleSearchShare = async () => {
+ try {
+ setTorrentLoading(true);
+ const response = await searchTorrents(shareSearch, 1);
+ if (response.data.code === 200) {
+ setTorrentPosts(response.data.data.records);
+ const total = response.data.data.total;
+ setTotalPages(Math.ceil(total / 5));
+ setCurrentPage(1);
+ } else {
+ setTorrentError(response.data.message || '搜索失败');
+ }
+ } catch (err) {
+ setTorrentError(err.message || '搜索失败');
+ } finally {
+ setTorrentLoading(false);
+ }
+ };
+
+ const handleResetShareSearch = async () => {
+ setShareSearch('');
+ setSelectedFilters(
+ Object.keys(filterCategories).reduce((acc, category) => {
+ acc[category] = 'all';
+ return acc;
+ }, {})
+ );
+ await fetchTorrentPosts(1, true);
+ };
+
+ // 添加搜索函数
+ const handleSearchRequest = async () => {
+ try {
+ setRequestLoading(true);
+ const response = await searchRequestPosts(requestSearch, currentPage);
+ if (response.data.code === 200) {
+ const postsWithCounts = await Promise.all(
+ response.data.data.records.map(async (post) => {
+ try {
+ const detailResponse = await getRequestPostDetail(post.id);
+ if (detailResponse.data.code === 200) {
+ return {
+ ...post,
+ replyCount: detailResponse.data.data.post.replyCount || 0,
+ isLiked: false
+ };
+ }
+ return post;
+ } catch (err) {
+ console.error(`获取帖子${post.id}详情失败:`, err);
+ return post;
+ }
+ })
+ );
+ setRequestPosts(postsWithCounts);
+ setTotalPages(Math.ceil(response.data.data.total / 5));
+ } else {
+ setRequestError(response.data.message || '搜索失败');
+ }
+ } catch (err) {
+ setRequestError(err.message || '搜索失败');
+ } finally {
+ setRequestLoading(false);
+ }
+ };
+
+ // 添加重置搜索函数
+ const handleResetRequestSearch = async () => {
+ setRequestSearch('');
+ await fetchRequestPosts(1); // 重置到第一页
+ };
+
+ // 添加搜索函数
+ const handleSearchHelp = async () => {
+ try {
+ setHelpLoading(true);
+ const response = await searchHelpPosts(helpSearch, currentPage);
+ if (response.data.code === 200) {
+ const postsWithCounts = await Promise.all(
+ response.data.data.records.map(async (post) => {
+ try {
+ const detailResponse = await getHelpPostDetail(post.id);
+ if (detailResponse.data.code === 200) {
+ return {
+ ...post,
+ replyCount: detailResponse.data.data.post.replyCount || 0,
+ isLiked: false
+ };
+ }
+ return post;
+ } catch (err) {
+ console.error(`获取帖子${post.id}详情失败:`, err);
+ return post;
+ }
+ })
+ );
+ setHelpPosts(postsWithCounts);
+ setTotalPages(Math.ceil(response.data.data.total / 5));
+ } else {
+ setHelpError(response.data.message || '搜索失败');
+ }
+ } catch (err) {
+ setHelpError(err.message || '搜索失败');
+ } finally {
+ setHelpLoading(false);
+ }
+ };
+
+ // 添加重置搜索函数
+ const handleResetHelpSearch = async () => {
+ setHelpSearch('');
+ await fetchHelpPosts(1); // 重置到第一页
+ };
+
+
+
//资源区
const handleFileChange = (e) => {
@@ -243,7 +286,10 @@
subtitle: uploadData.subtitle
};
- await createTorrent(torrentData, uploadData.file);
+ await createTorrent(uploadData.file, torrentData, (progress) => {
+ console.log(`上传进度: ${progress}%`);
+ // 这里可以添加进度条更新逻辑
+ });
// 上传成功处理
setShowUploadModal(false);
@@ -269,11 +315,164 @@
}
};
- const handlePostSubmit = async (e) => {
+ // 处理下载按钮点击
+ const handleDownloadClick = (torrent, e) => {
+ e.stopPropagation();
+ setSelectedTorrent(torrent);
+ setShowDownloadModal(true);
+ };
+
+ // 执行下载
+ const handleDownload = async () => {
+ if (!selectedTorrent || !downloadPath) return;
+
+ setIsDownloading(true);
+ setDownloadProgress(0);
+
+ try {
+ // 标准化路径
+ const cleanPath = downloadPath
+ .replace(/\\/g, '/') // 统一使用正斜杠
+ .replace(/\/+/g, '/') // 去除多余斜杠
+ .trim();
+
+ // 确保路径以斜杠结尾
+ const finalPath = cleanPath.endsWith('/') ? cleanPath : cleanPath + '/';
+
+ // 发起下载请求
+ await downloadTorrent(selectedTorrent.id, finalPath);
+
+ // 开始轮询进度
+ const interval = setInterval(async () => {
+ try {
+ const res = await getDownloadProgress();
+ const progresses = res.data.progresses;
+
+ if (progresses) {
+ // 使用完整的 torrent 文件路径作为键
+ const torrentPath = selectedTorrent.filePath.replace(/\\/g, '/');
+ const torrentHash = selectedTorrent.hash;
+ // 查找匹配的进度
+ let foundProgress = null;
+ for (const [key, value] of Object.entries(progresses)) {
+ const normalizedKey = key.replace(/\\/g, '/');
+ if (normalizedKey.includes(selectedTorrent.hash) ||
+ normalizedKey.includes(selectedTorrent.torrentName)) {
+ foundProgress = value;
+ break;
+ }
+ }
+ if (foundProgress !== null) {
+ const newProgress = Math.round(foundProgress * 100);
+ setDownloadProgress(newProgress);
+
+ // 检查是否下载完成
+ if (newProgress >= 100) {
+ clearInterval(interval);
+ setIsDownloading(false);
+ message.success('下载完成!');
+ setTimeout(() => setShowDownloadModal(false), 2000);
+ }
+ } else {
+ console.log('当前下载进度:', progresses); // 添加日志
+ }
+ }
+ } catch (error) {
+ console.error('获取进度失败:', error);
+ // 如果获取进度失败但文件已存在,也视为完成
+ const filePath = `${finalPath}${selectedTorrent.torrentName || 'downloaded_file'}`;
+ try {
+ const exists = await checkFileExists(filePath);
+ if (exists) {
+ clearInterval(interval);
+ setDownloadProgress(100);
+ setIsDownloading(false);
+ message.success('下载完成!');
+ setTimeout(() => setShowDownloadModal(false), 2000);
+ }
+ } catch (e) {
+ console.error('文件检查失败:', e);
+ }
+ }
+ }, 2000);
+
+ return () => clearInterval(interval);
+ } catch (error) {
+ setIsDownloading(false);
+ if (error.response && error.response.status === 409) {
+ message.error('分享率不足,无法下载此资源');
+ } else {
+ message.error('下载失败: ' + (error.message || '未知错误'));
+ }
+ }
+ };
+
+ const checkFileExists = async (filePath) => {
+ try {
+ // 这里需要根据您的实际环境实现文件存在性检查
+ // 如果是Electron应用,可以使用Node.js的fs模块
+ // 如果是纯前端,可能需要通过API请求后端检查
+ return true; // 暂时返回true,实际实现需要修改
+ } catch (e) {
+ console.error('检查文件存在性失败:', e);
+ return false;
+ }
+ };
+
+ const handleDeleteTorrent = async (torrentId, e) => {
+ e.stopPropagation(); // 阻止事件冒泡,避免触发资源项的点击事件
+
+ try {
+ // 确认删除
+ if (!window.confirm('确定要删除这个种子吗?此操作不可撤销!')) {
+ return;
+ }
+
+ // 调用删除API
+ await deleteTorrent(torrentId);
+
+ // 删除成功后刷新列表
+ message.success('种子删除成功');
+ await fetchTorrentPosts(currentPage);
+ } catch (error) {
+ console.error('删除种子失败:', error);
+ message.error('删除种子失败: ' + (error.response?.data?.message || error.message));
+ }
+ };
+
+ const handleRequestPostSubmit = async (e) => {
e.preventDefault();
try {
const username = localStorage.getItem('username');
- const response = await createPost(
+ const response = await createRequestPost(
+ postTitle,
+ postContent,
+ username,
+ selectedImage
+ );
+
+ if (response.data.code === 200) {
+ // 刷新帖子列表
+
+ await fetchRequestPosts(currentPage);
+ // 重置表单
+ setShowPostModal(false);
+ setPostTitle('');
+ setPostContent('');
+ setSelectedImage(null);
+ } else {
+ setHelpError(response.data.message || '发帖失败');
+ }
+ } catch (err) {
+ setHelpError(err.message || '发帖失败');
+ }
+ };
+
+ const handleHelpPostSubmit = async (e) => {
+ e.preventDefault();
+ try {
+ const username = localStorage.getItem('username');
+ const response = await createHelpPost(
postTitle,
postContent,
username,
@@ -283,6 +482,7 @@
if (response.data.code === 200) {
// 刷新帖子列表
await fetchHelpPosts(currentPage);
+
// 重置表单
setShowPostModal(false);
setPostTitle('');
@@ -339,6 +539,42 @@
}
}, [activeTab]);
+const fetchRequestPosts = async (page = 1) => {
+ setRequestLoading(true);
+ try {
+ const response = await getRequestPosts(page);
+ if (response.data.code === 200) {
+ const postsWithCounts = await Promise.all(
+ response.data.data.records.map(async (post) => {
+ try {
+ const detailResponse = await getRequestPostDetail(post.id);
+ if (detailResponse.data.code === 200) {
+ return {
+ ...post,
+ replyCount: detailResponse.data.data.post.replyCount || 0,
+ isLiked: false // 根据需要添加其他字段
+ };
+ }
+ return post; // 如果获取详情失败,返回原始帖子数据
+ } catch (err) {
+ console.error(`获取帖子${post.id}详情失败:`, err);
+ return post;
+ }
+ })
+ );
+ setRequestPosts(postsWithCounts);
+ setTotalPages(Math.ceil(response.data.data.total / 5)); // 假设每页5条
+ setCurrentPage(page);
+ } else {
+ setRequestError(response.data.message || '获取求助帖失败');
+ }
+ } catch (err) {
+ setRequestError(err.message || '获取求助帖失败');
+ } finally {
+ setRequestLoading(false);
+ }
+ };
+
const handleImageUpload = (e) => {
setSelectedImage(e.target.files[0]);
};
@@ -347,12 +583,12 @@
const fetchHelpPosts = async (page = 1) => {
setHelpLoading(true);
try {
- const response = await getPosts(page);
+ const response = await getHelpPosts(page);
if (response.data.code === 200) {
const postsWithCounts = await Promise.all(
response.data.data.records.map(async (post) => {
try {
- const detailResponse = await getPostDetail(post.id);
+ const detailResponse = await getHelpPostDetail(post.id);
if (detailResponse.data.code === 200) {
return {
...post,
@@ -382,8 +618,8 @@
useEffect(() => {
- if (activeTab === 'help') {
- fetchHelpPosts(currentPage);
+ if (activeTab === 'request') {
+ fetchRequestPosts(currentPage);
}
}, [activeTab, currentPage]); // 添加 currentPage 作为依赖
@@ -541,7 +777,10 @@
useEffect(() => {
if (activeTab === 'announcement') {
const timer = setInterval(() => {
- setCurrentSlide(prev => (prev + 1) % 3); // 3张轮播图循环
+ setCurrentSlide(prev => {
+ const count = carouselDiscounts.length || 1;
+ return (prev + 1) % count;
+ });
}, 3000);
return () => clearInterval(timer);
}
@@ -551,7 +790,9 @@
if (activeTab === 'help') {
fetchHelpPosts();
}
- }, [activeTab]);
+ }, [activeTab, currentPage]); // 添加 currentPage 作为依赖
+
+
const renderContent = () => {
switch (activeTab) {
@@ -575,22 +816,30 @@
</button>
</div>
{/* 轮播图区域 */}
- <div className="carousel-container">
- <div className={`carousel-slide ${currentSlide === 0 ? 'active' : ''}`}>
- <div className="carousel-image gray-bg">促销活动1</div>
+ <div className="carousel-container">
+ {carouselDiscounts.length === 0 ? (
+ <div className="carousel-slide active">
+ <div className="carousel-image gray-bg">暂无折扣活动</div>
</div>
- <div className={`carousel-slide ${currentSlide === 1 ? 'active' : ''}`}>
- <div className="carousel-image gray-bg">促销活动2</div>
+ ) : (
+ carouselDiscounts.map((discount, index) => (
+ <div key={index} className={`carousel-slide ${currentSlide === index ? 'active' : ''}`}>
+ <div className="carousel-image gray-bg">
+ <h3>{discount.type}</h3>
+ <p>{discount.name}</p>
+ <p>{new Date(discount.startTime).toLocaleDateString()} ~ {new Date(discount.endTime).toLocaleDateString()}</p>
+ </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 className="carousel-dots">
+ {carouselDiscounts.map((_, index) => (
+ <span key={index} className={`dot ${currentSlide === index ? 'active' : ''}`}></span>
+ ))}
</div>
+ </div>
+
+
{/* 公告区块区域 */}
<div className="announcement-grid">
@@ -601,9 +850,8 @@
onClick={(e) => handleAnnouncementClick(announcement, e)}
>
<h3>{announcement.title}</h3>
- <p>{announcement.excerpt}</p>
+ <p>{announcement.content.substring(0, 100)}...</p>
<div className="announcement-footer exclude-click">
- <span>{announcement.author}</span>
<span>{announcement.date}</span>
</div>
</div>
@@ -845,13 +1093,19 @@
</div>
<button
className="download-btn"
- onClick={(e) => {
- e.stopPropagation();
- // 下载逻辑
- }}
+ onClick={(e) => handleDownloadClick(torrent, e)}
>
立即下载
</button>
+ {/* 添加删除按钮 - 只有管理员或发布者可见 */}
+ {(userInfo?.isAdmin || userInfo?.name === torrent.username) && (
+ <button
+ className="delete-btn"
+ onClick={(e) => handleDeleteTorrent(torrent.id, e)}
+ >
+ 删除
+ </button>
+ )}
</div>
))}
</div>
@@ -889,11 +1143,11 @@
case 'request':
return (
<div className="content-area" data-testid="request-section">
- {/* 求种区搜索框 */}
+ {/* 求助区搜索框 */}
<div className="section-search-container">
<input
type="text"
- placeholder="搜索求种..."
+ placeholder="搜索求助..."
value={requestSearch}
onChange={(e) => setRequestSearch(e.target.value)}
className="section-search-input"
@@ -905,51 +1159,160 @@
>
搜索
</button>
+ <button
+ className="reset-button"
+ onClick={handleResetRequestSearch}
+ style={{marginLeft: '10px'}}
+ >
+ 重置
+ </button>
</div>
+
+ {/* 新增发帖按钮 */}
+ <div className="post-header">
+ <button
+ className="create-post-btn"
+ onClick={() => setShowPostModal(true)}
+ >
+ + 发帖求助
+ </button>
+ </div>
+
+ {/* 加载状态和错误提示 */}
+ {requestLoading && <div className="loading">加载中...</div>}
+ {requestError && <div className="error">{helpError}</div>}
{/* 求种区帖子列表 */}
<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 => (
+ {requestPosts.map(post => (
<div
key={post.id}
- className="request-post"
+ className={`request-post ${post.isSolved ? 'solved' : ''}`}
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>
+ <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}</span>
- <span className="post-comments">💬 {post.commentCount}</span>
+ <span className="post-likes">👍 {post.likeCount || 0}</span>
+ <span className="post-comments">💬 {post.replyCount || 0}</span>
</div>
</div>
))}
</div>
+ {/* 在帖子列表后添加分页控件 */}
+ <div className="pagination">
+ <button
+ onClick={() => fetchRequestPosts(currentPage - 1)}
+ disabled={currentPage === 1}
+ >
+ 上一页
+ </button>
+
+ {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
+ <button
+ key={page}
+ onClick={() => fetchRequestPosts(page)}
+ className={currentPage === page ? 'active' : ''}
+ >
+ {page}
+ </button>
+ ))}
+
+ <button
+ onClick={() => fetchRequestPosts(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={handleRequestPostSubmit}>
+ <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>
+
+
+
+
);
// 在Dashboard.jsx的renderContent函数中修改case 'help'部分
case 'help':
@@ -1063,7 +1426,7 @@
×
</button>
- <form onSubmit={handlePostSubmit}>
+ <form onSubmit={handleHelpPostSubmit}>
<div className="form-group">
<label>帖子标题</label>
<input
@@ -1128,6 +1491,7 @@
default:
return <div className="content-area" data-testid="default-section">公告区内容</div>;
}
+
};
if (loading) return <div className="loading">加载中...</div>;
@@ -1198,6 +1562,62 @@
{/* 内容区 */}
{renderContent()}
+ {/* 下载模态框 - 添加在这里 */}
+ {showDownloadModal && selectedTorrent && (
+ <div className="modal-overlay">
+ <div className="download-modal">
+ <h3>下载 {selectedTorrent.torrentName}</h3>
+ <button
+ className="close-btn"
+ onClick={() => !isDownloading && setShowDownloadModal(false)}
+ disabled={isDownloading}
+ >
+ ×
+ </button>
+
+ <div className="form-group">
+ <label>下载路径:</label>
+ <input
+ type="text"
+ value={downloadPath}
+ onChange={(e) => {
+ // 实时格式化显示
+ let path = e.target.value
+ .replace(/\t/g, '')
+ .replace(/\\/g, '/')
+ .replace(/\s+/g, ' ');
+ setDownloadPath(path);
+ }}
+ disabled={isDownloading}
+ placeholder="例如: D:/downloads/"
+ />
+ </div>
+
+ {isDownloading && (
+ <div className="progress-container">
+ <div className="progress-bar" style={{ width: `${downloadProgress}%` }}>
+ {downloadProgress}%
+ </div>
+ </div>
+ )}
+
+ <div className="modal-actions">
+ <button
+ onClick={() => !isDownloading && setShowDownloadModal(false)}
+ disabled={isDownloading}
+ >
+ 取消
+ </button>
+ <button
+ onClick={handleDownload}
+ disabled={isDownloading || !downloadPath}
+ >
+ {isDownloading ? '下载中...' : '开始下载'}
+ </button>
+ </div>
+ </div>
+ </div>
+ )}
</div>
);
};