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