validatedFilters($request); $query = $this->buildQuery($competition, $filters); $perPage = min((int) ($filters['per_page'] ?? 15), 100); $paginator = $query->paginate($perPage); $paginator->getCollection()->transform(fn (AudienceRegistration $row) => $this->toArray($row)); return response()->json($paginator); } public function export(Request $request, Competition $competition): BinaryFileResponse { $filters = $this->validatedFilters($request); $rows = $this->buildQuery($competition, $filters)->get(); $tmp = tempnam(sys_get_temp_dir(), 'audience_export_'); if ($tmp === false) { abort(Response::HTTP_INTERNAL_SERVER_ERROR, '无法创建导出文件'); } $xlsxPath = $tmp.'.xlsx'; @unlink($tmp); $writer = new Writer; $writer->openToFile($xlsxPath); $writer->addRow(Row::fromValues([ '场次', '姓名', '电话', '单位', '职务', '观赛目的', '报名时间', '核销状态', '到场时间', ])); foreach ($rows as $row) { $writer->addRow(Row::fromValues([ $row->session?->name ?? '', $row->name, $row->phone, $row->company ?? '', $row->job ?? '', $row->purpose, $row->registered_at ? $row->registered_at->copy()->timezone(BizDateTime::TIMEZONE)->format('Y-m-d H:i') : '', $row->checked_in_at ? '已到场' : '未到场', $row->checked_in_at ? $row->checked_in_at->copy()->timezone(BizDateTime::TIMEZONE)->format('Y-m-d H:i') : '', ])); } $writer->close(); $filename = '观众报名_'.$competition->slug.'_'.now()->timezone(BizDateTime::TIMEZONE)->format('Ymd_His').'.xlsx'; return response()->download($xlsxPath, $filename, [ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ])->deleteFileAfterSend(true); } /** * @return array */ private function validatedFilters(Request $request): array { return $request->validate([ 'page' => ['sometimes', 'integer', 'min:1'], 'per_page' => ['sometimes', 'integer', 'min:1', 'max:100'], 'audience_session_id' => ['sometimes', 'nullable', 'integer', 'min:1'], 'phone' => ['sometimes', 'nullable', 'string', 'max:20'], 'purpose' => ['sometimes', 'nullable', 'string', Rule::in(array_merge(AudienceRegistration::PURPOSES, ['']))], 'keyword' => ['sometimes', 'nullable', 'string', 'max:64'], 'checkin_status' => ['sometimes', 'nullable', 'string', Rule::in(['checked_in', 'not_checked_in', ''])], ]); } /** * @param array $filters * @return Builder */ private function buildQuery(Competition $competition, array $filters): Builder { $query = AudienceRegistration::query() ->with('session') ->where('competition_id', $competition->id) ->orderByDesc('registered_at') ->orderByDesc('id'); $sessionId = (int) ($filters['audience_session_id'] ?? 0); if ($sessionId > 0) { $query->where('audience_session_id', $sessionId); } $phone = trim((string) ($filters['phone'] ?? '')); if ($phone !== '') { $query->where('phone', 'like', '%'.$phone.'%'); } $purpose = trim((string) ($filters['purpose'] ?? '')); if ($purpose !== '') { $query->where('purpose', $purpose); } $keyword = trim((string) ($filters['keyword'] ?? '')); if ($keyword !== '') { $query->where(function (Builder $q) use ($keyword) { $q->where('name', 'like', '%'.$keyword.'%') ->orWhere('phone', 'like', '%'.$keyword.'%') ->orWhere('company', 'like', '%'.$keyword.'%'); }); } $checkinStatus = trim((string) ($filters['checkin_status'] ?? '')); if ($checkinStatus === 'checked_in') { $query->whereNotNull('checked_in_at'); } elseif ($checkinStatus === 'not_checked_in') { $query->whereNull('checked_in_at'); } return $query; } /** * @return array */ private function toArray(AudienceRegistration $row): array { return [ 'id' => $row->id, 'competition_id' => $row->competition_id, 'audience_session_id' => $row->audience_session_id, 'session_name' => $row->session?->name, 'session_code' => $row->session?->code, 'name' => $row->name, 'phone' => $row->phone, 'company' => $row->company, 'job' => $row->job, 'purpose' => $row->purpose, 'registered_at' => BizDateTime::toIso8601($row->registered_at), 'checked_in_at' => BizDateTime::toIso8601($row->checked_in_at), 'checked_in' => $row->checked_in_at !== null, ]; } }