| <template> |
| <div class="home-page"> |
| <!-- 导航栏 --> |
| <Navbar /> |
| |
| <!-- 主内容区 --> |
| <div class="main-content"> |
| <!-- 欢迎卡片 --> |
| <div class="welcome-card"> |
| <div class="welcome-header"> |
| <h1>欢迎回来,{{ username }}!</h1> |
| <p>当前时间:{{ currentTime }}</p> |
| </div> |
| |
| <!-- 用户统计概览 --> |
| <div class="stats-overview" v-if="userInfo"> |
| <div class="stat-item"> |
| <div class="stat-icon upload"> |
| <el-icon size="24"><Upload /></el-icon> |
| </div> |
| <div class="stat-content"> |
| <h3>{{ formatBytes(userInfo.user.uploaded) }}</h3> |
| <p>总上传</p> |
| </div> |
| </div> |
| |
| <div class="stat-item"> |
| <div class="stat-icon download"> |
| <el-icon size="24"><Download /></el-icon> |
| </div> |
| <div class="stat-content"> |
| <h3>{{ formatBytes(userInfo.user.downloaded) }}</h3> |
| <p>总下载</p> |
| </div> |
| </div> |
| |
| <div class="stat-item"> |
| <div class="stat-icon ratio"> |
| <el-icon size="24"><TrendCharts /></el-icon> |
| </div> |
| <div class="stat-content"> |
| <h3>{{ calculateRatio(userInfo.user.uploaded, userInfo.user.downloaded) }}</h3> |
| <p>分享率</p> |
| </div> |
| </div> |
| |
| <div class="stat-item"> |
| <div class="stat-icon points"> |
| <el-icon size="24"><Star /></el-icon> |
| </div> |
| <div class="stat-content"> |
| <h3>{{ userInfo.user.karma || '0' }}</h3> |
| <p>积分</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <!-- 功能快捷入口 --> |
| <div class="quick-actions"> |
| <h2>快捷操作</h2> |
| <div class="actions-grid"> |
| <div class="action-card" @click="$router.push('/upload')"> |
| <el-icon size="32" color="#67c23a"><Upload /></el-icon> |
| <h3>上传种子</h3> |
| <p>分享你的资源</p> |
| </div> |
| |
| <div class="action-card" @click="$router.push('/torrents')"> |
| <el-icon size="32" color="#409eff"><Search /></el-icon> |
| <h3>浏览种子</h3> |
| <p>发现新内容</p> |
| </div> |
| |
| <div class="action-card" @click="$router.push('/forum')"> |
| <el-icon size="32" color="#e6a23c"><ChatDotRound /></el-icon> |
| <h3>社区论坛</h3> |
| <p>交流讨论</p> |
| </div> |
| |
| <div class="action-card" @click="$router.push('/profile')"> |
| <el-icon size="32" color="#f56c6c"><User /></el-icon> |
| <h3>个人中心</h3> |
| <p>管理账户</p> |
| </div> |
| <div class="action-card" @click="$router.push('/chat')"> |
| <el-icon size="32" color="#8e44ad"><ChatDotRound /></el-icon> |
| <h3>聊天室</h3> |
| <p>实时聊天互动</p> |
| </div> |
| </div> |
| </div> |
| |
| <!-- API连接状态测试 --> |
| <div class="api-status-card"> |
| <h2>API连接状态</h2> |
| <div class="status-items"> |
| <div class="status-item"> |
| <el-icon :color="loginStatusColor"><CircleCheck /></el-icon> |
| <span>登录状态:{{ loginStatusText }}</span> |
| <el-button size="small" @click="checkLoginStatus">检查状态</el-button> |
| </div> |
| |
| <div class="status-item"> |
| <el-icon :color="userInfoStatusColor"><User /></el-icon> |
| <span>用户信息:{{ userInfoStatusText }}</span> |
| <el-button size="small" @click="refreshUserInfo">刷新信息</el-button> |
| </div> |
| </div> |
| |
| <!-- 用户详细信息展示 --> |
| <div class="user-details" v-if="userInfo"> |
| <h3>用户详细信息</h3> |
| <div class="details-grid"> |
| <div class="detail-item"> |
| <label>用户ID:</label> |
| <span>{{ userInfo.user.id }}</span> |
| </div> |
| <div class="detail-item"> |
| <label>用户名:</label> |
| <span>{{ userInfo.user.username }}</span> |
| </div> |
| <div class="detail-item"> |
| <label>邮箱:</label> |
| <span>{{ userInfo.user.email }}</span> |
| </div> |
| <div class="detail-item"> |
| <label>用户组:</label> |
| <span>{{ userInfo.user.group?.displayName || '未知' }}</span> |
| </div> |
| <div class="detail-item"> |
| <label>注册时间:</label> |
| <span>{{ formatDate(userInfo.user.createdAt) }}</span> |
| </div> |
| <div class="detail-item"> |
| <label>个性签名:</label> |
| <span>{{ userInfo.user.signature || '这个用户很懒,还没有个性签名' }}</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </template> |
| |
| <script> |
| import { ref, computed, onMounted, onUnmounted } from 'vue' |
| import { useRouter } from 'vue-router' |
| import { useStore } from 'vuex' |
| import { ElMessage, ElMessageBox } from 'element-plus' |
| import { |
| ArrowDown, |
| User, |
| Setting, |
| SwitchButton, |
| Upload, |
| Download, |
| TrendCharts, |
| Star, |
| Search, |
| ChatDotRound, |
| CircleCheck |
| } from '@element-plus/icons-vue' |
| import Navbar from "@/components/Navbar.vue"; |
| |
| export default { |
| name: 'HomeView', |
| components: { |
| ArrowDown, |
| User, |
| Setting, |
| SwitchButton, |
| Upload, |
| Download, |
| TrendCharts, |
| Star, |
| Search, |
| ChatDotRound, |
| CircleCheck, |
| Navbar |
| }, |
| setup() { |
| const router = useRouter() |
| const store = useStore() |
| |
| const currentTime = ref('') |
| const timeInterval = ref(null) |
| |
| // 从store获取用户信息 |
| const userInfo = computed(() => store.getters['auth/userInfo']) |
| const username = computed(() => store.getters['auth/username']) |
| const userAvatar = computed(() => store.getters['auth/avatar']) |
| const isAuthenticated = computed(() => store.getters['auth/isAuthenticated']) |
| |
| // API状态 |
| const loginStatusColor = computed(() => isAuthenticated.value ? '#67c23a' : '#f56c6c') |
| const loginStatusText = computed(() => isAuthenticated.value ? '已登录' : '未登录') |
| |
| const userInfoStatusColor = computed(() => userInfo.value ? '#67c23a' : '#f56c6c') |
| const userInfoStatusText = computed(() => userInfo.value ? '已获取' : '未获取') |
| |
| // 更新当前时间 |
| const updateCurrentTime = () => { |
| currentTime.value = new Date().toLocaleString('zh-CN') |
| } |
| |
| // 格式化字节数 |
| const formatBytes = (bytes) => { |
| if (!bytes || bytes === 0) return '0 B' |
| |
| const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] |
| const i = Math.floor(Math.log(bytes) / Math.log(1024)) |
| const size = (bytes / Math.pow(1024, i)).toFixed(2) |
| |
| return `${size} ${sizes[i]}` |
| } |
| |
| // 计算分享率 |
| const calculateRatio = (uploaded, downloaded) => { |
| if (!uploaded || !downloaded || downloaded === 0) { |
| return uploaded > 0 ? '∞' : '0.00' |
| } |
| return (uploaded / downloaded).toFixed(2) |
| } |
| |
| // 格式化日期 |
| const formatDate = (timestamp) => { |
| if (!timestamp) return '未知' |
| return new Date(timestamp).toLocaleDateString('zh-CN') |
| } |
| |
| // 检查登录状态 |
| const checkLoginStatus = async () => { |
| try { |
| await store.dispatch('auth/checkLoginStatus') |
| ElMessage.success('登录状态检查完成') |
| } catch (error) { |
| console.error('检查登录状态失败:', error) |
| ElMessage.error('检查登录状态失败') |
| } |
| } |
| |
| // 刷新用户信息 |
| const refreshUserInfo = async () => { |
| try { |
| await store.dispatch('auth/checkLoginStatus') |
| ElMessage.success('用户信息刷新成功') |
| } catch (error) { |
| console.error('刷新用户信息失败:', error) |
| ElMessage.error('刷新用户信息失败') |
| } |
| } |
| |
| // 处理用户菜单命令 |
| const handleUserCommand = async (command) => { |
| switch (command) { |
| case 'profile': |
| router.push('/profile') |
| break |
| case 'settings': |
| ElMessage.info('设置功能开发中...') |
| break |
| case 'logout': |
| try { |
| await ElMessageBox.confirm('确定要退出登录吗?', '提示', { |
| confirmButtonText: '确定', |
| cancelButtonText: '取消', |
| type: 'warning' |
| }) |
| |
| await store.dispatch('auth/logout') |
| router.push('/login') |
| } catch (error) { |
| // 用户取消操作 |
| if (error !== 'cancel') { |
| console.error('退出登录失败:', error) |
| } |
| } |
| break |
| } |
| } |
| |
| onMounted(() => { |
| // 开始时间更新 |
| updateCurrentTime() |
| timeInterval.value = setInterval(updateCurrentTime, 1000) |
| |
| // 检查登录状态 |
| if (!isAuthenticated.value) { |
| checkLoginStatus() |
| } |
| }) |
| |
| onUnmounted(() => { |
| // 清理定时器 |
| if (timeInterval.value) { |
| clearInterval(timeInterval.value) |
| } |
| }) |
| |
| return { |
| currentTime, |
| userInfo, |
| username, |
| userAvatar, |
| isAuthenticated, |
| loginStatusColor, |
| loginStatusText, |
| userInfoStatusColor, |
| userInfoStatusText, |
| formatBytes, |
| calculateRatio, |
| formatDate, |
| checkLoginStatus, |
| refreshUserInfo, |
| handleUserCommand |
| } |
| } |
| } |
| </script> |
| |
| <style lang="scss" scoped> |
| .home-page { |
| min-height: 100vh; |
| background: #f5f5f5; |
| } |
| |
| .navbar { |
| background: #fff; |
| box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); |
| height: 60px; |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 0 24px; |
| position: sticky; |
| top: 0; |
| z-index: 1000; |
| } |
| |
| .navbar-brand { |
| font-size: 20px; |
| font-weight: 700; |
| color: #409eff; |
| text-decoration: none; |
| } |
| |
| .navbar-nav { |
| display: flex; |
| align-items: center; |
| gap: 24px; |
| } |
| |
| .navbar-item { |
| color: #606266; |
| text-decoration: none; |
| font-weight: 500; |
| transition: color 0.3s; |
| |
| &:hover { |
| color: #409eff; |
| } |
| } |
| |
| .navbar-user { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| cursor: pointer; |
| padding: 8px; |
| border-radius: 6px; |
| transition: background-color 0.3s; |
| |
| &:hover { |
| background-color: #f5f7fa; |
| } |
| |
| .username { |
| font-weight: 500; |
| color: #303133; |
| } |
| } |
| |
| .main-content { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 24px; |
| } |
| |
| .welcome-card { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| color: white; |
| border-radius: 12px; |
| padding: 32px; |
| margin-bottom: 24px; |
| |
| .welcome-header { |
| text-align: center; |
| margin-bottom: 24px; |
| |
| h1 { |
| font-size: 28px; |
| margin-bottom: 8px; |
| } |
| |
| p { |
| opacity: 0.9; |
| font-size: 14px; |
| } |
| } |
| |
| .stats-overview { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| gap: 16px; |
| |
| .stat-item { |
| background: rgba(255, 255, 255, 0.1); |
| backdrop-filter: blur(10px); |
| border-radius: 8px; |
| padding: 20px; |
| display: flex; |
| align-items: center; |
| gap: 16px; |
| |
| .stat-icon { |
| width: 48px; |
| height: 48px; |
| border-radius: 50%; |
| background: rgba(255, 255, 255, 0.2); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .stat-content { |
| h3 { |
| font-size: 20px; |
| font-weight: 600; |
| margin: 0 0 4px 0; |
| } |
| |
| p { |
| font-size: 14px; |
| opacity: 0.8; |
| margin: 0; |
| } |
| } |
| } |
| } |
| } |
| |
| .quick-actions { |
| background: #fff; |
| border-radius: 12px; |
| padding: 24px; |
| margin-bottom: 24px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| |
| h2 { |
| font-size: 20px; |
| color: #303133; |
| margin: 0 0 20px 0; |
| } |
| |
| .actions-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| gap: 16px; |
| |
| .action-card { |
| background: #f8f9fa; |
| border-radius: 8px; |
| padding: 24px; |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.3s; |
| border: 2px solid transparent; |
| |
| &:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); |
| border-color: #409eff; |
| } |
| |
| h3 { |
| font-size: 16px; |
| color: #303133; |
| margin: 12px 0 8px 0; |
| } |
| |
| p { |
| font-size: 14px; |
| color: #909399; |
| margin: 0; |
| } |
| } |
| } |
| } |
| |
| .api-status-card { |
| background: #fff; |
| border-radius: 12px; |
| padding: 24px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| |
| h2 { |
| font-size: 20px; |
| color: #303133; |
| margin: 0 0 20px 0; |
| } |
| |
| .status-items { |
| margin-bottom: 24px; |
| |
| .status-item { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 12px 0; |
| border-bottom: 1px solid #ebeef5; |
| |
| &:last-child { |
| border-bottom: none; |
| } |
| |
| span { |
| flex: 1; |
| font-size: 14px; |
| color: #606266; |
| } |
| } |
| } |
| |
| .user-details { |
| border-top: 1px solid #ebeef5; |
| padding-top: 20px; |
| |
| h3 { |
| font-size: 16px; |
| color: #303133; |
| margin: 0 0 16px 0; |
| } |
| |
| .details-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |
| gap: 12px; |
| |
| .detail-item { |
| display: flex; |
| align-items: center; |
| padding: 8px 0; |
| |
| label { |
| font-weight: 500; |
| color: #909399; |
| min-width: 80px; |
| font-size: 14px; |
| } |
| |
| span { |
| color: #606266; |
| font-size: 14px; |
| } |
| } |
| } |
| } |
| } |
| |
| @media (max-width: 768px) { |
| .navbar { |
| padding: 0 16px; |
| |
| .navbar-nav { |
| gap: 16px; |
| } |
| |
| .navbar-user .username { |
| display: none; |
| } |
| } |
| |
| .main-content { |
| padding: 16px; |
| } |
| |
| .welcome-card { |
| padding: 24px 16px; |
| |
| .welcome-header h1 { |
| font-size: 24px; |
| } |
| |
| .stats-overview { |
| grid-template-columns: repeat(2, 1fr); |
| gap: 12px; |
| |
| .stat-item { |
| padding: 16px; |
| |
| .stat-content h3 { |
| font-size: 16px; |
| } |
| } |
| } |
| } |
| |
| .actions-grid { |
| grid-template-columns: repeat(2, 1fr); |
| gap: 12px; |
| |
| .action-card { |
| padding: 16px; |
| |
| h3 { |
| font-size: 14px; |
| } |
| |
| p { |
| font-size: 12px; |
| } |
| } |
| } |
| |
| .details-grid { |
| grid-template-columns: 1fr; |
| } |
| } |
| |
| @media (max-width: 480px) { |
| .stats-overview { |
| grid-template-columns: 1fr; |
| } |
| |
| .actions-grid { |
| grid-template-columns: 1fr; |
| } |
| } |
| </style> |