137 lines
5.5 KiB
HTML
137 lines
5.5 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Прохождение теста{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="test-detail-page" id="attempt-root" data-test-id="{{ test_id }}" data-attempt-id="{{ attempt_id }}">
|
|
<p class="link-back"><a href="/tests">← к списку тестов</a></p>
|
|
<h1 class="font-headline" style="font-size:1.35rem;margin-top:0;" id="attempt-title">Загрузка…</h1>
|
|
<p class="text-muted" style="margin-top:0;" id="attempt-subtitle"></p>
|
|
<p class="error-text" id="attempt-error" style="display:none;"></p>
|
|
|
|
<ol id="questions-list" style="padding-left:1.25rem;"></ol>
|
|
|
|
<div class="inline-actions" style="margin-top:1rem;">
|
|
<button type="button" class="btn btn-primary" id="submit-attempt-btn">Завершить тест</button>
|
|
</div>
|
|
|
|
<div id="attempt-result" class="surface-card" style="display:none;margin-top:1rem;padding:1rem;"></div>
|
|
</div>
|
|
|
|
<script>
|
|
(() => {
|
|
const root = document.getElementById('attempt-root');
|
|
const testId = root.dataset.testId;
|
|
const attemptId = root.dataset.attemptId;
|
|
const titleEl = document.getElementById('attempt-title');
|
|
const subEl = document.getElementById('attempt-subtitle');
|
|
const errEl = document.getElementById('attempt-error');
|
|
const listEl = document.getElementById('questions-list');
|
|
const resultEl = document.getElementById('attempt-result');
|
|
const submitBtn = document.getElementById('submit-attempt-btn');
|
|
let playData = null;
|
|
const selections = {};
|
|
|
|
function esc(s) {
|
|
return String(s ?? '').replace(/[&<>"']/g, (m) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));
|
|
}
|
|
function setErr(msg) {
|
|
errEl.textContent = msg || 'Ошибка.';
|
|
errEl.style.display = '';
|
|
}
|
|
function isSelected(qid, oid) {
|
|
return (selections[String(qid)] || []).includes(String(oid));
|
|
}
|
|
function toggle(qid, oid, multi) {
|
|
const k = String(qid);
|
|
const cur = selections[k] || [];
|
|
const id = String(oid);
|
|
if (multi) {
|
|
selections[k] = cur.includes(id) ? cur.filter(x => x !== id) : [...cur, id];
|
|
return;
|
|
}
|
|
selections[k] = [id];
|
|
}
|
|
function renderQuestions() {
|
|
listEl.innerHTML = '';
|
|
for (const q of (playData.questions || [])) {
|
|
const li = document.createElement('li');
|
|
li.style.marginBottom = '1.5rem';
|
|
li.innerHTML = '<p style="margin-top:0;margin-bottom:0.5rem;">' + esc(q.text) + '</p>';
|
|
const ul = document.createElement('ul');
|
|
ul.style.listStyle = 'none';
|
|
ul.style.padding = '0';
|
|
ul.style.margin = '0';
|
|
for (const o of (q.options || [])) {
|
|
const row = document.createElement('li');
|
|
row.style.marginBottom = '6px';
|
|
const type = q.hasMultipleAnswers ? 'checkbox' : 'radio';
|
|
const name = 'q-' + q.id;
|
|
row.innerHTML =
|
|
'<label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer;">' +
|
|
'<input type="' + type + '" ' + (q.hasMultipleAnswers ? '' : ('name="' + name + '"')) + ' ' + (isSelected(q.id, o.id) ? 'checked' : '') + ' />' +
|
|
'<span>' + esc(o.text) + '</span>' +
|
|
'</label>';
|
|
const input = row.querySelector('input');
|
|
input.addEventListener('change', () => {
|
|
toggle(q.id, o.id, q.hasMultipleAnswers);
|
|
renderQuestions();
|
|
});
|
|
ul.appendChild(row);
|
|
}
|
|
li.appendChild(ul);
|
|
listEl.appendChild(li);
|
|
}
|
|
}
|
|
|
|
async function load() {
|
|
try {
|
|
const r = await fetch('/api/tests/' + testId + '/attempts/' + attemptId + '/play');
|
|
const data = await r.json().catch(() => ({}));
|
|
if (!r.ok) throw new Error(data.error || 'Не удалось открыть попытку.');
|
|
playData = data;
|
|
titleEl.textContent = data.testTitle || 'Прохождение теста';
|
|
subEl.textContent = 'Порог зачёта: ' + (data.passingThreshold ?? 0) + '%.';
|
|
if (!Array.isArray(data.questions) || !data.questions.length) {
|
|
setErr('В активной версии нет вопросов.');
|
|
submitBtn.disabled = true;
|
|
return;
|
|
}
|
|
renderQuestions();
|
|
} catch (e) {
|
|
setErr(e.message);
|
|
submitBtn.disabled = true;
|
|
}
|
|
}
|
|
|
|
async function submit() {
|
|
submitBtn.disabled = true;
|
|
submitBtn.textContent = 'Отправка…';
|
|
try {
|
|
const r = await fetch('/api/tests/' + testId + '/attempts/' + attemptId + '/submit', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ answers: selections }),
|
|
});
|
|
const data = await r.json().catch(() => ({}));
|
|
if (!r.ok) throw new Error(data.error || 'Не удалось завершить попытку.');
|
|
resultEl.style.display = '';
|
|
resultEl.innerHTML =
|
|
'<h3 style="margin-top:0;">Результат</h3>' +
|
|
'<p>Правильно: <strong>' + data.correctCount + '</strong> из ' + data.totalQuestions +
|
|
' (' + data.percent + '%). Порог: ' + data.passingThreshold + '%.</p>' +
|
|
'<p class="' + (data.passed ? 'text-muted' : 'error-text') + '">' + (data.passed ? 'Зачёт.' : 'Незачёт.') + '</p>' +
|
|
'<p><a href="/tests/' + testId + '/attempts/' + data.attemptId + '/review">Разбор попытки</a></p>';
|
|
submitBtn.style.display = 'none';
|
|
} catch (e) {
|
|
setErr(e.message);
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = 'Завершить тест';
|
|
}
|
|
}
|
|
|
|
submitBtn.addEventListener('click', submit);
|
|
load();
|
|
})();
|
|
</script>
|
|
{% endblock %}
|