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

62 lines
8.6 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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)` 等门控块,是现新页的前身)。