345 lines
12 KiB
JavaScript
345 lines
12 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
|
|
var state = {
|
|
currentId: 1,
|
|
correctCount: 0,
|
|
totalCount: 0,
|
|
selectedOption: null,
|
|
isLoading: false,
|
|
answered: false,
|
|
currentKey: window.INITIAL_KEY || null
|
|
};
|
|
|
|
var elements = {
|
|
questionCard: document.getElementById('questionCard'),
|
|
navigation: document.getElementById('navigation'),
|
|
details: document.getElementById('details'),
|
|
loading: document.getElementById('loading'),
|
|
correctCount: document.getElementById('correctCount'),
|
|
totalCount: document.getElementById('totalCount'),
|
|
feedback: document.getElementById('feedback'),
|
|
feedbackIcon: document.getElementById('feedbackIcon'),
|
|
feedbackText: document.getElementById('feedbackText'),
|
|
overlay: document.getElementById('overlay'),
|
|
detailAuthor: document.getElementById('detailAuthor'),
|
|
detailDynasty: document.getElementById('detailDynasty'),
|
|
detailType: document.getElementById('detailType'),
|
|
detailGrade: document.getElementById('detailGrade')
|
|
};
|
|
|
|
function getUrlParam(name) {
|
|
var urlParams = new URLSearchParams(window.location.search);
|
|
return urlParams.get(name);
|
|
}
|
|
|
|
function setUrlParam(name, value) {
|
|
var url = new URL(window.location.href);
|
|
url.searchParams.set(name, value);
|
|
window.history.replaceState({}, '', url);
|
|
}
|
|
|
|
function fetchData(url, callback, errorCallback) {
|
|
if (!state.currentKey) {
|
|
errorCallback('未初始化 Key');
|
|
return;
|
|
}
|
|
|
|
var separator = url.indexOf('?') !== -1 ? '&' : '?';
|
|
var fullUrl = url + separator + 'key=' + encodeURIComponent(state.currentKey);
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', fullUrl, true);
|
|
xhr.setRequestHeader('Accept', 'application/json');
|
|
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState === 4) {
|
|
if (xhr.status === 200) {
|
|
try {
|
|
var response = JSON.parse(xhr.responseText);
|
|
if (response.new_key) {
|
|
state.currentKey = response.new_key;
|
|
}
|
|
callback(response);
|
|
} catch (e) {
|
|
errorCallback('解析响应失败');
|
|
}
|
|
} else if (xhr.status === 429) {
|
|
errorCallback('请求过于频繁,请稍后再试');
|
|
} else {
|
|
errorCallback('请求失败,状态码: ' + xhr.status);
|
|
}
|
|
}
|
|
};
|
|
|
|
xhr.onerror = function() {
|
|
errorCallback('网络错误');
|
|
};
|
|
|
|
xhr.send();
|
|
}
|
|
|
|
function fetchQuestion(id) {
|
|
if (state.isLoading) return;
|
|
|
|
state.isLoading = true;
|
|
state.answered = false;
|
|
state.selectedOption = null;
|
|
|
|
showLoading();
|
|
|
|
fetchData('api.php?id=' + id, function(response) {
|
|
state.isLoading = false;
|
|
|
|
if (response.success) {
|
|
renderQuestion(response.data);
|
|
updateNavigation(id);
|
|
updateDetails(response.data);
|
|
} else {
|
|
showError(response.message || '获取题目失败');
|
|
}
|
|
}, function(error) {
|
|
state.isLoading = false;
|
|
showError(error);
|
|
});
|
|
}
|
|
|
|
function submitAnswer(id, answer) {
|
|
if (state.isLoading || state.answered) return;
|
|
|
|
state.isLoading = true;
|
|
|
|
fetchData('api.php?id=' + id + '&msg=' + answer, function(response) {
|
|
state.isLoading = false;
|
|
|
|
if (response.success) {
|
|
state.totalCount++;
|
|
updateStats();
|
|
|
|
if (response.type === 'correct') {
|
|
state.correctCount++;
|
|
updateStats();
|
|
showFeedback('correct', '🎉 ' + response.message);
|
|
markOption(answer, 'correct');
|
|
|
|
setTimeout(function() {
|
|
hideFeedback();
|
|
if (response.next_question) {
|
|
state.currentId++;
|
|
setUrlParam('id', state.currentId);
|
|
renderQuestion(response.next_question);
|
|
updateNavigation(state.currentId);
|
|
updateDetails(response.next_question);
|
|
} else {
|
|
state.currentId++;
|
|
setUrlParam('id', state.currentId);
|
|
fetchQuestion(state.currentId);
|
|
}
|
|
}, 1500);
|
|
} else if (response.type === 'wrong') {
|
|
showFeedback('wrong', '😢 ' + response.message);
|
|
markOption(answer, 'wrong');
|
|
setTimeout(function() {
|
|
hideFeedback();
|
|
clearOptionMark(answer);
|
|
}, 2000);
|
|
} else if (response.type === 'hint') {
|
|
showHintMessage(response.message);
|
|
}
|
|
} else {
|
|
showFeedback('wrong', response.message || '提交失败');
|
|
setTimeout(hideFeedback, 2000);
|
|
}
|
|
}, function(error) {
|
|
state.isLoading = false;
|
|
showFeedback('wrong', error);
|
|
setTimeout(hideFeedback, 2000);
|
|
});
|
|
}
|
|
|
|
function getHint(id) {
|
|
if (state.isLoading) return;
|
|
|
|
state.isLoading = true;
|
|
|
|
fetchData('api.php?id=' + id + '&msg=提示', function(response) {
|
|
state.isLoading = false;
|
|
|
|
if (response.success && response.type === 'hint') {
|
|
showHintMessage(response.message);
|
|
} else {
|
|
showFeedback('wrong', response.message || '获取提示失败');
|
|
setTimeout(hideFeedback, 2000);
|
|
}
|
|
}, function(error) {
|
|
state.isLoading = false;
|
|
showFeedback('wrong', error);
|
|
setTimeout(hideFeedback, 2000);
|
|
});
|
|
}
|
|
|
|
function showLoading() {
|
|
elements.questionCard.innerHTML = '<div class="loading">加载中</div>';
|
|
elements.details.style.display = 'none';
|
|
}
|
|
|
|
function showError(message) {
|
|
elements.questionCard.innerHTML =
|
|
'<div class="error-message">' +
|
|
'<p>❌ ' + message + '</p>' +
|
|
'<button class="retry-btn" onclick="PoetryQuiz.retry()">🔄 重试</button>' +
|
|
'</div>';
|
|
elements.details.style.display = 'none';
|
|
}
|
|
|
|
function renderQuestion(data) {
|
|
var html = '<span class="question-number">第 ' + data.id + ' 题</span>';
|
|
html += '<div class="question">' + escapeHtml(data.question) + '</div>';
|
|
html += '<div class="options">';
|
|
|
|
data.options.forEach(function(option) {
|
|
html += '<button class="option-btn" data-num="' + option.num + '" onclick="PoetryQuiz.selectOption(' + option.num + ')">';
|
|
html += '<span class="option-num">' + option.num + '</span>';
|
|
html += '<span class="option-text">' + escapeHtml(option.text) + '</span>';
|
|
html += '</button>';
|
|
});
|
|
|
|
html += '</div>';
|
|
html += '<button class="hint-btn" onclick="PoetryQuiz.getHint()">';
|
|
html += '💡 获取提示';
|
|
html += '</button>';
|
|
html += '<div class="hint-message" id="hintMessage"></div>';
|
|
|
|
elements.questionCard.innerHTML = html;
|
|
}
|
|
|
|
function updateNavigation(currentId) {
|
|
var html = '<button class="nav-btn random" onclick="PoetryQuiz.goToRandomQuestion()">🎲 随机</button>';
|
|
var start = Math.max(1, currentId - 4);
|
|
var end = currentId + 5;
|
|
|
|
for (var i = start; i <= end; i++) {
|
|
var activeClass = i === currentId ? ' active' : '';
|
|
html += '<button class="nav-btn' + activeClass + '" onclick="PoetryQuiz.goToQuestion(' + i + ')">' + i + '</button>';
|
|
}
|
|
|
|
elements.navigation.innerHTML = html;
|
|
}
|
|
|
|
function updateDetails(data) {
|
|
elements.detailAuthor.textContent = data.author || '-';
|
|
elements.detailDynasty.textContent = data.dynasty || '-';
|
|
elements.detailType.textContent = data.type || '-';
|
|
elements.detailGrade.textContent = data.grade || '-';
|
|
elements.details.style.display = 'grid';
|
|
}
|
|
|
|
function updateStats() {
|
|
elements.correctCount.textContent = state.correctCount;
|
|
elements.totalCount.textContent = state.totalCount;
|
|
}
|
|
|
|
function selectOption(num) {
|
|
if (state.answered || state.isLoading) return;
|
|
|
|
var buttons = document.querySelectorAll('.option-btn');
|
|
buttons.forEach(function(btn) {
|
|
btn.classList.remove('selected');
|
|
});
|
|
|
|
var selectedBtn = document.querySelector('.option-btn[data-num="' + num + '"]');
|
|
if (selectedBtn) {
|
|
selectedBtn.classList.add('selected');
|
|
}
|
|
|
|
state.selectedOption = num;
|
|
submitAnswer(state.currentId, num);
|
|
}
|
|
|
|
function markOption(num, type) {
|
|
var btn = document.querySelector('.option-btn[data-num="' + num + '"]');
|
|
if (btn) {
|
|
btn.classList.add(type);
|
|
}
|
|
}
|
|
|
|
function clearOptionMark(num) {
|
|
var btn = document.querySelector('.option-btn[data-num="' + num + '"]');
|
|
if (btn) {
|
|
btn.classList.remove('wrong', 'selected');
|
|
}
|
|
}
|
|
|
|
function showFeedback(type, message) {
|
|
elements.feedback.className = 'feedback show ' + type;
|
|
elements.feedbackIcon.textContent = type === 'correct' ? '🎉' : '😢';
|
|
elements.feedbackText.textContent = message.replace(/^[🎉😢]\s*/, '');
|
|
elements.overlay.classList.add('show');
|
|
}
|
|
|
|
function hideFeedback() {
|
|
elements.feedback.classList.remove('show');
|
|
elements.overlay.classList.remove('show');
|
|
}
|
|
|
|
function showHintMessage(message) {
|
|
var hintEl = document.getElementById('hintMessage');
|
|
if (hintEl) {
|
|
hintEl.textContent = '💡 ' + message;
|
|
hintEl.classList.add('show');
|
|
}
|
|
}
|
|
|
|
function goToQuestion(id) {
|
|
if (state.isLoading) return;
|
|
|
|
state.currentId = id;
|
|
state.answered = false;
|
|
state.selectedOption = null;
|
|
setUrlParam('id', id);
|
|
fetchQuestion(id);
|
|
}
|
|
|
|
function goToRandomQuestion() {
|
|
if (state.isLoading) return;
|
|
|
|
var randomId = Math.floor(Math.random() * 500) + 1;
|
|
goToQuestion(randomId);
|
|
}
|
|
|
|
function retry() {
|
|
fetchQuestion(state.currentId);
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function init() {
|
|
var idParam = getUrlParam('id');
|
|
if (idParam) {
|
|
state.currentId = parseInt(idParam, 10) || 1;
|
|
} else {
|
|
state.currentId = Math.floor(Math.random() * 500) + 1;
|
|
}
|
|
|
|
fetchQuestion(state.currentId);
|
|
}
|
|
|
|
window.PoetryQuiz = {
|
|
selectOption: selectOption,
|
|
getHint: function() { getHint(state.currentId); },
|
|
goToQuestion: goToQuestion,
|
|
goToRandomQuestion: goToRandomQuestion,
|
|
retry: retry
|
|
};
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
})();
|