Розробка системи управління меню сайту
Система управління меню дозволяє адміністраторам сайту змінювати структуру навігації без участі розробників. Важлива для сайтів з динамічним контентом: нові розділи, сезонні зміни, A/B-тестування навігації.
Модель даних
menus (
id, name, slug, locale
-- 'main_menu', 'footer_menu', 'mobile_menu'
)
menu_items (
id, menu_id, parent_id, order,
type: link | page | category | custom,
label, url,
page_id, -- якщо type=page
category_id, -- якщо type=category
target: _self | _blank,
icon,
is_visible, css_class,
attributes (jsonb) -- data-атрибути, id
)
Інтерфейс Drag-and-drop
Для управління вкладеною структурою — бібліотека @dnd-kit/sortable або react-sortable-tree:
import { DndContext, closestCenter } from '@dnd-kit/core';
import { SortableContext, arrayMove, verticalListSortingStrategy } from '@dnd-kit/sortable';
function MenuEditor({ items, onReorder }) {
const [treeItems, setTreeItems] = useState(buildTree(items));
const handleDragEnd = ({ active, over }) => {
if (!over || active.id === over.id) return;
const oldIndex = treeItems.findIndex(i => i.id === active.id);
const newIndex = treeItems.findIndex(i => i.id === over.id);
const reordered = arrayMove(treeItems, oldIndex, newIndex);
setTreeItems(reordered);
onReorder(reordered.map(({ id }, order) => ({ id, order })));
};
return (
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={treeItems} strategy={verticalListSortingStrategy}>
{treeItems.map(item => (
<SortableMenuItem key={item.id} item={item} />
))}
</SortableContext>
</DndContext>
);
}
Серверна частина: отримання меню з кешуванням
class MenuService
{
public function getMenu(string $slug, string $locale): array
{
return Cache::remember("menu:{$slug}:{$locale}", 3600, function () use ($slug, $locale) {
$menu = Menu::where('slug', $slug)->where('locale', $locale)->first();
if (!$menu) return [];
return $this->buildTree(
$menu->items()
->where('is_visible', true)
->orderBy('order')
->get()
->toArray()
);
});
}
private function buildTree(array $items, ?int $parentId = null): array
{
return collect($items)
->where('parent_id', $parentId)
->map(fn($item) => array_merge($item, [
'children' => $this->buildTree($items, $item['id'])
]))
->values()
->toArray();
}
}
Інвалідація кешу
При будь-якій зміні меню — скинути кеш:
Menu::observe(MenuObserver::class);
class MenuObserver
{
public function saved(Menu $menu): void
{
Cache::forget("menu:{$menu->slug}:{$menu->locale}");
}
}
Типи пунктів меню
| Тип | Описання |
|---|---|
link |
Довільний URL, вводиться вручну |
page |
Вибір зі списку сторінок CMS |
category |
Вибір категорії каталогу |
custom |
Якірна ссилка (#section) або JS-дія |
Для типів page та category URL генерується автоматично при зміні slug сторінки/категорії.
Термін розробки: 2–3 дні для системи з drag-and-drop та кількома типами пунктів.







