admin-ui

Админка — роутинг, шапка с кнопками раздела, форма настроек. Использовать при правке frontend/src/admin.

SKILL.md

GitHub

Админка

  • Админка использует хэш-роутинг: одинаково работает и на /admin.html, и на /admin. Тема у неё всегда светлая.
  • Блоки страниц (карточки настроек, таблицы товаров и операций) оформлены общими правилами в admin/styles.css, а не в scoped-стилях страниц: блоки собраны из компонентов Element Plus, и до их внутренней разметки scoped-правила не достают. Всё под .admin, чтобы ничего не утекло в киоск.
  • Заголовок блока — его главная примета: крупнее текста внутри, на своей подложке и с полосой акцента слева. Таблицы оформлены как те же блоки, иначе страница распадается на «карточки здесь, голая таблица там».
  • Кнопки раздела живут в шапке админки: страница телепортирует их в #admin-actions (<Teleport … defer>, иначе цель ещё не отрисована). Форма настроек длинная, и кнопка сохранения не должна уезжать под нижний край вместе с ней.
  • Тара и обнуление живут в админке, а не в шапке киоска: покупателю они не нужны, а случайное нажатие тары посреди взвешивания портит покупку.
  • Вкладка «Етикетка» — конструктор: слева живое превью с перетаскиваемыми рамками блоков, справа их список и свойства выбранного. Своей отрисовки этикетки во фронтенде нет и не должно появиться — картинку рисует бэкенд тем же рендером, что и печать (см. скилл label-printing). Рамки позиционируются в процентах от листа, поэтому переживают любое масштабирование окна, а перетаскивание пересчитывает миллиметры через ширину листа.
  • В конструкторе есть то, без чего редактор не редактор: отмена и повтор (Ctrl+Z, Ctrl+Y; хранятся снимки раскладки целиком — в ней десяток блоков, и обратные операции городить незачем), стрелки для точной установки (0.5 мм, с Shift 5 мм; серия нажатий подряд — одна запись в истории), Ctrl+D для копии, Delete для удаления.
  • Прилипание к краям этикетки и к краям соседних блоков (порог 1.2 мм, Alt отключает): ровнять блоки на глаз по растру в четверть натуральной величины невозможно, а бумага разницу покажет. Направляющие видно ровно в момент прилипания.
  • Уход с несохранённой раскладкой перехватывается (onBeforeRouteLeave плюс beforeunload), а у кнопки сохранения стоит точка, пока есть незаписанные правки: соседний пункт меню в одном клике, и работа исчезала бы молча.
  • Ответы превью нумеруются: при быстрой правке они приходят вперемешку, и медленный затирал бы свежий. Ответ с устаревшим номером отбрасывается.
  • Блок, ушедший за край этикетки, помечается красным и пересчитывается в предупреждение под листом: на бумаге он просто не напечатается.
  • Метрологические параметры в админку не выносятся (см. скилл device-settings).

Страница настроек

Полсотни настроек разложены по семи блокам про части экрана прибора (SECTIONS в SettingsView.vue: устройство, этикетка и штрихкод, взвешивание и печать, экран и сессия, каталог, кнопки и клавиатура, размеры и цвета), а внутри больших блоков — подзаголовки (.group: «після друку», «заставки», «повідомлення», «сітка», «що на картці», «фото», «нижня панель», «клавіатура», «кеглі», «кольори»): десять полей подряд читаются как список, а не как настройки чего-то конкретного.

Выключатели живут отдельным списком (.switch-row), а не в сетке полей: у каждого своя строка-пояснение, и в двух колонках они рвутся по-разному. Числа с парой («столбцов × строк», «ширина × высота») держатся в одной строке (.row.nowrap) — разорванные по строкам, они читаются как два независимых поля. Пресеты и «одразу на ваги» переехали из шапки админки в панель самой страницы: в шапке они мешались с кнопками сохранения и датами обновлений. У каждого блока схема экрана с подсвеченной зоной — components/KioskSketch.vue, рисуется SVG кодом, а не картинкой из файла: подсветка зоны — заливка того же прямоугольника, и схема красится в цвета темы. Слева прилипшее оглавление с теми же схемами; активный пункт — по IntersectionObserver. Поля в две колонки с подписью сверху: вдвое короче простыни с подписями слева, а подсказка остаётся под своим полем. Новую настройку класть в блок про её часть экрана, а не в конец.

Даты обновлений в шапке

Шапка всегда показывает четыре отметки (GET /api/updated-at): каталог из 1С (время и число позиций), правка настроек, версия ПО и время последнего успешного опроса прибора. Оператор приходит с вопросом «свежий ли тут каталог и та ли сборка» — ответ должен быть на экране, а не за тремя кликами; замершее время опроса краснеет, когда прибор перестал отвечать.

Откуда что берётся: выгрузка — data/state.json (services/device_state.py, отметка ставится в import_1c), настройки — время файла settings.json, версия — S2L_BUILD_SHA/S2L_BUILD_AT из образа (их подставляет сборка в CI), а на машине разработчика их нет, и версией считается время сборки фронта. Отметку выгрузки нельзя держать в DeviceSettings: админка шлёт настройки целиком, и сохранение формы затирало бы её значением, прочитанным при открытии страницы.

«Одразу на ваги»

Переключатель в шапке страницы настроек: правка уезжает на прибор без «Зберегти», с паузой 400 мс (ползунок и поле дают десяток правок в секунду). Оператор настраивает сетку и кегли, глядя на сам прибор, — ходить к кнопке после каждого щелчка стрелкой бессмысленно. Выбор хранится в localStorage браузера, а не в настройках прибора: это привычка того, кто сейчас настраивает, а не свойство весов.

Название магазина из живой отправки исключено: оно печатается на этикетке, и прибор не должен увидеть его недописанным — в payload подставляется то, что уже стоит на приборе, а само имя уезжает по «Зберегти». Ответ сервера в форму не пишется: оператор может печатать прямо сейчас, и подмена значений стёрла бы половину слова. Снимок «что на приборе» держится в applied — по нему же отсекаются пустые отправки.

Пароль

Админка закрыта HTTP Basic (backend/app/auth.py, middleware в main.py): логин S2L_ADMIN_USER (по умолчанию admin), пароль S2L_ADMIN_PASSWORD из .env. Пустой пароль — админка открыта (режим разработки, при старте предупреждение в логе). Basic выбран за то, что ему не нужны ни форма входа, ни сессии: браузер спрашивает пароль один раз и сам подставляет его во все XHR той же страницы. Под паролем: /admin*, все не-GET запросы к /api/* кроме того, что делает сам киоск (/api/print, /api/scale/tare|zero) и приёма из 1С (свой токен), а из чтения — журнал /api/transactions и растры /labels/. Всё, что читает киоск, остаётся открытым. Осторожно с проверками: PUT /api/settings с пустым телом {} — это не «ничего», а сброс всех настроек к заводским. И не гонять настройки через Invoke-RestMethod Windows PowerShell 5.1: ответ без charset он читает как Latin-1, «грн» превращается в «Ð³Ñн», и обратный PUT записывает это в файл. Для правок настроек из консоли — python с .decode("utf-8") и ensure_ascii=False.