feat: add admin env badge, quick post button, and harden plugin enable
Build and Deploy / verify (push) Failing after 15m17s
Build and Deploy / deploy (push) Skipped

Show local/prod in the Filament topbar, jump to create post in one click, tighten sidebar spacing, and stop Hello World from 500ing when its tables are missing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
gouki
2026-09-18 08:24:16 +00:00
co-authored by Cursor
parent b39423831c
commit 469160dc3f
19 changed files with 825 additions and 43 deletions
@@ -45,7 +45,11 @@ class PluginManagerServiceProvider extends ServiceProvider
$provider = $manager->resolveProvider($key);
if ($provider && class_exists($provider)) {
$provider::bootPlugin($manager);
try {
$provider::bootPlugin($manager);
} catch (\Throwable $e) {
report($e);
}
}
}
}
@@ -49,6 +49,7 @@ class PostContentRenderer
// 插件钩子:付费内容过滤、AI 处理等
$html = app(\App\Blog\Support\PluginManager::class)->applyFilters('post.rendered', $html, $post);
$html = is_string($html) ? $html : '';
// 兜底:任何情况下不把裸 [paid] 标签输出到页面(如会员插件被停用时)
return preg_replace('/\[(\/?paid)\]/i', '', $html);
+36 -2
View File
@@ -6,6 +6,7 @@ declare(strict_types=1);
namespace App\Blog\Support;
use App\Models\PluginRecord;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use RuntimeException;
@@ -155,6 +156,8 @@ class PluginManager
throw new RuntimeException('无法启用 '.$plugin.':'.implode(';', $problems));
}
$this->migratePlugin($plugin);
[$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin);
PluginRecord::query()->updateOrCreate(
['vendor' => $vendor, 'name' => $name],
@@ -162,6 +165,29 @@ class PluginManager
);
}
/**
* Run the plugin's own migrations so enabling a demo/third-party plugin
* does not 500 on missing tables.
*/
public function migratePlugin(string $plugin): void
{
$relativePath = 'plugins/'.$plugin.'/database/migrations';
if (! is_dir(base_path($relativePath))) {
return;
}
$exitCode = Artisan::call('migrate', [
'--path' => $relativePath,
'--force' => true,
'--no-interaction' => true,
]);
if ($exitCode !== 0) {
throw new RuntimeException('插件迁移失败:'.$plugin.'。'.trim(Artisan::output()));
}
}
public function disable(string $plugin): void
{
$dependents = $this->dependents($plugin);
@@ -243,7 +269,11 @@ class PluginManager
{
foreach ($this->actions[$hook] ?? [] as $callbacks) {
foreach ($callbacks as $callback) {
$callback(...$args);
try {
$callback(...$args);
} catch (\Throwable $e) {
report($e);
}
}
}
}
@@ -252,7 +282,11 @@ class PluginManager
{
foreach ($this->filters[$hook] ?? [] as $callbacks) {
foreach ($callbacks as $callback) {
$value = $callback($value, ...$args);
try {
$value = $callback($value, ...$args);
} catch (\Throwable $e) {
report($e);
}
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Filament\Support;
/**
* Admin top-right environment badge: local stays local, production shows as prod.
*/
class AdminEnvironment
{
public static function name(): string
{
$environment = app()->environment();
return match ($environment) {
'production', 'prod' => 'prod',
default => $environment,
};
}
public static function color(): string
{
return match (app()->environment()) {
'production', 'prod' => 'danger',
'local' => 'gray',
'staging' => 'warning',
default => 'info',
};
}
}
+15 -7
View File
@@ -62,13 +62,21 @@ class AppServiceProvider extends ServiceProvider
PanelsRenderHook::STYLES_AFTER,
fn () => Blade::render(<<<'HTML'
<style>
.fi-sidebar { padding-inline: 0.75rem; }
.fi-sidebar-nav { padding-inline: 0; }
.fi-sidebar-nav-groups { margin-inline: 0; }
.fi-sidebar-nav .fi-sidebar-item-button { padding-inline: 0.6rem; border-radius: 0.5rem; }
.fi-sidebar-nav .fi-sidebar-item-label { font-size: 0.82rem; }
.fi-sidebar-nav .fi-sidebar-group-label { font-size: 0.68rem; letter-spacing: 0.04em; }
.fi-sidebar-brand { padding-block: 0.9rem; }
.ll-topbar-tools { display: flex; align-items: center; gap: 0.45rem; margin-inline-end: 0.15rem; }
.ll-env-badge { text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; }
.fi-sidebar { padding-inline: 0.65rem; }
.fi-sidebar-nav { padding-inline: 0; padding-block: 0.4rem !important; gap: 0.35rem !important; }
.fi-sidebar-nav-groups { margin-inline: 0; gap: 0.35rem !important; }
.fi-sidebar-group { gap: 0.08rem !important; }
.fi-sidebar-group-items { gap: 0.04rem !important; }
.fi-sidebar-nav .fi-sidebar-item-btn,
.fi-sidebar-nav .fi-sidebar-item-button { padding-inline: 0.55rem; padding-block: 0.26rem !important; min-height: 0 !important; border-radius: 0.45rem; }
.fi-sidebar-nav .fi-sidebar-group-btn { padding-block: 0.12rem !important; padding-inline: 0.55rem; }
.fi-sidebar-nav .fi-sidebar-item-label { font-size: 0.82rem; line-height: 1.2; }
.fi-sidebar-nav .fi-sidebar-group-label { font-size: 0.66rem; letter-spacing: 0.04em; line-height: 1.2; }
.fi-sidebar-brand { padding-block: 0.5rem; }
.fi-sidebar-header { height: auto !important; min-height: 2.6rem; }
/* 表格行紧凑:数据行高度由 .fi-ta-text / .fi-ta-icon 的 padding-block 驱动;操作列按钮禁止换行堆叠 */
.fi-ta-text, .fi-ta-icon { padding-block: calc(var(--spacing) * 1.75) !important; }
@@ -21,6 +21,7 @@ use Filament\Pages\Dashboard;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Filament\View\PanelsRenderHook;
use Filament\Widgets\AccountWidget;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
@@ -75,6 +76,10 @@ class AdminPanelProvider extends PanelProvider
->icon('heroicon-o-squares-plus')
->url(fn (): string => \App\Filament\Pages\ThemeInject::getUrl()),
])
->renderHook(
PanelsRenderHook::USER_MENU_BEFORE,
fn (): \Illuminate\Contracts\View\View => view('filament.hooks.topbar-tools')
)
->navigationGroups([
NavigationGroup::make('内容'),
NavigationGroup::make('管理'),