blob: 7a728b11894552ce46a4843bba5f53340f8d1834 [file] [log] [blame]
Xing Jinwenff16b1e2025-06-05 00:29:26 +08001<template>
2 <div class="home-page">
3 <!-- 导航栏 -->
4 <div class="navbar">
5 <router-link to="/home" class="navbar-brand">PT Tracker</router-link>
6 <div class="navbar-nav">
7 <router-link to="/home" class="navbar-item">首页</router-link>
8 <router-link to="/torrents" class="navbar-item">种子</router-link>
9 <router-link to="/forum" class="navbar-item">论坛</router-link>
10 <el-dropdown @command="handleUserCommand">
11 <span class="navbar-user">
12 <el-avatar :size="32" :src="userAvatar">
13 {{ username.charAt(0).toUpperCase() }}
14 </el-avatar>
15 <span class="username">{{ username }}</span>
16 <el-icon><ArrowDown /></el-icon>
17 </span>
18 <template #dropdown>
19 <el-dropdown-menu>
20 <el-dropdown-item command="profile">
21 <el-icon><User /></el-icon>
22 个人资料
23 </el-dropdown-item>
24 <el-dropdown-item command="settings">
25 <el-icon><Setting /></el-icon>
26 设置
27 </el-dropdown-item>
28 <el-dropdown-item divided command="logout">
29 <el-icon><SwitchButton /></el-icon>
30 退出登录
31 </el-dropdown-item>
32 </el-dropdown-menu>
33 </template>
34 </el-dropdown>
35 </div>
36 </div>
37
38 <!-- 主内容区 -->
39 <div class="main-content">
40 <!-- 欢迎卡片 -->
41 <div class="welcome-card">
42 <div class="welcome-header">
43 <h1>欢迎回来,{{ username }}!</h1>
44 <p>当前时间:{{ currentTime }}</p>
45 </div>
46
47 <!-- 用户统计概览 -->
48 <div class="stats-overview" v-if="userInfo">
49 <div class="stat-item">
50 <div class="stat-icon upload">
51 <el-icon size="24"><Upload /></el-icon>
52 </div>
53 <div class="stat-content">
54 <h3>{{ formatBytes(userInfo.user.uploaded) }}</h3>
55 <p>总上传</p>
56 </div>
57 </div>
58
59 <div class="stat-item">
60 <div class="stat-icon download">
61 <el-icon size="24"><Download /></el-icon>
62 </div>
63 <div class="stat-content">
64 <h3>{{ formatBytes(userInfo.user.downloaded) }}</h3>
65 <p>总下载</p>
66 </div>
67 </div>
68
69 <div class="stat-item">
70 <div class="stat-icon ratio">
71 <el-icon size="24"><TrendCharts /></el-icon>
72 </div>
73 <div class="stat-content">
74 <h3>{{ calculateRatio(userInfo.user.uploaded, userInfo.user.downloaded) }}</h3>
75 <p>分享率</p>
76 </div>
77 </div>
78
79 <div class="stat-item">
80 <div class="stat-icon points">
81 <el-icon size="24"><Star /></el-icon>
82 </div>
83 <div class="stat-content">
84 <h3>{{ userInfo.user.karma || '0' }}</h3>
85 <p>积分</p>
86 </div>
87 </div>
88 </div>
89 </div>
90
91 <!-- 功能快捷入口 -->
92 <div class="quick-actions">
93 <h2>快捷操作</h2>
94 <div class="actions-grid">
95 <div class="action-card" @click="$router.push('/upload')">
96 <el-icon size="32" color="#67c23a"><Upload /></el-icon>
97 <h3>上传种子</h3>
98 <p>分享你的资源</p>
99 </div>
100
101 <div class="action-card" @click="$router.push('/torrents')">
102 <el-icon size="32" color="#409eff"><Search /></el-icon>
103 <h3>浏览种子</h3>
104 <p>发现新内容</p>
105 </div>
106
107 <div class="action-card" @click="$router.push('/forum')">
108 <el-icon size="32" color="#e6a23c"><ChatDotRound /></el-icon>
109 <h3>社区论坛</h3>
110 <p>交流讨论</p>
111 </div>
112
113 <div class="action-card" @click="$router.push('/profile')">
114 <el-icon size="32" color="#f56c6c"><User /></el-icon>
115 <h3>个人中心</h3>
116 <p>管理账户</p>
117 </div>
118 </div>
119 </div>
120
121 <!-- API连接状态测试 -->
122 <div class="api-status-card">
123 <h2>API连接状态</h2>
124 <div class="status-items">
125 <div class="status-item">
126 <el-icon :color="loginStatusColor"><CircleCheck /></el-icon>
127 <span>登录状态:{{ loginStatusText }}</span>
128 <el-button size="small" @click="checkLoginStatus">检查状态</el-button>
129 </div>
130
131 <div class="status-item">
132 <el-icon :color="userInfoStatusColor"><User /></el-icon>
133 <span>用户信息:{{ userInfoStatusText }}</span>
134 <el-button size="small" @click="refreshUserInfo">刷新信息</el-button>
135 </div>
136 </div>
137
138 <!-- 用户详细信息展示 -->
139 <div class="user-details" v-if="userInfo">
140 <h3>用户详细信息</h3>
141 <div class="details-grid">
142 <div class="detail-item">
143 <label>用户ID:</label>
144 <span>{{ userInfo.user.id }}</span>
145 </div>
146 <div class="detail-item">
147 <label>用户名:</label>
148 <span>{{ userInfo.user.username }}</span>
149 </div>
150 <div class="detail-item">
151 <label>邮箱:</label>
152 <span>{{ userInfo.user.email }}</span>
153 </div>
154 <div class="detail-item">
155 <label>用户组:</label>
156 <span>{{ userInfo.user.group?.displayName || '未知' }}</span>
157 </div>
158 <div class="detail-item">
159 <label>注册时间:</label>
160 <span>{{ formatDate(userInfo.user.createdAt) }}</span>
161 </div>
162 <div class="detail-item">
163 <label>个性签名:</label>
164 <span>{{ userInfo.user.signature || '这个用户很懒,还没有个性签名' }}</span>
165 </div>
166 </div>
167 </div>
168 </div>
169 </div>
170 </div>
171</template>
172
173<script>
174import { ref, computed, onMounted, onUnmounted } from 'vue'
175import { useRouter } from 'vue-router'
176import { useStore } from 'vuex'
177import { ElMessage, ElMessageBox } from 'element-plus'
178import {
179 ArrowDown,
180 User,
181 Setting,
182 SwitchButton,
183 Upload,
184 Download,
185 TrendCharts,
186 Star,
187 Search,
188 ChatDotRound,
189 CircleCheck
190} from '@element-plus/icons-vue'
191
192export default {
193 name: 'HomeView',
194 components: {
195 ArrowDown,
196 User,
197 Setting,
198 SwitchButton,
199 Upload,
200 Download,
201 TrendCharts,
202 Star,
203 Search,
204 ChatDotRound,
205 CircleCheck
206 },
207 setup() {
208 const router = useRouter()
209 const store = useStore()
210
211 const currentTime = ref('')
212 const timeInterval = ref(null)
213
214 // 从store获取用户信息
215 const userInfo = computed(() => store.getters['auth/userInfo'])
216 const username = computed(() => store.getters['auth/username'])
217 const userAvatar = computed(() => store.getters['auth/avatar'])
218 const isAuthenticated = computed(() => store.getters['auth/isAuthenticated'])
219
220 // API状态
221 const loginStatusColor = computed(() => isAuthenticated.value ? '#67c23a' : '#f56c6c')
222 const loginStatusText = computed(() => isAuthenticated.value ? '已登录' : '未登录')
223
224 const userInfoStatusColor = computed(() => userInfo.value ? '#67c23a' : '#f56c6c')
225 const userInfoStatusText = computed(() => userInfo.value ? '已获取' : '未获取')
226
227 // 更新当前时间
228 const updateCurrentTime = () => {
229 currentTime.value = new Date().toLocaleString('zh-CN')
230 }
231
232 // 格式化字节数
233 const formatBytes = (bytes) => {
234 if (!bytes || bytes === 0) return '0 B'
235
236 const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
237 const i = Math.floor(Math.log(bytes) / Math.log(1024))
238 const size = (bytes / Math.pow(1024, i)).toFixed(2)
239
240 return `${size} ${sizes[i]}`
241 }
242
243 // 计算分享率
244 const calculateRatio = (uploaded, downloaded) => {
245 if (!uploaded || !downloaded || downloaded === 0) {
246 return uploaded > 0 ? '∞' : '0.00'
247 }
248 return (uploaded / downloaded).toFixed(2)
249 }
250
251 // 格式化日期
252 const formatDate = (timestamp) => {
253 if (!timestamp) return '未知'
254 return new Date(timestamp).toLocaleDateString('zh-CN')
255 }
256
257 // 检查登录状态
258 const checkLoginStatus = async () => {
259 try {
260 await store.dispatch('auth/checkLoginStatus')
261 ElMessage.success('登录状态检查完成')
262 } catch (error) {
263 console.error('检查登录状态失败:', error)
264 ElMessage.error('检查登录状态失败')
265 }
266 }
267
268 // 刷新用户信息
269 const refreshUserInfo = async () => {
270 try {
271 await store.dispatch('auth/checkLoginStatus')
272 ElMessage.success('用户信息刷新成功')
273 } catch (error) {
274 console.error('刷新用户信息失败:', error)
275 ElMessage.error('刷新用户信息失败')
276 }
277 }
278
279 // 处理用户菜单命令
280 const handleUserCommand = async (command) => {
281 switch (command) {
282 case 'profile':
283 router.push('/profile')
284 break
285 case 'settings':
286 ElMessage.info('设置功能开发中...')
287 break
288 case 'logout':
289 try {
290 await ElMessageBox.confirm('确定要退出登录吗?', '提示', {
291 confirmButtonText: '确定',
292 cancelButtonText: '取消',
293 type: 'warning'
294 })
295
296 await store.dispatch('auth/logout')
297 router.push('/login')
298 } catch (error) {
299 // 用户取消操作
300 if (error !== 'cancel') {
301 console.error('退出登录失败:', error)
302 }
303 }
304 break
305 }
306 }
307
308 onMounted(() => {
309 // 开始时间更新
310 updateCurrentTime()
311 timeInterval.value = setInterval(updateCurrentTime, 1000)
312
313 // 检查登录状态
314 if (!isAuthenticated.value) {
315 checkLoginStatus()
316 }
317 })
318
319 onUnmounted(() => {
320 // 清理定时器
321 if (timeInterval.value) {
322 clearInterval(timeInterval.value)
323 }
324 })
325
326 return {
327 currentTime,
328 userInfo,
329 username,
330 userAvatar,
331 isAuthenticated,
332 loginStatusColor,
333 loginStatusText,
334 userInfoStatusColor,
335 userInfoStatusText,
336 formatBytes,
337 calculateRatio,
338 formatDate,
339 checkLoginStatus,
340 refreshUserInfo,
341 handleUserCommand
342 }
343 }
344}
345</script>
346
347<style lang="scss" scoped>
348.home-page {
349 min-height: 100vh;
350 background: #f5f5f5;
351}
352
353.navbar {
354 background: #fff;
355 box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
356 height: 60px;
357 display: flex;
358 align-items: center;
359 justify-content: space-between;
360 padding: 0 24px;
361 position: sticky;
362 top: 0;
363 z-index: 1000;
364}
365
366.navbar-brand {
367 font-size: 20px;
368 font-weight: 700;
369 color: #409eff;
370 text-decoration: none;
371}
372
373.navbar-nav {
374 display: flex;
375 align-items: center;
376 gap: 24px;
377}
378
379.navbar-item {
380 color: #606266;
381 text-decoration: none;
382 font-weight: 500;
383 transition: color 0.3s;
384
385 &:hover {
386 color: #409eff;
387 }
388}
389
390.navbar-user {
391 display: flex;
392 align-items: center;
393 gap: 8px;
394 cursor: pointer;
395 padding: 8px;
396 border-radius: 6px;
397 transition: background-color 0.3s;
398
399 &:hover {
400 background-color: #f5f7fa;
401 }
402
403 .username {
404 font-weight: 500;
405 color: #303133;
406 }
407}
408
409.main-content {
410 max-width: 1200px;
411 margin: 0 auto;
412 padding: 24px;
413}
414
415.welcome-card {
416 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
417 color: white;
418 border-radius: 12px;
419 padding: 32px;
420 margin-bottom: 24px;
421
422 .welcome-header {
423 text-align: center;
424 margin-bottom: 24px;
425
426 h1 {
427 font-size: 28px;
428 margin-bottom: 8px;
429 }
430
431 p {
432 opacity: 0.9;
433 font-size: 14px;
434 }
435 }
436
437 .stats-overview {
438 display: grid;
439 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
440 gap: 16px;
441
442 .stat-item {
443 background: rgba(255, 255, 255, 0.1);
444 backdrop-filter: blur(10px);
445 border-radius: 8px;
446 padding: 20px;
447 display: flex;
448 align-items: center;
449 gap: 16px;
450
451 .stat-icon {
452 width: 48px;
453 height: 48px;
454 border-radius: 50%;
455 background: rgba(255, 255, 255, 0.2);
456 display: flex;
457 align-items: center;
458 justify-content: center;
459 }
460
461 .stat-content {
462 h3 {
463 font-size: 20px;
464 font-weight: 600;
465 margin: 0 0 4px 0;
466 }
467
468 p {
469 font-size: 14px;
470 opacity: 0.8;
471 margin: 0;
472 }
473 }
474 }
475 }
476}
477
478.quick-actions {
479 background: #fff;
480 border-radius: 12px;
481 padding: 24px;
482 margin-bottom: 24px;
483 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
484
485 h2 {
486 font-size: 20px;
487 color: #303133;
488 margin: 0 0 20px 0;
489 }
490
491 .actions-grid {
492 display: grid;
493 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
494 gap: 16px;
495
496 .action-card {
497 background: #f8f9fa;
498 border-radius: 8px;
499 padding: 24px;
500 text-align: center;
501 cursor: pointer;
502 transition: all 0.3s;
503 border: 2px solid transparent;
504
505 &:hover {
506 transform: translateY(-2px);
507 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
508 border-color: #409eff;
509 }
510
511 h3 {
512 font-size: 16px;
513 color: #303133;
514 margin: 12px 0 8px 0;
515 }
516
517 p {
518 font-size: 14px;
519 color: #909399;
520 margin: 0;
521 }
522 }
523 }
524}
525
526.api-status-card {
527 background: #fff;
528 border-radius: 12px;
529 padding: 24px;
530 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
531
532 h2 {
533 font-size: 20px;
534 color: #303133;
535 margin: 0 0 20px 0;
536 }
537
538 .status-items {
539 margin-bottom: 24px;
540
541 .status-item {
542 display: flex;
543 align-items: center;
544 gap: 12px;
545 padding: 12px 0;
546 border-bottom: 1px solid #ebeef5;
547
548 &:last-child {
549 border-bottom: none;
550 }
551
552 span {
553 flex: 1;
554 font-size: 14px;
555 color: #606266;
556 }
557 }
558 }
559
560 .user-details {
561 border-top: 1px solid #ebeef5;
562 padding-top: 20px;
563
564 h3 {
565 font-size: 16px;
566 color: #303133;
567 margin: 0 0 16px 0;
568 }
569
570 .details-grid {
571 display: grid;
572 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
573 gap: 12px;
574
575 .detail-item {
576 display: flex;
577 align-items: center;
578 padding: 8px 0;
579
580 label {
581 font-weight: 500;
582 color: #909399;
583 min-width: 80px;
584 font-size: 14px;
585 }
586
587 span {
588 color: #606266;
589 font-size: 14px;
590 }
591 }
592 }
593 }
594}
595
596@media (max-width: 768px) {
597 .navbar {
598 padding: 0 16px;
599
600 .navbar-nav {
601 gap: 16px;
602 }
603
604 .navbar-user .username {
605 display: none;
606 }
607 }
608
609 .main-content {
610 padding: 16px;
611 }
612
613 .welcome-card {
614 padding: 24px 16px;
615
616 .welcome-header h1 {
617 font-size: 24px;
618 }
619
620 .stats-overview {
621 grid-template-columns: repeat(2, 1fr);
622 gap: 12px;
623
624 .stat-item {
625 padding: 16px;
626
627 .stat-content h3 {
628 font-size: 16px;
629 }
630 }
631 }
632 }
633
634 .actions-grid {
635 grid-template-columns: repeat(2, 1fr);
636 gap: 12px;
637
638 .action-card {
639 padding: 16px;
640
641 h3 {
642 font-size: 14px;
643 }
644
645 p {
646 font-size: 12px;
647 }
648 }
649 }
650
651 .details-grid {
652 grid-template-columns: 1fr;
653 }
654}
655
656@media (max-width: 480px) {
657 .stats-overview {
658 grid-template-columns: 1fr;
659 }
660
661 .actions-grid {
662 grid-template-columns: 1fr;
663 }
664}
2081595154f846f912025-06-04 17:35:21 +0800665</style>