Веб-модули

Веб-модули — это особый механизм интеграции серверной платформы Melbis с её Windows-клиентом Melbis Shop. Суть механизма: внутри программы встроен полноценный браузер на базе Chromium, который умеет открывать страницы сайта прямо внутри окон программы и общаться с ними в обе стороны — передавать контекст текущей работы менеджера на сервер и получать обратно результат. Разработчик создаёт обычный Melbis-модуль, а программа открывает его внутри своего окна.

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


Встроенные веб-модули

Встроенные модули работают прямо внутри рабочих окон программы — «Товары / Цены», «Бизнес / Покупатели», «Бизнес / Заказы». Чтобы открыть встроенный веб-модуль в любом из этих окон, нажмите Ctrl+W — появится панель со встроенным браузером.

Ключевая особенность: когда менеджер перемещается по записям (выбирает товар, открывает заказ, переходит к покупателю), программа автоматически отправляет POST-запрос в модуль с контекстом текущего окна. Модуль всегда знает, с каким объектом сейчас работает менеджер, и может показать релевантную информацию — без каких-либо дополнительных действий с его стороны.

Этих модулей может быть несколько для каждого раздела — они переключаются вкладками. Для каждого модуля можно задать права доступа: какие группы пользователей его видят.

Примеры встроенных модулей для раздела «Товары / Цены»:

Примеры для раздела «Бизнес / Заказы» и «Бизнес / Покупатели»:

Настройка

Встроенные модули настраиваются в программе: «Проектирование → Модули и опции», закладка «Встроенные модули». Выберите группу («Товары», «Покупатель», «Заказы») и добавьте модуль со следующими параметрами:

Параметры, передаваемые программой

При каждом обращении к встроенному модулю программа отправляет POST-запрос с контекстом текущего окна. Ниже — полный набор параметров для раздела «Заказы»:

// $mVars в модуле:
[
    'get'  => ['mod' => 'melbis_web_sample'],
    'post' => [
        'order_id'                => '1',
        'order_version_id'        => '2',
        'order_version_client_id' => '1',
        'order_version_user_id'   => '1',
        'order_client_field_id'   => '3',
        'order_store_id'          => '2',
        'order_option_id'         => '3',
        'login'                   => 'admin',
        'pass_code'               => '21232f297a57a5a743894a0e4a801fc3',
    ]
]

В разделе «Товары» передаётся store_id выбранного товара. В разделе «Покупатели» — идентификатор клиента. Пароль передаётся в виде MD5-хэша.


Библиотека melbis_inc_auth

Для веб-модулей в стандартную поставку демо-магазина включена библиотека melbis_inc_auth. Она берёт на себя весь рутинный код: авторизацию пользователя по login + pass_code, хранение состояния авторизации в PHP-сессии, проверку прав доступа к модулю и маршрутизацию POST-вызовов между функциями модуля.

Центральная функция — MELBIS_INC_AUTH_router($module, $mVars) — выполняет следующее по порядку:

  1. Проверяет, не запрошен ли выход (logout в POST).
  2. Авторизует пользователя: по login + pass_code из POST, или по ранее сохранённой сессии.
  3. Проверяет право пользователя на доступ к данному модулю (по таблице прав в БД).
  4. Записывает через GlobalAssign('page', [...]) глобальные переменные, доступные во всех шаблонах: статус авторизации (auth), user_id и mod — URL текущего модуля. Переменная {PAGE:MOD} используется в шаблонах как базовый URL для AJAX-запросов.
  5. Маршрутизирует вызов: если в POST передан параметр func — вызывает функцию ИМЯ_МОДУЛЯ_func($userId, $mVars), иначе вызывает ИМЯ_МОДУЛЯ_default($userId, $mVars).

Благодаря этому вся главная функция модуля сводится к одной строке:

function MELBIS_WEB_SAMPLE($mVars)
{
    return MELBIS_INC_AUTH_router(MELBIS()->UnitName(), $mVars);
}

MELBIS()->UnitName() возвращает имя текущего модуля — оно передаётся в маршрутизатор для проверки прав и построения имён дочерних функций.


