JS-модули
Скопировать ссылку на статью
Скопировано

JS-модули позволяют расширить возможности интерфейса системы.

Архитектура JS-модуля

JS-модули является расширенной версией стандартных модулей Маркетплейса. Отличием является то, что они состоят из двух частей: backend-часть и frontend-часть.

Backend-часть JS-модуля

Backend-часть JS-модуля разрабатывается также, как и у стандартных модулей. В ее подготовке вам помогут статьи про основные требования к модулям, подключение и активацию модуля и добавление модуля в Маркетплейс.

Backend-часть модуля должна как минимум содержать логику по подключению и активации модуля в Маркетплейсе.

Если frontend-часть модуля будет обращаться к backend-части, то последняя помимо базовой логики должна также предоставлять ендпоинты для обработки запросов, отправляемых из frontend-части.

Важно!

После того, как вы подготовили backend-часть модуля, заведите его в разделе «Модули» партнерского кабинета. Далее обратитесь в партнерский отдел, чтобы для вашего модуля активировали JS-функциональность.

Frontend-часть JS-модуля

Frontend-часть JS-модуля должна представлять собой приложение, написанное на Javascript и Vue 3. Приложение будет выполняться в изолированной среде iframe. Выполнение приложения производится с помощью библиотеки удаленного рендеринга @omnicajs/vue-remote. Связь между приложением и основным интерфейсом системы (далее «хостом») осуществляется через postMessage.

К JS-приложениям предъявляются определенные требования и они содержат ограничения на используемый функционал Vue и JS, о чем описано подробнее ниже.

Требования к JS-модулям

1. Разрешенные компоненты для вывода на странице

В случае, когда JS-модуль добавляет функциональность на существующие страницы системы, непосредственно на странице (например, странице заказа) JS-модулям разрешено выводить только компоненты UiToolbarButton и UiToolbarLink из предоставляемых готовых Vue-компонентов в количестве до 2 штук в рамках одного target-а.

Подробнее с компонентами можно ознакомиться в витрине компонентов.

2. Отображение дополнительного контента

JS-модуль по клику на кнопку или ссылку может отображать дополнительные данные, формируемые самостоятельно или подгружаемые из сторонних систем. Для вывода этой информации предпочтительно использовать Vue-компонент боковой панели UiModalSidebar. В случае, если контент достаточно широкий: табличные данные, карта и другая подобная информация — допустимо использовать Vue-компонент модального окна UiModalWindow.

Подробнее с компонентами можно ознакомиться в витрине компонентов.

3. Локализация JS-модуля

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

В качестве библиотеки для организации переводом рекомендуется использовать vue-i18n. В примерах JS-модулей @retailcrm/core-ui-extensions-examples вы можете найти реализацию переводом надписей и выставление локали в соответствии с локалью аккаунты системы.

Ограничения JS-модулей

1. Ограничения на значения атрибутов элементов в шаблонах Vue-компонентов

Элементам можно присваивать атрибуты, например:

<div
    :class="someClass"
    :style="someStyle"
    :data-value="someValue"
/>

В примере выше атрибуты — это class, style и data-value. Нужно помнить, что в эти атрибуты можно передавать только простые стандартные данные: string, number, boolean, null, undefined. Также могут быть переданы массивы простых данных, объекты ключ-значение, в которых значения — простые данные, либо комбинации перечисленных типов. Любые другие данные, переданные как атрибуты, приведут к сбою работы виджета.

Например, нельзя передать экземпляр такого класса:

class MyClass {
  toString() {
    return somethingThatIsString
  }
}

и ожидать, что произойдет автоматическая конвертация. Вместо этого произойдет сбой.

Также на данный момент не поддерживается атрибут ref из Vue на элементах:

<div ref="myRef" />

2. Выполнение HTTP-запросов

В JS-приложении можно выполнять HTTP-запросы только через JS API. Другие способы выполнения HTTP-запросов использовать запрещено, они не будут работать.

Нужно инициализировать переменную от useHost() функции, у которой есть метод httpCall() для http-вызовов.

import { useHost } from '@retailcrm/embed-ui'

const host = useHost()

