feat: 重构移动端布局并新增专用单词本页面
This commit is contained in:
+243
@@ -0,0 +1,243 @@
|
||||
var wbBackend = false;
|
||||
var wbItems = [];
|
||||
var wbEditId = null;
|
||||
var LOCAL_WB_KEY = 'spanish_diary_wordbook';
|
||||
|
||||
function wbEsc(s) {
|
||||
var div = document.createElement('div');
|
||||
div.textContent = s == null ? '' : String(s);
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function wbMsg(text) {
|
||||
document.getElementById('wb-msg').textContent = text || '';
|
||||
}
|
||||
|
||||
function loadLocalWords() {
|
||||
try {
|
||||
wbItems = JSON.parse(localStorage.getItem(LOCAL_WB_KEY) || '[]');
|
||||
} catch (e) {
|
||||
wbItems = [];
|
||||
}
|
||||
renderWordbook();
|
||||
}
|
||||
|
||||
function saveLocalWords() {
|
||||
localStorage.setItem(LOCAL_WB_KEY, JSON.stringify(wbItems));
|
||||
}
|
||||
|
||||
function loadWords() {
|
||||
if (wbBackend) {
|
||||
apiGetVocabulary().then(function(items) {
|
||||
wbItems = items || [];
|
||||
renderWordbook();
|
||||
}).catch(function(err) {
|
||||
wbMsg(err.message || '加载单词本失败');
|
||||
});
|
||||
} else {
|
||||
loadLocalWords();
|
||||
}
|
||||
}
|
||||
|
||||
function getFilteredWords() {
|
||||
var keyword = document.getElementById('search-word').value.trim().toLowerCase();
|
||||
var category = document.getElementById('filter-category').value;
|
||||
var sort = document.getElementById('sort-word').value;
|
||||
var filtered = wbItems.filter(function(item) {
|
||||
var matchKeyword = !keyword ||
|
||||
String(item.word || '').toLowerCase().indexOf(keyword) >= 0 ||
|
||||
String(item.english || '').toLowerCase().indexOf(keyword) >= 0 ||
|
||||
String(item.chinese || '').toLowerCase().indexOf(keyword) >= 0;
|
||||
var matchCategory = !category || String(item.category || '') === category;
|
||||
return matchKeyword && matchCategory;
|
||||
});
|
||||
filtered.sort(function(a, b) {
|
||||
if (sort === 'word') return String(a.word || '').localeCompare(String(b.word || ''), 'es');
|
||||
if (sort === 'category') {
|
||||
var c = String(a.category || '').localeCompare(String(b.category || ''), 'zh-CN');
|
||||
if (c !== 0) return c;
|
||||
return String(a.word || '').localeCompare(String(b.word || ''), 'es');
|
||||
}
|
||||
return String(b.created_at || '').localeCompare(String(a.created_at || ''));
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function renderWordbook() {
|
||||
var categories = {};
|
||||
wbItems.forEach(function(item) {
|
||||
var cat = String(item.category || '其他').trim() || '其他';
|
||||
categories[cat] = (categories[cat] || 0) + 1;
|
||||
});
|
||||
var filter = document.getElementById('filter-category');
|
||||
var current = filter.value;
|
||||
var categoryKeys = Object.keys(categories).sort(function(a, b) {
|
||||
return a.localeCompare(b, 'zh-CN');
|
||||
});
|
||||
filter.innerHTML = '<option value="">全部分类</option>' + categoryKeys.map(function(cat) {
|
||||
return '<option value="' + wbEsc(cat) + '"' + (cat === current ? ' selected' : '') + '>' +
|
||||
wbEsc(cat) + ' (' + categories[cat] + ')</option>';
|
||||
}).join('');
|
||||
|
||||
document.getElementById('wb-total').textContent = '共 ' + wbItems.length + ' 个单词';
|
||||
document.getElementById('wb-category-count').textContent = categoryKeys.length + ' 个分类';
|
||||
|
||||
var filtered = getFilteredWords();
|
||||
var container = document.getElementById('wb-list');
|
||||
if (filtered.length === 0) {
|
||||
container.innerHTML = '<div class="empty-state">还没有收藏单词,或没有匹配结果</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = filtered.map(function(item) {
|
||||
var cat = String(item.category || '其他').trim() || '其他';
|
||||
return '<div class="word-card" data-id="' + item.id + '">' +
|
||||
'<div class="word-main">' +
|
||||
'<span class="word-text">' + wbEsc(item.word) + '</span>' +
|
||||
'<span class="word-cat">' + wbEsc(cat) + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="word-langs">' +
|
||||
'<div class="lang-line"><span class="lang-label">ES</span><span class="lang-value">' + wbEsc(item.word) + '</span><button class="speak-word" data-lang="es">🔊</button></div>' +
|
||||
'<div class="lang-line"><span class="lang-label">EN</span><span class="lang-value">' + wbEsc(item.english || '-') + '</span><button class="speak-word" data-lang="en">🔊</button></div>' +
|
||||
'<div class="lang-line"><span class="lang-label">CN</span><span class="lang-value">' + wbEsc(item.chinese || '-') + '</span><button class="speak-word" data-lang="zh">🔊</button></div>' +
|
||||
'</div>' +
|
||||
'<div class="word-actions">' +
|
||||
'<button class="edit-word">编辑</button>' +
|
||||
'<button class="danger delete-word">删除</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function addWord() {
|
||||
var data = {
|
||||
word: document.getElementById('add-word').value.trim(),
|
||||
english: document.getElementById('add-english').value.trim(),
|
||||
chinese: document.getElementById('add-chinese').value.trim(),
|
||||
category: document.getElementById('add-category').value.trim()
|
||||
};
|
||||
if (!data.word) {
|
||||
wbMsg('请输入西语单词');
|
||||
return;
|
||||
}
|
||||
var finish = function() {
|
||||
document.getElementById('add-word').value = '';
|
||||
document.getElementById('add-english').value = '';
|
||||
document.getElementById('add-chinese').value = '';
|
||||
document.getElementById('add-category').value = '';
|
||||
loadWords();
|
||||
};
|
||||
if (wbBackend) {
|
||||
apiAddVocabulary(data).then(finish).catch(function(err) {
|
||||
wbMsg(err.message || '添加失败');
|
||||
});
|
||||
} else {
|
||||
data.id = 'local-' + Date.now();
|
||||
data.created_at = new Date().toISOString();
|
||||
wbItems.unshift(data);
|
||||
saveLocalWords();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
function openEdit(id) {
|
||||
var item = wbItems.find(function(w) { return String(w.id) === String(id); });
|
||||
if (!item) return;
|
||||
wbEditId = id;
|
||||
document.getElementById('edit-word').value = item.word || '';
|
||||
document.getElementById('edit-english').value = item.english || '';
|
||||
document.getElementById('edit-chinese').value = item.chinese || '';
|
||||
document.getElementById('edit-category').value = item.category || '';
|
||||
document.getElementById('edit-overlay').classList.add('show');
|
||||
}
|
||||
|
||||
function closeEdit() {
|
||||
wbEditId = null;
|
||||
document.getElementById('edit-overlay').classList.remove('show');
|
||||
}
|
||||
|
||||
function saveEdit() {
|
||||
if (!wbEditId) return;
|
||||
var data = {
|
||||
word: document.getElementById('edit-word').value.trim(),
|
||||
english: document.getElementById('edit-english').value.trim(),
|
||||
chinese: document.getElementById('edit-chinese').value.trim(),
|
||||
category: document.getElementById('edit-category').value.trim()
|
||||
};
|
||||
if (!data.word) {
|
||||
wbMsg('请输入西语单词');
|
||||
return;
|
||||
}
|
||||
var finish = function() {
|
||||
closeEdit();
|
||||
loadWords();
|
||||
};
|
||||
if (wbBackend) {
|
||||
apiUpdateVocabulary(wbEditId, data).then(finish).catch(function(err) {
|
||||
wbMsg(err.message || '保存失败');
|
||||
});
|
||||
} else {
|
||||
var idx = wbItems.findIndex(function(w) { return String(w.id) === String(wbEditId); });
|
||||
if (idx >= 0) {
|
||||
Object.assign(wbItems[idx], data);
|
||||
saveLocalWords();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteWord(id) {
|
||||
if (!confirm('确定从单词本中删除该单词吗?')) return;
|
||||
var finish = function() {
|
||||
loadWords();
|
||||
};
|
||||
if (wbBackend) {
|
||||
apiDeleteVocabulary(id).then(finish).catch(function(err) {
|
||||
wbMsg(err.message || '删除失败');
|
||||
});
|
||||
} else {
|
||||
wbItems = wbItems.filter(function(w) { return String(w.id) !== String(id); });
|
||||
saveLocalWords();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('btn-add-word').addEventListener('click', addWord);
|
||||
document.getElementById('search-word').addEventListener('input', renderWordbook);
|
||||
document.getElementById('filter-category').addEventListener('change', renderWordbook);
|
||||
document.getElementById('sort-word').addEventListener('change', renderWordbook);
|
||||
document.getElementById('edit-cancel').addEventListener('click', closeEdit);
|
||||
document.getElementById('edit-save').addEventListener('click', saveEdit);
|
||||
|
||||
document.getElementById('wb-list').addEventListener('click', function(e) {
|
||||
var card = e.target.closest('.word-card');
|
||||
if (!card) return;
|
||||
var id = card.dataset.id;
|
||||
if (e.target.closest('.speak-word')) {
|
||||
var item = wbItems.find(function(w) { return String(w.id) === String(id); });
|
||||
if (!item) return;
|
||||
var lang = e.target.closest('.speak-word').dataset.lang;
|
||||
if (lang === 'es') speakText(item.word, 'es');
|
||||
else if (lang === 'en') speakText(item.english, 'en');
|
||||
else speakText(item.chinese, 'zh');
|
||||
return;
|
||||
}
|
||||
if (e.target.closest('.edit-word')) {
|
||||
openEdit(id);
|
||||
return;
|
||||
}
|
||||
if (e.target.closest('.delete-word')) {
|
||||
deleteWord(id);
|
||||
}
|
||||
});
|
||||
|
||||
apiGetMe().then(function() {
|
||||
wbBackend = true;
|
||||
loadWords();
|
||||
}).catch(function(err) {
|
||||
if (err && err.status === 401) {
|
||||
location.href = 'login.html';
|
||||
return;
|
||||
}
|
||||
wbBackend = false;
|
||||
loadWords();
|
||||
});
|
||||
Reference in New Issue
Block a user