exists($active) ? $active : config('themes.default'); } public function exists(string $theme): bool { return is_dir($this->path($theme)) && File::exists($this->path($theme).'/theme.json'); } public function manifest(string $theme): array { $file = $this->path($theme).'/theme.json'; if (! File::exists($file)) { return []; } return json_decode(File::get($file), true) ?? []; } /** * 扫描主题目录,返回所有主题 manifest。 */ public function all(): array { $themes = []; foreach (File::directories(config('themes.path')) as $dir) { $name = basename($dir); if (! File::exists($dir.'/theme.json')) { continue; } $manifest = $this->manifest($name); $themes[$name] = [ 'name' => $name, 'title' => $manifest['title'] ?? $name, 'version' => $manifest['version'] ?? '0.0.0', 'description' => $manifest['description'] ?? '', 'author' => $manifest['author'] ?? '', 'screenshot' => $manifest['screenshot'] ?? null, 'inject_points' => $manifest['inject_points'] ?? null, 'active' => $name === $this->active(), ]; } return $themes; } public function activate(string $theme): void { if (! $this->exists($theme)) { throw new \InvalidArgumentException("主题不存在: {$theme}"); } Setting::set('active_theme', $theme); $this->prependViews(); } /** * 把激活主题的 views 目录前置到全局视图路径。 * 在 Provider boot 与运行时切换主题时调用。 */ public function prependViews(): void { // 迁移尚未执行(migrate:fresh 首轮)时跳过 DB 查询,使用默认主题 $active = \Illuminate\Support\Facades\Schema::hasTable('settings') ? $this->active() : config('themes.default'); $views = $this->path($active).'/views'; if (! is_dir($views)) { return; } $paths = config('view.paths'); array_unshift($paths, $views); config(['view.paths' => $paths]); app('view')->getFinder()->setPaths($paths); } /** * 解析主题下的一个视图名。 * 优先使用激活主题的视图,缺失时回退到内置兜底视图(blog-default)。 */ public function view(string $view): string { $active = $this->active(); if (view()->exists("theme.{$active}.{$view}")) { return "theme.{$active}.{$view}"; } return "blog-default::{$view}"; } public function assetUrl(string $path, ?string $theme = null): string { $theme = $theme ?? $this->active(); return url('/themes/'.$theme.'/assets/'.ltrim($path, '/')); } /** * 主题变量(原 sablog stylevars 的现代化替代:主题级自定义配置)。 */ public function variables(?string $theme = null): array { $theme = $theme ?? $this->active(); $vars = Setting::get('theme_vars_'.$theme, []); return is_array($vars) ? $vars : (json_decode((string) $vars, true) ?: []); } public function variable(string $key, mixed $default = null, ?string $theme = null): mixed { return $this->variables($theme)[$key] ?? $default; } }