const { body, status } = await host.httpCall('/get-dictionary')
if (status === 200) {
  const response = JSON.parse(body) // или другой способ обработки, если ожидается не-JSON ответ
} else {
  throw new Error(`HTTP request failed with status: ${status}, body: ${body}`)
}

У функции httpCall(path: string, payload?: string|object) 2 параметра:

  • Обязательный параметр path, на который в бекенд модуля будет отправлен запрос
  • Опциональный параметр payload, в котором строкой или json-объектом можно передать дополнительные параметры

Примеры вызовов:

  • host.httpCall('/get-dictionary')
  • host.httpCall('/get-dictionary', 'somePayload')
  • host.httpCall('/get-dictionary', { order_id: orderId })

При вызове функции система отправляет на {baseUrl}/{path} бекенда модуля POST-запрос c Content-Type: application/x-www-form-urlencoded'.

Например, если модуль задал baseUrl, равный https://some-module.tech и указал path, равный /get-dictionary, то запрос придет на https://some-module.tech/get-dictionary.

В запросе может прийти 2 form-encoded параметра:

  • clientId=<clientIdForAccount> — обязательный параметр со значением clientId, которое модуль установил в данном аккаунте системы
  • payload=<payload> — опциональный параметр, если был передан payload

Если payload был указан строкой, то он будет передан как есть. Если был указан json-объект, то в payload будет передано его строковое представление

Пример передаваемых form-encoded данных:

clientId=client-id-xxx
payload={"order_id":85}

В ответ нужно вернуть ответ с нужным HTTP-статусом и телом. Они будут возвращены в ответе функции httpCall() как есть. В разборе ответа стоит проверять статус и обрабатывать ожидаемый и неожидаемый статус ответа.

Если в ответе возвращается JSON, то в JS-модуле его нужно самостоятельно распарсить.

Система отводит 2 секунды на установку соединения и 5 секунд на формирование ответа. Если превышен таймаут, то вернется HTTP-статус 500 с соответствующей ошибкой завершения по таймауту.

Важно!

При инициализации JS-модуля прямо на странице системы через window['CRM'].embed.register() HTTP-вызовы, содержащиеся в его логике, будут возвращать HTTP-статус 503 c телом ответа REQUIRED_INTEGRATION_MODULE.

Более подробный пример с http-вызовами

import {
    useOrderCardContext,
    useHost,
    useField,
} from '@retailcrm/embed-ui'

import { ref } from 'vue'

const order = useOrderCardContext()
const orderId = useField(order, 'id')

order.initialize()

const host = useHost()

const fetchDictionary = async () => {
  const { body, status } = await host.httpCall('/get-dictionary', { order_id: orderId })
  if (status === 200) {
    return JSON.parse(body) // или другой способ обработки, если ожидается не-JSON ответ
  }

  throw new Error(`HTTP request failed with status: ${status}, body: ${body}`)
}

// можно при взаимодействии с интерфейсом вызвать
const makeSomething = async (data) => {
  const { body, status } = await host.httpCall('/make-something', data)

  if (status !== 200) {
    throw new Error(`HTTP request failed with status: ${status}, body: ${body}`)
  }

  // опционально, обработка ответа
}

const dictionary = ref([])

fetchDictionary().then(data => dictionary.value = data)

Также вы можете найти работу с http-вызовами в примере cases/fiscalReceipts библиотеки примеров @retailcrm/core-ui-extensions-examples.

Точки встраивания (targets) и доступные в них данные

Приложение может встраиваться в определенные точки интерфейса (в коде они называются targets) и оперировать доступными в этих точках данными.

При подготовке JS-приложения вам нужно определиться с перечнем точек встраивания.

В разных точках доступен разный набор данных. Данные реактивные, то есть изменения применяются ко входным объектам при их изменении в интерфейсе (в частности в формах, например, форме заказа, форме клиента и т.п). Некоторые поля мутабельные, то есть их можно изменять в JS-приложении и эти изменения применятся к форме. Такие поля в справочнике отмечены как readonly: false.

Для получения объекта из контекста и полей из объекта, используйте функции из @retailcrm/embed-ui/index.d.ts, например:

import {
    useOrderCardContext,
    useSettingsContext,
    useField,
} from '@retailcrm/embed-ui'

