Разработка модуля многоуровневого меню 1С-Битрикс
Стандартный компонент bitrix:menu генерирует меню из файлов .menu.php или из структуры сайта. Для простых сайтов этого достаточно. Проблемы начинаются, когда маркетолог хочет управлять мегаменю с картинками, баннерами и колонками через административный интерфейс без правки кода, когда нужно A/B тестировать разные структуры меню или показывать разные пункты авторизованным и анонимным пользователям. Модуль многоуровневого меню даёт гибкое управление через интерфейс с кешированием и полной поддержкой мегаменю.
Модель данных
Модуль vendor.megamenu:
-
b_vendor_menu_config— конфигурации меню: id, code, name, site_id, lang, is_active -
b_vendor_menu_item— пункты меню: id, menu_id, parent_id, sort, title, url, url_type (absolute/relative/component), target, icon_id, image_id, css_class, visibility (all/authorized/unauthorized), condition (JSON), is_active -
b_vendor_menu_column— колонки мегаменю для пункта второго уровня: id, item_id, title, sort, items (JSON — ссылки без отдельных записей) -
b_vendor_menu_banner— баннеры в мегаменю: id, item_id, image_id, url, title, sort
Дерево пунктов меню
Пункты хранятся в adjacency list (parent_id). Для построения дерева используется итеративный обход, чтобы избежать рекурсии в PHP:
class MenuTreeBuilder
{
public function build(int $menuId): array
{
$items = MenuItemTable::getList([
'filter' => ['MENU_ID' => $menuId, 'IS_ACTIVE' => 'Y'],
'order' => ['PARENT_ID' => 'ASC', 'SORT' => 'ASC'],
])->fetchAll();
$tree = [];
$index = [];
foreach ($items as $item) {
$item['children'] = [];
$index[$item['ID']] = &$item;
}
foreach ($index as &$item) {
if ($item['PARENT_ID']) {
$index[$item['PARENT_ID']]['children'][] = &$item;
} else {
$tree[] = &$item;
}
}
return $tree;
}
}
Дерево кешируется целиком тегом menu_{code}_{lang}. Инвалидация — при любом изменении пункта или баннера этого меню.
Видимость пунктов
Условия видимости пункта меню:
-
visibility = 'authorized'— пункт скрыт для гостей -
visibility = 'unauthorized'— пункт скрыт для залогиненных пользователей -
condition(JSON) — произвольные условия: группы пользователей, регион, параметры запроса
class VisibilityChecker
{
public function isVisible(array $item): bool
{
global $USER;
if ($item['VISIBILITY'] === 'authorized' && !$USER->IsAuthorized()) return false;
if ($item['VISIBILITY'] === 'unauthorized' && $USER->IsAuthorized()) return false;
$condition = $item['CONDITION'] ?? [];
if (!empty($condition['user_groups'])) {
$userGroups = $USER->GetUserGroupArray();
if (!array_intersect($condition['user_groups'], $userGroups)) return false;
}
return true;
}
}
Условия видимости применяются уже после получения кешированного дерева — кеш хранит полное дерево, фильтрация происходит в памяти.
Мегаменю с колонками и баннерами
Для пунктов верхнего уровня можно настроить мегаменю-дропдаун с колонками:
[Каталог]
├─ Колонка 1: Электроника
│ ├─ Смартфоны
│ ├─ Ноутбуки
│ └─ Планшеты
├─ Колонка 2: Бытовая техника
│ ├─ Холодильники
│ └─ Стиральные машины
└─ Баннер: [Акции недели → /sale/]
Колонки хранятся в b_vendor_menu_column, баннеры — в b_vendor_menu_banner. Компонент генерирует HTML-структуру мегаменю, стилизация — через CSS Grid.
Drag-and-drop интерфейс
Административный интерфейс построен на Sortable.js (или аналоге). Пункты перетаскиваются в нужную позицию и в нужный родительский элемент. При перетаскивании AJAX-запрос обновляет sort и parent_id одной транзакцией.
Активный пункт
Активный пункт меню определяется по текущему URL:
// Точное совпадение URL или совпадение начала пути
function isActive(array $item, string $currentUrl): bool
{
if ($item['URL'] === $currentUrl) return true;
if ($item['URL_TYPE'] === 'section' && str_starts_with($currentUrl, $item['URL'])) return true;
return false;
}
Активный пункт и все его предки получают CSS-класс active.
Сроки разработки
| Этап | Срок |
|---|---|
| ORM-таблицы, модель дерева | 1 день |
| Построение дерева, кеширование | 1 день |
| Условия видимости пунктов | 1 день |
| Мегаменю: колонки, баннеры | 2 дня |
| Drag-and-drop интерфейс | 2 дня |
| Компоненты для сайта (HTML + CSS) | 2 дня |
| Тестирование | 1 день |
Итого: 10 рабочих дней. Для мобильного меню (offcanvas, hamburger) — дополнительно 1 день.







