getHost(); $configHost = parse_url(admin_setting('app_url'), PHP_URL_HOST); if ($requestHost !== $configHost) { abort(403); } } $theme = admin_setting('frontend_theme', 'Xboard'); $themeService = new ThemeService(); try { if (!$themeService->exists($theme)) { if ($theme !== 'Xboard') { Log::warning('Theme not found, switching to default theme', ['theme' => $theme]); $theme = 'Xboard'; admin_setting(['frontend_theme' => $theme]); } $themeService->switch($theme); } if (!$themeService->getThemeViewPath($theme)) { throw new Exception('主题视图文件不存在'); } $publicThemePath = public_path('theme/' . $theme); if (!File::exists($publicThemePath)) { $themePath = $themeService->getThemePath($theme); if (!$themePath || !File::copyDirectory($themePath, $publicThemePath)) { throw new Exception('主题初始化失败'); } Log::info('Theme initialized in public directory', ['theme' => $theme]); } $renderParams = [ 'title' => config('app.name'), 'theme' => $theme, 'version' => app(UpdateService::class)->getCurrentVersion(), 'description' => admin_setting('app_description', 'Xboard is best'), 'logo' => admin_setting('logo'), 'theme_config' => $themeService->getConfig($theme) ]; // Supply only the public application name; environment credentials stay server-side. $brand = json_encode((string) config('app.name'), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); $fullBrand = json_encode((string) config('app.full_name'), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); $brandScript = ''; $html = view('theme::' . $theme . '.dashboard', $renderParams)->render(); if ($theme === 'Stellar') { $html = \App\Support\StellarDevAssets::apply($request, $html); } return response(str_replace('', $brandScript . '', $html)); } catch (Exception $e) { Log::error('Theme rendering failed', [ 'theme' => $theme, 'error' => $e->getMessage() ]); abort(500, '主题加载失败'); } }; // Tutorials are independent pages, with their brand supplied from application configuration. Route::get('/tutorials/{platform}/{client}.html', function (string $platform, string $client) { $view = 'tutorials.' . $platform . '.' . $client; abort_unless(view()->exists($view), 404); return view($view, ['brand' => (string) config('app.name')]); })->where(['platform' => '[a-z0-9-]+', 'client' => '[a-z0-9-]+']); Route::get('/', $renderFrontend); //TODO:: 兼容 Route::get('/' . admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key')))), function () { return view('admin', [ 'title' => config('app.name'), 'theme_sidebar' => admin_setting('frontend_theme_sidebar', 'light'), 'theme_header' => admin_setting('frontend_theme_header', 'dark'), 'theme_color' => admin_setting('frontend_theme_color', 'default'), 'background_url' => admin_setting('frontend_background_url'), 'version' => app(UpdateService::class)->getCurrentVersion(), 'logo' => admin_setting('logo'), 'secure_path' => admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key')))) ]); }); Route::get('/' . (admin_setting('subscribe_path', 's')) . '/{token}', [\App\Http\Controllers\V1\Client\ClientController::class, 'subscribe']) ->middleware('client') ->name('client.subscribe'); // Stellar uses Vue history routes; unmatched API and asset requests remain 404s. Route::fallback(function (Request $request) use ($renderFrontend) { if ( admin_setting('frontend_theme', 'Xboard') !== 'Stellar' || $request->is('api', 'api/*', 'theme', 'theme/*', 'assets', 'assets/*') || pathinfo($request->path(), PATHINFO_EXTENSION) !== '' ) { abort(404); } return $renderFrontend($request); });