const order = useOrderCardContext()
const address = useField(order, 'delivery.address')

const settings = useSettingsContext()
const locale = useField(settings, 'system.locale')

Перечень точек встраивания

Доступные Vue-компоненты

Ряд Vue-компонентов, на которых строится интерфейс системы, доступен для JS-модулей. Вы можете использовать их для создания интерфейса JS-модуля.

Подробная информация о возможностях конфигурации компонентов доступна в витрине компонентов.

Доступны props и слоты (дефолтные и именнованные). Для каждого компонента они описаны в витрине. Методы компонентов, компонент Transition, scoped slots, refs, директивы (кроме v-if/v-show) и модификаторы на данный момент недоступны.

Также доступны события. Актуальный список событий можно посмотреть тут.

Для использования добавьте пакет компонентов в package.json вашего модуля:

npm i --save @retailcrm/embed-ui-v1-components

или

yarn add @retailcrm/embed-ui-v1-components

После чего в файле вашего компонента импортируйте нужный компонент и используйте:

// cases/someModule/SomeExtension.vue

<template>
    <UiToolbarButton>
        {{ t('click') }}
    </UiToolbarButton>
</template>

<script lang="ts" setup>
import { UiToolbarButton } from '@retailcrm/embed-ui-v1-components/remote'

//...
</script>

Разработка JS-приложения

В приложении нужно написать Vue-компонент(-ы) вашего JS-приложения. Например:

// cases/phoneReactive/PhoneReactiveExtension.vue

<template>
    <UiToolbarButton v-if="phone" @click="phone = t('callMade')">
        {{ t('callOn') }}{{ phone }}
    </UiToolbarButton>
</template>

<script lang="ts" setup>
import {
    useOrderCardContext,
    useField,
    useSettingsContext,
} from '@retailcrm/embed-ui'

import { UiToolbarButton } from '@retailcrm/embed-ui-v1-components/remote';
import { useI18n } from 'vue-i18n'
import { watch } from 'vue'

// set locale

const settings = useSettingsContext()
const locale = useField(settings, 'system.locale')

settings.initialize()

const i18n = useI18n()
const t = i18n.t

watch(locale, locale => i18n.locale.value = locale, { immediate: true })

// init order fields

const context = useOrderCardContext()
const phone = useField(context, 'customer.phone')

context.initialize()
</script>

<i18n locale="en-GB">
{
    "callOn": "Let's call on ",
    "callMade": "Call made"
}
</i18n>

<i18n locale="es-ES">
{
    "callOn": "Vamos a llamar a ",
    "callMade": "Llamada realizada"
}
</i18n>

<i18n locale="ru-RU">
{
    "callOn": "Звоним на ",
    "callMade": "Звонок совершен"
}
</i18n>

Во Vue-компоненте важно сразу предусмотреть переводы на 3 языка: русский, английский и испанский.

И далее инициализировать приложение в точках встранивания. Например:

// cases/phoneReactive/index.ts

import { createWidgetEndpoint } from '@retailcrm/embed-ui'
import { fromInsideIframe } from '@remote-ui/rpc'
import { createI18n } from 'vue-i18n'

import PhoneReactiveExtension from './PhoneReactiveExtension.vue'

createWidgetEndpoint({
    async run (createApp, root, pinia) {
        const i18n = createI18n({ legacy: false, fallbackLocale: 'en-GB' })
        const app = createApp(PhoneReactiveExtension)

        app.use(pinia)
        app.use(i18n)
        app.mount(root)

        return () => app.unmount()
    },
}, fromInsideIframe())

Примеры расширений вы можете посмотреть в библиотеке примеров @retailcrm/core-ui-extensions-examples.

Также могут быть полезны для изучения @omnicajs/vue-remote и @retailcrm/embed-ui.

Встраивание в интерфейс системы

Сборка JS-приложения

Вам требуется выполнить сборку приложения средствами webpack, vite и т.п. В @retailcrm/core-ui-extensions-examples сборка производится командой yarn build. В папке сборки должен появиться набор из index.html, css-файла и js-файла.

После этого требуется создать в папке сборки файл manifest.json

