feat: 新增打字练习记录与首页记录列表

This commit is contained in:
guoqingshan
2026-08-02 22:20:15 +08:00
parent 72d1e1adc5
commit 2ee9e9c564
7 changed files with 291 additions and 4 deletions
+89 -3
View File
@@ -3,6 +3,9 @@ var typingItems = [];
var typingIndex = 0;
var typingStats = { correct: 0, errors: 0 };
var typed = '';
var typingStartedAt = 0;
var typingFinished = false;
var typingSession = { mode: '', categoryId: '', categoryName: '', itemCount: 0 };
var KEY_ROWS = [
[
@@ -171,6 +174,14 @@ function startTyping() {
typingIndex = 0;
typingStats = { correct: 0, errors: 0 };
typed = '';
typingStartedAt = Date.now();
typingFinished = false;
typingSession = {
mode: typingMode,
categoryId: category.id,
categoryName: category.name,
itemCount: typingItems.length
};
document.getElementById('typing-input').style.display = '';
document.getElementById('setup-view').style.display = 'none';
document.getElementById('practice-view').style.display = 'block';
@@ -228,6 +239,79 @@ function renderTypingAccuracy() {
',正确率 ' + (totalKeys ? Math.round(typingStats.correct * 100 / totalKeys) : 100) + '%';
}
function formatTypingDuration(seconds) {
if (seconds < 60) return seconds + ' 秒';
var minutes = Math.floor(seconds / 60);
var rest = seconds % 60;
return minutes + ' 分 ' + rest + ' 秒';
}
function formatTypingTime(value) {
if (!value) return '';
var date = new Date(value);
if (isNaN(date.getTime())) return value;
return date.toLocaleString('zh-CN', { hour12: false });
}
function renderTypingLogs(rows) {
var list = document.getElementById('records-list');
if (!list) return;
if (!rows || !rows.length) {
list.innerHTML = '<div class="records-empty">还没有打字记录</div>';
return;
}
list.innerHTML = rows.map(function(row) {
var mode = row.mode === 'phrases' ? '日常口语' : '基础词汇';
var category = row.category_name || row.category_id || '未分类';
return '<div class="record-item">' +
'<div class="record-line">' +
'<span class="record-mode">' + escTyping(mode) + '</span>' +
'<span class="record-category">' + escTyping(category) + '</span>' +
'<span class="record-accuracy">正确率 ' + (row.accuracy || 0) + '%</span>' +
'<span class="record-duration">用时 ' + escTyping(formatTypingDuration(row.duration_seconds || 0)) + '</span>' +
'</div>' +
'<div class="record-meta">' +
escTyping(formatTypingTime(row.created_at)) + ' · ' + row.item_count +
' 个 · 正确 ' + row.correct_keys + ' · 错误 ' + row.error_keys +
'</div>' +
'</div>';
}).join('');
}
function loadTypingLogs() {
var list = document.getElementById('records-list');
if (!list) return;
list.textContent = '加载中...';
apiGetTypingLogs().then(renderTypingLogs).catch(function(err) {
list.textContent = err && err.status === 401
? '登录后可查看打字记录'
: '记录加载失败';
});
}
function saveTypingLog(accuracy) {
if (!typingStartedAt || typingFinished) return;
typingFinished = true;
var durationSeconds = Math.max(1, Math.round((Date.now() - typingStartedAt) / 1000));
apiSaveTypingLog({
mode: typingSession.mode,
categoryId: typingSession.categoryId,
categoryName: typingSession.categoryName,
itemCount: typingSession.itemCount,
correctKeys: typingStats.correct,
errorKeys: typingStats.errors,
accuracy: accuracy,
durationSeconds: durationSeconds
}).then(loadTypingLogs).catch(function() {});
}
function showSetupView() {
document.getElementById('typing-input').style.display = '';
document.getElementById('practice-view').style.display = 'none';
document.getElementById('setup-view').style.display = 'block';
loadTypingLogs();
}
function renderKeyboard(target) {
var next = target[typed.length] || '';
var press = keyPressForChar(next);
@@ -307,6 +391,7 @@ function renderTargetProgress(target) {
function renderFinish() {
var totalKeys = typingStats.correct + typingStats.errors;
var accuracy = totalKeys ? Math.round(typingStats.correct * 100 / totalKeys) : 100;
saveTypingLog(accuracy);
document.getElementById('target-text').innerHTML = '';
document.getElementById('chinese-text').textContent = '';
document.getElementById('next-key').textContent = '';
@@ -319,9 +404,7 @@ function renderFinish() {
'<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';
showSetupView();
});
}
@@ -337,6 +420,9 @@ document.querySelectorAll('.mode-select button').forEach(function(btn) {
document.getElementById('btn-start').addEventListener('click', startTyping);
document.getElementById('btn-restart').addEventListener('click', startTyping);
document.getElementById('btn-choose').addEventListener('click', showSetupView);
document.getElementById('btn-refresh-records').addEventListener('click', loadTypingLogs);
document.getElementById('typing-input').addEventListener('input', handleTypingInput);
renderCategories();
loadTypingLogs();