diff --git a/src/api/trainingTask.js b/src/api/trainingTask.js index e99eda6..67a0468 100644 --- a/src/api/trainingTask.js +++ b/src/api/trainingTask.js @@ -1,8 +1,11 @@ import request from "@/utils/request"; +import { getUserInfo } from "@/utils/auth"; // 学生页面和全局“补充步骤”导航会在同一轮路由渲染中读取同一任务配置。 // 仅合并进行中的请求:教师保存后下一次读取仍会获得最新配置,不引入陈旧缓存。 const pendingTaskRequests = new Map(); +const pendingStudentTaskListRequests = new Map(); +const STUDENT_TASK_NAVIGATION_CACHE_PREFIX = "student-training-task-navigation:v1:"; function notifyStudentTaskConfigLoading(taskKey, loading) { if (typeof window === "undefined") return; @@ -17,6 +20,51 @@ export function listTrainingTasks(params) { }); } +function getStudentTaskNavigationCacheKey() { + try { + const userInfo = JSON.parse(getUserInfo() || "{}"); + const userId = userInfo.userId || userInfo.user_id || userInfo.username || userInfo.userName; + return userId ? `${STUDENT_TASK_NAVIGATION_CACHE_PREFIX}${userId}` : ""; + } catch (error) { + return ""; + } +} + +export function getCachedStudentTrainingTasks() { + const cacheKey = getStudentTaskNavigationCacheKey(); + if (!cacheKey || typeof window === "undefined") return []; + try { + const cached = JSON.parse(window.sessionStorage.getItem(cacheKey) || "[]"); + return Array.isArray(cached) ? cached : []; + } catch (error) { + return []; + } +} + +/** + * The navigation and top bar both need the same enabled task list. Reuse one + * in-flight request and retain the last successful response for immediate menu rendering. + */ +export function loadStudentTrainingTasks() { + const cacheKey = getStudentTaskNavigationCacheKey() || "anonymous"; + if (pendingStudentTaskListRequests.has(cacheKey)) { + return pendingStudentTaskListRequests.get(cacheKey); + } + const requestPromise = listTrainingTasks({ enabledOnly: true }).then((res) => { + const tasks = Array.isArray(res?.data) ? res.data : []; + if (cacheKey !== "anonymous" && typeof window !== "undefined") { + window.sessionStorage.setItem(cacheKey, JSON.stringify(tasks)); + } + return { ...res, data: tasks }; + }); + pendingStudentTaskListRequests.set(cacheKey, requestPromise); + requestPromise.then( + () => pendingStudentTaskListRequests.delete(cacheKey), + () => pendingStudentTaskListRequests.delete(cacheKey) + ); + return requestPromise; +} + export function getTrainingTaskByKey(taskKey) { const normalizedTaskKey = String(taskKey || "").trim(); if (pendingTaskRequests.has(normalizedTaskKey)) { diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue index c799bfe..9940ec6 100644 --- a/src/layout/components/Navbar.vue +++ b/src/layout/components/Navbar.vue @@ -81,7 +81,7 @@ import useAppStore from "@/store/modules/app"; import useUserStore from "@/store/modules/user"; import useSettingsStore from "@/store/modules/settings"; import * as indexApi from "@/api/teacher"; -import { listTrainingTasks } from "@/api/trainingTask"; +import { getCachedStudentTrainingTasks, loadStudentTrainingTasks } from "@/api/trainingTask"; import { getStudentDemoSession, isStudentDemo } from "@/utils/studentDemo"; import { getComprehensiveTopTask } from "@/utils/studentTrainingNavigation"; const { proxy } = getCurrentInstance(); @@ -289,11 +289,17 @@ async function loadStudentTopTask() { studentTopTask.value = null; return; } + const cachedTasks = getCachedStudentTrainingTasks(); + if (cachedTasks.length) { + studentTopTask.value = getComprehensiveTopTask(cachedTasks); + } try { - const res = await listTrainingTasks({ enabledOnly: true }); + const res = await loadStudentTrainingTasks(); studentTopTask.value = getComprehensiveTopTask(res.data || []); } catch (error) { - studentTopTask.value = null; + if (!cachedTasks.length) { + studentTopTask.value = null; + } } } // 获取用户详情 diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue index fffb9a2..7b4b409 100644 --- a/src/layout/components/Sidebar/index.vue +++ b/src/layout/components/Sidebar/index.vue @@ -62,7 +62,7 @@ import useSettingsStore from "@/store/modules/settings"; import usePermissionStore from "@/store/modules/permission"; import { constantRoutes, dynamicRoutes, teacherRoutes, platformAdminRoutes, schoolAdminRoutes } from "@/router/index.js"; import useUserStore from "@/store/modules/user"; -import { listTrainingTasks } from "@/api/trainingTask"; +import { getCachedStudentTrainingTasks, loadStudentTrainingTasks } from "@/api/trainingTask"; import { buildStudentTaskSections } from "@/utils/studentTrainingNavigation"; import * as ElementPlusIconsVue from "@element-plus/icons-vue"; const { proxy } = getCurrentInstance(); @@ -469,11 +469,17 @@ async function loadStudentTaskNavigation() { studentTaskSections.value = []; return; } + const cachedTasks = getCachedStudentTrainingTasks(); + if (cachedTasks.length) { + studentTaskSections.value = buildStudentTaskSections(cachedTasks); + } try { - const res = await listTrainingTasks({ enabledOnly: true }); + const res = await loadStudentTrainingTasks(); studentTaskSections.value = buildStudentTaskSections(res.data || []); } catch (error) { - studentTaskSections.value = []; + if (!cachedTasks.length) { + studentTaskSections.value = []; + } } } function groupAndSortModules(modules) {