Структура модуля: функция _default

Функция _default — точка входа при первом открытии модуля. Она получает $mUserId (или null если авторизация не прошла) и полный массив $mVars.

function MELBIS_WEB_SAMPLE_default($mUserId, $mVars)
{
    $tpl = MELBIS()->TplCreate();

    if ( $mUserId > 0 )
    {
        // Подготовить кэш прав пользователя для работы с web_key
        MELBIS_INC_AUTH_web_key_prepare($mUserId);

        // Передать входные переменные в шаблон
        MELBIS()->TplAssign($tpl, 'VARS', var_export($mVars, true));

        // Передать данные заказа для механизма обратной передачи
        MELBIS()->TplAssign($tpl, 'ORDER', $mVars['post']['order'] ?? '{}');

        MELBIS()->TplParse($tpl, 'SCRIPTS', 'scripts');
        MELBIS()->TplParse($tpl, 'CONTENT', 'page');
    }
    else
    {
        // Пользователь не авторизован — показать форму входа
        MELBIS()->TplParse($tpl, 'CONTENT', 'auth');
    }

    MELBIS()->GlobalAppend('page', ['title' => 'Sample Web module']);

    return MELBIS()->TplFinal($tpl, 'main');
}

Шаблон auth.htm содержит вызов модуля melbis_web_auth — он выводит форму логина. После отправки формы MELBIS_INC_AUTH_router снова обрабатывает POST, авторизует пользователя и на этот раз возвращает основной шаблон page.htm.


AJAX-запросы из шаблона

Для интерактивных действий внутри модуля JavaScript передаёт параметр func в POST. Маршрутизатор найдёт и вызовет соответствующую функцию — при условии что пользователь авторизован и имеет доступ к модулю.

В шаблоне переменная {PAGE:MOD} содержит готовый URL текущего модуля (/?mod=melbis_web_sample) — его удобно использовать как точку для всех AJAX-запросов:

$('#melbis_table_cataloge').bootstrapTable({
    url: '{PAGE:MOD}',
    method: 'post',
    queryParams: function(params) {
        params.func = 'get_cataloge';  // маршрутизатор вызовет MELBIS_WEB_SAMPLE_get_cataloge
        return params;
    },
    pagination: true,
    sidePagination: 'server',
    pageSize: 20,
    sortName: 'absindex',
    sortOrder: 'asc'
});

На стороне PHP функция получает управление только если пользователь авторизован, выполняет запрос и возвращает JSON:

function MELBIS_WEB_SAMPLE_get_cataloge($mUserId, $mVars)
{
    $limit  = (int) $mVars['post']['limit'];
    $offset = (int) $mVars['post']['offset'];
    $sort   = preg_replace('/[^a-z_]/', '', $mVars['post']['sort']);
    $sort  .= ($mVars['post']['order'] == 'asc') ? ' ASC' : ' DESC';

    $command = "SELECT t.id, t.name, t.tlevel, COUNT(ts.id) AS amount
                  FROM {DBNICK}_topic t
             LEFT JOIN {DBNICK}_topic_store ts ON t.id = ts.topic_id
              GROUP BY t.id
              ORDER BY $sort";

    $data = MELBIS()->SqlSelectLimit(__LINE__, $command, $offset, $limit);

    return json_encode($data);
}

Такой же принцип применяется для любых других функций модуля — поиска, фильтрации, сохранения данных в БД, отправки уведомлений и т.п.


Обратная передача данных в программу

Наиболее уникальная возможность встроенных веб-модулей — передача данных из браузера обратно в Windows-программу без дополнительных HTTP-запросов.

Это реализовано через console.log со специальным ключом. Программа перехватывает вывод консоли браузера и при обнаружении ключа MELBIS_ORDER_UPDATE применяет полученные данные к редактируемому заказу.

Механизм работает только в окне редактирования заказа, пока заказ ещё не сохранён. В остальных разделах (товары, покупатели) веб-модуль при необходимости пишет изменения напрямую в базу данных через обычные SQL-методы парсера.

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

