TextbookCatalogResource.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <?php
  2. namespace App\Filament\Resources;
  3. use App\Filament\Resources\TextbookCatalogResource\Pages;
  4. use App\Models\TextbookCatalog;
  5. use App\Services\TextbookApiService;
  6. use BackedEnum;
  7. use UnitEnum;
  8. use Filament\Resources\Resource;
  9. use Filament\Tables;
  10. use Filament\Tables\Columns\TextColumn;
  11. use Filament\Tables\Columns\BadgeColumn;
  12. use Filament\Actions\EditAction;
  13. use Filament\Actions\DeleteAction;
  14. use Filament\Actions\Action;
  15. use Illuminate\Database\Eloquent\Model;
  16. class TextbookCatalogResource extends Resource
  17. {
  18. protected static ?string $model = ApiTextbookCatalog::class;
  19. protected static ?string $recordTitleAttribute = 'title';
  20. protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-squares-2x2';
  21. protected static ?string $navigationLabel = '教材目录';
  22. protected static UnitEnum|string|null $navigationGroup = '教材管理';
  23. protected static ?int $navigationSort = 3;
  24. protected static ?TextbookApiService $apiService = null;
  25. public static function boot()
  26. {
  27. parent::boot();
  28. static::$apiService = app(TextbookApiService::class);
  29. }
  30. protected static function getApiService(): TextbookApiService
  31. {
  32. if (!static::$apiService) {
  33. static::$apiService = app(TextbookApiService::class);
  34. }
  35. return static::$apiService;
  36. }
  37. public static function table(Tables\Table $table): Tables\Table
  38. {
  39. return $table
  40. ->columns([
  41. TextColumn::make('textbook.official_title')
  42. ->label('教材')
  43. ->searchable()
  44. ->wrap(),
  45. TextColumn::make('title')
  46. ->label('目录标题')
  47. ->searchable()
  48. ->wrap(),
  49. TextColumn::make('display_no')
  50. ->label('编号')
  51. ->searchable(),
  52. BadgeColumn::make('node_type')
  53. ->label('类型')
  54. ->formatStateUsing(fn (string $state): string => match ($state) {
  55. 'chapter' => '章',
  56. 'section' => '节',
  57. 'subsection' => '小节',
  58. 'item' => '条目',
  59. 'project' => '项目学习',
  60. 'reading' => '阅读材料',
  61. 'practice' => '综合实践',
  62. 'summary' => '复习',
  63. 'appendix' => '附录',
  64. default => '其他',
  65. })
  66. ->color('info'),
  67. TextColumn::make('depth')
  68. ->label('层级')
  69. ->sortable(),
  70. TextColumn::make('page_start')
  71. ->label('起始页码')
  72. ->sortable(),
  73. TextColumn::make('page_end')
  74. ->label('结束页码')
  75. ->sortable(),
  76. TextColumn::make('created_at')
  77. ->label('创建时间')
  78. ->dateTime('Y-m-d H:i')
  79. ->sortable()
  80. ->toggleable(),
  81. ])
  82. ->filters([
  83. Tables\Filters\SelectFilter::make('textbook_id')
  84. ->label('教材')
  85. ->options(function () {
  86. $textbooks = static::getApiService()->getTextbooks();
  87. $options = [];
  88. foreach ($textbooks['data'] ?? [] as $t) {
  89. $options[$t['id']] = $t['official_title'];
  90. }
  91. return $options;
  92. })
  93. ->searchable()
  94. ->preload(),
  95. Tables\Filters\SelectFilter::make('node_type')
  96. ->label('节点类型')
  97. ->options([
  98. 'chapter' => '章',
  99. 'section' => '节',
  100. 'subsection' => '小节',
  101. 'item' => '条目',
  102. 'project' => '项目学习',
  103. 'reading' => '阅读材料',
  104. 'practice' => '综合实践',
  105. 'summary' => '复习',
  106. 'appendix' => '附录',
  107. 'custom' => '其他',
  108. ]),
  109. ])
  110. ->actions([
  111. EditAction::make()
  112. ->label('编辑'),
  113. Action::make('delete')
  114. ->label('删除')
  115. ->color('danger')
  116. ->icon('heroicon-o-trash')
  117. ->requiresConfirmation()
  118. ->modalHeading('删除教材目录')
  119. ->modalDescription('确定要删除这个教材目录吗?此操作无法撤销。')
  120. ->action(function (Model $record) {
  121. $apiService = app(\App\Services\TextbookApiService::class);
  122. $deleted = $apiService->deleteTextbookCatalog($record->id);
  123. if ($deleted) {
  124. // 刷新页面
  125. return redirect()->refresh();
  126. } else {
  127. // 显示错误消息
  128. \Filament\Notifications\Notification::make()
  129. ->title('错误')
  130. ->body('删除失败,请重试。')
  131. ->danger()
  132. ->send();
  133. }
  134. }),
  135. ])
  136. ->paginated([10, 25, 50, 100])
  137. ->poll(null); // 禁用自动刷新
  138. }
  139. public static function getEloquentQuery(): \Illuminate\Database\Eloquent\builder
  140. {
  141. // 完全不使用数据库查询,所有数据通过 API 获取
  142. // 强制使用 migrations 表,这个表肯定存在
  143. return parent::getEloquentQuery()->from('migrations')->whereRaw('1=0');
  144. }
  145. public static function getRecord(?string $key): ?Model
  146. {
  147. // 教材目录是嵌套数据,需要根据 textbook_id 获取
  148. $textbookId = request()->get('textbook_id');
  149. if (!$textbookId) {
  150. return null;
  151. }
  152. $catalog = static::getApiService()->getTextbookCatalog((int) $textbookId, 'flat');
  153. foreach ($catalog as $node) {
  154. if ($node['id'] == $key) {
  155. return new ApiTextbookCatalog($node);
  156. }
  157. }
  158. return null;
  159. }
  160. public static function getRecords(): array
  161. {
  162. $textbookId = request()->get('tableFilters.textbook_id.value');
  163. if (!$textbookId) {
  164. return [];
  165. }
  166. $catalog = static::getApiService()->getTextbookCatalog((int) $textbookId, 'flat');
  167. $records = [];
  168. foreach ($catalog as $node) {
  169. $records[] = new ApiTextbookCatalog($node);
  170. }
  171. return $records;
  172. }
  173. public static function getPages(): array
  174. {
  175. return [
  176. 'index' => Pages\ManageTextbookCatalogs::route('/'),
  177. ];
  178. }
  179. public static function canViewAny(): bool
  180. {
  181. // 临时允许所有用户查看,等待权限系统完善
  182. return true;
  183. }
  184. public static function canCreate(): bool
  185. {
  186. // 临时允许所有用户创建,等待权限系统完善
  187. return true;
  188. }
  189. public static function canEdit(Model $record): bool
  190. {
  191. // 临时允许所有用户编辑,等待权限系统完善
  192. return true;
  193. }
  194. public static function canDelete(Model $record): bool
  195. {
  196. // 临时允许所有用户删除,等待权限系统完善
  197. return true;
  198. }
  199. public static function canDeleteAny(): bool
  200. {
  201. // 临时允许所有用户批量删除,等待权限系统完善
  202. return true;
  203. }
  204. protected static function deleteRecord(Model $record): bool
  205. {
  206. // 删除记录时,同时通过 API 删除题库服务中的数据
  207. return static::getApiService()->deleteTextbookCatalog($record->id);
  208. }
  209. }
  210. /**
  211. * API 教材目录模型 - 完全通过 API 获取数据
  212. * 这个类继承自 Model 但不执行任何数据库查询
  213. */
  214. class ApiTextbookCatalog extends \Illuminate\Database\Eloquent\Model
  215. {
  216. protected $table = 'migrations'; // 使用肯定存在的表
  217. // 禁用时间戳
  218. public $timestamps = false;
  219. // 禁用所有fillable检查
  220. protected $guarded = [];
  221. public function __construct(array $attributes = [])
  222. {
  223. parent::__construct($attributes);
  224. }
  225. }