Menu Renderer — BR 4.1
Контракты
- Фронт-спека: Внешние меню — Шаблоны монитора
- API endpoints в Catalog Service:
GET /r/{slug},GET /r/{slug}/data, WebSocket/r/{slug}/stream
Mini-bundle для рендера меню на мониторе. Отдельная сборка (Vite), деплоится в Catalog Service/resources/static/menu-renderer/.
Где живёт
В монорепо erp-admin/:
erp-admin/
├── apps/
│ ├── menu-renderer/ ← НОВОЕ. Vite project
│ │ ├── package.json
│ │ ├── vite.config.ts
│ │ ├── index.html (template для SSR)
│ │ ├── src/
│ │ │ ├── main.ts
│ │ │ ├── templates/
│ │ │ │ ├── grid.ts
│ │ │ │ ├── slider.ts
│ │ │ │ └── list.ts
│ │ │ ├── live-update.ts (WebSocket + polling)
│ │ │ ├── styles/
│ │ │ │ └── shared.css
│ │ │ └── utils.ts
│ │ └── tsconfig.json
│ └── menu-renderer-offline/ ← НОВОЕ. Bundle для offline ZIP (без WebSocket)
└── ...
Build → dist/menu-renderer/{index.js, style.css} — копируется CI/CD в Catalog Service.
Что делаем
Vanilla JS / TS клиент рендера
-
apps/menu-renderer/src/main.ts:- Парсит embedded JSON меню из
<script id="menu-data"> - Определяет шаблон по
menu.template - Вызывает
renderTemplate()который рисует DOM - Подключает live-update (WebSocket → polling fallback)
- Парсит embedded JSON меню из
-
apps/menu-renderer/src/templates/grid.ts:- Renders сетку карточек с заголовками категорий
- CSS Grid + clamp() для адаптивности
- Auto-перелистывание категорий каждые 15 сек если все не помещаются
-
apps/menu-renderer/src/templates/slider.ts:- Один товар на экран с auto-advance каждые 5 сек
- Crossfade transition
-
apps/menu-renderer/src/templates/list.ts:- Длинный список с большой ценой
- Группировка по категориям
Live-update
-
apps/menu-renderer/src/live-update.ts:connect(slug)— открыть WebSocket/r/{slug}/stream- На
menu_updated→fetch /r/{slug}/data→ сравнить version_hash →rerender(newData)если изменилось - Reconnect с экспоненциальным backoff (1s → 2s → 4s → 8s → 30s)
- Если connection lost > 60 сек → переключиться на polling (
setInterval30 сек) - При восстановлении WebSocket — отключить polling
- Heartbeat: ответ
pongнаpingот сервера; если нет ping > 60 сек — считаем lost
-
При
menu_unpublished/menu_archived— показать full-screen banner «Меню снято с публикации»
Адаптивность
- CSS-переменные:
:root { --columns-grid: 4; --font-base: 18px; } @media (min-width: 3000px) { /* 4K */ :root { --columns-grid: 6; --font-base: 28px; } } @media (orientation: portrait) { /* 9:16 */ :root { --columns-grid: 2; } } - Тестировать на 3 разрешениях: 1920×1080, 3840×2160, 1080×1920
Шрифты и assets
- Inter (или подобный) — bundled в bundle (Inter-Bold.woff2 ~80KB) для гарантированного рендера без интернета
- Иконки категорий — SVG из системы (если задана
icon_url) или fallback emoji
Server-side template (Thymeleaf или Mustache)
- В Catalog Service
resources/templates/menu.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title th:text="${menu.name}"></title>
<link rel="stylesheet" href="/static/menu-renderer/style.css">
</head>
<body>
<div id="menu-root"></div>
<script id="menu-data" type="application/json" th:utext="${menuJson}"></script>
<script src="/static/menu-renderer/index.js"></script>
</body>
</html>- Catalog Service подставляет
menuJson(resolved menu с overrides + stop-list filter) иslugдля WebSocket-URL - Client JS читает
menu-dataи рендерит
Offline ZIP version
-
apps/menu-renderer-offline/src/main.ts:- То же что live-версия, но БЕЗ live-update модуля
- Меню вшито прямо в bundle при сборке (server-side инжект через template’ы при создании ZIP)
-
OfflineZipBuilderв Catalog Service:- Использует тот же template
menu.html, но JS изapps/menu-renderer-offline/ - Скачивает картинки product → конвертирует пути к
assets/images/{filename} - Упаковывает всё в ZIP
- Использует тот же template
Preview-режим
- При
?preview=1:- В правом нижнем углу
🟡 PREVIEWиндикатор (фиксированный) - Не подключается WebSocket / polling
- Banner «Меню снято с публикации» не показывается даже если
archived(для тестирования)
- В правом нижнем углу
CI / deployment
-
pnpm --filter menu-renderer build→dist/menu-renderer/index.js,style.css - CI копирует bundle в
erp-catalog-service/src/main/resources/static/menu-renderer/перед сборкой Spring Boot - Версионирование bundle через hash (
index.{hash}.js) — кэширование 1 год
Не делаем (P1+)
- ❌ Server-side rendering (Astro/Next) — пока vanilla TS достаточно
- ❌ Multi-language UI — все надписи на русском
- ❌ Анимации beyond crossfade — добавим позже если надо
- ❌ Touch-события для планшетов — это монитор, не киоск
- ❌ Сбор метрик (FCP / LCP / heartbeat) — P1+
Verification
- На demo-coffee опубликовать меню → открыть
https://erp-test.nirbi.ru/r/{slug}в Chrome — рендер появляется - Запустить Chrome в kiosk-mode (F11) — никаких scrollbars, выглядит как реклама
- Изменить override в редакторе — через ≤30 сек kiosk-вкладка обновилась без F5
- Отключить интернет на машине → подождать → kiosk показывает «Нет соединения, последнее обновление в HH:MM»
- Включить интернет → переподключение → меню снова актуально
- Снять с публикации → kiosk показывает «Меню снято с публикации»
Ссылки
- BR 4.1
- Шаблоны монитора (фронт-спека)
- Catalog Service · API — render endpoints