UploadExamPaper.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. <?php
  2. namespace App\Filament\Pages;
  3. use App\Jobs\ProcessOCRRecord;
  4. use App\Models\OCRRecord;
  5. use App\Models\Student;
  6. use App\Models\Teacher;
  7. use BackedEnum;
  8. use Filament\Notifications\Notification;
  9. use Filament\Pages\Page;
  10. use Livewire\WithFileUploads;
  11. use Livewire\Attributes\Computed;
  12. use Illuminate\Support\Facades\Storage;
  13. use UnitEnum;
  14. class UploadExamPaper extends Page
  15. {
  16. use WithFileUploads;
  17. protected static ?string $title = '上传考试卷子';
  18. protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-arrow-up-tray';
  19. protected static ?string $navigationLabel = '上传考试卷子';
  20. protected static string|UnitEnum|null $navigationGroup = '操作';
  21. protected static ?int $navigationSort = 2;
  22. protected static ?string $slug = 'upload-exam-paper';
  23. protected string $view = 'filament.pages.upload-exam-paper';
  24. public ?string $selectedTeacherId = null;
  25. public ?string $selectedStudentId = null;
  26. public $uploadedImage = null;
  27. public bool $isUploading = false;
  28. #[Computed]
  29. public function teachers(): array
  30. {
  31. try {
  32. $teachers = Teacher::query()
  33. ->leftJoin('users as u', 'teachers.teacher_id', '=', 'u.user_id')
  34. ->select(
  35. 'teachers.teacher_id',
  36. 'teachers.name',
  37. 'teachers.subject',
  38. 'u.username',
  39. 'u.email'
  40. )
  41. ->orderBy('teachers.name')
  42. ->get();
  43. // 检查是否有学生没有对应的老师记录
  44. $teacherIds = $teachers->pluck('teacher_id')->toArray();
  45. $missingTeacherIds = Student::query()
  46. ->distinct()
  47. ->whereNotIn('teacher_id', $teacherIds)
  48. ->pluck('teacher_id')
  49. ->toArray();
  50. $teachersArray = $teachers->all();
  51. if (!empty($missingTeacherIds)) {
  52. foreach ($missingTeacherIds as $missingId) {
  53. $teachersArray[] = (object) [
  54. 'teacher_id' => $missingId,
  55. 'name' => '未知老师 (' . $missingId . ')',
  56. 'subject' => '未知',
  57. 'username' => null,
  58. 'email' => null
  59. ];
  60. }
  61. usort($teachersArray, function($a, $b) {
  62. return strcmp($a->name, $b->name);
  63. });
  64. }
  65. return $teachersArray;
  66. } catch (\Exception $e) {
  67. \Illuminate\Support\Facades\Log::error('加载老师列表失败', [
  68. 'error' => $e->getMessage()
  69. ]);
  70. return [];
  71. }
  72. }
  73. #[Computed]
  74. public function students(): array
  75. {
  76. if (empty($this->selectedTeacherId)) {
  77. return [];
  78. }
  79. try {
  80. return Student::query()
  81. ->leftJoin('users as u', 'students.student_id', '=', 'u.user_id')
  82. ->where('students.teacher_id', $this->selectedTeacherId)
  83. ->select(
  84. 'students.student_id',
  85. 'students.name',
  86. 'students.grade',
  87. 'students.class_name',
  88. 'u.username',
  89. 'u.email'
  90. )
  91. ->orderBy('students.grade')
  92. ->orderBy('students.class_name')
  93. ->orderBy('students.name')
  94. ->get()
  95. ->all();
  96. } catch (\Exception $e) {
  97. \Illuminate\Support\Facades\Log::error('加载学生列表失败', [
  98. 'teacher_id' => $this->selectedTeacherId,
  99. 'error' => $e->getMessage()
  100. ]);
  101. return [];
  102. }
  103. }
  104. #[Computed]
  105. public function recentRecords(): array
  106. {
  107. return OCRRecord::with('student')
  108. ->latest()
  109. ->take(5)
  110. ->get()
  111. ->toArray();
  112. }
  113. public function updatedSelectedTeacherId($value): void
  114. {
  115. // 当教师选择变化时,清空之前选择的学生
  116. $this->selectedStudentId = null;
  117. }
  118. public function submitUpload(): void
  119. {
  120. if (!$this->selectedTeacherId) {
  121. Notification::make()
  122. ->title('请选择老师')
  123. ->danger()
  124. ->send();
  125. return;
  126. }
  127. if (!$this->selectedStudentId) {
  128. Notification::make()
  129. ->title('请选择学生')
  130. ->danger()
  131. ->send();
  132. return;
  133. }
  134. if (!$this->uploadedImage) {
  135. Notification::make()
  136. ->title('请上传图片')
  137. ->danger()
  138. ->send();
  139. return;
  140. }
  141. $this->isUploading = true;
  142. try {
  143. // 保存图片
  144. $path = $this->uploadedImage->store('ocr-uploads', 'public');
  145. $filename = basename($path);
  146. // 创建OCR记录
  147. $record = OCRRecord::create([
  148. 'student_id' => $this->selectedStudentId,
  149. 'image_path' => $path,
  150. 'image_filename' => $filename,
  151. 'status' => 'pending',
  152. 'total_questions' => 0,
  153. 'processed_questions' => 0,
  154. ]);
  155. // 自动触发OCR处理
  156. ProcessOCRRecord::dispatch($record->id);
  157. // 立即更新状态为处理中,提供更好的用户体验
  158. $record->update(['status' => 'processing']);
  159. // 重置表单
  160. $this->selectedTeacherId = null;
  161. $this->selectedStudentId = null;
  162. $this->uploadedImage = null;
  163. Notification::make()
  164. ->title('上传成功')
  165. ->body("卷子已上传并开始OCR处理。记录ID: {$record->id}")
  166. ->success()
  167. ->send();
  168. // 刷新最近记录
  169. unset($this->recentRecords);
  170. } catch (\Exception $e) {
  171. Notification::make()
  172. ->title('上传失败')
  173. ->body($e->getMessage())
  174. ->danger()
  175. ->send();
  176. } finally {
  177. $this->isUploading = false;
  178. }
  179. }
  180. public function removeImage(): void
  181. {
  182. $this->uploadedImage = null;
  183. }
  184. }