From 22d8534fdfaff57af00760f9c6de4f5c01b7786a Mon Sep 17 00:00:00 2001 From: yuliang_guo Date: Wed, 28 Jan 2026 13:19:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(exam):=20=E5=A2=9E=E5=BC=BA=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E8=80=83=E9=A2=98=E7=AD=94=E6=A1=88=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=92=8C=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复正确答案字母解析的正则表达式 - 支持更多冒号格式(中英文冒号、带空格) - 添加详细的调试日志帮助定位问题 - 当无法解析时尝试内容匹配 --- frontend/src/views/exam/practice.vue | 66 +++++++++++++++++++++------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/frontend/src/views/exam/practice.vue b/frontend/src/views/exam/practice.vue index 1d57601..1eed353 100644 --- a/frontend/src/views/exam/practice.vue +++ b/frontend/src/views/exam/practice.vue @@ -340,30 +340,56 @@ const transformDifyQuestions = (difyQuestions: any[]): any[] => { if (q.type === 'single_choice' || q.type === 'multiple_choice') { const options = q.topic?.options || {} // 解析正确答案:支持 "A"、"A,B"、"A、B"、"A:xxx" 等多种格式 - const correctAnswerStr = String(q.correct || '') + const correctAnswerStr = String(q.correct || '').trim() + + console.log(`📝 开始解析题目[${index + 1}]`) + console.log(` 原始correct: "${correctAnswerStr}"`) + console.log(` 原始options:`, options) // 更精确地提取正确答案字母(避免误提取选项内容中的字母) let correctLetters: string[] = [] // 情况1:纯选项字母格式(如 "A", "B", "A,B", "A、B", "A,B,C") - const pureLetterMatch = correctAnswerStr.trim().match(/^[A-Da-d]([,、\s]+[A-Da-d])*$/) + const pureLetterMatch = correctAnswerStr.match(/^[A-Da-d]([,、\s]+[A-Da-d])*$/) if (pureLetterMatch) { correctLetters = correctAnswerStr.match(/[A-Da-d]/g)?.map(l => l.toUpperCase()) || [] + console.log(` ✓ 匹配情况1(纯字母): correctLetters=[${correctLetters.join(',')}]`) } else { - // 情况2:选项字母+内容格式(如 "A:xxx" 或 "A: xxx") - // 只提取开头的选项字母(可能有多个,如 "A,C:xxx") - const prefixMatch = correctAnswerStr.match(/^([A-Da-d](?:[,、\s]*[A-Da-d])*)[:::\s]/) + // 情况2:选项字母+内容格式(如 "A:xxx" 或 "A: xxx" 或 "A:xxx") + // 支持多种冒号格式:中文全角冒号、英文冒号 + const prefixMatch = correctAnswerStr.match(/^([A-Da-d](?:[,、\s]*[A-Da-d])*)\s*[::]\s*/) if (prefixMatch) { correctLetters = prefixMatch[1].match(/[A-Da-d]/g)?.map(l => l.toUpperCase()) || [] + console.log(` ✓ 匹配情况2(字母+冒号): prefixMatch="${prefixMatch[0]}", correctLetters=[${correctLetters.join(',')}]`) } else { - // 情况3:只有开头字母(如 "A" 后面直接跟内容) - const firstLetterMatch = correctAnswerStr.match(/^([A-Da-d])/) + // 情况3:只有开头字母(如 "A" 后面直接跟非字母内容) + const firstLetterMatch = correctAnswerStr.match(/^([A-Da-d])(?![A-Za-z])/) if (firstLetterMatch) { correctLetters = [firstLetterMatch[1].toUpperCase()] + console.log(` ✓ 匹配情况3(开头字母): correctLetters=[${correctLetters.join(',')}]`) + } else { + // 情况4:无法解析,尝试从选项中查找匹配的答案 + console.log(` ⚠️ 无法从correct字符串解析字母,尝试内容匹配`) + // 尝试在选项中查找完全匹配的内容 + const optionValues = Object.values(options) + optionValues.forEach((optVal: any, idx: number) => { + const optContent = String(optVal || '') + if (optContent === correctAnswerStr || optContent.includes(correctAnswerStr)) { + const letter = String.fromCharCode(65 + idx) + correctLetters.push(letter) + console.log(` → 内容匹配: 选项${letter}="${optContent.substring(0, 30)}..."`) + } + }) } } } + // 确保至少有一个正确答案 + if (correctLetters.length === 0) { + console.warn(` ❌ 警告:未能解析出正确答案字母,默认使用第一个选项`) + correctLetters = ['A'] + } + console.log(`🔍 解析题目[${index + 1}] - correct: "${q.correct}", correctLetters: [${correctLetters.join(', ')}]`) // 获取所有选项的键并排序,确保顺序一致 @@ -376,38 +402,46 @@ const transformDifyQuestions = (difyQuestions: any[]): any[] => { return a.localeCompare(b) }) + console.log(` 选项键排序后: [${optionKeys.join(', ')}]`) + transformed.options = optionKeys.map((key, idx) => { const opt = options[key] - const optStr = String(opt) + const optStr = String(opt || '') + // 提取选项内容(去掉 "A:" 或 "A:" 前缀) let content = optStr - if (optStr.match(/^[A-Da-d][::]/)) { - content = optStr.substring(2).trim() - } else if (optStr.includes(':')) { - content = optStr.split(':').slice(1).join(':').trim() + // 匹配 "A:xxx" 或 "A:xxx" 格式(支持中英文冒号) + const contentPrefixMatch = optStr.match(/^[A-Da-d]\s*[::]\s*(.*)$/) + if (contentPrefixMatch) { + content = contentPrefixMatch[1].trim() } - // 提取当前选项的字母(优先级:optStr前缀 > key字母 > 索引推断) + // 提取当前选项的字母 + // 优先级:1. 选项内容前缀 > 2. key字母 > 3. 索引推断 let optionLetter = '' + let letterSource = '' // 1. 尝试从选项内容前缀提取(如 "A:xxx" 或 "A:xxx") - const prefixMatch = optStr.match(/^([A-Da-d])[::]/) + const prefixMatch = optStr.match(/^([A-Da-d])\s*[::]/) if (prefixMatch) { optionLetter = prefixMatch[1].toUpperCase() + letterSource = '内容前缀' } - // 2. 尝试从 key 提取(如果 key 是单个字母) + // 2. 尝试从 key 提取(如果 key 是单个字母 A-D) else if (key.match(/^[A-Da-d]$/)) { optionLetter = key.toUpperCase() + letterSource = 'key字母' } // 3. 根据索引推断选项字母(0->A, 1->B, 2->C, 3->D) else { optionLetter = String.fromCharCode(65 + idx) // 65 = 'A' + letterSource = '索引推断' } // 判断当前选项是否正确:检查选项字母是否在正确答案列表中 const isCorrect = correctLetters.includes(optionLetter) - console.log(` 选项[${key}/${idx}]: letter=${optionLetter}, isCorrect=${isCorrect}, content="${content.substring(0, 20)}..."`) + console.log(` 选项[${key}/${idx}]: letter=${optionLetter}(${letterSource}), isCorrect=${isCorrect}, correctLetters=[${correctLetters.join(',')}], content="${content.substring(0, 25)}..."`) return { content, isCorrect, optionLetter } })