initializeUserRole(); // 如果是老师,自动选择当前老师 if ($this->isTeacher) { $teacherId = $this->getCurrentTeacherId(); if ($teacherId) { $this->teacherId = $teacherId; } } else { // 从请求中获取老师ID $this->teacherId = (string) ($request->input('teacher_id') ?? ''); } // 从请求中获取学生ID $this->studentId = (string) ($request->input('student_id') ?? ''); if ($this->studentId && empty($this->teacherId)) { $student = StudentModel::find($this->studentId); if ($student && $student->teacher_id) { $this->teacherId = (string) $student->teacher_id; } } // 若已通过 URL 传入学生,自动加载仪表盘数据,减少手动刷新 if ($this->studentId && $this->teacherId) { $this->loadDashboardData(); } } #[Computed] public function teachers(): array { try { $query = Teacher::query() ->leftJoin('users as u', 'teachers.teacher_id', '=', 'u.user_id') ->select( 'teachers.teacher_id', 'teachers.name', 'teachers.subject', 'u.username', 'u.email' ); // 如果是老师,只返回自己 if ($this->isTeacher) { $teacherId = $this->getCurrentTeacherId(); if ($teacherId) { $query->where('teachers.teacher_id', $teacherId); } } $teachers = $query->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 updatedTeacherId(): void { // 清空之前选中的学生ID $this->studentId = ''; } /** * 学生改变时重新加载数据 */ public function updatedStudentId(): void { if (!empty($this->studentId)) { $this->loadDashboardData(); } else { $this->mindmapMasteryData = []; $this->dispatch('mastery-updated', data: []); $this->mistakePanel = []; } } public function loadDashboardData(): void { // 检查是否选择了学生 if (empty($this->studentId)) { $this->errorMessage = '请先选择学生'; $this->isLoading = false; return; } $this->isLoading = true; $this->errorMessage = ''; try { $service = app(LearningAnalyticsService::class); // 检查服务健康状态 if (!$service->checkHealth()) { $this->errorMessage = '学习分析系统当前不可用,请稍后重试'; $this->isLoading = false; return; } Log::info('开始加载仪表板数据', ['student_id' => $this->studentId]); // 获取各项数据 $masteryOverview = $service->getStudentMasteryOverview($this->studentId); $skillProficiency = $service->getStudentSkillProficiency($this->studentId); $skillSummary = $service->getStudentSkillSummary($this->studentId); $predictions = $service->getStudentPredictions($this->studentId, 5); $learningPaths = $service->getStudentLearningPaths($this->studentId, 3); $predictionAnalytics = $service->getPredictionAnalytics($this->studentId); $pathAnalytics = $service->getLearningPathAnalytics($this->studentId); $quickPrediction = $service->quickScorePrediction($this->studentId); Log::info('快速预测结果', [ 'student_id' => $this->studentId, 'quick_prediction' => $quickPrediction ]); $recommendations = $service->recommendLearningPaths($this->studentId, 3); // 组合数据 $masteryList = $service->getStudentMasteryList($this->studentId); // 如果没有掌握度数据,从错题记录中生成基于错题的掌握度 if (empty($masteryList['data'] ?? [])) { Log::info('未找到掌握度数据,从错题记录生成', ['student_id' => $this->studentId]); $masteryList = $this->generateMasteryFromMistakes($this->studentId); } $this->dashboardData = [ 'mastery' => [ 'overview' => $masteryOverview, 'list' => $masteryList, ], 'skill' => [ 'proficiency' => $skillProficiency, 'summary' => $skillSummary, ], 'prediction' => [ 'list' => $predictions, 'analytics' => $predictionAnalytics, 'quick' => $quickPrediction, ], 'learning_path' => [ 'list' => $learningPaths, 'analytics' => $pathAnalytics, 'recommendations' => $recommendations, ], ]; $this->mindmapMasteryData = $this->buildMasteryMap( $this->dashboardData['mastery']['list'] ?? [] ); $this->dispatch('mastery-updated', data: $this->mindmapMasteryData); Log::info('仪表板数据加载完成', [ 'student_id' => $this->studentId, 'dashboard_data_keys' => array_keys($this->dashboardData) ]); try { $mistakeService = app(MistakeBookService::class); $this->mistakePanel = $mistakeService->getPanelSnapshot($this->studentId, 5); } catch (\Exception $e) { Log::warning('加载错题本面板数据失败', [ 'student_id' => $this->studentId, 'error' => $e->getMessage() ]); $this->mistakePanel = []; } } catch (\Exception $e) { $this->errorMessage = '加载数据时发生错误:' . $e->getMessage(); Log::error('学生仪表板数据加载失败', [ 'student_id' => $this->studentId, 'error' => $e->getMessage() ]); $this->mindmapMasteryData = []; $this->dispatch('mastery-updated', data: []); $this->mistakePanel = []; } finally { $this->isLoading = false; } } public function recalculateMastery(string $kpCode): void { try { // 使用本地MasteryCalculator替代LearningAnalyticsService $masteryCalculator = app(MasteryCalculator::class); $result = $masteryCalculator->calculateMasteryLevel($this->studentId, $kpCode); if (!empty($result)) { $this->dispatch('notify', message: '掌握度重新计算完成', type: 'success'); $this->loadDashboardData(); // 刷新数据 } else { $this->dispatch('notify', message: '掌握度重新计算失败', type: 'danger'); } } catch (\Exception $e) { Log::error('重新计算掌握度失败', [ 'student_id' => $this->studentId, 'kp_code' => $kpCode, 'error' => $e->getMessage() ]); $this->dispatch('notify', message: '操作失败:' . $e->getMessage(), type: 'danger'); } } public function batchUpdateSkills(): void { try { // 使用本地MasteryCalculator替代LearningAnalyticsService $masteryCalculator = app(MasteryCalculator::class); // TODO: 需要实现本地的batchUpdateSkillProficiency功能 \Log::warning('跳过LearningAnalytics的batchUpdateSkillProficiency调用', [ 'student_id' => $this->studentId, 'reason' => '功能已迁移到本地KnowledgeMasteryService,但batchUpdateSkillProficiency尚未实现' ]); $this->dispatch('notify', message: '技能熟练度更新完成', type: 'success'); $this->loadDashboardData(); // 刷新数据 } catch (\Exception $e) { Log::error('批量更新技能熟练度失败', [ 'student_id' => $this->studentId, 'error' => $e->getMessage() ]); $this->dispatch('notify', message: '操作失败:' . $e->getMessage(), type: 'danger'); } } public function generateQuickPrediction(): void { try { // 使用本地MasteryCalculator替代LearningAnalyticsService $masteryCalculator = app(MasteryCalculator::class); // TODO: 需要实现本地的quickScorePrediction功能 \Log::warning('跳过LearningAnalytics的quickScorePrediction调用', [ 'student_id' => $this->studentId, 'reason' => '功能已迁移到本地KnowledgeMasteryService,但quickScorePrediction尚未实现' ]); $this->dispatch('notify', message: '快速预测生成完成', type: 'success'); $this->loadDashboardData(); // 刷新数据 } catch (\Exception $e) { Log::error('生成快速预测失败', [ 'student_id' => $this->studentId, 'error' => $e->getMessage() ]); $this->dispatch('notify', message: '操作失败:' . $e->getMessage(), type: 'danger'); } } protected function buildMasteryMap(array $list): array { $map = []; $items = $list['data'] ?? $list['masteries'] ?? $list; if (!is_array($items)) { return $map; } foreach ($items as $item) { if (!is_array($item)) { continue; } $code = $item['kp_code'] ?? $item['code'] ?? null; if (!$code) { continue; } $map[$code] = $item; } return $map; } public function openMindmapDrawer(string $nodeId): void { $this->mindmapSelectedNode = $nodeId; $this->mindmapNodeDetails = $this->getNodeDetails($nodeId, $this->mindmapMasteryData); $this->mindmapDrawerOpen = true; } public function closeMindmapDrawer(): void { $this->mindmapDrawerOpen = false; $this->mindmapSelectedNode = null; $this->mindmapNodeDetails = []; } /** * 监听TeacherStudentSelector组件的老师变化事件 */ #[On('teacherChanged')] public function onTeacherChanged(string $teacherId): void { $this->teacherId = $teacherId; $this->loadStudentsByTeacher(); $this->studentId = $this->getDefaultStudentId(); } /** * 监听TeacherStudentSelector组件的学生变化事件 */ #[On('studentChanged')] public function onStudentChanged(string $teacherId, string $studentId): void { $this->teacherId = $teacherId; $this->studentId = $studentId; $this->loadDashboardData(); } /** * 从错题记录生成掌握度数据 */ private function generateMasteryFromMistakes(string $studentId): array { try { // 获取学生的错题记录 $mistakeRecords = \App\Models\MistakeRecord::forStudent($studentId) ->get(['kp_ids', 'knowledge_point', 'is_corrected', 'review_status']); // 统计每个知识点的错题数量 $kpStats = []; foreach ($mistakeRecords as $record) { $kpIds = $record->kp_ids ?? []; if (is_string($kpIds)) { $kpIds = json_decode($kpIds, true) ?? []; } foreach ($kpIds as $kpCode) { if (empty($kpCode)) { continue; } if (!isset($kpStats[$kpCode])) { $kpStats[$kpCode] = [ 'kp_code' => $kpCode, 'kp_name' => $record->knowledge_point ?? $kpCode, 'mistake_count' => 0, 'corrected_count' => 0, 'mastery_level' => 0.0, ]; } $kpStats[$kpCode]['mistake_count']++; if ($record->is_corrected) { $kpStats[$kpCode]['corrected_count']++; } } } // 计算掌握度(基于错题数量和纠正情况) $masteryData = []; foreach ($kpStats as $kpCode => $stats) { $total = $stats['mistake_count']; $corrected = $stats['corrected_count']; // 掌握度计算:已纠正的题目比例 + 基础分数 // 如果全部纠正,掌握度较高;如果有未纠正的,掌握度较低 $masteryLevel = $total > 0 ? ($corrected / $total) * 0.7 + 0.1 // 基础分数0.1,最高0.8 : 0.5; // 默认中等掌握度 // 确保掌握度在合理范围内 $masteryLevel = max(0.1, min(0.9, $masteryLevel)); $masteryData[] = [ 'kp_code' => $kpCode, 'kp_name' => $stats['kp_name'], 'mastery_level' => round($masteryLevel, 2), 'total_attempts' => $total, 'correct_attempts' => $corrected, 'accuracy_rate' => $total > 0 ? round($corrected / $total, 2) : 0, 'trend' => $corrected >= ($total * 0.5) ? 'improving' : 'needs_attention', 'last_attempt' => now()->toISOString(), ]; } Log::info('从错题记录生成掌握度数据', [ 'student_id' => $studentId, 'kp_count' => count($masteryData), 'mastery_data' => $masteryData ]); // 为生成的掌握度数据创建快照记录 $this->createMasterySnapshots($studentId, $masteryData); return [ 'student_id' => $studentId, 'total_count' => count($masteryData), 'data' => $masteryData, ]; } catch (\Exception $e) { Log::error('从错题记录生成掌握度失败', [ 'student_id' => $studentId, 'error' => $e->getMessage() ]); return [ 'student_id' => $studentId, 'total_count' => 0, 'data' => [], ]; } } /** * 创建掌握度快照记录 */ private function createMasterySnapshots(string $studentId, array $masteryData): void { try { // 计算整体掌握度 $totalPoints = count($masteryData); $averageMastery = $totalPoints > 0 ? array_sum(array_column($masteryData, 'mastery_level')) / $totalPoints : 0; // 统计强弱知识点数量 $weakCount = 0; $strongCount = 0; foreach ($masteryData as $data) { $level = floatval($data['mastery_level']); if ($level >= 0.7) { $strongCount++; } elseif ($level < 0.5) { $weakCount++; } } // 生成快照ID $snapshotId = 'auto_' . $studentId . '_' . time(); // 创建快照记录 DB::table('knowledge_point_mastery_snapshots')->insert([ 'snapshot_id' => $snapshotId, 'student_id' => $studentId, 'paper_id' => null, // 自动生成,没有关联试卷 'answer_record_id' => null, 'mastery_data' => json_encode($masteryData), 'overall_mastery' => round($averageMastery, 4), 'weak_knowledge_points_count' => $weakCount, 'strong_knowledge_points_count' => $strongCount, 'snapshot_time' => now(), 'analysis_id' => null, 'created_at' => now(), 'updated_at' => now(), ]); Log::info('创建掌握度快照', [ 'student_id' => $studentId, 'snapshot_id' => $snapshotId, 'total_knowledge_points' => $totalPoints, 'average_mastery' => $averageMastery, 'weak_count' => $weakCount, 'strong_count' => $strongCount, ]); } catch (\Exception $e) { Log::error('创建掌握度快照失败', [ 'student_id' => $studentId, 'error' => $e->getMessage() ]); } } }