mylaraveltools/panel
Composer 安装命令:
composer require mylaraveltools/panel
包简介
Panel — declarative monochrome admin panel for Laravel with Livewire and Tailwind
README 文档
README
Panel de administración declarativo y monocromático para Laravel. API de Resources al estilo Filament/Nova, con Livewire 3, Tailwind CSS, auth integrada, permisos Spatie, import/export y navegación SPA.
Parte del ecosistema My Laravel Tools (junto con mylaraveltools/alertas).
composer require mylaraveltools/panel
Migración: si usabas
mylaraveltools/minimalist, sustituye pormylaraveltools/panel. El namespace PHP sigue siendoMyLaravelTools\Panel— no cambia tu código.
Tabla de contenidos
- Requisitos
- Instalación paso a paso
- Tu primer CRUD en 5 minutos
- Configuración (
config/panel.php) - Autenticación y perfil
- Permisos (Spatie) y suplantación
- Navegación y páginas custom
- Importar y exportar datos
- Dashboard y widgets
- Relaciones entre modelos
- Tema, layout y SPA
- Comandos Artisan
- Personalizar vistas
- Solución de problemas
- Proyecto demo
- Desarrollo y tests
- Roadmap
- Licencia
Requisitos
| Requisito | Versión |
|---|---|
| PHP | 8.2+ |
| Laravel | 11, 12 o 13 |
| Livewire | 3.5+ |
| Tailwind CSS | 3+ en la app host |
Opcionales: spatie/laravel-permission, mylaraveltools/alertas.
Instalación paso a paso
Paso 1 — Composer
composer require mylaraveltools/panel
Desarrollo local (path repository):
{
"repositories": [
{ "type": "path", "url": "../minimalist-panel-library" }
],
"require": {
"mylaraveltools/panel": "@dev"
}
}
Paso 2 — Instalar el panel
php artisan panel:install
Esto publica config/panel.php, registra rutas en /admin y prepara Livewire.
Paso 3 — Tailwind
En tailwind.config.js incluye las vistas del paquete y activa modo oscuro por clase:
export default { darkMode: 'class', content: [ './resources/views/**/*.blade.php', './vendor/mylaraveltools/panel/resources/views/**/*.blade.php', ], };
Paso 4 — Alpine + Livewire
En resources/js/app.js, no importes Alpine en rutas del panel (/admin/*). Livewire lo gestiona:
const panelPath = '/admin'; if (!window.location.pathname.startsWith(panelPath)) { import('alpinejs').then(({ default: Alpine }) => { window.Alpine = Alpine; Alpine.start(); }); }
Importar Alpine en
/admin/loginrompewire:submit(el formulario no envía nada).
Paso 5 — Compilar y probar
npm run build php artisan serve
Abre http://127.0.0.1:8000/admin — verás login/registro si auth.enabled es true.
APP_URL debe coincidir con host y puerto (http://127.0.0.1:8000).
Tu primer CRUD en 5 minutos
1. Crear el Resource
php artisan panel:make-resource Product --model=Product
2. Definir formulario y tabla
// app/Panel/Resources/ProductResource.php final class ProductResource extends Resource { protected static string $model = Product::class; protected static ?string $label = 'Productos'; protected static ?string $icon = 'package'; public static function form(): array { return [ TextField::make('name')->label('Nombre')->required(), NumberField::make('price')->label('Precio')->min(0), ]; } public static function table(): array { return [ TextColumn::make('name')->label('Nombre')->searchable()->sortable(), TextColumn::make('price')->label('Precio')->sortable(), ]; } }
3. Listo
Auto-discovery en app/Panel/Resources/ (configurable). URL: /admin/resources/products (slug = kebab del modelo; puedes fijarlo con protected static ?string $slug = 'productos';).
Configuración (config/panel.php)
Toda la configuración vive en este archivo (compatible con config:cache). No hace falta .env, aunque puedes usarlo si prefieres.
| Clave | Descripción | Default |
|---|---|---|
path |
Prefijo URL | admin |
guard |
Guard de auth | web |
brand.name |
Nombre en sidebar | Panel |
brand.logo |
URL del logo (null = icono) |
null |
per_page |
Registros por página | 15 |
forms_in_modal |
Crear/editar en modal | true |
discovery |
Auto-discovery Resources | enabled |
pages |
Auto-discovery Pages | enabled |
permissions |
Spatie/Gate | disabled |
navigation |
Menú lateral (null = auto) |
null |
widgets |
Dashboard | [] |
import |
Import CSV/Excel | enabled + preview |
impersonation |
Suplantar usuarios | disabled |
theme.preset |
Preset visual (minimal, corporate, contrast, ocean) |
minimal |
extensions |
Vistas custom de campos/columnas y widgets | [] |
version |
Texto en sidebar (null = paquete) |
null |
Tema monocromático
'theme' => [ 'default' => 'dark', 'font' => 'Plus Jakarta Sans', 'colors' => [ 'primary' => '#000000', 'primary_dark' => '#ffffff', 'accent' => '#525252', 'success' => '#16a34a', 'danger' => '#dc2626', 'warning' => '#ca8a04', ], 'light' => [ /* bg, surface, card, border, heading, text, muted… */ ], 'dark' => [ /* … */ ], ],
Variables CSS: --panel-primary, --panel-bg, etc. Toggle claro/oscuro en el footer del sidebar (persiste en localStorage).
Presets de tema
'theme' => [ 'preset' => 'corporate', // minimal | corporate | contrast | ocean 'colors' => [ 'primary' => '#ff0000', // sobrescribe solo el preset ], ],
Extensibilidad (campos, columnas, widgets)
Por config (config/panel.php → extensions):
'extensions' => [ 'field_views' => ['rating' => 'panel-custom.fields.rating'], 'column_views' => ['rating' => 'panel-custom.columns.rating'], 'widgets' => [], ],
Por código (AppServiceProvider::boot):
use MyLaravelTools\Panel\Facades\PanelExtensions; PanelExtensions::registrarVistaCampo('rating', 'panel-custom.fields.rating'); PanelExtensions::registrarWidget(StatWidget::make('Total', fn () => Model::count()));
Campo custom en un Resource:
CustomField::make('payload') ->type('json-editor') ->view('panel-custom.fields.json-editor') ->label('Datos JSON'),
Campos integrados nuevos: ColorField, DateTimeField, KeyValueField (pares clave/valor → JSON).
Actualizar vistas publicadas:
php artisan panel:upgrade-views --dry-run php artisan panel:upgrade-views --force
Layout y apariencia
'layout' => [ 'density' => 'compact', // comfortable | compact 'content_width' => 'boxed', // full | boxed 'sidebar_collapsible' => true, 'show_breadcrumbs' => true, 'footer_links' => [ ['label' => 'Ayuda', 'route' => 'panel.dashboard'], ['label' => 'Web', 'url' => 'https://ejemplo.com', 'external' => true], ], ], 'brand' => [ 'name' => 'Mi Panel', 'logo' => '/img/logo.svg', 'logo_height' => '2.5rem', 'favicon' => '/favicon.ico', 'tagline' => 'Gestiona tu negocio', ], 'auth_ui' => [ 'layout' => 'split', // centered | split 'image' => '/img/auth-side.jpg', 'background' => 'linear-gradient(135deg, #0f172a, #1e3a5f)', 'show_tagline' => true, ], 'customization' => [ 'css' => '.panel-sidebar { border-right-width: 2px; }', 'head_view' => 'panel-custom.head', ],
RepeaterField — filas con varias columnas (JSON):
RepeaterField::make('lineas') ->columns(['concepto' => 'Concepto', 'importe' => 'Importe']) ->minRows(1) ->maxRows(10),
Máxima personalización (v0.24)
Modos de layout — sidebar (por defecto), topbar o dual (barra superior + lateral):
'layout' => [ 'mode' => 'sidebar', // sidebar | topbar | dual 'sidebar_position' => 'left', // left | right 'table_striped' => true, 'table_compact' => false, 'global_search' => true, 'per_page_options' => [15, 25, 50, 100], ],
Slots Blade — inyecta vistas en puntos del layout sin publicar todo el paquete:
'slots' => [ 'sidebar.before' => 'mi-app.panel.slots.aviso', 'main.after' => 'mi-app.panel.slots.analytics', 'topbar.end' => 'mi-app.panel.slots.acciones', ],
Slots disponibles: sidebar.before, sidebar.after, main.before, main.after, topbar.start, topbar.end, footer.before.
También vía código en AppServiceProvider:
use MyLaravelTools\Panel\Support\PanelExtensions; PanelExtensions::registrarSlot('main.before', 'mi-app.panel.banner');
Import upsert — actualiza registros existentes en lugar de fallar:
'import' => [ 'upsert' => true, 'upsert_key' => 'email', // global; el Resource puede sobreescribir ],
// En tu Resource public static function importUpsertKey(): ?string { return 'sku'; }
Hooks en Resources:
public static function navigationBadge(): ?string { return (string) static::model()::where('is_active', false)->count(); } public static function hiddenFromNavigation(): bool { return ! auth()->user()?->can('view settings'); } public static function perPageOptions(): array { return [10, 25, 50]; }
Presets de tema propios — archivo PHP que devuelve un array de presets:
'theme' => [ 'preset' => 'mi-marca', 'presets_file' => config_path('panel-theme-presets.php'), ],
Instalación con demo:
php artisan panel:install --demo
Autenticación y perfil
Auth integrada en /admin/login y /admin/register (tabla users de Laravel):
'auth' => [ 'enabled' => true, 'register' => true, 'register_role' => 'viewer', // Spatie, opcional 'password_reset' => true, 'email_verification' => false, // requiere MustVerifyEmail ],
- Auth externa (Breeze/Fortify):
'enabled' => false,'login_route' => 'login'. - Tras login: recarga completa al dashboard (sin loader SPA). Botón «Entrando» con puntos animados solo durante el POST.
- Recuperar contraseña:
/admin/forgot-password. - Perfil:
/admin/profile—'profile.enabled' => true.
Permisos (Spatie) y suplantación
Spatie Laravel Permission
composer require spatie/laravel-permission
'permissions' => [ 'enabled' => true, 'panel_access' => 'access panel', 'resources' => true, // RoleResource + PermissionResource 'manage_permission' => 'manage users', ],
RolesField/RolesColumnen usuarios.PermissionsField/PermissionsColumnen roles.- En Pages:
protected static ?string $permission = 'view reports'. - Policies:
php artisan panel:make-policy Product→ extiendeResourcePolicy(deny-by-default).
Suplantación de usuario
Navega el panel como otro usuario (permisos, menú y policies reales):
'impersonation' => [ 'enabled' => true, 'permission' => 'impersonate users', 'exclude_ids' => [], 'banner' => true, ],
- En el resource del modelo
User, menú ⋮ → Entrar como. - Aparece una tarjeta en el sidebar (encima del perfil) con botón Salir.
- Requiere permiso Spatie/Gate. No puedes suplantarte a ti mismo.
Navegación y páginas custom
Menú con grupos
// config/panel.php 'navigation' => require __DIR__.'/panel-navigation.php',
return [ ['resource' => ProductResource::class], ['page' => SettingsPage::class], [ 'type' => 'group', 'label' => 'Catálogo', 'icon' => 'package', 'children' => [ ['resource' => ProductResource::class], ['resource' => CategoryResource::class], ], ], ];
- Búsqueda global: Cmd/Ctrl+K.
- No uses
route()al cargar el config; usa la clave'route' => 'panel.dashboard'.
Páginas custom (no CRUD)
php artisan panel:make-page Settings
final class SettingsPage extends Page { protected static ?string $label = 'General'; protected static ?string $slug = 'settings-general'; public static function view(): string { return 'panel.pages.settings-general'; } }
Ruta: /admin/pages/{slug}. Vista con <x-panel::page-header>.
Importar y exportar datos
Export
Botones CSV, XLSX y PDF en listados. Con filas seleccionadas, exporta solo la selección.
Import (con vista previa)
'import' => [ 'enabled' => true, 'preview' => true, 'upsert' => true, 'upsert_key' => null, ],
- Botón Importar en el listado (permiso
create). - Sube
.csv,.txt,.xlsx,.xls. - Revisa filas válidas/errores → confirma.
- Con
upsertactivo: crea nuevos y actualiza existentes segúnimportUpsertKey()del Resource.
Personaliza columnas con Field::importable(false) o Resource::import().
Dashboard y widgets
'widgets' => [ ResourceCountWidget::make(ProductResource::class), StatWidget::make('Activos', fn () => Product::where('is_active', true)->count()) ->icon('check-circle'), ChartWidget::make('Ventas', 'bar', fn () => [ 'labels' => ['Ene', 'Feb'], 'values' => [12, 19], ])->themeColors(), ViewWidget::make('Custom', 'panel.widgets.mi-vista', fn () => ['total' => 100]) ->columnSpan(2), ],
Tipos ChartWidget: bar, line, pie, doughnut, progression. Gráficos reactivos al tema y SPA.
Relaciones entre modelos
Desde la vista Ver del registro padre:
public static function relations(): array { return [ RelationManager::make('products', ProductResource::class), RelationManager::hasOne('profile', ProfileResource::class), RelationManager::belongsToMany('tags', TagResource::class), RelationManager::morphMany('reviews', ReviewResource::class), RelationManager::morphToMany('tags', TagResource::class), ]; }
Tema, layout y SPA
- Sin header global — cada vista usa
<x-panel::page-header>(título + breadcrumbs). - Sidebar footer: perfil, idioma, tema, versión, logout.
- SPA:
wire:navigate, loader con porcentaje0%–100%, sidebar persistente. - Livewire: mantén
navigate.show_progress_bar => trueenconfig/livewire.php(la barra NProgress se oculta vía CSS del panel).
Fields y Columns
Fields: TextField, EmailField, PasswordField, TextareaField, BooleanField, SelectField, BelongsToField, NumberField, DateField, DateTimeField, ColorField, KeyValueField, CustomField, FileField, ImageField, RichTextField, RolesField, PermissionsField.
Columns: TextColumn, BooleanColumn, DateTimeColumn, BadgeColumn, ColorColumn, BelongsToColumn, ImageColumn, RolesColumn, PermissionsColumn.
Filtros: SelectFilter, BooleanFilter, DateRangeFilter, MultiSelectFilter.
Formularios: Section::make(), Tab::make(), soft deletes, bulk actions, RowAction.
Comandos Artisan
| Comando | Descripción |
|---|---|
php artisan panel:install |
Instalar panel |
php artisan panel:install --demo |
Instalar + navigation stub y PostResource ejemplo |
php artisan panel:make-resource Name |
Crear Resource |
php artisan panel:make-page Name |
Crear página custom |
php artisan panel:make-policy Name |
Crear Policy |
php artisan panel:make-widget Name --type=chart |
Crear clase widget para el dashboard |
php artisan panel:doctor |
Diagnosticar instalación del panel |
php artisan panel:upgrade-views |
Actualizar vistas publicadas |
php artisan vendor:publish --tag=panel-config |
Publicar config |
php artisan vendor:publish --tag=panel-views |
Publicar vistas Blade |
php artisan vendor:publish --tag=panel-documentation |
Copiar documentation/panel/ al proyecto |
Documentación interactiva (playground)
Ruta pública /playground (sin login) — documentation.enabled y documentation.path:
- Panel FAKE a pantalla completa + controles laterales
- Catálogo de todas las opciones de
config/panel.php - Vista previa en vivo (layout, marca, tema…)
- Markdown:
documentation/panel/README.md
Personalizar vistas
Si publicas vistas en resources/views/vendor/panel/, sobreescriben las del paquete.
Tras actualizar el paquete:
php artisan vendor:publish --tag=panel-views --force php artisan view:clear
Si no publicas vistas, Laravel usa las del vendor directamente (recomendado hasta que edites Blade).
Solución de problemas
| Problema | Solución |
|---|---|
| Login no envía el formulario | No importes Alpine en rutas /admin/* |
Alpine is not defined |
show_progress_bar => true en config/livewire.php |
| Estilos rotos | Incluye vistas del paquete en tailwind.config.js y npm run build |
| Feature nueva no aparece | Republica vistas con --force o borra resources/views/vendor/panel/ |
404 en /admin/resources/users |
El slug por defecto es user (singular); define $slug = 'users' |
| «Entrar como» no visible | Permiso impersonate users + php artisan db:seed con ese permiso |
| Redirecciones raras en login | APP_URL con host y puerto correctos |
Proyecto demo
Carpeta panel-demo/ con catálogo, ventas, Spatie, gráficos, import y suplantación.
cd panel-demo composer install && npm install && npm run build php artisan migrate:fresh --seed php artisan serve
| Usuario | Password | |
|---|---|---|
| Admin | admin@panel.test |
password |
| Editor | editor@panel.test |
password |
Ver panel-demo/README.md para rutas y features de prueba.
Desarrollo y tests
cd minimalist-panel-library composer test
- Contexto para agentes/IA: AGENTS.md
- Publicar en Packagist: PUBLISHING.md
- Historial: CHANGELOG.md
Roadmap
- CRUD, SPA, Excel/PDF, búsqueda global, i18n, tests, CI
- RowAction, modales, skeletons, filtros avanzados
- Forms en modal, tabs, export PDF
- Policies, páginas custom, permisos Spatie
- Auth integrada, reset password, perfil
- Import con preview, ChartWidget, ViewWidget, email verify
- Auth UX (v0.20), suplantación de usuario (v0.21)
- Packagist —
mylaraveltools/panel - Extensibilidad — presets tema, PanelExtensions, campos custom (v0.22)
- Layout — densidad, boxed, sidebar colapsable, auth split, RepeaterField (v0.23)
- Máxima personalización — topbar/dual, slots, upsert, tablas, presets propios (v0.24)
- Playground público, gráficos interactivos,
panel:doctor,panel:make-widget(v0.25) - Layout móvil pulido —
mobile-bar, drawer, modos sidebar/topbar/dual (v0.26) - Multi-panel, starter kit completo
Licencia
MIT — Alberto Gallardo Morales
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-22