@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);
// 再处理普通占位符
$renderedStem = preg_replace(['/(\s*)/u', '/\(\s*\)/', '/_{2,}/'], $blankSpan, $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); @endphp
{{ $label }}. {!! $mathProcessed ? $opt : \App\Services\MathFormulaProcessor::processFormulas($opt) !!}
@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
@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格式的underline命令
$renderedContent = preg_replace('/\\\underline\{[^}]*\}/', $blankSpan, $renderedContent);
$renderedContent = preg_replace('/\\\qquad+/', $blankSpan, $renderedContent);
// 再处理普通占位符
$renderedContent = preg_replace(['/(\s*)/u', '/\(\s*\)/', '/_{2,}/'], $blankSpan, $renderedContent);
if ($renderedContent === $q->content) {
$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
@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
@endif
@endforeach
@else
@endif
@endif