query('src', '')); $publicSourceCode = null; if (preg_match('/^[A-Za-z0-9_-]{2,64}$/', $sourceCode) === 1) { try { $publicSourceCode = PublicSourceChannel::query() ->where('source_code', $sourceCode) ->where('status', PublicSourceChannel::STATUS_ENABLED) ->value('source_code'); } catch (Throwable) { $publicSourceCode = null; } } return view('welcome', [ 'publicSourceCode' => $publicSourceCode, 'eventSiteRelative' => $eventSiteRelative, ]); }; Route::get('/', fn () => $showEventSite()); Route::get('/en', fn () => $showEventSite('frontend/event-site-en.html')); Route::get('/channel-entry', ChannelEntryController::class)->name('channel.entry'); /** * 选手端附件预览:签名临时 URL;跳过 session/CSRF,保留 SubstituteBindings 以解析 {file} 模型。 */ Route::get('/participant/application-files/{file}', [ApplicationFileController::class, 'downloadSigned']) ->middleware('signed') ->withoutMiddleware([ EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, ShareErrorsFromSession::class, VerifyCsrfToken::class, ]) ->whereNumber('file') ->name('participant.application-file.download'); /** * 前端 SPA(public/admin)。 * php artisan serve 在未使用项目根 server.php 时,可能把 /admin/c/... 改写成 /c/...; * 因此同时注册 /admin/{path?} 与 /c/{path?} 回退,均返回同一 index.html。 */ $serveAdminSpa = function (?string $path = null) { if ($path !== null && $path !== '') { $candidate = public_path('admin/'.$path); if (File::isFile($candidate)) { return response()->file($candidate); } } $index = public_path('admin/index.html'); if (! File::exists($index)) { abort(503, 'Admin frontend not built. Run npm run build in the frontend project.'); } return response()->file($index, ['Content-Type' => 'text/html; charset=UTF-8']); }; Route::redirect('/admin', '/admin/', 301); Route::get('/admin/{path?}', $serveAdminSpa)->where('path', '.*')->name('admin.spa'); Route::get('/c/{path?}', $serveAdminSpa)->where('path', '.*')->name('admin.spa.c-fallback');