KnowledgePoints.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <?php
  2. namespace App\Filament\Pages;
  3. use App\Services\KnowledgeServiceApi;
  4. use BackedEnum;
  5. use Filament\Pages\Page;
  6. use Filament\Tables\Actions\Action;
  7. use Filament\Tables\Columns\BadgeColumn;
  8. use Filament\Tables\Columns\TextColumn;
  9. use Filament\Tables\Concerns\InteractsWithTable;
  10. use Filament\Tables\Contracts\HasTable;
  11. use Filament\Tables\Filters\SelectFilter;
  12. use Filament\Tables\Table;
  13. use Illuminate\Support\Collection;
  14. use Illuminate\Support\Str;
  15. use UnitEnum;
  16. class KnowledgePoints extends Page implements HasTable
  17. {
  18. use InteractsWithTable;
  19. protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-map';
  20. protected static string|UnitEnum|null $navigationGroup = '知识图谱';
  21. protected static string|UnitEnum|null $navigationLabel = '知识点总览';
  22. protected static string $view = 'filament.pages.knowledge-points';
  23. protected KnowledgeServiceApi $knowledgeService;
  24. protected ?Collection $cachedPoints = null;
  25. public function mount(KnowledgeServiceApi $knowledgeService): void
  26. {
  27. $this->knowledgeService = $knowledgeService;
  28. }
  29. public function table(Table $table): Table
  30. {
  31. return $table
  32. ->columns([
  33. TextColumn::make('kp_code')
  34. ->label('编号')
  35. ->sortable()
  36. ->searchable(),
  37. TextColumn::make('cn_name')
  38. ->label('知识点')
  39. ->wrap()
  40. ->searchable(),
  41. BadgeColumn::make('phase')
  42. ->label('学段')
  43. ->colors([
  44. 'primary',
  45. 'success' => fn (?string $state): bool => $state === '高中',
  46. ]),
  47. TextColumn::make('grade')
  48. ->label('年级')
  49. ->sortable(),
  50. BadgeColumn::make('category')
  51. ->label('类别')
  52. ->color('info'),
  53. TextColumn::make('importance')
  54. ->label('重要度')
  55. ->sortable()
  56. ->formatStateUsing(fn ($state): string => number_format((float) $state, 1)),
  57. TextColumn::make('description')
  58. ->label('摘要')
  59. ->toggleable(isToggledHiddenByDefault: true)
  60. ->wrap()
  61. ->limit(80),
  62. ])
  63. ->filters([
  64. SelectFilter::make('phase')
  65. ->label('学段')
  66. ->options($this->getPhaseOptions()),
  67. SelectFilter::make('category')
  68. ->label('知识类别')
  69. ->options($this->getCategoryOptions()),
  70. ])
  71. ->records(fn (array $params) => $this->filterPoints($params))
  72. ->paginated(false)
  73. ->defaultSort('importance', 'desc')
  74. ->actions([
  75. Action::make('view')
  76. ->label('查看详情')
  77. ->icon('heroicon-m-eye')
  78. ->modalHeading(fn (array $record): string => "{$record['cn_name']}({$record['kp_code']})")
  79. ->modalContent(fn (array $record) => view('filament.pages.partials.kp-detail', [
  80. 'point' => $this->knowledgeService->getKnowledgePointDetail($record['kp_code']),
  81. ]))
  82. ->modalSubmitAction(false),
  83. ])
  84. ->deferLoading();
  85. }
  86. protected function filterPoints(array $params): Collection
  87. {
  88. $records = $this->getPoints();
  89. $getFilters = $params['filters'];
  90. if ($filters = $getFilters()) {
  91. if (! empty($filters['phase'])) {
  92. $records = $records->where('phase', $filters['phase']);
  93. }
  94. if (! empty($filters['category'])) {
  95. $records = $records->where('category', $filters['category']);
  96. }
  97. }
  98. $searchCallback = $params['search'];
  99. $search = Str::lower(trim((string) $searchCallback()));
  100. if ($search !== '') {
  101. $records = $records->filter(function (array $record) use ($search): bool {
  102. return Str::contains(Str::lower($record['cn_name'] ?? ''), $search)
  103. || Str::contains(Str::lower($record['kp_code'] ?? ''), $search)
  104. || Str::contains(Str::lower($record['description'] ?? ''), $search);
  105. });
  106. }
  107. $sortColumn = $params['sortColumn']();
  108. $sortDirection = $params['sortDirection']() ?? 'asc';
  109. if ($sortColumn) {
  110. $records = $records->sortBy(fn ($record) => $record[$sortColumn] ?? null, SORT_REGULAR, $sortDirection === 'desc');
  111. }
  112. return $records->values();
  113. }
  114. protected function getPoints(): Collection
  115. {
  116. if ($this->cachedPoints) {
  117. return $this->cachedPoints;
  118. }
  119. return $this->cachedPoints = $this->knowledgeService
  120. ->listKnowledgePoints()
  121. ->map(fn (array $point) => [
  122. 'kp_code' => $point['kp_code'] ?? null,
  123. 'cn_name' => $point['cn_name'] ?? '-',
  124. 'phase' => $point['phase'] ?? null,
  125. 'grade' => $point['grade'] ?? null,
  126. 'category' => $point['category'] ?? null,
  127. 'importance' => $point['importance'] ?? null,
  128. 'description' => $point['description'] ?? '',
  129. 'group_path' => $point['group_path'] ?? null,
  130. 'parents' => $point['parents'] ?? [],
  131. 'created_at' => $point['created_at'] ?? null,
  132. 'updated_at' => $point['updated_at'] ?? null,
  133. ]);
  134. }
  135. protected function getPhaseOptions(): array
  136. {
  137. return $this->getPoints()
  138. ->pluck('phase')
  139. ->filter()
  140. ->unique()
  141. ->sort()
  142. ->mapWithKeys(fn ($phase) => [$phase => $phase])
  143. ->all();
  144. }
  145. protected function getCategoryOptions(): array
  146. {
  147. return $this->getPoints()
  148. ->pluck('category')
  149. ->filter()
  150. ->unique()
  151. ->sort()
  152. ->mapWithKeys(fn ($category) => [$category => $category])
  153. ->all();
  154. }
  155. /**
  156. * @return array<int, array<string, string>>
  157. */
  158. public function getStatsProperty(): array
  159. {
  160. $points = $this->getPoints();
  161. $byPhase = $points->groupBy('phase')->map->count();
  162. $byCategory = $points->groupBy('category')->map->count();
  163. return [
  164. [
  165. 'label' => '知识点数量',
  166. 'value' => number_format($points->count()),
  167. ],
  168. [
  169. 'label' => '学段覆盖',
  170. 'value' => $byPhase->count() ? $byPhase->map(fn ($count, $phase) => "{$phase}:{$count}")->implode(' | ') : '未设置',
  171. ],
  172. [
  173. 'label' => '类别分布 Top3',
  174. 'value' => $byCategory->sortDesc()->take(3)->map(fn ($count, $category) => "{$category}:{$count}")->implode(' | ') ?: '未设置',
  175. ],
  176. [
  177. 'label' => '最近同步',
  178. 'value' => now()->toDateTimeString(),
  179. ],
  180. ];
  181. }
  182. }