27.08.2026 35 мин чтения

UI Bitrix Framework: личный кабинет на системных компонентах

Собираем личный кабинет — профиль, заказы, уведомления — на системных UI-компонентах ядра: Extension::load, ui.system.*, sidepanel и notification-manager. Без Bootstrap, без сборщика. Vue-версия — для подписчиков.

Кирилл Новожилов

Кирилл Новожилов

Автор

UI Bitrix Framework: личный кабинет на системных компонентах
Введение

Официальная документация описывает отдельные виджеты — поле ввода, меню, диалог, боковую панель. Но на практике разработчику нужен ответ на другой вопрос: как связать эти куски в цельный интерфейс публичной части, не таща Bootstrap и не копируя вёрстку из Битрикс24.

В этом туториале мы соберём личный кабинет — профиль, заказы, настройки уведомлений — на UI-расширениях ядра. Публичная часть показывает подключение через \Bitrix\Main\UI\Extension::load() и BX.Runtime.loadExtension() без сборщика. Vue-версия того же интерфейса и продакшен-нюансы — в блоке для подписчиков.

Исходники:

Что вы получите

После прочтения вы сможете:

  1. Понимать архитектуру UI Bitrix: расширения, rel, design tokens.
  2. Подключать системные компоненты (ui.system.*) на странице сайта через шаблон.
  3. Инициализировать JS-классы через BX.Runtime.loadExtension() без @bitrix/chef.
  4. Связать UI с PHP через Controllerable и BX.ajax.runComponentAction.
  5. Собрать из этих приёмов раздел профиля с сохранением — и самостоятельно повторить паттерн для блока уведомлений.

Уровень: Middle (нужен опыт с компонентами и D7).

Окружение: 1С-Битрикс 25.x+, PHP 8.2+, модуль ui. Примеры проверены на актуальном ядре (main 26.700.0, ui 26.550.0).

Часть 1. Как устроен UI Bitrix

1.1. Три слоя, которые нельзя путать

Слой Что это Пример
PHP Загрузка ассетов на страницу \Bitrix\Main\UI\Extension::load('ui.system.input')
JS-расширение Бандл + зависимости (rel в config.php) /bitrix/js/ui/system/input/
JS-класс / Vue API виджета new Input({...}), <BInput v-model="..." />
⚠️ Важно
PHP не рисует интерфейс. Он только регистрирует расширения через CJSCore. Вся отрисовка — в браузере.

Класс Extension из main делает ровно две вещи: находит config.php по имени расширения и вызывает CJSCore::init():

        // bitrix/modules/main/lib/ui/extension.php
public static function load($extNames): void
{
    foreach ((array)$extNames as $extName) {
        if (static::register($extName)) {
            \CJSCore::init($extName);
        }
    }
}

    

Имя расширения ui.system.input превращается в путь /bitrix/js/ui/system/input/.

1.2. Карта системных расширений

Состав ui.system.* зависит от версии ядра, поэтому ориентируйтесь не на документацию, а на папку /bitrix/js/ui/system/ своей сборки. Карта ниже актуальна для main 26.700.0 / ui 26.550.0:

Расширение Назначение Vue-обёртка
ui.system.input Поля ввода, пароль, чипы в input ui.system.input.vueBInput
ui.system.typography Заголовки и текст ui.system.typography.vueHeadline, Text
ui.system.menu Контекстное меню ui.system.menu.vue
ui.system.chip Чипы / теги ui.system.chip.vue
ui.system.alert Алерты-плашки статуса ui.system.alert.vue
ui.system.checkbox Чекбоксы ui.system.checkbox.vue
ui.system.radiobutton Радиокнопки ui.system.radiobutton.vue
ui.system.dialog Модальный диалог
ui.system.skeleton Скелетон загрузки ui.system.skeleton.vue
ui.system.label Подписи ui.system.label.vue
ui.system.highlighter Подсветка текста

На старых ядрах (main 25.x и раньше) части расширений может не быть: ui.system.alert, ui.system.checkbox и ui.system.radiobutton появились позже остальных. Для алертов там остаётся легаси-вариант ui.alerts — о миграции см. раздел 4.6.

1.3. «Старшие» UI-расширения вне system/

В документации их нет, но без них полноценный кабинет не собрать:

