blob: c8ee498a7690dbf1f5dacdec9772ca741e5c7504 [file] [log] [blame]
Xing Jinwenff16b1e2025-06-05 00:29:26 +08001<template>
2 <div class="torrent-detail-page">
3 <div class="page-container">
4 <!-- 返回按钮 -->
5 <div class="back-button">
6 <el-button :icon="ArrowLeft" @click="$router.back()">
7 返回列表
8 </el-button>
9 </div>
10
11 <!-- 种子基本信息 -->
12 <div class="torrent-header">
13 <div class="header-content">
14 <div class="torrent-cover">
15 <el-image
16 :src="torrentInfo.coverImage || '/default-cover.jpg'"
17 :alt="torrentInfo.title"
18 fit="cover"
19 class="cover-image"
20 >
21 <template #error>
22 <div class="image-placeholder">
23 <el-icon size="48"><Picture /></el-icon>
24 <span>暂无封面</span>
25 </div>
26 </template>
27 </el-image>
28 </div>
29
30 <div class="torrent-info">
31 <div class="category-tag">
32 <el-tag :type="getCategoryType(torrentInfo.category)" size="large">
33 {{ getCategoryName(torrentInfo.category) }}
34 </el-tag>
35 <el-tag v-if="torrentInfo.subcategory" type="info" size="small">
36 {{ torrentInfo.subcategory }}
37 </el-tag>
38 </div>
39
40 <h1 class="torrent-title">{{ torrentInfo.title }}</h1>
41
42 <div class="torrent-tags">
43 <el-tag
44 v-for="tag in torrentInfo.tags"
45 :key="tag"
46 size="small"
47 effect="plain"
48 >
49 {{ tag }}
50 </el-tag>
51 </div>
52
53 <div class="torrent-meta">
54 <div class="meta-item">
55 <el-icon><User /></el-icon>
56 <span>上传者:{{ torrentInfo.uploader }}</span>
57 </div>
58 <div class="meta-item">
59 <el-icon><Clock /></el-icon>
60 <span>上传时间:{{ formatDateTime(torrentInfo.uploadTime) }}</span>
61 </div>
62 <div class="meta-item">
63 <el-icon><Document /></el-icon>
64 <span>文件大小:{{ torrentInfo.size }}</span>
65 </div>
66 <div class="meta-item">
67 <el-icon><Files /></el-icon>
68 <span>文件数量:{{ torrentInfo.fileCount }} 个</span>
69 </div>
70 </div>
71
72 <div class="torrent-stats">
73 <div class="stat-item seeders">
74 <span class="stat-number">{{ torrentInfo.seeders }}</span>
75 <span class="stat-label">做种</span>
76 </div>
77 <div class="stat-item leechers">
78 <span class="stat-number">{{ torrentInfo.leechers }}</span>
79 <span class="stat-label">下载</span>
80 </div>
81 <div class="stat-item downloads">
82 <span class="stat-number">{{ torrentInfo.downloads }}</span>
83 <span class="stat-label">完成</span>
84 </div>
85 </div>
86
87 <div class="action-buttons">
88 <el-button
89 type="primary"
90 size="large"
91 :icon="Download"
92 @click="handleDownload"
93 :loading="downloading"
94 >
95 {{ downloading ? '准备中...' : '下载种子' }}
96 </el-button>
97 <el-button
98 type="success"
99 size="large"
100 :icon="Star"
101 @click="handleFavorite"
102 >
103 {{ isFavorited ? '已收藏' : '收藏' }}
104 </el-button>
105 <el-button
106 type="warning"
107 size="large"
108 :icon="Flag"
109 @click="handleReport"
110 >
111 举报
112 </el-button>
113 </div>
114 </div>
115 </div>
116 </div>
117
118 <!-- 详细信息选项卡 -->
119 <div class="detail-tabs">
120 <el-tabs v-model="activeTab" type="border-card">
121 <!-- 种子描述 -->
122 <el-tab-pane label="详细描述" name="description">
123 <div class="description-content">
124 <div v-if="torrentInfo.description" v-html="formatDescription(torrentInfo.description)"></div>
125 <div v-else class="no-description">暂无详细描述</div>
126 </div>
127 </el-tab-pane>
128
129 <!-- 文件列表 -->
130 <el-tab-pane label="文件列表" name="files" lazy>
131 <div class="files-list">
132 <el-table :data="torrentInfo.files" stripe>
133 <el-table-column label="文件名" prop="name" min-width="400">
134 <template #default="{ row }">
135 <div class="file-name">
136 <el-icon v-if="row.type === 'folder'"><Folder /></el-icon>
137 <el-icon v-else><Document /></el-icon>
138 <span>{{ row.name }}</span>
139 </div>
140 </template>
141 </el-table-column>
142 <el-table-column label="大小" prop="size" width="120" align="right" />
143 <el-table-column label="路径" prop="path" min-width="300" />
144 </el-table>
145 </div>
146 </el-tab-pane>
147
148 <!-- 用户活动 -->
149 <el-tab-pane label="用户活动" name="activity">
150 <div class="activity-section">
151 <div class="activity-stats">
152 <div class="stats-grid">
153 <div class="stat-card">
154 <h3>做种用户</h3>
155 <p class="stat-number">{{ torrentInfo.seeders }}</p>
156 </div>
157 <div class="stat-card">
158 <h3>下载用户</h3>
159 <p class="stat-number">{{ torrentInfo.leechers }}</p>
160 </div>
161 <div class="stat-card">
162 <h3>完成用户</h3>
163 <p class="stat-number">{{ torrentInfo.downloads }}</p>
164 </div>
165 </div>
166 </div>
167
168 <div class="user-lists">
169 <el-tabs v-model="activityTab" type="card">
170 <el-tab-pane label="做种用户" name="seeders">
171 <el-table :data="seedersList" max-height="400">
172 <el-table-column label="用户" prop="username" />
173 <el-table-column label="上传量" prop="uploaded" />
174 <el-table-column label="下载量" prop="downloaded" />
175 <el-table-column label="分享率" prop="ratio" />
176 <el-table-column label="做种时间" prop="seedTime" />
177 </el-table>
178 </el-tab-pane>
179
180 <el-tab-pane label="下载用户" name="leechers">
181 <el-table :data="leechersList" max-height="400">
182 <el-table-column label="用户" prop="username" />
183 <el-table-column label="进度" prop="progress">
184 <template #default="{ row }">
185 <el-progress :percentage="row.progress" :stroke-width="6" />
186 </template>
187 </el-table-column>
188 <el-table-column label="下载速度" prop="downloadSpeed" />
189 <el-table-column label="剩余时间" prop="eta" />
190 </el-table>
191 </el-tab-pane>
192 </el-tabs>
193 </div>
194 </div>
195 </el-tab-pane>
196
197 <!-- 评论区 -->
198 <el-tab-pane label="评论" name="comments">
199 <div class="comments-section">
200 <!-- 发表评论 -->
201 <div class="comment-form">
202 <el-input
203 v-model="newComment"
204 type="textarea"
205 :rows="4"
206 placeholder="发表你的评论..."
207 maxlength="500"
208 show-word-limit
209 />
210 <div class="comment-actions">
211 <el-button type="primary" @click="submitComment" :loading="submittingComment">
212 发表评论
213 </el-button>
214 </div>
215 </div>
216
217 <!-- 评论列表 -->
218 <div class="comments-list">
219 <div
220 v-for="comment in comments"
221 :key="comment.id"
222 class="comment-item"
223 >
224 <div class="comment-avatar">
225 <el-avatar :size="40">{{ comment.username.charAt(0) }}</el-avatar>
226 </div>
227 <div class="comment-content">
228 <div class="comment-header">
229 <span class="comment-username">{{ comment.username }}</span>
230 <span class="comment-time">{{ formatDateTime(comment.time) }}</span>
231 </div>
232 <div class="comment-text">{{ comment.content }}</div>
233 <div class="comment-actions">
234 <el-button type="text" size="small" @click="likeComment(comment.id)">
235 <el-icon><Like /></el-icon>
236 {{ comment.likes || 0 }}
237 </el-button>
238 <el-button type="text" size="small" @click="replyComment(comment.id)">
239 回复
240 </el-button>
241 </div>
242 </div>
243 </div>
244
245 <div v-if="comments.length === 0" class="no-comments">
246 暂无评论,快来发表第一条评论吧!
247 </div>
248 </div>
249 </div>
250 </el-tab-pane>
251 </el-tabs>
252 </div>
253 </div>
254 </div>
255</template>
256
257<script>
258import { ref, onMounted } from 'vue'
259import { useRoute, useRouter } from 'vue-router'
260import { ElMessage, ElMessageBox } from 'element-plus'
261import {
262 ArrowLeft,
263 Download,
264 Star,
265 Flag,
266 User,
267 Clock,
268 Document,
269 Files,
270 Picture,
271 Folder,
272 Like
273} from '@element-plus/icons-vue'
274
275export default {
276 name: 'TorrentDetailView',
277 setup() {
278 const route = useRoute()
279 const router = useRouter()
280
281 const activeTab = ref('description')
282 const activityTab = ref('seeders')
283 const downloading = ref(false)
284 const isFavorited = ref(false)
285 const submittingComment = ref(false)
286 const newComment = ref('')
287
288 const torrentInfo = ref({
289 id: 1,
290 title: '[4K蓝光原盘] 阿凡达:水之道 Avatar: The Way of Water (2022)',
291 category: 'movie',
292 subcategory: '科幻片',
293 uploader: 'MovieMaster',
294 uploadTime: '2025-06-03T10:30:00',
295 size: '85.6 GB',
296 fileCount: 125,
297 seeders: 128,
298 leechers: 45,
299 downloads: 892,
300 coverImage: 'https://example.com/avatar2-cover.jpg',
301 tags: ['4K', '蓝光原盘', '科幻', '詹姆斯·卡梅隆'],
302 description: `
303 <h3>影片信息</h3>
304 <p><strong>片名:</strong>阿凡达:水之道 / Avatar: The Way of Water</p>
305 <p><strong>年份:</strong>2022</p>
306 <p><strong>导演:</strong>詹姆斯·卡梅隆</p>
307 <p><strong>主演:</strong>萨姆·沃辛顿 / 佐伊·索尔达娜 / 西格妮·韦弗</p>
308 <p><strong>类型:</strong>科幻 / 动作 / 冒险</p>
309 <p><strong>制片国家/地区:</strong>美国</p>
310 <p><strong>语言:</strong>英语</p>
311 <p><strong>上映日期:</strong>2022-12-16</p>
312 <p><strong>片长:</strong>192分钟</p>
313
314 <h3>影片简介</h3>
315 <p>杰克·萨利和奈蒂莉组建了家庭,他们的孩子也逐渐成长。当危险威胁到他们时,杰克和奈蒂莉必须为彼此而战,为家庭而战,为生存而战。</p>
316
317 <h3>技术规格</h3>
318 <ul>
319 <li>视频:4K UHD 2160p / HEVC / HDR10</li>
320 <li>音频:Dolby Atmos TrueHD 7.1 / DTS-HD MA 7.1</li>
321 <li>字幕:中文 / 英文</li>
322 <li>片源:4K UHD 蓝光原盘</li>
323 </ul>
324
325 <h3>下载说明</h3>
326 <p>本资源为4K蓝光原盘,保持了最高的画质和音质。建议使用支持4K播放的设备观看。</p>
327 `,
328 files: [
329 { name: 'BDMV', type: 'folder', size: '85.6 GB', path: '/' },
330 { name: 'CERTIFICATE', type: 'folder', size: '2.1 MB', path: '/' },
331 { name: 'Avatar.The.Way.of.Water.2022.2160p.UHD.Blu-ray.x265.HDR.Atmos-DETAIL.mkv', type: 'file', size: '32.8 GB', path: '/BDMV/STREAM/' },
332 { name: 'Avatar.The.Way.of.Water.2022.Extras.mkv', type: 'file', size: '12.4 GB', path: '/BDMV/STREAM/' }
333 ]
334 })
335
336 const seedersList = ref([
337 { username: 'SeedMaster', uploaded: '2.5 TB', downloaded: '850 GB', ratio: '3.02', seedTime: '15天' },
338 { username: 'MovieFan88', uploaded: '1.8 TB', downloaded: '1.2 TB', ratio: '1.50', seedTime: '8天' },
339 { username: 'CinemaLover', uploaded: '3.2 TB', downloaded: '900 GB', ratio: '3.56', seedTime: '22天' }
340 ])
341
342 const leechersList = ref([
343 { username: 'NewUser123', progress: 65, downloadSpeed: '15.2 MB/s', eta: '2小时15分' },
344 { username: 'MovieSeeker', progress: 23, downloadSpeed: '8.7 MB/s', eta: '8小时32分' },
345 { username: 'FilmCollector', progress: 89, downloadSpeed: '22.1 MB/s', eta: '45分钟' }
346 ])
347
348 const comments = ref([
349 {
350 id: 1,
351 username: 'MovieReviewer',
352 content: '画质非常棒!4K HDR效果惊艳,水下场景美不胜收。感谢分享!',
353 time: '2025-06-03T12:00:00',
354 likes: 15
355 },
356 {
357 id: 2,
358 username: 'CinemaExpert',
359 content: '音效也很棒,Dolby Atmos的环绕效果让人身临其境。推荐大家下载!',
360 time: '2025-06-03T11:30:00',
361 likes: 8
362 }
363 ])
364
365 onMounted(() => {
366 const torrentId = route.params.id
367 fetchTorrentDetail(torrentId)
368 })
369
370 const fetchTorrentDetail = async (id) => {
371 try {
372 // 模拟API调用
373 console.log('获取种子详情:', id)
374 // 这里应该调用真实的API
375 } catch (error) {
376 ElMessage.error('获取种子详情失败')
377 router.back()
378 }
379 }
380
381 const formatDateTime = (dateString) => {
382 const date = new Date(dateString)
383 return date.toLocaleString('zh-CN', {
384 year: 'numeric',
385 month: '2-digit',
386 day: '2-digit',
387 hour: '2-digit',
388 minute: '2-digit'
389 })
390 }
391
392 const formatDescription = (description) => {
393 // 简单的HTML清理,实际项目中应该使用专门的库
394 return description.replace(/\n/g, '<br>')
395 }
396
397 const getCategoryType = (category) => {
398 const types = {
399 'movie': 'primary',
400 'tv': 'info',
401 'music': 'success',
402 'software': 'warning',
403 'game': 'danger'
404 }
405 return types[category] || 'default'
406 }
407
408 const getCategoryName = (category) => {
409 const names = {
410 'movie': '电影',
411 'tv': '电视剧',
412 'music': '音乐',
413 'software': '软件',
414 'game': '游戏'
415 }
416 return names[category] || category
417 }
418
419 const handleDownload = async () => {
420 downloading.value = true
421 try {
422 // 模拟下载准备过程
423 await new Promise(resolve => setTimeout(resolve, 1500))
424
425 // 实际项目中这里应该下载.torrent文件
426 const link = document.createElement('a')
427 link.href = '#' // 实际的种子文件下载链接
428 link.download = `${torrentInfo.value.title}.torrent`
429 link.click()
430
431 ElMessage.success('种子文件下载完成')
432 } catch (error) {
433 ElMessage.error('下载失败,请稍后重试')
434 } finally {
435 downloading.value = false
436 }
437 }
438
439 const handleFavorite = () => {
440 isFavorited.value = !isFavorited.value
441 ElMessage.success(isFavorited.value ? '已添加到收藏' : '已取消收藏')
442 }
443
444 const handleReport = async () => {
445 try {
446 await ElMessageBox.prompt('请说明举报原因', '举报内容', {
447 confirmButtonText: '提交举报',
448 cancelButtonText: '取消',
449 inputType: 'textarea',
450 inputPlaceholder: '请详细说明举报原因...'
451 })
452
453 ElMessage.success('举报已提交,我们会尽快处理')
454 } catch {
455 // 用户取消
456 }
457 }
458
459 const submitComment = async () => {
460 if (!newComment.value.trim()) {
461 ElMessage.warning('请输入评论内容')
462 return
463 }
464
465 submittingComment.value = true
466 try {
467 // 模拟提交评论
468 await new Promise(resolve => setTimeout(resolve, 1000))
469
470 const comment = {
471 id: Date.now(),
472 username: localStorage.getItem('username') || '用户',
473 content: newComment.value,
474 time: new Date().toISOString(),
475 likes: 0
476 }
477
478 comments.value.unshift(comment)
479 newComment.value = ''
480
481 ElMessage.success('评论发表成功')
482 } catch (error) {
483 ElMessage.error('发表评论失败')
484 } finally {
485 submittingComment.value = false
486 }
487 }
488
489 const likeComment = (commentId) => {
490 const comment = comments.value.find(c => c.id === commentId)
491 if (comment) {
492 comment.likes = (comment.likes || 0) + 1
493 ElMessage.success('点赞成功')
494 }
495 }
496
497 const replyComment = (commentId) => {
498 // 实现回复功能
499 ElMessage.info('回复功能开发中...')
500 }
501
502 return {
503 activeTab,
504 activityTab,
505 downloading,
506 isFavorited,
507 submittingComment,
508 newComment,
509 torrentInfo,
510 seedersList,
511 leechersList,
512 comments,
513 formatDateTime,
514 formatDescription,
515 getCategoryType,
516 getCategoryName,
517 handleDownload,
518 handleFavorite,
519 handleReport,
520 submitComment,
521 likeComment,
522 replyComment,
523 ArrowLeft,
524 Download,
525 Star,
526 Flag,
527 User,
528 Clock,
529 Document,
530 Files,
531 Picture,
532 Folder,
533 Like
534 }
535 }
536}
537</script>
538
539<style lang="scss" scoped>
540.torrent-detail-page {
541 max-width: 1200px;
542 margin: 0 auto;
543 padding: 24px;
544 background: #f5f5f5;
545 min-height: 100vh;
546}
547
548.back-button {
549 margin-bottom: 16px;
550}
551
552.torrent-header {
553 background: #fff;
554 border-radius: 12px;
555 padding: 32px;
556 margin-bottom: 24px;
557 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
558
559 .header-content {
560 display: flex;
561 gap: 32px;
562
563 .torrent-cover {
564 flex-shrink: 0;
565
566 .cover-image {
567 width: 200px;
568 height: 280px;
569 border-radius: 8px;
570 object-fit: cover;
571 }
572
573 .image-placeholder {
574 width: 200px;
575 height: 280px;
576 background: #f5f5f5;
577 border-radius: 8px;
578 display: flex;
579 flex-direction: column;
580 align-items: center;
581 justify-content: center;
582 color: #999;
583
584 span {
585 margin-top: 8px;
586 font-size: 14px;
587 }
588 }
589 }
590
591 .torrent-info {
592 flex: 1;
593
594 .category-tag {
595 margin-bottom: 16px;
596
597 .el-tag {
598 margin-right: 8px;
599 }
600 }
601
602 .torrent-title {
603 font-size: 28px;
604 font-weight: 600;
605 color: #2c3e50;
606 margin: 0 0 16px 0;
607 line-height: 1.3;
608 }
609
610 .torrent-tags {
611 margin-bottom: 20px;
612
613 .el-tag {
614 margin: 0 8px 8px 0;
615 }
616 }
617
618 .torrent-meta {
619 margin-bottom: 20px;
620
621 .meta-item {
622 display: flex;
623 align-items: center;
624 gap: 8px;
625 margin-bottom: 8px;
626 color: #7f8c8d;
627 font-size: 14px;
628
629 .el-icon {
630 color: #909399;
631 }
632 }
633 }
634
635 .torrent-stats {
636 display: flex;
637 gap: 32px;
638 margin-bottom: 24px;
639
640 .stat-item {
641 text-align: center;
642
643 .stat-number {
644 display: block;
645 font-size: 24px;
646 font-weight: 600;
647 margin-bottom: 4px;
648
649 &.seeders { color: #67c23a; }
650 &.leechers { color: #f56c6c; }
651 &.downloads { color: #409eff; }
652 }
653
654 .stat-label {
655 font-size: 14px;
656 color: #909399;
657 }
658 }
659 }
660
661 .action-buttons {
662 display: flex;
663 gap: 12px;
664 flex-wrap: wrap;
665 }
666 }
667 }
668}
669
670.detail-tabs {
671 background: #fff;
672 border-radius: 12px;
673 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
674
675 :deep(.el-tabs__content) {
676 padding: 24px;
677 }
678
679 .description-content {
680 line-height: 1.6;
681
682 :deep(h3) {
683 color: #2c3e50;
684 font-size: 18px;
685 font-weight: 600;
686 margin: 24px 0 12px 0;
687
688 &:first-child {
689 margin-top: 0;
690 }
691 }
692
693 :deep(p) {
694 margin-bottom: 12px;
695 color: #5a6c7d;
696 }
697
698 :deep(ul) {
699 margin: 12px 0;
700 padding-left: 20px;
701
702 li {
703 margin-bottom: 8px;
704 color: #5a6c7d;
705 }
706 }
707
708 .no-description {
709 text-align: center;
710 color: #909399;
711 padding: 40px 0;
712 }
713 }
714
715 .files-list {
716 .file-name {
717 display: flex;
718 align-items: center;
719 gap: 8px;
720
721 .el-icon {
722 color: #909399;
723 }
724 }
725 }
726
727 .activity-section {
728 .activity-stats {
729 margin-bottom: 24px;
730
731 .stats-grid {
732 display: grid;
733 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
734 gap: 16px;
735
736 .stat-card {
737 background: #f8f9fa;
738 padding: 20px;
739 border-radius: 8px;
740 text-align: center;
741
742 h3 {
743 font-size: 14px;
744 color: #909399;
745 margin: 0 0 8px 0;
746 }
747
748 .stat-number {
749 font-size: 24px;
750 font-weight: 600;
751 color: #2c3e50;
752 }
753 }
754 }
755 }
756 }
757
758 .comments-section {
759 .comment-form {
760 margin-bottom: 32px;
761
762 .comment-actions {
763 margin-top: 12px;
764 text-align: right;
765 }
766 }
767
768 .comments-list {
769 .comment-item {
770 display: flex;
771 gap: 16px;
772 margin-bottom: 24px;
773 padding-bottom: 24px;
774 border-bottom: 1px solid #f0f0f0;
775
776 &:last-child {
777 border-bottom: none;
778 margin-bottom: 0;
779 padding-bottom: 0;
780 }
781
782 .comment-content {
783 flex: 1;
784
785 .comment-header {
786 display: flex;
787 align-items: center;
788 gap: 12px;
789 margin-bottom: 8px;
790
791 .comment-username {
792 font-weight: 600;
793 color: #2c3e50;
794 }
795
796 .comment-time {
797 font-size: 12px;
798 color: #909399;
799 }
800 }
801
802 .comment-text {
803 color: #5a6c7d;
804 line-height: 1.5;
805 margin-bottom: 12px;
806 }
807
808 .comment-actions {
809 .el-button {
810 padding: 0;
811 margin-right: 16px;
812
813 .el-icon {
814 margin-right: 4px;
815 }
816 }
817 }
818 }
819 }
820
821 .no-comments {
822 text-align: center;
823 color: #909399;
824 padding: 40px 0;
825 }
826 }
827 }
828}
829
830// 响应式设计
831@media (max-width: 768px) {
832 .torrent-detail-page {
833 padding: 16px;
834 }
835
836 .torrent-header .header-content {
837 flex-direction: column;
838 text-align: center;
839
840 .torrent-cover {
841 align-self: center;
842 }
843
844 .torrent-stats {
845 justify-content: center;
846 }
847
848 .action-buttons {
849 justify-content: center;
850 }
851 }
852
853 .activity-section .stats-grid {
854 grid-template-columns: 1fr;
855 }
856
857 .comment-item {
858 flex-direction: column;
859 gap: 12px;
860 }
861}
xingjinwend652cc62025-06-04 19:52:19 +0800862</style>