null, 'grade' => null, 'term' => null, 'chapter' => null, 'source_type' => null, 'source_year' => null, 'textbook_id' => null, 'textbook_series' => null, 'source_name' => null, 'source_page' => null, 'tags' => '', ]; public array $batch = [ 'edition' => null, 'grade' => null, 'term' => null, 'chapter' => null, 'source_type' => null, 'source_year' => null, 'textbook_id' => null, 'textbook_series' => null, 'source_name' => null, 'source_page' => null, 'tags' => '', ]; public function mount(): void { $first = $this->papers()->first(); if ($first) { $this->selectPaper($first->id); } } public function papers() { $query = SourcePaper::query()->with(['textbook']); if ($this->search !== '') { $query->where(function ($q) { $q->where('title', 'like', '%' . $this->search . '%') ->orWhere('full_title', 'like', '%' . $this->search . '%') ->orWhere('paper_code', 'like', '%' . $this->search . '%'); }); } if ($this->gradeFilter) { $query->where('grade', $this->gradeFilter); } if ($this->termFilter) { $query->where('term', $this->termFilter); } return $query->orderByDesc('id')->limit(80)->get(); } public function selectedPaper(): ?SourcePaper { if (!$this->selectedPaperId) { return null; } return SourcePaper::query()->with('textbook')->find($this->selectedPaperId); } public function selectAllVisible(): void { $this->selectedIds = $this->papers()->pluck('id')->toArray(); } public function clearSelection(): void { $this->selectedIds = []; } public function selectPaper(int $paperId): void { $paper = SourcePaper::query()->find($paperId); if (!$paper) { return; } $this->selectedPaperId = $paperId; $meta = $paper->meta ?? []; $this->form = [ 'edition' => $paper->edition, 'grade' => $paper->grade, 'term' => $paper->term, 'chapter' => $paper->chapter, 'source_type' => $paper->source_type, 'source_year' => $paper->source_year, 'textbook_id' => $paper->textbook_id, 'textbook_series' => $paper->textbook_series, 'source_name' => Arr::get($meta, 'source_name'), 'source_page' => Arr::get($meta, 'source_page'), 'tags' => implode(',', Arr::get($meta, 'tags', [])), ]; } public function savePaper(): void { $paper = $this->selectedPaper(); if (!$paper) { return; } $meta = $paper->meta ?? []; $meta['source_name'] = $this->form['source_name'] ?? null; $meta['source_page'] = $this->form['source_page'] ?? null; $meta['tags'] = $this->explodeTags($this->form['tags'] ?? ''); $paper->update([ 'edition' => $this->form['edition'] ?? null, 'grade' => $this->form['grade'] ?? null, 'term' => $this->form['term'] ?? null, 'chapter' => $this->form['chapter'] ?? null, 'source_type' => $this->form['source_type'] ?? null, 'source_year' => $this->form['source_year'] ?? null, 'textbook_id' => $this->form['textbook_id'] ?? null, 'textbook_series' => $this->form['textbook_series'] ?? null, 'meta' => $meta, ]); } public function applyBatch(): void { if (empty($this->selectedIds)) { return; } $updates = array_filter([ 'edition' => $this->batch['edition'] ?? null, 'grade' => $this->batch['grade'] ?? null, 'term' => $this->batch['term'] ?? null, 'chapter' => $this->batch['chapter'] ?? null, 'source_type' => $this->batch['source_type'] ?? null, 'source_year' => $this->batch['source_year'] ?? null, 'textbook_id' => $this->batch['textbook_id'] ?? null, 'textbook_series' => $this->batch['textbook_series'] ?? null, ], fn ($value) => $value !== null && $value !== ''); foreach (SourcePaper::query()->whereIn('id', $this->selectedIds)->get() as $paper) { $meta = $paper->meta ?? []; if (!empty($this->batch['source_name'])) { $meta['source_name'] = $this->batch['source_name']; } if (!empty($this->batch['source_page'])) { $meta['source_page'] = $this->batch['source_page']; } if (!empty($this->batch['tags'])) { $meta['tags'] = $this->explodeTags($this->batch['tags']); } $paper->update(array_merge($updates, ['meta' => $meta])); } } public function autoInfer(): void { $paper = $this->selectedPaper(); if (!$paper) { return; } $title = (string) ($paper->title ?? $paper->full_title ?? ''); $raw = (string) ($paper->raw_markdown ?? ''); $context = $title . ' ' . $raw; $this->form['term'] = $this->inferTerm($context) ?? $this->form['term']; $this->form['grade'] = $this->inferGrade($context) ?? $this->form['grade']; $this->form['chapter'] = $this->inferChapter($context) ?? $this->form['chapter']; } public function autoInferSelected(): void { if (empty($this->selectedIds)) { return; } foreach (SourcePaper::query()->whereIn('id', $this->selectedIds)->get() as $paper) { $context = (string) ($paper->title ?? $paper->full_title ?? '') . ' ' . (string) ($paper->raw_markdown ?? ''); $updates = array_filter([ 'term' => $this->inferTerm($context), 'grade' => $this->inferGrade($context), 'chapter' => $this->inferChapter($context), ], fn ($value) => $value !== null && $value !== ''); if (!empty($updates)) { $paper->update($updates); } } } public function textbookOptions(): array { return Textbook::query()->orderBy('id')->pluck('official_title', 'id')->toArray(); } public function gradeOptions(): array { return collect(range(1, 12))->mapWithKeys(fn ($grade) => [$grade => $grade . '年级'])->toArray(); } public function termOptions(): array { return [ '上册' => '上册', '下册' => '下册', '上学期' => '上学期', '下学期' => '下学期', ]; } public function sourceTypeOptions(): array { return [ '期中' => '期中卷', '期末' => '期末卷', '单元卷' => '单元卷', '专项卷' => '专项卷', '教材' => '教材', '其他' => '其他', ]; } private function inferTerm(string $context): ?string { if (str_contains($context, '上册') || str_contains($context, '上学期')) { return '上册'; } if (str_contains($context, '下册') || str_contains($context, '下学期')) { return '下册'; } return null; } private function inferGrade(string $context): ?string { foreach (['七年级' => '7', '八年级' => '8', '九年级' => '9', '高一' => '10', '高二' => '11', '高三' => '12'] as $label => $value) { if (str_contains($context, $label)) { return $value; } } return null; } private function inferChapter(string $context): ?string { if (preg_match('/第[一二三四五六七八九十]+章[^\\n]*/u', $context, $match)) { return $match[0]; } return null; } private function explodeTags(string $tags): array { return array_values(array_filter(array_map('trim', explode(',', $tags)))); } }