feat(学生详情页): 重构学生详情页并添加作业记录功能
- 使用 van-tabs 重构标签页切换组件,提升用户体验 - 新增作业记录标签页,支持图片、视频和音频的展示与播放 - 实现点赞功能及相关API调用 - 优化多媒体播放控制逻辑,避免同时播放多个媒体 - 添加样式优化,提升页面整体美观度
Showing
1 changed file
with
468 additions
and
20 deletions
| ... | @@ -2,7 +2,7 @@ | ... | @@ -2,7 +2,7 @@ |
| 2 | * @Author: hookehuyr hookehuyr@gmail.com | 2 | * @Author: hookehuyr hookehuyr@gmail.com |
| 3 | * @Date: 2025-06-19 17:12:19 | 3 | * @Date: 2025-06-19 17:12:19 |
| 4 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 4 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 5 | - * @LastEditTime: 2025-06-19 17:27:16 | 5 | + * @LastEditTime: 2025-06-19 21:32:23 |
| 6 | * @FilePath: /mlaj/src/views/teacher/studentPage.vue | 6 | * @FilePath: /mlaj/src/views/teacher/studentPage.vue |
| 7 | * @Description: 学生详情页面 | 7 | * @Description: 学生详情页面 |
| 8 | --> | 8 | --> |
| ... | @@ -85,7 +85,7 @@ | ... | @@ -85,7 +85,7 @@ |
| 85 | </div> | 85 | </div> |
| 86 | 86 | ||
| 87 | <!-- 功能按钮 --> | 87 | <!-- 功能按钮 --> |
| 88 | - <div class="bg-white mt-2 p-4"> | 88 | + <div class="mt-2 p-4"> |
| 89 | <!-- 状态筛选 --> | 89 | <!-- 状态筛选 --> |
| 90 | <div class="flex items-center justify-end mb-4"> | 90 | <div class="flex items-center justify-end mb-4"> |
| 91 | <div @click="showStatusPopup = true" class="flex items-center text-sm text-gray-600 cursor-pointer"> | 91 | <div @click="showStatusPopup = true" class="flex items-center text-sm text-gray-600 cursor-pointer"> |
| ... | @@ -93,11 +93,7 @@ | ... | @@ -93,11 +93,7 @@ |
| 93 | <van-icon name="arrow-down" size="14" class="ml-1" /> | 93 | <van-icon name="arrow-down" size="14" class="ml-1" /> |
| 94 | </div> | 94 | </div> |
| 95 | </div> | 95 | </div> |
| 96 | - <div class="flex items-center justify-between mb-3"> | 96 | + <!-- <div class="flex items-center justify-between mb-3"> |
| 97 | - <div @click="activeTab = 'statistics'" | ||
| 98 | - :class="['flex-1 text-center py-2 text-sm font-medium cursor-pointer', activeTab === 'statistics' ? 'text-green-600 border-b-2 border-green-600' : 'text-gray-500']"> | ||
| 99 | - 打卡统计 | ||
| 100 | - </div> | ||
| 101 | <div @click="activeTab = 'homework'" | 97 | <div @click="activeTab = 'homework'" |
| 102 | :class="['flex-1 text-center py-2 text-sm font-medium cursor-pointer', activeTab === 'homework' ? 'text-green-600 border-b-2 border-green-600' : 'text-gray-500']"> | 98 | :class="['flex-1 text-center py-2 text-sm font-medium cursor-pointer', activeTab === 'homework' ? 'text-green-600 border-b-2 border-green-600' : 'text-gray-500']"> |
| 103 | 作业记录 | 99 | 作业记录 |
| ... | @@ -106,12 +102,24 @@ | ... | @@ -106,12 +102,24 @@ |
| 106 | :class="['flex-1 text-center py-2 text-sm font-medium cursor-pointer', activeTab === 'evaluation' ? 'text-green-600 border-b-2 border-green-600' : 'text-gray-500']"> | 102 | :class="['flex-1 text-center py-2 text-sm font-medium cursor-pointer', activeTab === 'evaluation' ? 'text-green-600 border-b-2 border-green-600' : 'text-gray-500']"> |
| 107 | 班主任点评 | 103 | 班主任点评 |
| 108 | </div> | 104 | </div> |
| 105 | + <div @click="activeTab = 'statistics'" | ||
| 106 | + :class="['flex-1 text-center py-2 text-sm font-medium cursor-pointer', activeTab === 'statistics' ? 'text-green-600 border-b-2 border-green-600' : 'text-gray-500']"> | ||
| 107 | + 打卡统计 | ||
| 108 | + </div> | ||
| 109 | + </div> --> | ||
| 110 | + <div class="px-4 py-3 bg-white" style="position: relative;"> | ||
| 111 | + <van-tabs v-model:active="activeTab" color="#10b981" sticky animated swipeable @change="handleTabChange"> | ||
| 112 | + <van-tab title="作业记录" name="homework"></van-tab> | ||
| 113 | + <van-tab title="班主任点评" name="evaluation"></van-tab> | ||
| 114 | + <van-tab title="打卡统计" name="statistics"></van-tab> | ||
| 115 | + </van-tabs> | ||
| 109 | </div> | 116 | </div> |
| 110 | 117 | ||
| 111 | - <!-- 记录列表 --> | 118 | + <!-- 记录列表 --> |
| 112 | - <van-list v-if="activeTab === 'statistics'" v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"> | 119 | + <van-list v-show="activeTab === 'statistics'" v-model:loading="recordLoading" :finished="recordFinished" |
| 120 | + finished-text="没有更多了" @load="onRecordLoad"> | ||
| 113 | <div v-for="record in filteredRecords" :key="record.id" | 121 | <div v-for="record in filteredRecords" :key="record.id" |
| 114 | - class="flex items-center justify-between py-4 border-b border-gray-100 last:border-b-0"> | 122 | + class="flex items-center justify-between py-4 border-b border-gray-100 last:border-b-0 bg-white px-4"> |
| 115 | <div class="flex items-center flex-1"> | 123 | <div class="flex items-center flex-1"> |
| 116 | <div class="mr-4"> | 124 | <div class="mr-4"> |
| 117 | <div style="display: flex; justify-content: center;"> | 125 | <div style="display: flex; justify-content: center;"> |
| ... | @@ -135,6 +143,77 @@ | ... | @@ -135,6 +143,77 @@ |
| 135 | </div> | 143 | </div> |
| 136 | </div> | 144 | </div> |
| 137 | </van-list> | 145 | </van-list> |
| 146 | + | ||
| 147 | + <!--作业记录 --> | ||
| 148 | + <van-list v-show="activeTab === 'homework' && checkinDataList.length" v-model:loading="loading" | ||
| 149 | + :finished="finished" finished-text="没有更多了" @load="onLoad" class="space-y-4"> | ||
| 150 | + <div class="post-card" v-for="post in checkinDataList" :key="post.id"> | ||
| 151 | + <div class="post-header"> | ||
| 152 | + <van-row> | ||
| 153 | + <van-col span="4"> | ||
| 154 | + <van-image round width="2.5rem" height="2.5rem" | ||
| 155 | + :src="post.user.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'" fit="cover" /> | ||
| 156 | + </van-col> | ||
| 157 | + <van-col span="17"> | ||
| 158 | + <div class="user-info"> | ||
| 159 | + <div class="username">{{ post.user.name }}</div> | ||
| 160 | + <div class="post-time">{{ post.user.time }}</div> | ||
| 161 | + </div> | ||
| 162 | + </van-col> | ||
| 163 | + <van-col span="3"> | ||
| 164 | + </van-col> | ||
| 165 | + </van-row> | ||
| 166 | + </div> | ||
| 167 | + <div class="post-content"> | ||
| 168 | + <div class="post-text">{{ post.content }}</div> | ||
| 169 | + <div class="post-media"> | ||
| 170 | + <div v-if="post.images.length" class="post-images"> | ||
| 171 | + <van-image width="30%" fit="cover" v-for="(image, index) in post.images" :key="index" :src="image" | ||
| 172 | + radius="5" @click="openImagePreview(index, post)" /> | ||
| 173 | + </div> | ||
| 174 | + <van-image-preview v-if="currentPost" v-model:show="showImagePreview" :images="currentPost.images" | ||
| 175 | + :start-position="startPosition" :show-index="true" @change="onChange" /> | ||
| 176 | + <div v-for="(v, idx) in post.videoList" :key="idx"> | ||
| 177 | + <!-- 视频封面和播放按钮 --> | ||
| 178 | + <div v-if="v.video && !v.isPlaying" class="relative w-full rounded-lg overflow-hidden" | ||
| 179 | + style="aspect-ratio: 16/9; margin-bottom: 1rem;"> | ||
| 180 | + <img :src="v.videoCover || 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png'" :alt="v.content" | ||
| 181 | + class="w-full h-full object-cover" /> | ||
| 182 | + <div class="absolute inset-0 flex items-center justify-center cursor-pointer bg-black/20" | ||
| 183 | + @click="startPlay(v)"> | ||
| 184 | + <div | ||
| 185 | + class="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors"> | ||
| 186 | + <van-icon name="play-circle-o" class="text-white" size="40" /> | ||
| 187 | + </div> | ||
| 188 | + </div> | ||
| 189 | + </div> | ||
| 190 | + <!-- 视频播放器 --> | ||
| 191 | + <VideoPlayer v-if="v.video && v.isPlaying" :video-url="v.video" | ||
| 192 | + class="post-video rounded-lg overflow-hidden" :ref="el => { | ||
| 193 | + if (el) { | ||
| 194 | + // 确保不重复添加 | ||
| 195 | + if (!videoPlayers?.includes(el)) { | ||
| 196 | + videoPlayers?.push(el); | ||
| 197 | + } | ||
| 198 | + } | ||
| 199 | + }" @onPlay="handleVideoPlay(player, post)" @onPause="handleVideoPause(post)" /> | ||
| 200 | + </div> | ||
| 201 | + <AudioPlayer v-if="post.audio.length" :songs="post.audio" class="post-audio" :id="post.id" :ref="el => { | ||
| 202 | + if (el) { | ||
| 203 | + // 确保不重复添加 | ||
| 204 | + if (!audioPlayers?.includes(el)) { | ||
| 205 | + audioPlayers?.push(el); | ||
| 206 | + } | ||
| 207 | + } | ||
| 208 | + }" @play="(player) => handleAudioPlay(player, post)" /> | ||
| 209 | + </div> | ||
| 210 | + </div> | ||
| 211 | + <div class="post-footer"> | ||
| 212 | + <van-icon @click="handLike(post)" name="good-job" class="like-icon" :color="post.is_liked ? 'red' : ''" /> | ||
| 213 | + <span class="like-count">{{ post.likes }}</span> | ||
| 214 | + </div> | ||
| 215 | + </div> | ||
| 216 | + </van-list> | ||
| 138 | </div> | 217 | </div> |
| 139 | 218 | ||
| 140 | <!-- 状态筛选弹窗 --> | 219 | <!-- 状态筛选弹窗 --> |
| ... | @@ -159,11 +238,20 @@ | ... | @@ -159,11 +238,20 @@ |
| 159 | </template> | 238 | </template> |
| 160 | 239 | ||
| 161 | <script setup> | 240 | <script setup> |
| 162 | -import { ref, computed, onMounted } from 'vue' | 241 | +import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| 163 | import { useRouter, useRoute } from 'vue-router' | 242 | import { useRouter, useRoute } from 'vue-router' |
| 243 | +import { showConfirmDialog, showSuccessToast, showFailToast, showLoadingToast } from 'vant'; | ||
| 244 | +import VideoPlayer from "@/components/ui/VideoPlayer.vue"; | ||
| 245 | +import AudioPlayer from "@/components/ui/AudioPlayer.vue"; | ||
| 246 | +import { useTitle } from '@vueuse/core'; | ||
| 247 | +import dayjs from 'dayjs'; | ||
| 248 | + | ||
| 249 | +import { getTaskDetailAPI, getUploadTaskListAPI, delUploadTaskInfoAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI } from "@/api/checkin"; | ||
| 250 | + | ||
| 164 | 251 | ||
| 165 | const router = useRouter() | 252 | const router = useRouter() |
| 166 | const route = useRoute() | 253 | const route = useRoute() |
| 254 | +useTitle(route.meta.title); | ||
| 167 | 255 | ||
| 168 | // 学生信息 | 256 | // 学生信息 |
| 169 | const studentInfo = ref({ | 257 | const studentInfo = ref({ |
| ... | @@ -185,7 +273,7 @@ const studentStats = ref({ | ... | @@ -185,7 +273,7 @@ const studentStats = ref({ |
| 185 | }) | 273 | }) |
| 186 | 274 | ||
| 187 | // 当前选中的标签页 | 275 | // 当前选中的标签页 |
| 188 | -const activeTab = ref('statistics') | 276 | +const activeTab = ref('homework') |
| 189 | 277 | ||
| 190 | // 状态筛选 | 278 | // 状态筛选 |
| 191 | const statusFilter = ref('按状态') | 279 | const statusFilter = ref('按状态') |
| ... | @@ -240,8 +328,8 @@ const records = ref([ | ... | @@ -240,8 +328,8 @@ const records = ref([ |
| 240 | ]) | 328 | ]) |
| 241 | 329 | ||
| 242 | // 列表加载状态 | 330 | // 列表加载状态 |
| 243 | -const loading = ref(false) | 331 | +const recordLoading = ref(false) |
| 244 | -const finished = ref(false) | 332 | +const recordFinished = ref(false) |
| 245 | 333 | ||
| 246 | /** | 334 | /** |
| 247 | * 过滤后的记录列表 | 335 | * 过滤后的记录列表 |
| ... | @@ -277,25 +365,33 @@ const onStatusSelect = (option) => { | ... | @@ -277,25 +365,33 @@ const onStatusSelect = (option) => { |
| 277 | } | 365 | } |
| 278 | 366 | ||
| 279 | /** | 367 | /** |
| 280 | - * 加载更多数据 | 368 | + * 加载更多记录数据 |
| 281 | */ | 369 | */ |
| 282 | -const onLoad = () => { | 370 | +const onRecordLoad = () => { |
| 283 | setTimeout(() => { | 371 | setTimeout(() => { |
| 284 | - loading.value = false | 372 | + recordLoading.value = false |
| 285 | - finished.value = true | 373 | + recordFinished.value = true |
| 286 | }, 1000) | 374 | }, 1000) |
| 287 | } | 375 | } |
| 288 | 376 | ||
| 289 | /** | 377 | /** |
| 290 | * 组件挂载时初始化数据 | 378 | * 组件挂载时初始化数据 |
| 291 | */ | 379 | */ |
| 292 | -onMounted(() => { | 380 | +const checkinDataList = ref([]); |
| 381 | +onMounted(async () => { | ||
| 293 | // 从路由参数获取学生ID | 382 | // 从路由参数获取学生ID |
| 294 | const studentId = route.params.id | 383 | const studentId = route.params.id |
| 295 | console.log('学生详情页面已加载,学生ID:', studentId) | 384 | console.log('学生详情页面已加载,学生ID:', studentId) |
| 296 | 385 | ||
| 297 | // 这里可以根据studentId调用API获取学生详细信息 | 386 | // 这里可以根据studentId调用API获取学生详细信息 |
| 298 | loadStudentData(studentId) | 387 | loadStudentData(studentId) |
| 388 | + | ||
| 389 | + const current_date = route.query.date; | ||
| 390 | + if (current_date) { | ||
| 391 | + onLoad(current_date); | ||
| 392 | + } else { | ||
| 393 | + onLoad(dayjs().format('YYYY-MM-DD')); | ||
| 394 | + } | ||
| 299 | }) | 395 | }) |
| 300 | 396 | ||
| 301 | /** | 397 | /** |
| ... | @@ -306,9 +402,290 @@ const loadStudentData = (studentId) => { | ... | @@ -306,9 +402,290 @@ const loadStudentData = (studentId) => { |
| 306 | // 这里可以调用API获取实际数据 | 402 | // 这里可以调用API获取实际数据 |
| 307 | console.log('加载学生数据:', studentId) | 403 | console.log('加载学生数据:', studentId) |
| 308 | } | 404 | } |
| 405 | + | ||
| 406 | +// 处理标签页切换 | ||
| 407 | +const handleTabChange = (name) => { | ||
| 408 | + // 先更新activeTab值 | ||
| 409 | + activeTab.value = name; | ||
| 410 | + | ||
| 411 | + nextTick(() => { | ||
| 412 | + // 停止所有视频和音频播放 | ||
| 413 | + if (videoPlayers.value) { | ||
| 414 | + videoPlayers.value.forEach(player => { | ||
| 415 | + console.warn(player); | ||
| 416 | + if (player && typeof player?.pause === 'function') { | ||
| 417 | + player?.pause(); | ||
| 418 | + } | ||
| 419 | + }); | ||
| 420 | + } | ||
| 421 | + | ||
| 422 | + stopAllAudio(); | ||
| 423 | + }) | ||
| 424 | +}; | ||
| 425 | + | ||
| 426 | +// 存储所有视频播放器的引用 | ||
| 427 | +const videoPlayers = ref([]); | ||
| 428 | + | ||
| 429 | +// 存储所有音频播放器的引用 | ||
| 430 | +const audioPlayers = ref([]); | ||
| 431 | + | ||
| 432 | +// 组件卸载前清理播放器引用和事件监听器 | ||
| 433 | +onBeforeUnmount(() => { | ||
| 434 | + // 停止所有视频和音频播放 | ||
| 435 | + if (videoPlayers.value) { | ||
| 436 | + videoPlayers.value.forEach(player => { | ||
| 437 | + if (player && typeof player?.pause === 'function') { | ||
| 438 | + player?.pause(); | ||
| 439 | + } | ||
| 440 | + }); | ||
| 441 | + } | ||
| 442 | + | ||
| 443 | + stopAllAudio(); | ||
| 444 | + | ||
| 445 | + // 清空引用数组 | ||
| 446 | + if (videoPlayers.value) videoPlayers.value = []; | ||
| 447 | + if (audioPlayers.value) audioPlayers.value = []; | ||
| 448 | +}); | ||
| 449 | + | ||
| 450 | +/** | ||
| 451 | + * 开始播放指定帖子的视频 | ||
| 452 | + * @param {Object} post - 要播放视频的帖子对象 | ||
| 453 | + */ | ||
| 454 | +const startPlay = (post) => { | ||
| 455 | + // 确保checkinDataList.value是一个数组 | ||
| 456 | + if (checkinDataList.value) { | ||
| 457 | + // 先暂停所有其他视频 | ||
| 458 | + checkinDataList.value.forEach(p => { | ||
| 459 | + p.videoList.forEach(v => { | ||
| 460 | + if (v.id !== post.id) { | ||
| 461 | + v.isPlaying = false; | ||
| 462 | + } | ||
| 463 | + }); | ||
| 464 | + }); | ||
| 465 | + } | ||
| 466 | + | ||
| 467 | + // 设置当前视频为播放状态 | ||
| 468 | + post.isPlaying = true; | ||
| 469 | +}; | ||
| 470 | + | ||
| 471 | +/** | ||
| 472 | + * 处理视频播放事件 | ||
| 473 | + * @param {Object} player - 视频播放器实例 | ||
| 474 | + * @param {Object} post - 包含视频的帖子对象 | ||
| 475 | + */ | ||
| 476 | +const handleVideoPlay = (player, post) => { | ||
| 477 | + stopAllAudio(); | ||
| 478 | +}; | ||
| 479 | + | ||
| 480 | +/** | ||
| 481 | + * 处理视频暂停事件 | ||
| 482 | + * @param {Object} post - 包含视频的帖子对象 | ||
| 483 | + */ | ||
| 484 | +const handleVideoPause = (post) => { | ||
| 485 | + // 视频暂停时不改变isPlaying状态,保持播放器可见 | ||
| 486 | + // 这样用户可以继续从暂停处播放 | ||
| 487 | +}; | ||
| 488 | + | ||
| 489 | +/** | ||
| 490 | + * 停止除当前播放器外的所有其他视频 | ||
| 491 | + * @param {Object} currentPlayer - 当前播放的视频播放器实例 | ||
| 492 | + * @param {Object} currentPost - 当前播放的帖子对象 | ||
| 493 | + */ | ||
| 494 | +const stopOtherVideos = (currentPlayer, currentPost) => { | ||
| 495 | + // 确保videoPlayers.value是一个数组 | ||
| 496 | + if (videoPlayers.value) { | ||
| 497 | + // 暂停其他视频播放器 | ||
| 498 | + videoPlayers.value.forEach(player => { | ||
| 499 | + if (player !== currentPlayer && player.pause) { | ||
| 500 | + player.pause(); | ||
| 501 | + } | ||
| 502 | + }); | ||
| 503 | + } | ||
| 504 | + | ||
| 505 | + // 更新其他帖子的播放状态 | ||
| 506 | + checkinDataList.value.forEach(p => { | ||
| 507 | + p.videoList.forEach(v => { | ||
| 508 | + if (v.id !== currentPost.id) { | ||
| 509 | + v.isPlaying = false; | ||
| 510 | + } | ||
| 511 | + }); | ||
| 512 | + }); | ||
| 513 | +}; | ||
| 514 | + | ||
| 515 | +/** | ||
| 516 | + * 处理音频播放事件 | ||
| 517 | + * @param {Object} player - 音频播放器实例 | ||
| 518 | + * @param {Object} post - 包含音频的帖子对象 | ||
| 519 | + */ | ||
| 520 | +const handleAudioPlay = (player, post) => { | ||
| 521 | + // 停止其他音频播放 | ||
| 522 | + stopOtherAudio(player, post); | ||
| 523 | +}; | ||
| 524 | + | ||
| 525 | +const stopOtherAudio = (currentPlayer, currentPost) => { | ||
| 526 | + // 确保audioPlayers.value是一个数组 | ||
| 527 | + if (audioPlayers.value) { | ||
| 528 | + // 暂停其他音频播放器 | ||
| 529 | + audioPlayers.value.forEach(player => { | ||
| 530 | + if (player.id !== currentPost.id && player.pause) { | ||
| 531 | + player.pause(); | ||
| 532 | + } | ||
| 533 | + }); | ||
| 534 | + } | ||
| 535 | + // 更新其他帖子的播放状态 | ||
| 536 | + checkinDataList.value.forEach(post => { | ||
| 537 | + if (post.id !== currentPost.id) { | ||
| 538 | + post.isPlaying = false; | ||
| 539 | + } | ||
| 540 | + }); | ||
| 541 | + // 停止所有视频播放 | ||
| 542 | + stopAllVideos(); | ||
| 543 | +} | ||
| 544 | + | ||
| 545 | +const stopAllAudio = () => { | ||
| 546 | + // 确保audioPlayers.value是一个数组 | ||
| 547 | + if (!audioPlayers.value) return; | ||
| 548 | + audioPlayers.value?.forEach(player => { | ||
| 549 | + // 使用组件暴露的pause方法 | ||
| 550 | + if (typeof player.pause === 'function') { | ||
| 551 | + player.pause(); | ||
| 552 | + } | ||
| 553 | + }); | ||
| 554 | + // 更新所有帖子的播放状态 | ||
| 555 | + checkinDataList.value.forEach(post => { | ||
| 556 | + if (post.audio.length) { | ||
| 557 | + post.isPlaying = false; | ||
| 558 | + } | ||
| 559 | + }); | ||
| 560 | +} | ||
| 561 | + | ||
| 562 | +/** | ||
| 563 | + * 停止所有视频播放 | ||
| 564 | + */ | ||
| 565 | +const stopAllVideos = () => { | ||
| 566 | + // 确保videoPlayers.value是一个数组 | ||
| 567 | + if (!videoPlayers.value) return; | ||
| 568 | + | ||
| 569 | + // 更新所有帖子的播放状态 | ||
| 570 | + checkinDataList.value.forEach(p => { | ||
| 571 | + p.videoList.forEach(v => { | ||
| 572 | + v.isPlaying = false; | ||
| 573 | + }); | ||
| 574 | + }); | ||
| 575 | +}; | ||
| 576 | + | ||
| 577 | +// 图片预览相关 | ||
| 578 | +const showImagePreview = ref(false); | ||
| 579 | +const startPosition = ref(0); | ||
| 580 | +const currentPost = ref(null); | ||
| 581 | + | ||
| 582 | +// 打开图片预览 | ||
| 583 | +const openImagePreview = (index, post) => { | ||
| 584 | + currentPost.value = post; | ||
| 585 | + startPosition.value = index; | ||
| 586 | + showImagePreview.value = true; | ||
| 587 | +} | ||
| 588 | + | ||
| 589 | +// 图片切换事件处理 | ||
| 590 | +const onChange = (index) => { | ||
| 591 | + startPosition.value = index; | ||
| 592 | +} | ||
| 593 | + | ||
| 594 | +const handLike = async (post) => { | ||
| 595 | + if (!post.is_liked) { | ||
| 596 | + const { code, data } = await likeUploadTaskInfoAPI({ checkin_id: post.id, }) | ||
| 597 | + if (code) { | ||
| 598 | + showSuccessToast('点赞成功') | ||
| 599 | + post.likes++; | ||
| 600 | + post.is_liked = true; | ||
| 601 | + } | ||
| 602 | + } else { | ||
| 603 | + const { code, data } = await dislikeUploadTaskInfoAPI({ checkin_id: post.id, }) | ||
| 604 | + if (code) { | ||
| 605 | + showSuccessToast('取消点赞成功') | ||
| 606 | + post.likes--; | ||
| 607 | + post.is_liked = false; | ||
| 608 | + } | ||
| 609 | + } | ||
| 610 | +} | ||
| 611 | + | ||
| 612 | +const loading = ref(false) | ||
| 613 | +const finished = ref(false) | ||
| 614 | +const limit = ref(3) | ||
| 615 | +const page = ref(0) | ||
| 616 | + | ||
| 617 | +const onLoad = async (date) => { | ||
| 618 | + const nextPage = page.value; | ||
| 619 | + const current_date = date || route.query.date || dayjs().format('YYYY-MM-DD'); | ||
| 620 | + // | ||
| 621 | + const res = await getUploadTaskListAPI({ | ||
| 622 | + limit: limit.value, | ||
| 623 | + page: nextPage, | ||
| 624 | + task_id: route.query.id, | ||
| 625 | + date: current_date | ||
| 626 | + }); | ||
| 627 | + if (res.code) { | ||
| 628 | + // 整理数据结构 | ||
| 629 | + checkinDataList.value = [...checkinDataList.value, ...formatData(res.data)]; | ||
| 630 | + finished.value = res.data.checkin_list.length < limit.value; | ||
| 631 | + page.value = nextPage + 1; | ||
| 632 | + } | ||
| 633 | + loading.value = false; | ||
| 634 | +}; | ||
| 635 | + | ||
| 636 | +const formatData = (data) => { | ||
| 637 | + let formattedData = []; | ||
| 638 | + formattedData = data?.checkin_list.map((item, index) => { | ||
| 639 | + let images = []; | ||
| 640 | + let audio = []; | ||
| 641 | + let videoList = []; | ||
| 642 | + if (item.file_type === 'image') { | ||
| 643 | + images = item.files.map(file => { | ||
| 644 | + return file.value; | ||
| 645 | + }); | ||
| 646 | + } else if (item.file_type === 'video') { | ||
| 647 | + videoList = item.files.map(file => { | ||
| 648 | + return { | ||
| 649 | + id: file.meta_id, | ||
| 650 | + video: file.value, | ||
| 651 | + videoCover: file.cover, | ||
| 652 | + isPlaying: false, | ||
| 653 | + } | ||
| 654 | + }) | ||
| 655 | + } else if (item.file_type === 'audio') { | ||
| 656 | + audio = item.files.map(file => { | ||
| 657 | + return { | ||
| 658 | + title: file.name ? file.name : '打卡音频', | ||
| 659 | + artist: file.artist ? file.artist : '', | ||
| 660 | + url: file.value, | ||
| 661 | + cover: file.cover ? file.cover : '', | ||
| 662 | + } | ||
| 663 | + }) | ||
| 664 | + } | ||
| 665 | + return { | ||
| 666 | + id: item.id, | ||
| 667 | + task_id: item.task_id, | ||
| 668 | + user: { | ||
| 669 | + name: item.username, | ||
| 670 | + avatar: item.avatar, | ||
| 671 | + time: item.created_time_desc, | ||
| 672 | + }, | ||
| 673 | + content: item.note, | ||
| 674 | + images, | ||
| 675 | + videoList, | ||
| 676 | + audio, | ||
| 677 | + isPlaying: false, | ||
| 678 | + likes: item.like_count, | ||
| 679 | + is_liked: item.is_like, | ||
| 680 | + is_my: item.is_my, | ||
| 681 | + file_type: item.file_type, | ||
| 682 | + } | ||
| 683 | + }) | ||
| 684 | + return formattedData; | ||
| 685 | +} | ||
| 309 | </script> | 686 | </script> |
| 310 | 687 | ||
| 311 | -<style scoped> | 688 | +<style scoped lang="less"> |
| 312 | /* 自定义样式 */ | 689 | /* 自定义样式 */ |
| 313 | .van-circle { | 690 | .van-circle { |
| 314 | font-size: 12px; | 691 | font-size: 12px; |
| ... | @@ -328,4 +705,75 @@ const loadStudentData = (studentId) => { | ... | @@ -328,4 +705,75 @@ const loadStudentData = (studentId) => { |
| 328 | .border-b-2 { | 705 | .border-b-2 { |
| 329 | border-bottom-width: 2px; | 706 | border-bottom-width: 2px; |
| 330 | } | 707 | } |
| 708 | + | ||
| 709 | +.post-card { | ||
| 710 | + // margin: 1rem 0; | ||
| 711 | + padding: 1rem; | ||
| 712 | + background-color: #FFF; | ||
| 713 | + border-radius: 5px; | ||
| 714 | + | ||
| 715 | + .post-header { | ||
| 716 | + margin-bottom: 1rem; | ||
| 717 | + } | ||
| 718 | + | ||
| 719 | + .user-info { | ||
| 720 | + margin-left: 0.5rem; | ||
| 721 | + | ||
| 722 | + .username { | ||
| 723 | + font-weight: 500; | ||
| 724 | + } | ||
| 725 | + | ||
| 726 | + .post-time { | ||
| 727 | + color: gray; | ||
| 728 | + font-size: 0.8rem; | ||
| 729 | + } | ||
| 730 | + } | ||
| 731 | + | ||
| 732 | + .post-menu { | ||
| 733 | + display: flex; | ||
| 734 | + justify-content: space-between; | ||
| 735 | + align-items: center; | ||
| 736 | + margin-bottom: 1rem; | ||
| 737 | + } | ||
| 738 | + | ||
| 739 | + .post-content { | ||
| 740 | + .post-text { | ||
| 741 | + color: #666; | ||
| 742 | + margin-bottom: 1rem; | ||
| 743 | + } | ||
| 744 | + | ||
| 745 | + .post-media { | ||
| 746 | + .post-images { | ||
| 747 | + display: flex; | ||
| 748 | + flex-wrap: wrap; | ||
| 749 | + gap: 0.5rem; | ||
| 750 | + } | ||
| 751 | + | ||
| 752 | + .post-video { | ||
| 753 | + margin: 1rem 0; | ||
| 754 | + width: 100%; | ||
| 755 | + border-radius: 8px; | ||
| 756 | + overflow: hidden; | ||
| 757 | + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||
| 758 | + } | ||
| 759 | + | ||
| 760 | + .post-audio { | ||
| 761 | + margin: 1rem 0; | ||
| 762 | + } | ||
| 763 | + } | ||
| 764 | + } | ||
| 765 | + | ||
| 766 | + .post-footer { | ||
| 767 | + margin-top: 1rem; | ||
| 768 | + color: #666; | ||
| 769 | + | ||
| 770 | + .like-icon { | ||
| 771 | + margin-right: 0.25rem; | ||
| 772 | + } | ||
| 773 | + | ||
| 774 | + .like-count { | ||
| 775 | + font-size: 0.9rem; | ||
| 776 | + } | ||
| 777 | + } | ||
| 778 | +} | ||
| 331 | </style> | 779 | </style> | ... | ... |
-
Please register or login to post a comment