From 6edf32a7b573a0293671dc63daabd1ec0e76a1b0 Mon Sep 17 00:00:00 2001 From: qinzhenpen Date: Mon, 28 Sep 2026 14:18:57 +0800 Subject: [PATCH] feat(studentDemo): implement demo session handling and role management Added functionality to manage student demo sessions, ensuring the role is fixed to student to prevent unauthorized access. Introduced a new method to unwrap demo payloads and save session data securely. Updated navigation logic to redirect users appropriately based on their role, enhancing the user experience during demo sessions. --- src/views/studentDemo/index.vue | 46 ++- src/views/teacherEnd/student/index.vue | 10 +- .../taskReview/StudentTaskReadonlyPreview.vue | 161 +++++++++ src/views/teacherEnd/trainingTask/index.vue | 331 ++++++++++++++++-- 4 files changed, 504 insertions(+), 44 deletions(-) diff --git a/src/views/studentDemo/index.vue b/src/views/studentDemo/index.vue index cb09e39..d908a8c 100644 --- a/src/views/studentDemo/index.vue +++ b/src/views/studentDemo/index.vue @@ -7,6 +7,23 @@ import { exchangeStudentDemoSession } from "@/api/studentDemo"; import { saveStudentDemoSession } from "@/utils/studentDemo"; import useUserStore from "@/store/modules/user"; +// 学生端演示登录态的角色:固定为学生 (roleId === 4),避免后端返回 +// 教师身份导致跳转到教师首页。后端若返回 demoTeachingClassId,会被 +// 透传保留,登录页/Navbar/AppMain 可据此识别为"演示学生"。 +const STUDENT_DEMO_ROLE_ID = "4"; + +function unwrapDemoPayload(payload) { + if (!payload || typeof payload !== "object") { + return { userInfo: { roleId: STUDENT_DEMO_ROLE_ID }, token: "" }; + } + // 兼容两种返回结构: + // 1) { token, roleId, userId, ... } —— 扁平结构 + // 2) { token, userInfo: { roleId, ... } } —— userInfo 嵌套 + const inner = payload.userInfo && typeof payload.userInfo === "object" ? payload.userInfo : payload; + const token = inner.token || payload.token || ""; + return { userInfo: { ...inner, token, roleId: STUDENT_DEMO_ROLE_ID }, token }; +} + const userStore = useUserStore(); const message = ref("正在连接教师演示会话…"); let readyTimer; @@ -15,15 +32,36 @@ function notifyReady() { if (window.opener) window.opener.postMessage({ type: "STUDENT_DEMO_READY" }, window.location.origin); } +function applyDemoSession(demoPayload) { + const { userInfo, token } = unwrapDemoPayload(demoPayload); + // 保存到 sessionStorage(getUserInfo/getToken 优先从这里读取) + // 注意:演示 session 只放在新窗口的 sessionStorage,避免覆盖 + // 教师主窗口的 cookie/sessionStorage 而把教师侧误切到学生身份。 + saveStudentDemoSession(userInfo); + // 立即更新 Pinia,避免 reload 之前出现教师内容闪烁 + userStore.token = token; + userStore.userInfo = userInfo; +} + function receiveTicket(event) { if (event.origin !== window.location.origin || event.source !== window.opener || event.data?.type !== "STUDENT_DEMO_TICKET") return; exchangeStudentDemoSession(event.data.ticket).then((res) => { - saveStudentDemoSession(res.data); - userStore.token = res.data.token; - userStore.userInfo = res.data; + if (!res || !res.data) { + message.value = "演示服务返回数据为空,请稍后重试"; + return; + } + applyDemoSession(res.data); window.clearInterval(readyTimer); window.removeEventListener("message", receiveTicket); - window.location.hash = "#/index"; + // 跳转学生端基础课首页 #/foundation/new-product-survey: + // roleId 已被改写为 4,filterDynamicRoutes 会加载 student 路由, + // 避开 /index 的角色分发逻辑(role==='3' 会落到 ), + // 强制落到学生工作台,避免任何教师侧兜底。 + const targetHash = "#/foundation/new-product-survey"; + if (window.location.hash !== targetHash) { + // 先改 hash 再 reload,避免仅 hash 变化时浏览器不重新加载 + window.location.hash = targetHash; + } window.location.reload(); }).catch(() => { message.value = "演示链接已失效,请返回教师端重新打开。"; }); } diff --git a/src/views/teacherEnd/student/index.vue b/src/views/teacherEnd/student/index.vue index 0f921b3..bff1c41 100644 --- a/src/views/teacherEnd/student/index.vue +++ b/src/views/teacherEnd/student/index.vue @@ -486,7 +486,7 @@ const beforeImportStudents = (file) => { return true } const getClassList = () => { - indexApi.getClassListBySchoolId({ schoolId: userStore.userInfo.schoolId }).then((res) => { + return indexApi.getClassListBySchoolId({ schoolId: userStore.userInfo.schoolId }).then((res) => { classList.value = res.data }) } @@ -542,11 +542,15 @@ const initPassword = (row, status) => { } onMounted(async () => { await userStore.ensureSchoolProductConfig().catch(() => null) - getList() - getClassList() if (!isTeacherRosterMode.value) { getFacultyList() } + await getClassList() + // 默认展示第一个班级数据:把 select 绑到第一个教学班并立即拉取学生列表 + if (!paramsquery.value.schoolClassId && studentClassOptions.value.length) { + paramsquery.value.schoolClassId = studentClassOptions.value[0].schoolClassId + } + getList() }) diff --git a/src/views/teacherEnd/trainingTask/index.vue b/src/views/teacherEnd/trainingTask/index.vue index 78420bd..24ae49b 100644 --- a/src/views/teacherEnd/trainingTask/index.vue +++ b/src/views/teacherEnd/trainingTask/index.vue @@ -156,6 +156,17 @@ {{ task.steps?.length || 0 }} 个步骤 + +
+ + + + + 对应学生页: + {{ resolveStudentPage(task).label }} + {{ resolveStudentPage(task).path }} +
@@ -311,31 +322,39 @@

上传案例资料,配置实训步骤

-
-
- - - - -
- {{ taskForm.materialName - }} - 暂未上传案例资料 +
+
+
+ + + +
+ {{ slot.label }} + {{ slot.material.name }} + 暂未上传{{ slot.label }} +
+
+
+ + + + + 预览 + + + + + + + {{ slot.material.name ? "重新上传" : "上传" }} + +
- - - - - - - - {{ materialUploading ? "上传中..." : "上传案例资料" }} - -
@@ -399,7 +418,19 @@
-
+ + + +
+ +
+ +
该资料无法在线预览,请下载后查看
+
+ +