1
1
<script setup lang="ts">
2
+ import type { AllClubs } from ' @@/types/api/user/all_clubs'
3
+ import type { MyRecords } from ' @@/types/api/cas/record/my'
2
4
import { useUser } from ' vue-clerk'
5
+ import { usePreferredDark } from ' @vueuse/core'
6
+ import {
7
+ User ,
8
+ Mail ,
9
+ Settings ,
10
+ Clock ,
11
+ } from ' lucide-vue-next'
3
12
4
13
definePageMeta ({
5
14
middleware: [' auth' ],
@@ -10,10 +19,151 @@ useHead({
10
19
})
11
20
12
21
const { user } = useUser ()
22
+
23
+ // Stats data and loading state
24
+ const stats = ref ({
25
+ totalActivities: 0 ,
26
+ totalClubs: 0 ,
27
+ lastActive: ' ' ,
28
+ })
29
+
30
+ const isLoading = ref (true )
31
+
32
+ // Fetch clubs data
33
+ const { data : clubs } = await useQuery <AllClubs >({
34
+ queryKey: [' /api/user/all_clubs' ],
35
+ })
36
+
37
+ // Calculate stats when clubs data is available
38
+ if (clubs .value ) {
39
+ const totalClubs = clubs .value .president .length + clubs .value .vice .length + clubs .value .member .length
40
+ stats .value .totalClubs = totalClubs
41
+ }
42
+
43
+ // Fetch activity records to calculate total activities and last active date
44
+ const fetchActivityData = async () => {
45
+ try {
46
+ let latestDate = new Date (0 )
47
+ let totalActivities = 0
48
+
49
+ // Only fetch if user has clubs
50
+ if (clubs .value ?.president .length || clubs .value ?.vice .length ) {
51
+ for (const club of [... clubs .value .president , ... clubs .value .vice ]) {
52
+ const { data } = await useFetch <MyRecords >(` /api/cas/record/my?club=${club .id } ` )
53
+ if (data .value ) {
54
+ totalActivities += data .value .data .length
55
+ // Find latest activity date
56
+ data .value .data .forEach (record => {
57
+ const recordDate = new Date (record .date )
58
+ if (recordDate > latestDate ) {
59
+ latestDate = recordDate
60
+ }
61
+ })
62
+ }
63
+ }
64
+ }
65
+
66
+ stats .value .totalActivities = totalActivities
67
+ stats .value .lastActive = latestDate .getTime () > 0
68
+ ? latestDate .toLocaleDateString ()
69
+ : ' 暂无活动'
70
+ } catch (error ) {
71
+ console .error (' Error fetching activity data:' , error )
72
+ } finally {
73
+ isLoading .value = false
74
+ }
75
+ }
76
+
77
+ onMounted (() => {
78
+ fetchActivityData ()
79
+ })
13
80
</script >
14
81
15
82
<template >
16
- <h1 class =" text-3xl font-semibold" >
17
- 👋 Hi, {{ user?.firstName }}!
18
- </h1 >
19
- </template >
83
+ <div class =" mx-auto max-w-4xl space-y-8" >
84
+ <Card >
85
+ <CardHeader >
86
+ <CardTitle class =" text-3xl" >
87
+ <div class =" flex items-center gap-4" >
88
+ <Avatar class =" h-16 w-16 rounded-lg" >
89
+ <AvatarImage v-if =" user" :src =" user.imageUrl" :alt =" user.firstName" />
90
+ <AvatarFallback class =" rounded-lg" >
91
+ {{ user?.firstName?.slice(0, 2) }}
92
+ </AvatarFallback >
93
+ </Avatar >
94
+ <div >
95
+ 👋 Hi, {{ user?.firstName }}!
96
+ <div class =" text-base font-normal text-muted-foreground" >
97
+ {{ user?.primaryEmailAddress }}
98
+ </div >
99
+ </div >
100
+ </div >
101
+ </CardTitle >
102
+ </CardHeader >
103
+ </Card >
104
+
105
+ <div class =" grid gap-4 md:grid-cols-2" >
106
+ <!-- Account Info Card -->
107
+ <Card >
108
+ <CardHeader >
109
+ <CardTitle class =" flex items-center gap-2" >
110
+ <User class =" h-5 w-5" />
111
+ 账户信息
112
+ </CardTitle >
113
+ <CardDescription >账户基本信息</CardDescription >
114
+ </CardHeader >
115
+ <CardContent >
116
+ <div class =" space-y-4" >
117
+ <div class =" flex items-center gap-4" >
118
+ <Mail class =" h-4 w-4 text-muted-foreground" />
119
+ <div >
120
+ <div class =" text-sm font-medium text-muted-foreground" >邮箱</div >
121
+ <div >{{ user?.primaryEmailAddress }}</div >
122
+ </div >
123
+ </div >
124
+ <div class =" flex items-center gap-4" >
125
+ <Clock class =" h-4 w-4 text-muted-foreground" />
126
+ <div >
127
+ <div class =" text-sm font-medium text-muted-foreground" >创建时间</div >
128
+ <div >{{ new Date(user?.createdAt || '').toLocaleDateString() }}</div >
129
+ </div >
130
+ </div >
131
+ </div >
132
+ </CardContent >
133
+ </Card >
134
+
135
+ <!-- Statistics Card -->
136
+ <Card >
137
+ <CardHeader >
138
+ <CardTitle class =" flex items-center gap-2" >
139
+ <Icon name =" material-symbols:analytics-outline" class =" h-5 w-5" />
140
+ 账户统计
141
+ </CardTitle >
142
+ <CardDescription >您的活动数据</CardDescription >
143
+ </CardHeader >
144
+ <CardContent >
145
+ <div v-if =" isLoading" class =" grid grid-cols-2 gap-4" >
146
+ <div v-for =" i in 3" :key =" i" class =" space-y-2" >
147
+ <Skeleton class =" h-4 w-24" />
148
+ <Skeleton class =" h-8 w-16" />
149
+ </div >
150
+ </div >
151
+ <div v-else class =" grid grid-cols-2 gap-4" >
152
+ <div class =" space-y-1" >
153
+ <div class =" text-sm font-medium text-muted-foreground" >所属社团</div >
154
+ <div class =" text-2xl font-bold" >{{ stats.totalClubs }}</div >
155
+ </div >
156
+ <div class =" space-y-1" >
157
+ <div class =" text-sm font-medium text-muted-foreground" >活动记录</div >
158
+ <div class =" text-2xl font-bold" >{{ stats.totalActivities }}</div >
159
+ </div >
160
+ <div class =" space-y-1" >
161
+ <div class =" text-sm font-medium text-muted-foreground" >最近活动</div >
162
+ <div class =" text-sm" >{{ stats.lastActive }}</div >
163
+ </div >
164
+ </div >
165
+ </CardContent >
166
+ </Card >
167
+ </div >
168
+ </div >
169
+ </template >
0 commit comments