diff --git a/src/utils/stepOneChoiceQuestions.js b/src/utils/stepOneChoiceQuestions.js index 98c9dca..328b615 100644 --- a/src/utils/stepOneChoiceQuestions.js +++ b/src/utils/stepOneChoiceQuestions.js @@ -23,6 +23,81 @@ export function createStepOneChoiceQuestion(index = 0) { }; } +/** + * 兼容「legacy-training-snapshot-v3」整页 DOM 快照: + * 工具栏的提交/保存兜底会把整页 input/textarea 的选中状态拍成快照塞进 step1Answer, + * 这类数据里没有结构化 choiceAnswers,只能按 DOM 顺序把单选组还原成逐题作答。 + * 分组规则:选项 key 恒为 A~F 单字母,且每组以 "A" 开头;遇到非单字母选项(如第二步的"京东") + * 说明自测区已结束,停止收集。 + */ +export function parseLegacySnapshotChoiceAnswers(value, questions = []) { + let source = value; + if (typeof value === "string") { + try { + source = JSON.parse(value); + } catch { + return null; + } + } + if (!source || typeof source !== "object" || !Array.isArray(source.fields)) return null; + const normalized = Array.isArray(questions) ? questions : parseStepOneChoiceQuestions(questions); + if (!normalized.length) return null; + const groups = []; + let current = null; + for (const field of source.fields) { + if (!field || field.type !== "radio") continue; + const key = String(field.value ?? "").trim().toUpperCase(); + if (!/^[A-F]$/.test(key)) { + // 已开始收集自测题后遇到非字母选项,说明后面是其它步骤的控件 + if (current) break; + continue; + } + if (key === "A") { + current = []; + groups.push(current); + } + if (!current) continue; + current.push({ key, checked: Boolean(field.checked) }); + } + const answers = {}; + groups.slice(0, normalized.length).forEach((group, index) => { + const picked = group.find((option) => option.checked); + if (picked) answers[normalized[index].id] = picked.key; + }); + return Object.keys(answers).length ? answers : null; +} + +/** + * 兼容 legacy 快照里的尾部 textarea(如新产品调查与分析第 4 步的三层次 AI 应用价值分析): + * 快照没有字段名,只能按 DOM 顺序取最后 N 个 textarea。调用方必须用 route 限定页面, + * 避免把其它页面的控件误当成第 4 步内容回填给教师端。 + */ +export function parseLegacySnapshotTailTextareas(value, fields = [], route = "") { + let source = value; + if (typeof value === "string") { + try { + source = JSON.parse(value); + } catch { + return null; + } + } + if (!source || typeof source !== "object" || !Array.isArray(source.fields)) return null; + if (route && String(source.route || "") !== route) return null; + if (!Array.isArray(fields) || !fields.length) return null; + const textareas = source.fields.filter((field) => field && (field.type === "textarea" || field.tag === "textarea")); + // 至少要有第 3 步的 rows 加上第 4 步的字段,才可能是“第 4 步在末尾”的结构 + if (textareas.length < fields.length + 3) return null; + const tail = textareas.slice(-fields.length); + const result = {}; + let filled = false; + tail.forEach((field, index) => { + const text = String(field?.value ?? "").trim(); + if (text) filled = true; + result[fields[index]] = text; + }); + return filled ? result : null; +} + export function parseStepOneChoiceQuestions(value) { let source = value; if (typeof value === "string") { diff --git a/src/views/components/TrainingTaskToolbar.vue b/src/views/components/TrainingTaskToolbar.vue index c77a3b9..8fbc272 100644 --- a/src/views/components/TrainingTaskToolbar.vue +++ b/src/views/components/TrainingTaskToolbar.vue @@ -10,6 +10,15 @@ :ai-manual-name="resolvedAiManual.name" :ai-manual-url="resolvedAiManual.url" /> + + + + {{ displayScore }}分 + + + 查看成绩 + + + + + + + 老师评分 + {{ formatScoreValue(teacherScore) }}分 + {{ formatScoreTime(teacherScoredAt) }} + + + AI 评分 + {{ formatScoreValue(aiScore) }}分 + + + + 老师评语 + {{ teacherComment }} + + + 参考答案 + {{ referenceAnswer }} + + + +
老师评分
{{ formatScoreValue(teacherScore) }}分
{{ formatScoreTime(teacherScoredAt) }}
AI 评分
{{ formatScoreValue(aiScore) }}分
老师评语
{{ teacherComment }}
参考答案
{{ referenceAnswer }}