@php $choiceQuestions = $questions['choice'] ?? []; $fillQuestions = $questions['fill'] ?? []; $answerQuestions = $questions['answer'] ?? []; $gradingMode = $grading ?? false; // 【新增】动态计算大题号 - 根据有题目的题型分配序号 $sectionNumbers = [ 'choice' => null, 'fill' => null, 'answer' => null ]; $currentSectionNumber = 1; // 只给有题目的题型分配序号 if (!empty($choiceQuestions)) { $sectionNumbers['choice'] = $currentSectionNumber++; } if (!empty($fillQuestions)) { $sectionNumbers['fill'] = $currentSectionNumber++; } if (!empty($answerQuestions)) { $sectionNumbers['answer'] = $currentSectionNumber++; } // 获取题型名称的辅助函数 $getSectionTitle = function($type, $sectionNumber) { $typeNames = [ 'choice' => '选择题', 'fill' => '填空题', 'answer' => '解答题' ]; // 将数字转换为中文数字 $chineseNumbers = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']; $chineseNumber = $chineseNumbers[$sectionNumber] ?? (string)$sectionNumber; return $chineseNumber . '、' . $typeNames[$type]; }; // 检查是否有数学公式处理标记,避免重复处理 $mathProcessed = false; // 检查所有题型中是否有任何题目包含 math_processed 标记 foreach ([$choiceQuestions, $fillQuestions, $answerQuestions] as $questionType) { foreach ($questionType as $q) { if (isset($q->math_processed) && $q->math_processed) { $mathProcessed = true; break 2; // 找到标记就退出两层循环 } } } // 计算填空空格数量 $countBlanks = function($text) { $count = 0; $count += preg_match_all('/_{2,}/u', $text, $m); $count += preg_match_all('/(\s*)/u', $text, $m); $count += preg_match_all('/\(\s*\)/', $text, $m); return max(1, $count); }; // 计算步骤数量 $countSteps = function($text) { $matches = []; $cnt = preg_match_all('/第\s*\d+\s*步/u', $text ?? '', $matches); return max(1, $cnt); }; $renderBoxes = function($num) { // 判卷方框放大 1.2 倍,保持单行布局 if ($num == 2) { // 两个方框时,使用右对齐布局 return '
' . str_repeat('', $num) . '
'; } return str_repeat('', $num); }; @endphp {{-- 【新增】步骤方框CSS样式 --}} @if($sectionNumbers['choice'] !== null)
{{ $getSectionTitle('choice', $sectionNumbers['choice']) }} @if(count($choiceQuestions) > 0) @php $choiceTotal = array_sum(array_map(fn($q) => $q->score ?? 5, $choiceQuestions)); @endphp (本大题共 {{ count($choiceQuestions) }} 小题,共 {{ $choiceTotal }} 分) @else (本大题共 0 小题,共 0 分) @endif
@if(count($choiceQuestions) > 0) @foreach($choiceQuestions as $index => $q) @php // 【修复】使用question_number字段作为显示序号,确保全局序号一致性 $questionNumber = $q->question_number ?? ($index + 1); $cleanContent = preg_replace('/^\d+[\.、]\s*/', '', $q->content); $cleanContent = trim($cleanContent); $options = $q->options ?? []; if (empty($options)) { $pattern = '/([A-D])[\.、:.:]\s*(.+?)(?=\s*[A-D][\.、:.:]|$)/su'; if (preg_match_all($pattern, $cleanContent, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $optionText = trim($match[2]); if (!empty($optionText)) { $options[] = $optionText; } } } } $stemLine = $cleanContent; if (!empty($options)) { if (preg_match('/^(.+?)(?=[A-D][\.、:.:])/su', $cleanContent, $stemMatch)) { $stemLine = trim($stemMatch[1]); } } // 将题干中的空括号/下划线替换为短波浪线;如无占位符,则在末尾追加短波浪线 $blankSpan = ' '; // 【修复】扩展下划线转换规则,支持LaTeX格式和多种占位符 $renderedStem = $stemLine; // 先处理LaTeX格式的underline命令 $renderedStem = preg_replace('/\\\underline\{[^}]*\}/', $blankSpan, $renderedStem); $renderedStem = preg_replace('/\\\qquad+/', $blankSpan, $renderedStem); // 【修复】在处理填空占位符时,保护LaTeX公式不被破坏 // 先标记LaTeX公式区域 $latexPlaceholders = []; $counter = 0; $renderedStem = preg_replace_callback('/\$[^$]+\$/u', function($matches) use (&$latexPlaceholders, &$counter, $blankSpan) { $placeholder = '<<>>'; $latexPlaceholders[$placeholder] = $matches[0]; $counter++; return $placeholder; }, $renderedStem); // 现在处理普通占位符(不会破坏LaTeX公式) $renderedStem = preg_replace(['/(\s*)/u', '/\(\s*\)/', '/_{2,}/'], $blankSpan, $renderedStem); // 恢复LaTeX公式(并进行HTML实体编码防止被浏览器解析) foreach ($latexPlaceholders as $placeholder => $latexContent) { $encodedLatex = htmlspecialchars($latexContent, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $renderedStem = str_replace($placeholder, $encodedLatex, $renderedStem); } // 如果没有占位符,在末尾添加 if ($renderedStem === $stemLine) { $renderedStem .= ' ' . $blankSpan; } $renderedStem = $mathProcessed ? $renderedStem : \App\Services\MathFormulaProcessor::processFormulas($renderedStem); @endphp
@if($gradingMode) {!! $renderBoxes(1) !!} @endif {{ $gradingMode ? '题目 ' : '' }}{{ $questionNumber }}.
{!! $renderedStem !!}
@if(!empty($options)) @php // 计算选项长度并动态选择布局 $optCount = count($options); $maxOptionLength = 0; foreach ($options as $opt) { $optText = strip_tags(\App\Services\MathFormulaProcessor::processFormulas($opt)); $maxOptionLength = max($maxOptionLength, mb_strlen($optText, 'UTF-8')); } // 根据最长选项长度和选项数量动态选择布局 // 短选项(≤15字符)且选项数≤4:4列布局 // 中等选项(16-30字符)或选项数>4:2列布局 // 长选项(>30字符):1列布局 if ($maxOptionLength <= 15 && $optCount <= 4) { $optionsClass = 'options-grid-4'; $layoutDesc = '4列布局'; } elseif ($maxOptionLength <= 30) { $optionsClass = 'options-grid-2'; $layoutDesc = '2列布局'; } else { $optionsClass = 'options-grid-1'; $layoutDesc = '1列布局'; } \Illuminate\Support\Facades\Log::debug('选择题布局决策', [ 'question_number' => $questionNumber, 'opt_count' => $optCount, 'max_length' => $maxOptionLength, 'selected_class' => $optionsClass, 'layout' => $layoutDesc ]); @endphp
@foreach($options as $optIndex => $opt) @php $label = chr(65 + (int)$optIndex); // 对LaTeX公式中的特殊字符进行HTML实体编码,防止被浏览器当作HTML标签处理 $encodedOpt = htmlspecialchars($opt, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $renderedOpt = $mathProcessed ? $encodedOpt : \App\Services\MathFormulaProcessor::processFormulas($encodedOpt); @endphp
{{ $label }}. {!! $renderedOpt !!}
@endforeach
@endif @if($gradingMode) @php $solutionText = trim($q->solution ?? ''); // 去掉前置的"解题思路"标签,避免出现"解题思路:【解题思路】"重复 $solutionText = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $solutionText); $solutionHtml = $solutionText === '' ? '(暂无解题思路)' : ($mathProcessed ? $solutionText : \App\Services\MathFormulaProcessor::processFormulas($solutionText)); @endphp
正确答案:{!! $mathProcessed ? ($q->answer ?? '') : \App\Services\MathFormulaProcessor::processFormulas($q->answer ?? '') !!}
解题思路:{!! $solutionHtml !!}
@endif
@endforeach @else
该题型正在生成中或暂无题目,请稍后刷新页面查看
@endif @endif @if($sectionNumbers['fill'] !== null)
{{ $getSectionTitle('fill', $sectionNumbers['fill']) }} @if(count($fillQuestions) > 0) @php $fillTotal = array_sum(array_map(fn($q) => $q->score ?? 5, $fillQuestions)); @endphp (本大题共 {{ count($fillQuestions) }} 小题,共 {{ $fillTotal }} 分) @else (本大题共 0 小题,共 0 分) @endif
@if(count($fillQuestions) > 0) @foreach($fillQuestions as $index => $q) @php // 【修复】使用question_number字段作为显示序号,确保全局序号一致性 $questionNumber = $q->question_number ?? (count($choiceQuestions) + $index + 1); $blankSpan = ' '; // 【修复】扩展下划线转换规则,支持LaTeX格式和多种占位符 $renderedContent = $q->content; // 【修复】在处理填空占位符时,保护LaTeX公式不被破坏 // 先标记LaTeX公式区域(支持包含反斜杠和花括号的LaTeX命令) $latexPlaceholders = []; $counter = 0; $renderedContent = preg_replace_callback('/\$(?:[^\$]|\\.)*\$/u', function($matches) use (&$latexPlaceholders, &$counter, $blankSpan) { $placeholder = '<<>>'; $latexPlaceholders[$placeholder] = $matches[0]; $counter++; return $placeholder; }, $renderedContent); // 现在处理普通占位符(不会破坏LaTeX公式) $renderedContent = preg_replace(['/(\s*)/u', '/\(\s*\)/', '/_{2,}/'], $blankSpan, $renderedContent); // 恢复LaTeX公式(并进行HTML实体编码防止被浏览器解析) foreach ($latexPlaceholders as $placeholder => $latexContent) { $encodedLatex = htmlspecialchars($latexContent, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $renderedContent = str_replace($placeholder, $encodedLatex, $renderedContent); } // 如果没有占位符且内容没有变化,在末尾添加 // 但要检查是否已经有填空占位符(如\underline{\qquad}) if ($renderedContent === $q->content && !preg_match('/\\\\underline|\\\\qquad|(\s*)|\(\s*\)/', $renderedContent)) { $renderedContent .= ' ' . $blankSpan; } $renderedContent = $mathProcessed ? $renderedContent : \App\Services\MathFormulaProcessor::processFormulas($renderedContent); @endphp
@if($gradingMode) {!! $renderBoxes($countBlanks($q->content ?? '')) !!} @endif {{ $gradingMode ? '题目 ' : '' }}{{ $questionNumber }}.
{!! $renderedContent !!}
@if($gradingMode) @php $solutionText = trim($q->solution ?? ''); // 去掉前置的“解题思路”标签,避免出现“解题思路:【解题思路】”重复 $solutionText = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $solutionText); $solutionHtml = $solutionText === '' ? '(暂无解题思路)' : ($mathProcessed ? $solutionText : \App\Services\MathFormulaProcessor::processFormulas($solutionText)); @endphp
正确答案:{!! $mathProcessed ? ($q->answer ?? '') : \App\Services\MathFormulaProcessor::processFormulas($q->answer ?? '') !!}
解题思路:{!! $solutionHtml !!}
@endif
@endforeach @else
该题型正在生成中或暂无题目,请稍后刷新页面查看
@endif @endif @if($sectionNumbers['answer'] !== null)
{{ $getSectionTitle('answer', $sectionNumbers['answer']) }} @if(count($answerQuestions) > 0) (本大题共 {{ count($answerQuestions) }} 小题,共 {{ array_sum(array_column($answerQuestions, 'score')) }} 分。解答应写出文字说明、证明过程或演算步骤) @else (本大题共 0 小题,共 0 分) @endif
@if(count($answerQuestions) > 0) @foreach($answerQuestions as $index => $q) @php // 【修复】使用question_number字段作为显示序号,确保全局序号一致性 $questionNumber = $q->question_number ?? (count($choiceQuestions) + count($fillQuestions) + $index + 1); @endphp
{{ $gradingMode ? '题目 ' : '' }}{{ $questionNumber }}.
@unless($gradingMode) (本小题满分 {{ $q->score ?? 10 }} 分) @endunless {!! $mathProcessed ? $q->content : \App\Services\MathFormulaProcessor::processFormulas($q->content) !!}
@unless($gradingMode)
作答
@endunless @if($gradingMode) @php $solutionRaw = $q->solution ?? ''; $solutionProcessed = $mathProcessed ? $solutionRaw : \App\Services\MathFormulaProcessor::processFormulas($solutionRaw); // 去掉分步得分等分值标记 $solutionProcessed = preg_replace('/(\s*\d+\s*分\s*)/u', '', $solutionProcessed); // 【修复】优化解析分段格式 - 支持两种格式: // 1. 【解题思路】格式 // 2. 解题过程:格式 // 先处理【】格式 $solutionProcessed = preg_replace('/【(解题思路|详细解答|最终答案)】/u', "\n\n===SECTION_START===\n【$1】\n===SECTION_END===\n\n", $solutionProcessed); // 【扩展】处理多种"解题过程"格式,包括带括号的内容 $solutionProcessed = preg_replace('/(解题过程\s*[^:\n]*:)/u', "\n\n===SECTION_START===\n【解题过程】\n===SECTION_END===\n\n", $solutionProcessed); // 按section分割内容 $sections = explode('===SECTION_START===', $solutionProcessed); $processedSections = []; foreach ($sections as $section) { if (empty(trim($section))) continue; // 去掉结尾标记 $section = str_replace('===SECTION_END===', '', $section); // 检查是否是解题相关部分 if (preg_match('/【(解题思路|详细解答|最终答案|解题过程)】/u', $section, $matches)) { $sectionTitle = $matches[0]; $sectionContent = preg_replace('/【(解题思路|详细解答|最终答案|解题过程)】/u', '', $section); // 【修复】处理步骤 - 在每个"步骤N"或"第N步"前添加方框 // 【优化】使用split分割步骤,为所有步骤添加方框(包括第一个) if (preg_match('/(步骤\s*\d+|第\s*\d+\s*步)/u', $sectionContent)) { // 使用前瞻断言分割,保留分隔符 $allSteps = preg_split('/(?=步骤\s*\d+|第\s*\d+\s*步)/u', $sectionContent, -1, PREG_SPLIT_NO_EMPTY); if (count($allSteps) > 0) { $processed = ''; // 为每个步骤添加方框(包括第一个) for ($i = 0; $i < count($allSteps); $i++) { $stepText = trim($allSteps[$i]); if (!empty($stepText)) { // 为每个步骤添加方框和换行(第一个步骤前面不加
) $prefix = ($i > 0) ? '
' : ''; $processed .= $prefix . '' . $renderBoxes(1) . '' . $stepText . ''; } } $sectionContent = $processed; } } else { // 没有明确步骤:在标题后添加一个方框作为开始 $sectionContent = '' . $renderBoxes(1) . '  ' . trim($sectionContent); } // 包装section $processedSections[] = '
' . $sectionTitle . '
' . $sectionContent . '
'; } else { // 非解题部分直接保留 $processedSections[] = $section; } } // 重新组合所有部分 $solutionProcessed = implode('', $processedSections); // 将多余的换行转换为
,但保留合理的段落间距 $solutionProcessed = preg_replace('/\n{3,}/u', "\n\n", $solutionProcessed); $solutionProcessed = nl2br($solutionProcessed); @endphp
正确答案:{!! $mathProcessed ? ($q->answer ?? '') : \App\Services\MathFormulaProcessor::processFormulas($q->answer ?? '') !!}
{!! $solutionProcessed !!}
@endif
@endforeach @else
该题型正在生成中或暂无题目,请稍后刷新页面查看
@endif @endif