You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/.workbuddy/memory/2026-10-14.md

8.6 KiB

2026-10-14

摸清教师端「任务评阅」页面结构(是否复用学生端组件)

结论:不是复制页面,而是同一个学生端 SFC 被动态复用(源码同一份,非副本)。

链路:

  • views/teacherEnd/score/TaskReviewPanel.vue(左侧班级/任务/学生列表 + 打分)右侧用 <iframe src="...#/task-review-preview?taskKey=..&reviewPreviewKey=..">
  • 隐藏教师路由 /task-review-preview(roles:["teacher"])→ views/teacherEnd/taskReview/StudentTaskReadonlyPreview.vue
  • 该组件用 <component :is> 动态加载 studentTaskPreviewMap.js:25 个 taskKey → 真实学生页组件(@/views/foundation|demand|product|channel|test|assessment/**.vue);仅 comprehensive-case-training 通过 props 传 previewTaskKey

数据注入(不走 props,走 sessionStorage + API 层拦截):

  • utils/trainingReviewPreview.js:写 teacher-task-review-preview:<previewKey> = {taskKey, teachingClassId, taskConfig, answer}
  • api/studentTrainingAnswer.js#getStudentTrainingAnswer 命中 preview 直接返回 preview.answer
  • api/trainingTask.js#getTrainingTaskByKey 返回该班生效 taskConfig(教师覆盖优先,否则管理员案例)
  • utils/request.js 拦截 POST/PUT/PATCH/DELETE(答案/学习时长/上传)直接 reject「只读模式」

只读保障三层:①点击/按键捕获 guardPreviewInteraction(拦 input/select/contenteditable/按钮,放行步骤导航)②scoped :deep 隐藏顶部 chrome、AI 侧栏、底部动作区,表单 pointer-events:none ③网络层拦截写请求 iframe 高度:子页 ResizeObserver + MutationObserver 测量 → postMessage(teacher-task-review-preview-height)→ 父页设置高度

风险点:学生页任何类名/结构改动会波及评阅预览(大量 :deep 依赖命名契约);学生页新增写接口需同步加入 isTrainingReviewPreviewRequest 名单。

追加:覆盖率与差异排查(重要发现)

  • taskKey 全覆盖(25 个),不会出现"不支持预览";但3 个 key 的预览加载的是废弃旧页面文件(仅 previewMap 引用,router 已无引用):
    taskKey 学生端实际文件 预览加载文件
    industry-demand-analysis demand/industry-demand-analysis.vue demand/index.vue
    competitive-environment-analysis demand/competitive-environment-analysis.vue demand/environment.vue
    market-opportunity-selection product/market-opportunity-selection.vue product/index.vue
    证据:旧页面模板头带 TrainingTaskToolbar + TrainingTaskBrief + training-core;新页面是自定义 task-header/task-brief-toggle + xxx-core
  • 另有 5 个 key 存在"一个 taskKey 对应多个路由页面",previewMap 只映射其一:/demand/user(user.vue 旧6KB) 与 /demand/people;/test/channelas(test/channel.vue 3KB) 与 /test/index;/test/marketing(marketing.vue 旧) 与 /test/promotion;/channel/purchase(purchase.vue) 与 /channel/index;/assessment/index(index.vue 旧16KB)+five/kano/market/traffic 与 /assessment/diagnosis
  • 有意差异(只读外壳):隐藏顶部标题/brief/各 toolbar、AI 侧栏与 AI 评测、底部动作区(55% 透明+灰度+不可点)、浮层弹窗;student-training-shell 改单列;步骤导航强制横排胶囊(132~220px);表单 pointer-events:none;数据走 sessionStorage 注入(该班生效 taskConfig + 学生 answer);iframe 无应用布局(学生端有顶栏/TagsView/侧边菜单)
  • 待办建议:修正 previewMap 三条指向新版文件;评估废弃重复路由(/demand/user、/test/channelas、/test/marketing、/channel/purchase、/assessment/index 等)

已执行:previewMap 全部改指最新页面

  • src/views/teacherEnd/taskReview/studentTaskPreviewMap.js:三条改为 demand/industry-demand-analysis.vue、demand/competitive-environment-analysis.vue、product/market-opportunity-selection.vue;顶部加注释声明"必须与 router 保持一致,不得指向废弃页面"
  • src/views/teacherEnd/taskReview/StudentTaskReadonlyPreview.vue:补两个类名 —— 隐藏列表加 .task-action-strip(否则工具栏被隐藏后残留 10px 空带)、白卡规则加 .industry-step-card(行业页步骤容器类名不同)
  • 校验:node 脚本交叉核对 = 映射 24 条、文件全部存在、router 全部引用、废弃页面残留引用 0;@vue/compiler-sfc parse + compileStyle 无错误
  • 遗留风险(previewMap 换新页后新增):新版页面采用 localStorage 草稿(loadDraft/persistDraft,键含当前用户 id),评阅预览同源会读写教师自己的草稿,理论上可能先闪出教师草稿;涉及 5 个页面(new-product-survey、product-development-factors、product-development-process、industry-demand-analysis、competitive-environment-analysis),可在这些页面用 getTrainingReviewPreviewKey() 判断预览态跳过草稿读写。待用户确认是否执行

已修复:评阅预览"看起来还在用废弃页面"的根因(两个叠加原因)

用户反馈截图里步骤导航是一条满宽蓝色大条,误判为废弃页面。实测根因 不是页面文件错了,而是样式失配:

  1. 学生页"新版皮肤"全部由布局门控选择器承载:#app .studentLayout section.app-main:has(.xxx-page) …(非 scoped 全局块,写在每个实训页底部)。 而 /task-review-preview 路由脱离 Layout(component 直接指向组件,无 Layout 包裹)→ 没有 .studentLayout、没有 section.app-main → 这些新样式全部失配,页面静默退化到全局旧版样式(步骤说明 small「当前步骤」重新出现、胶囊样式丢失、按钮配色不一致)。 studentLayout 类由 layout/index.vue 的 classObj 计算(仅 role 4 挂载);.app-main 由 layout/components/AppMain.vue 渲染且样式是 scoped。
  2. 预览 iframe 宽度约 1000px(TaskReviewPanel.vue 左栏 minmax(440px,520px) + 右栏自适应),命中 student-training.scss 的 @media (max-width: 1100px): .student-training-shell .step-indicator { flex-direction: column } + #app .student-training-shell .step-tab { width: 100% !important }。 后者带 #app(ID 级特异性 1,2,0),压过预览自身 :deep(.step-tab){max-width:220px}(0,4,0)→ 每个步骤胶囊被撑成整行、只剩第一个可见并出现横向滚动条。

修复(src/views/teacherEnd/taskReview/StudentTaskReadonlyPreview.vue,单文件):

  • 模板外层补空壳祖先:<div class="studentLayout student-task-preview-root"><section class="app-main">…,让门控样式集体生效
  • 新增一段非 scoped <style>:用 #app .studentLayout.student-task-preview-root …(1,3,0/1,4,0)压掉布局级背景与内边距,并复位 .step-tab/.flow-pill/.flow-step/.training-action 的 width: 100%
  • 保留原有兜底::deep() 里 flex-direction: row、隐藏 chrome、白卡等规则不变

验证方式(可复用):用本地 sass 编译 index.scss + 页面非 scoped 样式块 + 预览组件样式块,拼成静态 HTML(.studentLayout > section.app-main > main.student-task-readonly-preview + 4 个 .step-tab),用本机 Chrome --headless=new --window-size=1000,700 --screenshot=xxx.png 出图,页面内探针把 computed style 写进 <pre> 一起截进图里。 结果:修复前 tabWidths=952px×4(=容器满宽)、navScroll=3874/952 溢出、small=inline(与用户截图完全一致);修复后 tabWidths=214/169/199/200px 自然宽、navScroll=952/952 不溢出、small=none,与图二学生端一致。 注:Chrome for Windows 为 GUI 子系统,--dump-dom 不写 stdout,只能靠 --screenshot 落盘取证。

学生端菜单/页面全貌(roleId=4)

菜单不是硬编码:dynamicRoutes 里大量 hidden:true,真正侧边菜单由 utils/studentTrainingNavigation.js#buildStudentTaskSections() 按后端「课程分配」动态生成(按 projectName 分组),路径查 knownTaskPaths。 静态模块:/foundation(3) / /demand(8: index、environment、people、user、business-feasibility、consumer-behavior、consumer-scenario、feature-conversion) / /product(7: index、price、differentiation、structure、after-sales-service、requirements-doc、hardware-cost) / /test(4: index、promotion、channelas、marketing) / /channel(3: index、purchase、supplier) / /assessment(3: index、diagnosis、optimization) / /comprehensive / /training/task/:pageKey / /training-step/:taskKey/:stepId / /assistant / /ai/learning-situation-* / /analysis / /report / /bi / /examination(index、take/:id、result/:id) / /student-demo。 遗留:5 处重复 pageKey(一个 taskKey 对多个路由入口);demand/index.vue、demand/environment.vue、product/index.vue 三个文件已零引用但仍在磁盘(其内部仍保留 :has(.industry-demand-page) 等门控块,是现新页的前身)。