ActivityCard.vue
4.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<!--
* @Date: 2025-04-17 13:16:20
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-21 10:26:22
* @FilePath: /mlaj-reading-club/src/components/shared/ActivityCard.vue
* @Description: 文件描述
-->
<template>
<router-link
:to="`/activity/${activity.id}`"
class="block bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition duration-200"
>
<div class="relative pb-48 overflow-hidden">
<img
:src="activity.cover_image"
:alt="activity.title"
class="absolute inset-0 h-full w-full object-cover"
/>
<!-- <div
v-if="activity.tags && activity.tags.length"
class="absolute top-0 left-0 p-4 flex flex-wrap gap-2"
>
<span
v-for="tag in activity.tags"
:key="tag"
class="px-3 py-1 text-sm bg-green-500 text-white rounded-full"
>
{{ tag }}
</span>
</div> -->
<div class="absolute top-2 left-2">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
:class="statusBadgeClass">
{{ statusText }}
</span>
</div>
</div>
<div class="p-6">
<h3 class="text-xl font-semibold text-gray-800 mb-2">{{ activity.title }}</h3>
<p class="text-gray-600 text-sm mb-4 line-clamp-2">{{ activity.description }}</p>
<div class="flex items-center text-sm text-gray-500 mb-4">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span>{{ formatDateTime(activity.start_time) }}</span>
</div>
<div class="flex items-center text-sm text-gray-500 mb-4">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span>{{activity.activity_type === 'online' ? '线上活动' : (activity.location ? (typeof activity.location === 'object' ? activity.location.name : activity.location) : '地点未设置')}}</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<img
:src="activity.organizer_avatar"
:alt="activity.organizer_name"
class="w-8 h-8 rounded-full mr-2"
/>
<span class="text-sm text-gray-600">{{ activity.organizer_name }}</span>
</div>
<span class="text-sm font-medium text-green-500">
{{ activity.participant_count }}人参与
</span>
</div>
</div>
</router-link>
</template>
<script setup>
import { ref, computed, defineProps } from 'vue'
const props = defineProps({
activity: {
type: Object,
required: true
}
})
// 格式化日期时间
const formatDateTime = (dateTimeStr) => {
const date = new Date(dateTimeStr)
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}).format(date)
}
// Calculate if registration is still open
const now = new Date();
const registrationStart = new Date(props.activity.registration_start);
const registrationEnd = new Date(props.activity.registration_end);
const activityStart = new Date(props.activity.start_time);
const isRegistrationOpen = ref(now >= registrationStart && now <= registrationEnd);
const isUpcoming = ref(now < activityStart);
const isPast = ref(now > new Date(props.activity.end_time));
const isOngoing = ref(!isUpcoming && !isPast);
// Calculate capacity percentage
const capacityPercentage = Math.min((props.activity.participant_count / props.activity.max_participants) * 100, 100);
// Dynamically set status badge colors
const statusBadgeClass = computed(() => {
if (isPast.value) {
return 'bg-gray-100 text-gray-800';
} else if (isOngoing.value) {
return 'bg-green-100 text-green-800';
} else if (isRegistrationOpen.value) {
return 'bg-blue-100 text-blue-800';
} else if (isUpcoming.value && !isRegistrationOpen.value) {
return 'bg-yellow-100 text-yellow-800';
}
return '';
});
const statusText = computed(() => {
if (isPast.value) {
return '已结束';
} else if (isOngoing.value) {
return '进行中';
} else if (isRegistrationOpen.value) {
return '报名中';
} else if (isUpcoming.value && !isRegistrationOpen.value) {
return '即将开始';
}
return '';
});
</script>