Расширение Зачем в кабинете
ui.design-tokens CSS-переменные (--ui-color-*, --ui-font-*) — без них «серые» поля выглядят чужеродно
ui.buttons Кнопки действий (сохранить, навигация)
ui.dialogs.messagebox MessageBox.confirm() — подтверждение выхода
ui.notification-manager Тосты после сохранения
main.sidepanel Боковая панель с деталями заказа
ui.icon-set.api.core + ui.icon-set.outline Иконки в меню и кнопках

1.4. Design tokens — обязательная база

Почти все новые UI-компоненты завязаны на ui.design-tokens. Подключайте его первым:

        \Bitrix\Main\UI\Extension::load([
    'ui.design-tokens',
    'ui.system.input',
]);

    

Без tokens поля и кнопки получат дефолтные цвета ядра, которые могут конфликтовать с шаблоном сайта.

Часть 2. Архитектура примера «Личный кабинет»

        /local/
└── components/vendor/personal.cabinet/
    ├── class.php                          # Controllerable: saveProfile
    └── templates/.default/
        ├── template.php                   # Extension::load + data-config
        ├── script.js                      # BX.Runtime.loadExtension → UI
        └── style.css                      # Layout (сетка, карточки)
/personal/cabinet/index.php                # Страница-обёртка (в корне сайта)

    

Поток данных:

Три раздела кабинета:

  • Профильui.system.input + сохранение через AJAX (шаги 0–4).
  • Уведомления — переключатели на ui.system.checkbox + ui.notification-manager (шаг 5).
  • Заказы — список карточек + main.sidepanel с contentCallback (фрагменты в шаге 6, целиком — в полном примере).

Часть 3. Практика: разбираем приёмы по шагам

Формат этой части: каждый шаг добавляет один приём и объясняет его нюансы — ровно те, что заявлены в начале статьи. Код в шагах короткий, пишите его руками: цель — научиться и понять принцип. В конце каждого шага описано, что должно получиться на экране.

Если что-то не завелось — сверьтесь с полными исходниками.

Шаг 0. Каркас: страница, компонент и данные через D7

Страница-обёртка. NEED_AUTH берёт авторизацию на себя — неавторизованный увидит форму входа, и в компоненте можно не проверять права:

File: personal/cabinet/index.php

        <?php
define('NEED_AUTH', true);
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/header.php';

global $APPLICATION;

$APPLICATION->SetTitle('Личный кабинет');
$APPLICATION->IncludeComponent('vendor:personal.cabinet', '.default', [], false);

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/footer.php';

    

Минимальный компонент. Данные берём по-современному: CurrentUser вместо global $USER, ORM-класс UserTable вместо CUser::GetByID().

File: local/components/vendor/personal.cabinet/class.php

        <?php declare(strict_types=1);

use Bitrix\Main\Engine\CurrentUser;
use Bitrix\Main\UserTable;

if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
{
    die();
}

final class VendorPersonalCabinetComponent extends CBitrixComponent
{
    public function executeComponent(): void
    {
        $user = UserTable::getRow([
            'select' => ['ID', 'NAME', 'EMAIL', 'PERSONAL_PHONE'],
            'filter' => ['=ID' => (int)CurrentUser::get()->getId()],
        ]);

        $this->arResult['USER'] = [
            'ID' => (int)$user['ID'],
            'NAME' => (string)($user['NAME'] ?? ''),
            'EMAIL' => (string)($user['EMAIL'] ?? ''),
            'PERSONAL_PHONE' => (string)($user['PERSONAL_PHONE'] ?? ''),
        ];

        $this->includeComponentTemplate();
    }
}

    

И временный шаблон-заглушка — просто чтобы убедиться, что данные доехали:

File: local/components/vendor/personal.cabinet/templates/.default/template.php

        <?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
{
    die();
}
?>
<pre><?= htmlspecialcharsbx(print_r($arResult['USER'], true)) ?></pre>

    
Результат
Страница /personal/cabinet/ показывает массив с вашим ID, именем и e-mail. Серверная часть работает — переходим к UI.

Шаг 1. Extension::load: что на самом деле попадает на страницу

Заменяем заглушку. Подключаем пока всего два расширения и рисуем один пустой контейнер:

File: local/components/vendor/personal.cabinet/templates/.default/template.php

        <?php
use Bitrix\Main\UI\Extension;

if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
{
    die();
}

Extension::load([
    'ui.design-tokens',
    'ui.system.typography',
]);
?>
<div id="vpc-app"></div>

    

