Серверные слоты
Серверные слоты позволяют управлять содержимым интерфейса из PHP. Бэкенд указывает какой компонент показать и с какими props, а фронтенд резолвит компонент по имени и рендерит его.
Регистрация на бэкенде
use Frame\Core\Enums\SlotLocation;
use Frame\Core\Frame;
use Frame\Core\Slot;
Frame::registerSlots([
SlotLocation::LOGIN_BEFORE_FORM->value => Slot::make('Banner', [
'text' => 'Плановые работы с 20:00 до 22:00',
'variant' => 'warning',
]),
SlotLocation::LOGIN_SIDEBAR->value => Slot::make('LoginSidebar'),
]);
Можно регистрировать несколько слотов для одного location:
Frame::registerSlots([
SlotLocation::LOGIN_BEFORE_FORM->value => [
Slot::make('Banner', ['text' => 'Первый баннер', 'variant' => 'info']),
Slot::make('Banner', ['text' => 'Второй баннер', 'variant' => 'warning']),
],
]);
Slot
Slot::make(string $component, ?array $props = [], mixed $content = null)
SlotComponentRegistry.Регистрация компонента на фронтенде
Бэкенд передаёт имя компонента как строку. Фронтенд должен зарегистрировать Vue-компонент под этим именем:
import { SlotComponentRegistry } from '@frame/core'
import Banner from './components/Banner.vue'
import LoginSidebar from './components/LoginSidebar.vue'
SlotComponentRegistry.register('Banner', Banner)
SlotComponentRegistry.register('LoginSidebar', LoginSidebar)
Методы SlotComponentRegistry
import { SlotComponentRegistry } from '@frame/core'
SlotComponentRegistry.register('Banner', BannerComponent)
SlotComponentRegistry.registerMany({
Banner: BannerComponent,
LoginSidebar: LoginSidebarComponent,
})
SlotComponentRegistry.get('Banner') // Component | undefined
SlotComponentRegistry.has('Banner') // boolean
SlotComponentRegistry.unregister('Banner')
SlotComponentRegistry.clear()
Как данные попадают на фронт
Серверные слоты доставляются двумя путями:
Авторизованные страницы
Слоты приходят в ответе GET /meta вместе с модулями и дашбордами:
{
"dashboards": [...],
"modules": [...],
"slots": {
"resource-page-actions": [
{ "component": "MyWidget", "props": { "title": "Hello" } }
]
}
}
Публичные страницы (login)
Для locations с isPublic() = true данные приходят через GET /slots — без авторизации:
{
"slots": {
"login-before-form": [
{ "component": "Banner", "props": { "text": "...", "variant": "warning" } }
]
}
}
AppSlotRenderer автоматически выбирает источник — если пользователь авторизован, берёт из /meta, иначе запрашивает /slots.
SlotLocation
Enum Frame\Core\Enums\SlotLocation определяет все доступные locations:
SlotLocation::LOGIN_BEFORE_FORM // публичный
SlotLocation::LOGIN_SIDEBAR // публичный
SlotLocation::RESOURCE_PAGE_ACTIONS // защищённый
Каждый case имеет метод isPublic():
SlotLocation::LOGIN_BEFORE_FORM->isPublic() // true
SlotLocation::RESOURCE_PAGE_ACTIONS->isPublic() // false
// Все публичные locations
SlotLocation::publicValues() // ['login-before-form', 'login-after-form', ...]
Пример: баннер на странице входа
Бэкенд:
Frame::registerSlots([
SlotLocation::LOGIN_BEFORE_FORM->value => Slot::make('Banner', [
'text' => 'Система обновлена до версии 2.0.<br>Подробности в документации.',
'variant' => 'info',
'showIcon' => true,
]),
]);
Фронтенд — регистрация:
SlotComponentRegistry.register('Banner', BannerComponent)
Фронтенд — компонент:
<template>
<div class="mb-4 rounded-lg px-4 py-3 text-sm" :class="variantClasses">
<span v-html="text" />
</div>
</template>
<script lang="ts" setup>
const props = withDefaults(defineProps<{
text: string
variant?: 'info' | 'warning' | 'success' | 'danger'
showIcon?: boolean
}>(), {
variant: 'info',
showIcon: true,
})
// ...стили по variant
</script>
Props передаются с бэкенда — text, variant, showIcon управляются из PHP.
Slot в формах ресурсов
Slot также можно использовать в массиве fields() ресурса — наравне с полями. На фронтенде он рендерится как произвольный компонент внутри формы:
use Frame\Core\Slot;
public function fields(): array
{
return [
ID::make('id'),
Text::make('title'),
Slot::make('UserSalesComponent', [
'chartType' => 'bar',
]),
BelongsTo::make('author', UserResource::class),
];
}
Компонент должен быть зарегистрирован глобально через app.component():
app.component('UserSalesComponent', UserSalesComponent)
В форме компонент получает пропсы resource, formContext и всё что передано в props.