Browse Source

卷子生成效果制作

yemeishu 3 weeks ago
parent
commit
2f312cf2a9

+ 3 - 1
app/Filament/Pages/IntelligentExamGeneration.php

@@ -34,6 +34,7 @@ class IntelligentExamGeneration extends Page
     public ?string $difficultyCategory = '基础'; // 基础/进阶/竞赛
     public int $totalQuestions = 20;
     public int $totalScore = 100;
+    public bool $includeAnswer = true; // 是否生成答案
 
     // 知识点和技能点选择
     public array $selectedKpCodes = [];
@@ -1427,7 +1428,8 @@ Cache::put('generated_exam_' . $paperId, $examData, now()->addHour());
 
         // 调用PDF导出API
         return redirect()->route('filament.admin.auth.intelligent-exam.pdf', [
-            'paper_id' => $this->generatedPaperId
+            'paper_id' => $this->generatedPaperId,
+            'answer' => $this->includeAnswer ? 'true' : 'false'
         ]);
     }
 

+ 4 - 1
app/Http/Controllers/ExamPdfController.php

@@ -340,6 +340,8 @@ class ExamPdfController extends Controller
 
     public function show(Request $request, $paper_id)
     {
+        // 获取是否显示答案的参数,默认为true
+        $includeAnswer = $request->query('answer', 'true') !== 'false';
         // 使用 Eloquent 模型获取试卷数据
         $paper = \App\Models\Paper::where('paper_id', $paper_id)->first();
 
@@ -561,7 +563,8 @@ class ExamPdfController extends Controller
             'paper' => $paper,
             'questions' => $questions,
             'student' => $this->getStudentInfo($paper->student_id),
-            'teacher' => $this->getTeacherInfo($paper->teacher_id)
+            'teacher' => $this->getTeacherInfo($paper->teacher_id),
+            'includeAnswer' => $includeAnswer
         ]);
     }
 }

+ 16 - 1
resources/views/filament/pages/intelligent-exam-generation-simple.blade.php

@@ -194,6 +194,21 @@
                         placeholder="例如:因式分解专项练习(基础版)"
                     />
                 </div>
+
+                <div class="selection-card border rounded-lg p-4">
+                    <label class="flex items-center gap-3 cursor-pointer">
+                        <input
+                            type="checkbox"
+                            wire:model="includeAnswer"
+                            class="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
+                            checked
+                        />
+                        <div>
+                            <span class="block text-sm font-medium text-gray-700">生成参考答案</span>
+                            <span class="text-xs text-gray-500">开启后将在PDF中单独生成参考答案页,方便打印后核对</span>
+                        </div>
+                    </label>
+                </div>
             </div>
         </div>
 
@@ -722,7 +737,7 @@
                     <div class="p-6 bg-gray-100">
                         <iframe
                             id="pdfFrame"
-                            src="{{ route('filament.admin.auth.intelligent-exam.pdf', ['paper_id' => $generatedPaperId]) }}"
+                            src="{{ route('filament.admin.auth.intelligent-exam.pdf', ['paper_id' => $generatedPaperId, 'answer' => $includeAnswer ? 'true' : 'false']) }}"
                             class="w-full border-0 rounded-lg shadow-lg"
                             style="height: 1200px; background: white;"
                             title="试卷预览">

+ 136 - 3
resources/views/filament/pages/intelligent-exam-generation.blade.php

@@ -405,6 +405,55 @@
 
                 <div class="space-y-4">
                     @foreach($generatedQuestions as $index => $question)
