[], 'edges' => [], ]; protected ?KnowledgeServiceApi $knowledgeService = null; protected function getKnowledgeService(): KnowledgeServiceApi { if (!$this->knowledgeService) { $this->knowledgeService = app(KnowledgeServiceApi::class); } return $this->knowledgeService; } public function mount(Request $request): void { $this->kpCode = $request->query('kp_code'); $this->phaseFilter = $request->query('phase'); if (!$this->kpCode) { abort(404, '知识点代码不能为空'); } // 加载知识点详细信息 $this->knowledgePoint = $this->getKnowledgeService()->getFullGraphData($this->kpCode); if (!$this->knowledgePoint) { abort(404, '知识点不存在'); } $this->graphData = $this->buildGraphData($this->knowledgePoint); } public function getTitle(): string { return $this->knowledgePoint['cn_name'] ?? '知识点详情'; } public function getBreadcrumb(): string { return '知识点详情'; } public function getKnowledgePointProperty(): ?array { return $this->knowledgePoint; } public function getSkillsProperty(): Collection { return collect($this->knowledgePoint['skills'] ?? []); } public function getParentNodesProperty(): array { return $this->knowledgePoint['parent_details'] ?? []; } public function getChildNodesProperty(): array { return $this->knowledgePoint['child_details'] ?? []; } protected function getHeaderActions(): array { return [ Action::make('back_to_list') ->label('返回列表') ->icon('heroicon-o-arrow-left') ->url(route('filament.admin.pages.knowledge-points', [ 'phase' => $this->phaseFilter, 'selected' => $this->kpCode ])) ->color('gray'), ]; } public function getKnowledgeStatsProperty(): array { $skills = $this->skills; $parents = collect($this->parentNodes); $children = collect($this->childNodes); return [ 'skills_count' => $skills->count(), 'parents_count' => $parents->count(), 'children_count' => $children->count(), 'importance' => $this->knowledgePoint['importance'] ?? 0, 'category' => $this->knowledgePoint['category'] ?? '未分类', 'phase' => $this->knowledgePoint['phase'] ?? '未知学段', ]; } protected function buildGraphData(?array $point): array { if (empty($point)) { return [ 'nodes' => [], 'edges' => [], ]; } $centerId = $point['kp_code'] ?? uniqid('kp_', true); $nodeMap = [ $centerId => $this->formatGraphNode($point, 'current', 0), ]; $edges = []; $nodeBuckets = [ 'prerequisite' => $this->extractNodeList($point, [ 'prerequisite_kps', 'parent_nodes', 'parent_details', 'parents', ]), 'post' => $this->extractNodeList($point, [ 'post_kps', 'child_nodes', 'children', ]), 'related' => $this->extractNodeList($point, [ 'related_kps', ]), ]; foreach ($nodeBuckets as $type => $nodes) { foreach ($nodes as $index => $item) { $formatted = $this->formatGraphNode($item, $type, $index + 1); if (! $formatted) { continue; } $nodeMap[$formatted['id']] = $formatted; $edges[] = [ 'source' => $type === 'prerequisite' ? $formatted['id'] : $centerId, 'target' => $type === 'prerequisite' ? $centerId : $formatted['id'], 'type' => $type, ]; } } return [ 'nodes' => array_values($nodeMap), 'edges' => $edges, ]; } /** * @param array|string $item */ protected function formatGraphNode(array|string $item, string $type, int $index): ?array { if (is_array($item)) { $id = $item['kp_code'] ?? $item['id'] ?? $item['code'] ?? "{$type}-{$index}"; $label = $item['cn_name'] ?? $item['label'] ?? $item['name'] ?? $id; } elseif (is_string($item)) { $id = $item; $label = $item; $item = [ 'kp_code' => $id, 'cn_name' => $label, ]; } else { return null; } return [ 'id' => $id, 'label' => $label, 'type' => $type, 'distance' => $type === 'current' ? 0 : 1, 'meta' => $item, ]; } protected function extractNodeList(array $point, array $keys): array { foreach ($keys as $key) { if (!empty($point[$key]) && is_array($point[$key])) { return $point[$key]; } } return []; } }