<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> |