feat(checkin): 实现打卡动态列表分页加载和日期选择优化
重构打卡动态列表为分页加载方式,提升性能 优化日期选择逻辑,自动重置分页参数并重新加载数据 修复日历日期匹配逻辑,支持完整日期格式匹配
Showing
1 changed file
with
123 additions
and
92 deletions
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2025-05-29 15:34:17 | 2 | * @Date: 2025-05-29 15:34:17 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-06-09 15:50:10 | 4 | + * @LastEditTime: 2025-06-10 10:20:08 |
| 5 | * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue | 5 | * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | --> | 7 | --> |
| ... | @@ -67,83 +67,92 @@ | ... | @@ -67,83 +67,92 @@ |
| 67 | 67 | ||
| 68 | <div class="text-wrapper"> | 68 | <div class="text-wrapper"> |
| 69 | <div class="text-header">打卡动态</div> | 69 | <div class="text-header">打卡动态</div> |
| 70 | - <div class="post-card" v-for="post in checkinDataList" :key="post.id"> | 70 | + <van-list |
| 71 | - <div class="post-header"> | 71 | + v-if="checkinDataList.length" |
| 72 | - <van-row> | 72 | + v-model:loading="loading" |
| 73 | - <van-col span="4"> | 73 | + :finished="finished" |
| 74 | - <van-image round width="2.5rem" height="2.5rem" :src="post.user.avatar" fit="cover" /> | 74 | + finished-text="没有更多了" |
| 75 | - </van-col> | 75 | + @load="onLoad" |
| 76 | - <van-col span="17"> | 76 | + class="px-4 py-3 space-y-4" |
| 77 | - <div class="user-info"> | 77 | + > |
| 78 | - <div class="username">{{ post.user.name }}</div> | 78 | + <div class="post-card" v-for="post in checkinDataList" :key="post.id"> |
| 79 | - <div class="post-time">{{ post.user.time }}</div> | 79 | + <div class="post-header"> |
| 80 | - </div> | 80 | + <van-row> |
| 81 | - </van-col> | 81 | + <van-col span="4"> |
| 82 | - <van-col span="3"> | 82 | + <van-image round width="2.5rem" height="2.5rem" :src="post.user.avatar" fit="cover" /> |
| 83 | - <div v-if="post.is_my" class="post-menu"> | 83 | + </van-col> |
| 84 | - <van-icon name="edit" @click="editCheckin(post)" /> | 84 | + <van-col span="17"> |
| 85 | - <van-icon name="delete-o" @click="delCheckin(post)" /> | 85 | + <div class="user-info"> |
| 86 | + <div class="username">{{ post.user.name }}</div> | ||
| 87 | + <div class="post-time">{{ post.user.time }}</div> | ||
| 88 | + </div> | ||
| 89 | + </van-col> | ||
| 90 | + <van-col span="3"> | ||
| 91 | + <div v-if="post.is_my" class="post-menu"> | ||
| 92 | + <van-icon name="edit" @click="editCheckin(post)" /> | ||
| 93 | + <van-icon name="delete-o" @click="delCheckin(post)" /> | ||
| 94 | + </div> | ||
| 95 | + </van-col> | ||
| 96 | + </van-row> | ||
| 97 | + </div> | ||
| 98 | + <div class="post-content"> | ||
| 99 | + <div class="post-text">{{ post.content }}</div> | ||
| 100 | + <div class="post-media"> | ||
| 101 | + <div v-if="post.images.length" class="post-images"> | ||
| 102 | + <van-image width="30%" fit="cover" v-for="(image, index) in post.images" :key="index" :src="image" radius="5" | ||
| 103 | + @click="openImagePreview(index, post)" /> | ||
| 86 | </div> | 104 | </div> |
| 87 | - </van-col> | 105 | + <van-image-preview v-if="currentPost" v-model:show="showImagePreview" :images="currentPost.images" :start-position="startPosition" :show-index="true" @change="onChange" /> |
| 88 | - </van-row> | 106 | + <div v-for="(v, idx) in post.videoList" :key="idx"> |
| 89 | - </div> | 107 | + <!-- 视频封面和播放按钮 --> |
| 90 | - <div class="post-content"> | 108 | + <div v-if="v.video && !v.isPlaying" class="relative w-full rounded-lg overflow-hidden" style="aspect-ratio: 16/9; margin-bottom: 1rem;"> |
| 91 | - <div class="post-text">{{ post.content }}</div> | 109 | + <img :src="v.videoCover || 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png'" |
| 92 | - <div class="post-media"> | 110 | + :alt="v.content" class="w-full h-full object-cover" /> |
| 93 | - <div v-if="post.images.length" class="post-images"> | 111 | + <div class="absolute inset-0 flex items-center justify-center cursor-pointer bg-black/20" |
| 94 | - <van-image width="30%" fit="cover" v-for="(image, index) in post.images" :key="index" :src="image" radius="5" | 112 | + @click="startPlay(v)"> |
| 95 | - @click="openImagePreview(index, post)" /> | 113 | + <div |
| 96 | - </div> | 114 | + class="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors"> |
| 97 | - <van-image-preview v-if="currentPost" v-model:show="showImagePreview" :images="currentPost.images" :start-position="startPosition" :show-index="true" @change="onChange" /> | 115 | + <van-icon name="play-circle-o" class="text-white" size="40" /> |
| 98 | - <div v-for="(v, idx) in post.videoList" :key="idx"> | 116 | + </div> |
| 99 | - <!-- 视频封面和播放按钮 --> | ||
| 100 | - <div v-if="v.video && !v.isPlaying" class="relative w-full rounded-lg overflow-hidden" style="aspect-ratio: 16/9; margin-bottom: 1rem;"> | ||
| 101 | - <img :src="v.videoCover || 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png'" | ||
| 102 | - :alt="v.content" class="w-full h-full object-cover" /> | ||
| 103 | - <div class="absolute inset-0 flex items-center justify-center cursor-pointer bg-black/20" | ||
| 104 | - @click="startPlay(v)"> | ||
| 105 | - <div | ||
| 106 | - class="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors"> | ||
| 107 | - <van-icon name="play-circle-o" class="text-white" size="40" /> | ||
| 108 | </div> | 117 | </div> |
| 109 | </div> | 118 | </div> |
| 119 | + <!-- 视频播放器 --> | ||
| 120 | + <VideoPlayer v-if="v.video && v.isPlaying" :video-url="v.video" class="post-video rounded-lg overflow-hidden" | ||
| 121 | + :ref="el => { | ||
| 122 | + if(el) { | ||
| 123 | + // 确保不重复添加 | ||
| 124 | + if (!videoPlayers?.includes(el)) { | ||
| 125 | + videoPlayers?.push(el); | ||
| 126 | + } | ||
| 127 | + } | ||
| 128 | + }" | ||
| 129 | + @onPlay="handleVideoPlay(player, post)" | ||
| 130 | + @onPause="handleVideoPause(post)" /> | ||
| 110 | </div> | 131 | </div> |
| 111 | - <!-- 视频播放器 --> | 132 | + <AudioPlayer |
| 112 | - <VideoPlayer v-if="v.video && v.isPlaying" :video-url="v.video" class="post-video rounded-lg overflow-hidden" | 133 | + v-if="post.audio.length" |
| 134 | + :songs="post.audio" | ||
| 135 | + class="post-audio" | ||
| 136 | + :id="post.id" | ||
| 113 | :ref="el => { | 137 | :ref="el => { |
| 114 | if(el) { | 138 | if(el) { |
| 115 | // 确保不重复添加 | 139 | // 确保不重复添加 |
| 116 | - if (!videoPlayers?.includes(el)) { | 140 | + if (!audioPlayers?.includes(el)) { |
| 117 | - videoPlayers?.push(el); | 141 | + audioPlayers?.push(el); |
| 118 | } | 142 | } |
| 119 | } | 143 | } |
| 120 | }" | 144 | }" |
| 121 | - @onPlay="handleVideoPlay(player, post)" | 145 | + @play="(player) => handleAudioPlay(player, post)" |
| 122 | - @onPause="handleVideoPause(post)" /> | 146 | + /> |
| 123 | </div> | 147 | </div> |
| 124 | - <AudioPlayer | 148 | + </div> |
| 125 | - v-if="post.audio.length" | 149 | + <div class="post-footer"> |
| 126 | - :songs="post.audio" | 150 | + <van-icon @click="handLike(post)"name="good-job" class="like-icon" :color="post.is_liked ? 'red' : ''" /> |
| 127 | - class="post-audio" | 151 | + <span class="like-count">{{ post.likes }}</span> |
| 128 | - :id="post.id" | ||
| 129 | - :ref="el => { | ||
| 130 | - if(el) { | ||
| 131 | - // 确保不重复添加 | ||
| 132 | - if (!audioPlayers?.includes(el)) { | ||
| 133 | - audioPlayers?.push(el); | ||
| 134 | - } | ||
| 135 | - } | ||
| 136 | - }" | ||
| 137 | - @play="(player) => handleAudioPlay(player, post)" | ||
| 138 | - /> | ||
| 139 | </div> | 152 | </div> |
| 140 | </div> | 153 | </div> |
| 141 | - <div class="post-footer"> | 154 | + </van-list> |
| 142 | - <van-icon @click="handLike(post)"name="good-job" class="like-icon" :color="post.is_liked ? 'red' : ''" /> | 155 | + <van-empty v-else image="error" description="暂无数据" /> |
| 143 | - <span class="like-count">{{ post.likes }}</span> | ||
| 144 | - </div> | ||
| 145 | - </div> | ||
| 146 | - <van-empty v-if="!checkinDataList.length" image="error" description="暂无数据" /> | ||
| 147 | </div> | 156 | </div> |
| 148 | 157 | ||
| 149 | <div style="height: 5rem;"></div> | 158 | <div style="height: 5rem;"></div> |
| ... | @@ -353,33 +362,37 @@ const onChange = (index) => { | ... | @@ -353,33 +362,37 @@ const onChange = (index) => { |
| 353 | startPosition.value = index; | 362 | startPosition.value = index; |
| 354 | } | 363 | } |
| 355 | const formatter = (day) => { | 364 | const formatter = (day) => { |
| 365 | + const year = day.date.getFullYear(); | ||
| 356 | const month = day.date.getMonth() + 1; | 366 | const month = day.date.getMonth() + 1; |
| 357 | const date = day.date.getDate(); | 367 | const date = day.date.getDate(); |
| 358 | 368 | ||
| 359 | let checkin_days = myCheckinDates.value; | 369 | let checkin_days = myCheckinDates.value; |
| 360 | 370 | ||
| 361 | - if (month === 6) { | 371 | + // 检查当前日期是否在签到日期列表中 |
| 362 | - if (checkin_days.includes(date)) { | 372 | + if (checkin_days && checkin_days.length > 0) { |
| 373 | + // 格式化当前日期为YYYY-MM-DD格式,与checkin_days中的格式匹配 | ||
| 374 | + const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${date.toString().padStart(2, '0')}`; | ||
| 375 | + | ||
| 376 | + // 检查是否已签到 | ||
| 377 | + if (checkin_days.includes(formattedDate)) { | ||
| 363 | day.className = 'calendar-checkin'; | 378 | day.className = 'calendar-checkin'; |
| 364 | day.type = 'selected'; | 379 | day.type = 'selected'; |
| 365 | - day.bottomInfo = '已签到' | 380 | + day.bottomInfo = '已签到'; |
| 366 | } | 381 | } |
| 367 | } | 382 | } |
| 383 | + | ||
| 368 | // 选中今天的日期 | 384 | // 选中今天的日期 |
| 369 | if (dayjs(day.date).isSame(new Date(), 'day')) { | 385 | if (dayjs(day.date).isSame(new Date(), 'day')) { |
| 370 | day.className = 'calendar-today'; | 386 | day.className = 'calendar-today'; |
| 371 | - day.type ='selected'; | 387 | + day.type = 'selected'; |
| 372 | - day.bottomInfo = '今日' | 388 | + day.bottomInfo = '今日'; |
| 373 | } | 389 | } |
| 374 | 390 | ||
| 375 | return day; | 391 | return day; |
| 376 | } | 392 | } |
| 377 | 393 | ||
| 378 | const onSelectDay = (day) => { | 394 | const onSelectDay = (day) => { |
| 379 | - console.warn('选择了日期', dayjs(day).format('YYYY-MM-DD')); | ||
| 380 | getTaskDetail(dayjs(day).format('YYYY-MM')); | 395 | getTaskDetail(dayjs(day).format('YYYY-MM')); |
| 381 | - // 获取打卡动态列表 | ||
| 382 | - initUploadTaskList(dayjs(day).format('YYYY-MM-DD')); | ||
| 383 | // 修改浏览器地址把当前的date加入地址栏, 页面不刷新 | 396 | // 修改浏览器地址把当前的date加入地址栏, 页面不刷新 |
| 384 | router.push({ | 397 | router.push({ |
| 385 | path: route.path, | 398 | path: route.path, |
| ... | @@ -388,6 +401,12 @@ const onSelectDay = (day) => { | ... | @@ -388,6 +401,12 @@ const onSelectDay = (day) => { |
| 388 | date: dayjs(day).format('YYYY-MM-DD') | 401 | date: dayjs(day).format('YYYY-MM-DD') |
| 389 | } | 402 | } |
| 390 | }) | 403 | }) |
| 404 | + // 重置分页参数 | ||
| 405 | + page.value = 0 | ||
| 406 | + checkinDataList.value = [] | ||
| 407 | + finished.value = false | ||
| 408 | + // 重新加载数据 | ||
| 409 | + onLoad(dayjs(day).format('YYYY-MM-DD')) | ||
| 391 | } | 410 | } |
| 392 | 411 | ||
| 393 | const onClickSubtitle = (evt) => { | 412 | const onClickSubtitle = (evt) => { |
| ... | @@ -442,7 +461,6 @@ const handLike = async (post) => { | ... | @@ -442,7 +461,6 @@ const handLike = async (post) => { |
| 442 | } | 461 | } |
| 443 | 462 | ||
| 444 | const editCheckin = (post) => { | 463 | const editCheckin = (post) => { |
| 445 | - console.warn(post); | ||
| 446 | if (post.file_type === 'image') { | 464 | if (post.file_type === 'image') { |
| 447 | router.push({ | 465 | router.push({ |
| 448 | path: '/checkin/image', | 466 | path: '/checkin/image', |
| ... | @@ -505,7 +523,6 @@ const showProgress = ref(true); | ... | @@ -505,7 +523,6 @@ const showProgress = ref(true); |
| 505 | const getTaskDetail = async (month) => { | 523 | const getTaskDetail = async (month) => { |
| 506 | const { code, data } = await getTaskDetailAPI({ i: route.query.id, month }); | 524 | const { code, data } = await getTaskDetailAPI({ i: route.query.id, month }); |
| 507 | if (code) { | 525 | if (code) { |
| 508 | - console.warn(data); | ||
| 509 | taskDetail.value = data; | 526 | taskDetail.value = data; |
| 510 | progress1.value = (data.checkin_number/data.target_number)*100; // 计算进度条百分比 | 527 | progress1.value = (data.checkin_number/data.target_number)*100; // 计算进度条百分比 |
| 511 | showProgress.value = !isNaN(progress1.value); // 如果是NaN,就不显示进度条 | 528 | showProgress.value = !isNaN(progress1.value); // 如果是NaN,就不显示进度条 |
| ... | @@ -513,37 +530,45 @@ const getTaskDetail = async (month) => { | ... | @@ -513,37 +530,45 @@ const getTaskDetail = async (month) => { |
| 513 | // 获取当前用户的打卡日期 | 530 | // 获取当前用户的打卡日期 |
| 514 | myCheckinDates.value = data.my_checkin_dates; | 531 | myCheckinDates.value = data.my_checkin_dates; |
| 515 | // 把['2025-06-06'] 转化为 [6] 只取日期去掉0 | 532 | // 把['2025-06-06'] 转化为 [6] 只取日期去掉0 |
| 516 | - myCheckinDates.value = myCheckinDates.value.map(date => { | 533 | + // myCheckinDates.value = myCheckinDates.value.map(date => { |
| 517 | - return dayjs(date).date(); | 534 | + // return dayjs(date).date(); |
| 518 | - }) | 535 | + // }) |
| 519 | } | 536 | } |
| 520 | } | 537 | } |
| 521 | 538 | ||
| 522 | -const initUploadTaskList = async (date) => { | 539 | +const loading = ref(false) |
| 523 | - const toast = showLoadingToast({ | 540 | +const finished = ref(false) |
| 524 | - message: '加载中...', | 541 | +const limit = ref(3) |
| 525 | - forbidClick: true, | 542 | +const page = ref(0) |
| 543 | + | ||
| 544 | +const onLoad = async (date) => { | ||
| 545 | + const nextPage = page.value; | ||
| 546 | + const current_date = date || route.query.date || dayjs().format('YYYY-MM-DD'); | ||
| 547 | + // | ||
| 548 | + const res = await getUploadTaskListAPI({ | ||
| 549 | + limit: limit.value, | ||
| 550 | + page: nextPage, | ||
| 551 | + task_id: route.query.id, | ||
| 552 | + date: current_date | ||
| 526 | }); | 553 | }); |
| 527 | - const { code, data } = await getUploadTaskListAPI({ task_id: route.query.id, date, limit: 999 }); | 554 | + if (res.code) { |
| 528 | - if (code) { | ||
| 529 | - console.warn(data?.checkin_list); | ||
| 530 | // 整理数据结构 | 555 | // 整理数据结构 |
| 531 | - checkinDataList.value = formatData(data) | 556 | + checkinDataList.value = [...checkinDataList.value, ...formatData(res.data)]; |
| 532 | - toast.close(); | 557 | + finished.value = res.data.checkin_list.length < limit.value; |
| 558 | + page.value = nextPage + 1; | ||
| 533 | } | 559 | } |
| 534 | -} | 560 | + loading.value = false; |
| 561 | +}; | ||
| 535 | 562 | ||
| 536 | onMounted(async () => { | 563 | onMounted(async () => { |
| 537 | const current_date = route.query.date; | 564 | const current_date = route.query.date; |
| 538 | if (current_date) { | 565 | if (current_date) { |
| 539 | getTaskDetail(dayjs(current_date).format('YYYY-MM')); | 566 | getTaskDetail(dayjs(current_date).format('YYYY-MM')); |
| 540 | - // 获取打卡动态列表 | ||
| 541 | - initUploadTaskList(current_date); | ||
| 542 | myRefCalendar.value?.reset(new Date(current_date)); | 567 | myRefCalendar.value?.reset(new Date(current_date)); |
| 568 | + onLoad(current_date); | ||
| 543 | } else { | 569 | } else { |
| 544 | getTaskDetail(dayjs().format('YYYY-MM')); | 570 | getTaskDetail(dayjs().format('YYYY-MM')); |
| 545 | - // 获取打卡动态列表 | 571 | + onLoad(dayjs().format('YYYY-MM-DD')); |
| 546 | - initUploadTaskList(dayjs().format('YYYY-MM-DD')); | ||
| 547 | } | 572 | } |
| 548 | }) | 573 | }) |
| 549 | 574 | ||
| ... | @@ -606,6 +631,12 @@ const formatData = (data) => { | ... | @@ -606,6 +631,12 @@ const formatData = (data) => { |
| 606 | } | 631 | } |
| 607 | } | 632 | } |
| 608 | 633 | ||
| 634 | +.calendar-today { | ||
| 635 | + .van-calendar__selected-day { | ||
| 636 | + background: #FAAB0C !important; | ||
| 637 | + } | ||
| 638 | +} | ||
| 639 | + | ||
| 609 | .text-wrapper { | 640 | .text-wrapper { |
| 610 | padding: 1rem; | 641 | padding: 1rem; |
| 611 | color: #4caf50; | 642 | color: #4caf50; | ... | ... |
-
Please register or login to post a comment