hookehuyr

refactor: 优化页面组件和路由配置

重构了用户个人资料页、活动详情页、签到页和消息页的代码,移除了未使用的导入和调试日志。调整了路由配置,简化了消息页的UI,并优化了签到逻辑。这些改动旨在提高代码的可维护性和用户体验。
1 <!-- 1 <!--
2 * @Date: 2025-04-17 13:16:20 2 * @Date: 2025-04-17 13:16:20
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-04-21 14:56:50 4 + * @LastEditTime: 2025-04-21 15:29:42
5 * @FilePath: /mlaj-reading-club/src/components/shared/ActivityCard.vue 5 * @FilePath: /mlaj-reading-club/src/components/shared/ActivityCard.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -61,17 +61,29 @@ ...@@ -61,17 +61,29 @@
61 </div> 61 </div>
62 </div> 62 </div>
63 63
64 - <router-link :to="`/activity/${activity.id}`" 64 + <router-link v-if="!isRegistrationOpen" :to="`/activity/${activity.id}`"
65 class="mt-4 inline-flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-green-500 to-blue-500 hover:from-green-600 hover:to-blue-600"> 65 class="mt-4 inline-flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-green-500 to-blue-500 hover:from-green-600 hover:to-blue-600">
66 - {{ isPast ? '查看详情' : isRegistrationOpen ? '立即报名' : '了解更多' }} 66 + {{ isPast ? '查看详情' : '了解更多' }}
67 </router-link> 67 </router-link>
68 + <div v-else @click="handleRegistration"
69 + class="mt-4 inline-flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-green-500 to-blue-500 hover:from-green-600 hover:to-blue-600">
70 + 立即报名
71 + </div>
68 </div> 72 </div>
69 </div> 73 </div>
70 </div> 74 </div>
75 +
76 + <!-- Register Modal -->
77 + <Modal :isOpen="showRegisterModal" title="活动报名" @close="showRegisterModal = false">
78 + <form @submit.prevent="submitRegistration">
79 + <!-- Registration form fields -->
80 + </form>
81 + </Modal>
71 </template> 82 </template>
72 83
73 <script setup> 84 <script setup>
74 import { ref, computed, defineProps } from 'vue' 85 import { ref, computed, defineProps } from 'vue'
86 +import Modal from './Modal.vue'
75 87
76 const props = defineProps({ 88 const props = defineProps({
77 activity: { 89 activity: {
...@@ -136,4 +148,15 @@ const statusText = computed(() => { ...@@ -136,4 +148,15 @@ const statusText = computed(() => {
136 } 148 }
137 return ''; 149 return '';
138 }); 150 });
151 +
152 +const showRegisterModal = ref(false)
153 +
154 +const handleRegistration = () => {
155 + showRegisterModal.value = true
156 +}
157 +
158 +const submitRegistration = async () => {
159 + // TODO: Implement registration submission
160 + showRegisterModal.value = false
161 +}
139 </script> 162 </script>
......
1 /* 1 /*
2 * @Date: 2025-04-17 14:26:17 2 * @Date: 2025-04-17 14:26:17
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-04-21 13:26:32 4 + * @LastEditTime: 2025-04-21 15:07:36
5 * @FilePath: /mlaj-reading-club/src/main.js 5 * @FilePath: /mlaj-reading-club/src/main.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -27,8 +27,8 @@ const router = createRouter({ ...@@ -27,8 +27,8 @@ const router = createRouter({
27 { path: '/create-activity', component: () => import('./pages/CreateActivity.vue') }, 27 { path: '/create-activity', component: () => import('./pages/CreateActivity.vue') },
28 { path: '/profile', component: () => import('./pages/UserProfile.vue') }, 28 { path: '/profile', component: () => import('./pages/UserProfile.vue') },
29 { path: '/registration/:activityId', component: () => import('./pages/Registration.vue') }, 29 { path: '/registration/:activityId', component: () => import('./pages/Registration.vue') },
30 - { path: '/check-in/:activityId', component: () => import('./pages/CheckIn.vue') }, 30 + { path: '/check-in/:id', component: () => import('./pages/CheckIn.vue') },
31 - { path: '/messages/:activityId', component: () => import('./pages/Messages.vue') }, 31 + { path: '/messages', component: () => import('./pages/Messages.vue') },
32 { path: '/:pathMatch(.*)*', redirect: '/' } 32 { path: '/:pathMatch(.*)*', redirect: '/' }
33 ] 33 ]
34 }) 34 })
......
...@@ -231,7 +231,7 @@ ...@@ -231,7 +231,7 @@
231 </div> 231 </div>
232 <div class="mt-2 flex items-center"> 232 <div class="mt-2 flex items-center">
233 <span class="text-sm text-green-700 mr-2">报名状态:</span> 233 <span class="text-sm text-green-700 mr-2">报名状态:</span>
234 - <span :class="registrationStatusBadge.class">{{ registrationStatusBadge.text 234 + <span :class="registrationStatusBadge?.class">{{ registrationStatusBadge?.text
235 }}</span> 235 }}</span>
236 </div> 236 </div>
237 </div> 237 </div>
...@@ -271,13 +271,10 @@ ...@@ -271,13 +271,10 @@
271 </template> 271 </template>
272 272
273 <!-- Register Modal --> 273 <!-- Register Modal -->
274 - <Modal v-if="showRegisterModal" @close="showRegisterModal = false"> 274 + <Modal :isOpen="showRegisterModal" title="活动报名" @close="showRegisterModal = false">
275 - <template #title>活动报名</template>
276 - <template #content>
277 <form @submit.prevent="submitRegistration"> 275 <form @submit.prevent="submitRegistration">
278 <!-- Registration form fields --> 276 <!-- Registration form fields -->
279 </form> 277 </form>
280 - </template>
281 </Modal> 278 </Modal>
282 </div> 279 </div>
283 </template> 280 </template>
......
...@@ -261,6 +261,7 @@ import { useRoute, useRouter } from 'vue-router' ...@@ -261,6 +261,7 @@ import { useRoute, useRouter } from 'vue-router'
261 import activitiesData from '../data/activities.json' 261 import activitiesData from '../data/activities.json'
262 import registrationsData from '../data/registrations.json' 262 import registrationsData from '../data/registrations.json'
263 import usersData from '../data/users.json' 263 import usersData from '../data/users.json'
264 +import checkinsData from '../data/checkins.json'
264 import Button from '../components/shared/Button.vue' 265 import Button from '../components/shared/Button.vue'
265 266
266 const route = useRoute() 267 const route = useRoute()
...@@ -268,8 +269,9 @@ const router = useRouter() ...@@ -268,8 +269,9 @@ const router = useRouter()
268 const activities = ref(activitiesData.activities) 269 const activities = ref(activitiesData.activities)
269 const registrations = ref(registrationsData.registrations) 270 const registrations = ref(registrationsData.registrations)
270 const currentUser = ref(usersData.users[0]) 271 const currentUser = ref(usersData.users[0])
272 +const checkIns = ref(checkinsData)
271 273
272 -const activityId = route.params.activityId 274 +const activityId = route.params.id
273 const activity = ref(null) 275 const activity = ref(null)
274 const loading = ref(true) 276 const loading = ref(true)
275 const error = ref(null) 277 const error = ref(null)
...@@ -303,8 +305,8 @@ const fetchData = async () => { ...@@ -303,8 +305,8 @@ const fetchData = async () => {
303 userRegistration.value = registration 305 userRegistration.value = registration
304 306
305 // Check if user already checked in 307 // Check if user already checked in
306 - const checkIn = appStore.checkIns.find( 308 + const checkIn = checkIns.value.find(
307 - check => check.activity_id === activityId && check.user_id === appStore.currentUser.id 309 + check => check.activity_id === activityId && check.user_id === currentUser.id
308 ) 310 )
309 311
310 if (checkIn) { 312 if (checkIn) {
...@@ -341,8 +343,8 @@ const handleSubmitCode = async () => { ...@@ -341,8 +343,8 @@ const handleSubmitCode = async () => {
341 try { 343 try {
342 if (inputCode.value === checkInCode.value) { 344 if (inputCode.value === checkInCode.value) {
343 const now = new Date().toISOString().replace('T', ' ').substring(0, 19) 345 const now = new Date().toISOString().replace('T', ' ').substring(0, 19)
344 - const checkInResult = await appStore.addCheckIn({ 346 + const checkInResult = await addCheckIn({
345 - user_id: appStore.currentUser.id, 347 + user_id: currentUser.id,
346 activity_id: activityId, 348 activity_id: activityId,
347 check_in_time: now, 349 check_in_time: now,
348 status: 'checked_in' 350 status: 'checked_in'
...@@ -350,7 +352,7 @@ const handleSubmitCode = async () => { ...@@ -350,7 +352,7 @@ const handleSubmitCode = async () => {
350 352
351 if (checkInResult.success) { 353 if (checkInResult.success) {
352 userCheckIn.value = { 354 userCheckIn.value = {
353 - user_id: appStore.currentUser.id, 355 + user_id: currentUser.id,
354 activity_id: activityId, 356 activity_id: activityId,
355 check_in_time: now, 357 check_in_time: now,
356 status: 'checked_in' 358 status: 'checked_in'
...@@ -418,4 +420,33 @@ watch(countdown, (newValue) => { ...@@ -418,4 +420,33 @@ watch(countdown, (newValue) => {
418 420
419 // Initial data fetch 421 // Initial data fetch
420 onMounted(fetchData) 422 onMounted(fetchData)
423 +
424 +
425 +// 添加签到记录
426 +const addCheckIn = async (checkInData) => {
427 + try {
428 + // 模拟API调用延迟
429 + await new Promise(resolve => setTimeout(resolve, 1000))
430 +
431 + // 创建新的签到记录
432 + const newCheckIn = {
433 + id: `C${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`,
434 + activity_id: checkInData.activity_id,
435 + user_id: checkInData.user_id,
436 + checkin_time: checkInData.check_in_time,
437 + checkin_type: 'code',
438 + status: 'successful',
439 + notes: '',
440 + is_late: false
441 + }
442 +
443 + // 添加到签到记录中
444 + checkIns.value.push(newCheckIn)
445 +
446 + return { success: true }
447 + } catch (err) {
448 + console.error('Failed to add check-in:', err)
449 + return { success: false }
450 + }
451 +}
421 </script> 452 </script>
......
1 <template> 1 <template>
2 - <div class="min-h-screen bg-gray-50"> 2 + <div class="container mx-auto px-4 py-8">
3 - <!-- Header --> 3 + <h1 class="text-2xl font-bold mb-6">系统消息</h1>
4 - <div class="bg-white shadow-sm sticky top-0 z-10"> 4 +
5 - <div class="container mx-auto px-4 py-3"> 5 + <!-- 消息列表 -->
6 - <div class="flex items-center justify-between"> 6 + <div class="space-y-4">
7 - <div class="flex items-center"> 7 + <div v-for="message in messages" :key="message.id"
8 - <button @click="goBack" class="mr-3 text-gray-600 hover:text-gray-900"> 8 + class="bg-white p-4 rounded-lg shadow hover:shadow-md transition-shadow cursor-pointer"
9 - <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" 9 + :class="{'border-l-4 border-blue-500': !message.read_status}"
10 - fill="currentColor"> 10 + @click="openMessageDetail(message)">
11 - <path fill-rule="evenodd" 11 + <div class="flex justify-between items-start">
12 - d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" 12 + <div class="flex-1">
13 - clip-rule="evenodd" /> 13 + <p class="text-gray-900 font-medium">{{ message.content }}</p>
14 - </svg> 14 + <p class="text-sm text-gray-500 mt-1">{{ message.send_time }}</p>
15 - </button>
16 - <div>
17 - <h1 class="text-lg font-medium text-gray-900 truncate max-w-xs">{{ activity?.title }}</h1>
18 - <p class="text-sm text-gray-500">消息交流</p>
19 - </div>
20 - </div>
21 - <router-link :to="`/activity/${activityId}`"
22 - class="text-sm text-green-600 hover:text-green-700 font-medium">
23 - 活动详情
24 - </router-link>
25 - </div>
26 </div> 15 </div>
16 + <div class="ml-4">
17 + <span v-if="!message.read_status"
18 + class="inline-block w-2 h-2 bg-blue-500 rounded-full"></span>
27 </div> 19 </div>
28 -
29 - <!-- Message Tabs -->
30 - <div class="bg-white shadow-sm">
31 - <div class="container mx-auto px-4">
32 - <div class="flex overflow-x-auto hide-scrollbar">
33 - <button v-for="tab in tabs" :key="tab.value" :class="[
34 - 'py-3 px-4 text-sm font-medium border-b-2 whitespace-nowrap',
35 - activeTab === tab.value
36 - ? 'border-green-500 text-green-600'
37 - : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
38 - ]" @click="activeTab = tab.value">
39 - {{ tab.label }}
40 - </button>
41 </div> 20 </div>
42 </div> 21 </div>
43 </div> 22 </div>
44 23
45 - <!-- Messages Container --> 24 + <!-- 消息详情弹窗 -->
46 - <div class="container mx-auto px-4 py-4 flex flex-col h-[calc(100vh-13rem)]"> 25 + <div v-if="showModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
47 - <!-- Message List --> 26 + <div class="bg-white rounded-lg p-6 max-w-lg w-full mx-4">
48 - <div class="flex-grow overflow-y-auto mb-4 pr-1"> 27 + <div class="flex justify-between items-start mb-4">
49 - <div v-if="filteredMessages.length === 0" class="flex flex-col items-center justify-center h-full"> 28 + <h2 class="text-xl font-semibold">消息详情</h2>
50 - <div class="text-gray-400"> 29 + <button @click="closeModal" class="text-gray-500 hover:text-gray-700">
51 - <svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16" fill="none" viewBox="0 0 24 24" 30 + <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52 - stroke="currentColor"> 31 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
53 - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1"
54 - d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
55 </svg> 32 </svg>
56 - </div>
57 - <p class="text-gray-500 mt-2">暂无消息</p>
58 - <button v-if="activeTab !== 'all'" @click="activeTab = 'all'"
59 - class="mt-4 text-green-600 hover:text-green-700 font-medium text-sm">
60 - 查看全部消息
61 </button> 33 </button>
62 </div> 34 </div>
63 - <div v-else>
64 - <div v-for="date in sortedDates" :key="date">
65 - <div class="flex items-center justify-center my-4">
66 - <div class="border-t border-gray-200 flex-grow"></div>
67 - <span class="mx-4 text-xs text-gray-500">{{ formatDate(date) }}</span>
68 - <div class="border-t border-gray-200 flex-grow"></div>
69 - </div>
70 - <div v-for="message in groupedMessages[date]" :key="message.id">
71 - <component :is="messageComponent(message)" :message="message" />
72 - </div>
73 - </div>
74 - <div ref="messageEndRef" />
75 - </div>
76 - </div>
77 35
78 - <!-- Message Input --> 36 + <div class="mb-6">
79 - <div class="bg-white border border-gray-200 rounded-lg overflow-hidden"> 37 + <p class="text-gray-900 mb-2">{{ selectedMessage?.content }}</p>
80 - <div v-if="isOrganizer" class="p-2 bg-gray-50 border-b border-gray-200"> 38 + <p class="text-sm text-gray-500">{{ selectedMessage?.send_time }}</p>
81 - <div class="flex items-center"> 39 + </div>
82 - <span 40 +
83 - class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800"> 41 + <!-- 回复框 -->
84 - 组织者 42 + <div class="mt-4">
85 - </span> 43 + <textarea
86 - <span class="text-xs text-gray-500 ml-2"> 44 + v-model="replyContent"
87 - 您可以发送公告消息 45 + rows="3"
88 - </span> 46 + class="w-full border rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
47 + placeholder="输入回复内容..."
48 + ></textarea>
49 + <div class="flex justify-end mt-2">
50 + <button
51 + @click="sendReply"
52 + class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition-colors"
53 + >
54 + 发送回复
55 + </button>
89 </div> 56 </div>
90 </div> 57 </div>
91 - <form @submit.prevent="handleSendMessage" class="flex p-2">
92 - <input type="text" v-model="messageInput" placeholder="输入消息..."
93 - class="flex-grow border-none focus:ring-0 focus:outline-none" :disabled="isSubmitting" />
94 - <Button type="submit" variant="primary" size="sm" :disabled="!messageInput.trim() || isSubmitting">
95 - 发送
96 - </Button>
97 - </form>
98 </div> 58 </div>
99 </div> 59 </div>
100 </div> 60 </div>
101 </template> 61 </template>
102 62
103 <script setup> 63 <script setup>
104 -import { ref, computed, onMounted, watch } from 'vue' 64 +import { ref, onMounted } from 'vue'
105 -import { useRoute, useRouter } from 'vue-router' 65 +import messagesData from '../data/messages.json'
106 -import { useAppStore } from '../stores/app'
107 -import Button from '../components/shared/Button.vue'
108 66
109 -// Route and store setup 67 +const messages = ref([])
110 -const route = useRoute() 68 +const showModal = ref(false)
111 -const router = useRouter() 69 +const selectedMessage = ref(null)
112 -const store = useAppStore() 70 +const replyContent = ref('')
113 71
114 -// Props and refs 72 +onMounted(() => {
115 -const activityId = route.params.activityId 73 + messages.value = messagesData.messages
116 -const messageEndRef = ref(null)
117 -const messageInput = ref('')
118 -const activeTab = ref('all')
119 -const isSubmitting = ref(false)
120 -const activity = ref(null)
121 -const filteredMessages = ref([])
122 -const loading = ref(true)
123 -const error = ref(null)
124 -
125 -// Computed properties
126 -const isOrganizer = computed(() => {
127 - return store.currentUser && store.currentUser.id === activity.value?.organizer_id
128 }) 74 })
129 75
130 -// Tabs configuration 76 +const openMessageDetail = (message) => {
131 -const tabs = [ 77 + selectedMessage.value = message
132 - { label: '全部消息', value: 'all' }, 78 + showModal.value = true
133 - { label: '活动公告', value: 'announcements' }, 79 + // 更新消息已读状态
134 - { label: '问答交流', value: 'questions' }, 80 + if (!message.read_status) {
135 - { label: '我的消息', value: 'personal' } 81 + message.read_status = true
136 -]
137 -
138 -// Methods
139 -const goBack = () => router.back()
140 -
141 -const formatTime = (dateString) => {
142 - const date = new Date(dateString.replace(' ', 'T'))
143 - return new Intl.DateTimeFormat('zh-CN', {
144 - month: 'short',
145 - day: 'numeric',
146 - hour: '2-digit',
147 - minute: '2-digit',
148 - hour12: false
149 - }).format(date)
150 -}
151 -
152 -const formatDate = (dateString) => {
153 - return new Date(dateString).toLocaleDateString('zh-CN')
154 -}
155 -
156 -const handleSendMessage = async () => {
157 - if (!messageInput.value.trim()) return
158 -
159 - if (!store.currentUser) {
160 - alert('请先登录再发送消息')
161 - return
162 - }
163 -
164 - isSubmitting.value = true
165 -
166 - try {
167 - const now = new Date().toISOString().replace('T', ' ').substring(0, 19)
168 -
169 - const newMessage = {
170 - activity_id: activityId,
171 - user_id: store.currentUser.id,
172 - type: 'question',
173 - content: messageInput.value,
174 - timestamp: now,
175 - user_name: store.currentUser.name,
176 - user_avatar: store.currentUser.avatar
177 - }
178 -
179 - const result = await store.addMessage(newMessage)
180 -
181 - if (result.success) {
182 - messageInput.value = ''
183 - } else {
184 - alert(`发送失败: ${result.error}`)
185 - }
186 - } catch (error) {
187 - console.error('Failed to send message:', error)
188 - alert('发送消息失败,请稍后重试')
189 - } finally {
190 - isSubmitting.value = false
191 } 82 }
192 } 83 }
193 84
194 -// Message components 85 +const closeModal = () => {
195 -const AnnouncementMessage = { 86 + showModal.value = false
196 - props: ['message'], 87 + selectedMessage.value = null
197 - template: ` 88 + replyContent.value = ''
198 - <div class="bg-blue-50 border border-blue-100 rounded-lg p-4 mb-4">
199 - <div class="flex items-center mb-2">
200 - <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-500 mr-2" viewBox="0 0 20 20" fill="currentColor">
201 - <path fill-rule="evenodd" d="M18 3a1 1 0 00-1.447-.894L8.763 6H5a3 3 0 000 6h.28l1.771 5.316A1 1 0 008 18h1a1 1 0 001-1v-4.382l6.553 3.276A1 1 0 0018 15V3z" clip-rule="evenodd" />
202 - </svg>
203 - <span class="font-semibold text-blue-800">活动公告</span>
204 - </div>
205 - <p class="text-gray-700">{{ message.content }}</p>
206 - <div class="mt-2 text-xs text-gray-500 flex items-center justify-between">
207 - <span>{{ message.user_name }}</span>
208 - <span>{{ formatTime(message.timestamp) }}</span>
209 - </div>
210 - </div>
211 - `
212 } 89 }
213 90
214 -const AnswerMessage = { 91 +const sendReply = () => {
215 - props: ['message'], 92 + if (!replyContent.value.trim()) return
216 - template: `
217 - <div class="bg-green-50 border border-green-100 rounded-lg p-4 mb-4">
218 - <div class="flex items-center mb-2">
219 - <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-500 mr-2" viewBox="0 0 20 20" fill="currentColor">
220 - <path fill-rule="evenodd" d="M18 13V5a2 2 0 00-2-2H4a2 2 0 00-2 2v8a2 2 0 002 2h3l3 3 3-3h3a2 2 0 002-2zM5 7a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1zm1 3a1 1 0 100 2h3a1 1 0 100-2H6z" clip-rule="evenodd" />
221 - </svg>
222 - <span class="font-semibold text-green-800">回复</span>
223 - </div>
224 - <div v-if="message.reply_to" class="bg-white rounded p-2 mb-2 text-sm text-gray-600 border-l-2 border-gray-300">
225 - <p class="truncate">{{ message.reply_to }}</p>
226 - </div>
227 - <p class="text-gray-700">{{ message.content }}</p>
228 - <div class="mt-2 text-xs text-gray-500 flex items-center justify-between">
229 - <span>{{ message.user_name }}</span>
230 - <span>{{ formatTime(message.timestamp) }}</span>
231 - </div>
232 - </div>
233 - `
234 -}
235 93
236 -const DefaultMessage = { 94 + // 这里可以添加发送回复的逻辑
237 - props: ['message'], 95 + console.log('回复内容:', replyContent.value)
238 - setup(props) { 96 + console.log('回复给消息:', selectedMessage.value)
239 - const store = useAppStore()
240 - const isCurrentUser = computed(() => props.message.user_id === store.currentUser?.id)
241 - return { isCurrentUser, formatTime }
242 - },
243 - template: `
244 - <div :class="['flex', isCurrentUser ? 'justify-end' : 'justify-start', 'mb-4']">
245 - <div :class="['max-w-[80%]', isCurrentUser ? 'order-1' : 'order-2']">
246 - <div :class="['flex items-center', isCurrentUser ? 'justify-end' : 'justify-start', 'mb-1']">
247 - <span class="text-xs text-gray-500 mr-2">{{ message.user_name }}</span>
248 - <div class="h-6 w-6 rounded-full overflow-hidden bg-gray-200">
249 - <img v-if="message.user_avatar" :src="message.user_avatar" :alt="message.user_name" class="h-full w-full object-cover" />
250 - <svg v-else xmlns="http://www.w3.org/2000/svg" class="h-full w-full text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
251 - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
252 - </svg>
253 - </div>
254 - </div>
255 - <div :class="['rounded-lg p-3', isCurrentUser ? 'bg-green-100' : 'bg-white border border-gray-200']">
256 - <p class="text-gray-700">{{ message.content }}</p>
257 - </div>
258 - <div :class="['text-xs text-gray-500 mt-1', isCurrentUser ? 'text-right' : 'text-left']">
259 - {{ formatTime(message.timestamp) }}
260 - </div>
261 - </div>
262 - </div>
263 - `
264 -}
265 97
266 -const messageComponent = (message) => { 98 + // 清空回复内容并关闭弹窗
267 - switch (message.type) { 99 + replyContent.value = ''
268 - case 'announcement': 100 + closeModal()
269 - return AnnouncementMessage
270 - case 'answer':
271 - return AnswerMessage
272 - default:
273 - return DefaultMessage
274 - }
275 } 101 }
276 -
277 -// Computed properties for message grouping
278 -const groupedMessages = computed(() => {
279 - return filteredMessages.value.reduce((groups, message) => {
280 - const date = message.timestamp.split(' ')[0]
281 - if (!groups[date]) {
282 - groups[date] = []
283 - }
284 - groups[date].push(message)
285 - return groups
286 - }, {})
287 -})
288 -
289 -const sortedDates = computed(() => {
290 - return Object.keys(groupedMessages.value).sort()
291 -})
292 -
293 -// Watch for changes in messages and scroll to bottom
294 -watch(filteredMessages, () => {
295 - if (messageEndRef.value) {
296 - messageEndRef.value.scrollIntoView({ behavior: 'smooth' })
297 - }
298 -})
299 -
300 -// Filter messages based on active tab
301 -watch([() => store.messages, activeTab], () => {
302 - if (!store.messages) return
303 -
304 - let filtered = store.messages.filter(msg => msg.activity_id === activityId)
305 -
306 - switch (activeTab.value) {
307 - case 'announcements':
308 - filtered = filtered.filter(msg => msg.type === 'announcement')
309 - break
310 - case 'questions':
311 - filtered = filtered.filter(msg => msg.type === 'question' || msg.type === 'answer')
312 - break
313 - case 'personal':
314 - filtered = filtered.filter(msg =>
315 - msg.user_id === store.currentUser?.id || msg.to_user_id === store.currentUser?.id
316 - )
317 - break
318 - }
319 -
320 - filteredMessages.value = filtered.sort((a, b) => {
321 - return new Date(a.timestamp.replace(' ', 'T')) - new Date(b.timestamp.replace(' ', 'T'))
322 - })
323 -})
324 -
325 -// Initial data fetch
326 -onMounted(async () => {
327 - try {
328 - if (!store.currentUser) {
329 - error.value = '请先登录'
330 - loading.value = false
331 - return
332 - }
333 -
334 - if (store.activities.length > 0) {
335 - const foundActivity = store.getActivityById(activityId)
336 -
337 - if (foundActivity) {
338 - activity.value = foundActivity
339 -
340 - // Filter messages for this activity
341 - const activityMessages = store.messages.filter(
342 - msg => msg.activity_id === activityId
343 - ).sort((a, b) => {
344 - return new Date(a.timestamp.replace(' ', 'T')) - new Date(b.timestamp.replace(' ', 'T'))
345 - })
346 -
347 - filteredMessages.value = activityMessages
348 - } else {
349 - error.value = '未找到活动信息'
350 - }
351 - }
352 - loading.value = false
353 - } catch (err) {
354 - console.error('Failed to fetch data:', err)
355 - error.value = '加载数据失败'
356 - loading.value = false
357 - }
358 -})
359 </script> 102 </script>
360 -
361 -<style scoped>
362 -.hide-scrollbar {
363 - -ms-overflow-style: none;
364 - scrollbar-width: none;
365 -}
366 -
367 -.hide-scrollbar::-webkit-scrollbar {
368 - display: none;
369 -}
370 -</style>
......
...@@ -262,7 +262,6 @@ ...@@ -262,7 +262,6 @@
262 262
263 <script setup> 263 <script setup>
264 import { ref, computed, onMounted } from 'vue' 264 import { ref, computed, onMounted } from 'vue'
265 -import { useAppStore } from '../stores/app'
266 import Button from '../components/shared/Button.vue' 265 import Button from '../components/shared/Button.vue'
267 import Input from '../components/shared/Input.vue' 266 import Input from '../components/shared/Input.vue'
268 import ActivityCard from '../components/shared/ActivityCard.vue' 267 import ActivityCard from '../components/shared/ActivityCard.vue'
...@@ -351,8 +350,6 @@ onMounted(() => { ...@@ -351,8 +350,6 @@ onMounted(() => {
351 } 350 }
352 } 351 }
353 352
354 - console.warn(registrations.value);
355 - console.warn(activities.value);
356 if (currentUser.value && registrations.value && activities.value) { 353 if (currentUser.value && registrations.value && activities.value) {
357 // Filter user registrations 354 // Filter user registrations
358 const userRegs = registrations.value.filter(reg => reg.user_id === currentUser.value.id) 355 const userRegs = registrations.value.filter(reg => reg.user_id === currentUser.value.id)
......