knowledgeService = $knowledgeService; } public function table(Table $table): Table { return $table ->columns([ TextColumn::make('kp_code') ->label('编号') ->sortable() ->searchable(), TextColumn::make('cn_name') ->label('知识点') ->wrap() ->searchable(), BadgeColumn::make('phase') ->label('学段') ->colors([ 'primary', 'success' => fn (?string $state): bool => $state === '高中', ]), TextColumn::make('grade') ->label('年级') ->sortable(), BadgeColumn::make('category') ->label('类别') ->color('info'), TextColumn::make('importance') ->label('重要度') ->sortable() ->formatStateUsing(fn ($state): string => number_format((float) $state, 1)), TextColumn::make('description') ->label('摘要') ->toggleable(isToggledHiddenByDefault: true) ->wrap() ->limit(80), ]) ->filters([ SelectFilter::make('phase') ->label('学段') ->options($this->getPhaseOptions()), SelectFilter::make('category') ->label('知识类别') ->options($this->getCategoryOptions()), ]) ->records(fn (array $params) => $this->filterPoints($params)) ->paginated(false) ->defaultSort('importance', 'desc') ->actions([ Action::make('view') ->label('查看详情') ->icon('heroicon-m-eye') ->modalHeading(fn (array $record): string => "{$record['cn_name']}({$record['kp_code']})") ->modalContent(fn (array $record) => view('filament.pages.partials.kp-detail', [ 'point' => $this->knowledgeService->getKnowledgePointDetail($record['kp_code']), ])) ->modalSubmitAction(false), ]) ->deferLoading(); } protected function filterPoints(array $params): Collection { $records = $this->getPoints(); $getFilters = $params['filters']; if ($filters = $getFilters()) { if (! empty($filters['phase'])) { $records = $records->where('phase', $filters['phase']); } if (! empty($filters['category'])) { $records = $records->where('category', $filters['category']); } } $searchCallback = $params['search']; $search = Str::lower(trim((string) $searchCallback())); if ($search !== '') { $records = $records->filter(function (array $record) use ($search): bool { return Str::contains(Str::lower($record['cn_name'] ?? ''), $search) || Str::contains(Str::lower($record['kp_code'] ?? ''), $search) || Str::contains(Str::lower($record['description'] ?? ''), $search); }); } $sortColumn = $params['sortColumn'](); $sortDirection = $params['sortDirection']() ?? 'asc'; if ($sortColumn) { $records = $records->sortBy(fn ($record) => $record[$sortColumn] ?? null, SORT_REGULAR, $sortDirection === 'desc'); } return $records->values(); } protected function getPoints(): Collection { if ($this->cachedPoints) { return $this->cachedPoints; } return $this->cachedPoints = $this->knowledgeService ->listKnowledgePoints() ->map(fn (array $point) => [ 'kp_code' => $point['kp_code'] ?? null, 'cn_name' => $point['cn_name'] ?? '-', 'phase' => $point['phase'] ?? null, 'grade' => $point['grade'] ?? null, 'category' => $point['category'] ?? null, 'importance' => $point['importance'] ?? null, 'description' => $point['description'] ?? '', 'group_path' => $point['group_path'] ?? null, 'parents' => $point['parents'] ?? [], 'created_at' => $point['created_at'] ?? null, 'updated_at' => $point['updated_at'] ?? null, ]); } protected function getPhaseOptions(): array { return $this->getPoints() ->pluck('phase') ->filter() ->unique() ->sort() ->mapWithKeys(fn ($phase) => [$phase => $phase]) ->all(); } protected function getCategoryOptions(): array { return $this->getPoints() ->pluck('category') ->filter() ->unique() ->sort() ->mapWithKeys(fn ($category) => [$category => $category]) ->all(); } /** * @return array> */ public function getStatsProperty(): array { $points = $this->getPoints(); $byPhase = $points->groupBy('phase')->map->count(); $byCategory = $points->groupBy('category')->map->count(); return [ [ 'label' => '知识点数量', 'value' => number_format($points->count()), ], [ 'label' => '学段覆盖', 'value' => $byPhase->count() ? $byPhase->map(fn ($count, $phase) => "{$phase}:{$count}")->implode(' | ') : '未设置', ], [ 'label' => '类别分布 Top3', 'value' => $byCategory->sortDesc()->take(3)->map(fn ($count, $category) => "{$category}:{$count}")->implode(' | ') ?: '未设置', ], [ 'label' => '最近同步', 'value' => now()->toDateTimeString(), ], ]; } }