feat: 新增西班牙语词汇与口语打字练习页面
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
var typingMode = 'vocab';
|
||||
var typingItems = [];
|
||||
var typingIndex = 0;
|
||||
var typingStats = { correct: 0, errors: 0 };
|
||||
var typed = '';
|
||||
|
||||
var KEY_ROWS = [
|
||||
['1','2','3','4','5','6','7','8','9','0'],
|
||||
['q','w','e','r','t','y','u','i','o','p'],
|
||||
['a','s','d','f','g','h','j','k','l','ñ'],
|
||||
['z','x','c','v','b','n','m',',','.',';'],
|
||||
['á','é','í','ó','ú','ü','¿','¡',':','?','!',' ']
|
||||
];
|
||||
|
||||
function escTyping(s) {
|
||||
var div = document.createElement('div');
|
||||
div.textContent = s == null ? '' : String(s);
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function currentCategory() {
|
||||
var id = document.getElementById('category-select').value;
|
||||
return window.TYPING_DATA[typingMode].categories.find(function(c) {
|
||||
return c.id === id;
|
||||
});
|
||||
}
|
||||
|
||||
function renderCategories() {
|
||||
var data = window.TYPING_DATA[typingMode];
|
||||
var select = document.getElementById('category-select');
|
||||
select.innerHTML = data.categories.map(function(cat) {
|
||||
return '<option value="' + escTyping(cat.id) + '">' +
|
||||
escTyping(cat.name) + '(' + cat.items.length + ')</option>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function startTyping() {
|
||||
var category = currentCategory();
|
||||
if (!category) return;
|
||||
var source = category.items.map(function(item, idx) {
|
||||
return Object.assign({}, item, { id: idx + 1 });
|
||||
});
|
||||
if (document.getElementById('order-select').value === 'random') {
|
||||
for (var i = source.length - 1; i > 0; i--) {
|
||||
var j = Math.floor(Math.random() * (i + 1));
|
||||
var t = source[i]; source[i] = source[j]; source[j] = t;
|
||||
}
|
||||
}
|
||||
var count = document.getElementById('count-select').value;
|
||||
typingItems = count === 'all' ? source : source.slice(0, parseInt(count, 10) || 10);
|
||||
typingIndex = 0;
|
||||
typingStats = { correct: 0, errors: 0 };
|
||||
typed = '';
|
||||
document.getElementById('typing-input').style.display = '';
|
||||
document.getElementById('setup-view').style.display = 'none';
|
||||
document.getElementById('practice-view').style.display = 'block';
|
||||
renderPractice();
|
||||
}
|
||||
|
||||
function renderPractice() {
|
||||
if (typingIndex >= typingItems.length) {
|
||||
renderFinish();
|
||||
return;
|
||||
}
|
||||
var item = typingItems[typingIndex];
|
||||
var target = item.es;
|
||||
var targetHtml = '';
|
||||
for (var i = 0; i < target.length; i++) {
|
||||
var ch = escTyping(target[i]);
|
||||
if (i < typed.length) {
|
||||
targetHtml += '<span class="done">' + ch + '</span>';
|
||||
} else if (i === typed.length) {
|
||||
targetHtml += '<span class="current">' + ch + '</span>';
|
||||
} else {
|
||||
targetHtml += ch;
|
||||
}
|
||||
}
|
||||
document.getElementById('target-text').innerHTML = targetHtml;
|
||||
document.getElementById('chinese-text').textContent = item.zh || '';
|
||||
document.getElementById('progress-text').textContent =
|
||||
(typingIndex + 1) + ' / ' + typingItems.length;
|
||||
var totalKeys = typingStats.correct + typingStats.errors;
|
||||
document.getElementById('accuracy-text').textContent =
|
||||
'正确按键 ' + typingStats.correct + ',错误 ' + typingStats.errors +
|
||||
',正确率 ' + (totalKeys ? Math.round(typingStats.correct * 100 / totalKeys) : 100) + '%';
|
||||
document.getElementById('progress-fill').style.width =
|
||||
(typingIndex * 100 / typingItems.length) + '%';
|
||||
|
||||
var input = document.getElementById('typing-input');
|
||||
input.value = typed;
|
||||
renderNextKey(target);
|
||||
renderKeyboard(target);
|
||||
setTimeout(function() { input.focus(); }, 50);
|
||||
}
|
||||
|
||||
function renderNextKey(target) {
|
||||
var next = target[typed.length];
|
||||
document.getElementById('next-key').innerHTML = typed.length >= target.length
|
||||
? '已完成'
|
||||
: '下一键:<b>' + escTyping(next) + '</b>';
|
||||
}
|
||||
|
||||
function renderTypingAccuracy() {
|
||||
var totalKeys = typingStats.correct + typingStats.errors;
|
||||
document.getElementById('accuracy-text').textContent =
|
||||
'正确按键 ' + typingStats.correct + ',错误 ' + typingStats.errors +
|
||||
',正确率 ' + (totalKeys ? Math.round(typingStats.correct * 100 / totalKeys) : 100) + '%';
|
||||
}
|
||||
|
||||
function renderKeyboard(target) {
|
||||
var next = target[typed.length] || '';
|
||||
var container = document.getElementById('keyboard');
|
||||
container.innerHTML = KEY_ROWS.map(function(row) {
|
||||
return '<div class="kb-row">' + row.map(function(key) {
|
||||
var label = key === ' ' ? '␣' : key;
|
||||
var isHint = next && (
|
||||
key === next ||
|
||||
key.toLowerCase() === next.toLowerCase() ||
|
||||
(next === ' ' && key === ' ')
|
||||
);
|
||||
return '<button type="button" class="kb-key' + (isHint ? ' hint' : '') + (key === ' ' ? ' space' : '') + '" data-key="' + escTyping(key) + '">' + escTyping(label) + '</button>';
|
||||
}).join('') + '</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function handleTypingInput() {
|
||||
var input = document.getElementById('typing-input');
|
||||
var value = input.value;
|
||||
var item = typingItems[typingIndex];
|
||||
if (!item) return;
|
||||
var target = item.es;
|
||||
var oldLength = typed.length;
|
||||
typed = value;
|
||||
if (target.indexOf(typed) === 0) {
|
||||
typingStats.correct += Math.max(0, typed.length - oldLength);
|
||||
renderTargetProgress(target);
|
||||
} else {
|
||||
typingStats.errors++;
|
||||
typed = target.slice(0, oldLength);
|
||||
input.value = typed;
|
||||
renderTargetProgress(target);
|
||||
}
|
||||
renderNextKey(target);
|
||||
renderTypingAccuracy();
|
||||
if (typed.length >= target.length) {
|
||||
typingStats.correct += Math.max(0, target.length - typed.length);
|
||||
speakText(item.es, 'es');
|
||||
typingIndex++;
|
||||
typed = '';
|
||||
setTimeout(renderPractice, 700);
|
||||
}
|
||||
}
|
||||
|
||||
function renderTargetProgress(target) {
|
||||
var targetHtml = '';
|
||||
for (var i = 0; i < target.length; i++) {
|
||||
var ch = escTyping(target[i]);
|
||||
if (i < typed.length) {
|
||||
targetHtml += '<span class="done">' + ch + '</span>';
|
||||
} else if (i === typed.length) {
|
||||
targetHtml += '<span class="current">' + ch + '</span>';
|
||||
} else {
|
||||
targetHtml += ch;
|
||||
}
|
||||
}
|
||||
document.getElementById('target-text').innerHTML = targetHtml;
|
||||
}
|
||||
|
||||
function renderFinish() {
|
||||
var totalKeys = typingStats.correct + typingStats.errors;
|
||||
var accuracy = totalKeys ? Math.round(typingStats.correct * 100 / totalKeys) : 100;
|
||||
document.getElementById('target-text').innerHTML = '';
|
||||
document.getElementById('chinese-text').textContent = '';
|
||||
document.getElementById('next-key').textContent = '';
|
||||
document.getElementById('keyboard').innerHTML = '';
|
||||
document.getElementById('progress-text').textContent = '';
|
||||
document.getElementById('accuracy-text').textContent = '';
|
||||
document.getElementById('typing-input').style.display = 'none';
|
||||
document.getElementById('progress-fill').style.width = '100%';
|
||||
document.getElementById('target-text').innerHTML =
|
||||
'<div class="finish-box"><h2>练习完成</h2><p>完成 ' + typingItems.length +
|
||||
' 个,正确率 ' + accuracy + '%</p><button id="btn-again">再练一次</button></div>';
|
||||
document.getElementById('btn-again').addEventListener('click', function() {
|
||||
document.getElementById('typing-input').style.display = '';
|
||||
document.getElementById('setup-view').style.display = 'block';
|
||||
document.getElementById('practice-view').style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.mode-select button').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
typingMode = btn.dataset.mode;
|
||||
document.querySelectorAll('.mode-select button').forEach(function(b) {
|
||||
b.classList.toggle('active', b === btn);
|
||||
});
|
||||
renderCategories();
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('btn-start').addEventListener('click', startTyping);
|
||||
document.getElementById('btn-restart').addEventListener('click', startTyping);
|
||||
document.getElementById('typing-input').addEventListener('input', handleTypingInput);
|
||||
document.getElementById('keyboard').addEventListener('click', function(e) {
|
||||
var btn = e.target.closest('.kb-key');
|
||||
if (!btn) return;
|
||||
var input = document.getElementById('typing-input');
|
||||
var key = btn.dataset.key;
|
||||
var next = typingItems[typingIndex] && typingItems[typingIndex].es[typed.length];
|
||||
if (next && key.toLowerCase() === next.toLowerCase() && key !== next) {
|
||||
key = next;
|
||||
}
|
||||
input.value += key;
|
||||
handleTypingInput();
|
||||
input.focus();
|
||||
});
|
||||
|
||||
renderCategories();
|
||||
Reference in New Issue
Block a user