Структура manifest.json

Файл manifest.json содержит метаданные для JS-приложения. В @retailcrm/core-ui-extensions-examples файл создается автоматически при выполнении команды make zip-archive. Пример содержимого файла manifest.json:

{
  "code": "core-ui-extensions",
  "version": 154,
  "targets": ["order/card:delivery.address"],
  "entrypoint": "index.html",
  "scripts": ["extension.xxx.js"],
  "stylesheet": "extension.xxx.css"
}

Где:

  • code: string — уникальный код приложения. Обязательный параметр
  • version: number — версия приложения. Значение должно быть целым числом и больше 0. Обязательный параметр
  • targets: string[] — точки встраивания приложения. Обязательный параметр
  • entrypoint: string — HTML-файл, который является точкой входа для приложения. Обязательный параметр
  • scripts: string[] — JS-файлы приложения. Обязательный параметр
  • stylesheet: ?string — CSS-файл приложения. Необязательный параметр

Встраивание JS-приложения в интерфейс в ходе разработки

В ходе разработки для целей отладки вы можете встраивать JS-приложение на тех страницах, на которых находятся точки встраивания (targets) приложения.

Для этого запустите сервер, который будет отдавать файлы вашего приложения на базе nodejs, nginx или другого веб-сервера. В @retailcrm/core-ui-extensions-examples есть пример сервера в файле server.mjs. Вы можете его запустить командой node server.mjs.

После этого на нужной странице системе достаточно вызвать window['CRM'].embed.register(). Например:

window['CRM'].embed.register({
  "uuid": "62aa8145-ed53-4862-b28f-f1bc6b36a3a3",
  "targets": [
    "order/card:delivery.address"
  ],
  "entrypoint": "http://localhost:3000/extension/62aa8145-ed53-4862-b28f-f1bc6b36a3a3",
  "stylesheet": "http://localhost:3000/extension/62aa8145-ed53-4862-b28f-f1bc6b36a3a3/stylesheet"
})

Значение uuid может быть произвольным.

Публикация frontend-части

Публикация в маркетплейсе

Как было описано в начале статьи, модуль заводится в маркетплейсе, как и любой другой модуль, и через партнерский отдел в нем требуется активировать JS-функциональность.

После этого в карточке модуля будет доступна форма «JS-файл» для загрузки архива JS-части модуля. В @retailcrm/core-ui-extensions-examples есть команда make zip-archive, где можно посмотреть, как создать архив с модулем.

Архив загружается через форму. Если модуль опубликован в Маркетплейсе, то загруженная версия начнет инициализироваться в интерфейсе аккаунтов системы, где включен модуль.

При изменении исходного кода модуля (добавлении новых функций или исправлении ошибок) вам требуется его собрать, создать архив с новым кодом и загрузить через форму. Новая версия будет практически сразу доступна в аккаунтах, где включен модуль (возможны задержки до 10 мин).

Регистрация JS-модуля в отдельно-взятом аккаунте

Если вы разрабатываете кастомизированный JS-модуль для определенного аккаунта системы, вы можете зарегистрировать его с помощью API-метода POST /api/v5/integration-modules/{code}/edit, указав следующие данные:

  • integrationModule[baseUrl] — базовый адрес сервера модуля
  • integrationModule[integrations][embedJs][entrypoint] — относительный путь к HTML-файлу, который является точкой входа
  • integrationModule[integrations][embedJs][stylesheet] — относительный путь в css-стилям
  • integrationModule[integrations][embedJs][targets] — массив точек встраивания

Если вызов метода был успешный, то на страницах целевого аккаунта, на которых находятся указанные точки встраивания, будет инициализироваться JS-часть вашего модуля.

Благодарим за отзыв.
Была ли статья полезна?
Нет
  • Рекомендации не помогли
  • Нет ответа на мой вопрос
  • Текст трудно понять
  • Не нравится описанный функционал
Да
Предыдущая статья
Работа с ценами на модуль
В Маркетплейс можно размещать как бесплатные, так и платные модули.
Следующая статья
Точки встраивания JS-модулей
Точки интерфейса, куда могут встроиться JS-модули, а также доступные в этих точках объекты