完成上传下载连接,公告管理与详情页面,求种区页面,轮播图折扣显示,修改部分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>

     );

 };