Страница осталась пустой — и это правильный момент, чтобы понять, что именно сделал Extension::load(). Откройте исходный код страницы и найдите:

  • /bitrix/js/ui/system/typography/dist/typography.bundle.js — имя ui.system.typography превратилось в путь к папке расширения;
  • CSS-файл design tokens и бандлы зависимостей — их притянул rel из config.php расширения. Откройте /bitrix/js/ui/system/typography/config.php и сверьте список rel с тем, что появилось на странице.

⚠️ Важно
В этом весь PHP-слой UI: он ничего не рисует, а только кладёт бандлы на страницу и разворачивает дерево зависимостей. ui.design-tokens ставим в списке первым — почти все системные компоненты завязаны на его CSS-переменные, без него виджеты выглядят чужеродно.
Результат
Страница пустая, но в её исходнике — бандлы typography и design tokens со всеми зависимостями.

Шаг 2. BX.Runtime.loadExtension: получаем классы в JS

Создайте рядом с template.php файлы script.js и style.css. Битрикс подключает их из папки шаблона автоматически — никаких addExternalJS() / addExternalCss() не нужно.

File: local/components/vendor/personal.cabinet/templates/.default/script.js

        BX.ready(() => {
    BX.Runtime.loadExtension('ui.system.typography').then((exports) => {
        console.log(exports);

        const app = document.getElementById('vpc-app');
        app.append(exports.Headline.render('Личный кабинет', { size: 'md', tag: 'h1' }));
    });
});

    

Откройте консоль браузера: в exports лежат все ES-экспорты расширения — Headline, Text, Vue-обёртки. Это тот же результат, что дал бы import { Headline } from 'ui.system.typography' в собранном расширении, только без @bitrix/chef и этапа сборки.

Два нюанса, на которых спотыкаются чаще всего:

  1. BX.Runtime.loadExtension работает только в паре с Extension::load в PHP: если расширение не зарегистрировано на странице, экспортов вы не получите. Уберите строку из template.php и убедитесь сами.
  2. Создавать виджеты в инлайновом <script> посреди страницы нельзя — ядро main.core ещё не готово. Точка входа — всегда BX.ready.
Результат
Заголовок «Личный кабинет», отрисованный системной типографикой, и объект экспортов в консоли.

Шаг 3. data-config: передаём данные из PHP и рисуем поле

Данные компонента отдаём в JS через data--атрибут с JSON. Так PHP не смешивается с JS: шаблон не генерирует код, а script.js остаётся чистым статичным файлом:

File: local/components/vendor/personal.cabinet/templates/.default/template.php

        <?php
use Bitrix\Main\UI\Extension;
use Bitrix\Main\Web\Json;

if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
{
    die();
}

Extension::load([
    'ui.design-tokens',
    'ui.system.typography',
    'ui.system.input',
]);

$config = [
    'user' => $arResult['USER'],
];
?>
<div id="vpc-app" data-config="<?= htmlspecialcharsbx(Json::encode($config)) ?>"></div>

    

В script.js читаем конфиг и создаём системное поле:

        BX.ready(() => {
    const app = document.getElementById('vpc-app');
    const config = JSON.parse(app.dataset.config);

    Promise.all([
        BX.Runtime.loadExtension('ui.system.typography'),
        BX.Runtime.loadExtension('ui.system.input'),
    ]).then(([typography, inputModule]) => {
        app.append(typography.Headline.render('Профиль', { size: 'md', tag: 'h1' }));

        const nameInput = new inputModule.Input({
            label: 'Имя',
            value: config.user.NAME,
            size: inputModule.InputSize.Md,
            design: inputModule.InputDesign.Grey,
            required: true,
            withClear: true,
            onInput: () => nameInput.setError(''),
        });

        app.append(nameInput.render());
    });
});

    

Обратите внимание на связку setError('') в onInput: поле умеет подсвечивать ошибку, но сбрасывать её при новом вводе — ваша задача. Этот приём заработает в полную силу на следующем шаге, когда ошибки начнёт присылать сервер.

Результат
Серое системное поле «Имя», заполненное вашим именем, с кнопкой очистки. В исходнике страницы виден JSON в data-config.

Шаг 4. Controllerable: связываем UI с PHP

Теперь сохранение. Компонент объявляет AJAX-действия через контракт Controllerable — отдельный контроллер или REST для этого не нужны. Дополните class.php: добавьте к use новые классы, объявите интерфейсы и допишите методы:

        use Bitrix\Main\Engine\ActionFilter;
use Bitrix\Main\Engine\Contract\Controllerable;
use Bitrix\Main\Error;
use Bitrix\Main\Errorable;
use Bitrix\Main\ErrorCollection;
use Bitrix\Main\Mail\Address;