+                        @php
+                            $questionType = $question['question_type'] ?? ($question['type'] ?? '解答题');
+                            $isChoice = ($questionType === 'choice' || $questionType === '选择题');
+                            $options = $question['options'] ?? [];
+                            // 如果选项不在单独的字段中,尝试从题干中解析选项
+                            if ($isChoice && empty($options)) {
+                                $stem = $question['stem'] ?? '';
+                                preg_match_all('/([A-D])[\.\、\:]\s*(.+?)(?=[A-D][\.\、\:]|$)/', $stem, $matches);
+                                if (!empty($matches[1]) && !empty($matches[2])) {
+                                    $parsedOptions = [];
+                                    for ($i = 0; $i < count($matches[1]); $i++) {
+                                        $parsedOptions[$matches[1][$i]] = trim($matches[2][$i]);
+                                    }
+                                    $options = $parsedOptions;
+                                }
+                            }
+                            // 确保有4个选项(必须显示A、B、C、D)
+                            if ($isChoice) {
+                                $standardOptions = ['A', 'B', 'C', 'D'];
+                                $fullOptions = [];
+                                $optionIndex = 0;
+
+                                foreach ($standardOptions as $key) {
+                                    // 检查键值形式 (A, B, C, D)
+                                    if (isset($options[$key]) && !empty($options[$key])) {
+                                        $fullOptions[$key] = $options[$key];
+                                        $optionIndex++;
+                                    }
+                                    // 检查数组索引形式 (0, 1, 2, 3)
+                                    elseif (is_array($options) && isset($options[$optionIndex]) && !empty($options[$optionIndex])) {
+                                        $fullOptions[$key] = $options[$optionIndex];
+                                        $optionIndex++;
+                                    }
+                                    // 如果没有值,补充占位符
+                                    else {
+                                        // 根据选项字母生成占位符文本
+                                        $placeholders = [
+                                            'A' => '(待补充选项A)',
+                                            'B' => '(待补充选项B)',
+                                            'C' => '(待补充选项C)',
+                                            'D' => '(待补充选项D)'
+                                        ];
+                                        $fullOptions[$key] = $placeholders[$key];
+                                    }
+                                }
+                                $options = $fullOptions;
+                            }
+                        @endphp
+
                         <div class="border rounded-lg p-4">
                             <div class="flex items-start gap-4">
                                 <div class="flex-shrink-0 w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center text-blue-700 font-semibold">
@@ -432,9 +481,53 @@
                                     <div class="prose prose-sm max-w-none text-gray-900">
                                         {!! $question['stem'] !!}
                                     </div>
-                                    @if(!empty($question['answer']))
-                                        <div class="mt-2 text-sm text-gray-600">
-                                            <strong>参考答案:</strong> {!! $question['answer'] !!}
+
+                                    {{-- 选择题选项展示 --}}
+                                    @if($isChoice && !empty($options))
+                                        @php
+                                            // 判断选项长度,决定布局
+                                            $maxOptionLength = 0;
+                                            foreach ($options as $key => $option) {
+                                                $text = is_string($option) ? strip_tags($option) : (string)$option;
+                                                $length = mb_strlen($text);
+                                                $maxOptionLength = max($maxOptionLength, $length);
+                                            }
+                                            // 如果最长选项不超过20个字符,可以考虑一行显示
+                                            $shouldDisplayInline = $maxOptionLength <= 20;
+                                        @endphp
+
+                                        <div class="mt-3 space-y-2">
+                                            @if($shouldDisplayInline)
+                                                {{-- 短选项:一行显示 --}}
+                                                <div class="grid grid-cols-2 gap-3">
+                                                    @foreach($options as $key => $option)
+                                                        <div class="flex items-start gap-2 p-2 bg-gray-50 rounded
+                                                            {{ strpos($option, '待补充') !== false ? 'opacity-60 border border-dashed border-gray-300' : '' }}">
+                                                            <span class="flex-shrink-0 w-6 h-6 bg-blue-600 text-white text-sm rounded-full flex items-center justify-center font-semibold">
+                                                                {{ $key }}
+                                                            </span>
+                                                            <span class="text-sm text-gray-900 break-words">
+                                                                {!! $option !!}
+                                                            </span>
+                                                        </div>
+                                                    @endforeach
+                                                </div>
+                                            @else
+                                                {{-- 长选项:单独显示 --}}
+                                                <div class="space-y-2">
+                                                    @foreach($options as $key => $option)
+                                                        <div class="flex items-start gap-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors
+                                                            {{ strpos($option, '待补充') !== false ? 'opacity-60 border border-dashed border-gray-300' : '' }}">
+                                                            <span class="flex-shrink-0 w-7 h-7 bg-blue-600 text-white text-sm rounded-full flex items-center justify-center font-semibold">
+                                                                {{ $key }}
+                                                            </span>
+                                                            <span class="text-sm text-gray-900 leading-relaxed">
+                                                                {!! $option !!}
+                                                            </span>
+                                                        </div>
+                                                    @endforeach
+                                                </div>
+                                            @endif
                                         </div>
                                     @endif
                                 </div>
