first(); if (!$student) { return response()->json([ 'success' => false, 'message' => '学生不存在', ], 404); } // 获取查询参数 $level = $request->query('level'); // 筛选特定层级的知识点 $sort = $request->query('sort', 'mastery_asc'); // 排序方式 // 构建查询(解决字符集不匹配问题) // 【修复】显式选择mastery_level和mastery_change字段,避免访问器转换 $query = StudentKnowledgeMastery::forStudent($studentId) ->with('knowledgePoint') // 预加载知识点信息 ->select([ 'student_knowledge_mastery.*', 'knowledge_points.name as kp_name', 'knowledge_points.parent_kp_code', // 显式选择原始字段,避免访问器 DB::raw('CAST(student_knowledge_mastery.mastery_level AS DECIMAL(10,4)) as mastery_level_raw'), DB::raw('CAST(student_knowledge_mastery.mastery_change AS DECIMAL(10,4)) as mastery_change_raw'), ]) ->join('knowledge_points', 'student_knowledge_mastery.kp_code', '=', DB::raw('CAST(knowledge_points.kp_code AS CHAR)')); // 按层级筛选 if ($level) { if ($level === 'top') { // 只查询顶级知识点(没有父级) $query->whereNull('knowledge_points.parent_kp_code'); } elseif ($level === 'leaf') { // 只查询叶子知识点(没有子级) $query->whereNotIn('knowledge_points.kp_code', function ($q) { $q->select('parent_kp_code') ->from('knowledge_points') ->whereNotNull('parent_kp_code'); }); } } // 排序 switch ($sort) { case 'mastery_desc': $query->orderBy('mastery_level', 'desc'); break; case 'name_asc': $query->orderBy('kp_name', 'asc'); break; case 'name_desc': $query->orderBy('kp_name', 'desc'); break; case 'attempts_desc': $query->orderBy('total_attempts', 'desc'); break; case 'mastery_asc': default: $query->orderBy('mastery_level', 'asc'); break; } $masteryRecords = $query->get(); // 构建返回数据 $knowledgePoints = []; foreach ($masteryRecords as $record) { // 计算正确率 $correctRate = $record->total_attempts > 0 ? round(($record->correct_attempts / $record->total_attempts) * 100, 2) / 100 : 0.0; // 计算稳定度(基于近期的变化趋势) $stability = $this->calculateStability($record); // 获取层级信息 $level = $record->knowledgePoint->parent_kp_code ? '子级' : '顶级'; // 获取父子关系 $parentName = null; if ($record->knowledgePoint->parent_kp_code) { $parentKp = KnowledgePoint::where('kp_code', $record->knowledgePoint->parent_kp_code)->first(); $parentName = $parentKp?->name ?? $record->knowledgePoint->parent_kp_code; } // 【修复】使用CAST后的原始字段,避免访问器转换 $rawMasteryLevel = $record->mastery_level_raw; $rawMasteryChange = $record->mastery_change_raw; $masteryLevel = is_numeric($rawMasteryLevel) ? (float)$rawMasteryLevel : 0.0; $masteryChange = is_numeric($rawMasteryChange) ? (float)$rawMasteryChange : 0.0; $knowledgePoints[] = [ 'kp_code' => $record->kp_code, 'knowledge_point' => $record->kp_name ?: $record->kp_code, // 使用名称,如果没有名称则使用代码 'mastery' => round($masteryLevel, 4), 'stability' => $stability, 'level' => $level, 'parent_kp_code' => $record->knowledgePoint->parent_kp_code, 'parent_kp_name' => $parentName, 'last_updated' => $record->last_mastery_update?->toISOString(), 'practice_count' => (int)$record->total_attempts, 'correct_rate' => $correctRate, 'mastery_change' => $masteryChange, 'mastery_trend' => $record->mastery_trend, 'trend_label' => $record->trend_label ?? '稳定', ]; } // 【新功能】获取父节点掌握度 $parentMasteryLevels = $this->getParentMasteryLevels($studentId, $knowledgePoints); Log::info('获取学生知识点掌握详情', [ 'student_id' => $studentId, 'count' => count($knowledgePoints), 'parent_count' => count($parentMasteryLevels), 'level' => $level, 'sort' => $sort, ]); return response()->json([ 'success' => true, 'data' => [ 'student_id' => $studentId, 'student_name' => $student->name, 'knowledge_points' => array_values($knowledgePoints), // 重新索引 'parent_mastery_levels' => $parentMasteryLevels, // 新增:父节点掌握度 'total_count' => count($knowledgePoints), ], ]); } catch (\Exception $e) { Log::error('获取学生知识点掌握详情失败', [ 'student_id' => $studentId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); return response()->json([ 'success' => false, 'message' => '获取失败:' . $e->getMessage(), ], 500); } } /** * 计算稳定度 */ private function calculateStability(StudentKnowledgeMastery $record): float { // 如果数据不足,返回默认值 if ($record->total_attempts < 3) { return 0.5; } // 基于掌握度变化计算稳定度 // 变化越小,稳定度越高 $changeAbs = abs($record->mastery_change ?? 0); // 稳定度 = 1 - (变化幅度 * 10),范围0-1 $stability = max(0, min(1, 1 - ($changeAbs * 10))); return round($stability, 4); } /** * 获取学生知识点层级关系 * * @param string $studentId * @return JsonResponse */ public function getKnowledgeHierarchy(string $studentId): JsonResponse { try { // 获取学生的所有知识点 $masteryRecords = StudentKnowledgeMastery::forStudent($studentId) ->with('knowledgePoint') ->get(); // 构建父子关系 $hierarchy = []; $processed = []; foreach ($masteryRecords as $record) { $kpCode = $record->kp_code; // 避免重复处理 if (isset($processed[$kpCode])) { continue; } $kp = $record->knowledgePoint; if (!$kp) { continue; } $processed[$kpCode] = true; // 如果是顶级知识点 if (!$kp->parent_kp_code) { $hierarchy[] = [ 'kp_code' => $kpCode, 'kp_name' => $kp->name ?: $kpCode, 'mastery' => round($record->mastery_level, 4), 'level' => 'top', 'children' => $this->getChildKnowledgePoints($masteryRecords, $kpCode), ]; } } // 如果没有顶级知识点,尝试构建其他层级的结构 if (empty($hierarchy)) { foreach ($masteryRecords as $record) { $kpCode = $record->kp_code; $kp = $record->knowledgePoint; if (!$kp) { continue; } $hierarchy[] = [ 'kp_code' => $kpCode, 'kp_name' => $kp->name ?: $kpCode, 'mastery' => round($record->mastery_level, 4), 'level' => $kp->parent_kp_code ? 'child' : 'top', 'parent_kp_code' => $kp->parent_kp_code, ]; } } return response()->json([ 'success' => true, 'data' => [ 'student_id' => $studentId, 'hierarchy' => $hierarchy, 'total_count' => count($hierarchy), ], ]); } catch (\Exception $e) { Log::error('获取学生知识点层级关系失败', [ 'student_id' => $studentId, 'error' => $e->getMessage(), ]); return response()->json([ 'success' => false, 'message' => '获取失败:' . $e->getMessage(), ], 500); } } /** * 获取子知识点 */ private function getChildKnowledgePoints($masteryRecords, string $parentKpCode): array { $children = []; foreach ($masteryRecords as $record) { $kp = $record->knowledgePoint; if ($kp && $kp->parent_kp_code === $parentKpCode) { $children[] = [ 'kp_code' => $kp->kp_code, 'kp_name' => $kp->name ?: $kp->kp_code, 'mastery' => round($record->mastery_level, 4), 'level' => 'child', 'children' => $this->getChildKnowledgePoints($masteryRecords, $kp->kp_code), ]; } } return $children; } /** * 【新功能】获取父节点掌握度 */ private function getParentMasteryLevels(string $studentId, array $knowledgePoints): array { try { $parentMasteryLevels = []; // 收集所有父节点代码 $parentKpCodes = []; foreach ($knowledgePoints as $kp) { if (!empty($kp['parent_kp_code'])) { $parentKpCodes[$kp['parent_kp_code']] = true; } } if (empty($parentKpCodes)) { return $parentMasteryLevels; } // 查询父节点的掌握度 $parentRecords = StudentKnowledgeMastery::forStudent($studentId) ->whereIn('kp_code', array_keys($parentKpCodes)) ->get(); foreach ($parentRecords as $record) { // 使用CAST后的原始字段 $rawMasteryLevel = $record->getAttributeValue('mastery_level'); $rawMasteryChange = $record->getAttributeValue('mastery_change'); $masteryLevel = is_numeric($rawMasteryLevel) ? (float)$rawMasteryLevel : 0.0; $masteryChange = is_numeric($rawMasteryChange) ? (float)$rawMasteryChange : 0.0; // 获取父节点的父节点 $kp = $record->knowledgePoint; $grandParentKpCode = $kp?->parent_kp_code; $parentMasteryLevels[] = [ 'kp_code' => $record->kp_code, 'knowledge_point' => $kp?->name ?: $record->kp_code, 'mastery' => round($masteryLevel, 4), 'mastery_change' => $masteryChange, 'mastery_trend' => $record->mastery_trend, 'trend_label' => $record->trend_label ?? '稳定', 'level' => '父级', 'parent_kp_code' => $grandParentKpCode, 'practice_count' => (int)$record->total_attempts, 'correct_rate' => $record->total_attempts > 0 ? round(($record->correct_attempts / $record->total_attempts), 4) : 0.0, 'is_calculated' => ($record->mastery_trend === 'calculated'), // 标记为计算得出 'last_updated' => $record->last_mastery_update?->toISOString(), ]; } return $parentMasteryLevels; } catch (\Exception $e) { Log::error('获取父节点掌握度失败', [ 'student_id' => $studentId, 'error' => $e->getMessage(), ]); return []; } } }