mistakeId = Request::get('mistake_id'); $this->studentId = Request::get('student_id'); $this->questionId = Request::get('question_id'); if ($this->mistakeId && $this->studentId) { $this->sourceType = 'mistake'; $this->loadFromMistake(); } else { $this->sourceType = 'question'; $this->loadFromQuestionBank(); } if (empty($this->questionData)) { $this->notifyAndRedirect( '未能加载到题目数据,请稍后重试', $this->sourceType === 'mistake' ? route('filament.admin.pages.mistake-book') : route('filament.admin.pages.question-management') ); } } protected function loadFromQuestionBank(): void { if (!$this->questionId) { $this->notifyAndRedirect('缺少题目ID参数', route('filament.admin.pages.question-management')); return; } $question = $this->fetchQuestion($this->questionId); if ($question) { $this->questionData = $this->prepareQuestionDisplay($question); $this->attachGlobalAccuracy(); } } protected function loadFromMistake(): void { $mistakeService = app(MistakeBookService::class); $detail = $mistakeService->getMistakeDetail($this->mistakeId, $this->studentId); if (empty($detail)) { $this->notifyAndRedirect('未能获取错题详情', route('filament.admin.pages.mistake-book')); return; } $this->mistakeData = $detail; $this->questionId = $detail['question_id'] ?? $this->questionId; $this->studentName = $detail['student_name'] ?? null; $bankQuestion = $this->fetchQuestion($this->questionId); $fallbackQuestion = $detail['question'] ?? []; if (!$bankQuestion && empty($fallbackQuestion)) { $this->notifyAndRedirect('题目不存在或已被删除', route('filament.admin.pages.mistake-book')); return; } $question = array_merge($fallbackQuestion, $bankQuestion ?? []); $question = $this->prepareQuestionDisplay($question); $this->questionData = array_merge($question, [ 'mistake_info' => [ 'student_answer' => $detail['student_answer'] ?? '', 'correct' => (bool) ($detail['correct'] ?? false), 'score' => $detail['score'] ?? null, 'full_score' => $detail['full_score'] ?? null, 'partial_score_ratio' => $detail['partial_score_ratio'] ?? null, 'error_type' => $detail['error_type'] ?? '', 'mistake_category' => $detail['mistake_category'] ?? '', 'ai_analysis' => $detail['ai_analysis'] ?? [], 'created_at' => $detail['created_at'] ?? '', ], ]); // 尝试从本地学生表补充姓名,避免仅展示ID if (!$this->studentName && $this->studentId) { try { $student = Student::find($this->studentId); if ($student && $student->name) { $this->studentName = $student->name; } } catch (\Throwable $e) { Log::warning('Load student name failed', [ 'student_id' => $this->studentId, 'error' => $e->getMessage(), ]); } } $this->loadHistorySummary(); $this->attachGlobalAccuracy(); } protected function fetchQuestion(?string $questionId): ?array { if (!$questionId) { return null; } try { $service = app(QuestionServiceApi::class); $response = $service->getQuestionDetail($questionId); return $response['data'] ?? null; } catch (\Throwable $e) { Log::error('Failed to load question detail', [ 'question_id' => $questionId, 'error' => $e->getMessage(), ]); return null; } } protected function notifyAndRedirect(string $message, string $route): void { Notification::make() ->title('提示') ->body($message) ->danger() ->send(); $this->redirect($route); } public function getTitle(): string { if ($this->sourceType === 'mistake') { $label = $this->questionData['question_number'] ?? ('#' . ($this->mistakeId ?? '')); return '错题详情 - ' . $label; } if (!empty($this->questionData)) { return '题目详情 - ' . ($this->questionData['question_code'] ?? $this->questionId); } return '题目详情'; } public function getBreadcrumbs(): array { $breadcrumbs = [ [ 'name' => '题库管理', 'url' => route('filament.admin.pages.question-management'), ], ]; if ($this->sourceType === 'mistake') { $breadcrumbs[] = [ 'name' => '错题本', 'url' => route('filament.admin.pages.mistake-book'), ]; } $breadcrumbs[] = [ 'name' => '题目详情', 'url' => '', ]; return $breadcrumbs; } public function getDifficultyColor(): string { if (!isset($this->questionData['difficulty'])) { return 'bg-gray-100 text-gray-700'; } $difficulty = (float) $this->questionData['difficulty']; if ($difficulty < 0.4) { return 'bg-green-100 text-green-700'; } elseif ($difficulty < 0.7) { return 'bg-yellow-100 text-yellow-700'; } return 'bg-red-100 text-red-700'; } public function getDifficultyLabel(): string { if (!isset($this->questionData['difficulty'])) { return '未知'; } $difficulty = (float) $this->questionData['difficulty']; if ($difficulty < 0.4) { return '简单'; } elseif ($difficulty < 0.7) { return '中等'; } return '困难'; } public function getKnowledgePointName(): string { if (!isset($this->questionData['kp_code'])) { return ''; } $kpCode = $this->questionData['kp_code']; static $kpMap = null; if ($kpMap === null) { $kpMap = []; try { $service = app(KnowledgeGraphService::class); $resp = $service->listKnowledgePoints(1, 1000); foreach ($resp['data'] ?? [] as $kp) { $code = $kp['kp_code'] ?? $kp['id'] ?? null; if (!$code) { continue; } $kpMap[$code] = $kp['cn_name'] ?? $kp['name'] ?? $code; } } catch (\Throwable $e) { Log::warning('Load knowledge point names failed', [ 'error' => $e->getMessage(), ]); } } return $kpMap[$kpCode] ?? $kpCode; } protected function loadHistorySummary(): void { if (!$this->studentId || !$this->questionId) { return; } try { $service = app(MistakeBookService::class); $list = $service->listMistakes([ 'student_id' => $this->studentId, 'per_page' => 100, ]); $data = $list['data'] ?? []; $filtered = array_values(array_filter($data, function ($item) { $qid = $item['question_id'] ?? ($item['question']['id'] ?? null); if ($qid === null) { return false; } return (string) $qid === (string) $this->questionId; })); if (empty($filtered)) { return; } $total = count($filtered); $correct = count(array_filter($filtered, fn ($item) => !empty($item['correct']))); $latest = collect($filtered)->sortByDesc('created_at')->first(); $this->historySummary = [ 'total' => $total, 'correct' => $correct, 'last_correct' => (bool) ($latest['correct'] ?? false), 'last_time' => $latest['created_at'] ?? null, ]; } catch (\Throwable $e) { Log::warning('Load history summary failed', [ 'student_id' => $this->studentId, 'question_id' => $this->questionId, 'error' => $e->getMessage(), ]); } } /** * 对题干/选项做展示友好化处理:拆分嵌入式选项,保留 display_stem 与 display_options */ protected function prepareQuestionDisplay(array $question): array { $question['display_stem'] = $question['stem'] ?? ''; // 规范化选项 $options = $question['options'] ?? []; if (is_string($options)) { $decoded = json_decode($options, true); if (is_array($decoded)) { $options = $decoded; } } // 如果没有单独选项,但题干里嵌入了 A./B./C./D.,尝试提取 if (empty($options) && !empty($question['stem']) && is_string($question['stem'])) { $stem = $question['stem']; $parsedOptions = []; $pattern = '/(?= 2 && count($parsedOptions) <= 4) { $options = $parsedOptions; $firstMatchPos = mb_strpos($stem, $matches[0][0]); if ($firstMatchPos !== false) { $question['display_stem'] = trim(mb_substr($stem, 0, $firstMatchPos)); } } } } $question['display_options'] = $options; return $question; } protected function attachGlobalAccuracy(): void { if (!$this->questionId) { return; } try { $service = app(MistakeBookService::class); $accuracy = $service->getQuestionAccuracy($this->questionId); if ($accuracy !== null) { $this->questionData['global_accuracy'] = $accuracy; } } catch (\Throwable $e) { Log::warning('Attach global accuracy failed', [ 'question_id' => $this->questionId, 'error' => $e->getMessage(), ]); } } }