feat: 主题内容注入点系统(10 个标准位置,同位置多条注入块可排序/启停,stylevar 变量复活)+ 后台主题注入管理页(Tab 分区)+ 缓存/设置收进顶部用户下拉菜单(Livewire action 即时通知)

This commit is contained in:
ak
2026-08-11 23:20:57 +08:00
parent e91675e172
commit 3e3b9a2bfd
98 changed files with 485 additions and 105 deletions
-64
View File
@@ -1,64 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Blog\Support\PageCacheMiddleware;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache;
class CacheManager extends Page
{
protected static \UnitEnum|string|null $navigationGroup = '管理';
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-bolt';
protected static ?string $navigationLabel = '缓存管理';
protected string $view = 'filament.pages.cache-manager';
public function flushPages(): void
{
PageCacheMiddleware::flush();
Notification::make()->title('前台页面缓存已清空')->success()->send();
}
public function flushSidebar(): void
{
foreach (['blog.sidebar.categories', 'blog.sidebar.recent_posts', 'blog.sidebar.recent_comments', 'blog.sidebar.hot_tags', 'blog.sidebar.links', 'blog.sidebar.stats'] as $key) {
Cache::forget($key);
}
Notification::make()->title('侧边栏缓存已清空')->success()->send();
}
public function flushSettings(): void
{
\App\Models\Setting::flushCache();
Cache::forget('attach.legacy_ids');
Notification::make()->title('设置缓存已清空')->success()->send();
}
public function flushAll(): void
{
PageCacheMiddleware::flush();
\App\Models\Setting::flushCache();
Cache::flush();
Notification::make()->title('全部缓存已清空')->success()->send();
}
public function recompile(): void
{
Artisan::call('view:clear');
Artisan::call('route:clear');
Artisan::call('config:clear');
Notification::make()->title('视图/路由/配置缓存已重建')->success()->send();
}
public function getPageCacheCountProperty(): int
{
return PageCacheMiddleware::count();
}
}
+142
View File
@@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Blog\Support\ThemeInjector;
use App\Blog\Support\ThemeManager;
use App\Models\Setting;
use Filament\Actions\Action;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Schema;
class ThemeInject extends Page
{
use InteractsWithForms;
protected static \UnitEnum|string|null $navigationGroup = '管理';
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-squares-plus';
protected static ?string $navigationLabel = '主题注入';
protected string $view = 'filament.pages.theme-inject';
public array $data = [];
public function mount(): void
{
$this->form->fill([
'inject' => $this->normalizeBlocks(Setting::get('inject.blocks', [])),
'theme_vars' => app(ThemeManager::class)->variables(),
]);
}
public function form(Schema $schema): Schema
{
$tabs = [];
$i = 0;
foreach (ThemeInjector::POINTS as $point => $label) {
$tabs[] = Tab::make(($i + 1).'. '.$label)
->schema([
Repeater::make("inject.{$point}")
->label('注入块(可多条,按排序渲染)')
->schema([
TextInput::make('title')->label('名称')->placeholder('如:百度统计')->columnSpan(2),
Textarea::make('html')->label('内容(HTML')->rows(3)->columnSpanFull(),
TextInput::make('sort')->label('排序')->numeric()->default(0)->columnSpan(1),
Toggle::make('enabled')->label('启用')->default(true)->columnSpan(1),
])
->columns(3)
->defaultItems(0)
->collapsible()
->addActionLabel('添加一条'),
]);
$i++;
}
$tabs[] = Tab::make('主题变量(stylevar')
->schema([
KeyValue::make('theme_vars')
->label('当前主题变量:视图用 {{ theme(\'var\', \'key\') }} 引用')
->keyLabel('变量名')
->valueLabel('内容(HTML')
->reorderable()
->columnSpanFull(),
]);
return $schema
->components([
Tabs::make('inject-tabs')
->tabs($tabs),
])
->statePath('data');
}
public function save(): void
{
$data = $this->form->getState();
$blocks = [];
foreach ($data['inject'] ?? [] as $point => $items) {
foreach ($items ?? [] as $item) {
if (trim((string) ($item['html'] ?? '')) === '') {
continue;
}
$blocks[] = [
'point' => $point,
'title' => $item['title'] ?? '',
'html' => $item['html'],
'sort' => (int) ($item['sort'] ?? 0),
'enabled' => (bool) ($item['enabled'] ?? true),
];
}
}
Setting::set('inject.blocks', $blocks);
Setting::set('theme_vars_'.app(ThemeManager::class)->active(), $data['theme_vars'] ?? []);
Notification::make()->title('注入内容已保存')->success()->send();
}
protected function getFormActions(): array
{
return [
Action::make('save')->label('保存')->submit('save'),
];
}
private function normalizeBlocks(mixed $blocks): array
{
if (! is_array($blocks)) {
$blocks = json_decode((string) $blocks, true) ?: [];
}
$normalized = [];
foreach ($blocks as $b) {
$point = $b['point'] ?? null;
if (! $point) {
continue;
}
$normalized[$point][] = [
'title' => $b['title'] ?? '',
'html' => $b['html'] ?? '',
'sort' => (int) ($b['sort'] ?? 0),
'enabled' => (bool) ($b['enabled'] ?? true),
];
}
return $normalized;
}
}