/
Coderdev
/
web-nodejs-labs
Обзор
Документация
Войти
/
Coderdev
/
web-nodejs-labs
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
public/lab12/task1/index.html
230 строк
11 KB
Coderdev
1
26 май 2026, 12:10
26 май 2026, 12:10
d97058c
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ЛР12 — Валидация формы</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Space Mono','Courier New',monospace; background: #fafafa; color: #1a1a1a; font-size: 13px; line-height: 1.6; } .wrap { max-width: 680px; margin: 0 auto; padding: 40px 20px; } h1 { font-size: 18px; font-weight: 700; text-transform: uppercase; letter-spacing: 2px; margin-bottom: 8px; } .subtitle { font-size: 11px; color: #999; margin-bottom: 32px; } h2 { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 3px; color: #999; margin-bottom: 16px; padding-bottom: 6px; border-bottom: 1px solid #e5e5e5; } .panel { background: #fff; padding: 24px; margin-bottom: 20px; border: 1px solid #e5e5e5; } label { display: block; font-size: 10px; text-transform: uppercase; letter-spacing: 1px; color: #999; margin-bottom: 4px; } .field { margin-bottom: 16px; } input, select { font-family: inherit; font-size: 12px; width: 100%; height: 34px; padding: 4px 10px; border: 1px solid #ddd; background: #fafafa; color: #1a1a1a; outline: none; transition: border-color 0.15s; } input:focus, select:focus { border-color: #1a1a1a; } input.invalid { border-color: #c0392b; } input.valid { border-color: #27ae60; } .hint { font-size: 10px; color: #bbb; margin-top: 3px; } .hint.err { color: #c0392b; } button { font-family: inherit; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; display: inline-block; cursor: pointer; padding: 8px 20px; border: 1px solid #1a1a1a; background: #1a1a1a; color: #fff; transition: 0.15s; } button:hover { background: #444; } .result-area { margin-top: 24px; } .result-box { padding: 14px 18px; border: 1px solid #e5e5e5; background: #fff; font-size: 12px; } .result-box.ok { border-color: #27ae60; background: #f0faf4; } .result-box.err { border-color: #c0392b; background: #fdf0f0; } .result-label { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 2px; margin-bottom: 6px; } .result-label.ok { color: #27ae60; } .result-label.err { color: #c0392b; } .result-data { font-size: 11px; color: #555; margin-top: 8px; border-top: 1px solid #eee; padding-top: 8px; } .result-data pre { white-space: pre-wrap; word-break: break-all; font-family: inherit; } .mode-info { font-size: 10px; color: #aaa; margin-top: 4px; } .back-link { margin-top: 40px; } .back-link a { font-size: 11px; color: #999; text-decoration: none; text-transform: uppercase; letter-spacing: 1px; } .back-link a:hover { color: #1a1a1a; } .divider { height: 1px; background: #e5e5e5; margin: 8px 0 16px; } .section-note { font-size: 10px; color: #999; padding: 8px 12px; border-left: 2px solid #ddd; margin-bottom: 16px; } </style> </head> <body> <div class="wrap"> <h1>Валидация формы</h1> <p class="subtitle">Регулярные выражения — клиент и сервер</p> <div class="panel"> <h2>Данные студента</h2> <div class="section-note"> Валидация выполняется дважды: сначала на клиенте, затем на сервере выбранным способом. </div> <div class="field"> <label>ФИО</label> <input id="fio" type="text" placeholder="Иванов Иван Иванович" autocomplete="off"> <div class="hint" id="fioHint">Только русские буквы, пробел, дефис</div> </div> <div class="field"> <label>Шифр зачётной книжки</label> <input id="recordBook" type="text" placeholder="ПИ-2025-11" autocomplete="off"> <div class="hint" id="recordHint">Формат: ФАК-ГГГГ-НН (факультеты: ПИ, ИТ, БИ; год 2020–2025; номер 1–99)</div> </div> <div class="field"> <label>Группа</label> <input id="group" type="text" placeholder="ПИ-2401" autocomplete="off"> <div class="hint" id="groupHint">Формат: ФАК-YYНН (год 20–25; группа 01–04; факультеты: ПИ, ИТ, БИ, ЭС, МХ, ЮФ, ФК)</div> </div> <div class="divider"></div> <div class="field"> <label>Способ проверки на бэкенде</label> <select id="checkMode"> <option value="no-check">Без проверки на сервере (/no-check)</option> <option value="manual-check" selected>Ручная проверка (/manual-check)</option> <option value="schema-check">Проверка по схеме (/schema-check)</option> </select> <div class="mode-info" id="modeInfo">Сервер проверяет данные вручную через regexp.test()</div> </div> <button onclick="submitForm()">Проверить</button> </div> <div class="result-area" id="resultArea" style="display:none"></div> <div class="back-link"><a href="/lab12/">← К списку заданий</a></div> </div> <script> const API = '/api/lab12'; const fioRegexp = /^[А-ЯЁа-яё]+([\s\-][А-ЯЁа-яё]+)+$/; const recordBookRegexp = /^(ПИ|ИТ|БИ)-(202[0-5])-\d{1,2}$/; const groupRegexp = /^(ПИ|ИТ|БИ|ЭС|МХ|ЮФ|ФК)-2[0-5]0[1-4]$/; const modeDescriptions = { 'no-check': 'Сервер НЕ проверяет данные — принимает всё', 'manual-check': 'Сервер проверяет данные вручную через regexp.test()', 'schema-check': 'Сервер проверяет данные через Fastify JSON Schema' }; document.getElementById('checkMode').addEventListener('change', function () { document.getElementById('modeInfo').textContent = modeDescriptions[this.value]; }); function validateField(input, regexp, hintEl, okMsg, errMsg) { const val = input.value.trim(); if (!val) { input.className = ''; hintEl.className = 'hint'; hintEl.textContent = okMsg; return false; } const ok = regexp.test(val); input.className = ok ? 'valid' : 'invalid'; hintEl.className = ok ? 'hint' : 'hint err'; hintEl.textContent = ok ? okMsg : errMsg; return ok; } document.getElementById('fio').addEventListener('input', function () { validateField( this, fioRegexp, document.getElementById('fioHint'), 'Только русские буквы, пробел, дефис', 'Ошибка: используйте только русские буквы, пробел или дефис' ); }); document.getElementById('recordBook').addEventListener('input', function () { validateField( this, recordBookRegexp, document.getElementById('recordHint'), 'Формат: ФАК-ГГГГ-НН (факультеты: ПИ, ИТ, БИ; год 2020–2025; номер 1–99)', 'Ошибка: например ПИ-2025-11 (год 2020–2025, факультеты ПИ/ИТ/БИ)' ); }); document.getElementById('group').addEventListener('input', function () { validateField( this, groupRegexp, document.getElementById('groupHint'), 'Формат: ФАК-YYНН (год 20–25; группа 01–04; факультеты: ПИ, ИТ, БИ, ЭС, МХ, ЮФ, ФК)', 'Ошибка: например ПИ-2401 (год 20–25, группа 01–04)' ); }); function showResult(ok, title, messages, data) { const area = document.getElementById('resultArea'); const list = Array.isArray(messages) ? messages : [messages]; const msgHtml = list.length > 1 ? '<ul style="margin:6px 0 0 16px">' + list.map(m => '<li>' + m + '</li>').join('') + '</ul>' : '<div>' + list[0] + '</div>'; const dataStr = data ? '<div class="result-data"><pre>' + JSON.stringify(data, null, 2) + '</pre></div>' : ''; area.innerHTML = ` <div class="result-box ${ok ? 'ok' : 'err'}"> <div class="result-label ${ok ? 'ok' : 'err'}">${title}</div> ${msgHtml} ${dataStr} </div>`; area.style.display = 'block'; } async function submitForm() { const fio = document.getElementById('fio').value.trim(); const recordBookNumber = document.getElementById('recordBook').value.trim(); const group = document.getElementById('group').value.trim(); const mode = document.getElementById('checkMode').value; // Клиентская валидация if (mode !== 'no-check') { const errors = []; if (!fio) errors.push('Поле ФИО не заполнено'); else if (!fioRegexp.test(fio)) errors.push('Некорректный формат ФИО (только русские буквы, пробел, дефис)'); if (!recordBookNumber) errors.push('Поле «Шифр зачётной книжки» не заполнено'); else if (!recordBookRegexp.test(recordBookNumber)) errors.push('Некорректный формат шифра зачётной книжки (пример: ПИ-2025-10)'); if (!group) errors.push('Поле «Группа» не заполнено'); else if (!groupRegexp.test(group)) errors.push('Некорректный формат группы (пример: ПИ-2401)'); if (errors.length) { showResult(false, 'Ошибка клиента', errors, null); return; } } // Серверная валидация try { const r = await fetch(API + '/' + mode, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fio, recordBookNumber, group }) }); const data = await r.json(); if (!r.ok) { showResult(false, 'Ошибка сервера [' + r.status + ']', data.error || 'Неизвестная ошибка', null); } else { showResult(true, 'Проверка пройдена', data.message, data.data); } } catch (e) { showResult(false, 'Ошибка сети', e.message, null); } } </script> </body> </html>