blob: a79a5285341b1c4d8592b3965e636ef053f8d895 [file] [log] [blame]
Xing Jinwenff16b1e2025-06-05 00:29:26 +08001<template>
2 <div class="torrent-detail-page">
3 <div class="page-container">
vulgar52019bf462d2025-06-07 17:54:04 +08004 <!-- 加载状态 -->
5 <div v-if="loading" class="loading-container">
6 <el-skeleton :rows="8" animated />
Xing Jinwenff16b1e2025-06-05 00:29:26 +08007 </div>
8
vulgar52019bf462d2025-06-07 17:54:04 +08009 <!-- 详情内容 -->
10 <div v-else-if="torrentInfo">
11 <!-- 返回按钮 -->
12 <div class="back-button">
13 <el-button :icon="ArrowLeft" @click="$router.back()">
14 返回列表
15 </el-button>
16 </div>
17
18 <!-- 种子基本信息 -->
19 <div class="torrent-header">
20 <div class="header-content">
21 <div class="torrent-cover">
22 <el-image
23 :src="torrentInfo.coverImage || '/default-cover.jpg'"
24 :alt="torrentInfo.title"
25 fit="cover"
26 class="cover-image"
27 >
28 <template #error>
29 <div class="image-placeholder">
30 <el-icon size="48"><Picture /></el-icon>
31 <span>暂无封面</span>
32 </div>
33 </template>
34 </el-image>
35 </div>
36
37 <div class="torrent-info">
38 <div class="category-tag">
39 <el-tag :type="getCategoryType(torrentInfo.category?.slug)" size="large">
40 {{ torrentInfo.category?.name || '未分类' }}
41 </el-tag>
42 <el-tag v-if="torrentInfo.subTitle" type="info" size="small">
43 {{ torrentInfo.subTitle }}
44 </el-tag>
45 </div>
46
47 <h1 class="torrent-title">{{ torrentInfo.title }}</h1>
48
49 <div class="torrent-tags" v-if="torrentInfo.tag && torrentInfo.tag.length > 0">
50 <el-tag
51 v-for="(tag, index) in parsedTags"
52 :key="index"
53 size="small"
54 effect="plain"
55 >
56 {{ tag }}
57 </el-tag>
58 </div>
59
60 <div class="torrent-meta">
61 <div class="meta-item">
62 <el-icon><User /></el-icon>
63 <span>上传者:{{ torrentInfo.user?.username || '未知' }}</span>
Xing Jinwenff16b1e2025-06-05 00:29:26 +080064 </div>
vulgar52019bf462d2025-06-07 17:54:04 +080065 <div class="meta-item">
66 <el-icon><Clock /></el-icon>
67 <span>上传时间:{{ formatDateTime(torrentInfo.createdAt) }}</span>
68 </div>
69 <div class="meta-item">
70 <el-icon><Document /></el-icon>
71 <span>文件大小:{{ formatFileSize(torrentInfo.size) }}</span>
72 </div>
73 <div class="meta-item">
74 <el-icon><Files /></el-icon>
75 <span>完成次数:{{ torrentInfo.finishes }} 次</span>
76 </div>
77 <div class="meta-item">
78 <el-icon><Star /></el-icon>
79 <span>推广策略:{{ torrentInfo.promotionPolicy?.displayName || '默认' }}</span>
80 </div>
Xing Jinwenff16b1e2025-06-05 00:29:26 +080081 </div>
vulgar52019bf462d2025-06-07 17:54:04 +080082
83 <div class="torrent-stats">
84 <div class="stat-item seeders">
85 <span class="stat-number">{{ peerStats.seeders }}</span>
86 <span class="stat-label">做种</span>
87 </div>
88 <div class="stat-item leechers">
89 <span class="stat-number">{{ peerStats.leechers }}</span>
90 <span class="stat-label">下载</span>
91 </div>
92 <div class="stat-item downloads">
93 <span class="stat-number">{{ torrentInfo.finishes }}</span>
94 <span class="stat-label">完成</span>
95 </div>
Xing Jinwenff16b1e2025-06-05 00:29:26 +080096 </div>
vulgar52019bf462d2025-06-07 17:54:04 +080097
98 <div class="action-buttons">
99 <el-button
100 type="primary"
101 size="large"
102 :icon="Download"
103 @click="handleDownload"
104 :loading="downloading"
105 >
106 {{ downloading ? '准备中...' : '下载种子' }}
107 </el-button>
108 <el-button
109 type="success"
110 size="large"
111 :icon="Star"
112 @click="handleFavorite"
113 >
114 {{ isFavorited ? '已收藏' : '收藏' }}
115 </el-button>
116 <el-button
117 type="warning"
118 size="large"
119 :icon="Flag"
120 @click="handleReport"
121 >
122 举报
123 </el-button>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800124 </div>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800125 </div>
126 </div>
127 </div>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800128
vulgar52019bf462d2025-06-07 17:54:04 +0800129 <!-- 详细信息选项卡 -->
130 <div class="detail-tabs">
131 <el-tabs v-model="activeTab" type="border-card">
132 <!-- 种子描述 -->
133 <el-tab-pane label="详细描述" name="description">
134 <div class="description-content">
135 <div v-if="torrentInfo.description" v-html="formatDescription(torrentInfo.description)"></div>
136 <div v-else class="no-description">暂无详细描述</div>
137 </div>
138 </el-tab-pane>
139
140 <!-- 文件列表 -->
141 <el-tab-pane label="文件列表" name="files" lazy>
142 <div class="files-list">
143 <div v-if="torrentFiles.length > 0">
144 <el-table :data="torrentFiles" stripe>
145 <el-table-column label="文件名" prop="name" min-width="400">
146 <template #default="{ row }">
147 <div class="file-name">
148 <el-icon v-if="row.type === 'folder'"><Folder /></el-icon>
149 <el-icon v-else><Document /></el-icon>
150 <span>{{ row.name }}</span>
151 </div>
152 </template>
153 </el-table-column>
154 <el-table-column label="大小" prop="size" width="120" align="right" />
155 <el-table-column label="路径" prop="path" min-width="300" />
156 </el-table>
157 </div>
158 <div v-else class="no-files">
159 <el-empty description="文件列表加载中..." />
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800160 </div>
161 </div>
vulgar52019bf462d2025-06-07 17:54:04 +0800162 </el-tab-pane>
163
164 <!-- 用户活动 -->
165 <el-tab-pane label="用户活动" name="activity">
166 <div class="activity-section">
167 <div class="activity-stats">
168 <div class="stats-grid">
169 <div class="stat-card">
170 <h3>做种用户</h3>
171 <p class="stat-number">{{ peerStats.seeders }}</p>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800172 </div>
vulgar52019bf462d2025-06-07 17:54:04 +0800173 <div class="stat-card">
174 <h3>下载用户</h3>
175 <p class="stat-number">{{ peerStats.leechers }}</p>
176 </div>
177 <div class="stat-card">
178 <h3>完成用户</h3>
179 <p class="stat-number">{{ torrentInfo.finishes }}</p>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800180 </div>
181 </div>
182 </div>
183
vulgar52019bf462d2025-06-07 17:54:04 +0800184 <div class="user-lists">
185 <el-tabs v-model="activityTab" type="card">
186 <el-tab-pane label="做种用户" name="seeders">
187 <el-table :data="seedersList" max-height="400">
188 <el-table-column label="用户" prop="username" />
189 <el-table-column label="上传量" prop="uploaded" />
190 <el-table-column label="下载量" prop="downloaded" />
191 <el-table-column label="分享率" prop="ratio" />
192 <el-table-column label="做种时间" prop="seedTime" />
193 </el-table>
194 </el-tab-pane>
195
196 <el-tab-pane label="下载用户" name="leechers">
197 <el-table :data="leechersList" max-height="400">
198 <el-table-column label="用户" prop="username" />
199 <el-table-column label="进度" prop="progress">
200 <template #default="{ row }">
201 <el-progress :percentage="row.progress" :stroke-width="6" />
202 </template>
203 </el-table-column>
204 <el-table-column label="下载速度" prop="downloadSpeed" />
205 <el-table-column label="剩余时间" prop="eta" />
206 </el-table>
207 </el-tab-pane>
208 </el-tabs>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800209 </div>
210 </div>
vulgar52019bf462d2025-06-07 17:54:04 +0800211 </el-tab-pane>
212
213 <!-- 评论区 -->
214 <el-tab-pane label="评论" name="comments">
215 <div class="comments-section">
216 <!-- 发表评论 -->
217 <div class="comment-form">
218 <el-input
219 v-model="newComment"
220 type="textarea"
221 :rows="4"
222 placeholder="发表你的评论..."
223 maxlength="500"
224 show-word-limit
225 />
226 <div class="comment-actions">
227 <el-button type="primary" @click="submitComment" :loading="submittingComment">
228 发表评论
229 </el-button>
230 </div>
231 </div>
232
233 <!-- 评论列表 -->
234 <div class="comments-list">
235 <div
236 v-for="comment in comments"
237 :key="comment.id"
238 class="comment-item"
239 >
240 <div class="comment-avatar">
241 <el-avatar :size="40">{{ comment.username.charAt(0) }}</el-avatar>
242 </div>
243 <div class="comment-content">
244 <div class="comment-header">
245 <span class="comment-username">{{ comment.username }}</span>
246 <span class="comment-time">{{ formatDateTime(comment.time) }}</span>
247 </div>
248 <div class="comment-text">{{ comment.content }}</div>
249 <div class="comment-actions">
250 <el-button type="text" size="small" @click="likeComment(comment.id)">
251 <el-icon><Like /></el-icon>
252 {{ comment.likes || 0 }}
253 </el-button>
254 <el-button type="text" size="small" @click="replyComment(comment.id)">
255 回复
256 </el-button>
257 </div>
258 </div>
259 </div>
260
261 <div v-if="comments.length === 0" class="no-comments">
262 暂无评论,快来发表第一条评论吧!
263 </div>
264 </div>
265 </div>
266 </el-tab-pane>
267 </el-tabs>
268 </div>
269 </div>
270
271 <!-- 错误状态 -->
272 <div v-else class="error-container">
273 <el-empty description="种子信息加载失败">
274 <el-button type="primary" @click="retry">重试</el-button>
275 </el-empty>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800276 </div>
277 </div>
278 </div>
279</template>
280
281<script>
vulgar52019bf462d2025-06-07 17:54:04 +0800282import { ref, onMounted, computed } from 'vue'
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800283import { useRoute, useRouter } from 'vue-router'
284import { ElMessage, ElMessageBox } from 'element-plus'
285import {
286 ArrowLeft,
287 Download,
288 Star,
289 Flag,
290 User,
291 Clock,
292 Document,
293 Files,
294 Picture,
295 Folder,
296 Like
297} from '@element-plus/icons-vue'
vulgar52019bf462d2025-06-07 17:54:04 +0800298import axios from 'axios'
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800299
300export default {
301 name: 'TorrentDetailView',
vulgar52014958b252025-06-08 03:26:43 +0800302 components: {
303 ArrowLeft,
304 Download,
305 Star,
306 Flag,
307 User,
308 Clock,
309 Document,
310 Files,
311 Picture,
312 Folder,
313 Like
314 },
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800315 setup() {
316 const route = useRoute()
317 const router = useRouter()
318
vulgar52019bf462d2025-06-07 17:54:04 +0800319 const loading = ref(true)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800320 const activeTab = ref('description')
321 const activityTab = ref('seeders')
322 const downloading = ref(false)
323 const isFavorited = ref(false)
324 const submittingComment = ref(false)
325 const newComment = ref('')
326
vulgar52019bf462d2025-06-07 17:54:04 +0800327 const torrentInfo = ref(null)
328 const torrentFiles = ref([])
329 const peerStats = ref({
330 seeders: 0,
331 leechers: 0
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800332 })
333
vulgar52019bf462d2025-06-07 17:54:04 +0800334 const seedersList = ref([])
335 const leechersList = ref([])
336 const comments = ref([])
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800337
vulgar52019bf462d2025-06-07 17:54:04 +0800338 // 解析标签
339 const parsedTags = computed(() => {
340 if (!torrentInfo.value?.tag || !Array.isArray(torrentInfo.value.tag)) {
341 return []
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800342 }
vulgar52019bf462d2025-06-07 17:54:04 +0800343
344 const tags = []
345 torrentInfo.value.tag.forEach(tagString => {
346 try {
347 const parsed = JSON.parse(tagString)
348 if (Array.isArray(parsed)) {
349 tags.push(...parsed)
350 } else {
351 tags.push(parsed)
352 }
353 } catch (e) {
354 tags.push(tagString)
355 }
356 })
357 return tags
358 })
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800359
360 onMounted(() => {
vulgar52014958b252025-06-08 03:26:43 +0800361 const infoHash = route.params.infoHash
vulgar52019bf462d2025-06-07 17:54:04 +0800362 if (infoHash) {
363 fetchTorrentDetail(infoHash)
364 } else {
365 ElMessage.error('缺少种子标识符')
366 router.back()
367 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800368 })
369
vulgar52019bf462d2025-06-07 17:54:04 +0800370 const fetchTorrentDetail = async (infoHash) => {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800371 try {
vulgar52019bf462d2025-06-07 17:54:04 +0800372 loading.value = true
373
374 // 获取种子详情
vulgar52014958b252025-06-08 03:26:43 +0800375 const response = await axios.get(`/api/torrent/view/${infoHash}`)
vulgar52019bf462d2025-06-07 17:54:04 +0800376
377 if (response.data.code === 0) {
378 torrentInfo.value = response.data
379
380 // 获取文件列表(如果有相关API)
381 await fetchTorrentFiles(infoHash)
382
383 // 获取用户活动数据(如果有相关API)
384 await fetchPeerStats(infoHash)
385
386 // 获取评论(如果有相关API)
387 await fetchComments(infoHash)
388 } else {
389 throw new Error(response.data.message || '获取种子详情失败')
390 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800391 } catch (error) {
vulgar52019bf462d2025-06-07 17:54:04 +0800392 console.error('获取种子详情失败:', error)
393 ElMessage.error(error.message || '获取种子详情失败')
394 torrentInfo.value = null
395 } finally {
396 loading.value = false
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800397 }
398 }
399
vulgar52019bf462d2025-06-07 17:54:04 +0800400 const fetchTorrentFiles = async (infoHash) => {
401 try {
402 // 这里应该调用获取文件列表的API
403 // const response = await axios.get(`http://localhost:8081/api/torrent/${infoHash}/files`)
404 // torrentFiles.value = response.data
405
406 // 临时模拟数据
407 torrentFiles.value = [
408 {
409 name: 'example_file.iso',
410 type: 'file',
411 size: formatFileSize(torrentInfo.value?.size || 0),
412 path: '/'
413 }
414 ]
415 } catch (error) {
416 console.error('获取文件列表失败:', error)
417 }
418 }
419
420 const fetchPeerStats = async (infoHash) => {
421 try {
422 // 这里应该调用获取用户活动数据的API
423 // const response = await axios.get(`http://localhost:8081/api/torrent/${infoHash}/peers`)
424 // peerStats.value = response.data
425
426 // 临时模拟数据
427 peerStats.value = {
428 seeders: Math.floor(Math.random() * 50) + 10,
429 leechers: Math.floor(Math.random() * 30) + 5
430 }
431
432 // 模拟用户列表
433 seedersList.value = [
434 { username: 'SeedMaster', uploaded: '2.5 TB', downloaded: '850 GB', ratio: '3.02', seedTime: '15天' }
435 ]
436
437 leechersList.value = [
438 { username: 'NewUser123', progress: 65, downloadSpeed: '15.2 MB/s', eta: '2小时15分' }
439 ]
440 } catch (error) {
441 console.error('获取用户活动数据失败:', error)
442 }
443 }
444
445 const fetchComments = async (infoHash) => {
446 try {
447 // 这里应该调用获取评论的API
448 // const response = await axios.get(`http://localhost:8081/api/torrent/${infoHash}/comments`)
449 // comments.value = response.data
450
451 // 临时模拟数据
452 comments.value = []
453 } catch (error) {
454 console.error('获取评论失败:', error)
455 }
456 }
457
458 const formatDateTime = (timestamp) => {
459 if (!timestamp) return '未知'
460 const date = new Date(timestamp)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800461 return date.toLocaleString('zh-CN', {
462 year: 'numeric',
463 month: '2-digit',
464 day: '2-digit',
465 hour: '2-digit',
466 minute: '2-digit'
467 })
468 }
469
vulgar52019bf462d2025-06-07 17:54:04 +0800470 const formatFileSize = (bytes) => {
471 if (!bytes || bytes === 0) return '0 B'
472
473 const k = 1024
474 const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
475 const i = Math.floor(Math.log(bytes) / Math.log(k))
476
477 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
478 }
479
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800480 const formatDescription = (description) => {
vulgar52019bf462d2025-06-07 17:54:04 +0800481 if (!description) return ''
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800482 return description.replace(/\n/g, '<br>')
483 }
484
vulgar52019bf462d2025-06-07 17:54:04 +0800485 const getCategoryType = (categorySlug) => {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800486 const types = {
vulgar52019bf462d2025-06-07 17:54:04 +0800487 'os': 'primary',
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800488 'movie': 'primary',
489 'tv': 'info',
490 'music': 'success',
491 'software': 'warning',
492 'game': 'danger'
493 }
vulgar52019bf462d2025-06-07 17:54:04 +0800494 return types[categorySlug] || 'default'
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800495 }
496
497 const handleDownload = async () => {
vulgar52014958b252025-06-08 03:26:43 +0800498 if (!torrentInfo.value?.infoHash) return
vulgar52019bf462d2025-06-07 17:54:04 +0800499
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800500 downloading.value = true
501 try {
vulgar52014958b252025-06-08 03:26:43 +0800502 // 调用下载种子文件的API
vulgar52019bf462d2025-06-07 17:54:04 +0800503 const response = await axios.get(
vulgar52014958b252025-06-08 03:26:43 +0800504 `/api/torrent/download/${torrentInfo.value.infoHash}`,
vulgar52019bf462d2025-06-07 17:54:04 +0800505 { responseType: 'blob' }
506 )
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800507
vulgar52019bf462d2025-06-07 17:54:04 +0800508 // 创建下载链接
509 const url = window.URL.createObjectURL(new Blob([response.data]))
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800510 const link = document.createElement('a')
vulgar52019bf462d2025-06-07 17:54:04 +0800511 link.href = url
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800512 link.download = `${torrentInfo.value.title}.torrent`
vulgar52019bf462d2025-06-07 17:54:04 +0800513 document.body.appendChild(link)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800514 link.click()
vulgar52019bf462d2025-06-07 17:54:04 +0800515 document.body.removeChild(link)
516 window.URL.revokeObjectURL(url)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800517
518 ElMessage.success('种子文件下载完成')
519 } catch (error) {
vulgar52019bf462d2025-06-07 17:54:04 +0800520 console.error('下载失败:', error)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800521 ElMessage.error('下载失败,请稍后重试')
522 } finally {
523 downloading.value = false
524 }
525 }
526
527 const handleFavorite = () => {
528 isFavorited.value = !isFavorited.value
529 ElMessage.success(isFavorited.value ? '已添加到收藏' : '已取消收藏')
530 }
531
532 const handleReport = async () => {
533 try {
534 await ElMessageBox.prompt('请说明举报原因', '举报内容', {
535 confirmButtonText: '提交举报',
536 cancelButtonText: '取消',
537 inputType: 'textarea',
538 inputPlaceholder: '请详细说明举报原因...'
539 })
540
541 ElMessage.success('举报已提交,我们会尽快处理')
542 } catch {
543 // 用户取消
544 }
545 }
546
547 const submitComment = async () => {
548 if (!newComment.value.trim()) {
549 ElMessage.warning('请输入评论内容')
550 return
551 }
552
553 submittingComment.value = true
554 try {
vulgar52019bf462d2025-06-07 17:54:04 +0800555 // 这里应该调用提交评论的API
556 // await axios.post(`http://localhost:8081/api/torrent/${torrentInfo.value.infoHash}/comments`, {
557 // content: newComment.value
558 // })
559
560 // 模拟提交
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800561 await new Promise(resolve => setTimeout(resolve, 1000))
562
563 const comment = {
564 id: Date.now(),
565 username: localStorage.getItem('username') || '用户',
566 content: newComment.value,
567 time: new Date().toISOString(),
568 likes: 0
569 }
570
571 comments.value.unshift(comment)
572 newComment.value = ''
573
574 ElMessage.success('评论发表成功')
575 } catch (error) {
vulgar52019bf462d2025-06-07 17:54:04 +0800576 console.error('发表评论失败:', error)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800577 ElMessage.error('发表评论失败')
578 } finally {
579 submittingComment.value = false
580 }
581 }
582
583 const likeComment = (commentId) => {
584 const comment = comments.value.find(c => c.id === commentId)
585 if (comment) {
586 comment.likes = (comment.likes || 0) + 1
587 ElMessage.success('点赞成功')
588 }
589 }
590
591 const replyComment = (commentId) => {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800592 ElMessage.info('回复功能开发中...')
593 }
594
vulgar52019bf462d2025-06-07 17:54:04 +0800595 const retry = () => {
vulgar52014958b252025-06-08 03:26:43 +0800596 const infoHash = route.params.infoHash
vulgar52019bf462d2025-06-07 17:54:04 +0800597 if (infoHash) {
598 fetchTorrentDetail(infoHash)
599 }
600 }
601
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800602 return {
vulgar52019bf462d2025-06-07 17:54:04 +0800603 loading,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800604 activeTab,
605 activityTab,
606 downloading,
607 isFavorited,
608 submittingComment,
609 newComment,
610 torrentInfo,
vulgar52019bf462d2025-06-07 17:54:04 +0800611 torrentFiles,
612 peerStats,
613 parsedTags,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800614 seedersList,
615 leechersList,
616 comments,
617 formatDateTime,
vulgar52019bf462d2025-06-07 17:54:04 +0800618 formatFileSize,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800619 formatDescription,
620 getCategoryType,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800621 handleDownload,
622 handleFavorite,
623 handleReport,
624 submitComment,
625 likeComment,
626 replyComment,
vulgar52019bf462d2025-06-07 17:54:04 +0800627 retry,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800628 ArrowLeft,
629 Download,
630 Star,
631 Flag,
632 User,
633 Clock,
634 Document,
635 Files,
636 Picture,
637 Folder,
638 Like
639 }
640 }
641}
642</script>
643
644<style lang="scss" scoped>
645.torrent-detail-page {
646 max-width: 1200px;
647 margin: 0 auto;
648 padding: 24px;
649 background: #f5f5f5;
650 min-height: 100vh;
651}
652
vulgar52019bf462d2025-06-07 17:54:04 +0800653.loading-container, .error-container {
654 background: #fff;
655 border-radius: 12px;
656 padding: 40px;
657 text-align: center;
658}
659
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800660.back-button {
661 margin-bottom: 16px;
662}
663
664.torrent-header {
665 background: #fff;
666 border-radius: 12px;
667 padding: 32px;
668 margin-bottom: 24px;
669 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
670
671 .header-content {
672 display: flex;
673 gap: 32px;
674
675 .torrent-cover {
676 flex-shrink: 0;
677
678 .cover-image {
679 width: 200px;
680 height: 280px;
681 border-radius: 8px;
682 object-fit: cover;
683 }
684
685 .image-placeholder {
686 width: 200px;
687 height: 280px;
688 background: #f5f5f5;
689 border-radius: 8px;
690 display: flex;
691 flex-direction: column;
692 align-items: center;
693 justify-content: center;
694 color: #999;
695
696 span {
697 margin-top: 8px;
698 font-size: 14px;
699 }
700 }
701 }
702
703 .torrent-info {
704 flex: 1;
705
706 .category-tag {
707 margin-bottom: 16px;
708
709 .el-tag {
710 margin-right: 8px;
711 }
712 }
713
714 .torrent-title {
715 font-size: 28px;
716 font-weight: 600;
717 color: #2c3e50;
718 margin: 0 0 16px 0;
719 line-height: 1.3;
720 }
721
722 .torrent-tags {
723 margin-bottom: 20px;
724
725 .el-tag {
726 margin: 0 8px 8px 0;
727 }
728 }
729
730 .torrent-meta {
731 margin-bottom: 20px;
732
733 .meta-item {
734 display: flex;
735 align-items: center;
736 gap: 8px;
737 margin-bottom: 8px;
738 color: #7f8c8d;
739 font-size: 14px;
740
741 .el-icon {
742 color: #909399;
743 }
744 }
745 }
746
747 .torrent-stats {
748 display: flex;
749 gap: 32px;
750 margin-bottom: 24px;
751
752 .stat-item {
753 text-align: center;
754
755 .stat-number {
756 display: block;
757 font-size: 24px;
758 font-weight: 600;
759 margin-bottom: 4px;
vulgar52019bf462d2025-06-07 17:54:04 +0800760 color: #2c3e50;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800761 }
762
763 .stat-label {
764 font-size: 14px;
765 color: #909399;
766 }
vulgar52019bf462d2025-06-07 17:54:04 +0800767
768 &.seeders .stat-number { color: #67c23a; }
769 &.leechers .stat-number { color: #f56c6c; }
770 &.downloads .stat-number { color: #409eff; }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800771 }
772 }
773
774 .action-buttons {
775 display: flex;
776 gap: 12px;
777 flex-wrap: wrap;
778 }
779 }
780 }
781}
782
783.detail-tabs {
784 background: #fff;
785 border-radius: 12px;
786 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
787
788 :deep(.el-tabs__content) {
789 padding: 24px;
790 }
791
792 .description-content {
793 line-height: 1.6;
794
795 :deep(h3) {
796 color: #2c3e50;
797 font-size: 18px;
798 font-weight: 600;
799 margin: 24px 0 12px 0;
800
801 &:first-child {
802 margin-top: 0;
803 }
804 }
805
806 :deep(p) {
807 margin-bottom: 12px;
808 color: #5a6c7d;
809 }
810
811 :deep(ul) {
812 margin: 12px 0;
813 padding-left: 20px;
814
815 li {
816 margin-bottom: 8px;
817 color: #5a6c7d;
818 }
819 }
820
821 .no-description {
822 text-align: center;
823 color: #909399;
824 padding: 40px 0;
825 }
826 }
827
828 .files-list {
829 .file-name {
830 display: flex;
831 align-items: center;
832 gap: 8px;
833
834 .el-icon {
835 color: #909399;
836 }
837 }
vulgar52019bf462d2025-06-07 17:54:04 +0800838
839 .no-files {
840 padding: 40px 0;
841 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800842 }
843
844 .activity-section {
845 .activity-stats {
846 margin-bottom: 24px;
847
848 .stats-grid {
849 display: grid;
850 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
851 gap: 16px;
852
853 .stat-card {
854 background: #f8f9fa;
855 padding: 20px;
856 border-radius: 8px;
857 text-align: center;
858
859 h3 {
860 font-size: 14px;
861 color: #909399;
862 margin: 0 0 8px 0;
863 }
864
865 .stat-number {
866 font-size: 24px;
867 font-weight: 600;
868 color: #2c3e50;
869 }
870 }
871 }
872 }
873 }
874
875 .comments-section {
876 .comment-form {
877 margin-bottom: 32px;
878
879 .comment-actions {
880 margin-top: 12px;
881 text-align: right;
882 }
883 }
884
885 .comments-list {
886 .comment-item {
887 display: flex;
888 gap: 16px;
889 margin-bottom: 24px;
890 padding-bottom: 24px;
891 border-bottom: 1px solid #f0f0f0;
892
893 &:last-child {
894 border-bottom: none;
895 margin-bottom: 0;
896 padding-bottom: 0;
897 }
898
899 .comment-content {
900 flex: 1;
901
902 .comment-header {
903 display: flex;
904 align-items: center;
905 gap: 12px;
906 margin-bottom: 8px;
907
908 .comment-username {
909 font-weight: 600;
910 color: #2c3e50;
911 }
912
913 .comment-time {
914 font-size: 12px;
915 color: #909399;
916 }
917 }
918
919 .comment-text {
920 color: #5a6c7d;
921 line-height: 1.5;
922 margin-bottom: 12px;
923 }
924
925 .comment-actions {
926 .el-button {
927 padding: 0;
928 margin-right: 16px;
929
930 .el-icon {
931 margin-right: 4px;
932 }
933 }
934 }
935 }
936 }
937
938 .no-comments {
939 text-align: center;
940 color: #909399;
941 padding: 40px 0;
942 }
943 }
944 }
945}
946
947// 响应式设计
948@media (max-width: 768px) {
949 .torrent-detail-page {
950 padding: 16px;
951 }
952
953 .torrent-header .header-content {
954 flex-direction: column;
955 text-align: center;
956
957 .torrent-cover {
958 align-self: center;
959 }
960
961 .torrent-stats {
962 justify-content: center;
963 }
964
965 .action-buttons {
966 justify-content: center;
967 }
968 }
969
970 .activity-section .stats-grid {
971 grid-template-columns: 1fr;
972 }
973
974 .comment-item {
975 flex-direction: column;
976 gap: 12px;
977 }
978}
vulgar52019bf462d2025-06-07 17:54:04 +0800979</style>