TextbookTable.php 9.3 KB

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