| <template> |
| <div class="torrent-detail-page"> |
| <div class="page-container"> |
| <!-- 返回按钮 --> |
| <div class="back-button"> |
| <el-button :icon="ArrowLeft" @click="$router.back()"> |
| 返回列表 |
| </el-button> |
| </div> |
| |
| <!-- 种子基本信息 --> |
| <div class="torrent-header"> |
| <div class="header-content"> |
| <div class="torrent-cover"> |
| <el-image |
| :src="torrentInfo.coverImage || '/default-cover.jpg'" |
| :alt="torrentInfo.title" |
| fit="cover" |
| class="cover-image" |
| > |
| <template #error> |
| <div class="image-placeholder"> |
| <el-icon size="48"><Picture /></el-icon> |
| <span>暂无封面</span> |
| </div> |
| </template> |
| </el-image> |
| </div> |
| |
| <div class="torrent-info"> |
| <div class="category-tag"> |
| <el-tag :type="getCategoryType(torrentInfo.category)" size="large"> |
| {{ getCategoryName(torrentInfo.category) }} |
| </el-tag> |
| <el-tag v-if="torrentInfo.subcategory" type="info" size="small"> |
| {{ torrentInfo.subcategory }} |
| </el-tag> |
| </div> |
| |
| <h1 class="torrent-title">{{ torrentInfo.title }}</h1> |
| |
| <div class="torrent-tags"> |
| <el-tag |
| v-for="tag in torrentInfo.tags" |
| :key="tag" |
| size="small" |
| effect="plain" |
| > |
| {{ tag }} |
| </el-tag> |
| </div> |
| |
| <div class="torrent-meta"> |
| <div class="meta-item"> |
| <el-icon><User /></el-icon> |
| <span>上传者:{{ torrentInfo.uploader }}</span> |
| </div> |
| <div class="meta-item"> |
| <el-icon><Clock /></el-icon> |
| <span>上传时间:{{ formatDateTime(torrentInfo.uploadTime) }}</span> |
| </div> |
| <div class="meta-item"> |
| <el-icon><Document /></el-icon> |
| <span>文件大小:{{ torrentInfo.size }}</span> |
| </div> |
| <div class="meta-item"> |
| <el-icon><Files /></el-icon> |
| <span>文件数量:{{ torrentInfo.fileCount }} 个</span> |
| </div> |
| </div> |
| |
| <div class="torrent-stats"> |
| <div class="stat-item seeders"> |
| <span class="stat-number">{{ torrentInfo.seeders }}</span> |
| <span class="stat-label">做种</span> |
| </div> |
| <div class="stat-item leechers"> |
| <span class="stat-number">{{ torrentInfo.leechers }}</span> |
| <span class="stat-label">下载</span> |
| </div> |
| <div class="stat-item downloads"> |
| <span class="stat-number">{{ torrentInfo.downloads }}</span> |
| <span class="stat-label">完成</span> |
| </div> |
| </div> |
| |
| <div class="action-buttons"> |
| <el-button |
| type="primary" |
| size="large" |
| :icon="Download" |
| @click="handleDownload" |
| :loading="downloading" |
| > |
| {{ downloading ? '准备中...' : '下载种子' }} |
| </el-button> |
| <el-button |
| type="success" |
| size="large" |
| :icon="Star" |
| @click="handleFavorite" |
| > |
| {{ isFavorited ? '已收藏' : '收藏' }} |
| </el-button> |
| <el-button |
| type="warning" |
| size="large" |
| :icon="Flag" |
| @click="handleReport" |
| > |
| 举报 |
| </el-button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <!-- 详细信息选项卡 --> |
| <div class="detail-tabs"> |
| <el-tabs v-model="activeTab" type="border-card"> |
| <!-- 种子描述 --> |
| <el-tab-pane label="详细描述" name="description"> |
| <div class="description-content"> |
| <div v-if="torrentInfo.description" v-html="formatDescription(torrentInfo.description)"></div> |
| <div v-else class="no-description">暂无详细描述</div> |
| </div> |
| </el-tab-pane> |
| |
| <!-- 文件列表 --> |
| <el-tab-pane label="文件列表" name="files" lazy> |
| <div class="files-list"> |
| <el-table :data="torrentInfo.files" stripe> |
| <el-table-column label="文件名" prop="name" min-width="400"> |
| <template #default="{ row }"> |
| <div class="file-name"> |
| <el-icon v-if="row.type === 'folder'"><Folder /></el-icon> |
| <el-icon v-else><Document /></el-icon> |
| <span>{{ row.name }}</span> |
| </div> |
| </template> |
| </el-table-column> |
| <el-table-column label="大小" prop="size" width="120" align="right" /> |
| <el-table-column label="路径" prop="path" min-width="300" /> |
| </el-table> |
| </div> |
| </el-tab-pane> |
| |
| <!-- 用户活动 --> |
| <el-tab-pane label="用户活动" name="activity"> |
| <div class="activity-section"> |
| <div class="activity-stats"> |
| <div class="stats-grid"> |
| <div class="stat-card"> |
| <h3>做种用户</h3> |
| <p class="stat-number">{{ torrentInfo.seeders }}</p> |
| </div> |
| <div class="stat-card"> |
| <h3>下载用户</h3> |
| <p class="stat-number">{{ torrentInfo.leechers }}</p> |
| </div> |
| <div class="stat-card"> |
| <h3>完成用户</h3> |
| <p class="stat-number">{{ torrentInfo.downloads }}</p> |
| </div> |
| </div> |
| </div> |
| |
| <div class="user-lists"> |
| <el-tabs v-model="activityTab" type="card"> |
| <el-tab-pane label="做种用户" name="seeders"> |
| <el-table :data="seedersList" max-height="400"> |
| <el-table-column label="用户" prop="username" /> |
| <el-table-column label="上传量" prop="uploaded" /> |
| <el-table-column label="下载量" prop="downloaded" /> |
| <el-table-column label="分享率" prop="ratio" /> |
| <el-table-column label="做种时间" prop="seedTime" /> |
| </el-table> |
| </el-tab-pane> |
| |
| <el-tab-pane label="下载用户" name="leechers"> |
| <el-table :data="leechersList" max-height="400"> |
| <el-table-column label="用户" prop="username" /> |
| <el-table-column label="进度" prop="progress"> |
| <template #default="{ row }"> |
| <el-progress :percentage="row.progress" :stroke-width="6" /> |
| </template> |
| </el-table-column> |
| <el-table-column label="下载速度" prop="downloadSpeed" /> |
| <el-table-column label="剩余时间" prop="eta" /> |
| </el-table> |
| </el-tab-pane> |
| </el-tabs> |
| </div> |
| </div> |
| </el-tab-pane> |
| |
| <!-- 评论区 --> |
| <el-tab-pane label="评论" name="comments"> |
| <div class="comments-section"> |
| <!-- 发表评论 --> |
| <div class="comment-form"> |
| <el-input |
| v-model="newComment" |
| type="textarea" |
| :rows="4" |
| placeholder="发表你的评论..." |
| maxlength="500" |
| show-word-limit |
| /> |
| <div class="comment-actions"> |
| <el-button type="primary" @click="submitComment" :loading="submittingComment"> |
| 发表评论 |
| </el-button> |
| </div> |
| </div> |
| |
| <!-- 评论列表 --> |
| <div class="comments-list"> |
| <div |
| v-for="comment in comments" |
| :key="comment.id" |
| class="comment-item" |
| > |
| <div class="comment-avatar"> |
| <el-avatar :size="40">{{ comment.username.charAt(0) }}</el-avatar> |
| </div> |
| <div class="comment-content"> |
| <div class="comment-header"> |
| <span class="comment-username">{{ comment.username }}</span> |
| <span class="comment-time">{{ formatDateTime(comment.time) }}</span> |
| </div> |
| <div class="comment-text">{{ comment.content }}</div> |
| <div class="comment-actions"> |
| <el-button type="text" size="small" @click="likeComment(comment.id)"> |
| <el-icon><Like /></el-icon> |
| {{ comment.likes || 0 }} |
| </el-button> |
| <el-button type="text" size="small" @click="replyComment(comment.id)"> |
| 回复 |
| </el-button> |
| </div> |
| </div> |
| </div> |
| |
| <div v-if="comments.length === 0" class="no-comments"> |
| 暂无评论,快来发表第一条评论吧! |
| </div> |
| </div> |
| </div> |
| </el-tab-pane> |
| </el-tabs> |
| </div> |
| </div> |
| </div> |
| </template> |
| |
| <script> |
| import { ref, onMounted } from 'vue' |
| import { useRoute, useRouter } from 'vue-router' |
| import { ElMessage, ElMessageBox } from 'element-plus' |
| import { |
| ArrowLeft, |
| Download, |
| Star, |
| Flag, |
| User, |
| Clock, |
| Document, |
| Files, |
| Picture, |
| Folder, |
| Like |
| } from '@element-plus/icons-vue' |
| |
| export default { |
| name: 'TorrentDetailView', |
| setup() { |
| const route = useRoute() |
| const router = useRouter() |
| |
| const activeTab = ref('description') |
| const activityTab = ref('seeders') |
| const downloading = ref(false) |
| const isFavorited = ref(false) |
| const submittingComment = ref(false) |
| const newComment = ref('') |
| |
| const torrentInfo = ref({ |
| id: 1, |
| title: '[4K蓝光原盘] 阿凡达:水之道 Avatar: The Way of Water (2022)', |
| category: 'movie', |
| subcategory: '科幻片', |
| uploader: 'MovieMaster', |
| uploadTime: '2025-06-03T10:30:00', |
| size: '85.6 GB', |
| fileCount: 125, |
| seeders: 128, |
| leechers: 45, |
| downloads: 892, |
| coverImage: 'https://example.com/avatar2-cover.jpg', |
| tags: ['4K', '蓝光原盘', '科幻', '詹姆斯·卡梅隆'], |
| description: ` |
| <h3>影片信息</h3> |
| <p><strong>片名:</strong>阿凡达:水之道 / Avatar: The Way of Water</p> |
| <p><strong>年份:</strong>2022</p> |
| <p><strong>导演:</strong>詹姆斯·卡梅隆</p> |
| <p><strong>主演:</strong>萨姆·沃辛顿 / 佐伊·索尔达娜 / 西格妮·韦弗</p> |
| <p><strong>类型:</strong>科幻 / 动作 / 冒险</p> |
| <p><strong>制片国家/地区:</strong>美国</p> |
| <p><strong>语言:</strong>英语</p> |
| <p><strong>上映日期:</strong>2022-12-16</p> |
| <p><strong>片长:</strong>192分钟</p> |
| |
| <h3>影片简介</h3> |
| <p>杰克·萨利和奈蒂莉组建了家庭,他们的孩子也逐渐成长。当危险威胁到他们时,杰克和奈蒂莉必须为彼此而战,为家庭而战,为生存而战。</p> |
| |
| <h3>技术规格</h3> |
| <ul> |
| <li>视频:4K UHD 2160p / HEVC / HDR10</li> |
| <li>音频:Dolby Atmos TrueHD 7.1 / DTS-HD MA 7.1</li> |
| <li>字幕:中文 / 英文</li> |
| <li>片源:4K UHD 蓝光原盘</li> |
| </ul> |
| |
| <h3>下载说明</h3> |
| <p>本资源为4K蓝光原盘,保持了最高的画质和音质。建议使用支持4K播放的设备观看。</p> |
| `, |
| files: [ |
| { name: 'BDMV', type: 'folder', size: '85.6 GB', path: '/' }, |
| { name: 'CERTIFICATE', type: 'folder', size: '2.1 MB', path: '/' }, |
| { 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/' }, |
| { name: 'Avatar.The.Way.of.Water.2022.Extras.mkv', type: 'file', size: '12.4 GB', path: '/BDMV/STREAM/' } |
| ] |
| }) |
| |
| const seedersList = ref([ |
| { username: 'SeedMaster', uploaded: '2.5 TB', downloaded: '850 GB', ratio: '3.02', seedTime: '15天' }, |
| { username: 'MovieFan88', uploaded: '1.8 TB', downloaded: '1.2 TB', ratio: '1.50', seedTime: '8天' }, |
| { username: 'CinemaLover', uploaded: '3.2 TB', downloaded: '900 GB', ratio: '3.56', seedTime: '22天' } |
| ]) |
| |
| const leechersList = ref([ |
| { username: 'NewUser123', progress: 65, downloadSpeed: '15.2 MB/s', eta: '2小时15分' }, |
| { username: 'MovieSeeker', progress: 23, downloadSpeed: '8.7 MB/s', eta: '8小时32分' }, |
| { username: 'FilmCollector', progress: 89, downloadSpeed: '22.1 MB/s', eta: '45分钟' } |
| ]) |
| |
| const comments = ref([ |
| { |
| id: 1, |
| username: 'MovieReviewer', |
| content: '画质非常棒!4K HDR效果惊艳,水下场景美不胜收。感谢分享!', |
| time: '2025-06-03T12:00:00', |
| likes: 15 |
| }, |
| { |
| id: 2, |
| username: 'CinemaExpert', |
| content: '音效也很棒,Dolby Atmos的环绕效果让人身临其境。推荐大家下载!', |
| time: '2025-06-03T11:30:00', |
| likes: 8 |
| } |
| ]) |
| |
| onMounted(() => { |
| const torrentId = route.params.id |
| fetchTorrentDetail(torrentId) |
| }) |
| |
| const fetchTorrentDetail = async (id) => { |
| try { |
| // 模拟API调用 |
| console.log('获取种子详情:', id) |
| // 这里应该调用真实的API |
| } catch (error) { |
| ElMessage.error('获取种子详情失败') |
| router.back() |
| } |
| } |
| |
| const formatDateTime = (dateString) => { |
| const date = new Date(dateString) |
| return date.toLocaleString('zh-CN', { |
| year: 'numeric', |
| month: '2-digit', |
| day: '2-digit', |
| hour: '2-digit', |
| minute: '2-digit' |
| }) |
| } |
| |
| const formatDescription = (description) => { |
| // 简单的HTML清理,实际项目中应该使用专门的库 |
| return description.replace(/\n/g, '<br>') |
| } |
| |
| const getCategoryType = (category) => { |
| const types = { |
| 'movie': 'primary', |
| 'tv': 'info', |
| 'music': 'success', |
| 'software': 'warning', |
| 'game': 'danger' |
| } |
| return types[category] || 'default' |
| } |
| |
| const getCategoryName = (category) => { |
| const names = { |
| 'movie': '电影', |
| 'tv': '电视剧', |
| 'music': '音乐', |
| 'software': '软件', |
| 'game': '游戏' |
| } |
| return names[category] || category |
| } |
| |
| const handleDownload = async () => { |
| downloading.value = true |
| try { |
| // 模拟下载准备过程 |
| await new Promise(resolve => setTimeout(resolve, 1500)) |
| |
| // 实际项目中这里应该下载.torrent文件 |
| const link = document.createElement('a') |
| link.href = '#' // 实际的种子文件下载链接 |
| link.download = `${torrentInfo.value.title}.torrent` |
| link.click() |
| |
| ElMessage.success('种子文件下载完成') |
| } catch (error) { |
| ElMessage.error('下载失败,请稍后重试') |
| } finally { |
| downloading.value = false |
| } |
| } |
| |
| const handleFavorite = () => { |
| isFavorited.value = !isFavorited.value |
| ElMessage.success(isFavorited.value ? '已添加到收藏' : '已取消收藏') |
| } |
| |
| const handleReport = async () => { |
| try { |
| await ElMessageBox.prompt('请说明举报原因', '举报内容', { |
| confirmButtonText: '提交举报', |
| cancelButtonText: '取消', |
| inputType: 'textarea', |
| inputPlaceholder: '请详细说明举报原因...' |
| }) |
| |
| ElMessage.success('举报已提交,我们会尽快处理') |
| } catch { |
| // 用户取消 |
| } |
| } |
| |
| const submitComment = async () => { |
| if (!newComment.value.trim()) { |
| ElMessage.warning('请输入评论内容') |
| return |
| } |
| |
| submittingComment.value = true |
| try { |
| // 模拟提交评论 |
| await new Promise(resolve => setTimeout(resolve, 1000)) |
| |
| const comment = { |
| id: Date.now(), |
| username: localStorage.getItem('username') || '用户', |
| content: newComment.value, |
| time: new Date().toISOString(), |
| likes: 0 |
| } |
| |
| comments.value.unshift(comment) |
| newComment.value = '' |
| |
| ElMessage.success('评论发表成功') |
| } catch (error) { |
| ElMessage.error('发表评论失败') |
| } finally { |
| submittingComment.value = false |
| } |
| } |
| |
| const likeComment = (commentId) => { |
| const comment = comments.value.find(c => c.id === commentId) |
| if (comment) { |
| comment.likes = (comment.likes || 0) + 1 |
| ElMessage.success('点赞成功') |
| } |
| } |
| |
| const replyComment = (commentId) => { |
| // 实现回复功能 |
| ElMessage.info('回复功能开发中...') |
| } |
| |
| return { |
| activeTab, |
| activityTab, |
| downloading, |
| isFavorited, |
| submittingComment, |
| newComment, |
| torrentInfo, |
| seedersList, |
| leechersList, |
| comments, |
| formatDateTime, |
| formatDescription, |
| getCategoryType, |
| getCategoryName, |
| handleDownload, |
| handleFavorite, |
| handleReport, |
| submitComment, |
| likeComment, |
| replyComment, |
| ArrowLeft, |
| Download, |
| Star, |
| Flag, |
| User, |
| Clock, |
| Document, |
| Files, |
| Picture, |
| Folder, |
| Like |
| } |
| } |
| } |
| </script> |
| |
| <style lang="scss" scoped> |
| .torrent-detail-page { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 24px; |
| background: #f5f5f5; |
| min-height: 100vh; |
| } |
| |
| .back-button { |
| margin-bottom: 16px; |
| } |
| |
| .torrent-header { |
| background: #fff; |
| border-radius: 12px; |
| padding: 32px; |
| margin-bottom: 24px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| |
| .header-content { |
| display: flex; |
| gap: 32px; |
| |
| .torrent-cover { |
| flex-shrink: 0; |
| |
| .cover-image { |
| width: 200px; |
| height: 280px; |
| border-radius: 8px; |
| object-fit: cover; |
| } |
| |
| .image-placeholder { |
| width: 200px; |
| height: 280px; |
| background: #f5f5f5; |
| border-radius: 8px; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| color: #999; |
| |
| span { |
| margin-top: 8px; |
| font-size: 14px; |
| } |
| } |
| } |
| |
| .torrent-info { |
| flex: 1; |
| |
| .category-tag { |
| margin-bottom: 16px; |
| |
| .el-tag { |
| margin-right: 8px; |
| } |
| } |
| |
| .torrent-title { |
| font-size: 28px; |
| font-weight: 600; |
| color: #2c3e50; |
| margin: 0 0 16px 0; |
| line-height: 1.3; |
| } |
| |
| .torrent-tags { |
| margin-bottom: 20px; |
| |
| .el-tag { |
| margin: 0 8px 8px 0; |
| } |
| } |
| |
| .torrent-meta { |
| margin-bottom: 20px; |
| |
| .meta-item { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| margin-bottom: 8px; |
| color: #7f8c8d; |
| font-size: 14px; |
| |
| .el-icon { |
| color: #909399; |
| } |
| } |
| } |
| |
| .torrent-stats { |
| display: flex; |
| gap: 32px; |
| margin-bottom: 24px; |
| |
| .stat-item { |
| text-align: center; |
| |
| .stat-number { |
| display: block; |
| font-size: 24px; |
| font-weight: 600; |
| margin-bottom: 4px; |
| |
| &.seeders { color: #67c23a; } |
| &.leechers { color: #f56c6c; } |
| &.downloads { color: #409eff; } |
| } |
| |
| .stat-label { |
| font-size: 14px; |
| color: #909399; |
| } |
| } |
| } |
| |
| .action-buttons { |
| display: flex; |
| gap: 12px; |
| flex-wrap: wrap; |
| } |
| } |
| } |
| } |
| |
| .detail-tabs { |
| background: #fff; |
| border-radius: 12px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| |
| :deep(.el-tabs__content) { |
| padding: 24px; |
| } |
| |
| .description-content { |
| line-height: 1.6; |
| |
| :deep(h3) { |
| color: #2c3e50; |
| font-size: 18px; |
| font-weight: 600; |
| margin: 24px 0 12px 0; |
| |
| &:first-child { |
| margin-top: 0; |
| } |
| } |
| |
| :deep(p) { |
| margin-bottom: 12px; |
| color: #5a6c7d; |
| } |
| |
| :deep(ul) { |
| margin: 12px 0; |
| padding-left: 20px; |
| |
| li { |
| margin-bottom: 8px; |
| color: #5a6c7d; |
| } |
| } |
| |
| .no-description { |
| text-align: center; |
| color: #909399; |
| padding: 40px 0; |
| } |
| } |
| |
| .files-list { |
| .file-name { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| |
| .el-icon { |
| color: #909399; |
| } |
| } |
| } |
| |
| .activity-section { |
| .activity-stats { |
| margin-bottom: 24px; |
| |
| .stats-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); |
| gap: 16px; |
| |
| .stat-card { |
| background: #f8f9fa; |
| padding: 20px; |
| border-radius: 8px; |
| text-align: center; |
| |
| h3 { |
| font-size: 14px; |
| color: #909399; |
| margin: 0 0 8px 0; |
| } |
| |
| .stat-number { |
| font-size: 24px; |
| font-weight: 600; |
| color: #2c3e50; |
| } |
| } |
| } |
| } |
| } |
| |
| .comments-section { |
| .comment-form { |
| margin-bottom: 32px; |
| |
| .comment-actions { |
| margin-top: 12px; |
| text-align: right; |
| } |
| } |
| |
| .comments-list { |
| .comment-item { |
| display: flex; |
| gap: 16px; |
| margin-bottom: 24px; |
| padding-bottom: 24px; |
| border-bottom: 1px solid #f0f0f0; |
| |
| &:last-child { |
| border-bottom: none; |
| margin-bottom: 0; |
| padding-bottom: 0; |
| } |
| |
| .comment-content { |
| flex: 1; |
| |
| .comment-header { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| margin-bottom: 8px; |
| |
| .comment-username { |
| font-weight: 600; |
| color: #2c3e50; |
| } |
| |
| .comment-time { |
| font-size: 12px; |
| color: #909399; |
| } |
| } |
| |
| .comment-text { |
| color: #5a6c7d; |
| line-height: 1.5; |
| margin-bottom: 12px; |
| } |
| |
| .comment-actions { |
| .el-button { |
| padding: 0; |
| margin-right: 16px; |
| |
| .el-icon { |
| margin-right: 4px; |
| } |
| } |
| } |
| } |
| } |
| |
| .no-comments { |
| text-align: center; |
| color: #909399; |
| padding: 40px 0; |
| } |
| } |
| } |
| } |
| |
| // 响应式设计 |
| @media (max-width: 768px) { |
| .torrent-detail-page { |
| padding: 16px; |
| } |
| |
| .torrent-header .header-content { |
| flex-direction: column; |
| text-align: center; |
| |
| .torrent-cover { |
| align-self: center; |
| } |
| |
| .torrent-stats { |
| justify-content: center; |
| } |
| |
| .action-buttons { |
| justify-content: center; |
| } |
| } |
| |
| .activity-section .stats-grid { |
| grid-template-columns: 1fr; |
| } |
| |
| .comment-item { |
| flex-direction: column; |
| gap: 12px; |
| } |
| } |
| </style> |