blob: 1780d68c5a188cf8bd31b27c7c5908f150be4a8c [file] [log] [blame]
vulgar52019469e5c2025-06-09 01:34:47 +08001<template>
2 <div class="torrent-detail-page">
3 <div class="page-container">
4 <!-- 加载状态 -->
5 <div v-if="loading" class="loading-container">
6 <el-skeleton :rows="8" animated />
7 </div>
8
9 <!-- 详情内容 -->
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>
64 </div>
65 <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>
81 </div>
82
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>
96 </div>
97
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>
124 </div>
125 </div>
126 </div>
127 </div>
128
129 <!-- 详细信息选项卡 -->
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="文件列表加载中..." />
160 </div>
161 </div>
162 </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>
172 </div>
173 <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>
180 </div>
181 </div>
182 </div>
183
184 <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>
209 </div>
210 </div>
211 </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>
276 </div>
277 </div>
278 </div>
279</template>
280
281<script>
282import { ref, onMounted, computed } from 'vue'
283import { 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'
298import axios from 'axios'
299
300export default {
301 name: 'TorrentDetailView',
302 components: {
303 ArrowLeft,
304 Download,
305 Star,
306 Flag,
307 User,
308 Clock,
309 Document,
310 Files,
311 Picture,
312 Folder,
313 Like
314 },
315 setup() {
316 const route = useRoute()
317 const router = useRouter()
318
319 const loading = ref(true)
320 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
327 const torrentInfo = ref(null)
328 const torrentFiles = ref([])
329 const peerStats = ref({
330 seeders: 0,
331 leechers: 0
332 })
333
334 const seedersList = ref([])
335 const leechersList = ref([])
336 const comments = ref([])
337
338 // 解析标签
339 const parsedTags = computed(() => {
340 if (!torrentInfo.value?.tag || !Array.isArray(torrentInfo.value.tag)) {
341 return []
342 }
343
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 })
359
360 onMounted(() => {
361 const infoHash = route.params.infoHash
362 if (infoHash) {
363 fetchTorrentDetail(infoHash)
364 } else {
365 ElMessage.error('缺少种子标识符')
366 router.back()
367 }
368 })
369
370 const fetchTorrentDetail = async (infoHash) => {
371 try {
372 loading.value = true
373
374 // 获取种子详情
375 const response = await axios.get(`/api/torrent/view/${infoHash}`)
376
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 }
391 } catch (error) {
392 console.error('获取种子详情失败:', error)
393 ElMessage.error(error.message || '获取种子详情失败')
394 torrentInfo.value = null
395 } finally {
396 loading.value = false
397 }
398 }
399
400 const fetchTorrentFiles = async (infoHash) => {
401 try {
402 if (torrentInfo.value?.fileList) {
403 // 将文件列表转换为所需的格式
404 torrentFiles.value = Object.entries(torrentInfo.value.fileList).map(([path, size]) => ({
405 name: path.split('/').pop() || path, // 获取文件名
406 type: 'file',
407 size: formatFileSize(size),
408 path: path.split('/').slice(0, -1).join('/') || '/' // 获取文件路径
409 }))
410 } else {
411 // 如果是单文件种子,使用种子标题作为文件名
412 torrentFiles.value = [
413 {
414 name: torrentInfo.value?.title || 'unknown',
415 type: 'file',
416 size: formatFileSize(torrentInfo.value?.size || 0),
417 path: '/'
418 }
419 ]
420 }
421 } catch (error) {
422 console.error('获取文件列表失败:', error)
423 }
424 }
425
426 const fetchPeerStats = async (infoHash) => {
427 try {
428 // 这里应该调用获取用户活动数据的API
429 // const response = await axios.get(`http://localhost:8081/api/torrent/${infoHash}/peers`)
430 // peerStats.value = response.data
431
432 // 临时模拟数据
433 peerStats.value = {
434 seeders: Math.floor(Math.random() * 50) + 10,
435 leechers: Math.floor(Math.random() * 30) + 5
436 }
437
438 // 模拟用户列表
439 seedersList.value = [
440 { username: 'SeedMaster', uploaded: '2.5 TB', downloaded: '850 GB', ratio: '3.02', seedTime: '15天' }
441 ]
442
443 leechersList.value = [
444 { username: 'NewUser123', progress: 65, downloadSpeed: '15.2 MB/s', eta: '2小时15分' }
445 ]
446 } catch (error) {
447 console.error('获取用户活动数据失败:', error)
448 }
449 }
450
451 const fetchComments = async (infoHash) => {
452 try {
453 // 这里应该调用获取评论的API
454 // const response = await axios.get(`http://localhost:8081/api/torrent/${infoHash}/comments`)
455 // comments.value = response.data
456
457 // 临时模拟数据
458 comments.value = []
459 } catch (error) {
460 console.error('获取评论失败:', error)
461 }
462 }
463
464 const formatDateTime = (timestamp) => {
465 if (!timestamp) return '未知'
466 const date = new Date(timestamp)
467 return date.toLocaleString('zh-CN', {
468 year: 'numeric',
469 month: '2-digit',
470 day: '2-digit',
471 hour: '2-digit',
472 minute: '2-digit'
473 })
474 }
475
476 const formatFileSize = (bytes) => {
477 if (!bytes || bytes === 0) return '0 B'
478
479 const k = 1024
480 const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
481 const i = Math.floor(Math.log(bytes) / Math.log(k))
482
483 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
484 }
485
486 const formatDescription = (description) => {
487 if (!description) return ''
488 return description.replace(/\n/g, '<br>')
489 }
490
491 const getCategoryType = (categorySlug) => {
492 const types = {
493 'os': 'primary',
494 'movie': 'primary',
495 'tv': 'info',
496 'music': 'success',
497 'software': 'warning',
498 'game': 'danger'
499 }
500 return types[categorySlug] || 'default'
501 }
502
503 const handleDownload = async () => {
504 if (!torrentInfo.value?.infoHash) return
505
506 downloading.value = true
507 try {
508 // 调用下载种子文件的API
509 const response = await axios.get(
510 `/api/torrent/download/${torrentInfo.value.infoHash}`,
511 {
512 responseType: 'blob',
513 // 如果需要传递passkey,可以在这里添加params
514 params: {
515 // passkey: userStore.passkey // 如果你有用户store存储passkey
516 }
517 }
518 )
519
520 // 从响应头中获取文件名,如果没有则使用默认格式
521 let fileName = response.headers?.['content-disposition']?.split('filename=')[1]
522 if (!fileName) {
523 // 使用默认的文件名格式
524 fileName = `${torrentInfo.value.title}.torrent`
525 } else {
526 // 解码文件名
527 fileName = decodeURIComponent(fileName)
528 }
529
530 // 创建下载链接
531 const url = window.URL.createObjectURL(new Blob([response.data], { type: 'application/x-bittorrent' }))
532 const link = document.createElement('a')
533 link.href = url
534 link.download = fileName
535 document.body.appendChild(link)
536 link.click()
537 document.body.removeChild(link)
538 window.URL.revokeObjectURL(url)
539
540 ElMessage.success('种子文件下载完成')
541 } catch (error) {
542 console.error('下载失败:', error)
543 ElMessage.error(error.response?.data?.message || '下载失败,请稍后重试')
544 } finally {
545 downloading.value = false
546 }
547 }
548
549 const handleFavorite = () => {
550 isFavorited.value = !isFavorited.value
551 ElMessage.success(isFavorited.value ? '已添加到收藏' : '已取消收藏')
552 }
553
554 const handleReport = async () => {
555 try {
556 await ElMessageBox.prompt('请说明举报原因', '举报内容', {
557 confirmButtonText: '提交举报',
558 cancelButtonText: '取消',
559 inputType: 'textarea',
560 inputPlaceholder: '请详细说明举报原因...'
561 })
562
563 ElMessage.success('举报已提交,我们会尽快处理')
564 } catch {
565 // 用户取消
566 }
567 }
568
569 const submitComment = async () => {
570 if (!newComment.value.trim()) {
571 ElMessage.warning('请输入评论内容')
572 return
573 }
574
575 submittingComment.value = true
576 try {
577 // 这里应该调用提交评论的API
578 // await axios.post(`http://localhost:8081/api/torrent/${torrentInfo.value.infoHash}/comments`, {
579 // content: newComment.value
580 // })
581
582 // 模拟提交
583 await new Promise(resolve => setTimeout(resolve, 1000))
584
585 const comment = {
586 id: Date.now(),
587 username: localStorage.getItem('username') || '用户',
588 content: newComment.value,
589 time: new Date().toISOString(),
590 likes: 0
591 }
592
593 comments.value.unshift(comment)
594 newComment.value = ''
595
596 ElMessage.success('评论发表成功')
597 } catch (error) {
598 console.error('发表评论失败:', error)
599 ElMessage.error('发表评论失败')
600 } finally {
601 submittingComment.value = false
602 }
603 }
604
605 const likeComment = (commentId) => {
606 const comment = comments.value.find(c => c.id === commentId)
607 if (comment) {
608 comment.likes = (comment.likes || 0) + 1
609 ElMessage.success('点赞成功')
610 }
611 }
612
613 const replyComment = (commentId) => {
614 ElMessage.info('回复功能开发中...')
615 }
616
617 const retry = () => {
618 const infoHash = route.params.infoHash
619 if (infoHash) {
620 fetchTorrentDetail(infoHash)
621 }
622 }
623
624 return {
625 loading,
626 activeTab,
627 activityTab,
628 downloading,
629 isFavorited,
630 submittingComment,
631 newComment,
632 torrentInfo,
633 torrentFiles,
634 peerStats,
635 parsedTags,
636 seedersList,
637 leechersList,
638 comments,
639 formatDateTime,
640 formatFileSize,
641 formatDescription,
642 getCategoryType,
643 handleDownload,
644 handleFavorite,
645 handleReport,
646 submitComment,
647 likeComment,
648 replyComment,
649 retry,
650 ArrowLeft,
651 Download,
652 Star,
653 Flag,
654 User,
655 Clock,
656 Document,
657 Files,
658 Picture,
659 Folder,
660 Like
661 }
662 }
663}
664</script>
665
666<style lang="scss" scoped>
667.torrent-detail-page {
668 max-width: 1200px;
669 margin: 0 auto;
670 padding: 24px;
671 background: #f5f5f5;
672 min-height: 100vh;
673}
674
675.loading-container, .error-container {
676 background: #fff;
677 border-radius: 12px;
678 padding: 40px;
679 text-align: center;
680}
681
682.back-button {
683 margin-bottom: 16px;
684}
685
686.torrent-header {
687 background: #fff;
688 border-radius: 12px;
689 padding: 32px;
690 margin-bottom: 24px;
691 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
692
693 .header-content {
694 display: flex;
695 gap: 32px;
696
697 .torrent-cover {
698 flex-shrink: 0;
699
700 .cover-image {
701 width: 200px;
702 height: 280px;
703 border-radius: 8px;
704 object-fit: cover;
705 }
706
707 .image-placeholder {
708 width: 200px;
709 height: 280px;
710 background: #f5f5f5;
711 border-radius: 8px;
712 display: flex;
713 flex-direction: column;
714 align-items: center;
715 justify-content: center;
716 color: #999;
717
718 span {
719 margin-top: 8px;
720 font-size: 14px;
721 }
722 }
723 }
724
725 .torrent-info {
726 flex: 1;
727
728 .category-tag {
729 margin-bottom: 16px;
730
731 .el-tag {
732 margin-right: 8px;
733 }
734 }
735
736 .torrent-title {
737 font-size: 28px;
738 font-weight: 600;
739 color: #2c3e50;
740 margin: 0 0 16px 0;
741 line-height: 1.3;
742 }
743
744 .torrent-tags {
745 margin-bottom: 20px;
746
747 .el-tag {
748 margin: 0 8px 8px 0;
749 }
750 }
751
752 .torrent-meta {
753 margin-bottom: 20px;
754
755 .meta-item {
756 display: flex;
757 align-items: center;
758 gap: 8px;
759 margin-bottom: 8px;
760 color: #7f8c8d;
761 font-size: 14px;
762
763 .el-icon {
764 color: #909399;
765 }
766 }
767 }
768
769 .torrent-stats {
770 display: flex;
771 gap: 32px;
772 margin-bottom: 24px;
773
774 .stat-item {
775 text-align: center;
776
777 .stat-number {
778 display: block;
779 font-size: 24px;
780 font-weight: 600;
781 margin-bottom: 4px;
782 color: #2c3e50;
783 }
784
785 .stat-label {
786 font-size: 14px;
787 color: #909399;
788 }
789
790 &.seeders .stat-number { color: #67c23a; }
791 &.leechers .stat-number { color: #f56c6c; }
792 &.downloads .stat-number { color: #409eff; }
793 }
794 }
795
796 .action-buttons {
797 display: flex;
798 gap: 12px;
799 flex-wrap: wrap;
800 }
801 }
802 }
803}
804
805.detail-tabs {
806 background: #fff;
807 border-radius: 12px;
808 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
809
810 :deep(.el-tabs__content) {
811 padding: 24px;
812 }
813
814 .description-content {
815 line-height: 1.6;
816
817 :deep(h3) {
818 color: #2c3e50;
819 font-size: 18px;
820 font-weight: 600;
821 margin: 24px 0 12px 0;
822
823 &:first-child {
824 margin-top: 0;
825 }
826 }
827
828 :deep(p) {
829 margin-bottom: 12px;
830 color: #5a6c7d;
831 }
832
833 :deep(ul) {
834 margin: 12px 0;
835 padding-left: 20px;
836
837 li {
838 margin-bottom: 8px;
839 color: #5a6c7d;
840 }
841 }
842
843 .no-description {
844 text-align: center;
845 color: #909399;
846 padding: 40px 0;
847 }
848 }
849
850 .files-list {
851 .file-name {
852 display: flex;
853 align-items: center;
854 gap: 8px;
855
856 .el-icon {
857 color: #909399;
858 }
859 }
860
861 .no-files {
862 padding: 40px 0;
863 }
864 }
865
866 .activity-section {
867 .activity-stats {
868 margin-bottom: 24px;
869
870 .stats-grid {
871 display: grid;
872 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
873 gap: 16px;
874
875 .stat-card {
876 background: #f8f9fa;
877 padding: 20px;
878 border-radius: 8px;
879 text-align: center;
880
881 h3 {
882 font-size: 14px;
883 color: #909399;
884 margin: 0 0 8px 0;
885 }
886
887 .stat-number {
888 font-size: 24px;
889 font-weight: 600;
890 color: #2c3e50;
891 }
892 }
893 }
894 }
895 }
896
897 .comments-section {
898 .comment-form {
899 margin-bottom: 32px;
900
901 .comment-actions {
902 margin-top: 12px;
903 text-align: right;
904 }
905 }
906
907 .comments-list {
908 .comment-item {
909 display: flex;
910 gap: 16px;
911 margin-bottom: 24px;
912 padding-bottom: 24px;
913 border-bottom: 1px solid #f0f0f0;
914
915 &:last-child {
916 border-bottom: none;
917 margin-bottom: 0;
918 padding-bottom: 0;
919 }
920
921 .comment-content {
922 flex: 1;
923
924 .comment-header {
925 display: flex;
926 align-items: center;
927 gap: 12px;
928 margin-bottom: 8px;
929
930 .comment-username {
931 font-weight: 600;
932 color: #2c3e50;
933 }
934
935 .comment-time {
936 font-size: 12px;
937 color: #909399;
938 }
939 }
940
941 .comment-text {
942 color: #5a6c7d;
943 line-height: 1.5;
944 margin-bottom: 12px;
945 }
946
947 .comment-actions {
948 .el-button {
949 padding: 0;
950 margin-right: 16px;
951
952 .el-icon {
953 margin-right: 4px;
954 }
955 }
956 }
957 }
958 }
959
960 .no-comments {
961 text-align: center;
962 color: #909399;
963 padding: 40px 0;
964 }
965 }
966 }
967}
968
969// 响应式设计
970@media (max-width: 768px) {
971 .torrent-detail-page {
972 padding: 16px;
973 }
974
975 .torrent-header .header-content {
976 flex-direction: column;
977 text-align: center;
978
979 .torrent-cover {
980 align-self: center;
981 }
982
983 .torrent-stats {
984 justify-content: center;
985 }
986
987 .action-buttons {
988 justify-content: center;
989 }
990 }
991
992 .activity-section .stats-grid {
993 grid-template-columns: 1fr;
994 }
995
996 .comment-item {
997 flex-direction: column;
998 gap: 12px;
999 }
1000}
1001</style>