admin-ui
Админка — роутинг, шапка с кнопками раздела, форма настроек. Использовать при правке frontend/src/admin.
SKILL.md.claude/skills/admin-ui/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.