SourcePaperEnrichment.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <?php
  2. namespace App\Filament\Pages;
  3. use App\Models\SourcePaper;
  4. use App\Models\Textbook;
  5. use Filament\Pages\Page;
  6. use Illuminate\Support\Arr;
  7. class SourcePaperEnrichment extends Page
  8. {
  9. protected static bool $shouldRegisterNavigation = false;
  10. protected static ?string $navigationLabel = '卷子信息补录';
  11. protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-document-text';
  12. protected static string|\UnitEnum|null $navigationGroup = '卷子导入流程';
  13. protected static ?int $navigationSort = 99;
  14. protected string $view = 'filament.pages.source-paper-enrichment';
  15. public string $search = '';
  16. public ?string $gradeFilter = null;
  17. public ?string $termFilter = null;
  18. public bool $dense = false;
  19. public ?int $selectedPaperId = null;
  20. public array $selectedIds = [];
  21. public array $form = [
  22. 'edition' => null,
  23. 'grade' => null,
  24. 'term' => null,
  25. 'chapter' => null,
  26. 'source_type' => null,
  27. 'source_year' => null,
  28. 'textbook_id' => null,
  29. 'textbook_series' => null,
  30. 'source_name' => null,
  31. 'source_page' => null,
  32. 'tags' => '',
  33. ];
  34. public array $batch = [
  35. 'edition' => null,
  36. 'grade' => null,
  37. 'term' => null,
  38. 'chapter' => null,
  39. 'source_type' => null,
  40. 'source_year' => null,
  41. 'textbook_id' => null,
  42. 'textbook_series' => null,
  43. 'source_name' => null,
  44. 'source_page' => null,
  45. 'tags' => '',
  46. ];
  47. public function mount(): void
  48. {
  49. $first = $this->papers()->first();
  50. if ($first) {
  51. $this->selectPaper($first->id);
  52. }
  53. }
  54. public function papers()
  55. {
  56. $query = SourcePaper::query()->with(['textbook']);
  57. if ($this->search !== '') {
  58. $query->where(function ($q) {
  59. $q->where('title', 'like', '%' . $this->search . '%')
  60. ->orWhere('full_title', 'like', '%' . $this->search . '%')
  61. ->orWhere('paper_code', 'like', '%' . $this->search . '%');
  62. });
  63. }
  64. if ($this->gradeFilter) {
  65. $query->where('grade', $this->gradeFilter);
  66. }
  67. if ($this->termFilter) {
  68. $query->where('term', $this->termFilter);
  69. }
  70. return $query->orderByDesc('id')->limit(80)->get();
  71. }
  72. public function selectedPaper(): ?SourcePaper
  73. {
  74. if (!$this->selectedPaperId) {
  75. return null;
  76. }
  77. return SourcePaper::query()->with('textbook')->find($this->selectedPaperId);
  78. }
  79. public function selectAllVisible(): void
  80. {
  81. $this->selectedIds = $this->papers()->pluck('id')->toArray();
  82. }
  83. public function clearSelection(): void
  84. {
  85. $this->selectedIds = [];
  86. }
  87. public function selectPaper(int $paperId): void
  88. {
  89. $paper = SourcePaper::query()->find($paperId);
  90. if (!$paper) {
  91. return;
  92. }
  93. $this->selectedPaperId = $paperId;
  94. $meta = $paper->meta ?? [];
  95. $this->form = [
  96. 'edition' => $paper->edition,
  97. 'grade' => $paper->grade,
  98. 'term' => $paper->term,
  99. 'chapter' => $paper->chapter,
  100. 'source_type' => $paper->source_type,
  101. 'source_year' => $paper->source_year,
  102. 'textbook_id' => $paper->textbook_id,
  103. 'textbook_series' => $paper->textbook_series,
  104. 'source_name' => Arr::get($meta, 'source_name'),
  105. 'source_page' => Arr::get($meta, 'source_page'),
  106. 'tags' => implode(',', Arr::get($meta, 'tags', [])),
  107. ];
  108. }
  109. public function savePaper(): void
  110. {
  111. $paper = $this->selectedPaper();
  112. if (!$paper) {
  113. return;
  114. }
  115. $meta = $paper->meta ?? [];
  116. $meta['source_name'] = $this->form['source_name'] ?? null;
  117. $meta['source_page'] = $this->form['source_page'] ?? null;
  118. $meta['tags'] = $this->explodeTags($this->form['tags'] ?? '');
  119. $paper->update([
  120. 'edition' => $this->form['edition'] ?? null,
  121. 'grade' => $this->form['grade'] ?? null,
  122. 'term' => $this->form['term'] ?? null,
  123. 'chapter' => $this->form['chapter'] ?? null,
  124. 'source_type' => $this->form['source_type'] ?? null,
  125. 'source_year' => $this->form['source_year'] ?? null,
  126. 'textbook_id' => $this->form['textbook_id'] ?? null,
  127. 'textbook_series' => $this->form['textbook_series'] ?? null,
  128. 'meta' => $meta,
  129. ]);
  130. }
  131. public function applyBatch(): void
  132. {
  133. if (empty($this->selectedIds)) {
  134. return;
  135. }
  136. $updates = array_filter([
  137. 'edition' => $this->batch['edition'] ?? null,
  138. 'grade' => $this->batch['grade'] ?? null,
  139. 'term' => $this->batch['term'] ?? null,
  140. 'chapter' => $this->batch['chapter'] ?? null,
  141. 'source_type' => $this->batch['source_type'] ?? null,
  142. 'source_year' => $this->batch['source_year'] ?? null,
  143. 'textbook_id' => $this->batch['textbook_id'] ?? null,
  144. 'textbook_series' => $this->batch['textbook_series'] ?? null,
  145. ], fn ($value) => $value !== null && $value !== '');
  146. foreach (SourcePaper::query()->whereIn('id', $this->selectedIds)->get() as $paper) {
  147. $meta = $paper->meta ?? [];
  148. if (!empty($this->batch['source_name'])) {
  149. $meta['source_name'] = $this->batch['source_name'];
  150. }
  151. if (!empty($this->batch['source_page'])) {
  152. $meta['source_page'] = $this->batch['source_page'];
  153. }
  154. if (!empty($this->batch['tags'])) {
  155. $meta['tags'] = $this->explodeTags($this->batch['tags']);
  156. }
  157. $paper->update(array_merge($updates, ['meta' => $meta]));
  158. }
  159. }
  160. public function autoInfer(): void
  161. {
  162. $paper = $this->selectedPaper();
  163. if (!$paper) {
  164. return;
  165. }
  166. $title = (string) ($paper->title ?? $paper->full_title ?? '');
  167. $raw = (string) ($paper->raw_markdown ?? '');
  168. $context = $title . ' ' . $raw;
  169. $this->form['term'] = $this->inferTerm($context) ?? $this->form['term'];
  170. $this->form['grade'] = $this->inferGrade($context) ?? $this->form['grade'];
  171. $this->form['chapter'] = $this->inferChapter($context) ?? $this->form['chapter'];
  172. }
  173. public function autoInferSelected(): void
  174. {
  175. if (empty($this->selectedIds)) {
  176. return;
  177. }
  178. foreach (SourcePaper::query()->whereIn('id', $this->selectedIds)->get() as $paper) {
  179. $context = (string) ($paper->title ?? $paper->full_title ?? '') . ' ' . (string) ($paper->raw_markdown ?? '');
  180. $updates = array_filter([
  181. 'term' => $this->inferTerm($context),
  182. 'grade' => $this->inferGrade($context),
  183. 'chapter' => $this->inferChapter($context),
  184. ], fn ($value) => $value !== null && $value !== '');
  185. if (!empty($updates)) {
  186. $paper->update($updates);
  187. }
  188. }
  189. }
  190. public function textbookOptions(): array
  191. {
  192. return Textbook::query()->orderBy('id')->pluck('official_title', 'id')->toArray();
  193. }
  194. public function gradeOptions(): array
  195. {
  196. return collect(range(1, 12))->mapWithKeys(fn ($grade) => [$grade => $grade . '年级'])->toArray();
  197. }
  198. public function termOptions(): array
  199. {
  200. return [
  201. '上册' => '上册',
  202. '下册' => '下册',
  203. '上学期' => '上学期',
  204. '下学期' => '下学期',
  205. ];
  206. }
  207. public function sourceTypeOptions(): array
  208. {
  209. return [
  210. '期中' => '期中卷',
  211. '期末' => '期末卷',
  212. '单元卷' => '单元卷',
  213. '专项卷' => '专项卷',
  214. '教材' => '教材',
  215. '其他' => '其他',
  216. ];
  217. }
  218. private function inferTerm(string $context): ?string
  219. {
  220. if (str_contains($context, '上册') || str_contains($context, '上学期')) {
  221. return '上册';
  222. }
  223. if (str_contains($context, '下册') || str_contains($context, '下学期')) {
  224. return '下册';
  225. }
  226. return null;
  227. }
  228. private function inferGrade(string $context): ?string
  229. {
  230. foreach (['七年级' => '7', '八年级' => '8', '九年级' => '9', '高一' => '10', '高二' => '11', '高三' => '12'] as $label => $value) {
  231. if (str_contains($context, $label)) {
  232. return $value;
  233. }
  234. }
  235. return null;
  236. }
  237. private function inferChapter(string $context): ?string
  238. {
  239. if (preg_match('/第[一二三四五六七八九十]+章[^\\n]*/u', $context, $match)) {
  240. return $match[0];
  241. }
  242. return null;
  243. }
  244. private function explodeTags(string $tags): array
  245. {
  246. return array_values(array_filter(array_map('trim', explode(',', $tags))));
  247. }
  248. }