TeacherStudentSelector.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <?php
  2. namespace App\Forms\Components;
  3. use App\Models\Student;
  4. use App\Models\Teacher;
  5. use Filament\Forms\Components\Field;
  6. use Filament\Forms\Components\Select;
  7. use Illuminate\Support\Collection;
  8. class TeacherStudentSelector extends Field
  9. {
  10. protected string $view = 'forms.components.teacher-student-selector';
  11. protected array $teacherOptions = [];
  12. protected array $studentOptions = [];
  13. protected bool $enableTeacherFilter = true;
  14. protected bool $required = false;
  15. protected string $teacherLabel = '选择老师';
  16. protected string $studentLabel = '选择学生';
  17. protected string $teacherPlaceholder = '请选择老师...';
  18. protected string $studentPlaceholder = '请选择学生...';
  19. protected ?string $teacherHelperText = null;
  20. protected ?string $studentHelperText = null;
  21. public function setUp(): void
  22. {
  23. parent::setUp();
  24. $this->afterStateUpdated(function (TeacherStudentSelector $component, string|int|null $state) {
  25. // 当老师选择变化时,清空学生选择并更新学生选项
  26. if ($this->isTeacherField()) {
  27. $component->getContainer()->getComponent('student_id')->state('');
  28. }
  29. });
  30. // 初始化老师选项
  31. $this->loadTeacherOptions();
  32. }
  33. protected function isTeacherField(): bool
  34. {
  35. return $this->getName() === 'teacher_id';
  36. }
  37. protected function loadTeacherOptions(): void
  38. {
  39. try {
  40. $teachers = Teacher::query()
  41. ->leftJoin('users as u', 'teachers.teacher_id', '=', 'u.user_id')
  42. ->select(
  43. 'teachers.teacher_id',
  44. 'teachers.name',
  45. 'teachers.subject',
  46. 'u.username',
  47. 'u.email'
  48. )
  49. ->orderBy('teachers.name')
  50. ->get();
  51. // 检查是否有学生没有对应的老师记录
  52. $teacherIds = $teachers->pluck('teacher_id')->toArray();
  53. $missingTeacherIds = Student::query()
  54. ->distinct()
  55. ->whereNotIn('teacher_id', $teacherIds)
  56. ->pluck('teacher_id')
  57. ->toArray();
  58. $teachersArray = $teachers->all();
  59. if (!empty($missingTeacherIds)) {
  60. foreach ($missingTeacherIds as $missingId) {
  61. $teachersArray[] = (object) [
  62. 'teacher_id' => $missingId,
  63. 'name' => '未知老师 (' . $missingId . ')',
  64. 'subject' => '未知',
  65. 'username' => null,
  66. 'email' => null
  67. ];
  68. }
  69. usort($teachersArray, function($a, $b) {
  70. return strcmp($a->name, $b->name);
  71. });
  72. }
  73. $this->teacherOptions = collect($teachersArray)->mapWithKeys(function ($teacher) {
  74. return [
  75. $teacher->teacher_id => trim($teacher->name .
  76. ($teacher->subject ? " ({$teacher->subject})" : ''))
  77. ];
  78. })->toArray();
  79. } catch (\Exception $e) {
  80. \Illuminate\Support\Facades\Log::error('加载老师列表失败', [
  81. 'error' => $e->getMessage()
  82. ]);
  83. $this->teacherOptions = [];
  84. }
  85. }
  86. protected function loadStudentOptions(?string $teacherId = null): array
  87. {
  88. if (empty($teacherId)) {
  89. return [];
  90. }
  91. try {
  92. return Student::query()
  93. ->leftJoin('users as u', 'students.student_id', '=', 'u.user_id')
  94. ->where('students.teacher_id', $teacherId)
  95. ->select(
  96. 'students.student_id',
  97. 'students.name',
  98. 'students.grade',
  99. 'students.class_name',
  100. 'u.username',
  101. 'u.email'
  102. )
  103. ->orderBy('students.grade')
  104. ->orderBy('students.class_name')
  105. ->orderBy('students.name')
  106. ->get()
  107. ->mapWithKeys(function ($student) {
  108. return [
  109. $student->student_id => trim($student->name .
  110. " ({$student->grade} - {$student->class_name})")
  111. ];
  112. })
  113. ->toArray();
  114. } catch (\Exception $e) {
  115. \Illuminate\Support\Facades\Log::error('加载学生列表失败', [
  116. 'teacher_id' => $teacherId,
  117. 'error' => $e->getMessage()
  118. ]);
  119. return [];
  120. }
  121. }
  122. public function teacherOptions(array $options): static
  123. {
  124. $this->teacherOptions = $options;
  125. return $this;
  126. }
  127. public function studentOptions(array $options): static
  128. {
  129. $this->studentOptions = $options;
  130. return $this;
  131. }
  132. public function required(bool $condition = true): static
  133. {
  134. $this->required = $condition;
  135. return $this;
  136. }
  137. public function enableTeacherFilter(bool $condition = true): static
  138. {
  139. $this->enableTeacherFilter = $condition;
  140. return $this;
  141. }
  142. public function teacherLabel(string $label): static
  143. {
  144. $this->teacherLabel = $label;
  145. return $this;
  146. }
  147. public function studentLabel(string $label): static
  148. {
  149. $this->studentLabel = $label;
  150. return $this;
  151. }
  152. public function teacherPlaceholder(string $placeholder): static
  153. {
  154. $this->teacherPlaceholder = $placeholder;
  155. return $this;
  156. }
  157. public function studentPlaceholder(string $placeholder): static
  158. {
  159. $this->studentPlaceholder = $placeholder;
  160. return $this;
  161. }
  162. public function teacherHelperText(?string $text): static
  163. {
  164. $this->teacherHelperText = $text;
  165. return $this;
  166. }
  167. public function studentHelperText(?string $text): static
  168. {
  169. $this->studentHelperText = $text;
  170. return $this;
  171. }
  172. public function getTeacherOptions(): array
  173. {
  174. return $this->teacherOptions;
  175. }
  176. public function getStudentOptions(?string $teacherId = null): array
  177. {
  178. return $this->enableTeacherFilter ?
  179. $this->loadStudentOptions($teacherId) :
  180. $this->studentOptions;
  181. }
  182. public function isRequired(): bool
  183. {
  184. return $this->required;
  185. }
  186. public function isTeacherFilterEnabled(): bool
  187. {
  188. return $this->enableTeacherFilter;
  189. }
  190. public function getTeacherLabel(): string
  191. {
  192. return $this->teacherLabel;
  193. }
  194. public function getStudentLabel(): string
  195. {
  196. return $this->studentLabel;
  197. }
  198. public function getTeacherPlaceholder(): string
  199. {
  200. return $this->teacherPlaceholder;
  201. }
  202. public function getStudentPlaceholder(): string
  203. {
  204. return $this->studentPlaceholder;
  205. }
  206. public function getTeacherHelperText(): ?string
  207. {
  208. return $this->teacherHelperText;
  209. }
  210. public function getStudentHelperText(): ?string
  211. {
  212. return $this->studentHelperText;
  213. }
  214. public static function make(string $name): static
  215. {
  216. return new static($name);
  217. }
  218. }