blob: c3c367ec4c454e34f8a99d44c7f37012463060f8 [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',
302 setup() {
303 const route = useRoute()
304 const router = useRouter()
305
vulgar52019bf462d2025-06-07 17:54:04 +0800306 const loading = ref(true)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800307 const activeTab = ref('description')
308 const activityTab = ref('seeders')
309 const downloading = ref(false)
310 const isFavorited = ref(false)
311 const submittingComment = ref(false)
312 const newComment = ref('')
313
vulgar52019bf462d2025-06-07 17:54:04 +0800314 const torrentInfo = ref(null)
315 const torrentFiles = ref([])
316 const peerStats = ref({
317 seeders: 0,
318 leechers: 0
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800319 })
320
vulgar52019bf462d2025-06-07 17:54:04 +0800321 const seedersList = ref([])
322 const leechersList = ref([])
323 const comments = ref([])
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800324
vulgar52019bf462d2025-06-07 17:54:04 +0800325 // 解析标签
326 const parsedTags = computed(() => {
327 if (!torrentInfo.value?.tag || !Array.isArray(torrentInfo.value.tag)) {
328 return []
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800329 }
vulgar52019bf462d2025-06-07 17:54:04 +0800330
331 const tags = []
332 torrentInfo.value.tag.forEach(tagString => {
333 try {
334 const parsed = JSON.parse(tagString)
335 if (Array.isArray(parsed)) {
336 tags.push(...parsed)
337 } else {
338 tags.push(parsed)
339 }
340 } catch (e) {
341 tags.push(tagString)
342 }
343 })
344 return tags
345 })
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800346
347 onMounted(() => {
vulgar52019bf462d2025-06-07 17:54:04 +0800348 const infoHash = route.params.infoHash || route.params.id
349 if (infoHash) {
350 fetchTorrentDetail(infoHash)
351 } else {
352 ElMessage.error('缺少种子标识符')
353 router.back()
354 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800355 })
356
vulgar52019bf462d2025-06-07 17:54:04 +0800357 const fetchTorrentDetail = async (infoHash) => {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800358 try {
vulgar52019bf462d2025-06-07 17:54:04 +0800359 loading.value = true
360
361 // 获取种子详情
362 const response = await axios.get(`http://localhost:8081/api/torrent/view/${infoHash}`)
363
364 if (response.data.code === 0) {
365 torrentInfo.value = response.data
366
367 // 获取文件列表(如果有相关API)
368 await fetchTorrentFiles(infoHash)
369
370 // 获取用户活动数据(如果有相关API)
371 await fetchPeerStats(infoHash)
372
373 // 获取评论(如果有相关API)
374 await fetchComments(infoHash)
375 } else {
376 throw new Error(response.data.message || '获取种子详情失败')
377 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800378 } catch (error) {
vulgar52019bf462d2025-06-07 17:54:04 +0800379 console.error('获取种子详情失败:', error)
380 ElMessage.error(error.message || '获取种子详情失败')
381 torrentInfo.value = null
382 } finally {
383 loading.value = false
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800384 }
385 }
386
vulgar52019bf462d2025-06-07 17:54:04 +0800387 const fetchTorrentFiles = async (infoHash) => {
388 try {
389 // 这里应该调用获取文件列表的API
390 // const response = await axios.get(`http://localhost:8081/api/torrent/${infoHash}/files`)
391 // torrentFiles.value = response.data
392
393 // 临时模拟数据
394 torrentFiles.value = [
395 {
396 name: 'example_file.iso',
397 type: 'file',
398 size: formatFileSize(torrentInfo.value?.size || 0),
399 path: '/'
400 }
401 ]
402 } catch (error) {
403 console.error('获取文件列表失败:', error)
404 }
405 }
406
407 const fetchPeerStats = async (infoHash) => {
408 try {
409 // 这里应该调用获取用户活动数据的API
410 // const response = await axios.get(`http://localhost:8081/api/torrent/${infoHash}/peers`)
411 // peerStats.value = response.data
412
413 // 临时模拟数据
414 peerStats.value = {
415 seeders: Math.floor(Math.random() * 50) + 10,
416 leechers: Math.floor(Math.random() * 30) + 5
417 }
418
419 // 模拟用户列表
420 seedersList.value = [
421 { username: 'SeedMaster', uploaded: '2.5 TB', downloaded: '850 GB', ratio: '3.02', seedTime: '15天' }
422 ]
423
424 leechersList.value = [
425 { username: 'NewUser123', progress: 65, downloadSpeed: '15.2 MB/s', eta: '2小时15分' }
426 ]
427 } catch (error) {
428 console.error('获取用户活动数据失败:', error)
429 }
430 }
431
432 const fetchComments = async (infoHash) => {
433 try {
434 // 这里应该调用获取评论的API
435 // const response = await axios.get(`http://localhost:8081/api/torrent/${infoHash}/comments`)
436 // comments.value = response.data
437
438 // 临时模拟数据
439 comments.value = []
440 } catch (error) {
441 console.error('获取评论失败:', error)
442 }
443 }
444
445 const formatDateTime = (timestamp) => {
446 if (!timestamp) return '未知'
447 const date = new Date(timestamp)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800448 return date.toLocaleString('zh-CN', {
449 year: 'numeric',
450 month: '2-digit',
451 day: '2-digit',
452 hour: '2-digit',
453 minute: '2-digit'
454 })
455 }
456
vulgar52019bf462d2025-06-07 17:54:04 +0800457 const formatFileSize = (bytes) => {
458 if (!bytes || bytes === 0) return '0 B'
459
460 const k = 1024
461 const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
462 const i = Math.floor(Math.log(bytes) / Math.log(k))
463
464 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
465 }
466
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800467 const formatDescription = (description) => {
vulgar52019bf462d2025-06-07 17:54:04 +0800468 if (!description) return ''
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800469 return description.replace(/\n/g, '<br>')
470 }
471
vulgar52019bf462d2025-06-07 17:54:04 +0800472 const getCategoryType = (categorySlug) => {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800473 const types = {
vulgar52019bf462d2025-06-07 17:54:04 +0800474 'os': 'primary',
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800475 'movie': 'primary',
476 'tv': 'info',
477 'music': 'success',
478 'software': 'warning',
479 'game': 'danger'
480 }
vulgar52019bf462d2025-06-07 17:54:04 +0800481 return types[categorySlug] || 'default'
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800482 }
483
484 const handleDownload = async () => {
vulgar52019bf462d2025-06-07 17:54:04 +0800485 if (!torrentInfo.value) return
486
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800487 downloading.value = true
488 try {
vulgar52019bf462d2025-06-07 17:54:04 +0800489 // 这里应该调用下载种子文件的API
490 const response = await axios.get(
491 `http://localhost:8081/api/torrent/download/${torrentInfo.value.infoHash}`,
492 { responseType: 'blob' }
493 )
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800494
vulgar52019bf462d2025-06-07 17:54:04 +0800495 // 创建下载链接
496 const url = window.URL.createObjectURL(new Blob([response.data]))
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800497 const link = document.createElement('a')
vulgar52019bf462d2025-06-07 17:54:04 +0800498 link.href = url
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800499 link.download = `${torrentInfo.value.title}.torrent`
vulgar52019bf462d2025-06-07 17:54:04 +0800500 document.body.appendChild(link)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800501 link.click()
vulgar52019bf462d2025-06-07 17:54:04 +0800502 document.body.removeChild(link)
503 window.URL.revokeObjectURL(url)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800504
505 ElMessage.success('种子文件下载完成')
506 } catch (error) {
vulgar52019bf462d2025-06-07 17:54:04 +0800507 console.error('下载失败:', error)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800508 ElMessage.error('下载失败,请稍后重试')
509 } finally {
510 downloading.value = false
511 }
512 }
513
514 const handleFavorite = () => {
515 isFavorited.value = !isFavorited.value
516 ElMessage.success(isFavorited.value ? '已添加到收藏' : '已取消收藏')
517 }
518
519 const handleReport = async () => {
520 try {
521 await ElMessageBox.prompt('请说明举报原因', '举报内容', {
522 confirmButtonText: '提交举报',
523 cancelButtonText: '取消',
524 inputType: 'textarea',
525 inputPlaceholder: '请详细说明举报原因...'
526 })
527
528 ElMessage.success('举报已提交,我们会尽快处理')
529 } catch {
530 // 用户取消
531 }
532 }
533
534 const submitComment = async () => {
535 if (!newComment.value.trim()) {
536 ElMessage.warning('请输入评论内容')
537 return
538 }
539
540 submittingComment.value = true
541 try {
vulgar52019bf462d2025-06-07 17:54:04 +0800542 // 这里应该调用提交评论的API
543 // await axios.post(`http://localhost:8081/api/torrent/${torrentInfo.value.infoHash}/comments`, {
544 // content: newComment.value
545 // })
546
547 // 模拟提交
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800548 await new Promise(resolve => setTimeout(resolve, 1000))
549
550 const comment = {
551 id: Date.now(),
552 username: localStorage.getItem('username') || '用户',
553 content: newComment.value,
554 time: new Date().toISOString(),
555 likes: 0
556 }
557
558 comments.value.unshift(comment)
559 newComment.value = ''
560
561 ElMessage.success('评论发表成功')
562 } catch (error) {
vulgar52019bf462d2025-06-07 17:54:04 +0800563 console.error('发表评论失败:', error)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800564 ElMessage.error('发表评论失败')
565 } finally {
566 submittingComment.value = false
567 }
568 }
569
570 const likeComment = (commentId) => {
571 const comment = comments.value.find(c => c.id === commentId)
572 if (comment) {
573 comment.likes = (comment.likes || 0) + 1
574 ElMessage.success('点赞成功')
575 }
576 }
577
578 const replyComment = (commentId) => {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800579 ElMessage.info('回复功能开发中...')
580 }
581
vulgar52019bf462d2025-06-07 17:54:04 +0800582 const retry = () => {
583 const infoHash = route.params.infoHash || route.params.id
584 if (infoHash) {
585 fetchTorrentDetail(infoHash)
586 }
587 }
588
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800589 return {
vulgar52019bf462d2025-06-07 17:54:04 +0800590 loading,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800591 activeTab,
592 activityTab,
593 downloading,
594 isFavorited,
595 submittingComment,
596 newComment,
597 torrentInfo,
vulgar52019bf462d2025-06-07 17:54:04 +0800598 torrentFiles,
599 peerStats,
600 parsedTags,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800601 seedersList,
602 leechersList,
603 comments,
604 formatDateTime,
vulgar52019bf462d2025-06-07 17:54:04 +0800605 formatFileSize,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800606 formatDescription,
607 getCategoryType,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800608 handleDownload,
609 handleFavorite,
610 handleReport,
611 submitComment,
612 likeComment,
613 replyComment,
vulgar52019bf462d2025-06-07 17:54:04 +0800614 retry,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800615 ArrowLeft,
616 Download,
617 Star,
618 Flag,
619 User,
620 Clock,
621 Document,
622 Files,
623 Picture,
624 Folder,
625 Like
626 }
627 }
628}
629</script>
630
631<style lang="scss" scoped>
632.torrent-detail-page {
633 max-width: 1200px;
634 margin: 0 auto;
635 padding: 24px;
636 background: #f5f5f5;
637 min-height: 100vh;
638}
639
vulgar52019bf462d2025-06-07 17:54:04 +0800640.loading-container, .error-container {
641 background: #fff;
642 border-radius: 12px;
643 padding: 40px;
644 text-align: center;
645}
646
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800647.back-button {
648 margin-bottom: 16px;
649}
650
651.torrent-header {
652 background: #fff;
653 border-radius: 12px;
654 padding: 32px;
655 margin-bottom: 24px;
656 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
657
658 .header-content {
659 display: flex;
660 gap: 32px;
661
662 .torrent-cover {
663 flex-shrink: 0;
664
665 .cover-image {
666 width: 200px;
667 height: 280px;
668 border-radius: 8px;
669 object-fit: cover;
670 }
671
672 .image-placeholder {
673 width: 200px;
674 height: 280px;
675 background: #f5f5f5;
676 border-radius: 8px;
677 display: flex;
678 flex-direction: column;
679 align-items: center;
680 justify-content: center;
681 color: #999;
682
683 span {
684 margin-top: 8px;
685 font-size: 14px;
686 }
687 }
688 }
689
690 .torrent-info {
691 flex: 1;
692
693 .category-tag {
694 margin-bottom: 16px;
695
696 .el-tag {
697 margin-right: 8px;
698 }
699 }
700
701 .torrent-title {
702 font-size: 28px;
703 font-weight: 600;
704 color: #2c3e50;
705 margin: 0 0 16px 0;
706 line-height: 1.3;
707 }
708
709 .torrent-tags {
710 margin-bottom: 20px;
711
712 .el-tag {
713 margin: 0 8px 8px 0;
714 }
715 }
716
717 .torrent-meta {
718 margin-bottom: 20px;
719
720 .meta-item {
721 display: flex;
722 align-items: center;
723 gap: 8px;
724 margin-bottom: 8px;
725 color: #7f8c8d;
726 font-size: 14px;
727
728 .el-icon {
729 color: #909399;
730 }
731 }
732 }
733
734 .torrent-stats {
735 display: flex;
736 gap: 32px;
737 margin-bottom: 24px;
738
739 .stat-item {
740 text-align: center;
741
742 .stat-number {
743 display: block;
744 font-size: 24px;
745 font-weight: 600;
746 margin-bottom: 4px;
vulgar52019bf462d2025-06-07 17:54:04 +0800747 color: #2c3e50;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800748 }
749
750 .stat-label {
751 font-size: 14px;
752 color: #909399;
753 }
vulgar52019bf462d2025-06-07 17:54:04 +0800754
755 &.seeders .stat-number { color: #67c23a; }
756 &.leechers .stat-number { color: #f56c6c; }
757 &.downloads .stat-number { color: #409eff; }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800758 }
759 }
760
761 .action-buttons {
762 display: flex;
763 gap: 12px;
764 flex-wrap: wrap;
765 }
766 }
767 }
768}
769
770.detail-tabs {
771 background: #fff;
772 border-radius: 12px;
773 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
774
775 :deep(.el-tabs__content) {
776 padding: 24px;
777 }
778
779 .description-content {
780 line-height: 1.6;
781
782 :deep(h3) {
783 color: #2c3e50;
784 font-size: 18px;
785 font-weight: 600;
786 margin: 24px 0 12px 0;
787
788 &:first-child {
789 margin-top: 0;
790 }
791 }
792
793 :deep(p) {
794 margin-bottom: 12px;
795 color: #5a6c7d;
796 }
797
798 :deep(ul) {
799 margin: 12px 0;
800 padding-left: 20px;
801
802 li {
803 margin-bottom: 8px;
804 color: #5a6c7d;
805 }
806 }
807
808 .no-description {
809 text-align: center;
810 color: #909399;
811 padding: 40px 0;
812 }
813 }
814
815 .files-list {
816 .file-name {
817 display: flex;
818 align-items: center;
819 gap: 8px;
820
821 .el-icon {
822 color: #909399;
823 }
824 }
vulgar52019bf462d2025-06-07 17:54:04 +0800825
826 .no-files {
827 padding: 40px 0;
828 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800829 }
830
831 .activity-section {
832 .activity-stats {
833 margin-bottom: 24px;
834
835 .stats-grid {
836 display: grid;
837 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
838 gap: 16px;
839
840 .stat-card {
841 background: #f8f9fa;
842 padding: 20px;
843 border-radius: 8px;
844 text-align: center;
845
846 h3 {
847 font-size: 14px;
848 color: #909399;
849 margin: 0 0 8px 0;
850 }
851
852 .stat-number {
853 font-size: 24px;
854 font-weight: 600;
855 color: #2c3e50;
856 }
857 }
858 }
859 }
860 }
861
862 .comments-section {
863 .comment-form {
864 margin-bottom: 32px;
865
866 .comment-actions {
867 margin-top: 12px;
868 text-align: right;
869 }
870 }
871
872 .comments-list {
873 .comment-item {
874 display: flex;
875 gap: 16px;
876 margin-bottom: 24px;
877 padding-bottom: 24px;
878 border-bottom: 1px solid #f0f0f0;
879
880 &:last-child {
881 border-bottom: none;
882 margin-bottom: 0;
883 padding-bottom: 0;
884 }
885
886 .comment-content {
887 flex: 1;
888
889 .comment-header {
890 display: flex;
891 align-items: center;
892 gap: 12px;
893 margin-bottom: 8px;
894
895 .comment-username {
896 font-weight: 600;
897 color: #2c3e50;
898 }
899
900 .comment-time {
901 font-size: 12px;
902 color: #909399;
903 }
904 }
905
906 .comment-text {
907 color: #5a6c7d;
908 line-height: 1.5;
909 margin-bottom: 12px;
910 }
911
912 .comment-actions {
913 .el-button {
914 padding: 0;
915 margin-right: 16px;
916
917 .el-icon {
918 margin-right: 4px;
919 }
920 }
921 }
922 }
923 }
924
925 .no-comments {
926 text-align: center;
927 color: #909399;
928 padding: 40px 0;
929 }
930 }
931 }
932}
933
934// 响应式设计
935@media (max-width: 768px) {
936 .torrent-detail-page {
937 padding: 16px;
938 }
939
940 .torrent-header .header-content {
941 flex-direction: column;
942 text-align: center;
943
944 .torrent-cover {
945 align-self: center;
946 }
947
948 .torrent-stats {
949 justify-content: center;
950 }
951
952 .action-buttons {
953 justify-content: center;
954 }
955 }
956
957 .activity-section .stats-grid {
958 grid-template-columns: 1fr;
959 }
960
961 .comment-item {
962 flex-direction: column;
963 gap: 12px;
964 }
965}
vulgar52019bf462d2025-06-07 17:54:04 +0800966</style>