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