@@ -442,6 +535,46 @@
                         </div>
                     @endforeach
                 </div>
+
+                {{-- 参考答案部分 --}}
+                <div class="mt-8 bg-white p-6 rounded-lg border shadow-sm border-emerald-200">
+                    <x-slot name="header">
+                        <div class="flex items-center gap-3">
+                            <div class="w-10 h-10 bg-emerald-100 rounded-lg flex items-center justify-center">
+                                <x-heroicon-o-document-text class="w-6 h-6 text-emerald-600" />
+                            </div>
+                            <div>
+                                <h3 class="text-xl font-bold text-gray-900">参考答案</h3>
+                                <p class="text-sm text-gray-500">{{ $paperName ?: '智能生成试卷' }}</p>
+                            </div>
+                        </div>
+                    </x-slot>
+
+                    <div class="space-y-4">
+                        @foreach($generatedQuestions as $index => $question)
+                            @if(!empty($question['answer']))
+                                <div class="flex items-start gap-4 p-4 bg-emerald-50 rounded-lg">
+                                    <div class="flex-shrink-0 w-10 h-10 bg-emerald-600 text-white rounded-full flex items-center justify-center font-semibold">
+                                        {{ $index + 1 }}
+                                    </div>
+                                    <div class="flex-1">
+                                        <div class="text-sm text-gray-600 mb-1">
+                                            <strong>{{ $question['question_type'] ?? '题目' }} · {{ $question['kp_code'] ?? '' }}</strong>
+                                        </div>
+                                        <div class="text-sm text-emerald-700 font-medium">
+                                            <strong>答案:</strong> {!! $question['answer'] !!}
+                                        </div>
+                                        @if(isset($question['score']))
+                                            <div class="text-xs text-gray-500 mt-1">
+                                                分值:{{ $question['score'] }}分
+                                            </div>
+                                        @endif
+                                    </div>
+                                </div>
+                            @endif
+                        @endforeach
+                    </div>
+                </div>
             </div>
         @endif
     </div>

+ 212 - 8
resources/views/pdf/exam-paper.blade.php

@@ -83,13 +83,36 @@
             margin-left: 35px; /* 对齐题目内容 */
             margin-top: 10px;
         }
