| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- <?php
- namespace App\Livewire;
- use Livewire\Component;
- use App\Models\Student;
- use Illuminate\Support\Facades\Http;
- use Illuminate\Support\Facades\DB;
- class StudentKnowledgeGraph extends Component
- {
- public $selectedStudentId = null;
- public $selectedStudent = null;
- public $knowledgePoints = [];
- public $dependencies = [];
- public $masteryData = [];
- public $statistics = [];
- public $learningPath = [];
- public $isLoading = false;
- public $students = [];
- public bool $showNodeDetails = true;
- public string $detailLayout = 'inline';
- protected $rules = [
- 'selectedStudentId' => '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');
- }
- }
|