// PHP: получить данные заказа, переданные программой, и отдать в шаблон
MELBIS()->TplAssign($tpl, 'ORDER', $mVars['post']['order'] ?? '{}');
// Шаблон: распарсить JSON и построить таблицы
var melbis_order = {ORDER};

function melbis_init_back() {
    for (var table in melbis_order) {
        var data    = melbis_order[table];
        var columns = [];
        for (var c in data[0]) {
            columns.push({field: c, title: c});
        }
        $('.melbis_table_order[data-table="' + table + '"]').bootstrapTable({
            columns: columns,
            data:    data
        });
    }
}

// Клик по ячейке — диалог редактирования
$('.melbis_table_order').on('click-cell.bs.table', function(event, field, value, row) {
    var table = event.target.dataset.table;
    bootbox.prompt({
        title: 'Edit value of "' + field + '"',
        value: value,
        callback: function(result) {
            if (result != null) {
                var update = {};
                update[table] = [{id: row.id, [field]: result}];
                // Передать изменение в программу через консоль
                console.log('MELBIS_ORDER_UPDATE' + JSON.stringify(update));
            }
        }
    });
});

Программа считывает вывод консоли, распознаёт ключ MELBIS_ORDER_UPDATE и применяет изменения к полям заказа в своём интерфейсе.


Внешние веб-модули

Внешние модули отличаются от встроенных принципиально одним: они не привязаны к конкретному окну программы и не получают контекст текущей записи. Вместо этого при открытии из программы передаются только параметры авторизации пользователя (login, pass_code).

Доступ к внешним модулям открывается через раздел «Бизнес / Веб-модули» — здесь расположен браузер с каталогом всех доступных модулей в левой панели. Кроме того, любой из таких модулей можно открыть в обычном браузере на любом устройстве — планшете, смартфоне, рабочем месте без программы.

Это делает внешние модули универсальным инструментом для дашбордов, аналитических отчётов и административных панелей — всего, что не требует привязки к конкретному товару или заказу. На скриншоте выше — пример реального внешнего модуля с аналитикой продаж, финансовым учётом, складским анализом и инструментами управления, встроенными прямо в программу.

Настройка внешних модулей — «Проектирование → Модули и опции», закладка «Внешние веб-модули»: задаётся название, URL и символьный ключ каждого модуля, а также права доступа по группам пользователей.


Авторизация во внешних модулях

Поскольку внешние модули открываются в том числе из обычного браузера, авторизация здесь работает в двух режимах автоматически:

При открытии из программы — Melbis Shop передаёт login и pass_code в POST. MELBIS_INC_AUTH_router авторизует пользователя без участия формы и сразу показывает содержимое.

При открытии в браузере напрямую — POST не содержит учётных данных, маршрутизатор возвращает $mUserId = null, и функция _default подставляет шаблон формы авторизации. После успешного входа данные сохраняются в PHP-сессии, и при последующих запросах форма больше не показывается.

Один и тот же код модуля работает в обоих случаях — разветвление происходит полностью автоматически внутри библиотеки melbis_inc_auth. Разработчику достаточно корректно обработать случай $mUserId == null в функции _default.


Итоговая схема

Windows Melbis Shop
    │
    │  POST: store_id / order_id / login / pass_code
    ▼
index.php → Run('melbis_web_sample')
    │
    ▼
MELBIS_WEB_SAMPLE($mVars)
    │
    ▼
MELBIS_INC_AUTH_router(...)
    ├─ авторизация + проверка прав
    ├─ func = 'default'      → MELBIS_WEB_SAMPLE_default($userId, $mVars)      → HTML
    ├─ func = 'get_cataloge' → MELBIS_WEB_SAMPLE_get_cataloge($userId, $mVars)  → JSON
    └─ func = 'get_goods'    → MELBIS_WEB_SAMPLE_get_goods($userId, $mVars)     → JSON

HTML-страница в браузере программы
    │  (только для окна редактирования заказа)
    ▼
console.log('MELBIS_ORDER_UPDATE{"version":[...]}')
    │
    ▼
Windows Melbis Shop — применяет изменения к заказу