StudentKnowledgeGraph.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. <?php
  2. namespace App\Livewire;
  3. use Livewire\Component;
  4. use App\Models\Student;
  5. use Illuminate\Support\Facades\Http;
  6. use Illuminate\Support\Facades\DB;
  7. class StudentKnowledgeGraph extends Component
  8. {
  9. public $selectedStudentId = null;
  10. public $selectedStudent = null;
  11. public $knowledgePoints = [];
  12. public $dependencies = [];
  13. public $masteryData = [];
  14. public $statistics = [];
  15. public $learningPath = [];
  16. public $isLoading = false;
  17. public $students = [];
  18. protected $rules = [
  19. 'selectedStudentId' => 'required|exists:students,student_id',
  20. ];
  21. public function mount()
  22. {
  23. $this->loadStudents();
  24. }
  25. public function updatedSelectedStudentId($value)
  26. {
  27. if ($value) {
  28. $this->loadStudentData($value);
  29. } else {
  30. $this->resetData();
  31. }
  32. }
  33. public function loadStudents()
  34. {
  35. $query = DB::table('students')
  36. ->select('student_id', 'name', 'grade', 'class_name')
  37. ->orderBy('grade')
  38. ->orderBy('class_name')
  39. ->orderBy('name');
  40. // 如果是老师,只加载自己的学生
  41. $currentUser = auth()->user();
  42. if ($currentUser?->isTeacher() ?? false) {
  43. $teacherId = $currentUser->teacher?->teacher_id;
  44. if ($teacherId) {
  45. $query->where('teacher_id', $teacherId);
  46. }
  47. }
  48. $this->students = $query
  49. ->get()
  50. ->map(function ($student) {
  51. return [
  52. 'id' => $student->student_id,
  53. 'label' => "{$student->name} ({$student->grade}-{$student->class_name})",
  54. ];
  55. })
  56. ->toArray();
  57. }
  58. public function loadStudentData($studentId)
  59. {
  60. $this->isLoading = true;
  61. try {
  62. // 获取学生信息
  63. $this->selectedStudent = DB::table('students')
  64. ->where('student_id', $studentId)
  65. ->first();
  66. // 调用LearningAnalytics API获取知识图谱数据
  67. $this->fetchKnowledgeGraphData($studentId);
  68. } catch (\Exception $e) {
  69. session()->flash('error', '加载数据失败:' . $e->getMessage());
  70. \Log::error('加载学生知识图谱失败', [
  71. 'student_id' => $studentId,
  72. 'error' => $e->getMessage(),
  73. ]);
  74. }
  75. $this->isLoading = false;
  76. }
  77. private function fetchKnowledgeGraphData($studentId)
  78. {
  79. $baseUrl = config('services.learning_analytics.url', 'http://localhost:5010');
  80. try {
  81. // 获取掌握度数据
  82. $masteryResponse = Http::timeout(10)->get($baseUrl . '/api/mastery/' . $studentId);
  83. if ($masteryResponse->successful()) {
  84. $this->masteryData = $masteryResponse->json();
  85. }
  86. // 获取依赖关系
  87. $dependencyResponse = Http::timeout(10)->get($baseUrl . '/api/knowledge/dependencies');
  88. if ($dependencyResponse->successful()) {
  89. $this->dependencies = $dependencyResponse->json();
  90. }
  91. // 获取统计信息
  92. $statsResponse = Http::timeout(10)->get($baseUrl . '/api/mastery/' . $studentId . '/statistics');
  93. if ($statsResponse->successful()) {
  94. $this->statistics = $statsResponse->json();
  95. }
  96. // 获取学习路径
  97. $pathResponse = Http::timeout(10)->get($baseUrl . '/api/learning-path/' . $studentId);
  98. if ($pathResponse->successful()) {
  99. $this->learningPath = $pathResponse->json();
  100. }
  101. // 构建知识点图谱数据
  102. $this->buildKnowledgeGraphData();
  103. } catch (\Exception $e) {
  104. \Log::warning('LearningAnalytics API调用失败,使用本地数据', [
  105. 'error' => $e->getMessage(),
  106. ]);
  107. // 如果API调用失败,使用本地模拟数据
  108. $this->loadMockData($studentId);
  109. }
  110. }
  111. private function buildKnowledgeGraphData()
  112. {
  113. $nodes = [];
  114. $links = [];
  115. // 处理掌握度数据,构建节点
  116. if (isset($this->masteryData['masteries'])) {
  117. foreach ($this->masteryData['masteries'] as $mastery) {
  118. $nodes[] = [
  119. 'id' => $mastery['kp_code'],
  120. 'label' => $mastery['kp_code'],
  121. 'mastery' => $mastery['mastery_level'],
  122. 'color' => $this->getMasteryColor($mastery['mastery_level']),
  123. 'size' => $this->getMasterySize($mastery['mastery_level']),
  124. ];
  125. }
  126. }
  127. // 处理依赖关系,构建边
  128. if (isset($this->dependencies['dependencies'])) {
  129. foreach ($this->dependencies['dependencies'] as $dep) {
  130. $links[] = [
  131. 'source' => $dep['prerequisite_kp'],
  132. 'target' => $dep['dependent_kp'],
  133. 'strength' => $dep['influence_weight'],
  134. 'type' => $dep['dependency_type'],
  135. ];
  136. }
  137. }
  138. $this->knowledgePoints = [
  139. 'nodes' => $nodes,
  140. 'links' => $links,
  141. ];
  142. }
  143. private function loadMockData($studentId)
  144. {
  145. // 模拟数据,用于演示
  146. $mockKnowledgePoints = [
  147. 'R01' => ['name' => '有理数', 'mastery' => 0.85],
  148. 'R02' => ['name' => '整式运算', 'mastery' => 0.72],
  149. 'R03' => ['name' => '一元一次方程', 'mastery' => 0.65],
  150. 'R04' => ['name' => '因式分解', 'mastery' => 0.45],
  151. 'R05' => ['name' => '二次方程', 'mastery' => 0.30],
  152. 'R06' => ['name' => '二次函数', 'mastery' => 0.25],
  153. 'R07' => ['name' => '几何图形', 'mastery' => 0.78],
  154. 'R08' => ['name' => '三角形', 'mastery' => 0.68],
  155. ];
  156. $nodes = [];
  157. foreach ($mockKnowledgePoints as $code => $data) {
  158. $nodes[] = [
  159. 'id' => $code,
  160. 'label' => $data['name'],
  161. 'mastery' => $data['mastery'],
  162. 'color' => $this->getMasteryColor($data['mastery']),
  163. 'size' => $this->getMasterySize($data['mastery']),
  164. ];
  165. }
  166. $links = [
  167. ['source' => 'R01', 'target' => 'R02', 'strength' => 0.9, 'type' => 'must'],
  168. ['source' => 'R02', 'target' => 'R03', 'strength' => 0.8, 'type' => 'must'],
  169. ['source' => 'R02', 'target' => 'R04', 'strength' => 0.7, 'type' => 'should'],
  170. ['source' => 'R03', 'target' => 'R05', 'strength' => 0.9, 'type' => 'must'],
  171. ['source' => 'R04', 'target' => 'R05', 'strength' => 0.8, 'type' => 'should'],
  172. ['source' => 'R05', 'target' => 'R06', 'strength' => 0.9, 'type' => 'must'],
  173. ['source' => 'R07', 'target' => 'R08', 'strength' => 0.8, 'type' => 'should'],
  174. ];
  175. $this->knowledgePoints = [
  176. 'nodes' => $nodes,
  177. 'links' => $links,
  178. ];
  179. $this->masteryData = [
  180. 'masteries' => array_map(function ($code, $data) use ($studentId) {
  181. return [
  182. 'student_id' => $studentId,
  183. 'kp_code' => $code,
  184. 'mastery_level' => $data['mastery'],
  185. 'confidence_level' => 0.8,
  186. ];
  187. }, array_keys($mockKnowledgePoints), $mockKnowledgePoints),
  188. ];
  189. $this->statistics = [
  190. 'total_knowledge_points' => count($mockKnowledgePoints),
  191. 'average_mastery' => array_sum(array_column($mockKnowledgePoints, 'mastery')) / count($mockKnowledgePoints),
  192. 'high_mastery_count' => count(array_filter($mockKnowledgePoints, fn($d) => $d['mastery'] >= 0.7)),
  193. 'medium_mastery_count' => count(array_filter($mockKnowledgePoints, fn($d) => $d['mastery'] >= 0.4 && $d['mastery'] < 0.7)),
  194. 'low_mastery_count' => count(array_filter($mockKnowledgePoints, fn($d) => $d['mastery'] < 0.4)),
  195. ];
  196. }
  197. private function getMasteryColor($mastery)
  198. {
  199. if ($mastery >= 0.8) return '#10b981'; // 绿色 - 优秀
  200. if ($mastery >= 0.6) return '#3b82f6'; // 蓝色 - 良好
  201. if ($mastery >= 0.4) return '#f59e0b'; // 黄色 - 中等
  202. if ($mastery >= 0.2) return '#f97316'; // 橙色 - 待提高
  203. return '#ef4444'; // 红色 - 薄弱
  204. }
  205. private function getMasterySize($mastery)
  206. {
  207. return max(10, $mastery * 40); // 最小10px,最大40px
  208. }
  209. private function resetData()
  210. {
  211. $this->selectedStudent = null;
  212. $this->knowledgePoints = [];
  213. $this->dependencies = [];
  214. $this->masteryData = [];
  215. $this->statistics = [];
  216. $this->learningPath = [];
  217. }
  218. public function render()
  219. {
  220. return view('livewire.student-knowledge-graph');
  221. }
  222. }