columns([ TextColumn::make('title') ->label('标题') ->searchable() ->limit(40) ->description(fn ($record) => $record->category?->name), TextColumn::make('content_format') ->label('格式') ->badge() ->formatStateUsing(fn ($state) => $state === 'markdown' ? 'MD' : 'HTML') ->color(fn ($state) => $state === 'markdown' ? 'success' : 'gray'), TextColumn::make('status') ->label('状态') ->badge() ->formatStateUsing(fn ($state) => match ($state) { 'published' => '已发布', 'draft' => '草稿', 'private' => '私密', default => $state, }) ->color(fn ($state) => match ($state) { 'published' => 'success', 'draft' => 'warning', default => 'gray', }), IconColumn::make('cover_state') ->label('封面') ->boolean() ->trueIcon('heroicon-o-photo') ->falseIcon('heroicon-o-x-mark') ->trueColor('success') ->falseColor('gray') ->state(fn (Post $record): bool => $record->coverImageUrl() !== null) ->tooltip(fn (Post $record): string => $record->coverImageUrl() !== null ? '已有封面' : '无封面,可用「生成封面」AI 补一张') ->toggleable(), TextColumn::make('sticky_level') ->label('置顶') ->badge() ->sortable() ->formatStateUsing(fn ($state) => Post::stickyLevelOptions()[(int) $state] ?? '不置顶') ->color(fn ($state) => match ((int) $state) { Post::STICKY_HEADLINE => 'danger', Post::STICKY_STRONG => 'warning', Post::STICKY_LIGHT => 'success', default => 'gray', }), TextColumn::make('views') ->label('阅读') ->numeric() ->sortable(), TextColumn::make('comment_count') ->label('评论') ->numeric() ->sortable(), TextColumn::make('published_at') ->label('发布时间') ->dateTime('Y-m-d H:i') ->sortable(), ]) ->filters([ SelectFilter::make('category_id') ->label('分类') ->relationship('category', 'name'), SelectFilter::make('status') ->label('状态') ->options([ 'published' => '已发布', 'draft' => '草稿', 'private' => '私密', ]), SelectFilter::make('content_format') ->label('格式') ->options([ 'markdown' => 'Markdown', 'html' => 'HTML', ]), SelectFilter::make('sticky_level') ->label('置顶') ->options(Post::stickyLevelOptions()), SelectFilter::make('cover_status') ->label('封面') ->options([ 'with' => '有封面', 'without' => '无封面', 'no_image' => '完全无图(正文/附件也没有)', ]) ->query(fn (Builder $query, array $data): Builder => match ($data['value'] ?? null) { 'with' => $query->where(fn (Builder $q) => $q ->whereNotNull('cover_media_id') ->orWhereHas('media', fn (Builder $m) => $m->where('collection_name', 'cover'))), 'without' => $query ->whereNull('cover_media_id') ->whereDoesntHave('media', fn (Builder $m) => $m->where('collection_name', 'cover')), 'no_image' => $query ->whereNull('cover_media_id') ->whereDoesntHave('media', fn (Builder $m) => $m->where(fn (Builder $x) => $x ->where('collection_name', 'cover') ->orWhere(fn (Builder $y) => $y ->where('collection_name', 'attachments') ->where('mime_type', 'like', 'image/%')))) ->where('content', 'not like', '%where('content', 'not like', '%](http%'), default => $query, }), ], FiltersLayout::AboveContent) ->filtersFormColumns(3) ->filtersFormWidth(Width::ThreeExtraLarge) ->recordActions([ Action::make('generateCover') ->label('生成封面') ->icon('heroicon-o-photo') ->color('gray') ->requiresConfirmation() ->modalHeading('AI 生成封面') ->modalDescription(fn (Post $record): string => "根据《{$record->title}》的标题和摘要生成一张封面图,约需 20 秒,请耐心等待。") ->action(function (Post $record): void { try { app(CoverGenerator::class)->generateFor($record); Notification::make()->title('封面已生成')->success()->send(); } catch (\Throwable $e) { Notification::make()->title('生成失败')->body($e->getMessage())->danger()->send(); } }), Action::make('preview') ->label('预览') ->url(fn (Post $record): string => $record->url) ->openUrlInNewTab(), ViewAction::make(), EditAction::make(), ]) ->recordUrl(null) ->recordAction(null) ->toolbarActions([ BulkActionGroup::make([ DeleteBulkAction::make(), ]), ]) ->defaultSort('published_at', 'desc'); } }