hookehuyr

feat(checkin): 实现打卡动态列表分页加载和日期选择优化

重构打卡动态列表为分页加载方式,提升性能
优化日期选择逻辑,自动重置分页参数并重新加载数据
修复日历日期匹配逻辑,支持完整日期格式匹配
<!--
* @Date: 2025-05-29 15:34:17
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-06-09 15:50:10
* @LastEditTime: 2025-06-10 10:20:08
* @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue
* @Description: 文件描述
-->
......@@ -67,83 +67,92 @@
<div class="text-wrapper">
<div class="text-header">打卡动态</div>
<div class="post-card" v-for="post in checkinDataList" :key="post.id">
<div class="post-header">
<van-row>
<van-col span="4">
<van-image round width="2.5rem" height="2.5rem" :src="post.user.avatar" fit="cover" />
</van-col>
<van-col span="17">
<div class="user-info">
<div class="username">{{ post.user.name }}</div>
<div class="post-time">{{ post.user.time }}</div>
</div>
</van-col>
<van-col span="3">
<div v-if="post.is_my" class="post-menu">
<van-icon name="edit" @click="editCheckin(post)" />
<van-icon name="delete-o" @click="delCheckin(post)" />
<van-list
v-if="checkinDataList.length"
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
class="px-4 py-3 space-y-4"
>
<div class="post-card" v-for="post in checkinDataList" :key="post.id">
<div class="post-header">
<van-row>
<van-col span="4">
<van-image round width="2.5rem" height="2.5rem" :src="post.user.avatar" fit="cover" />
</van-col>
<van-col span="17">
<div class="user-info">
<div class="username">{{ post.user.name }}</div>
<div class="post-time">{{ post.user.time }}</div>
</div>
</van-col>
<van-col span="3">
<div v-if="post.is_my" class="post-menu">
<van-icon name="edit" @click="editCheckin(post)" />
<van-icon name="delete-o" @click="delCheckin(post)" />
</div>
</van-col>
</van-row>
</div>
<div class="post-content">
<div class="post-text">{{ post.content }}</div>
<div class="post-media">
<div v-if="post.images.length" class="post-images">
<van-image width="30%" fit="cover" v-for="(image, index) in post.images" :key="index" :src="image" radius="5"
@click="openImagePreview(index, post)" />
</div>
</van-col>
</van-row>
</div>
<div class="post-content">
<div class="post-text">{{ post.content }}</div>
<div class="post-media">
<div v-if="post.images.length" class="post-images">
<van-image width="30%" fit="cover" v-for="(image, index) in post.images" :key="index" :src="image" radius="5"
@click="openImagePreview(index, post)" />
</div>
<van-image-preview v-if="currentPost" v-model:show="showImagePreview" :images="currentPost.images" :start-position="startPosition" :show-index="true" @change="onChange" />
<div v-for="(v, idx) in post.videoList" :key="idx">
<!-- 视频封面和播放按钮 -->
<div v-if="v.video && !v.isPlaying" class="relative w-full rounded-lg overflow-hidden" style="aspect-ratio: 16/9; margin-bottom: 1rem;">
<img :src="v.videoCover || 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png'"
:alt="v.content" class="w-full h-full object-cover" />
<div class="absolute inset-0 flex items-center justify-center cursor-pointer bg-black/20"
@click="startPlay(v)">
<div
class="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors">
<van-icon name="play-circle-o" class="text-white" size="40" />
<van-image-preview v-if="currentPost" v-model:show="showImagePreview" :images="currentPost.images" :start-position="startPosition" :show-index="true" @change="onChange" />
<div v-for="(v, idx) in post.videoList" :key="idx">
<!-- 视频封面和播放按钮 -->
<div v-if="v.video && !v.isPlaying" class="relative w-full rounded-lg overflow-hidden" style="aspect-ratio: 16/9; margin-bottom: 1rem;">
<img :src="v.videoCover || 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png'"
:alt="v.content" class="w-full h-full object-cover" />
<div class="absolute inset-0 flex items-center justify-center cursor-pointer bg-black/20"
@click="startPlay(v)">
<div
class="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors">
<van-icon name="play-circle-o" class="text-white" size="40" />
</div>
</div>
</div>
<!-- 视频播放器 -->
<VideoPlayer v-if="v.video && v.isPlaying" :video-url="v.video" class="post-video rounded-lg overflow-hidden"
:ref="el => {
if(el) {
// 确保不重复添加
if (!videoPlayers?.includes(el)) {
videoPlayers?.push(el);
}
}
}"
@onPlay="handleVideoPlay(player, post)"
@onPause="handleVideoPause(post)" />
</div>
<!-- 视频播放器 -->
<VideoPlayer v-if="v.video && v.isPlaying" :video-url="v.video" class="post-video rounded-lg overflow-hidden"
<AudioPlayer
v-if="post.audio.length"
:songs="post.audio"
class="post-audio"
:id="post.id"
:ref="el => {
if(el) {
// 确保不重复添加
if (!videoPlayers?.includes(el)) {
videoPlayers?.push(el);
if (!audioPlayers?.includes(el)) {
audioPlayers?.push(el);
}
}
}"
@onPlay="handleVideoPlay(player, post)"
@onPause="handleVideoPause(post)" />
@play="(player) => handleAudioPlay(player, post)"
/>
</div>
<AudioPlayer
v-if="post.audio.length"
:songs="post.audio"
class="post-audio"
:id="post.id"
:ref="el => {
if(el) {
// 确保不重复添加
if (!audioPlayers?.includes(el)) {
audioPlayers?.push(el);
}
}
}"
@play="(player) => handleAudioPlay(player, post)"
/>
</div>
<div class="post-footer">
<van-icon @click="handLike(post)"name="good-job" class="like-icon" :color="post.is_liked ? 'red' : ''" />
<span class="like-count">{{ post.likes }}</span>
</div>
</div>
<div class="post-footer">
<van-icon @click="handLike(post)"name="good-job" class="like-icon" :color="post.is_liked ? 'red' : ''" />
<span class="like-count">{{ post.likes }}</span>
</div>
</div>
<van-empty v-if="!checkinDataList.length" image="error" description="暂无数据" />
</van-list>
<van-empty v-else image="error" description="暂无数据" />
</div>
<div style="height: 5rem;"></div>
......@@ -353,33 +362,37 @@ const onChange = (index) => {
startPosition.value = index;
}
const formatter = (day) => {
const year = day.date.getFullYear();
const month = day.date.getMonth() + 1;
const date = day.date.getDate();
let checkin_days = myCheckinDates.value;
if (month === 6) {
if (checkin_days.includes(date)) {
// 检查当前日期是否在签到日期列表中
if (checkin_days && checkin_days.length > 0) {
// 格式化当前日期为YYYY-MM-DD格式,与checkin_days中的格式匹配
const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${date.toString().padStart(2, '0')}`;
// 检查是否已签到
if (checkin_days.includes(formattedDate)) {
day.className = 'calendar-checkin';
day.type = 'selected';
day.bottomInfo = '已签到'
day.bottomInfo = '已签到';
}
}
// 选中今天的日期
if (dayjs(day.date).isSame(new Date(), 'day')) {
day.className = 'calendar-today';
day.type ='selected';
day.bottomInfo = '今日'
day.type = 'selected';
day.bottomInfo = '今日';
}
return day;
}
const onSelectDay = (day) => {
console.warn('选择了日期', dayjs(day).format('YYYY-MM-DD'));
getTaskDetail(dayjs(day).format('YYYY-MM'));
// 获取打卡动态列表
initUploadTaskList(dayjs(day).format('YYYY-MM-DD'));
// 修改浏览器地址把当前的date加入地址栏, 页面不刷新
router.push({
path: route.path,
......@@ -388,6 +401,12 @@ const onSelectDay = (day) => {
date: dayjs(day).format('YYYY-MM-DD')
}
})
// 重置分页参数
page.value = 0
checkinDataList.value = []
finished.value = false
// 重新加载数据
onLoad(dayjs(day).format('YYYY-MM-DD'))
}
const onClickSubtitle = (evt) => {
......@@ -442,7 +461,6 @@ const handLike = async (post) => {
}
const editCheckin = (post) => {
console.warn(post);
if (post.file_type === 'image') {
router.push({
path: '/checkin/image',
......@@ -505,7 +523,6 @@ const showProgress = ref(true);
const getTaskDetail = async (month) => {
const { code, data } = await getTaskDetailAPI({ i: route.query.id, month });
if (code) {
console.warn(data);
taskDetail.value = data;
progress1.value = (data.checkin_number/data.target_number)*100; // 计算进度条百分比
showProgress.value = !isNaN(progress1.value); // 如果是NaN,就不显示进度条
......@@ -513,37 +530,45 @@ const getTaskDetail = async (month) => {
// 获取当前用户的打卡日期
myCheckinDates.value = data.my_checkin_dates;
// 把['2025-06-06'] 转化为 [6] 只取日期去掉0
myCheckinDates.value = myCheckinDates.value.map(date => {
return dayjs(date).date();
})
// myCheckinDates.value = myCheckinDates.value.map(date => {
// return dayjs(date).date();
// })
}
}
const initUploadTaskList = async (date) => {
const toast = showLoadingToast({
message: '加载中...',
forbidClick: true,
const loading = ref(false)
const finished = ref(false)
const limit = ref(3)
const page = ref(0)
const onLoad = async (date) => {
const nextPage = page.value;
const current_date = date || route.query.date || dayjs().format('YYYY-MM-DD');
//
const res = await getUploadTaskListAPI({
limit: limit.value,
page: nextPage,
task_id: route.query.id,
date: current_date
});
const { code, data } = await getUploadTaskListAPI({ task_id: route.query.id, date, limit: 999 });
if (code) {
console.warn(data?.checkin_list);
if (res.code) {
// 整理数据结构
checkinDataList.value = formatData(data)
toast.close();
checkinDataList.value = [...checkinDataList.value, ...formatData(res.data)];
finished.value = res.data.checkin_list.length < limit.value;
page.value = nextPage + 1;
}
}
loading.value = false;
};
onMounted(async () => {
const current_date = route.query.date;
if (current_date) {
getTaskDetail(dayjs(current_date).format('YYYY-MM'));
// 获取打卡动态列表
initUploadTaskList(current_date);
myRefCalendar.value?.reset(new Date(current_date));
onLoad(current_date);
} else {
getTaskDetail(dayjs().format('YYYY-MM'));
// 获取打卡动态列表
initUploadTaskList(dayjs().format('YYYY-MM-DD'));
onLoad(dayjs().format('YYYY-MM-DD'));
}
})
......@@ -606,6 +631,12 @@ const formatData = (data) => {
}
}
.calendar-today {
.van-calendar__selected-day {
background: #FAAB0C !important;
}
}
.text-wrapper {
padding: 1rem;
color: #4caf50;
......