optionalUser($request); $pageSize = (int) $request->query('page_size', 20); $page = max(1, (int) $request->query('page', 1)); if ($request->filled('progress_status')) { $statusFilter = (int) $request->query('progress_status'); $courses = $this->publishedQuery($request) ->orderByDesc('teach_start_date') ->orderByDesc('id') ->get() ->filter(fn (Course $course) => MiniappPresenter::resolveCourseProgressStatus($course) === $statusFilter) ->values(); $total = $courses->count(); $items = $courses ->slice(($page - 1) * $pageSize, $pageSize) ->map(fn (Course $course) => MiniappPresenter::serializeCourseList($course, $user)) ->values() ->all(); return $this->ok([ 'items' => $items, 'meta' => [ 'current_page' => $page, 'per_page' => $pageSize, 'total' => $total, 'last_page' => max(1, (int) ceil($total / $pageSize)), ], ]); } $paginator = $this->publishedQuery($request) ->orderByDesc('teach_start_date') ->orderByDesc('id') ->paginate($pageSize) ->withQueryString(); $paginator->getCollection()->transform( fn (Course $course) => MiniappPresenter::serializeCourseList($course, $user) ); return $this->paginated($paginator); } public function hot(): JsonResponse { $items = Course::query() ->with(['courseSystemItem', 'coverMedia', 'promoMedia']) ->withCount('signups') ->where('published', 1) ->get() ->filter(fn (Course $course) => in_array( MiniappPresenter::resolveCourseProgressStatus($course), [1, 2], true )) ->sort(function (Course $a, Course $b) { $signupDelta = ((int) $b->signups_count) <=> ((int) $a->signups_count); if ($signupDelta !== 0) { return $signupDelta; } return $b->id <=> $a->id; }) ->take(3) ->map(fn (Course $course) => MiniappPresenter::serializeCourseList($course)) ->values() ->all(); return $this->ok(['items' => $items]); } public function latest(): JsonResponse { $items = Course::query() ->with(['courseSystemItem', 'coverMedia', 'promoMedia']) ->withCount('signups') ->where('published', 1) ->orderByRaw('teach_start_date IS NULL') ->orderByDesc('teach_start_date') ->orderByDesc('id') ->limit(3) ->get() ->map(fn (Course $course) => MiniappPresenter::serializeCourseList($course)) ->values() ->all(); return $this->ok(['items' => $items]); } public function show(Request $request, int $course): JsonResponse { $user = $this->optionalUser($request); $model = Course::query() ->with(['courseSystemItem', 'coverMedia', 'promoMedia']) ->withCount('signups') ->where('published', 1) ->findOrFail($course); return $this->ok(MiniappPresenter::serializeCourseList($model, $user)); } public function signup(Request $request, int $course): JsonResponse { /** @var MiniappUser $user */ $user = $request->user(); $model = Course::query()->withCount('signups')->where('published', 1)->findOrFail($course); if (CourseSignup::query()->where('course_id', $course)->where('miniapp_user_id', $user->id)->exists()) { return $this->fail('您已报名该课程', 422); } if (! MiniappPresenter::canSignupCourse($model, (int) $model->signups_count)) { return $this->fail('当前不可报名', 422); } $data = $request->validate([ 'name' => ['required', 'string', 'max:64'], 'mobile' => ['required', 'string', 'max:32'], 'company' => ['nullable', 'string', 'max:128'], ]); CourseSignup::query()->create([ 'course_id' => $course, 'miniapp_user_id' => $user->id, 'name' => $data['name'], 'mobile' => $data['mobile'], 'company' => $data['company'] ?? null, 'signed_up_at' => now(), 'status' => 1, ]); if (! $user->name) { $user->name = $data['name']; } if (! $user->mobile) { $user->mobile = $data['mobile']; } if (! $user->company && ! empty($data['company'])) { $user->company = $data['company']; } $user->save(); return $this->ok(null, '报名成功'); } public function mySignups(Request $request): JsonResponse { /** @var MiniappUser $user */ $user = $request->user(); $courseIds = CourseSignup::query() ->where('miniapp_user_id', $user->id) ->pluck('course_id'); $items = Course::query() ->with(['courseSystemItem', 'coverMedia', 'promoMedia']) ->withCount('signups') ->whereIn('id', $courseIds) ->orderByDesc('teach_start_date') ->orderByDesc('id') ->get() ->map(fn (Course $course) => MiniappPresenter::serializeCourseList($course, $user)) ->values() ->all(); return $this->ok(['items' => $items]); } public function calendar(Request $request): JsonResponse { /** @var MiniappUser $user */ $user = $request->user(); $data = $request->validate([ 'month' => ['required', 'date_format:Y-m'], ]); $courseIds = CourseSignup::query() ->where('miniapp_user_id', $user->id) ->pluck('course_id'); $courses = Course::query() ->with(['courseSystemItem']) ->withCount('signups') ->whereIn('id', $courseIds) ->get(); [$year, $month] = array_map('intval', explode('-', $data['month'])); $monthStart = sprintf('%04d-%02d-01', $year, $month); $monthEnd = date('Y-m-t', strtotime($monthStart)); $events = $courses->map(function (Course $course) { $progressStatus = MiniappPresenter::resolveCourseProgressStatus($course); return [ 'id' => $course->id, 'title' => $course->title, 'start_date' => $course->teach_start_date?->toDateString(), 'end_date' => $course->teach_end_date?->toDateString(), 'time_range' => MiniappPresenter::timeRange( MiniappPresenter::formatTimeValue($course->teach_start_time), MiniappPresenter::formatTimeValue($course->teach_end_time) ), 'location' => $course->location, 'progress_status' => $progressStatus, 'progress_status_label' => MiniappPresenter::progressStatusLabel($progressStatus), ]; })->filter(function (array $event) use ($monthStart, $monthEnd) { if (! $event['start_date']) { return false; } $start = $event['start_date']; $end = $event['end_date'] ?: $start; return $start <= $monthEnd && $end >= $monthStart; })->values(); return $this->ok([ 'month' => $data['month'], 'events' => $events, ]); } protected function publishedQuery(Request $request) { $query = Course::query() ->with(['courseSystemItem', 'coverMedia', 'promoMedia']) ->withCount('signups') ->where('published', 1); if ($kw = $request->query('keyword')) { $query->where(function ($q) use ($kw) { $q->where('title', 'like', "%{$kw}%") ->orWhere('location', 'like', "%{$kw}%"); }); } if ($request->filled('course_system_dict_item_id')) { $query->where('course_system_dict_item_id', (int) $request->query('course_system_dict_item_id')); } return $query; } }