with(['activityTypeItem']) ->withCount(['sessions', 'signups']); if ($kw = $request->query('keyword')) { $query->where(function ($q) use ($kw) { $q->where('title', 'like', "%{$kw}%") ->orWhere('location', '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')); } if ($request->filled('progress_status')) { $query->where('progress_status', (int) $request->query('progress_status')); } if ($request->filled('published')) { $query->where('published', (int) $request->query('published')); } $paginator = $query ->orderByDesc('id') ->paginate((int) $request->query('page_size', 20)) ->withQueryString(); $paginator->getCollection()->transform(fn (Activity $a) => $this->serializeList($a)); return $this->paginated($paginator); } public function store(Request $request): JsonResponse { if (! DictType::query()->where('code', 'activity_type')->where('status', 1)->exists()) { return $this->fail('字典「活动类型」未配置,请在后台维护数据字典或执行 ActivityDictionarySeeder', 422); } $data = $this->validatedActivity($request); $data['progress_status'] = ScheduleProgressStatus::resolve( $data['event_start_date'] ?? null, $data['event_end_date'] ?? null, $data['signup_start_date'] ?? null, $data['signup_end_date'] ?? null, ); $activity = Activity::query()->create($data); ActivitySigninCodeSync::ensureActivityCode($activity->fresh()); return $this->ok(['id' => $activity->id], '已创建'); } public function show(int $activity): JsonResponse { $model = Activity::query() ->with(['activityTypeItem']) ->withCount(['sessions', 'signups']) ->findOrFail($activity); return $this->ok($this->serializeDetail($model)); } public function update(Request $request, int $activity): JsonResponse { $model = Activity::query()->findOrFail($activity); $data = $this->validatedActivity($request, partial: true); $model->fill($data); $model->progress_status = ScheduleProgressStatus::resolve( $model->event_start_date?->toDateString(), $model->event_end_date?->toDateString(), $model->signup_start_date?->toDateString(), $model->signup_end_date?->toDateString(), ); $model->save(); ActivitySigninCodeSync::ensureActivityCode($model); return $this->ok(null, '已保存'); } public function updateShelf(Request $request, int $activity): JsonResponse { $data = $request->validate([ 'published' => ['required', 'integer', 'in:0,1'], ]); $model = Activity::query()->findOrFail($activity); $model->published = (int) $data['published']; $model->save(); return $this->ok(null, '已更新发布状态'); } public function destroy(int $activity): JsonResponse { Activity::query()->findOrFail($activity)->delete(); return $this->ok(null, '已删除'); } /** * @return array */ protected function validatedActivity(Request $request, bool $partial = false): array { $activityTypeId = DictType::query()->where('code', 'activity_type')->where('status', 1)->value('id'); return $request->validate([ 'title' => [$partial ? 'sometimes' : 'required', 'string', 'max:255'], 'activity_type_dict_item_id' => $this->dictItemRules($activityTypeId, $partial ? 'sometimes' : 'required'), 'quota' => ['nullable', 'integer', 'min:0'], 'event_start_date' => [$partial ? 'sometimes' : 'required', 'date'], 'event_end_date' => $partial ? ['sometimes', 'date'] : ['required', 'date', 'after_or_equal:event_start_date'], 'signup_start_date' => [$partial ? 'sometimes' : 'required', 'date'], 'signup_end_date' => $partial ? ['sometimes', 'date'] : ['required', 'date', 'after_or_equal:signup_start_date'], 'location' => ['nullable', 'string', 'max:255'], 'intro_html' => ['nullable', 'string'], 'published' => ['nullable', 'integer', 'in:0,1'], 'remark' => ['nullable', 'string'], 'sort' => ['nullable', 'integer'], ]); } /** * @return array */ protected function dictItemRules(?int $dictTypeId, string $presence): array { if (! $dictTypeId) { return match ($presence) { 'required' => ['required', 'integer'], 'sometimes' => ['sometimes', 'nullable', 'integer'], default => ['nullable', 'integer'], }; } $exists = Rule::exists('dict_items', 'id')->where( fn ($q) => $q->where('dict_type_id', $dictTypeId)->where('status', 1) ); return match ($presence) { 'required' => ['required', 'integer', $exists], 'sometimes' => ['sometimes', 'nullable', 'integer', $exists], default => ['nullable', 'integer', $exists], }; } /** * @return array */ protected function serializeList(Activity $a): array { return [ 'id' => $a->id, 'signin_code' => ActivitySigninCodeSync::activitySigninCode($a), 'title' => $a->title, 'activity_type_dict_item_id' => $a->activity_type_dict_item_id, 'activity_type_item' => $this->serializeDictItem($a->activityTypeItem), 'quota' => (int) $a->quota, 'event_start_date' => $a->event_start_date?->toDateString(), 'event_end_date' => $a->event_end_date?->toDateString(), 'signup_start_date' => $a->signup_start_date?->toDateString(), 'signup_end_date' => $a->signup_end_date?->toDateString(), 'location' => $a->location, 'progress_status' => ScheduleProgressStatus::resolve( $a->event_start_date?->toDateString(), $a->event_end_date?->toDateString(), $a->signup_start_date?->toDateString(), $a->signup_end_date?->toDateString(), ), 'published' => (int) $a->published, 'sessions_count' => (int) ($a->sessions_count ?? 0), 'signups_count' => (int) ($a->signups_count ?? 0), 'created_at' => $a->created_at?->toIso8601String(), ]; } /** * @return array */ protected function serializeDetail(Activity $a): array { $row = $this->serializeList($a); $row['intro_html'] = $a->intro_html; $row['remark'] = $a->remark; return $row; } /** * @return array{id:int,label:string,value:string}|null */ protected function serializeDictItem(?DictItem $item): ?array { if (! $item) { return null; } return [ 'id' => $item->id, 'label' => $item->label, 'value' => $item->value, ]; } }