EditTextbook.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. <?php
  2. namespace App\Filament\Resources\TextbookResource\Pages;
  3. use App\Filament\Resources\TextbookResource;
  4. use App\Services\TextbookApiService;
  5. use App\Services\TextbookCoverStorageService;
  6. use App\Models\Textbook;
  7. use Filament\Forms;
  8. use Filament\Actions;
  9. use Filament\Schemas\Components\Section;
  10. use Filament\Resources\Pages\Page;
  11. use Illuminate\Http\Request;
  12. use Livewire\WithFileUploads;
  13. class EditTextbook extends Page implements Forms\Contracts\HasForms
  14. {
  15. use Forms\Concerns\InteractsWithForms;
  16. use WithFileUploads;
  17. protected static string $resource = TextbookResource::class;
  18. public array $data = [];
  19. public ?int $recordId = null;
  20. public function mount(Request $request): void
  21. {
  22. // 从路由参数获取教材ID,避免Livewire的隐式绑定
  23. $this->recordId = (int) $request->route('record');
  24. if (!$this->recordId) {
  25. abort(404);
  26. }
  27. // 从API获取教材数据
  28. $apiService = app(TextbookApiService::class);
  29. $textbookData = $apiService->getTextbook($this->recordId);
  30. if (!$textbookData) {
  31. abort(404);
  32. }
  33. // 初始化表单数据
  34. $this->data = $textbookData;
  35. $this->form->fill($this->data);
  36. }
  37. public function save(): void
  38. {
  39. // 验证数据
  40. $this->validate([
  41. 'data.series_id' => 'required|integer',
  42. 'data.stage' => 'required|string',
  43. 'data.grade' => 'nullable|integer',
  44. 'data.semester' => 'nullable|integer',
  45. 'data.official_title' => 'required|string|max:255',
  46. 'data.isbn' => 'nullable|string|max:255',
  47. 'data.status' => 'required|string|in:draft,published,archived',
  48. ]);
  49. // 只传递标量字段到API,跳过关系字段和嵌套对象
  50. $allowedFields = [
  51. 'series_id', 'stage', 'grade', 'semester', 'naming_scheme',
  52. 'track', 'module_type', 'volume_no', 'legacy_code',
  53. 'curriculum_standard_year', 'curriculum_revision_year',
  54. 'approval_authority', 'approval_year', 'edition_label',
  55. 'official_title', 'aliases', 'isbn',
  56. 'cover_path', 'status', 'sort_order', 'meta'
  57. ];
  58. $updateData = [];
  59. foreach ($allowedFields as $field) {
  60. if (isset($this->data[$field]) && !is_array($this->data[$field]) && !is_object($this->data[$field])) {
  61. $updateData[$field] = $this->data[$field];
  62. }
  63. }
  64. // 调用API更新
  65. $apiService = app(TextbookApiService::class);
  66. $updatedData = $apiService->updateTextbook($this->recordId, $updateData);
  67. // 更新本地数据
  68. $this->data = array_merge($this->data, $updatedData);
  69. // 显示成功消息
  70. \Filament\Notifications\Notification::make()
  71. ->title('教材更新成功')
  72. ->success()
  73. ->send();
  74. }
  75. protected function getHeaderActions(): array
  76. {
  77. return [
  78. Actions\Action::make('back')
  79. ->label('返回列表')
  80. ->url(static::$resource::getUrl('index'))
  81. ->color('gray'),
  82. ];
  83. }
  84. public function getTitle(): string
  85. {
  86. return '编辑教材';
  87. }
  88. public function getView(): string
  89. {
  90. return 'filament.resources.textbook-resource.edit';
  91. }
  92. public function form(\Filament\Schemas\Schema $schema): \Filament\Schemas\Schema
  93. {
  94. return $schema
  95. ->schema([
  96. Section::make('基本信息')
  97. ->schema([
  98. Forms\Components\Select::make('series_id')
  99. ->label('教材系列')
  100. ->options(function () {
  101. $apiService = app(TextbookApiService::class);
  102. $series = $apiService->getTextbookSeries();
  103. $options = [];
  104. foreach ($series['data'] as $s) {
  105. $options[$s['id']] = $s['name'];
  106. }
  107. return $options;
  108. })
  109. ->required()
  110. ->searchable(),
  111. Forms\Components\Select::make('stage')
  112. ->label('学段')
  113. ->options([
  114. 'primary' => '小学',
  115. 'junior' => '初中',
  116. 'senior' => '高中',
  117. ])
  118. ->required()
  119. ->reactive(),
  120. Forms\Components\TextInput::make('grade')
  121. ->label('年级')
  122. ->numeric()
  123. ->helperText('例如:7表示七年级'),
  124. Forms\Components\Select::make('semester')
  125. ->label('学期')
  126. ->options([
  127. 1 => '上学期',
  128. 2 => '下学期',
  129. ])
  130. ->helperText('选择学期'),
  131. Forms\Components\TextInput::make('official_title')
  132. ->label('教材名称')
  133. ->required()
  134. ->maxLength(255),
  135. Forms\Components\TextInput::make('isbn')
  136. ->label('ISBN')
  137. ->maxLength(255),
  138. Forms\Components\Select::make('status')
  139. ->label('状态')
  140. ->options([
  141. 'draft' => '草稿',
  142. 'published' => '已发布',
  143. 'archived' => '已归档',
  144. ])
  145. ->required(),
  146. ])
  147. ->columns(2),
  148. Section::make('封面上传')
  149. ->schema([
  150. Forms\Components\FileUpload::make('cover_path')
  151. ->label('封面图片')
  152. ->image()
  153. ->directory('textbook-covers')
  154. ->saveUploadedFileUsing(function ($component, $file) {
  155. $uploader = app(TextbookCoverStorageService::class);
  156. $url = $uploader->uploadCover($file, $this->recordId ? (string) $this->recordId : null);
  157. if ($url) {
  158. return $url;
  159. }
  160. return $file->storePubliclyAs(
  161. $component->getDirectory(),
  162. $component->getUploadedFileNameForStorage($file),
  163. $component->getDiskName(),
  164. );
  165. })
  166. ->helperText('建议尺寸 600x800,JPG/PNG'),
  167. ])
  168. ->extraAttributes(['id' => 'cover']),
  169. ])
  170. ->statePath('data');
  171. }
  172. }