You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
5.9 KiB

<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Models\AudienceRegistration;
use App\Models\Competition;
use App\Support\BizDateTime;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Writer\XLSX\Writer;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
class AudienceRegistrationController extends Controller
{
public function index(Request $request, Competition $competition): JsonResponse
{
$filters = $this->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<string, mixed>
*/
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<string, mixed> $filters
* @return Builder<AudienceRegistration>
*/
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<string, mixed>
*/
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,
];
}
}