final class VendorPersonalCabinetComponent extends CBitrixComponent implements Controllerable, Errorable
{
    private ErrorCollection $errors;

    public function onPrepareComponentParams($arParams)
    {
        $this->errors = new ErrorCollection();

        return parent::onPrepareComponentParams($arParams);
    }

    public function configureActions(): array
    {
        return [
            'saveProfile' => [
                'prefilters' => [
                    new ActionFilter\Authentication(),
                    new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_POST]),
                    new ActionFilter\Csrf(),
                ],
            ],
        ];
    }

    public function getErrors(): array
    {
        return $this->errors->toArray();
    }

    public function getErrorByCode($code): ?Error
    {
        return $this->errors->getErrorByCode($code);
    }

    public function saveProfileAction(string $name, string $email): ?array
    {
        $name = trim($name);

        if ($name === '')
        {
            $this->errors->setError(new Error('Укажите имя.'));
            return null;
        }

        if (!(new Address($email))->validate())
        {
            $this->errors->setError(new Error('Укажите корректный e-mail.'));
            return null;
        }

        $user = new CUser();
        $updated = $user->Update((int)CurrentUser::get()->getId(), [
            'NAME' => $name,
            'EMAIL' => $email,
        ]);

        if (!$updated)
        {
            $this->errors->setError(new Error((string)$user->LAST_ERROR));
            return null;
        }

        return ['user' => ['NAME' => $name, 'EMAIL' => $email]];
    }

    // executeComponent() — без изменений
}

    
⚠️ Важно
В этой статье мы рассматриваем работу с UI, поэтому здесь намеренно упростил валидацию полей через условия, а не через DTO и аттрибуты.

Что здесь важно понять:

  • Имя метода. Действие saveProfile = метод saveProfileAction. Аргументы придут из data AJAX-запроса по именам — приведение и типизацию ядро сделает само. Параметр без значения по умолчанию при этом обязателен: не пришлёт клиент поле — действие ответит ошибкой Could not find value for parameter {…}. Необязательным полям давайте дефолт (string $phone = '') — биндер подставит его сам.
  • Префильтры — защита действия: Authentication не пустит гостя, HttpMethod отсечёт GET, Csrf потребует валидный sessid (клиентский хелпер подставит его сам; дёрните URL действия руками без токена — получите ошибку безопасности).
  • Ошибки. У Engine\Controller есть готовый addError(), а у компонента — нет: движок после выполнения действия сам спрашивает ошибки у того, кто реализует интерфейс Errorable. Поэтому держим ErrorCollection, инициализируем её в onPrepareComponentParams() (его ядро вызывает и при AJAX-запросе) и складываем ошибки через setError + return null — клиент получит их массивом errors. Исключения на границе компонента не нужны.

Для вызова действия JS должен знать имя компонента и его подписанные параметры. Добавьте их в $config в template.php (а в Extension::loadui.buttons и ui.notification-manager):

        $config = [
    'componentName' => $this->getComponent()->getName(),
    'signedParameters' => $this->getComponent()->getSignedParameters(),
    'user' => $arResult['USER'],
];

    

signedParameters — параметры компонента, подписанные ключом сайта: при AJAX-вызове сервер доверяет им, потому что подпись нельзя подделать на клиенте.

Остался клиент. В script.js расширяем список загружаемых расширений — в Promise.all появляются два новых, и у колбэка then соответственно два новых аргумента:

        Promise.all([
    BX.Runtime.loadExtension('ui.system.typography'),
    BX.Runtime.loadExtension('ui.system.input'),
    BX.Runtime.loadExtension('ui.buttons'),
    BX.Runtime.loadExtension('ui.notification-manager'),
]).then(([typography, inputModule, buttons, notificationManager]) => {
    // здесь — Headline и nameInput из шага 3,
    // рядом добавьте emailInput по образцу nameInput
});

    

Порядок важен: аргументы в then приходят в том же порядке, что и промисы в массиве. Внутрь колбэка добавьте кнопку с вызовом действия:

        const saveButton = new buttons.Button({
    text: 'Сохранить',
    color: buttons.ButtonColor.SUCCESS,
    round: true,
    noCaps: true,
    onclick: () => {
        BX.ajax.runComponentAction(config.componentName, 'saveProfile', {
            mode: 'class',
            signedParameters: config.signedParameters,
            data: {
                name: nameInput.getValue().trim(),
                email: emailInput.getValue().trim(),
            },
        }).then(() => {
            notificationManager.Notifier.notifyViaBrowserProvider({
                id: 'profile-saved',
                title: 'Профиль сохранён',
                text: 'Данные профиля обновлены.',
            });
        }).catch((errorResponse) => {
            const message = errorResponse.errors?.[0]?.message || 'Не удалось сохранить профиль.';
            notificationManager.Notifier.notifyViaBrowserProvider({
                id: 'profile-save-error',
                text: message,
            });
        });
    },
});

