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'); $activities = $this->publishedQuery($request) ->orderByDesc('event_start_date') ->orderByDesc('id') ->get() ->filter(fn (Activity $activity) => MiniappPresenter::resolveActivityProgressStatus($activity) === $statusFilter) ->values(); $total = $activities->count(); $items = $activities ->slice(($page - 1) * $pageSize, $pageSize) ->map(function (Activity $activity) use ($user) { $row = MiniappPresenter::serializeActivityList($activity, $user); $row['summary'] = strip_tags((string) $activity->intro_html); return $row; }) ->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('event_start_date') ->orderByDesc('id') ->paginate($pageSize) ->withQueryString(); $paginator->getCollection()->transform(function (Activity $activity) use ($user) { $row = MiniappPresenter::serializeActivityList($activity, $user); $row['summary'] = strip_tags((string) $activity->intro_html); return $row; }); return $this->paginated($paginator); } public function show(Request $request, int $activity): JsonResponse { $user = $this->optionalUser($request); $model = Activity::query() ->with(['activityTypeItem', 'sessions' => fn ($q) => $q->orderBy('sort')->orderBy('id')]) ->withCount('signups') ->where('published', 1) ->findOrFail($activity); $row = MiniappPresenter::serializeActivityList($model, $user); $row['summary'] = strip_tags((string) $model->intro_html); $row['sessions'] = $model->sessions ->map(fn (ActivitySession $session) => MiniappPresenter::serializeActivitySession($session, $user)) ->values() ->all(); return $this->ok($row); } public function signup(Request $request, int $activity): JsonResponse { /** @var MiniappUser $user */ $user = $request->user(); $model = Activity::query()->where('published', 1)->findOrFail($activity); if (MiniappPresenter::activityDisplayStatus($model) !== '报名中') { return $this->fail('当前不在报名期', 422); } $data = $request->validate([ 'activity_session_id' => ['required', 'integer', 'exists:activity_sessions,id'], 'name' => ['required', 'string', 'max:64'], 'mobile' => ['nullable', 'string', 'max:32'], 'company' => ['nullable', 'string', 'max:128'], ]); $session = ActivitySession::query() ->where('activity_id', $activity) ->findOrFail((int) $data['activity_session_id']); if (ActivitySignup::query() ->where('activity_session_id', $session->id) ->where('miniapp_user_id', $user->id) ->exists()) { return $this->fail('您已报名该场次', 422); } $signed = $session->signups()->count(); $capacity = (int) ($session->capacity ?? 0); if ($capacity > 0 && $signed >= $capacity) { return $this->fail('该场次名额已满', 422); } ActivitySignup::query()->create([ 'activity_id' => $activity, 'activity_session_id' => $session->id, 'miniapp_user_id' => $user->id, 'name' => $data['name'], 'mobile' => $data['mobile'] ?? null, 'company' => $data['company'] ?? null, 'signed_up_at' => now(), 'status' => 1, ]); if (! $user->name) { $user->name = $data['name']; } if (! $user->mobile && ! empty($data['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(); $signups = ActivitySignup::query() ->with(['activity.activityTypeItem', 'session']) ->where('miniapp_user_id', $user->id) ->orderByDesc('id') ->get() ->map(function (ActivitySignup $signup) use ($user) { $activity = $signup->activity; if (! $activity) { return null; } $row = MiniappPresenter::serializeActivityList($activity, $user); $row['summary'] = strip_tags((string) $activity->intro_html); $row['session'] = $signup->session ? MiniappPresenter::serializeActivitySession($signup->session, $user) : null; return $row; }) ->filter() ->values() ->all(); return $this->ok(['items' => $signups]); } public function calendar(Request $request): JsonResponse { $data = $request->validate([ 'month' => ['required', 'date_format:Y-m'], ]); [$year, $month] = array_map('intval', explode('-', $data['month'])); $monthStart = sprintf('%04d-%02d-01', $year, $month); $monthEnd = date('Y-m-t', strtotime($monthStart)); $sessions = ActivitySession::query() ->with('activity') ->whereHas('activity', fn ($q) => $q->where('published', 1)) ->orderBy('starts_at') ->orderBy('id') ->get(); $events = $sessions->map(function (ActivitySession $session) { $activity = $session->activity; if (! $activity) { return null; } $startDate = $session->starts_at?->toDateString(); $endDate = $session->ends_at?->toDateString() ?: $startDate; $progressStatus = $startDate ? ScheduleProgressStatus::resolve($startDate, $endDate) : MiniappPresenter::resolveActivityProgressStatus($activity); $sessionTitle = trim((string) $session->title); $title = $sessionTitle !== '' ? $activity->title.' · '.$sessionTitle : $activity->title; return [ 'id' => $session->id, 'activity_id' => $activity->id, 'title' => $title, 'start_date' => $startDate, 'end_date' => $endDate, 'time_range' => MiniappPresenter::timeRange( $session->starts_at?->format('H:i'), $session->ends_at?->format('H:i') ), 'location' => $session->venue ?: $activity->location, 'progress_status' => $progressStatus, 'progress_status_label' => MiniappPresenter::progressStatusLabel($progressStatus), ]; })->filter(function (?array $event) use ($monthStart, $monthEnd) { if (! $event || ! $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 = Activity::query() ->with(['activityTypeItem', 'sessions']) ->withCount(['sessions', 'signups']) ->where('published', 1); if ($kw = $request->query('keyword')) { $query->where(function ($q) use ($kw) { $q->where('title', 'like', "%{$kw}%") ->orWhere('location', 'like', "%{$kw}%") ->orWhere('intro_html', 'like', "%{$kw}%"); }); } if ($request->filled('activity_type_dict_item_id')) { $query->where('activity_type_dict_item_id', (int) $request->query('activity_type_dict_item_id')); } return $query; } }