feat(路由): 添加keep-alive缓存IndexCheckInPage组件
refactor(路由): 修改滚动行为以记住位置 feat(打卡): 添加打卡后列表自动刷新功能 fix(打卡列表): 解决重复数据加载问题
Showing
5 changed files
with
106 additions
and
14 deletions
| ... | @@ -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"> | ... | ... |
-
Please register or login to post a comment