MasteryHeatmap.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. <?php
  2. namespace App\Livewire;
  3. use App\Services\LearningAnalyticsService;
  4. use Livewire\Component;
  5. class MasteryHeatmap extends Component
  6. {
  7. public string $studentId = '';
  8. public array $heatmapData = [];
  9. public bool $isLoading = false;
  10. public string $errorMessage = '';
  11. // 热力图配置
  12. public array $config = [
  13. 'cellSize' => 60,
  14. 'itemWidth' => 100,
  15. 'itemHeight' => 80,
  16. 'colorStops' => [
  17. ['offset' => 0, 'color' => '#ef4444', 'name' => '薄弱'],
  18. ['offset' => 0.5, 'color' => '#f59e0b', 'name' => '一般'],
  19. ['offset' => 0.7, 'color' => '#10b981', 'name' => '良好'],
  20. ['offset' => 1, 'color' => '#3b82f6', 'name' => '掌握'],
  21. ]
  22. ];
  23. public function mount(string $studentId): void
  24. {
  25. $this->studentId = $studentId;
  26. $this->loadHeatmapData();
  27. }
  28. public function loadHeatmapData(): void
  29. {
  30. $this->isLoading = true;
  31. $this->errorMessage = '';
  32. try {
  33. $service = new LearningAnalyticsService();
  34. $masteryList = $service->getStudentMasteryList($this->studentId);
  35. if ($masteryList && isset($masteryList['data'])) {
  36. $this->heatmapData = $this->processHeatmapData($masteryList['data']);
  37. } else {
  38. $this->heatmapData = [];
  39. }
  40. } catch (\Exception $e) {
  41. $this->errorMessage = '加载热力图数据失败:' . $e->getMessage();
  42. $this->heatmapData = [];
  43. } finally {
  44. $this->isLoading = false;
  45. }
  46. }
  47. /**
  48. * 处理热力图数据
  49. */
  50. private function processHeatmapData(array $masteryList): array
  51. {
  52. $processedData = [];
  53. $categories = []; // 知识分类
  54. foreach ($masteryList as $mastery) {
  55. $kpCode = $mastery['kp_code'];
  56. $masteryLevel = $mastery['mastery_level'];
  57. $trend = $mastery['mastery_trend'];
  58. // 根据知识点编码提取分类
  59. $category = $this->extractCategory($kpCode);
  60. if (!in_array($category, $categories)) {
  61. $categories[] = $category;
  62. }
  63. // 确定颜色
  64. $color = $this->getMasteryColor($masteryLevel);
  65. $borderColor = $this->getTrendColor($trend);
  66. $processedData[] = [
  67. 'kp_code' => $kpCode,
  68. 'category' => $category,
  69. 'mastery_level' => $masteryLevel,
  70. 'accuracy_rate' => $mastery['accuracy_rate'],
  71. 'total_attempts' => $mastery['total_attempts'],
  72. 'trend' => $trend,
  73. 'color' => $color,
  74. 'border_color' => $borderColor,
  75. 'text_color' => $masteryLevel > 0.5 ? '#ffffff' : '#000000',
  76. ];
  77. }
  78. // 按分类排序
  79. usort($categories, fn($a, $b) => $a <=> $b);
  80. return [
  81. 'data' => $processedData,
  82. 'categories' => $categories,
  83. ];
  84. }
  85. /**
  86. * 根据知识点编码提取分类
  87. */
  88. private function extractCategory(string $kpCode): string
  89. {
  90. // 简单的分类逻辑,实际应根据知识点体系进行分类
  91. if (strpos($kpCode, 'KP') === 0) {
  92. $number = (int) substr($kpCode, 2);
  93. if ($number >= 100 && $number < 200) {
  94. return '基础概念';
  95. } elseif ($number >= 200 && $number < 300) {
  96. return '基础技能';
  97. } elseif ($number >= 300 && $number < 400) {
  98. return '综合应用';
  99. } elseif ($number >= 400 && $number < 500) {
  100. return '高级技巧';
  101. } else {
  102. return '其他';
  103. }
  104. }
  105. return '未分类';
  106. }
  107. /**
  108. * 根据掌握度获取颜色
  109. */
  110. private function getMasteryColor(float $masteryLevel): string
  111. {
  112. if ($masteryLevel < 0.3) {
  113. return '#ef4444'; // 红色 - 薄弱
  114. } elseif ($masteryLevel < 0.5) {
  115. return '#f97316'; // 橙色 - 需要改进
  116. } elseif ($masteryLevel < 0.7) {
  117. return '#eab308'; // 黄色 - 一般
  118. } elseif ($masteryLevel < 0.85) {
  119. return '#22c55e'; // 绿色 - 良好
  120. } else {
  121. return '#3b82f6'; // 蓝色 - 掌握
  122. }
  123. }
  124. /**
  125. * 根据趋势获取边框颜色
  126. */
  127. private function getTrendColor(string $trend): string
  128. {
  129. return match ($trend) {
  130. 'improving' => '#10b981', // 绿色上升箭头
  131. 'stable' => '#6b7280', // 灰色横线
  132. 'declining' => '#ef4444', // 红色下降箭头
  133. 'insufficient' => '#d1d5db', // 灰色虚线
  134. default => '#9ca3af',
  135. };
  136. }
  137. /**
  138. * 获取掌握度等级名称
  139. */
  140. public function getMasteryLevelName(float $masteryLevel): string
  141. {
  142. if ($masteryLevel < 0.3) {
  143. return '薄弱';
  144. } elseif ($masteryLevel < 0.5) {
  145. return '入门';
  146. } elseif ($masteryLevel < 0.7) {
  147. return '进阶';
  148. } elseif ($masteryLevel < 0.85) {
  149. return '熟练';
  150. } else {
  151. return '精通';
  152. }
  153. }
  154. /**
  155. * 获取趋势图标
  156. */
  157. public function getTrendIcon(string $trend): string
  158. {
  159. return match ($trend) {
  160. 'improving' => '↗',
  161. 'stable' => '→',
  162. 'declining' => '↘',
  163. 'insufficient' => '⋯',
  164. default => '?',
  165. };
  166. }
  167. public function render()
  168. {
  169. return view('livewire.mastery-heatmap');
  170. }
  171. }