app.append(saveButton.render());

    

mode: 'class' говорит ядру искать действие в классе компонента (а не в файле ajax.php рядом с ним).

Результат
  • меняете имя → «Сохранить» → тост в углу, данные в админке (Настройки → Пользователи) обновились;
  • вводите некорректный e-mail → тост с текстом ошибки с сервера — та самая ошибка из ErrorCollection действия;
  • во вкладке Network виден запрос на /bitrix/services/main/ajax.php с action вашего компонента и sessid в теле.

Красивее показывать серверные ошибки не тостом, а плашкой ui.system.alert — его API разобран в разделе 4.6, подключать loadExtension вы уже умеете.

Шаг 5. Закрепление: блок «Уведомления» своими руками

Все приёмы у вас в руках — соберите второй блок кабинета самостоятельно, без готового кода:

  1. В class.php добавьте в $this->arResult['NOTIFICATIONS'] демо-массив подписок (id, label, enabled) и прокиньте его в $config шаблона — как user в шаге 3.
  2. В Extension::load добавьте ui.system.checkbox (и наборы иконок ui.icon-set.api.core + ui.icon-set.outline — галочка чекбокса рисуется иконкой).
  3. В script.js отрендерите по строке на подписку: Text + чекбокс. Из нового — только API чекбокса:
        const checkbox = new checkboxModule.Checkbox({
    size: checkboxModule.CheckboxSize.Md,
    checked: item.enabled,
    onChange: ({ checked }) => {
        // тост через Notifier — как в шаге 4
    },
});
row.append(checkbox.render());

    

Колбэк onChange получает { checked, event }; состоянием можно управлять из кода — setChecked(), isChecked(), для «выбрана часть подписок» есть опция indeterminate. Сохранение настроек на сервер — ещё одно действие в configureActions() по образцу шага 4.

Результат
Под формой профиля — список подписок с системными чекбоксами, клик переключает состояние и показывает тост. Если застряли — сверьтесь с полными исходниками.

Шаг 6. Куда расти: заказы, sidepanel, меню

Фрагменты ниже — из полного примера: там кабинет получает третий раздел «Заказы», навигацию-вкладки и скелетон загрузки. Они строятся поверх каркаса из шагов 0–5; каждому нужно своё расширение в Extension::load шаблона и в loadExtension.

Боковая панель заказа (main.sidepanel). Документация показывает открытие по URL, но для кабинета удобнее contentCallback — не нужна отдельная страница:

        ui.SidePanel.Instance.open(`order-${order.id}`, {
    width: 520,
    cacheable: false,
    allowChangeHistory: false,
    title: `Заказ №${order.number}`,
    contentCallback: () => {
        const content = document.createElement('div');
        content.style.padding = '24px';
        // ... Headline + Text + кнопка «Закрыть»
        return content;
    },
});

    

allowChangeHistory: false критичен для публичной части — иначе слайдер перепишет URL.

Контекстное меню (ui.system.menu):

        const menu = new ui.Menu({
    bindElement: button.getContainer(),
    items: [
        {
            id: 'logout',
            title: 'Выйти',
            design: ui.MenuItemDesign.Alert,
            onClick: () => { /* ... */ },
        },
    ],
});
menu.show(event.currentTarget);

    

Подтверждение (ui.dialogs.messagebox):

        ui.MessageBox.confirm(
    config.messages.logoutConfirm,
    config.messages.logoutTitle,
    () => { /* redirect logout */ },
);

    

Скелетон (ui.system.skeleton) — пока грузится первая секция:

        sectionNode.append(
    ui.Circle(48),
    ui.Line(220, 16, 8),
    ui.Line(360, 12, 8),
);

    

Часть 4. О чём не расскажут в официальной документации

4.1. BX.Runtime.loadExtension vs import в расширении

Документация показывает два пути:

        // Путь A — модульное расширение (нужен @bitrix/chef)
import { Input } from 'ui.system.input';

// Путь B — шаблон компонента (без сборщика)
BX.Runtime.loadExtension('ui.system.input').then(({ Input }) => { ... });

    

