Pages personnalisées
Quand un écran ne rentre ni dans un Resource ni dans une Page (un calendrier, un assistant en plusieurs étapes, un éditeur visuel...), CustomPage l'enregistre dans la navigation du panneau et son contenu est un composant Vue que vous écrivez. Arrel vous donne le kit de composants de sa propre interface pour que l'écran ressemble au reste du panneau.
Une page personnalisée a trois pièces :
- L'entrée de navigation, en PHP (
CustomPage). - Le composant Vue, dans votre frontend.
- Les endpoints que le composant consomme, dans votre application.
L'entrée de navigation
Panel::make('admin')
->customPages([
CustomPage::make('pass-list', 'Pass list')
->icon('clipboard-document-check')
->group('Attendance')
->sort(10)
->visible(fn (): bool => auth()->user()->can('attendance.create'))
->badge(fn (): int => Absence::pending()->count()),
])CustomPage::make($slug, $label) réserve l'URL /{prefix}/{panel}/custom/{slug} et l'entrée de menu.
| Méthode | Description |
|---|---|
icon(string $icon) | Icône de la navigation. Voir la liste. |
group(string $group) | Groupe de la navigation. Les pages et les Resources qui partagent un nom de groupe s'affichent ensemble. |
sort(int $sort) | Position dans la navigation, de la plus petite à la plus grande. |
visible(Closure $callback) | S'il retourne faux, l'entrée n'apparaît pas. Évalué à chaque requête, par utilisateur. |
badge(Closure $count) | Un compteur affiché sur le lien. Il n'est affiché que s'il est supérieur à zéro. |
visible() ne masque que le lien. Les endpoints qui alimentent la page doivent faire leur propre autorisation (voir Vos propres endpoints).
Le composant
Une page s'enregistre depuis JavaScript, avec un script que vous chargez dans le panneau avec Panel::scripts(). Il y a deux façons de le faire. La recommandée est registerMountablePage.
registerMountablePage : votre propre application Vue
Arrel vous donne un conteneur vide et vous y montez votre propre application Vue, avec votre build, vos dépendances et votre version de Vue :
import '@arrel/ui.css'
import { createApp, type App } from 'vue'
import PassListPage from './PassListPage.vue'
const mounted = new WeakMap<HTMLElement, App>()
window.Arrel.registerMountablePage('pass-list', {
mount(container, props) {
const app = createApp(PassListPage, props)
app.mount(container)
mounted.set(container, app)
},
unmount(container) {
mounted.get(container)?.unmount()
mounted.delete(container)
},
})props contient panel (l'identifiant du panneau) et slug (celui de la page). Arrel ne réconcilie jamais les vnodes de votre composant, il n'y a donc aucun risque d'avoir deux instances de Vue mélangées.
registerPage : composants dans l'arbre d'Arrel
registerPage monte le composant dans l'arbre d'Arrel, avec son instance de Vue. C'est la version runtime only, sans compilateur de templates : il faut h() ou des composants précompilés.
const { h } = window.Arrel.Vue
window.Arrel.registerPage('about', {
render() {
return h('div', [h('h1', 'About')])
},
})window.Arrel.Vue expose h, ref, reactive, computed, watch, onMounted et onBeforeUnmount.
registerMountablePage | registerPage | |
|---|---|---|
| Instance de Vue | La vôtre, séparée | Celle d'Arrel |
| Templates | N'importe lesquels, avec votre build | Seulement h() ou composants précompilés |
| Cycle de vie | Vous le gérez (mount() et unmount()) | Arrel le gère |
L'API globale window.Arrel
| Membre | Description |
|---|---|
panel | L'identifiant du panneau actuel. |
prefix | Le préfixe configuré dans config('arrel.prefix'). |
url(path = '') | Construit une URL dans le panneau : Arrel.url('posts'). |
navigate(path) | Navigue dans la SPA sans recharger la page. Retourne une promesse. |
refreshNavigation() | Recharge la navigation, par exemple pour rafraîchir un badge() après une action. |
registerPage(slug, component) | Enregistre un composant Vue d'Arrel. |
registerMountablePage(slug, page) | Enregistre une page montable. |
Vue | Fonctions de Vue pour registerPage. |
Le kit de composants (@arrel/ui)
Le paquet publie le kit de composants de sa propre interface, pour que vos pages aient les mêmes boutons, champs et états. Rien n'est injecté dans la page : vous l'intégrez à votre build.
| Composant | Props principales |
|---|---|
Badge | color : gray, success, warning, danger ou info. |
Button | variant : primary, secondary ou danger. type, disabled, href, newTab. |
DatePicker | v-model, displayFormat, min, max, isDisabled(iso), clearable, invalid. |
Icon | name, size. |
Input | v-model, type, placeholder, invalid. |
Label | for. |
RichEditor | v-model, field, error. |
Select | v-model, options, nullable, placeholder, invalid. |
Textarea | v-model, rows, placeholder, invalid. |
Toggle | v-model. |
En plus des composants, il exporte requestJson, ApiError, formatDate et t.
requestJson<T>(url, init)fait unfetchavec la session et l'en-tête CSRF que Sanctum attend, et lève uneApiErrorsi la réponse n'est pas correcte. Pour une réponse 422,error.validationErrorscontient les erreurs de validation par champ.formatDate(value, format)formate une date avec la syntaxe dedate()de PHP.t(key)retourne un texte de l'interface, déjà traduit avecPanel::translations().
Le brancher à votre build
Le kit est distribué compilé dans dist/ui à l'intérieur du paquet, avec Vue comme dépendance externe (c'est le Vue de votre build). Définissez un alias dans Vite :
// vite.config.js
import path from 'node:path'
const arrel = path.resolve(__dirname, 'vendor/arrel/arrel')
export default defineConfig({
resolve: {
alias: {
'@arrel/ui.css': `${arrel}/dist/ui/arrel-ui.css`,
'@arrel/ui': `${arrel}/dist/ui/arrel-ui.js`,
},
},
})Et, pour TypeScript, le chemin vers les types :
{
"compilerOptions": {
"paths": {
"@arrel/ui": ["./vendor/arrel/arrel/dist/ui/types/ui.d.ts"]
}
}
}Importez @arrel/ui.css une seule fois à l'entrée de vos pages, pour que les composants portent les styles d'Arrel.
Vos propres endpoints
Les données d'une page personnalisée vivent dans votre application, pas dans Arrel. Pour qu'elles héritent de l'authentification, du second facteur et de l'identification du tenant, montez-les avec la même pile de middleware qu'Arrel :
Route::middleware([
...config('arrel.middleware'),
ForceJsonResponse::class,
EnsureFrontendRequestsAreStateful::class,
'web',
'auth:sanctum',
EnsureEmailIsVerified::class,
EnsureTwoFactorIsSetUp::class,
...config('arrel.authenticated_middleware'),
])->group(function (): void {
Route::get('/arrel-app/pass-list', PassListController::class);
});À l'intérieur, autorisez chaque requête avec vos Policies ou avec Gate. Le fait que visible() masque l'entrée n'empêche pas quelqu'un d'appeler l'endpoint à la main.
Une fois qu'une action de la page modifie un compteur, appelez window.Arrel.refreshNavigation() pour que le badge() se rafraîchisse.