22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 1 | import React, { useEffect, useState } from 'react'; |
| 2 | import axios from 'axios'; |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 3 | import { useParams } from 'wouter'; |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 4 | import Header from '../../../components/Header'; |
| 5 | import './SeedDetail.css'; |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 6 | import { useUser } from '../../../context/UserContext'; |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 7 | import SeedRating from './SeedRating'; |
Krishya | 2283d88 | 2025-05-27 22:25:19 +0800 | [diff] [blame] | 8 | |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 9 | const SeedDetail = () => { |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 10 | const params = useParams(); |
| 11 | const seed_id = params.id; |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 12 | |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 13 | const [seed, setSeed] = useState(null); |
| 14 | const [coverImage, setCoverImage] = useState(null); |
| 15 | const [error, setError] = useState(null); |
| 16 | const [comments, setComments] = useState([]); |
| 17 | const [newComment, setNewComment] = useState(''); |
| 18 | |
| 19 | const { user } = useUser(); |
| 20 | |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 21 | const formatImageUrl = (url) => { |
| 22 | if (!url) return ''; |
| 23 | const filename = url.split('/').pop(); |
22301009 | 4158f3a | 2025-06-06 19:59:10 +0800 | [diff] [blame] | 24 | return `http://localhost:5011/uploads/torrents/${filename}`; |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 25 | }; |
| 26 | |
| 27 | useEffect(() => { |
| 28 | if (!seed_id) { |
| 29 | setError('无效的种子ID'); |
| 30 | return; |
| 31 | } |
| 32 | |
| 33 | const fetchSeedDetail = async () => { |
| 34 | try { |
| 35 | const res = await axios.post(`/seeds/info/${seed_id}`); |
| 36 | if (res.data.code === 0) { |
| 37 | const seedData = res.data.data; |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 38 | let cover = seedData.imageUrl; |
| 39 | if (!cover && seedData.imgUrl) { |
| 40 | const imgs = seedData.imgUrl |
| 41 | .split(',') |
| 42 | .map((i) => i.trim()) |
| 43 | .filter(Boolean); |
| 44 | cover = imgs.length > 0 ? formatImageUrl(imgs[0]) : null; |
| 45 | } |
| 46 | setCoverImage(cover); |
| 47 | setSeed(seedData); |
| 48 | setError(null); |
| 49 | } else { |
| 50 | setError('未能获取种子信息'); |
| 51 | } |
| 52 | } catch (err) { |
| 53 | console.error('请求种子详情出错:', err); |
| 54 | setError('获取种子详情失败'); |
| 55 | } |
| 56 | }; |
| 57 | |
| 58 | const fetchComments = async () => { |
| 59 | try { |
| 60 | const res = await axios.get(`/seeds/${seed_id}/comments`); |
| 61 | if (res.data.code === 0) { |
| 62 | setComments(res.data.data || []); |
| 63 | } else { |
| 64 | setComments([]); |
| 65 | } |
| 66 | } catch { |
| 67 | setComments([]); |
| 68 | } |
| 69 | }; |
| 70 | |
| 71 | fetchSeedDetail(); |
| 72 | fetchComments(); |
| 73 | }, [seed_id]); |
| 74 | |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 75 | const handleDownload = async (seedId) => { |
| 76 | if (!user || !user.userId) { |
| 77 | alert('请先登录再下载种子文件'); |
| 78 | return; |
| 79 | } |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 80 | |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 81 | try { |
| 82 | const response = await axios.get(`/seeds/${seedId}/download`, { |
| 83 | params: { passkey: user.userId }, |
| 84 | responseType: 'blob', |
| 85 | }); |
Krishya | c0f7e9b | 2025-04-22 15:28:28 +0800 | [diff] [blame] | 86 | |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 87 | const blob = new Blob([response.data], { type: 'application/x-bittorrent' }); |
| 88 | const downloadUrl = URL.createObjectURL(blob); |
| 89 | const a = document.createElement('a'); |
| 90 | a.href = downloadUrl; |
| 91 | a.download = `${seedId}.torrent`; |
| 92 | a.click(); |
| 93 | URL.revokeObjectURL(downloadUrl); |
| 94 | } catch (error) { |
| 95 | console.error('下载失败:', error); |
| 96 | alert('下载失败,请稍后再试。'); |
| 97 | } |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 98 | }; |
Krishya | c0f7e9b | 2025-04-22 15:28:28 +0800 | [diff] [blame] | 99 | |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 100 | const handleCollect = async () => { |
| 101 | if (!user || !user.userId) { |
| 102 | alert('请先登录再收藏'); |
| 103 | return; |
| 104 | } |
| 105 | |
| 106 | try { |
| 107 | const res = await axios.post(`/seeds/${seed.id}/favorite-toggle`, null, { |
| 108 | params: { user_id: user.userId }, |
| 109 | }); |
| 110 | |
| 111 | if (res.data.code === 0) { |
| 112 | alert('操作成功'); |
| 113 | } else { |
| 114 | alert(res.data.msg || '操作失败'); |
| 115 | } |
| 116 | } catch (err) { |
| 117 | console.error('收藏失败:', err); |
| 118 | alert('收藏失败,请稍后再试。'); |
| 119 | } |
| 120 | }; |
| 121 | |
| 122 | const handleAddComment = async () => { |
| 123 | if (!user || !user.userId) { |
| 124 | alert('请登录后发表评论'); |
| 125 | return; |
| 126 | } |
| 127 | |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 128 | if (newComment.trim()) { |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 129 | try { |
| 130 | const res = await axios.post(`/seeds/${seed_id}/comments`, { |
| 131 | user_id: user.userId, |
| 132 | content: newComment, |
| 133 | }); |
| 134 | |
| 135 | if (res.data.code === 0) { |
| 136 | setComments([...comments, { content: newComment, user: user.username || '匿名用户' }]); |
| 137 | setNewComment(''); |
| 138 | } else { |
| 139 | alert(res.data.msg || '评论失败'); |
| 140 | } |
| 141 | } catch (err) { |
| 142 | console.error('评论提交失败:', err); |
| 143 | alert('评论失败,请稍后重试'); |
| 144 | } |
Krishya | c0f7e9b | 2025-04-22 15:28:28 +0800 | [diff] [blame] | 145 | } |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 146 | }; |
Krishya | c0f7e9b | 2025-04-22 15:28:28 +0800 | [diff] [blame] | 147 | |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 148 | if (error) { |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 149 | return ( |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 150 | <div className="seed-detail-page"> |
| 151 | <Header /> |
| 152 | <div className="seed-detail"> |
| 153 | <p className="error-text">{error}</p> |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 154 | </div> |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 155 | </div> |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 156 | ); |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | if (!seed) { |
| 160 | return ( |
| 161 | <div className="seed-detail-page"> |
| 162 | <Header /> |
| 163 | <div className="seed-detail"> |
| 164 | <p>加载中...</p> |
| 165 | </div> |
| 166 | </div> |
| 167 | ); |
| 168 | } |
| 169 | |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 170 | const tags = Array.isArray(seed.tags) |
| 171 | ? seed.tags |
| 172 | : typeof seed.tags === 'string' |
| 173 | ? seed.tags.split(',').map((t) => t.trim()) |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 174 | : []; |
| 175 | |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 176 | const actors = Array.isArray(seed.actors) |
| 177 | ? seed.actors |
| 178 | : typeof seed.actors === 'string' |
| 179 | ? seed.actors.split(',').map((a) => a.trim()) |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 180 | : []; |
| 181 | |
| 182 | return ( |
| 183 | <div className="seed-detail-page"> |
| 184 | <Header /> |
| 185 | <div className="seed-detail"> |
| 186 | <h1>{seed.title}</h1> |
| 187 | <div className="seed-header-container"> |
| 188 | <div className="seed-info"> |
| 189 | <div className="seed-basic-info"> |
| 190 | <p><strong>分类:</strong>{seed.category || '未知'}</p> |
| 191 | <p><strong>发布时间:</strong>{seed.upload_time ? new Date(seed.upload_time).toLocaleString() : '未知'}</p> |
| 192 | <p><strong>标签:</strong>{tags.join(' / ')}</p> |
| 193 | <p><strong>简介:</strong>{seed.description || '无'}</p> |
| 194 | <p><strong>大小:</strong>{seed.size || '未知'}</p> |
| 195 | <p><strong>分辨率:</strong>{seed.resolution || '未知'}</p> |
| 196 | <p><strong>片长:</strong>{seed.duration || '未知'}</p> |
| 197 | <p><strong>地区:</strong>{seed.region || '未知'}</p> |
| 198 | <p><strong>下载次数:</strong>{seed.downloads ?? 0}</p> |
| 199 | </div> |
| 200 | {(seed.category === '电影' || seed.category === '电视剧') && ( |
| 201 | <div className="seed-media-info"> |
| 202 | <p><strong>导演:</strong>{seed.director || '未知'}</p> |
| 203 | <p><strong>编剧:</strong>{seed.writer || '未知'}</p> |
| 204 | <p><strong>主演:</strong>{actors.join(' / ')}</p> |
| 205 | </div> |
| 206 | )} |
| 207 | </div> |
| 208 | <img |
| 209 | src={coverImage || '/default-cover.png'} |
| 210 | alt={seed.title} |
| 211 | className="cover-image" |
| 212 | /> |
| 213 | </div> |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 214 | |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 215 | <div className="action-buttons"> |
| 216 | <button className="btn" onClick={() => handleDownload(seed.id)}>下载</button> |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 217 | <button className="btn-outline" onClick={handleCollect}>收藏</button> |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 218 | {/* <button className="btn" onClick={handleCollect}>收藏</button> */} |
| 219 | <SeedRating seedId={seed.id} /> |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 220 | </div> |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 221 | |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 222 | <hr className="divider" /> |
| 223 | <h3>评论区</h3> |
| 224 | <div className="comments-section"> |
| 225 | <div className="comments-list"> |
22301009 | 4952a0f | 2025-06-07 18:58:16 +0800 | [diff] [blame] | 226 | {comments.length === 0 ? ( |
| 227 | <p className="no-comments">暂无评论</p> |
| 228 | ) : ( |
| 229 | comments.map((comment, index) => ( |
| 230 | <div key={index} className="comment"> |
| 231 | <p className="comment-user">{comment.user}</p> |
| 232 | <p className="comment-content">{comment.content}</p> |
| 233 | </div> |
| 234 | )) |
| 235 | )} |
22301009 | 80aaf0d | 2025-06-05 23:20:05 +0800 | [diff] [blame] | 236 | </div> |
| 237 | <div className="add-comment-form"> |
| 238 | <textarea |
| 239 | placeholder="输入你的评论..." |
| 240 | value={newComment} |
| 241 | onChange={(e) => setNewComment(e.target.value)} |
| 242 | /> |
| 243 | <div className="comment-options"> |
| 244 | <button className="btn" onClick={handleAddComment}>发布评论</button> |
| 245 | </div> |
| 246 | </div> |
| 247 | </div> |
| 248 | </div> |
| 249 | </div> |
| 250 | ); |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 251 | }; |
| 252 | |
| 253 | export default SeedDetail; |