hookehuyr

feat(路由): 添加keep-alive缓存IndexCheckInPage组件

refactor(路由): 修改滚动行为以记住位置
feat(打卡): 添加打卡后列表自动刷新功能
fix(打卡列表): 解决重复数据加载问题
...@@ -87,7 +87,9 @@ onUnmounted(() => { ...@@ -87,7 +87,9 @@ onUnmounted(() => {
87 <!-- 通过v-slot获取当前路由组件。使用component是为了实现动态组件渲染和加载状态的控制:当Component存在时渲染路由组件,不存在时显示加载动画。直接使用 --> 87 <!-- 通过v-slot获取当前路由组件。使用component是为了实现动态组件渲染和加载状态的控制:当Component存在时渲染路由组件,不存在时显示加载动画。直接使用 -->
88 <router-view v-slot="{ Component }"> 88 <router-view v-slot="{ Component }">
89 <div v-if="Component"> 89 <div v-if="Component">
90 - <component :is="Component" /> 90 + <keep-alive :include="['IndexCheckInPage']">
91 + <component :is="Component" />
92 + </keep-alive>
91 </div> 93 </div>
92 <div v-else 94 <div v-else
93 class="flex items-center justify-center h-screen bg-gradient-to-br from-green-50 via-teal-50 to-blue-50"> 95 class="flex items-center justify-center h-screen bg-gradient-to-br from-green-50 via-teal-50 to-blue-50">
......
...@@ -331,6 +331,18 @@ export function useCheckin() { ...@@ -331,6 +331,18 @@ export function useCheckin() {
331 331
332 if (result.code) { 332 if (result.code) {
333 showToast('提交成功') 333 showToast('提交成功')
334 +
335 + // 设置刷新标记,用于列表页更新数据
336 + const refreshType = route.query.status === 'edit' ? 'edit' : 'add';
337 + sessionStorage.setItem('checkin_refresh_flag', refreshType);
338 + if (refreshType === 'edit') {
339 + sessionStorage.setItem('checkin_refresh_id', route.query.post_id);
340 + } else if (result.data) {
341 + // 尝试获取新ID
342 + const newId = result.data.id || (typeof result.data !== 'object' ? result.data : null);
343 + if (newId) sessionStorage.setItem('checkin_refresh_id', newId);
344 + }
345 +
334 router.back() 346 router.back()
335 } 347 }
336 } catch (error) { 348 } catch (error) {
......
1 /* 1 /*
2 * @Date: 2025-03-20 20:36:36 2 * @Date: 2025-03-20 20:36:36
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-04 11:25:22 4 + * @LastEditTime: 2025-12-19 00:16:42
5 * @FilePath: /mlaj/src/router/index.js 5 * @FilePath: /mlaj/src/router/index.js
6 * @Description: 路由实例创建和导出 6 * @Description: 路由实例创建和导出
7 */ 7 */
...@@ -12,12 +12,15 @@ import { getUserIsLoginAPI } from '@/api/auth' ...@@ -12,12 +12,15 @@ import { getUserIsLoginAPI } from '@/api/auth'
12 import { getUserInfoAPI } from '@/api/users' 12 import { getUserInfoAPI } from '@/api/users'
13 13
14 const router = createRouter({ 14 const router = createRouter({
15 - history: createWebHashHistory(import.meta.env.VITE_BASE || '/'), 15 + history: createWebHashHistory(import.meta.env.VITE_BASE || '/'),
16 - routes, 16 + routes,
17 - scrollBehavior() { 17 + scrollBehavior(to, from, savedPosition) {
18 - // 每次路由切换后,页面滚动到顶部 18 + if (savedPosition) {
19 - return { top: 0, left: 0 } 19 + return savedPosition
20 - }, 20 + }
21 + // 每次路由切换后,页面滚动到顶部
22 + return { top: 0, left: 0 }
23 + },
21 }) 24 })
22 25
23 // 导航守卫 26 // 导航守卫
......
1 <!-- 1 <!--
2 * @Date: 2025-03-20 19:55:21 2 * @Date: 2025-03-20 19:55:21
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-18 11:22:03 4 + * @LastEditTime: 2025-12-18 23:53:12
5 * @FilePath: /mlaj/src/views/HomePage.vue 5 * @FilePath: /mlaj/src/views/HomePage.vue
6 * @Description: 美乐爱觉教育首页组件 6 * @Description: 美乐爱觉教育首页组件
7 * 7 *
...@@ -496,7 +496,7 @@ import SummerCampCard from '@/components/ui/SummerCampCard.vue' ...@@ -496,7 +496,7 @@ import SummerCampCard from '@/components/ui/SummerCampCard.vue'
496 import VideoPlayer from '@/components/ui/VideoPlayer.vue' 496 import VideoPlayer from '@/components/ui/VideoPlayer.vue'
497 import CheckInList from '@/components/ui/CheckInList.vue' 497 import CheckInList from '@/components/ui/CheckInList.vue'
498 498
499 -// TODO: 导入模拟数据和工具函数 499 +// 导入模拟数据和工具函数
500 import { liveStreams } from '@/utils/mockData' 500 import { liveStreams } from '@/utils/mockData'
501 import { useTitle } from '@vueuse/core' 501 import { useTitle } from '@vueuse/core'
502 import { useAuth } from '@/contexts/auth' 502 import { useAuth } from '@/contexts/auth'
......
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-12-18 23:48:47 4 + * @LastEditTime: 2025-12-19 00:12:31
5 * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue 5 * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -137,8 +137,14 @@ ...@@ -137,8 +137,14 @@
137 </AppLayout> 137 </AppLayout>
138 </template> 138 </template>
139 139
140 +<script>
141 +export default {
142 + name: 'IndexCheckInPage'
143 +}
144 +</script>
145 +
140 <script setup> 146 <script setup>
141 -import { ref, onBeforeUnmount, onMounted, computed, nextTick, getCurrentInstance } from 'vue' 147 +import { ref, onBeforeUnmount, onMounted, computed, nextTick, getCurrentInstance, onActivated, onDeactivated } from 'vue'
142 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router' 148 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router'
143 import { showConfirmDialog, showSuccessToast, showFailToast, showLoadingToast } from 'vant'; 149 import { showConfirmDialog, showSuccessToast, showFailToast, showLoadingToast } from 'vant';
144 import AppLayout from "@/components/layout/AppLayout.vue"; 150 import AppLayout from "@/components/layout/AppLayout.vue";
...@@ -149,7 +155,7 @@ import CheckinCard from "@/components/checkin/CheckinCard.vue"; ...@@ -149,7 +155,7 @@ import CheckinCard from "@/components/checkin/CheckinCard.vue";
149 import { useTitle, useResizeObserver, useScroll } from '@vueuse/core'; 155 import { useTitle, useResizeObserver, useScroll } from '@vueuse/core';
150 import dayjs from 'dayjs'; 156 import dayjs from 'dayjs';
151 157
152 -import { getTaskDetailAPI, getUploadTaskListAPI, delUploadTaskInfoAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI } from "@/api/checkin"; 158 +import { getTaskDetailAPI, getUploadTaskListAPI, delUploadTaskInfoAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI, getUploadTaskInfoAPI } from "@/api/checkin";
153 import { getTeacherFindSettingsAPI } from "@/api/teacher"; 159 import { getTeacherFindSettingsAPI } from "@/api/teacher";
154 160
155 const route = useRoute() 161 const route = useRoute()
...@@ -595,7 +601,15 @@ const onLoad = async (date) => { ...@@ -595,7 +601,15 @@ const onLoad = async (date) => {
595 }); 601 });
596 if (res.code) { 602 if (res.code) {
597 // 整理数据结构 603 // 整理数据结构
598 - checkinDataList.value = [...checkinDataList.value, ...formatData(res.data)]; 604 + const newItems = formatData(res.data);
605 + // 去重合并
606 + const existingIds = new Set(checkinDataList.value.map(item => item.id));
607 + const uniqueNewItems = newItems.filter(item => !existingIds.has(item.id));
608 +
609 + if (uniqueNewItems.length > 0) {
610 + checkinDataList.value.push(...uniqueNewItems);
611 + }
612 +
599 finished.value = res.data.checkin_list.length < limit.value; 613 finished.value = res.data.checkin_list.length < limit.value;
600 page.value = nextPage + 1; 614 page.value = nextPage + 1;
601 } 615 }
...@@ -696,6 +710,67 @@ const formatData = (data) => { ...@@ -696,6 +710,67 @@ const formatData = (data) => {
696 }) 710 })
697 return formattedData; 711 return formattedData;
698 } 712 }
713 +
714 +// 保存滚动位置
715 +const savedScrollTop = ref(0)
716 +
717 +onDeactivated(() => {
718 + savedScrollTop.value = window.scrollY
719 +})
720 +
721 +onActivated(async () => {
722 + // 恢复滚动位置
723 + if (savedScrollTop.value > 0) {
724 + setTimeout(() => {
725 + window.scrollTo(0, savedScrollTop.value)
726 + }, 0)
727 + }
728 +
729 + // 检查是否有数据刷新标记
730 + const refreshType = sessionStorage.getItem('checkin_refresh_flag')
731 + const refreshId = sessionStorage.getItem('checkin_refresh_id')
732 +
733 + if (refreshType) {
734 + // 清除标记
735 + sessionStorage.removeItem('checkin_refresh_flag')
736 + sessionStorage.removeItem('checkin_refresh_id')
737 +
738 + if (refreshId) {
739 + try {
740 + // 获取最新的打卡信息
741 + const { code, data } = await getUploadTaskInfoAPI({ i: refreshId })
742 + if (code && data) {
743 + // 构造伪造的 data 对象以适配 formatData
744 + const mockData = { checkin_list: [data] }
745 + const formattedList = formatData(mockData)
746 +
747 + if (formattedList && formattedList.length > 0) {
748 + const formattedItem = formattedList[0]
749 +
750 + if (refreshType === 'edit') {
751 + // 编辑模式:更新列表中的对应项
752 + const index = checkinDataList.value.findIndex(item => item.id == refreshId)
753 + if (index > -1) {
754 + checkinDataList.value.splice(index, 1, formattedItem)
755 + }
756 + } else if (refreshType === 'add') {
757 + // 新增模式:添加到列表顶部,且去重
758 + const exists = checkinDataList.value.some(item => item.id == formattedItem.id)
759 + if (!exists) {
760 + checkinDataList.value.unshift(formattedItem)
761 + }
762 + // 更新统计数据
763 + const current_date = route.query.date || dayjs().format('YYYY-MM-DD');
764 + getTaskDetail(dayjs(current_date).format('YYYY-MM'));
765 + }
766 + }
767 + }
768 + } catch (error) {
769 + console.error('刷新打卡数据失败:', error)
770 + }
771 + }
772 + }
773 +})
699 </script> 774 </script>
700 775
701 <style lang="less"> 776 <style lang="less">
......