'required|exists:students,student_id', ]; public function mount(bool $showNodeDetails = true, string $detailLayout = 'inline') { $this->showNodeDetails = $showNodeDetails; $this->detailLayout = in_array($detailLayout, ['inline', 'drawer'], true) ? $detailLayout : 'inline'; $this->loadStudents(); } public function updatedSelectedStudentId($value) { if ($value) { $this->loadStudentData($value); } else { $this->resetData(); } } public function loadStudents() { $this->students = DB::table('students') ->select('student_id', 'name', 'grade', 'class_name') ->orderBy('grade') ->orderBy('class_name') ->orderBy('name') ->get() ->map(function ($student) { return [ 'id' => $student->student_id, 'label' => "{$student->name} ({$student->grade}-{$student->class_name})", ]; }) ->toArray(); } public function loadStudentData($studentId) { $this->isLoading = true; try { // 获取学生信息 $this->selectedStudent = DB::table('students') ->where('student_id', $studentId) ->first(); // 调用LearningAnalytics API获取知识图谱数据 $this->fetchKnowledgeGraphData($studentId); } catch (\Exception $e) { session()->flash('error', '加载数据失败:' . $e->getMessage()); \Log::error('加载学生知识图谱失败', [ 'student_id' => $studentId, 'error' => $e->getMessage(), ]); } $this->isLoading = false; } private function fetchKnowledgeGraphData($studentId) { $baseUrl = config('services.learning_analytics.url', 'http://localhost:5010'); $masteryPayload = []; try { // 获取掌握度数据 $masteryResponse = Http::timeout(10)->get($baseUrl . '/api/mastery/' . $studentId); if ($masteryResponse->successful()) { $masteryPayload = $masteryResponse->json(); } // 获取依赖关系 $dependencyResponse = Http::timeout(10)->get($baseUrl . '/api/knowledge/dependencies'); if ($dependencyResponse->successful()) { $this->dependencies = $dependencyResponse->json(); } // 获取统计信息 $statsResponse = Http::timeout(10)->get($baseUrl . '/api/mastery/' . $studentId . '/statistics'); if ($statsResponse->successful()) { $this->statistics = $statsResponse->json(); } // 获取学习路径 $pathResponse = Http::timeout(10)->get($baseUrl . '/api/learning-path/' . $studentId); if ($pathResponse->successful()) { $this->learningPath = $pathResponse->json(); } $this->setMasteryData($masteryPayload); // 构建知识点图谱数据 $this->buildKnowledgeGraphData(); } catch (\Exception $e) { \Log::warning('LearningAnalytics API调用失败,使用本地数据', [ 'error' => $e->getMessage(), ]); // 如果API调用失败,使用本地模拟数据 $this->loadMockData($studentId); } } private function buildKnowledgeGraphData() { $nodes = []; $links = []; $masteries = $this->masteryData['masteries'] ?? []; // 处理掌握度数据,构建节点 foreach ($masteries as $mastery) { if (!isset($mastery['mastery_level'])) { continue; } $masteryLevel = (float) $mastery['mastery_level']; $kpCode = $mastery['kp_code'] ?? null; if (!$kpCode) { continue; } $nodes[] = [ 'id' => $kpCode, 'label' => $mastery['kp_name'] ?? $kpCode, 'mastery' => $masteryLevel, 'color' => $this->getMasteryColor($masteryLevel), 'size' => $this->getMasterySize($masteryLevel), ]; } // 处理依赖关系,构建边 if (isset($this->dependencies['dependencies'])) { foreach ($this->dependencies['dependencies'] as $dep) { $links[] = [ 'source' => $dep['prerequisite_kp'], 'target' => $dep['dependent_kp'], 'strength' => $dep['influence_weight'], 'type' => $dep['dependency_type'], ]; } } $this->knowledgePoints = [ 'nodes' => $nodes, 'links' => $links, ]; $this->dispatchGraphUpdated(); } private function loadMockData($studentId) { // 模拟数据,用于演示 $mockKnowledgePoints = [ 'R01' => ['name' => '有理数', 'mastery' => 0.85], 'R02' => ['name' => '整式运算', 'mastery' => 0.72], 'R03' => ['name' => '一元一次方程', 'mastery' => 0.65], 'R04' => ['name' => '因式分解', 'mastery' => 0.45], 'R05' => ['name' => '二次方程', 'mastery' => 0.30], 'R06' => ['name' => '二次函数', 'mastery' => 0.25], 'R07' => ['name' => '几何图形', 'mastery' => 0.78], 'R08' => ['name' => '三角形', 'mastery' => 0.68], ]; $nodes = []; foreach ($mockKnowledgePoints as $code => $data) { $nodes[] = [ 'id' => $code, 'label' => $data['name'], 'mastery' => $data['mastery'], 'color' => $this->getMasteryColor($data['mastery']), 'size' => $this->getMasterySize($data['mastery']), ]; } $links = [ ['source' => 'R01', 'target' => 'R02', 'strength' => 0.9, 'type' => 'must'], ['source' => 'R02', 'target' => 'R03', 'strength' => 0.8, 'type' => 'must'], ['source' => 'R02', 'target' => 'R04', 'strength' => 0.7, 'type' => 'should'], ['source' => 'R03', 'target' => 'R05', 'strength' => 0.9, 'type' => 'must'], ['source' => 'R04', 'target' => 'R05', 'strength' => 0.8, 'type' => 'should'], ['source' => 'R05', 'target' => 'R06', 'strength' => 0.9, 'type' => 'must'], ['source' => 'R07', 'target' => 'R08', 'strength' => 0.8, 'type' => 'should'], ]; $this->knowledgePoints = [ 'nodes' => $nodes, 'links' => $links, ]; $this->setMasteryData([ 'masteries' => array_map(function ($code, $data) use ($studentId) { return [ 'student_id' => $studentId, 'kp_code' => $code, 'mastery_level' => $data['mastery'], 'confidence_level' => 0.8, ]; }, array_keys($mockKnowledgePoints), $mockKnowledgePoints), ]); $this->statistics = [ 'total_knowledge_points' => count($mockKnowledgePoints), 'average_mastery' => array_sum(array_column($mockKnowledgePoints, 'mastery')) / count($mockKnowledgePoints), 'high_mastery_count' => count(array_filter($mockKnowledgePoints, fn($d) => $d['mastery'] >= 0.7)), 'medium_mastery_count' => count(array_filter($mockKnowledgePoints, fn($d) => $d['mastery'] >= 0.4 && $d['mastery'] < 0.7)), 'low_mastery_count' => count(array_filter($mockKnowledgePoints, fn($d) => $d['mastery'] < 0.4)), ]; $this->dispatchGraphUpdated(); } private function getMasteryColor($mastery) { if ($mastery >= 0.8) return '#10b981'; // 绿色 - 优秀 if ($mastery >= 0.6) return '#3b82f6'; // 蓝色 - 良好 if ($mastery >= 0.4) return '#f59e0b'; // 黄色 - 中等 if ($mastery >= 0.2) return '#f97316'; // 橙色 - 待提高 return '#ef4444'; // 红色 - 薄弱 } private function getMasterySize($mastery) { return max(10, $mastery * 40); // 最小10px,最大40px } private function resetData() { $this->selectedStudent = null; $this->knowledgePoints = []; $this->dependencies = []; $this->masteryData = []; $this->statistics = []; $this->learningPath = []; $this->dispatchGraphUpdated(); } private function dispatchGraphUpdated(): void { // 通知前端重新渲染图谱(更新节点颜色/大小等) $this->dispatch('knowledgeGraphUpdated', $this->knowledgePoints); } private function setMasteryData(array $payload): void { $masteries = $this->normalizeMasteries($payload); $this->masteryData = array_merge($payload, [ 'masteries' => $masteries, 'mastery_map' => $this->buildMasteryMap($masteries), ]); } private function normalizeMasteries(array $raw): array { if (isset($raw['masteries']) && is_array($raw['masteries'])) { return array_values($raw['masteries']); } $candidates = []; if (isset($raw['data']) && is_array($raw['data'])) { $candidates[] = $raw['data']; } if (isset($raw['Target']) && is_array($raw['Target'])) { $candidates[] = $raw['Target']; } if ($this->isAssociativeArray($raw) && $this->looksLikeMasteryRecord(reset($raw))) { $candidates[] = $raw; } foreach ($candidates as $candidate) { $normalized = $this->normalizeCandidateMasteries($candidate); if (!empty($normalized)) { return $normalized; } } return []; } private function normalizeCandidateMasteries(array $candidate): array { $normalized = []; foreach ($candidate as $key => $entry) { if (!is_array($entry) || !isset($entry['mastery_level'])) { continue; } if (!isset($entry['kp_code']) && is_string($key)) { $entry['kp_code'] = $key; } if (isset($entry['kp_code'])) { $normalized[] = $entry; } } return $normalized; } private function isAssociativeArray(array $array): bool { return array_keys($array) !== range(0, count($array) - 1); } private function looksLikeMasteryRecord($entry): bool { return is_array($entry) && array_key_exists('mastery_level', $entry); } private function buildMasteryMap(array $masteries): array { $map = []; foreach ($masteries as $mastery) { if (!isset($mastery['kp_code'])) { continue; } $map[$mastery['kp_code']] = $mastery; } return $map; } public function render() { return view('livewire.student-knowledge-graph'); } }