KnowledgePointDetail.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <?php
  2. namespace App\Filament\Pages;
  3. use App\Services\KnowledgeServiceApi;
  4. use BackedEnum;
  5. use Filament\Actions\Action;
  6. use Filament\Pages\Page;
  7. use Illuminate\Http\Request;
  8. use Illuminate\Support\Collection;
  9. use UnitEnum;
  10. class KnowledgePointDetail extends Page
  11. {
  12. protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-document-text';
  13. protected static ?int $navigationSort = 2;
  14. // 不在导航菜单中显示这个页面
  15. protected static bool $shouldRegisterNavigation = false;
  16. protected string $view = 'filament.pages.knowledge-point-detail';
  17. public ?string $kpCode = null;
  18. public ?string $phaseFilter = null;
  19. public ?array $knowledgePoint = null;
  20. public array $graphData = [
  21. 'nodes' => [],
  22. 'edges' => [],
  23. ];
  24. protected ?KnowledgeServiceApi $knowledgeService = null;
  25. protected function getKnowledgeService(): KnowledgeServiceApi
  26. {
  27. if (!$this->knowledgeService) {
  28. $this->knowledgeService = app(KnowledgeServiceApi::class);
  29. }
  30. return $this->knowledgeService;
  31. }
  32. public function mount(Request $request): void
  33. {
  34. $this->kpCode = $request->query('kp_code');
  35. $this->phaseFilter = $request->query('phase');
  36. if (!$this->kpCode) {
  37. abort(404, '知识点代码不能为空');
  38. }
  39. // 加载知识点详细信息
  40. $this->knowledgePoint = $this->getKnowledgeService()->getFullGraphData($this->kpCode);
  41. if (!$this->knowledgePoint) {
  42. abort(404, '知识点不存在');
  43. }
  44. $this->graphData = $this->buildGraphData($this->knowledgePoint);
  45. }
  46. public function getTitle(): string
  47. {
  48. return $this->knowledgePoint['cn_name'] ?? '知识点详情';
  49. }
  50. public function getBreadcrumb(): string
  51. {
  52. return '知识点详情';
  53. }
  54. public function getKnowledgePointProperty(): ?array
  55. {
  56. return $this->knowledgePoint;
  57. }
  58. public function getSkillsProperty(): Collection
  59. {
  60. return collect($this->knowledgePoint['skills'] ?? []);
  61. }
  62. public function getParentNodesProperty(): array
  63. {
  64. return $this->knowledgePoint['parent_details'] ?? [];
  65. }
  66. public function getChildNodesProperty(): array
  67. {
  68. return $this->knowledgePoint['child_details'] ?? [];
  69. }
  70. protected function getHeaderActions(): array
  71. {
  72. return [
  73. Action::make('back_to_list')
  74. ->label('返回列表')
  75. ->icon('heroicon-o-arrow-left')
  76. ->url(route('filament.admin.resources.knowledge-points.index'))
  77. ->color('gray'),
  78. ];
  79. }
  80. public function getKnowledgeStatsProperty(): array
  81. {
  82. $skills = $this->skills;
  83. $parents = collect($this->parentNodes);
  84. $children = collect($this->childNodes);
  85. return [
  86. 'skills_count' => $skills->count(),
  87. 'parents_count' => $parents->count(),
  88. 'children_count' => $children->count(),
  89. 'importance' => $this->knowledgePoint['importance'] ?? 0,
  90. 'category' => $this->knowledgePoint['category'] ?? '未分类',
  91. 'phase' => $this->knowledgePoint['phase'] ?? '未知学段',
  92. ];
  93. }
  94. protected function buildGraphData(?array $point): array
  95. {
  96. if (empty($point)) {
  97. return [
  98. 'nodes' => [],
  99. 'edges' => [],
  100. ];
  101. }
  102. $centerId = $point['kp_code'] ?? uniqid('kp_', true);
  103. $nodeMap = [
  104. $centerId => $this->formatGraphNode($point, 'current', 0),
  105. ];
  106. $edges = [];
  107. $nodeBuckets = [
  108. 'prerequisite' => $this->extractNodeList($point, [
  109. 'prerequisite_kps',
  110. 'child_nodes',
  111. 'children',
  112. ]),
  113. 'post' => $this->extractNodeList($point, [
  114. 'post_kps',
  115. 'parent_nodes'
  116. ]),
  117. 'related' => $this->extractNodeList($point, [
  118. 'related_kps',
  119. ]),
  120. ];
  121. foreach ($nodeBuckets as $type => $nodes) {
  122. foreach ($nodes as $index => $item) {
  123. $formatted = $this->formatGraphNode($item, $type, $index + 1);
  124. if (! $formatted) {
  125. continue;
  126. }
  127. $nodeMap[$formatted['id']] = $formatted;
  128. $edges[] = [
  129. 'source' => $type === 'prerequisite' ? $formatted['id'] : $centerId,
  130. 'target' => $type === 'prerequisite' ? $centerId : $formatted['id'],
  131. 'type' => $type,
  132. ];
  133. }
  134. }
  135. return [
  136. 'nodes' => array_values($nodeMap),
  137. 'edges' => $edges,
  138. ];
  139. }
  140. /**
  141. * @param array<string, mixed>|string $item
  142. */
  143. protected function formatGraphNode(array|string $item, string $type, int $index): ?array
  144. {
  145. if (is_array($item)) {
  146. $id = $item['kp_code'] ?? $item['id'] ?? $item['code'] ?? "{$type}-{$index}";
  147. $label = $item['cn_name'] ?? $item['label'] ?? $item['name'] ?? $id;
  148. } elseif (is_string($item)) {
  149. $id = $item;
  150. $label = $item;
  151. $item = [
  152. 'kp_code' => $id,
  153. 'cn_name' => $label,
  154. ];
  155. } else {
  156. return null;
  157. }
  158. return [
  159. 'id' => $id,
  160. 'label' => $label,
  161. 'type' => $type,
  162. 'distance' => $type === 'current' ? 0 : 1,
  163. 'meta' => $item,
  164. ];
  165. }
  166. protected function extractNodeList(array $point, array $keys): array
  167. {
  168. foreach ($keys as $key) {
  169. if (!empty($point[$key]) && is_array($point[$key])) {
  170. return $point[$key];
  171. }
  172. }
  173. return [];
  174. }
  175. }