[], 'skill_ids' => [], 'error_types' => [], 'time_range' => 'last_30', 'start_date' => null, 'end_date' => null, ]; public array $filterOptions = [ 'knowledge_points' => [], 'skills' => [], ]; public array $mistakes = []; public array $patterns = []; public array $summary = []; public array $recommendations = []; public array $relatedQuestions = []; public array $selectedMistakeIds = []; public bool $isLoading = false; public string $errorMessage = ''; public string $actionMessage = ''; public string $actionMessageType = 'success'; public function mount(Request $request): void { $this->teacherId = (string) ($request->input('teacher_id') ?? ''); $this->studentId = (string) ($request->input('student_id') ?? ''); $this->filters['time_range'] = (string) ($request->input('range') ?? 'last_30'); if ($this->studentId && empty($this->teacherId)) { $student = Student::find($this->studentId); if ($student && $student->teacher_id) { $this->teacherId = (string) $student->teacher_id; } } $this->loadFilterOptions(); if ($this->studentId) { $this->loadMistakeData(); } } public function updatedStudentId(): void { if ($this->studentId) { $this->loadMistakeData(); } else { $this->resetPageState(); } } public function loadMistakeData(): void { if (empty($this->studentId)) { $this->errorMessage = '请先选择学生'; return; } $this->isLoading = true; $this->errorMessage = ''; $this->actionMessage = ''; try { $service = app(MistakeBookService::class); $list = $service->listMistakes([ ...$this->filters, 'student_id' => $this->studentId, ]); $this->mistakes = $list['data'] ?? []; $this->summary = $service->summarize($this->studentId); $this->patterns = $service->getMistakePatterns($this->studentId); // 清理无效的选中项 $validIds = collect($this->mistakes)->pluck('id')->filter()->all(); $this->selectedMistakeIds = array_values( array_intersect($this->selectedMistakeIds, $validIds) ); } catch (\Throwable $e) { $this->errorMessage = '加载错题本数据失败:' . $e->getMessage(); Log::error('Load mistake book failed', [ 'student_id' => $this->studentId, 'error' => $e->getMessage(), ]); } finally { $this->isLoading = false; } } public function refreshPatterns(): void { if (!$this->studentId) { return; } try { $service = app(MistakeBookService::class); $this->patterns = $service->getMistakePatterns($this->studentId); } catch (\Throwable $e) { Log::error('Refresh mistake patterns failed', [ 'student_id' => $this->studentId, 'error' => $e->getMessage(), ]); } } public function toggleFavorite(string $mistakeId): void { $service = app(MistakeBookService::class); $current = $this->findMistakeById($mistakeId); $willFavorite = !($current['favorite'] ?? false); if ($service->toggleFavorite($mistakeId, $willFavorite)) { $this->updateMistakeField($mistakeId, 'favorite', $willFavorite); $this->notify('已更新收藏状态'); } else { $this->notify('收藏操作失败,请稍后再试', 'danger'); } } public function markReviewed(string $mistakeId): void { $service = app(MistakeBookService::class); if ($service->markReviewed($mistakeId)) { $this->updateMistakeField($mistakeId, 'reviewed', true); $this->notify('已标记为已复习'); } else { $this->notify('标记失败,请稍后再试', 'danger'); } } public function addToRetryList(string $mistakeId): void { $service = app(MistakeBookService::class); if ($service->addToRetryList($mistakeId)) { $this->notify('已加入重练清单'); } else { $this->notify('加入清单失败,请稍后再试', 'danger'); } } public function loadRelatedQuestions(string $mistakeId): void { $mistake = $this->findMistakeById($mistakeId); if (empty($mistake)) { return; } $questionBank = app(QuestionBankService::class); $kpIds = Arr::wrap($mistake['kp_ids'] ?? []); $skills = Arr::wrap($mistake['skill_ids'] ?? $mistake['skills'] ?? []); $response = $questionBank->filterQuestions(array_filter([ 'kp_codes' => !empty($kpIds) ? implode(',', $kpIds) : null, 'skills' => !empty($skills) ? implode(',', $skills) : null, 'limit' => 5, ])); $this->relatedQuestions[$mistakeId] = $response['data'] ?? []; } public function toggleSelection(string $mistakeId): void { if (in_array($mistakeId, $this->selectedMistakeIds, true)) { $this->selectedMistakeIds = array_values(array_diff($this->selectedMistakeIds, [$mistakeId])); } else { $this->selectedMistakeIds[] = $mistakeId; } } public function generatePracticeFromSelection(): void { if (empty($this->selectedMistakeIds)) { $this->notify('请先选择至少一道错题', 'warning'); return; } $selected = array_filter($this->mistakes, fn ($item) => in_array($item['id'] ?? '', $this->selectedMistakeIds, true)); $kpIds = collect($selected) ->pluck('kp_ids') ->flatten() ->filter() ->unique() ->values() ->all(); $skillIds = collect($selected) ->pluck('skill_ids') ->flatten() ->filter() ->unique() ->values() ->all(); $service = app(MistakeBookService::class); $result = $service->recommendPractice($this->studentId, $kpIds, $skillIds); $this->recommendations = $result['data'] ?? ($result['questions'] ?? []); if (!empty($this->recommendations)) { $this->notify('已生成重练题单'); } else { $this->notify('未能生成题单,请稍后再试', 'warning'); } } public function applyFilters(): void { $this->loadMistakeData(); } public function clearCustomRange(): void { $this->filters['start_date'] = null; $this->filters['end_date'] = null; } #[Computed] public function teachers(): array { try { $teachers = Teacher::query() ->leftJoin('users as u', 'teachers.teacher_id', '=', 'u.user_id') ->select( 'teachers.teacher_id', 'teachers.name', 'teachers.subject', 'u.username', 'u.email' ) ->orderBy('teachers.name') ->get(); $teacherIds = $teachers->pluck('teacher_id')->toArray(); $missingTeacherIds = Student::query() ->distinct() ->whereNotIn('teacher_id', $teacherIds) ->pluck('teacher_id') ->toArray(); $teachersArray = $teachers->all(); if (!empty($missingTeacherIds)) { foreach ($missingTeacherIds as $missingId) { $teachersArray[] = (object) [ 'teacher_id' => $missingId, 'name' => '未知老师 (' . $missingId . ')', 'subject' => '未知', 'username' => null, 'email' => null ]; } usort($teachersArray, function($a, $b) { return strcmp($a->name, $b->name); }); } return $teachersArray; } catch (\Exception $e) { Log::error('加载老师列表失败', [ 'error' => $e->getMessage() ]); return []; } } #[Computed] public function students(): array { if (empty($this->teacherId)) { return []; } try { return Student::query() ->leftJoin('users as u', 'students.student_id', '=', 'u.user_id') ->where('students.teacher_id', $this->teacherId) ->select( 'students.student_id', 'students.name', 'students.grade', 'students.class_name', 'u.username', 'u.email' ) ->orderBy('students.grade') ->orderBy('students.class_name') ->orderBy('students.name') ->get() ->all(); } catch (\Exception $e) { Log::error('加载学生列表失败', [ 'teacher_id' => $this->teacherId, 'error' => $e->getMessage() ]); return []; } } public function getStudents(): array { return Student::query() ->select(['student_id', 'name', 'grade', 'class_name']) ->orderBy('grade') ->orderBy('class_name') ->orderBy('name') ->get() ->toArray(); } #[On('teacherChanged')] public function onTeacherChanged(string $teacherId): void { $this->teacherId = $teacherId; $this->studentId = ''; $this->resetPageState(); } #[On('studentChanged')] public function onStudentChanged(?string $teacherId, ?string $studentId): void { $this->teacherId = (string) ($teacherId ?? ''); $this->studentId = (string) ($studentId ?? ''); if ($this->studentId) { $this->loadMistakeData(); } else { $this->resetPageState(); } } protected function loadFilterOptions(): void { try { $knowledgeService = app(KnowledgeServiceApi::class); $knowledge = $knowledgeService->listKnowledgePoints(150); $this->filterOptions['knowledge_points'] = $knowledge ->map(function ($item) { $code = $item['kp_code'] ?? $item['code'] ?? null; if (!$code) { return null; } return [ 'code' => $code, 'name' => $item['cn_name'] ?? $item['name'] ?? $code, ]; }) ->filter() ->take(200) ->values() ->toArray(); $skills = $knowledgeService->listSkills(null, 200); $this->filterOptions['skills'] = $skills ->map(function ($item) { return [ 'id' => $item['skill_id'] ?? $item['id'] ?? ($item['code'] ?? ''), 'name' => $item['name'] ?? $item['skill_name'] ?? ($item['code'] ?? ''), 'kp_code' => $item['kp_code'] ?? $item['knowledge_point_code'] ?? null, ]; }) ->filter(fn ($item) => filled($item['id'])) ->values() ->toArray(); } catch (\Throwable $e) { Log::error('Load filter options failed', [ 'error' => $e->getMessage(), ]); $this->filterOptions = ['knowledge_points' => [], 'skills' => []]; } } protected function updateMistakeField(string $mistakeId, string $field, $value): void { foreach ($this->mistakes as &$mistake) { if (($mistake['id'] ?? null) === $mistakeId) { $mistake[$field] = $value; break; } } } protected function findMistakeById(string $mistakeId): array { foreach ($this->mistakes as $mistake) { if (($mistake['id'] ?? null) === $mistakeId) { return $mistake; } } return []; } protected function resetPageState(): void { $this->mistakes = []; $this->patterns = []; $this->summary = []; $this->selectedMistakeIds = []; $this->recommendations = []; $this->relatedQuestions = []; $this->actionMessage = ''; $this->errorMessage = ''; } protected function notify(string $message, string $type = 'success'): void { $this->actionMessage = $message; $this->actionMessageType = $type; } }