StudentAnalysis.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. <?php
  2. namespace App\Filament\Pages;
  3. use App\Services\KnowledgeGraphService;
  4. use App\Services\KnowledgeMasteryService;
  5. use App\Services\MasteryCalculator;
  6. use BackedEnum;
  7. use Filament\Pages\Page;
  8. use UnitEnum;
  9. class StudentAnalysis extends Page
  10. {
  11. protected static ?string $title = '学生掌握度分析';
  12. protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-chart-bar';
  13. protected static ?string $navigationLabel = '学生分析';
  14. protected static string|UnitEnum|null $navigationGroup = '其他';
  15. protected static ?int $navigationSort = 24;
  16. protected string $view = 'filament.pages.student-analysis-simple';
  17. /**
  18. * 禁用导航显示(与"学生仪表板"功能重复)
  19. */
  20. public static function shouldRegisterNavigation(): bool
  21. {
  22. return false;
  23. }
  24. // 当前选中的学生
  25. public ?string $selectedStudentId = null;
  26. public array $studentInfo = [];
  27. public array $masteryData = [];
  28. public array $weaknesses = [];
  29. public array $skills = [];
  30. public array $learningPath = [];
  31. public array $knowledgePointHierarchy = []; // 【新增】父子知识点层级关系
  32. /**
  33. * 获取所有学生列表
  34. */
  35. public function students(): array
  36. {
  37. return \App\Models\Student::all()->toArray();
  38. }
  39. public function updatedSelectedStudentId($value)
  40. {
  41. if ($value) {
  42. $this->loadAnalysisData();
  43. } else {
  44. $this->reset(['studentInfo', 'masteryData', 'weaknesses', 'skills', 'learningPath']);
  45. }
  46. }
  47. public function loadAnalysisData()
  48. {
  49. if (!$this->selectedStudentId) {
  50. return;
  51. }
  52. // 使用本地的KnowledgeMasteryService
  53. $masteryService = app(KnowledgeMasteryService::class);
  54. $masteryCalculator = app(MasteryCalculator::class);
  55. // 1. 获取学生掌握度数据
  56. $stats = $masteryService->getStats($this->selectedStudentId);
  57. $this->studentInfo = $stats['success'] ? $stats['data'] : [];
  58. // 2. 获取掌握度概览
  59. $overview = $masteryCalculator->getStudentMasteryOverview($this->selectedStudentId);
  60. // 3. 获取薄弱点列表(掌握度 < 0.5)
  61. $this->weaknesses = array_filter($overview['details'] ?? [], function($item) {
  62. return floatval($item->mastery_level ?? 0) < 0.5;
  63. });
  64. // 4. 获取技能熟练度(从掌握度数据转换)
  65. $this->skills = $this->getSkillsData($overview);
  66. // 5. 获取学习路径建议(基于薄弱点)
  67. $this->learningPath = $this->generateLearningPath($this->weaknesses);
  68. // 6. 获取知识点掌握度详情
  69. $this->masteryData = $overview['details'] ?? [];
  70. // 7. 【新增】获取父子知识点层级关系
  71. $this->knowledgePointHierarchy = $this->getKnowledgePointHierarchy($this->selectedStudentId);
  72. }
  73. /**
  74. * 从掌握度数据转换技能数据
  75. */
  76. private function getSkillsData(array $overview): array
  77. {
  78. $skills = [];
  79. foreach ($overview['details'] ?? [] as $detail) {
  80. $skills[] = [
  81. 'skill_name' => $detail->kp_code,
  82. 'proficiency' => floatval($detail->mastery_level ?? 0),
  83. 'mastery_level' => floatval($detail->mastery_level ?? 0),
  84. ];
  85. }
  86. return $skills;
  87. }
  88. /**
  89. * 基于薄弱点生成学习路径
  90. */
  91. private function generateLearningPath(array $weaknesses): array
  92. {
  93. $path = [];
  94. foreach (array_slice($weaknesses, 0, 5) as $weakness) {
  95. $path[] = [
  96. 'kp_code' => $weakness->kp_code ?? '',
  97. 'recommendation' => '建议重点练习此知识点',
  98. 'priority' => 'high',
  99. 'estimated_hours' => 2,
  100. ];
  101. }
  102. return $path;
  103. }
  104. /**
  105. * 【新增】获取父子知识点层级关系
  106. */
  107. private function getKnowledgePointHierarchy(string $studentId): array
  108. {
  109. try {
  110. // 使用MasteryCalculator的getStudentMasteryOverviewWithHierarchy方法
  111. $masteryCalculator = app(MasteryCalculator::class);
  112. $hierarchyData = $masteryCalculator->getStudentMasteryOverviewWithHierarchy($studentId);
  113. // 构建父子关系数据
  114. $hierarchy = [
  115. 'parent_mastery_levels' => $hierarchyData['parent_mastery_levels'] ?? [],
  116. 'child_knowledge_points' => [], // 每个父节点下的子知识点
  117. ];
  118. // 获取所有知识点及其父节点信息
  119. $allKnowledgePoints = \DB::connection('mysql')
  120. ->table('knowledge_points as kp')
  121. ->leftJoin('student_knowledge_mastery as skm', function($join) use ($studentId) {
  122. $join->on('kp.kp_code', '=', 'skm.kp_code')
  123. ->where('skm.student_id', '=', $studentId);
  124. })
  125. ->select([
  126. 'kp.kp_code',
  127. 'kp.cn_name as kp_name',
  128. 'kp.parent_kp_code',
  129. 'kp.level',
  130. 'skm.mastery_level',
  131. 'skm.confidence_level',
  132. 'skm.total_attempts',
  133. 'skm.correct_attempts',
  134. ])
  135. ->get();
  136. // 按父节点分组
  137. foreach ($allKnowledgePoints as $kp) {
  138. $parentCode = $kp->parent_kp_code ?: 'ROOT'; // 根节点标记
  139. if (!isset($hierarchy['child_knowledge_points'][$parentCode])) {
  140. $hierarchy['child_knowledge_points'][$parentCode] = [];
  141. }
  142. $hierarchy['child_knowledge_points'][$parentCode][] = [
  143. 'kp_code' => $kp->kp_code,
  144. 'kp_name' => $kp->kp_name ?? $kp->kp_code,
  145. 'level' => $kp->level ?? 1,
  146. 'mastery_level' => floatval($kp->mastery_level ?? 0),
  147. 'confidence_level' => floatval($kp->confidence_level ?? 0),
  148. 'total_attempts' => intval($kp->total_attempts ?? 0),
  149. 'correct_attempts' => intval($kp->correct_attempts ?? 0),
  150. ];
  151. }
  152. return $hierarchy;
  153. } catch (\Exception $e) {
  154. \Illuminate\Support\Facades\Log::error('获取知识点层级关系失败', [
  155. 'student_id' => $studentId,
  156. 'error' => $e->getMessage()
  157. ]);
  158. return [
  159. 'parent_mastery_levels' => [],
  160. 'child_knowledge_points' => []
  161. ];
  162. }
  163. }
  164. private function getMasteryDetails(string $studentId): array
  165. {
  166. try {
  167. // 从MySQL直接查询学生掌握度详情
  168. $db = app('db');
  169. $db->connection('remote_mysql');
  170. $masteryRecords = \Illuminate\Support\Facades\DB::connection('remote_mysql')
  171. ->table('student_mastery as sm')
  172. ->join('knowledge_points as kp', 'sm.kp', '=', 'kp.kp')
  173. ->where('sm.student_id', $studentId)
  174. ->select([
  175. 'sm.kp as kp_code',
  176. 'kp.cn_name as kp_name',
  177. 'sm.mastery',
  178. 'sm.stability',
  179. 'sm.update_time'
  180. ])
  181. ->orderBy('sm.mastery', 'asc')
  182. ->limit(50)
  183. ->get()
  184. ->toArray();
  185. return array_map(function ($record) {
  186. return [
  187. 'kp_code' => $record->kp_code,
  188. 'kp_name' => $record->kp_name,
  189. 'mastery' => (float) $record->mastery,
  190. 'stability' => (float) $record->stability,
  191. 'update_time' => $record->update_time,
  192. 'mastery_level' => $this->getMasteryLevel((float) $record->mastery),
  193. 'weakness_score' => 1.0 - (float) $record->mastery
  194. ];
  195. }, $masteryRecords);
  196. } catch (\Exception $e) {
  197. \Illuminate\Support\Facades\Log::error('获取掌握度详情失败', [
  198. 'student_id' => $studentId,
  199. 'error' => $e->getMessage()
  200. ]);
  201. return [];
  202. }
  203. }
  204. private function getMasteryLevel(float $mastery): string
  205. {
  206. if ($mastery >= 0.9) return '优秀';
  207. if ($mastery >= 0.8) return '良好';
  208. if ($mastery >= 0.7) return '中等';
  209. if ($mastery >= 0.6) return '及格';
  210. return '需提升';
  211. }
  212. public function getMasteryColor(float $mastery): string
  213. {
  214. if ($mastery >= 0.9) return '#10b981'; // emerald-500
  215. if ($mastery >= 0.8) return '#34d399'; // emerald-400
  216. if ($mastery >= 0.7) return '#fbbf24'; // amber-400
  217. if ($mastery >= 0.6) return '#fb923c'; // orange-400
  218. return '#ef4444'; // red-500
  219. }
  220. public function getMasteryBgColor(float $mastery): string
  221. {
  222. if ($mastery >= 0.9) return 'bg-emerald-100';
  223. if ($mastery >= 0.8) return 'bg-emerald-50';
  224. if ($mastery >= 0.7) return 'bg-amber-100';
  225. if ($mastery >= 0.6) return 'bg-orange-100';
  226. return 'bg-red-100';
  227. }
  228. public function generateStudyPlan()
  229. {
  230. if (!$this->selectedStudentId || empty($this->weaknesses)) {
  231. return;
  232. }
  233. // TODO: 调用LearningAnalytics服务生成学习计划
  234. // 这里可以调用专门的API来生成个性化学习计划
  235. return redirect()->route('filament.admin.auth.learning-plan', [
  236. 'student_id' => $this->selectedStudentId
  237. ]);
  238. }
  239. public function exportAnalysis()
  240. {
  241. if (!$this->selectedStudentId) {
  242. return;
  243. }
  244. // TODO: 导出分析报告为PDF或Excel
  245. // 可以调用专门的导出服务
  246. }
  247. }