user(); $payload = [ 'id' => $user->id, 'mobile' => $user->mobile, 'name' => $user->name, ]; $slug = $request->query('competition_slug'); if (is_string($slug) && $slug !== '') { $competition = Competition::query() ->where('slug', $slug) ->where('published', true) ->first(); if ($competition) { $payload['signup_locale'] = SignupDisplay::audienceLocale((int) $user->id, (int) $competition->id); $payload['signup_locale_locked'] = AudienceRegistration::existsForUserCompetition( (int) $user->id, (int) $competition->id ); } } return response()->json($payload); } public function sessions(Request $request): JsonResponse { $competition = $this->publishedCompetition($request); /** @var User $user */ $user = $request->user(); $registrationsBySession = AudienceRegistration::query() ->where('user_id', $user->id) ->where('competition_id', $competition->id) ->get() ->keyBy(fn (AudienceRegistration $row) => (int) $row->audience_session_id); $sessions = $competition->audienceSessions() ->withCount('registrations') ->where('enabled', true) ->get() ->map(fn (AudienceSession $session) => $this->sessionPayload( $session, $registrationsBySession->get((int) $session->id) )); return response()->json(['data' => $sessions]); } public function storeRegistration(Request $request, AudienceRegistrationService $service): JsonResponse { $competition = $this->publishedCompetition($request); /** @var User $user */ $user = $request->user(); if ($request->exists('purpose')) { $request->merge([ 'purpose' => SignupOptionCatalog::normalize('purpose', (string) $request->input('purpose')), ]); } $purposes = SignupOptionCatalog::acceptedValues(SignupOptionCatalog::audiencePurposes(), 'purpose'); $competition->loadMissing('audienceFormSchema'); $schema = $competition->audienceFormSchema?->schema_json; $schemaKeys = SignupApplicationFieldRegistry::keysFromSchemaJson(is_array($schema) ? $schema : null); $companyRequired = $this->audienceSchemaFieldRequired(is_array($schema) ? $schema : null, 'company', true); $rules = [ 'audience_session_id' => ['required', 'integer', 'min:1'], 'name' => ['required', 'string', 'max:64'], 'company' => [$companyRequired ? 'required' : 'nullable', 'string', 'max:128'], 'job' => ['nullable', 'string', 'max:128'], 'purpose' => ['required', 'string', Rule::in($purposes)], ]; foreach ($schemaKeys as $key) { if (isset($rules[$key]) || in_array($key, ['phone', 'audience_session_id'], true)) { continue; } $rules[$key] = ['nullable', 'string', 'max:5000']; } $data = $request->validate($rules, [], [ 'name' => '个人姓名', 'company' => '所属单位', 'job' => '个人职务', 'purpose' => '观赛目的', 'audience_session_id' => '赛事场次', ]); $mobile = trim((string) $user->mobile); if (! preg_match('/^1[3-9]\d{9}$/', $mobile)) { throw ValidationException::withMessages([ 'phone' => ['登录手机号无效,请重新登录'], ]); } $data['phone'] = $mobile; $columnKeys = ['audience_session_id', 'name', 'company', 'job', 'purpose', 'phone']; $answers = []; foreach ($data as $key => $value) { if (! in_array($key, $columnKeys, true)) { $answers[$key] = $value; } } $data['answers_json'] = $answers; $registration = $service->register($user, $competition, $data); $registration->load('session'); $hadPrior = AudienceRegistration::query() ->where('user_id', $user->id) ->where('competition_id', $competition->id) ->whereKeyNot($registration->id) ->exists(); if (! $hadPrior) { CompetitionPortalLocale::remember( (int) $user->id, (int) $competition->id, PortalLocale::PORTAL_AUDIENCE, PortalLocale::fromRequest( is_string($request->query('lang')) ? $request->query('lang') : (is_string($request->input('lang')) ? $request->input('lang') : null) ) ); } return response()->json($this->registrationPayload($registration, $user), 201); } public function registrations(Request $request): JsonResponse { $competition = $this->publishedCompetition($request); /** @var User $user */ $user = $request->user(); $rows = AudienceRegistration::query() ->with('session') ->where('user_id', $user->id) ->where('competition_id', $competition->id) ->orderByDesc('registered_at') ->orderByDesc('id') ->get() ->map(fn (AudienceRegistration $row) => $this->registrationPayload($row, $user)); return response()->json(['data' => $rows]); } /** * @return array */ private function sessionPayload(AudienceSession $session, ?AudienceRegistration $registration): array { $isRegistered = $registration !== null; $reviewStatus = $registration?->review_status; $status = $session->windowStatus(); $statusLabel = $isRegistered ? ($registration->isApproved() ? '已通过' : '待审核') : $session->statusLabel(); return [ 'id' => $session->id, 'code' => $session->code, 'name' => $session->name, 'name_en' => $session->name_en, 'event_date_text' => $session->event_date_text, 'event_date_text_en' => $session->event_date_text_en, 'address' => $session->address, 'address_en' => $session->address_en, 'capacity' => (int) $session->capacity, 'registered_count' => $session->registeredCount(), 'remaining' => $session->remainingCount(), 'is_final' => (bool) $session->is_final, 'signup_start_at' => BizDateTime::toIso8601($session->signup_start_at), 'signup_end_at' => BizDateTime::toIso8601($session->signup_end_at), 'status' => $status, 'status_label' => $statusLabel, 'is_registered' => $isRegistered, 'review_status' => $reviewStatus, 'is_available' => ! $isRegistered && $session->isAvailable(), ]; } /** * @return array */ private function registrationPayload(AudienceRegistration $row, User $user): array { $session = $row->session; $locale = SignupDisplay::audienceLocale((int) $user->id, (int) $row->competition_id); $payload = [ 'id' => $row->id, 'audience_session_id' => $row->audience_session_id, 'session_name' => $session ? SignupDisplay::sessionName($session, $locale) : null, 'session_name_en' => $session ? SignupDisplay::sessionName($session, PortalLocale::EN) : null, 'event_date_text' => $session ? SignupDisplay::sessionDateText($session, $locale) : null, 'event_date_text_en' => $session ? SignupDisplay::sessionDateText($session, PortalLocale::EN) : null, 'address' => $session ? SignupDisplay::sessionAddress($session, $locale) : null, 'address_en' => $session ? SignupDisplay::sessionAddress($session, PortalLocale::EN) : null, 'name' => $row->name, 'phone' => $row->phone, 'company' => $row->company, 'job' => $row->job, 'purpose' => $row->purpose, 'purpose_label' => SignupDisplay::purposeLabel((string) $row->purpose, $locale), 'review_status' => $row->review_status ?: AudienceRegistration::STATUS_PENDING, 'approved_event_time_text' => $row->isApproved() ? $row->approved_event_time_text : null, 'approved_event_address_text' => $row->isApproved() ? $row->approved_event_address_text : null, 'ticket_code' => $row->isApproved() ? $row->ticket_code : null, 'registered_at' => BizDateTime::toIso8601($row->registered_at), 'checked_in_at' => BizDateTime::toIso8601($row->checked_in_at), 'signup_locale' => $locale, ]; $answers = is_array($row->answers_json) ? $row->answers_json : []; foreach ($answers as $key => $value) { if (! array_key_exists($key, $payload)) { $payload[$key] = $value; } } return $payload; } private function publishedCompetition(Request $request): Competition { $data = $request->validate([ 'competition_slug' => ['required', 'string', 'max:64'], ]); $competition = Competition::query() ->where('slug', $data['competition_slug']) ->where('published', true) ->first(); if ($competition === null) { throw ValidationException::withMessages([ 'competition_slug' => ['赛事不存在或未发布'], ]); } return $competition; } /** * @param list|null $schema */ private function audienceSchemaFieldRequired(?array $schema, string $key, bool $default): bool { if (! is_array($schema) || $schema === []) { return $default; } foreach ($schema as $row) { if (! is_array($row) || ($row['key'] ?? '') !== $key) { continue; } return (bool) ($row['required'] ?? false); } return false; } }