TextbookTable.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <?php
  2. namespace App\Filament\Resources\TextbookResource\Tables;
  3. use App\Filament\Resources\TextbookResource;
  4. use App\Models\TextbookSeries;
  5. use Filament\Actions\EditAction;
  6. use Filament\Actions\Action;
  7. use Filament\Actions\BulkAction;
  8. use Filament\Tables;
  9. use Filament\Tables\Columns\TextColumn;
  10. use Filament\Tables\Filters\SelectFilter;
  11. use Filament\Tables\Table;
  12. use Illuminate\Database\Eloquent\Model;
  13. use Filament\Tables\Enums\FiltersLayout;
  14. use Filament\Tables\Filters\Filter;
  15. use Filament\Forms\Components\TextInput;
  16. use Illuminate\Support\HtmlString;
  17. class TextbookTable
  18. {
  19. public static function make(Table $table): Table
  20. {
  21. // 直接返回配置好的表格,不使用query()
  22. return $table
  23. ->defaultSort('id', 'asc')
  24. ->columns([
  25. TextColumn::make('id')->label('ID')->sortable(),
  26. TextColumn::make('official_title')
  27. ->label('教材名称')
  28. ->searchable()
  29. ->wrap()
  30. // 副标题链接:不依赖单独「配图」列是否被横向滚动/列管理隐藏,用户一定能点到
  31. ->description(function (Model $record): HtmlString {
  32. $href = TextbookResource::getUrl('covers', ['record' => $record->getKey()]);
  33. return new HtmlString(
  34. '<a href="' . e($href) . '" class="fi-link fi-size-sm text-primary-600 hover:underline dark:text-primary-400">管理配图</a>'
  35. );
  36. }),
  37. // 独立列:用 HTML 直链,避免 TextColumn::url() + icon/color 与 getUrl($stateItem) 组合导致链接丢失
  38. TextColumn::make('covers_nav')
  39. ->label('配图')
  40. ->alignCenter()
  41. ->tooltip('上传、排序、删除多图(独立页面)')
  42. ->state(static fn (): string => "\u{00A0}")
  43. ->formatStateUsing(static function ($state, TextColumn $column): HtmlString {
  44. $href = TextbookResource::getUrl('covers', ['record' => $column->getRecord()->getKey()]);
  45. return new HtmlString(
  46. '<a href="' . e($href) . '" class="fi-link fi-size-sm font-medium text-primary-600 hover:underline dark:text-primary-400">管理配图</a>'
  47. );
  48. })
  49. ->html(),
  50. TextColumn::make('series_name')->label('教材系列')->sortable(),
  51. TextColumn::make('series_id')->label('系列ID')->sortable(),
  52. TextColumn::make('stage')
  53. ->label('学段')
  54. ->sortable()
  55. ->formatStateUsing(fn ($state) => match ($state) {
  56. 'primary' => '小学',
  57. 'junior' => '初中',
  58. 'senior' => '高中',
  59. default => $state ?: '未标注',
  60. }),
  61. TextColumn::make('grade')->label('年级')->sortable(),
  62. TextColumn::make('semester')->label('学期')->sortable(),
  63. TextColumn::make('isbn')->label('ISBN')->toggleable(isToggledHiddenByDefault: true),
  64. TextColumn::make('status')
  65. ->label('状态')
  66. ->sortable()
  67. ->formatStateUsing(fn ($state) => match ($state) {
  68. 'draft' => '草稿',
  69. 'published' => '已发布',
  70. 'archived' => '已归档',
  71. default => $state ?: '未知',
  72. }),
  73. TextColumn::make('created_at')
  74. ->label('创建时间')
  75. ->dateTime()
  76. ->sortable()
  77. ->toggleable(isToggledHiddenByDefault: true),
  78. TextColumn::make('updated_at')
  79. ->label('更新时间')
  80. ->dateTime()
  81. ->sortable()
  82. ->toggleable(isToggledHiddenByDefault: true),
  83. ])
  84. ->filters([
  85. SelectFilter::make('series_id')
  86. ->label('教材系列')
  87. ->options(fn (): array => TextbookSeries::query()
  88. ->orderBy('sort_order')
  89. ->orderBy('id')
  90. ->pluck('name', 'id')
  91. ->toArray()),
  92. SelectFilter::make('stage')
  93. ->label('学段')
  94. ->options([
  95. 'primary' => '小学',
  96. 'junior' => '初中',
  97. 'senior' => '高中',
  98. ]),
  99. SelectFilter::make('grade')
  100. ->label('年级')
  101. ->options(collect(range(1, 12))->mapWithKeys(fn ($grade) => [$grade => "{$grade}年级"])->all()),
  102. SelectFilter::make('semester')
  103. ->label('学期')
  104. ->options([
  105. 1 => '上学期',
  106. 2 => '下学期',
  107. ]),
  108. SelectFilter::make('status')
  109. ->label('发布状态')
  110. ->options([
  111. 'draft' => '草稿',
  112. 'published' => '已发布',
  113. 'archived' => '已归档',
  114. ]),
  115. Filter::make('keyword')
  116. ->label('关键词')
  117. ->form([
  118. TextInput::make('value')
  119. ->placeholder('教材名称 / ISBN / 系列'),
  120. ]),
  121. ], layout: FiltersLayout::AboveContentCollapsible)
  122. ->actions([
  123. EditAction::make()
  124. ->label('编辑')
  125. ->icon('heroicon-o-pencil-square')
  126. ->iconButton()
  127. ->tooltip('编辑'),
  128. Action::make('covers')
  129. ->label('配图')
  130. ->icon('heroicon-o-photo')
  131. ->color('primary')
  132. ->tooltip('管理配图')
  133. ->url(fn (Model $record): string => TextbookResource::getUrl('covers', ['record' => $record->getKey()])),
  134. Action::make('delete')
  135. ->label('删除')
  136. ->color('danger')
  137. ->icon('heroicon-o-trash')
  138. ->iconButton()
  139. ->tooltip('删除')
  140. ->requiresConfirmation()
  141. ->modalHeading('删除教材')
  142. ->modalDescription('确定要删除这个教材吗?此操作无法撤销。')
  143. ->action(function (Model $record) {
  144. // 添加调试日志
  145. \Log::info('Deleting textbook', ['id' => $record->id, 'record' => $record]);
  146. if (!$record || !$record->id) {
  147. \Filament\Notifications\Notification::make()
  148. ->title('错误')
  149. ->body('无效的教材记录。')
  150. ->danger()
  151. ->send();
  152. return;
  153. }
  154. $apiService = app(\App\Services\TextbookApiService::class);
  155. $deleted = $apiService->deleteTextbook($record->id);
  156. \Log::info('Delete result', ['deleted' => $deleted]);
  157. if ($deleted) {
  158. \Filament\Notifications\Notification::make()
  159. ->title('成功')
  160. ->body('教材删除成功。')
  161. ->success()
  162. ->send();
  163. } else {
  164. \Filament\Notifications\Notification::make()
  165. ->title('错误')
  166. ->body('删除失败,请重试。')
  167. ->danger()
  168. ->send();
  169. }
  170. }),
  171. Action::make('view_catalog')
  172. ->label('查看目录')
  173. ->icon('heroicon-o-list-bullet')
  174. ->iconButton()
  175. ->tooltip('查看目录')
  176. ->url(fn(Model $record): string =>
  177. route('filament.admin.resources.textbook-catalogs.index', ['filters[textbook_id][value]' => $record->id])
  178. ),
  179. ])
  180. ->bulkActions([
  181. \Filament\Actions\BulkActionGroup::make([
  182. \Filament\Actions\DeleteBulkAction::make()
  183. ->label('批量删除'),
  184. BulkAction::make('archive')
  185. ->label('批量归档')
  186. ->color('warning')
  187. ->icon('heroicon-o-archive-box')
  188. ->requiresConfirmation()
  189. ->action(function ($records): void {
  190. $apiService = app(\App\Services\TextbookApiService::class);
  191. foreach ($records as $record) {
  192. $apiService->updateTextbook((int) $record->id, ['status' => 'archived']);
  193. }
  194. }),
  195. ]),
  196. ])
  197. ->recordUrl(fn (Model $record): string => route('filament.admin.resources.textbooks.view', $record))
  198. ->defaultSort('sort_order')
  199. ->paginated([10, 25, 50, 100]);
  200. }
  201. }