+        .options-grid-4 {
+            display: grid;
+            grid-template-columns: repeat(4, 1fr);
+            gap: 8px 12px;
+            margin-left: 35px;
+            margin-top: 10px;
+        }
+        .options-grid-2 {
+            display: grid;
+            grid-template-columns: 1fr 1fr;
+            gap: 8px 20px;
+            margin-left: 35px;
+            margin-top: 10px;
+        }
         .option {
             width: 100%;
             font-size: 14px;
-            margin-bottom: 8px;
-            padding-left: 10px;
             line-height: 1.5;
             word-wrap: break-word;
+            display: flex;
+            align-items: flex-start;
+        }
+        .option-inline {
+            display: inline-flex;
+            align-items: baseline;
+            margin-right: 20px;
+        }
+        .option-compact {
+            font-size: 13px;
+            line-height: 1.4;
         }
         .fill-line {
             display: inline-block;
@@ -186,12 +209,96 @@
                 </div>
 
                 @if(!empty($options))
-                    <div class="options">
-                        @foreach($options as $optIndex => $option)
-                            <div class="option">
-                                {{ chr(65 + $optIndex) }}. @math($option)
-                            </div>
-                        @endforeach
+                    @php
+                        // 确保有4个选项(A、B、C、D)
+                        $standardOptions = ['A', 'B', 'C', 'D'];
+                        $displayOptions = [];
+                        foreach ($standardOptions as $idx => $letter) {
+                            if (isset($options[$idx]) && !empty($options[$idx])) {
+                                $displayOptions[$letter] = $options[$idx];
+                            } else {
+                                // 补充缺失的选项
+                                $displayOptions[$letter] = '(待补充选项' . $letter . ')';
+                            }
+                        }
+
+                        // 计算选项长度,决定布局
+                        $maxOptionLength = 0;
+                        $totalOptionLength = 0;
+                        foreach ($displayOptions as $letter => $option) {
+                            $text = strip_tags($option);
+                            $length = mb_strlen($text);
+                            $maxOptionLength = max($maxOptionLength, $length);
+                            $totalOptionLength += $length;
+                        }
+
+                        // 三种布局选择:
+                        // 1. 一行4列:极短选项(单个选项≤10字符)
+                        // 2. 一行2列:短选项(单个选项≤25字符)
+                        // 3. 垂直布局:长选项(单个选项>25字符)
+                        if ($maxOptionLength <= 10) {
+                            $layoutType = 'inline-4';
+                            $maxOptionLength = 12; // 给一点余量
+                        } elseif ($maxOptionLength <= 25) {
+                            $layoutType = 'grid-2';
+                            $maxOptionLength = 30; // 给一点余量
+                        } else {
+                            $layoutType = 'vertical';
+                        }
+                    @endphp
+
+                    @if($layoutType === 'inline-4')
+                        {{-- 极短选项:一行4列布局 --}}
+                        <div style="margin-left: 35px; margin-top: 10px;">
+                            <span style="font-size: 14px;">
+                                @foreach($displayOptions as $letter => $option)
+                                    <span class="option-inline option-compact">
+                                        <span style="font-weight: bold; margin-right: 4px;">{{ $letter }}.</span>
+                                        <span>@math($option)</span>
+                                    </span>
+                                @endforeach
+                            </span>
+                        </div>
+                    @elseif($layoutType === 'grid-2')
+                        {{-- 短选项:一行2列布局 --}}
+                        <div class="options-grid-2">
+                            @foreach($displayOptions as $letter => $option)
+                                <div class="option">
+                                    <span style="font-weight: bold; margin-right: 8px;">{{ $letter }}.</span>
+                                    <span>@math($option)</span>
+                                </div>
+                            @endforeach
+                        </div>
+                    @else
+                        {{-- 长选项:垂直布局 --}}
+                        <div class="options">
+                            @foreach($displayOptions as $letter => $option)
+                                <div class="option">
+                                    <span style="font-weight: bold; margin-right: 8px;">{{ $letter }}.</span>
+                                    <span>@math($option)</span>
+                                </div>
+                            @endforeach
+                        </div>
+                    @endif
+                @else
+                    {{-- 如果没有任何选项,显示占位符 --}}
+                    <div class="options-grid-2">
+                        <div class="option" style="font-style: italic; color: #999;">
+                            <span style="font-weight: bold; margin-right: 8px;">A.</span>
+                            <span>(待补充选项A)</span>
+                        </div>
+                        <div class="option" style="font-style: italic; color: #999;">
+                            <span style="font-weight: bold; margin-right: 8px;">B.</span>
+                            <span>(待补充选项B)</span>
+                        </div>
+                        <div class="option" style="font-style: italic; color: #999;">
+                            <span style="font-weight: bold; margin-right: 8px;">C.</span>
+                            <span>(待补充选项C)</span>
+                        </div>
+                        <div class="option" style="font-style: italic; color: #999;">
+                            <span style="font-weight: bold; margin-right: 8px;">D.</span>
+                            <span>(待补充选项D)</span>
+                        </div>
                     </div>
                 @endif
             </div>
@@ -278,6 +385,103 @@
         (选择题 {{ $totalChoiceScore }} 分 + 填空题 {{ $totalFillScore }} 分 + 解答题 {{ $totalAnswerScore }} 分)
     </div>
 
+    {{-- 参考答案(仅在开启时显示) --}}
+    @if($includeAnswer)
+        <div style="page-break-before: always; margin-top: 40px;">
+            <div style="text-align: center; font-size: 22px; font-weight: bold; margin-bottom: 30px; border-bottom: 2px solid #000; padding-bottom: 10px;">
+                参考答案
+            </div>
+            <div style="font-size: 14px; margin-bottom: 20px; text-align: center; color: #666;">
+                {{ $paper->paper_name ?? '未命名试卷' }}
+            </div>
+
+            {{-- 选择题答案 --}}
+            @if(count($questions['choice']) > 0)
+                <div style="margin-bottom: 20px;">
+                    <div style="font-weight: bold; font-size: 16px; margin-bottom: 10px;">一、选择题</div>
+                    <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; font-size: 14px;">
+                        @foreach($questions['choice'] as $index => $q)
+                            <div style="padding: 8px; background: #f5f5f5; border-radius: 4px;">
+                                <span style="font-weight: bold;">{{ $index + 1 }}.</span>
+                                @if(!empty($q->answer))
+                                    <span style="margin-left: 8px; font-weight: bold; color: #d32f2f;">
+                                        @php
+                                            // 提取答案中的选项字母
+                                            $answerText = $q->answer;
+                                            $letter = '';
+                                            if (preg_match('/([A-D])/i', $answerText, $match)) {
+                                                $letter = strtoupper($match[1]);
+                                            } elseif (preg_match('/答案[::]\s*([A-D])/i', $answerText, $match)) {
+                                                $letter = strtoupper($match[1]);
+                                            }
+                                            echo $letter ?: '(待补充)';
+                                        @endphp
+                                    </span>
+                                @else
+                                    <span style="margin-left: 8px; color: #999; font-style: italic;">(待补充)</span>
+                                @endif
+                            </div>
+                        @endforeach
+                    </div>
+                </div>
+            @endif
+
+            {{-- 填空题答案 --}}
+            @if(count($questions['fill']) > 0)
+                <div style="margin-bottom: 20px;">
+                    <div style="font-weight: bold; font-size: 16px; margin-bottom: 10px;">二、填空题</div>
+                    <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; font-size: 14px;">
+                        @foreach($questions['fill'] as $index => $q)
+                            <div style="padding: 8px; background: #f5f5f5; border-radius: 4px;">
+                                <span style="font-weight: bold;">{{ count($questions['choice']) + $index + 1 }}.</span>
+                                <span style="margin-left: 8px;">
+                                    @if(!empty($q->answer))
+                                        @math($q->answer)
+                                    @else
+                                        <span style="color: #999; font-style: italic;">(待补充)</span>
+                                    @endif
+                                </span>
+                            </div>
+                        @endforeach
+                    </div>
+                </div>
+            @endif
+
+            {{-- 解答题答案 --}}
+            @if(count($questions['answer']) > 0)
+                <div style="margin-bottom: 20px;">
+                    <div style="font-weight: bold; font-size: 16px; margin-bottom: 15px;">三、解答题</div>
+                    <div style="space-y: 15px;">
+                        @foreach($questions['answer'] as $index => $q)
+                            @php
+                                $questionNumber = count($questions['choice']) + count($questions['fill']) + $index + 1;
+                            @endphp
+                            <div style="margin-bottom: 20px; padding: 15px; background: #f9f9f9; border-radius: 4px; border-left: 4px solid #4163ff;">
+                                <div style="font-weight: bold; font-size: 15px; margin-bottom: 10px;">
+                                    {{ $questionNumber }}. ({{ $q->score ?? 10 }}分)
+                                </div>
+                                @if(!empty($q->answer))
+                                    <div style="font-size: 14px; line-height: 1.8;">
+                                        @math($q->answer)
+                                    </div>
+                                @else
+                                    <div style="font-size: 14px; color: #999; font-style: italic;">
+                                        (答案待补充或请参考标准答案)
+                                    </div>
+                                @endif
+                                @if(!empty($q->solution))
+                                    <div style="margin-top: 10px; font-size: 13px; color: #666; padding-top: 10px; border-top: 1px dashed #ddd;">
+                                        <strong>解析:</strong> @math($q->solution)
+                                    </div>
+                                @endif
+                            </div>
+                        @endforeach
+                    </div>
+                </div>
+            @endif
+        </div>
+    @endif
+
     <div class="no-print" style="position: fixed; bottom: 20px; right: 20px;">
         <button onclick="window.print()" style="padding: 10px 20px; background: #4163ff; color: white; border: none; border-radius: 5px; cursor: pointer;">打印试卷</button>
     </div>