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, о чем описано подробнее ниже.
Ограничения
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. Запросы буду направляться к backend-части модуля.
Важно!
На данный момент данная функциональность в разработке. Описание, как выполнять HTTP-запросы, появится с выходом функциональности.
Другие способы выполнения HTTP-запросов использовать запрещено, они не будут работать.
Точки встраивания (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-модуля.
Разработка JS-приложения
В приложении нужно объявить используемые компоненты из библиотеки интерфейса системы, например:
// src/components/index.ts
const UiButton = defineRemoteComponent('UiButton')
const UiModalWindow = defineRemoteComponent('UiModalWindow', [
'update:opened',
] as unknown as {
'update:opened': (opened: boolean) => void,
}, [
'title',
'footer',
])
const CrmYandexMap = defineRemoteComponent('CrmYandexMap', [
'change',
] as unknown as {
'change': (address: string) => void,
})
export {
UiButton,
UiModalWindow,
CrmYandexMap,
}
Далее написать Vue-компонент(-ы) вашего JS-приложения. Например:
// src/extensions/VExtension.vue
<template>
<UiButton appearance="secondary" class="mt-4" @click="opened = true">
На карте
</UiButton>
<UiModalWindow v-model:opened="opened" :class="$style['window']">
<template #title>
Адрес
</template>
<CrmYandexMap
:api-key="'dd51f938-0693-457d-ae62-6d50fa668d0a'"
:address="address || ''"
@change="address = $event"
/>
<template #footer>
<UiButton appearance="secondary" @click="opened = false">
Закрыть
</UiButton>
</template>
</UiModalWindow>
</template>
<script lang="ts" setup>
import {
UiButton,
UiModalWindow,
CrmYandexMap,
} from '@/components'
import { onMounted, ref } from 'vue'
import {
useOrderCardContext,
useField,
} from '@retailcrm/embed-ui'
const order = useOrderCardContext()
const opened = ref(false)
const address = useField(order, 'delivery.address')
onMounted(() => order.initialize())
</script>
<style lang="less" module>
.window :global(.omnica-modal-window__content) {
padding: 0;
}
</style>
И инициализировать приложение в точках встранивания. Например:
// src/extension.js
import { createWidgetEndpoint } from '@retailcrm/embed-ui'
import { fromInsideIframe } from '@remote-ui/rpc'
import VExtension from '@/extension/VExtension.vue'
createWidgetEndpoint({
async run (createApp, root, pinia, target) {
const app = createApp(VExtension, { target })
app.use(pinia)
app.mount(root)
return () => app.unmount()
},
}, fromInsideIframe())
Более подробно можно посмотреть в документации @omnicajs/vue-remote и @retailcrm/embed-ui.
Примеры расширений вы можете посмотреть в @retailcrm/core-ui-extensions-examples.
Встраивание в интерфейс системы
Сборка 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]
— относительный путь к js-скриптуintegrationModule[integrations][embedJs][stylesheet]
— относительный путь в css-стилямintegrationModule[integrations][embedJs][targets]
— массив точек встраивания
Если вызов метода был успешный, то на страницах целевого аккаунта, на которых находятся указанные точки встраивания, будет инициализироваться JS-часть вашего модуля.