withCount('teachers'); if ($kw = $request->query('keyword')) { $query->where(function ($q) use ($kw) { $q->where('title', 'like', "%{$kw}%") ->orWhere('authors', 'like', "%{$kw}%") ->orWhere('school_name', 'like', "%{$kw}%") ->orWhere('summary', 'like', "%{$kw}%"); }); } if ($request->filled('school_name')) { $query->where('school_name', $request->query('school_name')); } if ($request->filled('university_id')) { $query->where('university_id', (int) $request->query('university_id')); } if ($request->filled('research_direction_id')) { $dirId = (int) $request->query('research_direction_id'); $query->whereHas('teachers.researchDirections', fn ($q) => $q->where('research_directions.id', $dirId)); } elseif ($request->filled('research_direction')) { $dir = $request->query('research_direction'); $query->whereHas('teachers.researchDirections', fn ($q) => $q->where('research_directions.name', 'like', "%{$dir}%")); } if ($request->query('link_status') === 'linked') { $query->has('teachers'); } elseif ($request->query('link_status') === 'unlinked') { $query->doesntHave('teachers'); } if ($request->filled('source')) { $query->where('source', $request->query('source')); } if ($request->filled('crawl_job_id')) { $query->where('crawl_job_id', (int) $request->query('crawl_job_id')); } $paginator = $query ->orderByDesc('published_at') ->orderByDesc('id') ->paginate((int) $request->query('page_size', 20)) ->withQueryString(); $paginator->getCollection()->transform(fn (Paper $p) => $this->serializeList($p)); return $this->paginated($paginator); } public function filterOptions(): JsonResponse { $schools = Paper::query() ->whereNotNull('school_name') ->distinct() ->orderBy('school_name') ->pluck('school_name') ->filter() ->values(); $directions = ResearchDirection::query() ->where('status', 1) ->whereHas('teachers') ->orderBy('sort') ->orderBy('name') ->pluck('name') ->values(); return $this->ok([ 'school_names' => $schools, 'research_directions' => $directions, ]); } public function show(int $paper): JsonResponse { $model = Paper::query()->with(['teachers.university'])->findOrFail($paper); return $this->ok($this->serializeDetail($model)); } public function attachTeachers(Request $request, int $paper): JsonResponse { $data = $request->validate([ 'teacher_ids' => ['required', 'array', 'min:1'], 'teacher_ids.*' => ['integer', 'exists:teachers,id'], ]); $model = Paper::query()->findOrFail($paper); $user = $this->gridScope->userFromRequest($request); $teacherIds = array_values(array_unique(array_map('intval', $data['teacher_ids']))); $teachers = Teacher::query()->whereIn('id', $teacherIds)->get(); foreach ($teachers as $teacher) { $this->gridScope->assertTeacherAccessible($user, $teacher); } $model->teachers()->syncWithoutDetaching($teacherIds); return $this->ok(null, '已关联老师'); } public function destroy(int $paper): JsonResponse { $model = Paper::query()->findOrFail($paper); $model->teachers()->detach(); $model->delete(); return $this->ok(null, '已删除'); } /** * @return array */ protected function serializeList(Paper $p): array { $isNew = $p->source === 'crawl' && $p->created_at && $p->created_at->gte(Carbon::now()->subDay()); return [ 'id' => $p->id, 'title' => $p->title, 'authors' => $p->authors, 'school_name' => $p->school_name, 'published_at' => $p->published_at?->format('Y-m'), 'imported_at' => $p->created_at?->format('Y-m-d'), 'url' => $p->url, 'summary' => $p->summary, 'source' => $p->source, 'is_new' => $isNew, 'teachers_count' => (int) ($p->teachers_count ?? 0), 'is_linked' => (int) ($p->teachers_count ?? 0) > 0, ]; } /** * @return array */ protected function serializeDetail(Paper $p): array { $row = $this->serializeList($p); $row['published_at'] = $p->published_at?->toDateString(); $row['teachers'] = $p->teachers->map(fn ($t) => [ 'id' => $t->id, 'name' => $t->name, 'university_name' => $t->university?->name, ])->values(); return $row; } }