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