KnowledgePointDetail.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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.pages.knowledge-points', [
  77. 'phase' => $this->phaseFilter,
  78. 'selected' => $this->kpCode
  79. ]))
  80. ->color('gray'),
  81. ];
  82. }
  83. public function getKnowledgeStatsProperty(): array
  84. {
  85. $skills = $this->skills;
  86. $parents = collect($this->parentNodes);
  87. $children = collect($this->childNodes);
  88. return [
  89. 'skills_count' => $skills->count(),
  90. 'parents_count' => $parents->count(),
  91. 'children_count' => $children->count(),
  92. 'importance' => $this->knowledgePoint['importance'] ?? 0,
  93. 'category' => $this->knowledgePoint['category'] ?? '未分类',
  94. 'phase' => $this->knowledgePoint['phase'] ?? '未知学段',
  95. ];
  96. }
  97. protected function buildGraphData(?array $point): array
  98. {
  99. if (empty($point)) {
  100. return [
  101. 'nodes' => [],
  102. 'edges' => [],
  103. ];
  104. }
  105. $centerId = $point['kp_code'] ?? uniqid('kp_', true);
  106. $nodeMap = [
  107. $centerId => $this->formatGraphNode($point, 'current', 0),
  108. ];
  109. $edges = [];
  110. $nodeBuckets = [
  111. 'prerequisite' => $this->extractNodeList($point, [
  112. 'prerequisite_kps',
  113. 'parent_nodes',
  114. 'parent_details',
  115. 'parents',
  116. ]),
  117. 'post' => $this->extractNodeList($point, [
  118. 'post_kps',
  119. 'child_nodes',
  120. 'children',
  121. ]),
  122. 'related' => $this->extractNodeList($point, [
  123. 'related_kps',
  124. ]),
  125. ];
  126. foreach ($nodeBuckets as $type => $nodes) {
  127. foreach ($nodes as $index => $item) {
  128. $formatted = $this->formatGraphNode($item, $type, $index + 1);
  129. if (! $formatted) {
  130. continue;
  131. }
  132. $nodeMap[$formatted['id']] = $formatted;
  133. $edges[] = [
  134. 'source' => $type === 'prerequisite' ? $formatted['id'] : $centerId,
  135. 'target' => $type === 'prerequisite' ? $centerId : $formatted['id'],
  136. 'type' => $type,
  137. ];
  138. }
  139. }
  140. return [
  141. 'nodes' => array_values($nodeMap),
  142. 'edges' => $edges,
  143. ];
  144. }
  145. /**
  146. * @param array<string, mixed>|string $item
  147. */
  148. protected function formatGraphNode(array|string $item, string $type, int $index): ?array
  149. {
  150. if (is_array($item)) {
  151. $id = $item['kp_code'] ?? $item['id'] ?? $item['code'] ?? "{$type}-{$index}";
  152. $label = $item['cn_name'] ?? $item['label'] ?? $item['name'] ?? $id;
  153. } elseif (is_string($item)) {
  154. $id = $item;
  155. $label = $item;
  156. $item = [
  157. 'kp_code' => $id,
  158. 'cn_name' => $label,
  159. ];
  160. } else {
  161. return null;
  162. }
  163. return [
  164. 'id' => $id,
  165. 'label' => $label,
  166. 'type' => $type,
  167. 'distance' => $type === 'current' ? 0 : 1,
  168. 'meta' => $item,
  169. ];
  170. }
  171. protected function extractNodeList(array $point, array $keys): array
  172. {
  173. foreach ($keys as $key) {
  174. if (!empty($point[$key]) && is_array($point[$key])) {
  175. return $point[$key];
  176. }
  177. }
  178. return [];
  179. }
  180. }