feat: 重构移动端布局并新增专用单词本页面

This commit is contained in:
guoqingshan
2026-08-02 11:00:39 +08:00
parent 870f8a5da3
commit b337ee23c2
8 changed files with 867 additions and 48 deletions
+152 -15
View File
@@ -8,6 +8,7 @@ const dbDeleteEntry = deleteEntry;
var BACKEND_MODE = false;
var currentUser = null;
var favoriteWords = {};
var LOCAL_WB_KEY = 'spanish_diary_wordbook';
var currentTranslationSettings = { translator: 'local' };
var liveTranslationTimer = null;
var liveTranslationSeq = 0;
@@ -181,9 +182,6 @@ function updateVocabulary(text) {
}
container.innerHTML = vocab.map(function(v) {
var isFavorite = favoriteWords[v.word.toLowerCase()] ? 'active' : '';
var favBtn = BACKEND_MODE
? '<button class="favorite-btn ' + isFavorite + '" data-word="' + escHtml(v.word) + '" data-en="' + escHtml(v.english) + '" data-zh="' + escHtml(v.chinese) + '" title="收藏单词">★</button>'
: '';
return '<div class="vocab-item">' +
'<span class="vocab-word">' + escHtml(v.word) + '</span>' +
'<button class="vocab-speech" data-word="' + escHtml(v.word) + '" data-lang="es">' + '\uD83D\uDD0A' + '</button>' +
@@ -191,7 +189,7 @@ function updateVocabulary(text) {
'<button class="vocab-speech" data-word="' + escHtml(v.word) + '" data-lang="en">🔊</button>' +
'<span class="vocab-zh">' + escHtml(v.chinese) + '</span>' +
'<button class="vocab-speech" data-word="' + escHtml(v.word) + '" data-lang="zh">🔊</button>' +
favBtn +
'<button class="favorite-btn ' + isFavorite + '" data-word="' + escHtml(v.word) + '" data-en="' + escHtml(v.english) + '" data-zh="' + escHtml(v.chinese) + '" title="收藏单词">★</button>' +
'</div>';
}).join('');
}
@@ -252,6 +250,7 @@ function selectEntry(id) {
setEditor(found);
renderTranslate(found);
renderList();
showEditorView();
});
}
@@ -312,13 +311,48 @@ function saveCurrentEntry() {
function newEntry() {
clearEditor();
document.getElementById('entry-date').value = todayStr();
showEditorView();
document.getElementById('entry-text').focus();
}
function showDiaryListView() {
document.body.dataset.view = 'list';
document.querySelectorAll('#mobile-nav button').forEach(function(btn) {
btn.classList.toggle('active', btn.dataset.view === 'list');
closeMobileMenu();
}
function showEditorView() {
document.body.dataset.view = 'editor';
closeMobileMenu();
}
function showTranslationView() {
document.body.dataset.view = 'translate';
closeMobileMenu();
}
function hideTranslationView() {
document.body.dataset.view = 'editor';
}
function showStatsView() {
document.body.dataset.view = 'stats';
closeMobileMenu();
}
function openMobileMenu() {
refreshMobileMenuAuth();
document.getElementById('mobile-menu-overlay').classList.add('show');
}
function closeMobileMenu() {
document.getElementById('mobile-menu-overlay').classList.remove('show');
}
function refreshMobileMenuAuth() {
var loggedIn = !!currentUser;
['password', 'settings', 'logout'].forEach(function(action) {
var el = document.querySelector('#mobile-menu [data-menu="' + action + '"]');
if (el) el.style.display = loggedIn ? '' : 'none';
});
}
@@ -507,6 +541,7 @@ function renderUserArea() {
if (currentUser.isAdmin) {
document.getElementById('admin-link').style.display = '';
}
refreshMobileMenuAuth();
}
function loadBackendData() {
@@ -522,6 +557,7 @@ function loadBackendData() {
}
function loadLocalData() {
loadLocalFavoriteWords();
safeGetAllEntries().then(function(entries) {
state.entries = entries;
renderList();
@@ -530,6 +566,25 @@ function loadLocalData() {
});
}
function getLocalWordbook() {
try {
return JSON.parse(localStorage.getItem(LOCAL_WB_KEY) || '[]');
} catch (e) {
return [];
}
}
function saveLocalWordbook(items) {
localStorage.setItem(LOCAL_WB_KEY, JSON.stringify(items));
}
function loadLocalFavoriteWords() {
favoriteWords = {};
getLocalWordbook().forEach(function(item) {
favoriteWords[String(item.word || '').toLowerCase()] = item;
});
}
function loadStats() {
if (!BACKEND_MODE) {
document.getElementById('stats-content').textContent = '登录后显示统计';
@@ -565,6 +620,30 @@ function loadFavoriteWords() {
function toggleFavorite(btn) {
var word = btn.dataset.word;
var key = word.toLowerCase();
if (!BACKEND_MODE) {
var localItems = getLocalWordbook();
if (favoriteWords[key]) {
localItems = localItems.filter(function(item) {
return String(item.word || '').toLowerCase() !== key;
});
delete favoriteWords[key];
btn.classList.remove('active');
} else {
var localItem = {
id: 'local-' + Date.now(),
word: word,
english: btn.dataset.en || '',
chinese: btn.dataset.zh || '',
category: '',
created_at: new Date().toISOString()
};
localItems.unshift(localItem);
favoriteWords[key] = localItem;
btn.classList.add('active');
}
saveLocalWordbook(localItems);
return;
}
if (favoriteWords[key]) {
apiDeleteVocabulary(favoriteWords[key].id).then(function() {
delete favoriteWords[key];
@@ -688,6 +767,40 @@ function bindAppEvents() {
});
document.getElementById('btn-new').addEventListener('click', newEntry);
document.getElementById('btn-back-list').addEventListener('click', showEditorView);
document.getElementById('menu-btn').addEventListener('click', openMobileMenu);
document.getElementById('translate-btn').addEventListener('click', function() {
if (document.body.dataset.view === 'translate') {
hideTranslationView();
} else {
showTranslationView();
}
});
document.getElementById('mobile-menu-overlay').addEventListener('click', function(e) {
if (e.target === this) closeMobileMenu();
});
document.getElementById('mobile-menu').addEventListener('click', function(e) {
var item = e.target.closest('[data-menu]');
if (!item) return;
var action = item.dataset.menu;
if (action === 'diary') {
showDiaryListView();
} else if (action === 'stats') {
showStatsView();
} else if (action === 'password') {
closeMobileMenu();
openPasswordModal();
} else if (action === 'settings') {
closeMobileMenu();
openSettingsModal();
} else if (action === 'logout') {
closeMobileMenu();
handleLogout();
} else {
closeMobileMenu();
}
});
document.getElementById('btn-save').addEventListener('click', saveCurrentEntry);
document.getElementById('btn-helper').addEventListener('click', openHelperModal);
document.getElementById('helper-cancel').addEventListener('click', closeHelperModal);
@@ -702,15 +815,6 @@ function bindAppEvents() {
document.getElementById('password-save').addEventListener('click', savePasswordModal);
document.getElementById('btn-logout').addEventListener('click', handleLogout);
document.querySelectorAll('#mobile-nav button').forEach(function(btn) {
btn.addEventListener('click', function() {
document.body.dataset.view = btn.dataset.view;
document.querySelectorAll('#mobile-nav button').forEach(function(b) {
b.classList.toggle('active', b === btn);
});
});
});
document.querySelectorAll('.btn-speech').forEach(function(btn) {
btn.addEventListener('click', function() { handleSpeech(btn.dataset.lang); });
});
@@ -769,6 +873,39 @@ function bindAppEvents() {
document.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'Enter') saveCurrentEntry();
});
var swipeStartX = null;
var swipeStartY = null;
var swipeActive = false;
var appElement = document.getElementById('app');
appElement.addEventListener('touchstart', function(e) {
if (e.touches.length !== 1) return;
swipeStartX = e.touches[0].clientX;
swipeStartY = e.touches[0].clientY;
swipeActive = true;
}, { passive: true });
appElement.addEventListener('touchmove', function(e) {
if (!swipeActive || e.touches.length !== 1) return;
var dx = e.touches[0].clientX - swipeStartX;
var dy = e.touches[0].clientY - swipeStartY;
if (Math.abs(dx) > 30 && Math.abs(dx) > Math.abs(dy)) {
e.preventDefault();
}
}, { passive: false });
appElement.addEventListener('touchend', function(e) {
if (!swipeActive) return;
var dx = e.changedTouches[0].clientX - swipeStartX;
var dy = e.changedTouches[0].clientY - swipeStartY;
var view = document.body.dataset.view;
if (dx < -55 && Math.abs(dx) > Math.abs(dy) * 1.2 && view === 'editor') {
showTranslationView();
} else if (dx > 55 && Math.abs(dx) > Math.abs(dy) * 1.2 && (view === 'translate' || view === 'stats')) {
hideTranslationView();
}
swipeStartX = null;
swipeStartY = null;
swipeActive = false;
});
}
function startApp() {