feat: 重构移动端布局并新增专用单词本页面
This commit is contained in:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user