81 lines
7.2 KiB
Markdown
81 lines
7.2 KiB
Markdown
# Интерактивные билеты охотника (a1-tir.ru/bilety)
|
||
|
||
Проект: скачать страницу билетов для экзамена на гражданское оружие и превратить в интерактивный тренажёр.
|
||
|
||
**Возможности:** номер правильного ответа скрыт · ответы кликабельны · выбранный неправильный → красный, правильный → зелёный.
|
||
|
||
**Файлы (исходники в `~/Downloads/`):**
|
||
- `bilety.html` — финальная статичная страница (кнопки вшиты в HTML, 181 карточка / 543 кнопки, ≈902 КБ); деплой в `/Library/WebServer/Documents/bilety.html`
|
||
- `origen_raw.html` — оригинальная скачанная копия (чистый источник для генерации)
|
||
- `gen2.py` — Python-генератор оригинала → `bilety.html` (статические кнопки, без JS-скрипта)
|
||
- `gen_fix.py`, `fix216.py` — точечные скрипты восстановления потерянных вопросов
|
||
- `bilety.js`, `bilety.css` — ранняя (отклонённая) версия enhancer'а на клиентском JS; в финале НЕ используются
|
||
- `a1-tir_bilety_raw.html` — оригинальная скачанная копия (альтернативное имя)
|
||
|
||
---
|
||
|
||
## Статус
|
||
- ✅ Страница скачана и переработана в интерактив.
|
||
- ✅ **Задеплоено в Eagle Dashboard Pages** — `/Library/WebServer/Documents/bilety.html` (902285 байт), URL `/local-pages/bilety.html`. Рестарт дашборда не нужен (сканируется живьём).
|
||
- ✅ **Все 181 вопрос покрыты** (1.1→1.93, 2.1→2.42, 58→103), каждая карточка ровно 3 кнопки, номер правильного скрыт, клик красит красный/зелёный.
|
||
- Изначально терялось 5 вопросов (1.93, 81, 83, 94, 2.16) из-за нестандартной обёртки `<em>` и вопроса без `<strong>`. Восстановлены скриптами `~/Downloads/gen_fix.py` и `~/Downloads/fix216.py`. Подробности — в `personal/tech/bilety-oxota-interaktiv.md`.
|
||
|
||
---
|
||
|
||
## Источник и структура (Tilda)
|
||
|
||
Страница — Tilda-лендинг, весь исходный HTML на одной строке (`a1-tir_bilety_raw.html`, строка 228 содержит контент).
|
||
|
||
Экзамен разложен по **4 блокам** `data-record-type="106"` (Tilda text-блок `div.field-text.t-text`):
|
||
- `rec465410787` — «Правовая подготовка», вопросы 1.1–1.49
|
||
- `rec1120437831` — без заголовка (продолжение правовой), 1.50–1.93
|
||
- `rec1120438216` — «Огневая подготовка», 2.1–2.41
|
||
- `rec465410788` — без заголовка (правила охоты), 58–103 · **вопросы из нескольких `<strong>`**
|
||
- Итого ~180 вопросов.
|
||
|
||
### Структура одного вопроса в DOM (проверено по сырым данным)
|
||
Каждый блок — один большой div; внутри `childNodes`:
|
||
- `<br/>` — разделители (шум)
|
||
- `<strong>Вопрос</strong>` — текст вопроса; может занимать **несколько подряд** `<strong>` (напр. вопрос 58); внутри возможен вложенный `<a>` (consultantplus)
|
||
- шапка раздела — `<p><strong>Заголовок</strong></p>` (strong вложен в p → не ловится как вопрос)
|
||
- текстовые узлы ответов: `"1. Текст"`, `"2. ..."`, `"3. ..."` (бывают ведущие пробелы)
|
||
- `<em>N</em>` — **номер правильного ответа** (чистое число)
|
||
- примечание — `<em><u>Примечание</u>: …</em>` (НЕ номер, сохранить)
|
||
- шум: `<strong> </strong>`, `<em> </em>`, пустой `<em>` с `<strong data-redactor-tag>`
|
||
|
||
Контентных `<img>`/`<svg>` в блоках вопросов нет — только текст.
|
||
|
||
---
|
||
|
||
## Решение — чисто клиентский JS (без библиотек/backend)
|
||
|
||
`bilety.js` на `DOMContentLoaded` обрабатывает каждый `[data-record-type="106"] .t-text`:
|
||
|
||
**Парсер `parseQuiz` (конечный автомат по childNodes):**
|
||
- `<strong>` с непустым текстом, не ` `: если у текущего вопроса ещё нет ответов и фаза `question` → **мержим** текст (многострочный вопрос); иначе → **новый вопрос**.
|
||
- текстовый узел, матчащий `^\d+[\.\s:]` и при отсутствии заданного `correct` → ответ `{n, text}` (фаза → `answers`); иначе — продолжение последнего ответа.
|
||
- `<em>` с чистым числом → `question.correct = N` (только в фазе `answers`, чтобы не спутать с «Примечание»); с текстом → `question.note = innerHTML` (сохранить); пустой/` ` → шум.
|
||
- `<p>` при `cur === null` и нет вопросов → `headerHTML` (заголовок раздела сохраняем).
|
||
- фильтр: оставляем только вопросы с `correct !== null`.
|
||
|
||
**Рендер `renderQuestion`:** карточка `.whale-q` → `.whale-q-text` (вопрос, `textContent`, жирный) + `.whale-answers` с кнопками `.whale-a` (`data-n` = номер ответа) + опционально `.whale-note`.
|
||
|
||
**Click-логика:** первый клик блокируется (`data-locked`). Если `n === correct` → класс `correct` (зелёный); иначе класс `wrong` (красный) **и** правильный ответ получает `correct` (чтобы показать верный). Управляется `data-correct` на карточке.
|
||
|
||
**Инъекция в страницу:**
|
||
- `<link rel="stylesheet" href="bilety.css">` перед `</head>`
|
||
- `<script src="bilety.js" charset="utf-8"></script>` перед `</body>`
|
||
- мета для дашборда в `<head>`: `eagle-test-purpose`, `eagle-test-contains`.
|
||
|
||
**Дизайн .whale-a:** карточки-кнопки (flex column), `#f5f5f5`, hover `#ececec`; `.correct`=`#d4edda`/`#28a745`, `.wrong`=`#f8d7da`/`#dc3545` (с `!important`).
|
||
|
||
---
|
||
|
||
## Pitfall: локальное открытие `file://`
|
||
Страница Tilda использует `sessionStorage` для анимации появления `.t-records` — при открытии локально это работает. Просто открыть `bilety.html` двойным кликом достаточно для проверки функционала.
|
||
|
||
---
|
||
|
||
## Связанное
|
||
- Доставка в дашборд — механика Pages: `personal/projects/personal-os/eagle-dashboard.md`.
|