Для публичной части сайта путь B часто практичнее: не нужен CI-сборщик в маленьком проекте.

4.2. Extension::getResourceList() — один запрос, все зависимости

Если строите SPA или AJAX-оболочку и хотите отдать ассеты одним JSON:

        use Bitrix\Main\UI\Extension;

$resources = Extension::getResourceList([
    'ui.system.input',
    'ui.buttons',
], ['with_dependency' => true]);

// $resources['js'], $resources['css'], $resources['settings']

    

Метод разворачивает rel и post_rel из всех config.php. Полезно для кастомного шаблона, который не использует bitrix/header.php.

4.3. settings в config.php расширения

В config.php можно передать PHP-данные в JS:

        return [
    'js' => 'dist/app.bundle.js',
    'settings' => [
        'maxUploadSize' => 5_242_880,
    ],
];

    

На клиенте: BX.Extension.getSettings('vendor.myext'). Документация это не освещает, но паттерн используется в ядре (CRM, IM).

4.4. ui.system.input тянет Vue-зависимости

Даже если вы используете только JS-класс Input, в rel уже есть ui.system.chip.vue и ui.icon-set.api.vue. Это нормально — бандлер ядра собирает общий пакет. Не пугайтесь «лишних» .vue в Network: они не монтируют приложение сами.

4.5. Порядок загрузки на публичной странице

Типичная ошибка — вызвать new Input(...) в <script> до BX.ready. Правильная последовательность:

  1. bitrix/header.php → ядро main.core.
  2. Extension::load(...) в шаблоне.
  3. BX.readyBX.Runtime.loadExtension → создание виджетов.

4.6. ui.system.alert вместо легаси ui.alerts

В актуальном ядре системный алерт уже есть, и API у него отличается от старого ui.alerts:

        const alert = new ui.Alert({
    design: ui.AlertDesign.tintedSuccess,
    content: 'Профиль сохранён',
    hasCloseButton: true,
    events: {
        closeButtonClick: () => alert.destroy(),
    },
});
alertNode.append(alert.render());

    

Соответствие при миграции с ui.alerts:

Легаси ui.alerts Новый ui.system.alert
color: Alert.Color.SUCCESS design: AlertDesign.tintedSuccess
text: '...' content: '...' (строка или HTMLElement)
closeBtn: true hasCloseButton: true + событие closeButtonClick
getContainer() render()

Доступные варианты AlertDesign: tinted (нейтральный), tintedSuccess, tintedWarning, tintedAlert.

Если Extension::load('ui.system.alert') молча не регистрирует расширение — ядро старое (main 25.x и раньше), и там остаётся ui.alerts:

        \Bitrix\Main\UI\Extension::load('ui.alerts');

    
        const alert = new ui.Alert({
    text: 'Профиль сохранён',
    color: ui.Alert.Color.SUCCESS,
    closeBtn: true,
});
alertNode.append(alert.getContainer());

    

Часть 5. Антипаттерны

Симптом Причина Решение
Поле без стилей, «голый» input Не загружен ui.design-tokens Добавить в Extension::load первым
BX.Runtime.loadExtension → undefined Бандл не на странице Сначала Extension::load в PHP
Слайдер меняет URL сайта allowChangeHistory по умолчанию true Передать false
runComponentAction 403 Нет CSRF или не авторизован Проверить ActionFilter\Csrf, сессию
Иконка не видна Нет CSS-набора Подключить ui.icon-set.outline
Двойная инициализация script.js без guard Проверять root.__inited

Часть 6. Полный пример

Полный пример — это кабинет из шагов 0–5 плюс раздел «Заказы» с боковой панелью, навигация-вкладки между разделами, контекстное меню с подтверждением выхода и скелетон загрузки. Код — в репозитории подписчиков:

Скопируйте local/ и personal/ в корень сайта → откройте /personal/cabinet/.

Продолжение по подписке: Кофе && Код

Чтобы получить доступ к платной части статьи, оформите подписку BXMax.

Что внутри:

  • Часть 7. Vue-версия на BitrixVue 3
  • Часть 8. Серверные уведомления через Pull
  • Часть 9. Из script.js шаблона — в собранное расширение
  • Заключение
Исходники
Доступно по подписке: Кофе && Код
Открыть уровни поддержки
Опубликовано 11 часов назад

Комментарии (0)

Пожалуйста, войдите в аккаунт, чтобы оставить комментарий

Оставить комментарий

Похожие статьи

Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…
Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти