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 ''; }).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 += '' + ch + ''; } else if (i === typed.length) { targetHtml += '' + ch + ''; } 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 ? '已完成' : '下一键:' + escTyping(next) + ''; } 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 '
' + row.map(function(key) { var label = key === ' ' ? '␣' : key; var isHint = next && ( key === next || key.toLowerCase() === next.toLowerCase() || (next === ' ' && key === ' ') ); return ''; }).join('') + '
'; }).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 += '' + ch + ''; } else if (i === typed.length) { targetHtml += '' + ch + ''; } 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 = '

练习完成

完成 ' + typingItems.length + ' 个,正确率 ' + accuracy + '%

'; 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();