Initial commit: Flutter 无书应用项目
This commit is contained in:
344
ht/p1/api.js
Normal file
344
ht/p1/api.js
Normal file
@@ -0,0 +1,344 @@
|
||||
(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();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user