87 lines
10 KiB
Markdown
87 lines
10 KiB
Markdown
# Гайд: как встроить конструктор диаграмм в поле Bitrix
|
||
|
||
Короткая практическая версия — только три вопроса: куда положить билд, как собрать HTML и куда писать начальное значение. Про полноценный переиспользуемый тип свойства инфоблока (регистрация, `ConvertToDB` и т.п.) — отдельный документ, [`docs/bitrix-integration.md`](./bitrix-integration.md). Если поле одно и на одной странице — этого гайда достаточно.
|
||
|
||
## 1. Куда положить билд конструктора
|
||
|
||
Приложение — статическая сборка (`npm run build` → `dist/`), которая раздаётся тем же nginx, что и сам портал. Ничего разворачивать отдельно не нужно.
|
||
|
||
1. Собрать в репозитории `portal-chart-builder`:
|
||
```
|
||
VITE_BASE_PATH=/local/diagram-app/ npm run build
|
||
```
|
||
- `VITE_BASE_PATH` — подпуть, под которым будет отдаваться приложение. Должен совпадать с тем, куда положите `dist/` на шаге 2.
|
||
- `VITE_BITRIX_ORIGIN` здесь **не нужен**: приложение и портал на одном домене, origin хоста приложение берёт из `window.location.origin` в рантайме. Задавать переменную нужно только если хост на другом origin — например, локальная разработка (dev-сервер хоста и `vite preview` этого приложения на разных портах) или деплой отдельным контейнером (см. `bitrix-integration.md`, раздел 7, вариант B).
|
||
2. Скопировать содержимое `dist/` в `local/diagram-app/` внутри каталога сайта на сервере (тот же каталог, где лежит остальной код портала — `bitrix_www`/аналог на проде).
|
||
3. Готово — но с оговоркой: приложение доступно **не** по фиксированному `index.html`.
|
||
|
||
**Имя `index.html` в `dist/` — с хешем содержимого**, например `index-2fddeca6.html`, а не просто `index.html` (см. `vite.config.ts`, плагин `hashIndexHtml` — хеширует и сам HTML-файл, не только JS/CSS, которые Vite и так хеширует по умолчанию). Хеш меняется при каждой значимой пересборке и не меняется, если содержимое не менялось. Значит:
|
||
- **`src` у iframe нельзя хардкодить строкой** — нужно на своей стороне подставлять актуальное имя файла (например, взять единственный `index-*.html` в `local/diagram-app/`, или через отдельный метод получения актуального имени — решение за бэкендом).
|
||
- Ручной cache-busting (`?v=...`) для JS/CSS/HTML **больше не нужен**: раз URL меняется вместе с содержимым, старый закэшированный файл просто никогда больше не запрашивается, как только известно новое имя.
|
||
- **После каждого редеплоя старые `index-<hash>.html`/`index-<hash>.js`/`index-<hash>.css` от прошлых версий стоит убирать** из `local/diagram-app/` — они не удаляются сами, и если способ поиска актуального файла — «взять единственный файл по маске», их накопление сломает эту логику.
|
||
|
||
Если схему нужно только показать, без правки, — добавьте к `src` iframe `?mode=view`: редактор откроется в режиме просмотра, переключатель режимов будет скрыт (параметр дописывается к уже вычисленному актуальному имени файла).
|
||
|
||
## 2. Как сформировать HTML
|
||
|
||
Три части: скрытое поле со значением, iframe с приложением, скрипт-мост между ними.
|
||
|
||
```html
|
||
<input type="hidden" id="DIAGRAM-DATA" name="DIAGRAM-DATA" value="<?= htmlspecialcharsbx($currentValue) ?>">
|
||
<iframe id="diagram-frame" src="/local/diagram-app/<?= htmlspecialcharsbx($actualIndexFileName) ?>" style="width:100%;height:600px;border:1px solid #d7dae0;border-radius:6px"></iframe>
|
||
<!-- $actualIndexFileName — актуальное имя index-<hash>.html, см. раздел 1 -->
|
||
|
||
<script>
|
||
(function () {
|
||
var APP_ORIGIN = window.location.origin; // приложение и портал на одном домене
|
||
var input = document.getElementById('DIAGRAM-DATA');
|
||
var frame = document.getElementById('diagram-frame');
|
||
|
||
window.addEventListener('message', function (event) {
|
||
if (event.origin !== APP_ORIGIN || event.source !== frame.contentWindow) return;
|
||
if (!event.data || typeof event.data !== 'object') return;
|
||
|
||
if (event.data.type === 'diagram:ready') {
|
||
frame.contentWindow.postMessage({ type: 'diagram:init', payload: input.value || null }, APP_ORIGIN);
|
||
} else if (event.data.type === 'diagram:change') {
|
||
input.value = event.data.payload;
|
||
}
|
||
});
|
||
})();
|
||
</script>
|
||
```
|
||
|
||
Как это работает:
|
||
- iframe грузит приложение → оно шлёт мосту `{ type: 'diagram:ready' }`.
|
||
- Мост в ответ шлёт `{ type: 'diagram:init', payload: input.value }` — приложение рисует диаграмму из этого значения (или пустой холст, если `payload` пуст).
|
||
- Каждая правка в редакторе прилетает мосту как `{ type: 'diagram:change', payload: '<JSON-строка>' }` — мост тут же кладёт её в `input.value`.
|
||
- Дальше это обычный `<input>` внутри формы: если у него есть `id`, `name` и он лежит в `<form>` — браузер сам отправит его значение при обычном сабмите формы. Никакого дополнительного JS для сохранения не нужно.
|
||
|
||
**`id`/`name` = `"DIAGRAM-DATA"` захардкожены** — это ок, если поле на странице ровно одно. Если полей несколько (например, полноценный переиспользуемый тип свойства, который может стоять на разных элементах) — `name` должен быть уникальным и совпадать с тем, что ожидает обработчик сохранения на бэкенде (в терминах Bitrix custom property type — `$strHTMLControlName['VALUE']`, см. `docs/bitrix-integration.md`, раздел 3).
|
||
|
||
**Обязательно к соблюдению, не опционально:**
|
||
- `event.origin` проверяется у входящих сообщений (уже в сниппете выше).
|
||
- `targetOrigin` у `postMessage` — всегда конкретный домен (`APP_ORIGIN`), никогда `'*'`.
|
||
- Значение попадает на страницу только через `value`-атрибут (`htmlspecialcharsbx()`), не через встраивание в JS-код — так не нужно никакого JSON-специфичного экранирования, обычного `htmlspecialcharsbx()` достаточно.
|
||
|
||
## 3. Куда записывать начальное значение
|
||
|
||
`value="<?= htmlspecialcharsbx($currentValue) ?>"` — сюда подставляется то, что уже сохранено для этого элемента/записи (JSON-строка со схемой диаграммы) или пустая строка, если диаграммы ещё нет.
|
||
|
||
Откуда взять `$currentValue` — зависит от того, где именно хранится значение на вашей стороне (поле БД, свойство инфоблока, опция и т.п.) — это единственное, что специфично для конкретной интеграции, дальше всё стандартно:
|
||
|
||
```php
|
||
$currentValue = /* ваш способ прочитать сохранённое значение, например: */
|
||
$arElement['PROPERTY_DIAGRAM_VALUE'] ?? '';
|
||
```
|
||
|
||
Требований к содержимому со стороны бэкенда — минимум: это либо пустая строка, либо синтаксически валидный JSON. Всю содержательную валидацию (обязательные поля узлов и т.п.) делает само React-приложение — бэкенду глубже проверять не нужно. Пример реального значения — в `docs/bitrix-integration.md`, раздел 6.
|
||
|
||
## Проверка
|
||
|
||
1. Открыть страницу — iframe показывает пустую диаграмму (для нового элемента) или ранее сохранённую.
|
||
2. Отредактировать (подвигать фигуры, поменять текст).
|
||
3. Сабмитнуть форму как обычно (не внутри iframe).
|
||
4. Перезагрузить страницу — изменения на месте.
|
||
5. Проверить напрямую в БД/хранилище — там лежит валидный JSON.
|