yemeishu 2 недель назад
Родитель
Сommit
39e0faaeea

+ 41 - 15
app/Services/ExamPdfExportService.php

@@ -72,7 +72,8 @@ class ExamPdfExportService
                 return null;
             }
 
-            $path = "analysis_reports/{$paperId}_{$studentId}.pdf";
+            $version = time();
+            $path = "analysis_reports/{$paperId}_{$studentId}_{$version}.pdf";
             Storage::disk('public')->put($path, $pdfBinary);
 
             return URL::to(Storage::url($path));
@@ -239,6 +240,8 @@ class ExamPdfExportService
         ]);
         $process->setTimeout(60);
 
+        $killSignal = \defined('SIGKILL') ? \SIGKILL : 9;
+
         try {
             $startedAt = microtime(true);
             Log::info('ExamPdfExportService: Chrome 渲染启动', [
@@ -263,7 +266,7 @@ class ExamPdfExportService
                         'duration_sec' => round(microtime(true) - $startedAt, 3),
                         'tmp_pdf_size' => filesize($tmpPdf),
                     ]);
-                    $process->stop(5, SIGKILL);
+                    $process->stop(5, $killSignal);
                     break;
                 }
                 usleep(200_000); // 200ms
@@ -274,7 +277,7 @@ class ExamPdfExportService
                 Log::warning('ExamPdfExportService: Chrome 轮询超时,强制结束', [
                     'duration_sec' => round(microtime(true) - $startedAt, 3),
                 ]);
-                $process->stop(5, SIGKILL);
+                $process->stop(5, $killSignal);
             }
 
             $process->wait();
@@ -298,7 +301,7 @@ class ExamPdfExportService
                 'trace' => $e->getTraceAsString(),
             ]);
             if ($process->isRunning()) {
-                $process->stop(5, SIGKILL);
+                $process->stop(5, $killSignal);
             }
             $pdfExists = file_exists($tmpPdf);
             $pdfSize = $pdfExists ? filesize($tmpPdf) : null;
@@ -326,7 +329,7 @@ class ExamPdfExportService
                 'trace' => $e->getTraceAsString(),
             ]);
             if ($process->isRunning()) {
-                $process->stop(5, SIGKILL);
+                $process->stop(5, $killSignal);
             }
             $pdfExists = file_exists($tmpPdf);
             $pdfSize = $pdfExists ? filesize($tmpPdf) : null;
@@ -387,7 +390,7 @@ class ExamPdfExportService
     private function buildAnalysisPayload(string $paperId, string $studentId): ?array
     {
         $paper = Paper::with(['questions' => function ($query) {
-            $query->orderBy('question_number');
+            $query->orderBy('question_number')->orderBy('id');
         }])->find($paperId);
         if (!$paper) {
             Log::error('ExamPdfExportService: 未找到试卷,无法生成学情报告', [
@@ -445,17 +448,31 @@ class ExamPdfExportService
             }
         }
 
-        $questions = $paper->questions
-            ->map(function (PaperQuestion $question) use ($kpNameMap, $questionDetails) {
+        // 分组保持卷面顺序:选择题 -> 填空题 -> 解答题
+        $grouped = [
+            'choice' => [],
+            'fill' => [],
+            'answer' => [],
+        ];
+
+        $sortedQuestions = $paper->questions
+            ->sortBy(function (PaperQuestion $q, int $idx) {
+                $number = $q->question_number ?? $idx + 1;
+                return is_numeric($number) ? (float) $number : ($q->id ?? $idx);
+            });
+
+        foreach ($sortedQuestions as $idx => $question) {
             $kpCode = $question->knowledge_point ?? '';
             $kpName = $kpNameMap[$kpCode] ?? $kpCode ?: '未标注';
             $detail = $questionDetails[(string) ($question->question_id ?? '')] ?? [];
             $solution = $detail['solution'] ?? $detail['解析'] ?? $detail['analysis'] ?? null;
-            $typeRaw = $detail['question_type'] ?? $detail['type'] ?? $question->question_type ?? '';
+            // 题型优先使用试卷记录,其次题库详情
+            $typeRaw = $question->question_type ?? ($detail['question_type'] ?? $detail['type'] ?? '');
             $normalizedType = $this->normalizeQuestionType($typeRaw);
+            $number = $question->question_number ?? ($idx + 1);
 
-            return [
-                'question_number' => $question->question_number,
+            $payload = [
+                'question_number' => $number,
                 'question_text' => is_array($question->question_text) ? json_encode($question->question_text, JSON_UNESCAPED_UNICODE) : ($question->question_text ?? ''),
                 'question_type' => $normalizedType,
                 'knowledge_point' => $kpCode,
@@ -463,10 +480,19 @@ class ExamPdfExportService
                 'score' => $question->score,
                 'solution' => $solution,
             ];
-        })
-            ->sortBy('question_number')
-            ->values()
-            ->toArray();
+
+            $grouped[$normalizedType][] = $payload;
+        }
+
+        $ordered = array_merge($grouped['choice'], $grouped['fill'], $grouped['answer']);
+
+        // 按卷面顺序重新编号以匹配判卷/显示
+        foreach ($ordered as $i => &$q) {
+            $q['display_number'] = $i + 1;
+        }
+        unset($q);
+
+        $questions = $ordered;
 
         $questionInsights = $analysisData['question_results'] ?? [];
         $masterySummary = $this->buildMasterySummary($masteryData, $kpNameMap);

+ 2 - 2
resources/views/exam-analysis/pdf-report.blade.php

@@ -82,7 +82,7 @@
         @endphp
         @foreach($questions as $q)
             @php
-                $insight = $insightMap[$q['question_number']] ?? [];
+                $insight = $insightMap[$q['question_number']] ?? ($insightMap[$q['display_number'] ?? null] ?? []);
                 $score = $insight['score'] ?? ($insight['student_score'] ?? null);
                 $fullScore = $insight['full_score'] ?? ($q['score'] ?? null);
                 $analysisRaw = $insight['analysis']
@@ -117,7 +117,7 @@
             <div style="border:1px solid #e5e7eb; border-radius:10px; padding:12px 14px; margin-bottom:10px; background:#fff; page-break-inside: avoid;">
                 <div style="display:flex; justify-content:space-between; align-items:center; gap:8px; margin-bottom:6px;">
                     <div style="display:flex; align-items:center; gap:8px; font-weight:600;">
-                        <span class="tag">题号 {{ $q['question_number'] }}</span>
+                        <span class="tag">题号 {{ $q['display_number'] ?? $q['question_number'] }}</span>
                         <span class="tag" style="background: #eef2ff; color:#4338ca;">{{ $q['knowledge_point_name'] ?? $q['knowledge_point'] ?? '-' }}</span>
                         <span class="tag" style="background: {{ $badgeColor }}; color:#fff;">{{ $badgeText }}</span>
                     </div>