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
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,
|
|
];
|
|
}
|
|
}
|