Skip to content

Commit 6942187

Browse files
committed
feat(profile): profile page with activity stats and account info
1 parent a485a44 commit 6942187

File tree

2 files changed

+155
-5
lines changed

2 files changed

+155
-5
lines changed

app/components/custom/sidebar.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ const sidebarData = ref({
292292
</DropdownMenuLabel>
293293
<DropdownMenuSeparator />
294294
<DropdownMenuGroup>
295-
<NuxtLink to="#" target="_blank" as-child>
295+
<NuxtLink to="/profile" as-child>
296296
<DropdownMenuItem>
297297
<Icon class="mr-1" name="material-symbols:person-outline" />
298298
<span>Profile</span>

app/pages/profile.vue

Lines changed: 154 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<script setup lang="ts">
2+
import type { AllClubs } from '@@/types/api/user/all_clubs'
3+
import type { MyRecords } from '@@/types/api/cas/record/my'
24
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'
312
413
definePageMeta({
514
middleware: ['auth'],
@@ -10,10 +19,151 @@ useHead({
1019
})
1120
1221
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+
})
1380
</script>
1481

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

Comments
 (0)