KnowledgePoints.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <?php
  2. namespace App\Filament\Pages;
  3. use App\Services\KnowledgeServiceApi;
  4. use BackedEnum;
  5. use Filament\Pages\Page;
  6. use Illuminate\Support\Collection;
  7. use Illuminate\Support\Str;
  8. use UnitEnum;
  9. class KnowledgePoints extends Page
  10. {
  11. protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-map';
  12. protected static string|UnitEnum|null $navigationGroup = '知识图谱';
  13. protected static ?string $navigationLabel = '知识点总览';
  14. protected string $view = 'filament.pages.knowledge-points';
  15. public ?string $phaseFilter = null;
  16. public ?string $categoryFilter = null;
  17. public ?string $search = null;
  18. public int $page = 1;
  19. public int $perPage = 8;
  20. protected ?array $selectedPoint = null;
  21. protected bool $loading = false;
  22. protected ?KnowledgeServiceApi $knowledgeService = null;
  23. public function mount(): void
  24. {
  25. $this->hydratePoints();
  26. }
  27. protected function getKnowledgeService(): KnowledgeServiceApi
  28. {
  29. if (!$this->knowledgeService) {
  30. $this->knowledgeService = app(KnowledgeServiceApi::class);
  31. }
  32. return $this->knowledgeService;
  33. }
  34. public function updatedPhaseFilter(): void
  35. {
  36. $this->page = 1;
  37. $this->hydratePoints();
  38. }
  39. public function updatedCategoryFilter(): void
  40. {
  41. $this->page = 1;
  42. $this->hydratePoints();
  43. }
  44. public function updatedSearch(): void
  45. {
  46. $this->page = 1;
  47. $this->hydratePoints();
  48. }
  49. public function changePage(int $page): void
  50. {
  51. $this->page = max($page, 1);
  52. $this->hydratePoints();
  53. }
  54. public function selectPoint(string $kpCode): void
  55. {
  56. $this->selectedPoint = $this->getKnowledgeService()->getKnowledgePointDetail($kpCode);
  57. }
  58. public function getStatsProperty(): array
  59. {
  60. $all = $this->getKnowledgeService()->listKnowledgePoints();
  61. $phases = $all->groupBy('phase')->map->count()->sortDesc()->map(fn ($count, $phase) => "{$phase}·{$count}");
  62. $categories = $all->groupBy('category')->map->count()->sortDesc()->take(3)->map(fn ($count, $category) => "{$category}·{$count}");
  63. return [
  64. ['label' => '知识点数量', 'value' => number_format($all->count()), 'hint' => '当前版本节点'],
  65. ['label' => '学段覆盖', 'value' => $phases->implode(' | ') ?: '未配置', 'hint' => 'phase 字段'],
  66. ['label' => 'Top 类别', 'value' => $categories->implode(' | ') ?: '未配置', 'hint' => 'category 字段'],
  67. ['label' => '平均重要度', 'value' => number_format($all->avg('importance') ?? 0, 1), 'hint' => 'importance'],
  68. ];
  69. }
  70. public function getPhaseOptionsProperty(): array
  71. {
  72. return $this->getKnowledgeService()
  73. ->listKnowledgePoints()
  74. ->pluck('phase')
  75. ->filter()
  76. ->unique()
  77. ->sort()
  78. ->mapWithKeys(fn ($phase) => [$phase => $phase])
  79. ->all();
  80. }
  81. public function getCategoryOptionsProperty(): array
  82. {
  83. return $this->getKnowledgeService()
  84. ->listKnowledgePoints()
  85. ->pluck('category')
  86. ->filter()
  87. ->unique()
  88. ->sort()
  89. ->mapWithKeys(fn ($category) => [$category => $category])
  90. ->all();
  91. }
  92. public function getPaginatedPointsProperty(): array
  93. {
  94. $filters = array_filter([
  95. 'phase' => $this->phaseFilter,
  96. 'category' => $this->categoryFilter,
  97. ]);
  98. $response = $this->getKnowledgeService()->paginateKnowledgePoints(
  99. page: $this->page,
  100. perPage: $this->perPage,
  101. filters: $filters
  102. );
  103. $records = collect($response['data'] ?? []);
  104. if (filled($this->search)) {
  105. $search = Str::lower($this->search);
  106. $records = $records->filter(function (array $record) use ($search): bool {
  107. return Str::contains(Str::lower($record['cn_name'] ?? ''), $search)
  108. || Str::contains(Str::lower($record['kp_code'] ?? ''), $search);
  109. });
  110. }
  111. $meta = $response['meta'] ?? [];
  112. return [
  113. 'data' => $records->values(),
  114. 'total' => $meta['total'] ?? $records->count(),
  115. 'page' => $meta['page'] ?? $this->page,
  116. 'total_pages' => $meta['total_pages'] ?? 1,
  117. 'has_prev' => $meta['has_prev'] ?? ($this->page > 1),
  118. 'has_next' => $meta['has_next'] ?? false,
  119. ];
  120. }
  121. public function getSelectedSkillsProperty(): Collection
  122. {
  123. return collect($this->selectedPoint['skills'] ?? []);
  124. }
  125. public function getSelectedPointProperty(): ?array
  126. {
  127. return $this->selectedPoint;
  128. }
  129. public function getRelatedNodesProperty(): array
  130. {
  131. $point = $this->selectedPoint;
  132. if (! $point) {
  133. return ['parents' => [], 'children' => []];
  134. }
  135. $parents = collect($point['parents'] ?? [])
  136. ->map(fn ($name, $index) => [
  137. 'id' => "parent-{$index}",
  138. 'label' => $name,
  139. 'relation' => 'parent',
  140. ])->values()->all();
  141. $children = collect($point['children'] ?? [])
  142. ->map(fn ($name, $index) => [
  143. 'id' => "child-{$index}",
  144. 'label' => $name,
  145. 'relation' => 'child',
  146. ])->values()->all();
  147. return ['parents' => $parents, 'children' => $children];
  148. }
  149. protected function hydratePoints(): void
  150. {
  151. $this->loading = true;
  152. $this->selectedPoint = null;
  153. $points = $this->paginatedPoints['data'];
  154. if ($points->isNotEmpty()) {
  155. $this->selectPoint($points->first()['kp_code']);
  156. }
  157. $this->loading = false;
  158. }
  159. }