| <template> |
| <div class="home-page"> |
| <div class="home-container"> |
| <!-- 用户信息和导航 --> |
| <header class="site-header"> |
| <div class="header-content"> |
| <div class="logo-section"> |
| <h2 class="site-logo">🚀 PT Tracker</h2> |
| </div> |
| <div class="user-section"> |
| <div class="user-stats"> |
| <div class="stat-item"> |
| <span class="stat-label">上传:</span> |
| <span class="stat-value">{{ userStats.uploaded }}</span> |
| </div> |
| <div class="stat-item"> |
| <span class="stat-label">下载:</span> |
| <span class="stat-value">{{ userStats.downloaded }}</span> |
| </div> |
| <div class="stat-item"> |
| <span class="stat-label">分享率:</span> |
| <span class="stat-value ratio" :class="getRatioClass(userStats.ratio)"> |
| {{ userStats.ratio }} |
| </span> |
| </div> |
| </div> |
| <div class="user-info"> |
| <el-avatar :size="40" :icon="UserFilled" /> |
| <span class="username">{{ userInfo.username }}</span> |
| <el-dropdown @command="handleUserCommand"> |
| <el-icon class="dropdown-icon"><ArrowDown /></el-icon> |
| <template #dropdown> |
| <el-dropdown-menu> |
| <el-dropdown-item command="profile">个人资料</el-dropdown-item> |
| <el-dropdown-item command="settings">设置</el-dropdown-item> |
| <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item> |
| </el-dropdown-menu> |
| </template> |
| </el-dropdown> |
| </div> |
| </div> |
| </div> |
| </header> |
| |
| <!-- 主要内容区域 --> |
| <main class="main-content"> |
| <!-- 统计概览 --> |
| <section class="stats-overview"> |
| <div class="stats-grid"> |
| <div class="stat-card"> |
| <div class="stat-icon"> |
| <el-icon size="32" color="#67c23a"><Download /></el-icon> |
| </div> |
| <div class="stat-info"> |
| <h3>{{ siteStats.totalTorrents }}</h3> |
| <p>种子总数</p> |
| </div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-icon"> |
| <el-icon size="32" color="#409eff"><User /></el-icon> |
| </div> |
| <div class="stat-info"> |
| <h3>{{ siteStats.totalUsers }}</h3> |
| <p>注册用户</p> |
| </div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-icon"> |
| <el-icon size="32" color="#f56c6c"><Upload /></el-icon> |
| </div> |
| <div class="stat-info"> |
| <h3>{{ siteStats.totalSize }}</h3> |
| <p>数据总量</p> |
| </div> |
| </div> |
| </div> |
| </section> |
| |
| <!-- 快速操作 --> |
| <section class="quick-actions"> |
| <h2 class="section-title">快速操作</h2> |
| <div class="action-grid"> |
| <div class="action-card" @click="navigateTo('/torrents')"> |
| <el-icon size="48" color="#409eff"><Search /></el-icon> |
| <h3>浏览种子</h3> |
| <p>搜索和浏览所有可用的种子资源</p> |
| </div> |
| <div class="action-card" @click="navigateTo('/upload')"> |
| <el-icon size="48" color="#67c23a"><Upload /></el-icon> |
| <h3>上传资源</h3> |
| <p>分享你的资源,为社区做贡献</p> |
| </div> |
| <div class="action-card" @click="navigateTo('/forum')"> |
| <el-icon size="48" color="#e6a23c"><ChatDotRound /></el-icon> |
| <h3>论坛交流</h3> |
| <p>与其他用户交流讨论</p> |
| </div> |
| <div class="action-card" @click="navigateTo('/rankings')"> |
| <el-icon size="48" color="#f56c6c"><TrophyBase /></el-icon> |
| <h3>排行榜</h3> |
| <p>查看用户和资源排行</p> |
| </div> |
| </div> |
| </section> |
| |
| <!-- 最新种子 --> |
| <section class="latest-torrents"> |
| <div class="section-header"> |
| <h2 class="section-title">最新种子</h2> |
| <el-button type="primary" text @click="navigateTo('/torrents')"> |
| 查看更多 <el-icon><ArrowRight /></el-icon> |
| </el-button> |
| </div> |
| <div class="torrents-list"> |
| <div |
| v-for="torrent in latestTorrents" |
| :key="torrent.id" |
| class="torrent-item" |
| @click="navigateTo(`/torrent/${torrent.id}`)" |
| > |
| <div class="torrent-info"> |
| <div class="torrent-category"> |
| <el-tag :type="getCategoryType(torrent.category)" size="small"> |
| {{ torrent.category }} |
| </el-tag> |
| </div> |
| <h4 class="torrent-title">{{ torrent.title }}</h4> |
| <div class="torrent-meta"> |
| <span class="meta-item"> |
| <el-icon><User /></el-icon> |
| {{ torrent.uploader }} |
| </span> |
| <span class="meta-item"> |
| <el-icon><Clock /></el-icon> |
| {{ formatTime(torrent.uploadTime) }} |
| </span> |
| <span class="meta-item"> |
| <el-icon><Coin /></el-icon> |
| {{ torrent.size }} |
| </span> |
| </div> |
| </div> |
| <div class="torrent-stats"> |
| <div class="stat-group"> |
| <span class="stat-number seeders">{{ torrent.seeders }}</span> |
| <span class="stat-label">做种</span> |
| </div> |
| <div class="stat-group"> |
| <span class="stat-number leechers">{{ torrent.leechers }}</span> |
| <span class="stat-label">下载</span> |
| </div> |
| <div class="stat-group"> |
| <span class="stat-number">{{ torrent.downloads }}</span> |
| <span class="stat-label">完成</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| </section> |
| |
| <!-- 个人活动 --> |
| <section class="user-activity"> |
| <h2 class="section-title">我的活动</h2> |
| <div class="activity-grid"> |
| <div class="activity-card"> |
| <h3>我的种子</h3> |
| <div class="activity-stats"> |
| <div class="activity-item"> |
| <span class="activity-label">正在做种:</span> |
| <span class="activity-value">{{ userActivity.seeding }}</span> |
| </div> |
| <div class="activity-item"> |
| <span class="activity-label">正在下载:</span> |
| <span class="activity-value">{{ userActivity.downloading }}</span> |
| </div> |
| <div class="activity-item"> |
| <span class="activity-label">已上传:</span> |
| <span class="activity-value">{{ userActivity.uploaded }}</span> |
| </div> |
| </div> |
| <el-button type="primary" size="small" @click="navigateTo('/my-torrents')"> |
| 查看详情 |
| </el-button> |
| </div> |
| |
| <div class="activity-card"> |
| <h3>邀请管理</h3> |
| <div class="activity-stats"> |
| <div class="activity-item"> |
| <span class="activity-label">可用邀请:</span> |
| <span class="activity-value">{{ userActivity.invitations }}</span> |
| </div> |
| <div class="activity-item"> |
| <span class="activity-label">已邀请:</span> |
| <span class="activity-value">{{ userActivity.invited }}</span> |
| </div> |
| </div> |
| <el-button type="success" size="small" @click="navigateTo('/invitations')"> |
| 管理邀请 |
| </el-button> |
| </div> |
| </div> |
| </section> |
| </main> |
| </div> |
| </div> |
| </template> |
| |
| <script> |
| import { ref, onMounted } from 'vue' |
| import { useRouter } from 'vue-router' |
| import { ElMessage, ElMessageBox } from 'element-plus' |
| import { |
| UserFilled, |
| ArrowDown, |
| Download, |
| Upload, |
| ChatDotRound, |
| Search, |
| User, |
| Connection, |
| TrophyBase, |
| ArrowRight, |
| Clock, |
| Coin |
| } from '@element-plus/icons-vue' |
| |
| export default { |
| name: 'HomeView', |
| components: { |
| UserFilled, |
| ArrowDown |
| }, |
| setup() { |
| const router = useRouter() |
| |
| const userInfo = ref({ |
| username: '', |
| loginTime: '' |
| }) |
| |
| const userStats = ref({ |
| uploaded: '128.5 GB', |
| downloaded: '45.2 GB', |
| ratio: '2.84' |
| }) |
| |
| const siteStats = ref({ |
| totalTorrents: '12,458', |
| totalUsers: '8,924', |
| onlineUsers: '342', |
| totalSize: '45.2 TB' |
| }) |
| |
| const userActivity = ref({ |
| seeding: 15, |
| downloading: 3, |
| uploaded: 8, |
| invitations: 5, |
| invited: 12 |
| }) |
| |
| const latestTorrents = ref([ |
| { |
| id: 1, |
| title: '[4K蓝光原盘] 阿凡达:水之道 Avatar: The Way of Water (2022)', |
| category: '电影', |
| uploader: 'MovieMaster', |
| uploadTime: '2025-06-03T10:30:00', |
| size: '85.6 GB', |
| seeders: 128, |
| leechers: 45, |
| downloads: 892 |
| }, |
| { |
| id: 2, |
| title: '[FLAC] Taylor Swift - Midnights (Deluxe Edition) [2022]', |
| category: '音乐', |
| uploader: 'MusicLover', |
| uploadTime: '2025-06-03T09:15:00', |
| size: '1.2 GB', |
| seeders: 67, |
| leechers: 12, |
| downloads: 456 |
| }, |
| { |
| id: 3, |
| title: '[合集] Adobe Creative Suite 2025 完整版', |
| category: '软件', |
| uploader: 'TechGuru', |
| uploadTime: '2025-06-03T08:45:00', |
| size: '12.8 GB', |
| seeders: 234, |
| leechers: 89, |
| downloads: 1205 |
| } |
| ]) |
| |
| onMounted(() => { |
| userInfo.value = { |
| username: localStorage.getItem('username') || '用户', |
| loginTime: localStorage.getItem('loginTime') || '' |
| } |
| |
| // 模拟获取用户数据 |
| fetchUserData() |
| }) |
| |
| const fetchUserData = () => { |
| // 这里应该是API调用,现在使用模拟数据 |
| console.log('获取用户数据...') |
| } |
| |
| const formatTime = (timeString) => { |
| if (!timeString) return '' |
| const date = new Date(timeString) |
| const now = new Date() |
| const diff = now - date |
| const hours = Math.floor(diff / (1000 * 60 * 60)) |
| |
| if (hours < 1) return '刚刚' |
| if (hours < 24) return `${hours}小时前` |
| const days = Math.floor(hours / 24) |
| return `${days}天前` |
| } |
| |
| const getRatioClass = (ratio) => { |
| const r = parseFloat(ratio) |
| if (r >= 2) return 'excellent' |
| if (r >= 1) return 'good' |
| return 'warning' |
| } |
| |
| const getCategoryType = (category) => { |
| const types = { |
| '电影': 'primary', |
| '音乐': 'success', |
| '软件': 'warning', |
| '游戏': 'danger', |
| '电视剧': 'info' |
| } |
| return types[category] || 'default' |
| } |
| |
| const navigateTo = (path) => { |
| router.push(path) |
| } |
| |
| const handleUserCommand = async (command) => { |
| switch (command) { |
| case 'profile': |
| navigateTo('/profile') |
| break |
| case 'settings': |
| navigateTo('/settings') |
| break |
| case 'logout': |
| await handleLogout() |
| break |
| } |
| } |
| |
| const handleLogout = async () => { |
| try { |
| await ElMessageBox.confirm( |
| '确定要退出登录吗?', |
| '提示', |
| { |
| confirmButtonText: '确定', |
| cancelButtonText: '取消', |
| type: 'warning' |
| } |
| ) |
| |
| localStorage.removeItem('isLoggedIn') |
| localStorage.removeItem('username') |
| localStorage.removeItem('loginTime') |
| localStorage.removeItem('rememberLogin') |
| |
| ElMessage.success('已安全退出') |
| router.push('/login') |
| |
| } catch { |
| // 用户取消退出 |
| } |
| } |
| |
| return { |
| userInfo, |
| userStats, |
| siteStats, |
| userActivity, |
| latestTorrents, |
| formatTime, |
| getRatioClass, |
| getCategoryType, |
| navigateTo, |
| handleUserCommand, |
| handleLogout, |
| Download, |
| Upload, |
| ChatDotRound, |
| Search, |
| User, |
| Connection, |
| TrophyBase, |
| ArrowRight, |
| Clock, |
| Coin |
| } |
| } |
| } |
| </script> |
| |
| <style lang="scss" scoped> |
| .home-page { |
| min-height: 100vh; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| padding: 0; |
| } |
| |
| .home-container { |
| max-width: 1200px; |
| margin: 0 auto; |
| background: #fff; |
| min-height: 100vh; |
| } |
| |
| // 网站头部 |
| .site-header { |
| background: #fff; |
| box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); |
| position: sticky; |
| top: 0; |
| z-index: 100; |
| |
| .header-content { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 16px 24px; |
| |
| .logo-section { |
| .site-logo { |
| margin: 0; |
| font-size: 24px; |
| font-weight: bold; |
| color: #667eea; |
| } |
| } |
| |
| .user-section { |
| display: flex; |
| align-items: center; |
| gap: 24px; |
| |
| .user-stats { |
| display: flex; |
| gap: 16px; |
| |
| .stat-item { |
| font-size: 14px; |
| |
| .stat-label { |
| color: #909399; |
| margin-right: 4px; |
| } |
| |
| .stat-value { |
| font-weight: 600; |
| color: #2c3e50; |
| |
| &.ratio { |
| &.excellent { color: #67c23a; } |
| &.good { color: #e6a23c; } |
| &.warning { color: #f56c6c; } |
| } |
| } |
| } |
| } |
| |
| .user-info { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| cursor: pointer; |
| |
| .username { |
| font-weight: 500; |
| color: #2c3e50; |
| } |
| |
| .dropdown-icon { |
| color: #909399; |
| transition: transform 0.3s ease; |
| |
| &:hover { |
| transform: rotate(180deg); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // 主要内容 |
| .main-content { |
| padding: 24px; |
| } |
| |
| .section-title { |
| font-size: 20px; |
| font-weight: 600; |
| color: #2c3e50; |
| margin-bottom: 16px; |
| } |
| |
| .section-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 16px; |
| } |
| |
| // 统计概览 |
| .stats-overview { |
| margin-bottom: 32px; |
| |
| .stats-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| gap: 16px; |
| |
| .stat-card { |
| background: #fff; |
| border-radius: 12px; |
| padding: 24px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| border: 1px solid #f0f0f0; |
| display: flex; |
| align-items: center; |
| gap: 16px; |
| transition: all 0.3s ease; |
| |
| &:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); |
| } |
| |
| .stat-info { |
| h3 { |
| font-size: 24px; |
| font-weight: bold; |
| color: #2c3e50; |
| margin: 0 0 4px 0; |
| } |
| |
| p { |
| font-size: 14px; |
| color: #909399; |
| margin: 0; |
| } |
| } |
| } |
| } |
| } |
| |
| // 快速操作 |
| .quick-actions { |
| margin-bottom: 32px; |
| |
| .action-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); |
| gap: 20px; |
| |
| .action-card { |
| background: #fff; |
| border-radius: 12px; |
| padding: 32px 24px; |
| text-align: center; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| border: 1px solid #f0f0f0; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| |
| &:hover { |
| transform: translateY(-4px); |
| box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15); |
| } |
| |
| h3 { |
| font-size: 18px; |
| font-weight: 600; |
| color: #2c3e50; |
| margin: 16px 0 8px 0; |
| } |
| |
| p { |
| font-size: 14px; |
| color: #7f8c8d; |
| line-height: 1.5; |
| margin: 0; |
| } |
| } |
| } |
| } |
| |
| // 最新种子 |
| .latest-torrents { |
| margin-bottom: 32px; |
| |
| .torrents-list { |
| background: #fff; |
| border-radius: 12px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| border: 1px solid #f0f0f0; |
| overflow: hidden; |
| |
| .torrent-item { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 20px 24px; |
| border-bottom: 1px solid #f5f5f5; |
| cursor: pointer; |
| transition: background-color 0.3s ease; |
| |
| &:hover { |
| background-color: #f8f9fa; |
| } |
| |
| &:last-child { |
| border-bottom: none; |
| } |
| |
| .torrent-info { |
| flex: 1; |
| |
| .torrent-category { |
| margin-bottom: 8px; |
| } |
| |
| .torrent-title { |
| font-size: 16px; |
| font-weight: 500; |
| color: #2c3e50; |
| margin: 0 0 8px 0; |
| line-height: 1.4; |
| } |
| |
| .torrent-meta { |
| display: flex; |
| gap: 16px; |
| |
| .meta-item { |
| display: flex; |
| align-items: center; |
| gap: 4px; |
| font-size: 12px; |
| color: #909399; |
| |
| .el-icon { |
| font-size: 12px; |
| } |
| } |
| } |
| } |
| |
| .torrent-stats { |
| display: flex; |
| gap: 24px; |
| |
| .stat-group { |
| text-align: center; |
| |
| .stat-number { |
| display: block; |
| font-size: 16px; |
| font-weight: 600; |
| |
| &.seeders { color: #67c23a; } |
| &.leechers { color: #f56c6c; } |
| } |
| |
| .stat-label { |
| font-size: 12px; |
| color: #909399; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // 用户活动 |
| .user-activity { |
| .activity-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); |
| gap: 20px; |
| |
| .activity-card { |
| background: #fff; |
| border-radius: 12px; |
| padding: 24px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| border: 1px solid #f0f0f0; |
| |
| h3 { |
| font-size: 18px; |
| font-weight: 600; |
| color: #2c3e50; |
| margin: 0 0 16px 0; |
| } |
| |
| .activity-stats { |
| margin-bottom: 16px; |
| |
| .activity-item { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 8px 0; |
| |
| .activity-label { |
| font-size: 14px; |
| color: #7f8c8d; |
| } |
| |
| .activity-value { |
| font-size: 14px; |
| font-weight: 600; |
| color: #2c3e50; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // 响应式设计 |
| @media (max-width: 768px) { |
| .header-content { |
| flex-direction: column; |
| gap: 16px; |
| |
| .user-stats { |
| flex-direction: column; |
| gap: 8px; |
| text-align: center; |
| } |
| } |
| |
| .main-content { |
| padding: 16px; |
| } |
| |
| .stats-grid, |
| .action-grid, |
| .activity-grid { |
| grid-template-columns: 1fr; |
| } |
| |
| .torrent-item { |
| flex-direction: column; |
| align-items: flex-start; |
| gap: 16px; |
| |
| .torrent-stats { |
| width: 100%; |
| justify-content: space-around; |
| } |
| } |
| } |
| </style> |