feat: 西班牙语日记学习工具与知识库
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
# Mi Diario — 西班牙语日记学习工具
|
||||||
|
|
||||||
|
一个纯前端的西班牙语学习应用,基于外研社《现代西班牙语1》整理知识库。
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 📝 西班牙语日记:新建、编辑、删除、按日期管理
|
||||||
|
- 🌐 实时翻译:输入西语自动显示英语和中文对照
|
||||||
|
- 🔊 发音朗读:支持西语、英语、中文三种语言(Web Speech API)
|
||||||
|
- 📖 单词本:自动提取输入中的关键词并给出解释,支持点击发音
|
||||||
|
- 📚 语法知识库:按教材整理的 40 个语音和语法知识点
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
直接打开 `index.html` 即可使用(推荐 Edge / Chrome)。
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
index.html — 主页面
|
||||||
|
style.css — 主页面样式
|
||||||
|
app.js — 应用逻辑
|
||||||
|
db.js — IndexedDB 数据存储
|
||||||
|
translate.js — 翻译模块
|
||||||
|
speech.js — 发音模块
|
||||||
|
knowledge.html — 知识库首页
|
||||||
|
knowledge/ — 6 个分类知识页面
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据存储
|
||||||
|
|
||||||
|
阶段 1 使用浏览器 IndexedDB 本地存储,无需后端。后续计划迁移到 Python + SQLite。
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
// Alias db.js functions
|
||||||
|
const dbGetAllEntries = getAllEntries;
|
||||||
|
const dbGetEntry = getEntry;
|
||||||
|
const dbAddEntry = addEntry;
|
||||||
|
const dbUpdateEntry = updateEntry;
|
||||||
|
const dbDeleteEntry = deleteEntry;
|
||||||
|
|
||||||
|
var savedSelStart = 0;
|
||||||
|
var savedSelEnd = 0;
|
||||||
|
|
||||||
|
let state = {
|
||||||
|
entries: [],
|
||||||
|
currentId: null
|
||||||
|
};
|
||||||
|
|
||||||
|
function uuid() {
|
||||||
|
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
function todayStr() {
|
||||||
|
const d = new Date();
|
||||||
|
return d.getFullYear() + '-' +
|
||||||
|
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
||||||
|
String(d.getDate()).padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Render functions -----
|
||||||
|
|
||||||
|
function renderList() {
|
||||||
|
const ul = document.getElementById('entry-list');
|
||||||
|
ul.innerHTML = state.entries.map(e => {
|
||||||
|
const preview = e.spanish.slice(0, 30) + (e.spanish.length > 30 ? '...' : '');
|
||||||
|
const active = e.id === state.currentId ? 'active' : '';
|
||||||
|
return '<li class="' + active + '" data-id="' + e.id + '">' +
|
||||||
|
'<div class="entry-date">' + escHtml(e.date) + '</div>' +
|
||||||
|
'<div class="entry-preview">' + escHtml(preview) + '</div>' +
|
||||||
|
'<div class="entry-actions">' +
|
||||||
|
'<button class="btn-delete" data-id="' + e.id + '">🗑 删除</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'</li>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function escHtml(s) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = s;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTranslate(entry) {
|
||||||
|
document.getElementById('translate-en').textContent = entry ? entry.english : '-';
|
||||||
|
document.getElementById('translate-cn').textContent = entry ? entry.chinese : '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEditor(entry) {
|
||||||
|
document.getElementById('entry-date').value = entry ? entry.date : todayStr();
|
||||||
|
document.getElementById('entry-text').value = entry ? entry.spanish : '';
|
||||||
|
const hint = document.getElementById('entry-hint');
|
||||||
|
hint.textContent = entry && entry.spanish
|
||||||
|
? '已保存。修改后点击保存更新。'
|
||||||
|
: '输入西语后实时显示翻译';
|
||||||
|
updateLiveTranslation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearEditor() {
|
||||||
|
state.currentId = null;
|
||||||
|
document.querySelectorAll('#entry-list li').forEach(function(el) { el.classList.remove('active'); });
|
||||||
|
setEditor(null);
|
||||||
|
renderTranslate(null);
|
||||||
|
clearLiveTranslation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Live translation & vocabulary -----
|
||||||
|
|
||||||
|
function updateLiveTranslation() {
|
||||||
|
var text = document.getElementById('entry-text').value;
|
||||||
|
if (!text.trim()) {
|
||||||
|
clearLiveTranslation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var result = translateText(text);
|
||||||
|
document.getElementById('editor-translate-en').textContent = result.english;
|
||||||
|
document.getElementById('editor-translate-cn').textContent = result.chinese;
|
||||||
|
|
||||||
|
updateVocabulary(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLiveTranslation() {
|
||||||
|
document.getElementById('editor-translate-en').textContent = '-';
|
||||||
|
document.getElementById('editor-translate-cn').textContent = '-';
|
||||||
|
document.getElementById('vocab-list').innerHTML = '';
|
||||||
|
document.getElementById('selection-translate').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateVocabulary(text) {
|
||||||
|
var vocab = getVocabulary(text);
|
||||||
|
var container = document.getElementById('vocab-list');
|
||||||
|
if (vocab.length === 0) {
|
||||||
|
container.innerHTML = '<div style="font-size:0.75rem;color:var(--text-muted);font-style:italic">输入西语后显示单词</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
container.innerHTML = vocab.map(function(v) {
|
||||||
|
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>' +
|
||||||
|
'<span class="vocab-en">' + escHtml(v.english) + '</span>' +
|
||||||
|
'<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>' +
|
||||||
|
'</div>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVocabSpeech(word, lang) {
|
||||||
|
var entry = getVocabForWord(word);
|
||||||
|
if (entry) {
|
||||||
|
if (lang === 'en') speakText(entry.en, 'en');
|
||||||
|
else if (lang === 'zh') speakText(entry.zh, 'zh');
|
||||||
|
else speakText(word, 'es');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelection() {
|
||||||
|
var ta = document.getElementById('entry-text');
|
||||||
|
var selText = ta.value.substring(ta.selectionStart, ta.selectionEnd).trim();
|
||||||
|
var el = document.getElementById('selection-translate');
|
||||||
|
if (selText && selText.length > 0) {
|
||||||
|
var result = translateSelection(selText);
|
||||||
|
document.getElementById('selected-translation').textContent =
|
||||||
|
result.english + ' / ' + result.chinese;
|
||||||
|
el.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
el.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Event handlers -----
|
||||||
|
|
||||||
|
function selectEntry(id) {
|
||||||
|
state.currentId = id;
|
||||||
|
dbGetEntry(id).then(function(entry) {
|
||||||
|
if (entry) {
|
||||||
|
setEditor(entry);
|
||||||
|
renderTranslate(entry);
|
||||||
|
renderList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCurrentEntry() {
|
||||||
|
var date = document.getElementById('entry-date').value;
|
||||||
|
var spanish = document.getElementById('entry-text').value.trim();
|
||||||
|
if (!spanish) { alert('请输入西语内容'); return; }
|
||||||
|
|
||||||
|
var translation = translateText(spanish);
|
||||||
|
|
||||||
|
var savePromise;
|
||||||
|
if (state.currentId) {
|
||||||
|
savePromise = dbGetEntry(state.currentId).then(function(existing) {
|
||||||
|
existing.date = date;
|
||||||
|
existing.spanish = spanish;
|
||||||
|
existing.english = translation.english;
|
||||||
|
existing.chinese = translation.chinese;
|
||||||
|
existing.updatedAt = new Date().toISOString();
|
||||||
|
return dbUpdateEntry(existing);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var entry = {
|
||||||
|
id: uuid(),
|
||||||
|
date: date,
|
||||||
|
spanish: spanish,
|
||||||
|
english: translation.english,
|
||||||
|
chinese: translation.chinese,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
|
};
|
||||||
|
savePromise = dbAddEntry(entry).then(function() {
|
||||||
|
state.currentId = entry.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
savePromise.then(function() {
|
||||||
|
return dbGetAllEntries();
|
||||||
|
}).then(function(entries) {
|
||||||
|
state.entries = entries;
|
||||||
|
renderList();
|
||||||
|
renderTranslate({ english: translation.english, chinese: translation.chinese });
|
||||||
|
var found = entries.find(function(e) { return e.id === state.currentId; });
|
||||||
|
if (found) setEditor(found);
|
||||||
|
document.getElementById('btn-save').textContent = '\uD83D\uDCBE 已保存 \u2713';
|
||||||
|
setTimeout(function() { document.getElementById('btn-save').textContent = '\uD83D\uDCBE 保存'; }, 1500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function newEntry() {
|
||||||
|
clearEditor();
|
||||||
|
document.getElementById('entry-date').value = todayStr();
|
||||||
|
document.getElementById('entry-text').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Modal -----
|
||||||
|
|
||||||
|
var pendingDeleteId = null;
|
||||||
|
|
||||||
|
function showDeleteModal(id) {
|
||||||
|
pendingDeleteId = id;
|
||||||
|
document.getElementById('modal-overlay').classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideDeleteModal() {
|
||||||
|
pendingDeleteId = null;
|
||||||
|
document.getElementById('modal-overlay').classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDelete(id) {
|
||||||
|
dbDeleteEntry(id).then(function() {
|
||||||
|
if (state.currentId === id) clearEditor();
|
||||||
|
return dbGetAllEntries();
|
||||||
|
}).then(function(entries) {
|
||||||
|
state.entries = entries;
|
||||||
|
renderList();
|
||||||
|
hideDeleteModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveTextareaSelection() {
|
||||||
|
var ta = document.getElementById('entry-text');
|
||||||
|
savedSelStart = ta.selectionStart;
|
||||||
|
savedSelEnd = ta.selectionEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Speech handler -----
|
||||||
|
|
||||||
|
function handleSpeech(lang) {
|
||||||
|
var text = lang === 'en'
|
||||||
|
? document.getElementById('translate-en').textContent
|
||||||
|
: document.getElementById('translate-cn').textContent;
|
||||||
|
if (text && text !== '-') speakText(text, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Initialization -----
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
dbGetAllEntries().then(function(entries) {
|
||||||
|
state.entries = entries;
|
||||||
|
renderList();
|
||||||
|
setEditor(null);
|
||||||
|
renderTranslate(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('entry-list').addEventListener('click', function(e) {
|
||||||
|
var li = e.target.closest('li[data-id]');
|
||||||
|
if (!li) return;
|
||||||
|
if (e.target.closest('.btn-delete')) {
|
||||||
|
showDeleteModal(li.dataset.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectEntry(li.dataset.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-new').addEventListener('click', newEntry);
|
||||||
|
document.getElementById('btn-save').addEventListener('click', saveCurrentEntry);
|
||||||
|
|
||||||
|
document.querySelectorAll('.btn-speech').forEach(function(btn) {
|
||||||
|
btn.addEventListener('click', function() { handleSpeech(btn.dataset.lang); });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Live translation on input
|
||||||
|
document.getElementById('entry-text').addEventListener('input', updateLiveTranslation);
|
||||||
|
|
||||||
|
// Selection translation
|
||||||
|
document.getElementById('entry-text').addEventListener('mouseup', function(e) { handleSelection(); saveTextareaSelection(); });
|
||||||
|
document.getElementById('entry-text').addEventListener('keyup', function(e) { handleSelection(); saveTextareaSelection(); });
|
||||||
|
|
||||||
|
// Vocabulary speech delegation
|
||||||
|
document.getElementById('editor-vocab').addEventListener('click', function(e) {
|
||||||
|
var btn = e.target.closest('.vocab-speech');
|
||||||
|
if (btn) handleVocabSpeech(btn.dataset.word, btn.dataset.lang);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Vocabulary double-click to auto-read
|
||||||
|
document.getElementById('editor-vocab').addEventListener('dblclick', function(e) {
|
||||||
|
var item = e.target.closest('.vocab-item');
|
||||||
|
if (!item) return;
|
||||||
|
var word = item.querySelector('.vocab-word').textContent;
|
||||||
|
var el = e.target;
|
||||||
|
if (el.classList.contains('vocab-en') || el.closest('.vocab-en')) {
|
||||||
|
speakText(item.querySelector('.vocab-en').textContent, 'en');
|
||||||
|
} else if (el.classList.contains('vocab-zh') || el.closest('.vocab-zh')) {
|
||||||
|
speakText(item.querySelector('.vocab-zh').textContent, 'zh');
|
||||||
|
} else {
|
||||||
|
speakText(word, 'es');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Textarea double-click auto-read
|
||||||
|
document.getElementById('entry-text').addEventListener('dblclick', function() {
|
||||||
|
var ta = document.getElementById('entry-text');
|
||||||
|
setTimeout(function() {
|
||||||
|
var txt = ta.value.substring(ta.selectionStart, ta.selectionEnd).trim();
|
||||||
|
if (txt) speakText(txt, 'es');
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Speak selected button - reads selection aloud
|
||||||
|
document.getElementById('btn-speak-selected').addEventListener('click', function() {
|
||||||
|
var ta = document.getElementById('entry-text');
|
||||||
|
var start = savedSelStart || ta.selectionStart;
|
||||||
|
var end = savedSelEnd || ta.selectionEnd;
|
||||||
|
if (start === end) { start = 0; end = ta.value.length; }
|
||||||
|
var sel = ta.value.substring(start, end).trim();
|
||||||
|
if (sel) {
|
||||||
|
speakText(sel, 'es');
|
||||||
|
document.getElementById('selected-translation').textContent = sel;
|
||||||
|
document.getElementById('selection-translate').style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#modal-box .btn-cancel').addEventListener('click', hideDeleteModal);
|
||||||
|
document.querySelector('#modal-box .btn-confirm').addEventListener('click', function() {
|
||||||
|
if (pendingDeleteId) confirmDelete(pendingDeleteId);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.ctrlKey && e.key === 'Enter') saveCurrentEntry();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
const DB_NAME = 'SpanishDiary';
|
||||||
|
const DB_VERSION = 1;
|
||||||
|
const STORE_NAME = 'entries';
|
||||||
|
|
||||||
|
let db = null;
|
||||||
|
|
||||||
|
function openDb() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (db) { resolve(db); return; }
|
||||||
|
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
||||||
|
req.onupgradeneeded = (e) => {
|
||||||
|
const d = e.target.result;
|
||||||
|
if (!d.objectStoreNames.contains(STORE_NAME)) {
|
||||||
|
const store = d.createObjectStore(STORE_NAME, { keyPath: 'id' });
|
||||||
|
store.createIndex('date', 'date', { unique: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
req.onsuccess = (e) => { db = e.target.result; resolve(db); };
|
||||||
|
req.onerror = (e) => reject(e.target.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllEntries() {
|
||||||
|
return openDb().then(db => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(STORE_NAME, 'readonly');
|
||||||
|
const store = tx.objectStore(STORE_NAME);
|
||||||
|
const index = store.index('date');
|
||||||
|
const req = index.openCursor(null, 'prev');
|
||||||
|
const results = [];
|
||||||
|
req.onsuccess = (e) => {
|
||||||
|
const cursor = e.target.result;
|
||||||
|
if (cursor) { results.push(cursor.value); cursor.continue(); }
|
||||||
|
else resolve(results);
|
||||||
|
};
|
||||||
|
req.onerror = (e) => reject(e.target.error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEntry(id) {
|
||||||
|
return openDb().then(db => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(STORE_NAME, 'readonly');
|
||||||
|
const store = tx.objectStore(STORE_NAME);
|
||||||
|
const req = store.get(id);
|
||||||
|
req.onsuccess = () => resolve(req.result || null);
|
||||||
|
req.onerror = (e) => reject(e.target.error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEntry(entry) {
|
||||||
|
return openDb().then(db => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(STORE_NAME, 'readwrite');
|
||||||
|
const store = tx.objectStore(STORE_NAME);
|
||||||
|
const req = store.add(entry);
|
||||||
|
req.onsuccess = () => resolve(entry);
|
||||||
|
req.onerror = (e) => reject(e.target.error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateEntry(entry) {
|
||||||
|
return openDb().then(db => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(STORE_NAME, 'readwrite');
|
||||||
|
const store = tx.objectStore(STORE_NAME);
|
||||||
|
const req = store.put(entry);
|
||||||
|
req.onsuccess = () => resolve(entry);
|
||||||
|
req.onerror = (e) => reject(e.target.error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteEntry(id) {
|
||||||
|
return openDb().then(db => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(STORE_NAME, 'readwrite');
|
||||||
|
const store = tx.objectStore(STORE_NAME);
|
||||||
|
const req = store.delete(id);
|
||||||
|
req.onsuccess = () => resolve();
|
||||||
|
req.onerror = (e) => reject(e.target.error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Mi Diario — 西班牙语日记</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<aside id="panel-list">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h2>📋 日记</h2>
|
||||||
|
<button id="btn-new" title="新建日记">+</button>
|
||||||
|
</div>
|
||||||
|
<ul id="entry-list"></ul>
|
||||||
|
|
||||||
|
<div id="grammar-quick">
|
||||||
|
<div class="panel-header grammar-header">
|
||||||
|
<h2>📚 语法速查</h2>
|
||||||
|
<a class="more-link" href="knowledge.html">更多 >></a>
|
||||||
|
</div>
|
||||||
|
<ul id="grammar-list">
|
||||||
|
<li><a href="knowledge/otros.html#ser-estar">ser/estar 区别</a></li>
|
||||||
|
<li><a href="knowledge/verbo.html#presente">现在时变位</a></li>
|
||||||
|
<li><a href="knowledge/sustantivo.html#articulo-def">定冠词用法</a></li>
|
||||||
|
<li><a href="knowledge/fonetica.html#acento">重音规则</a></li>
|
||||||
|
<li><a href="knowledge/otros.html#hora">时间表达</a></li>
|
||||||
|
<li><a href="knowledge/verbo.html#frases-verbales">常用动词短语</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main id="panel-editor">
|
||||||
|
<div class="panel-header">
|
||||||
|
<input type="date" id="entry-date">
|
||||||
|
<button id="btn-speak-selected" title="朗读选中的文本">🔊 朗读</button>
|
||||||
|
<button id="btn-save">💾 保存</button>
|
||||||
|
</div>
|
||||||
|
<textarea id="entry-text" placeholder="用西班牙语写下今天的日记..." spellcheck="false"></textarea>
|
||||||
|
<div id="entry-hint">输入西语后实时显示翻译</div>
|
||||||
|
|
||||||
|
<div id="editor-translate">
|
||||||
|
<div class="editor-section-title">🌐 实时翻译</div>
|
||||||
|
<div class="editor-translate-line">
|
||||||
|
<span class="et-label">EN:</span>
|
||||||
|
<span id="editor-translate-en" class="et-text">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="editor-translate-line">
|
||||||
|
<span class="et-label">CN:</span>
|
||||||
|
<span id="editor-translate-cn" class="et-text">-</span>
|
||||||
|
</div>
|
||||||
|
<div id="selection-translate" style="display:none">
|
||||||
|
<div class="editor-translate-line">
|
||||||
|
<span class="et-label">选中:</span>
|
||||||
|
<span id="selected-translation" class="et-text"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="editor-vocab">
|
||||||
|
<div class="editor-section-title">📖 单词本</div>
|
||||||
|
<div id="vocab-list"></div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<aside id="panel-translate">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h2>🌐 翻译</h2>
|
||||||
|
</div>
|
||||||
|
<div class="translate-section">
|
||||||
|
<div class="translate-label">🇬🇧 English</div>
|
||||||
|
<div id="translate-en" class="translate-text">-</div>
|
||||||
|
<button class="btn-speech" data-lang="en">🔊 朗读</button>
|
||||||
|
</div>
|
||||||
|
<div class="translate-section">
|
||||||
|
<div class="translate-label">🇨🇳 中文</div>
|
||||||
|
<div id="translate-cn" class="translate-text">-</div>
|
||||||
|
<button class="btn-speech" data-lang="zh">🔊 朗读</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete confirmation modal -->
|
||||||
|
<div id="modal-overlay">
|
||||||
|
<div id="modal-box">
|
||||||
|
<p>确定删除这条日记吗?此操作不可撤销。</p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-cancel">取消</button>
|
||||||
|
<button class="btn-confirm">删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="db.js"></script>
|
||||||
|
<script src="translate.js"></script>
|
||||||
|
<script src="speech.js"></script>
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>西班牙语知识库 — 现代西班牙语1</title>
|
||||||
|
<link rel="stylesheet" href="knowledge/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a class="back" href="index.html">← 返回日记</a>
|
||||||
|
<span class="home">📚 西班牙语知识库</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1>现代西班牙语 1 知识库</h1>
|
||||||
|
<p class="page-subtitle">按外研社教材整理,语音 + 语法共 40 个知识点,支持点击发音。</p>
|
||||||
|
|
||||||
|
<div class="category-grid">
|
||||||
|
<a class="category-card" href="knowledge/fonetica.html">
|
||||||
|
<h2>🔊 语音发音</h2>
|
||||||
|
<p>字母表、元音、辅音、音节与重音</p>
|
||||||
|
<span class="count">8 个知识点</span>
|
||||||
|
</a>
|
||||||
|
<a class="category-card" href="knowledge/sustantivo.html">
|
||||||
|
<h2>📦 名词与冠词</h2>
|
||||||
|
<p>名词的性数、定冠词、不定冠词</p>
|
||||||
|
<span class="count">4 个知识点</span>
|
||||||
|
</a>
|
||||||
|
<a class="category-card" href="knowledge/adjetivo.html">
|
||||||
|
<h2>🎨 形容词</h2>
|
||||||
|
<p>形容词变化、位置、指示与物主形容词</p>
|
||||||
|
<span class="count">4 个知识点</span>
|
||||||
|
</a>
|
||||||
|
<a class="category-card" href="knowledge/pronombre.html">
|
||||||
|
<h2>👤 代词</h2>
|
||||||
|
<p>人称、宾格、与格、指示、疑问代词</p>
|
||||||
|
<span class="count">5 个知识点</span>
|
||||||
|
</a>
|
||||||
|
<a class="category-card" href="knowledge/verbo.html">
|
||||||
|
<h2>⚡ 动词与时态</h2>
|
||||||
|
<p>规则变位、现在时、完成时、过去时</p>
|
||||||
|
<span class="count">7 个知识点</span>
|
||||||
|
</a>
|
||||||
|
<a class="category-card" href="knowledge/otros.html">
|
||||||
|
<h2>🧩 词类与句型</h2>
|
||||||
|
<p>副词、介词、数词、常用句型</p>
|
||||||
|
<span class="count">12 个知识点</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="speech.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
var btn = e.target.closest('.speak-btn');
|
||||||
|
if (btn) {
|
||||||
|
var lang = btn.dataset.lang || 'es';
|
||||||
|
var text = btn.dataset.text;
|
||||||
|
if (text) speakText(text, lang);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>形容词 — 西班牙语知识库</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a class="back" href="../knowledge.html">← 知识库</a>
|
||||||
|
<span class="home">🎨 形容词</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1>形容词</h1>
|
||||||
|
<p class="page-subtitle">《现代西班牙语1》形容词用法,共 4 个知识点。</p>
|
||||||
|
|
||||||
|
<div class="article" id="adjetivo-genero">
|
||||||
|
<h2>1. 形容词的性与数</h2>
|
||||||
|
<div class="article-title-en">Género y número del adjetivo</div>
|
||||||
|
<p>形容词必须与所修饰名词在<strong>性和数</strong>上保持一致。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>规则</th><th>阳性</th><th>阴性</th><th>复数</th></tr>
|
||||||
|
<tr><td>以 -o 结尾:变 -a 为阴性,加 -s</td><td>bueno</td><td>buena</td><td>buenos / buenas</td></tr>
|
||||||
|
<tr><td>以 -e 结尾:单复数同形,加 -s</td><td>grande</td><td>grande</td><td>grandes</td></tr>
|
||||||
|
<tr><td>以辅音结尾:复数加 -es</td><td>feliz</td><td>feliz</td><td>felices</td></tr>
|
||||||
|
<tr><td>以 -dor/-or 结尾:阴性加 -a</td><td>trabajador</td><td>trabajadora</td><td>trabajadores</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="example"><span>un libro bueno, una amiga buena, unos libros buenos</span><span class="zh">一本好书,一位好朋友,一些好书</span><button class="speak-btn" data-text="un libro bueno una amiga buena unos libros buenos">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>形容词的性数要与名词一致,尤其是复数中"阳性复数 + 阴性名词"时各自变。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="adjetivo-pos">
|
||||||
|
<h2>2. 形容词的位置与短尾形式</h2>
|
||||||
|
<div class="article-title-en">Posición del adjetivo y apócope</div>
|
||||||
|
<p><strong>一般位置:</strong>西班牙语形容词通常放在名词<strong>之后</strong>,强调客观属性。</p>
|
||||||
|
<div class="example"><span>una casa blanca, un libro interesante</span><span class="zh">一座白房子,一本有趣的书</span><button class="speak-btn" data-text="una casa blanca un libro interesante">🔊</button></div>
|
||||||
|
<p><strong>前置情况:</strong>数量词、限定词和一些常见形容词置于名词前,且发生<strong>短尾</strong>。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>短尾形式</th><th>原形</th><th>示例</th></tr>
|
||||||
|
<tr><td>un</td><td>uno</td><td>un libro</td></tr>
|
||||||
|
<tr><td>buen</td><td>bueno</td><td>un buen amigo</td></tr>
|
||||||
|
<tr><td>mal</td><td>malo</td><td>un mal día</td></tr>
|
||||||
|
<tr><td>gran</td><td>grande</td><td>un gran hombre</td></tr>
|
||||||
|
<tr><td>primer / tercer</td><td>primero / tercero</td><td>el primer libro</td></tr>
|
||||||
|
<tr><td>algún / ningún</td><td>alguno / ninguno</td><td>algún día</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>bueno 前置短尾为 buen,但后置不变:un hombre bueno。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="demostrativo">
|
||||||
|
<h2>3. 指示形容词</h2>
|
||||||
|
<div class="article-title-en">Los adjetivos demostrativos</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>距离</th><th>阳性</th><th>阴性</th><th>阳性复数</th><th>阴性复数</th></tr>
|
||||||
|
<tr><td>近(这里)</td><td>este</td><td>esta</td><td>estos</td><td>estas</td></tr>
|
||||||
|
<tr><td>中(那里)</td><td>ese</td><td>esa</td><td>esos</td><td>esas</td></tr>
|
||||||
|
<tr><td>远(那边)</td><td>aquel</td><td>aquella</td><td>aquellos</td><td>aquellas</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="example"><span>este libro, esa casa, aquellos años</span><span class="zh">这本书,那座房子,那些年代</span><button class="speak-btn" data-text="este libro esa casa aquellos años">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>指示形容词修饰名词时<strong>不带重音</strong>;指示代词带重音(éste、ése、aquél)。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="posesivo">
|
||||||
|
<h2>4. 物主形容词</h2>
|
||||||
|
<div class="article-title-en">Los adjetivos posesivos</div>
|
||||||
|
<p><strong>非重读物主形容词</strong>(置于名词前,最常用):</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>人称</th><th>单数</th><th>复数</th></tr>
|
||||||
|
<tr><td>mi / mis</td><td>mi libro</td><td>mis libros</td></tr>
|
||||||
|
<tr><td>tu / tus</td><td>tu libro</td><td>tus libros</td></tr>
|
||||||
|
<tr><td>su / sus</td><td>su libro</td><td>sus libros</td></tr>
|
||||||
|
<tr><td>nuestro/a / nuestros/as</td><td>nuestro libro</td><td>nuestros libros</td></tr>
|
||||||
|
<tr><td>vuestro/a / vuestros/as</td><td>vuestro libro</td><td>vuestros libros</td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>重读物主形容词</strong>(置于名词后,表强调):mío/mía、tuyo/tuya、suyo/suya、nuestro/a、vuestro/a。</p>
|
||||||
|
<div class="example"><span>mi amigo, tus ideas, su casa, nuestros padres</span><span class="zh">我的朋友,你的想法,他/她的家,我们的父母</span><button class="speak-btn" data-text="mi amigo tus ideas su casa nuestros padres">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>su/sus 含义多(他的/她的/您的/他们的),需靠上下文判断;mi/tu/su 单数不随名词的性变化。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../speech.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
var btn = e.target.closest('.speak-btn');
|
||||||
|
if (btn) {
|
||||||
|
var lang = btn.dataset.lang || 'es';
|
||||||
|
var text = btn.dataset.text;
|
||||||
|
if (text) speakText(text, lang);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>语音发音 — 西班牙语知识库</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a class="back" href="../knowledge.html">← 知识库</a>
|
||||||
|
<span class="home">🔊 语音发音</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1>语音发音</h1>
|
||||||
|
<p class="page-subtitle">《现代西班牙语1》语音部分,共 8 个知识点。点击 🔊 可听发音。</p>
|
||||||
|
|
||||||
|
<div class="article" id="alfabeto">
|
||||||
|
<h2>1. 字母表与发音</h2>
|
||||||
|
<div class="article-title-en">El alfabeto español</div>
|
||||||
|
<p>西班牙语字母表共 <strong>29 个字母</strong>,其中 ch、ll、ñ 为西班牙语特有字母(1994 年西班牙皇家语言学院将 ch、ll 并入字母表,但传统教材仍单独列示)。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>字母</th><th>名称</th><th>示例</th><th>发音</th></tr>
|
||||||
|
<tr><td>A a</td><td>a</td><td>amigo</td><td><button class="speak-btn" data-text="amigo">🔊</button></td></tr>
|
||||||
|
<tr><td>B b</td><td>be</td><td>bueno</td><td><button class="speak-btn" data-text="bueno">🔊</button></td></tr>
|
||||||
|
<tr><td>C c</td><td>ce</td><td>casa</td><td><button class="speak-btn" data-text="casa">🔊</button></td></tr>
|
||||||
|
<tr><td>Ch ch</td><td>che</td><td>chico</td><td><button class="speak-btn" data-text="chico">🔊</button></td></tr>
|
||||||
|
<tr><td>D d</td><td>de</td><td>día</td><td><button class="speak-btn" data-text="día">🔊</button></td></tr>
|
||||||
|
<tr><td>E e</td><td>e</td><td>escuela</td><td><button class="speak-btn" data-text="escuela">🔊</button></td></tr>
|
||||||
|
<tr><td>F f</td><td>efe</td><td>familia</td><td><button class="speak-btn" data-text="familia">🔊</button></td></tr>
|
||||||
|
<tr><td>G g</td><td>ge</td><td>gracias</td><td><button class="speak-btn" data-text="gracias">🔊</button></td></tr>
|
||||||
|
<tr><td>H h</td><td>hache</td><td>hola</td><td><button class="speak-btn" data-text="hola">🔊</button></td></tr>
|
||||||
|
<tr><td>I i</td><td>i</td><td>idea</td><td><button class="speak-btn" data-text="idea">🔊</button></td></tr>
|
||||||
|
<tr><td>J j</td><td>jota</td><td>joven</td><td><button class="speak-btn" data-text="joven">🔊</button></td></tr>
|
||||||
|
<tr><td>K k</td><td>ka</td><td>kilo</td><td><button class="speak-btn" data-text="kilo">🔊</button></td></tr>
|
||||||
|
<tr><td>L l</td><td>ele</td><td>libro</td><td><button class="speak-btn" data-text="libro">🔊</button></td></tr>
|
||||||
|
<tr><td>Ll ll</td><td>elle</td><td>llamar</td><td><button class="speak-btn" data-text="llamar">🔊</button></td></tr>
|
||||||
|
<tr><td>M m</td><td>eme</td><td>madre</td><td><button class="speak-btn" data-text="madre">🔊</button></td></tr>
|
||||||
|
<tr><td>N n</td><td>ene</td><td>noche</td><td><button class="speak-btn" data-text="noche">🔊</button></td></tr>
|
||||||
|
<tr><td>Ñ ñ</td><td>eñe</td><td>niño</td><td><button class="speak-btn" data-text="niño">🔊</button></td></tr>
|
||||||
|
<tr><td>O o</td><td>o</td><td>obra</td><td><button class="speak-btn" data-text="obra">🔊</button></td></tr>
|
||||||
|
<tr><td>P p</td><td>pe</td><td>padre</td><td><button class="speak-btn" data-text="padre">🔊</button></td></tr>
|
||||||
|
<tr><td>Q q</td><td>cu</td><td>queso</td><td><button class="speak-btn" data-text="queso">🔊</button></td></tr>
|
||||||
|
<tr><td>R r</td><td>erre</td><td>pero</td><td><button class="speak-btn" data-text="pero">🔊</button></td></tr>
|
||||||
|
<tr><td>Rr rr</td><td>erre doble</td><td>perro</td><td><button class="speak-btn" data-text="perro">🔊</button></td></tr>
|
||||||
|
<tr><td>S s</td><td>ese</td><td>sí</td><td><button class="speak-btn" data-text="sí">🔊</button></td></tr>
|
||||||
|
<tr><td>T t</td><td>te</td><td>tiempo</td><td><button class="speak-btn" data-text="tiempo">🔊</button></td></tr>
|
||||||
|
<tr><td>U u</td><td>u</td><td>universidad</td><td><button class="speak-btn" data-text="universidad">🔊</button></td></tr>
|
||||||
|
<tr><td>V v</td><td>uve</td><td>vivir</td><td><button class="speak-btn" data-text="vivir">🔊</button></td></tr>
|
||||||
|
<tr><td>W w</td><td>uve doble</td><td>web</td><td><button class="speak-btn" data-text="web">🔊</button></td></tr>
|
||||||
|
<tr><td>X x</td><td>equis</td><td>examen</td><td><button class="speak-btn" data-text="examen">🔊</button></td></tr>
|
||||||
|
<tr><td>Y y</td><td>ye</td><td>yo</td><td><button class="speak-btn" data-text="yo">🔊</button></td></tr>
|
||||||
|
<tr><td>Z z</td><td>zeta</td><td>zapato</td><td><button class="speak-btn" data-text="zapato">🔊</button></td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>H 不发音;ñ 发"ni"的鼻音;rr 为颤音,不能出现在词首,词首 r 直接读颤音。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="vocales">
|
||||||
|
<h2>2. 元音发音</h2>
|
||||||
|
<div class="article-title-en">Las vocales</div>
|
||||||
|
<p>西班牙语只有 <strong>5 个元音</strong>:a、e、i、o、u。发音固定,不因拼写变化。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>元音</th><th>分类</th><th>发音提示</th><th>示例</th><th>发音</th></tr>
|
||||||
|
<tr><td>a</td><td>强元音</td><td>类似"啊",口型大</td><td>casa</td><td><button class="speak-btn" data-text="casa">🔊</button></td></tr>
|
||||||
|
<tr><td>e</td><td>强元音</td><td>类似"诶",比英语短促</td><td>mesa</td><td><button class="speak-btn" data-text="mesa">🔊</button></td></tr>
|
||||||
|
<tr><td>i</td><td>弱元音</td><td>类似"衣",短促</td><td>sí</td><td><button class="speak-btn" data-text="sí">🔊</button></td></tr>
|
||||||
|
<tr><td>o</td><td>强元音</td><td>类似"哦",圆唇</td><td>todo</td><td><button class="speak-btn" data-text="todo">🔊</button></td></tr>
|
||||||
|
<tr><td>u</td><td>弱元音</td><td>类似"乌",圆唇</td><td>luna</td><td><button class="speak-btn" data-text="luna">🔊</button></td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>西班牙语元音没有长音/短音区分,发音时不要拉长。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="consonantes1">
|
||||||
|
<h2>3. 辅音发音①(b/v、c、d、g)</h2>
|
||||||
|
<div class="article-title-en">Las consonantes (1)</div>
|
||||||
|
<p><strong>b / v:</strong>发音相同。在停顿或词首读爆破音 [b],位于元音间读摩擦音 [β](双唇轻微摩擦)。</p>
|
||||||
|
<div class="example"><span>bien, vivir, haba</span><span class="zh">好,生活,豆子</span><button class="speak-btn" data-text="bien vivir haba">🔊</button></div>
|
||||||
|
|
||||||
|
<p><strong>c:</strong>在 e/i 前读 [θ](类似英语 think 的 th)或拉美地区读 [s];在 a/o/u 前读 [k]。</p>
|
||||||
|
<div class="example"><span>cero, casa, como</span><span class="zh">零,房子,我吃</span><button class="speak-btn" data-text="cero casa como">🔊</button></div>
|
||||||
|
|
||||||
|
<p><strong>d:</strong>词首/停顿后读 [d];元音间读 [ð](舌尖轻触上齿)。</p>
|
||||||
|
<div class="example"><span>día, nada, ciudad</span><span class="zh">天,没有什么,城市</span><button class="speak-btn" data-text="día nada ciudad">🔊</button></div>
|
||||||
|
|
||||||
|
<p><strong>g:</strong>在 e/i 前读 [x](类似"哈");在 a/o/u 前读 [g]。gui/gue 中 u 不发音。</p>
|
||||||
|
<div class="example"><span>gente, gato, guía</span><span class="zh">人们,猫,指南</span><button class="speak-btn" data-text="gente gato guía">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>gue/gui 的 u 不发音;üe/üi(如 pingüino)中 ü 才发音。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="consonantes2">
|
||||||
|
<h2>4. 辅音发音②(h、j、ll、ñ、q、r/rr、z)</h2>
|
||||||
|
<div class="article-title-en">Las consonantes (2)</div>
|
||||||
|
<p><strong>h:</strong>永远不发音。</p>
|
||||||
|
<div class="example"><span>hola, ahora</span><span class="zh">你好,现在</span><button class="speak-btn" data-text="hola ahora">🔊</button></div>
|
||||||
|
|
||||||
|
<p><strong>j:</strong>读 [x],类似中文"哈"但更用力。</p>
|
||||||
|
<div class="example"><span>joven, jefe</span><span class="zh">年轻的,老板</span><button class="speak-btn" data-text="joven jefe">🔊</button></div>
|
||||||
|
|
||||||
|
<p><strong>ll:</strong>传统读 [ʎ],多数地区读 [j] 或 [ʒ],类似"衣"或"日"。</p>
|
||||||
|
<div class="example"><span>llamar, calle</span><span class="zh">呼叫,街道</span><button class="speak-btn" data-text="llamar calle">🔊</button></div>
|
||||||
|
|
||||||
|
<p><strong>ñ:</strong>读 [ɲ],类似"ni"连读。</p>
|
||||||
|
<div class="example"><span>niño, España</span><span class="zh">男孩,西班牙</span><button class="speak-btn" data-text="niño España">🔊</button></div>
|
||||||
|
|
||||||
|
<p><strong>q:</strong>只出现在 que/qui 中,u 不发音,读 [k]。</p>
|
||||||
|
<div class="example"><span>queso, química</span><span class="zh">奶酪,化学</span><button class="speak-btn" data-text="queso química">🔊</button></div>
|
||||||
|
|
||||||
|
<p><strong>r / rr:</strong>r 在词中读单颤 [ɾ];词首、n/l/s 后及 rr 读多颤 [r]。</p>
|
||||||
|
<div class="example"><span>pero, perro, río</span><span class="zh">但是,狗,河</span><button class="speak-btn" data-text="pero perro río">🔊</button></div>
|
||||||
|
|
||||||
|
<p><strong>z:</strong>在西班牙读 [θ],拉美多读 [s],与 c+e/i 相同。</p>
|
||||||
|
<div class="example"><span>zapato, zorro</span><span class="zh">鞋,狐狸</span><button class="speak-btn" data-text="zapato zorro">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>r 与 rr 是语义对立音位,pero(但是)与 perro(狗)必须区分。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="diptongos">
|
||||||
|
<h2>5. 二重元音与三重元音</h2>
|
||||||
|
<div class="article-title-en">Diptongos y triptongos</div>
|
||||||
|
<p><strong>二重元音:</strong>一个强元音 + 一个弱元音,或两个弱元音组成一个音节。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>组合</th><th>示例</th><th>发音</th></tr>
|
||||||
|
<tr><td>ai / ay</td><td>aire, hay</td><td><button class="speak-btn" data-text="aire hay">🔊</button></td></tr>
|
||||||
|
<tr><td>ei / ey</td><td>seis, rey</td><td><button class="speak-btn" data-text="seis rey">🔊</button></td></tr>
|
||||||
|
<tr><td>oi / oy</td><td>hoy, soy</td><td><button class="speak-btn" data-text="hoy soy">🔊</button></td></tr>
|
||||||
|
<tr><td>ia</td><td>familia</td><td><button class="speak-btn" data-text="familia">🔊</button></td></tr>
|
||||||
|
<tr><td>ie</td><td>tiempo</td><td><button class="speak-btn" data-text="tiempo">🔊</button></td></tr>
|
||||||
|
<tr><td>io</td><td>diario</td><td><button class="speak-btn" data-text="diario">🔊</button></td></tr>
|
||||||
|
<tr><td>ua / ue / uo</td><td>agua, bueno, cuota</td><td><button class="speak-btn" data-text="agua bueno cuota">🔊</button></td></tr>
|
||||||
|
<tr><td>ui / uy</td><td>muy, cuidado</td><td><button class="speak-btn" data-text="muy cuidado">🔊</button></td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>三重元音:</strong>弱 + 强 + 弱,如 <em>Uruguay</em>、<em>Paraguay</em>。</p>
|
||||||
|
<div class="example"><span>Uruguay, Paraguay</span><span class="zh">乌拉圭,巴拉圭</span><button class="speak-btn" data-text="Uruguay Paraguay">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>两个强元音不能组成二重元音,须分属两个音节,如 <em>cae</em>(ca-e)。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="silabas">
|
||||||
|
<h2>6. 分音节规则</h2>
|
||||||
|
<div class="article-title-en">La división silábica</div>
|
||||||
|
<ol>
|
||||||
|
<li>一个辅音 + 一个元音组成音节:<em>ca-sa, pe-rro</em></li>
|
||||||
|
<li>两个元音之间有一个辅音时,辅音跟后:<em>a-mi-go</em></li>
|
||||||
|
<li>两个辅音之间一般分属两个音节:<em>es-cue-la, can-tar</em></li>
|
||||||
|
<li>辅音连缀 pr、pl、tr、tl、dr、gr、br、cr、fr、fl、gl、cl 不分开:<em>pa-dre, li-bro, cla-se</em></li>
|
||||||
|
<li>ch、ll、rr 作为单个辅音,不分开:<em>chi-co, ca-lle, pe-rro</em></li>
|
||||||
|
<li>二重元音属于同一音节:<em>tie-mpo, cau-sa</em></li>
|
||||||
|
<li>两个强元音分属不同音节:<em>le-er, ca-er</em></li>
|
||||||
|
</ol>
|
||||||
|
<div class="example"><span>es-cue-la, li-bro, tie-mpo</span><span class="zh">学校,书,时间</span><button class="speak-btn" data-text="escuela libro tiempo">🔊</button></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="acento">
|
||||||
|
<h2>7. 重音规则</h2>
|
||||||
|
<div class="article-title-en">Las reglas de acentuación</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>情况</th><th>规则</th><th>示例</th></tr>
|
||||||
|
<tr><td>以元音或 n/s 结尾</td><td>重音在倒数第二个音节</td><td>casa, joven, lunes</td></tr>
|
||||||
|
<tr><td>以其他辅音结尾</td><td>重音在最后一个音节</td><td>ciudad, español, mujer</td></tr>
|
||||||
|
<tr><td>不符合上述规则</td><td>必须加重音符号</td><td>música, teléfono, lápiz</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>重音符号还用于区分同形词:<em>si</em>(如果)与 <em>sí</em>(是),<em>el</em>(定冠词)与 <em>él</em>(他),<em>tu</em>(你的)与 <em>tú</em>(你)。</p>
|
||||||
|
<div class="example"><span>música, español, lápiz</span><span class="zh">音乐,西班牙语,铅笔</span><button class="speak-btn" data-text="música español lápiz">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>疑问词 qué、dónde、cuándo 永远带重音;关系词 que、donde、cuando 不带。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="entonacion">
|
||||||
|
<h2>8. 连读与语调</h2>
|
||||||
|
<div class="article-title-en">Enlace y entonación</div>
|
||||||
|
<p><strong>连读:</strong>词尾辅音与下一词首元音连接:<em>los amigos</em> → lo-sa-mi-gos。</p>
|
||||||
|
<div class="example"><span>los amigos, un hombre</span><span class="zh">朋友们,一个男人</span><button class="speak-btn" data-text="los amigos un hombre">🔊</button></div>
|
||||||
|
<p><strong>陈述句语调:</strong>句末下降。</p>
|
||||||
|
<div class="example"><span>Hoy estudio español.</span><span class="zh">今天我学习西班牙语。</span><button class="speak-btn" data-text="Hoy estudio español.">🔊</button></div>
|
||||||
|
<p><strong>一般疑问句语调:</strong>句末上升。</p>
|
||||||
|
<div class="example"><span>¿Estudias español?</span><span class="zh">你学习西班牙语吗?</span><button class="speak-btn" data-text="¿Estudias español?">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>问句首尾需写倒问号 ¿ 和正问号 ?;选择问句中最后一个选项前语调开始下降。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../speech.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
var btn = e.target.closest('.speak-btn');
|
||||||
|
if (btn) {
|
||||||
|
var lang = btn.dataset.lang || 'es';
|
||||||
|
var text = btn.dataset.text;
|
||||||
|
if (text) speakText(text, lang);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>词类与句型 — 西班牙语知识库</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a class="back" href="../knowledge.html">← 知识库</a>
|
||||||
|
<span class="home">🧩 词类与句型</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1>词类与句型</h1>
|
||||||
|
<p class="page-subtitle">《现代西班牙语1》常用词类与句型,共 12 个知识点。</p>
|
||||||
|
|
||||||
|
<div class="article" id="adverbios">
|
||||||
|
<h2>1. 常用副词</h2>
|
||||||
|
<div class="article-title-en">Los adverbios comunes</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>副词</th><th>含义</th><th>示例</th></tr>
|
||||||
|
<tr><td>muy</td><td>非常(修饰形容词/副词)</td><td>muy bien(很好)</td></tr>
|
||||||
|
<tr><td>mucho</td><td>很多(修饰动词/名词)</td><td>Estudio mucho.</td></tr>
|
||||||
|
<tr><td>poco</td><td>少</td><td>poco a poco(一点一点)</td></tr>
|
||||||
|
<tr><td>siempre</td><td>总是</td><td>Siempre llego temprano.</td></tr>
|
||||||
|
<tr><td>nunca</td><td>从不</td><td>Nunca como carne.</td></tr>
|
||||||
|
<tr><td>ahora</td><td>现在</td><td>Ahora estudio.</td></tr>
|
||||||
|
<tr><td>ya</td><td>已经</td><td>Ya he terminado.</td></tr>
|
||||||
|
<tr><td>todavía</td><td>仍然</td><td>Todavía no llego.</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="example"><span>Estudio mucho. Ahora vivo en Madrid. Nunca como tarde.</span><span class="zh">我学习很多。现在我住在马德里。我从不晚吃饭。</span><button class="speak-btn" data-text="Estudio mucho. Ahora vivo en Madrid. Nunca como tarde.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>muy 修饰形容词/副词;mucho 修饰动词或作名词/形容词,二者不可混用。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="preposiciones">
|
||||||
|
<h2>2. 常用介词</h2>
|
||||||
|
<div class="article-title-en">Las preposiciones comunes</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>介词</th><th>含义</th><th>示例</th></tr>
|
||||||
|
<tr><td>a</td><td>到、向</td><td>Voy a casa.</td></tr>
|
||||||
|
<tr><td>de</td><td>的、从</td><td>el libro de Juan</td></tr>
|
||||||
|
<tr><td>en</td><td>在(地点/时间)</td><td>en casa</td></tr>
|
||||||
|
<tr><td>con</td><td>和、用</td><td>con mis amigos</td></tr>
|
||||||
|
<tr><td>sin</td><td>没有</td><td>sin azúcar</td></tr>
|
||||||
|
<tr><td>para</td><td>为了、给</td><td>para ti</td></tr>
|
||||||
|
<tr><td>por</td><td>由于、经过、为了</td><td>por favor</td></tr>
|
||||||
|
<tr><td>sobre</td><td>关于、在…上面</td><td>sobre la mesa</td></tr>
|
||||||
|
<tr><td>desde</td><td>自从、从</td><td>desde enero</td></tr>
|
||||||
|
<tr><td>hasta</td><td>直到</td><td>hasta mañana</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="example"><span>Voy a casa con mis amigos. El libro está sobre la mesa.</span><span class="zh">我和朋友们回家。书在桌子上。</span><button class="speak-btn" data-text="Voy a casa con mis amigos. El libro está sobre la mesa.">🔊</button></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="contracciones">
|
||||||
|
<h2>3. 介词冠词缩合</h2>
|
||||||
|
<div class="article-title-en">Las contracciones al y del</div>
|
||||||
|
<p>西班牙语只有两组强制缩合:</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>缩合</th><th>构成</th><th>示例</th></tr>
|
||||||
|
<tr><td>al</td><td>a + el</td><td>Voy al parque.(我去公园)</td></tr>
|
||||||
|
<tr><td>del</td><td>de + el</td><td>el libro del profesor(老师的书)</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>其他介词 + el 不缩合:<em>en el parque, para el niño</em>。</p>
|
||||||
|
<div class="example"><span>Voy al cine. Salgo del trabajo a las seis.</span><span class="zh">我去电影院。我六点下班。</span><button class="speak-btn" data-text="Voy al cine. Salgo del trabajo a las seis.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>a el 和 de el 必须写 al / del;但 de + él(他)不缩合,因 él 是人称代词。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="cardinales">
|
||||||
|
<h2>4. 基数词</h2>
|
||||||
|
<div class="article-title-en">Los numerales cardinales</div>
|
||||||
|
<table>
|
||||||
|
<tr><td>0 cero</td><td>1 uno</td><td>2 dos</td><td>3 tres</td></tr>
|
||||||
|
<tr><td>4 cuatro</td><td>5 cinco</td><td>6 seis</td><td>7 siete</td></tr>
|
||||||
|
<tr><td>8 ocho</td><td>9 nueve</td><td>10 diez</td><td>11 once</td></tr>
|
||||||
|
<tr><td>12 doce</td><td>13 trece</td><td>14 catorce</td><td>15 quince</td></tr>
|
||||||
|
<tr><td>16 dieciséis</td><td>17 diecisiete</td><td>18 dieciocho</td><td>19 diecinueve</td></tr>
|
||||||
|
<tr><td>20 veinte</td><td>21 veintiuno</td><td>30 treinta</td><td>40 cuarenta</td></tr>
|
||||||
|
<tr><td>50 cincuenta</td><td>60 sesenta</td><td>70 setenta</td><td>80 ochenta</td></tr>
|
||||||
|
<tr><td>90 noventa</td><td>100 cien</td><td>101 ciento uno</td><td>200 doscientos</td></tr>
|
||||||
|
<tr><td>1000 mil</td><td>1,000,000 un millón</td><td></td><td></td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="example"><span>Tengo veinte años. Hay cien estudiantes.</span><span class="zh">我二十岁。有一百名学生。</span><button class="speak-btn" data-text="Tengo veinte años. Hay cien estudiantes.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>cien 只用于精确 100 或 100 后接名词;101 以上用 ciento(ciento uno)。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="ordinales">
|
||||||
|
<h2>5. 序数词</h2>
|
||||||
|
<div class="article-title-en">Los numerales ordinales</div>
|
||||||
|
<table>
|
||||||
|
<tr><td>1º primero</td><td>2º segundo</td><td>3º tercero</td><td>4º cuarto</td></tr>
|
||||||
|
<tr><td>5º quinto</td><td>6º sexto</td><td>7º séptimo</td><td>8º octavo</td></tr>
|
||||||
|
<tr><td>9º noveno</td><td>10º décimo</td><td>11º undécimo</td><td>12º duodécimo</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>序数词有性数变化:<em>la primera lección, los primeros días</em>。</p>
|
||||||
|
<div class="example"><span>La primera lección es fácil. Vivo en el tercer piso.</span><span class="zh">第一课很简单。我住在三楼。</span><button class="speak-btn" data-text="La primera lección es fácil. Vivo en el tercer piso.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>primero / tercero 在阳性单数名词前短尾为 primer / tercer。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="fechas">
|
||||||
|
<h2>6. 日期表达</h2>
|
||||||
|
<div class="article-title-en">Las fechas</div>
|
||||||
|
<ul>
|
||||||
|
<li>星期:lunes、martes、miércoles、jueves、viernes、sábado、domingo</li>
|
||||||
|
<li>月份:enero、febrero、marzo、abril、mayo、junio、julio、agosto、septiembre、octubre、noviembre、diciembre</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>格式:</strong>Hoy es <em>lunes, 31 de julio de 2026</em>(今天是 2026 年 7 月 31 日星期一)。</p>
|
||||||
|
<div class="example"><span>Hoy es lunes, 31 de julio de 2026. Mi cumpleaños es el 5 de mayo.</span><span class="zh">今天是 2026 年 7 月 31 日星期一。我的生日是 5 月 5 日。</span><button class="speak-btn" data-text="Hoy es lunes, 31 de julio de 2026. Mi cumpleaños es el 5 de mayo.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>日期中月份不用大写;"在星期几"用 el + 星期(el lunes),"在某日"也用 el + 数字(el 31)。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="hora">
|
||||||
|
<h2>7. 时间表达</h2>
|
||||||
|
<div class="article-title-en">La hora</div>
|
||||||
|
<p><strong>整点:</strong>Es la una. / Son las dos.(一点/两点)</p>
|
||||||
|
<p><strong>半点:</strong>Son las tres y media.(三点半)</p>
|
||||||
|
<p><strong>刻钟:</strong>Son las cuatro y cuarto.(四点一刻)</p>
|
||||||
|
<p><strong>差几分:</strong>Son las cinco menos diez.(差十分五点)</p>
|
||||||
|
<p><strong>下午/晚上:</strong>de la tarde / de la noche;中午 de la mañana。</p>
|
||||||
|
<div class="example"><span>Son las tres y media de la tarde. Son las cinco menos diez.</span><span class="zh">现在是下午三点半。差十分五点。</span><button class="speak-btn" data-text="Son las tres y media de la tarde. Son las cinco menos diez.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>只有 1 点用 es la una,其他都用 son las;问时间用 ¿Qué hora es?</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="interrogacion">
|
||||||
|
<h2>8. 疑问句</h2>
|
||||||
|
<div class="article-title-en">Las oraciones interrogativas</div>
|
||||||
|
<ul>
|
||||||
|
<li><strong>一般疑问句:</strong>句末语调上升,可省略主语,句首句尾加 ¿ ?</li>
|
||||||
|
<li><strong>特殊疑问句:</strong>疑问词 + 动词 + 其他:<em>¿Dónde vives?</em></li>
|
||||||
|
<li><strong>选择疑问句:</strong>¿... o ...?:<em>¿Café o té?</em></li>
|
||||||
|
<li><strong>否定疑问句:</strong>¿No estudias?(你不学习吗?)</li>
|
||||||
|
</ul>
|
||||||
|
<div class="example"><span>¿Estudias español? ¿Qué haces? ¿Café o té?</span><span class="zh">你学西班牙语吗?你在做什么?咖啡还是茶?</span><button class="speak-btn" data-text="¿Estudias español? ¿Qué haces? ¿Café o té?">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>疑问句首必须加倒问号 ¿,句末用正问号 ?。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="negacion">
|
||||||
|
<h2>9. 否定句</h2>
|
||||||
|
<div class="article-title-en">Las oraciones negativas</div>
|
||||||
|
<ul>
|
||||||
|
<li>在动词前加 no:<em>No estudio hoy.</em>(今天我不学习)</li>
|
||||||
|
<li>nunca / jamás(从不):<em>Nunca fumo.</em></li>
|
||||||
|
<li>nadie(没人):<em>No viene nadie.</em> / <em>Nadie viene.</em></li>
|
||||||
|
<li>nada(没什么):<em>No quiero nada.</em> / <em>Nada me gusta.</em></li>
|
||||||
|
<li>ninguno/a(没有一个):<em>No tengo ningún libro.</em></li>
|
||||||
|
<li>tampoco(也不):<em>Yo tampoco estudio.</em></li>
|
||||||
|
</ul>
|
||||||
|
<div class="example"><span>No estudio hoy. Nunca fumo. No tengo ningún libro.</span><span class="zh">今天我不学习。我从不抽烟。我没有一本书。</span><button class="speak-btn" data-text="No estudio hoy. Nunca fumo. No tengo ningún libro.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>西班牙语双重否定是正常用法:No tengo nada(我什么都没有),不要改成 No tengo algo。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="hay">
|
||||||
|
<h2>10. hay 存在句</h2>
|
||||||
|
<div class="article-title-en">Hay</div>
|
||||||
|
<p>hay 表示"有、存在",无人称,不随主语变化,后接单数或复数名词。</p>
|
||||||
|
<ul>
|
||||||
|
<li>肯定:<em>Hay un libro en la mesa.</em></li>
|
||||||
|
<li>否定:<em>No hay agua.</em>(没有水)</li>
|
||||||
|
<li>疑问:<em>¿Hay algún supermercado aquí?</em>(这里有超市吗?)</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>区分:</strong>hay 表示存在;tener 表示拥有:<em>Hay dos perros.</em>(有两只狗存在)/ <em>Tengo dos perros.</em>(我拥有两只狗)</p>
|
||||||
|
<div class="example"><span>Hay un libro en la mesa. No hay agua. Hay dos supermercados aquí.</span><span class="zh">桌上有一本书。没有水。这里有两家超市。</span><button class="speak-btn" data-text="Hay un libro en la mesa. No hay agua. Hay dos supermercados aquí.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>hay 永远不变位为 han/hayn;"有许多"用 hay muchos / muchas。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="ser-estar">
|
||||||
|
<h2>11. ser / estar 区别</h2>
|
||||||
|
<div class="article-title-en">Ser vs. Estar</div>
|
||||||
|
<table>
|
||||||
|
<tr><th></th><th>ser</th><th>estar</th></tr>
|
||||||
|
<tr><td>本质/身份</td><td>Soy estudiante.(我是学生)</td><td>—</td></tr>
|
||||||
|
<tr><td>国籍/来源</td><td>Ella es de China.(她来自中国)</td><td>—</td></tr>
|
||||||
|
<tr><td>时间/日期</td><td>Hoy es lunes.(今天是星期一)</td><td>—</td></tr>
|
||||||
|
<tr><td>职业</td><td>Es médico.(他是医生)</td><td>—</td></tr>
|
||||||
|
<tr><td>位置</td><td>—</td><td>Estoy en casa.(我在家)</td></tr>
|
||||||
|
<tr><td>临时状态</td><td>—</td><td>Estoy cansado.(我累了)</td></tr>
|
||||||
|
<tr><td>进行时</td><td>—</td><td>Estoy leyendo.(我正在读)</td></tr>
|
||||||
|
<tr><td>性质</td><td>Es bonito.(它很漂亮)</td><td>Está bonito.(现在看起来漂亮)</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="example"><span>Soy estudiante. Estoy en casa. Estoy cansado. Hoy es lunes.</span><span class="zh">我是学生。我在家。我累了。今天是星期一。</span><button class="speak-btn" data-text="Soy estudiante. Estoy en casa. Estoy cansado. Hoy es lunes.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>ser + 形容词表固有性质,estar + 形容词表临时状态:Es listo(聪明)≠ Está listo(准备好了)。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="orden">
|
||||||
|
<h2>12. 基本语序</h2>
|
||||||
|
<div class="article-title-en">El orden de las palabras</div>
|
||||||
|
<p><strong>陈述句基本语序:</strong>主语 + 动词 + 宾语 / 补语。</p>
|
||||||
|
<div class="example"><span>Yo estudio español en la universidad.</span><span class="zh">我在大学学习西班牙语。</span><button class="speak-btn" data-text="Yo estudio español en la universidad.">🔊</button></div>
|
||||||
|
<p><strong>常见变体:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>状语可前置:<em>Ahora estudio.</em>(现在我在学习)</li>
|
||||||
|
<li>疑问句动词常前置:<em>¿Vives en Madrid?</em></li>
|
||||||
|
<li>主语可后置强调:<em>Llegó Juan.</em>(胡安到了)</li>
|
||||||
|
<li>形容词通常后置:<em>una casa blanca</em></li>
|
||||||
|
</ul>
|
||||||
|
<div class="example"><span>Ahora estudio español en la universidad. ¿Vives en Madrid?</span><span class="zh">现在我在大学学习西班牙语。你住在马德里吗?</span><button class="speak-btn" data-text="Ahora estudio español en la universidad. ¿Vives en Madrid?">🔊</button></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../speech.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
var btn = e.target.closest('.speak-btn');
|
||||||
|
if (btn) {
|
||||||
|
var lang = btn.dataset.lang || 'es';
|
||||||
|
var text = btn.dataset.text;
|
||||||
|
if (text) speakText(text, lang);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>代词 — 西班牙语知识库</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a class="back" href="../knowledge.html">← 知识库</a>
|
||||||
|
<span class="home">👤 代词</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1>代词</h1>
|
||||||
|
<p class="page-subtitle">《现代西班牙语1》代词体系,共 5 个知识点。</p>
|
||||||
|
|
||||||
|
<div class="article" id="personales">
|
||||||
|
<h2>1. 人称代词(主格)</h2>
|
||||||
|
<div class="article-title-en">Los pronombres personales</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>人称</th><th>单数</th><th>复数</th></tr>
|
||||||
|
<tr><td>第一人称</td><td>yo(我)</td><td>nosotros / nosotras(我们)</td></tr>
|
||||||
|
<tr><td>第二人称</td><td>tú(你), usted(您)</td><td>vosotros / vosotras(你们), ustedes(您们)</td></tr>
|
||||||
|
<tr><td>第三人称</td><td>él(他), ella(她)</td><td>ellos / ellas(他们/她们)</td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>用法要点:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>西班牙人称代词作主语时常省略,动词变位已表明人称:<em>Estudio español.</em>(我学西语)</li>
|
||||||
|
<li>usted/ustedes 虽为第二人称,但动词按第三人称变位</li>
|
||||||
|
<li>拉美通常用 ustedes 代替 vosotros</li>
|
||||||
|
</ul>
|
||||||
|
<div class="example"><span>Yo soy estudiante. Tú eres profesor. Él es médico.</span><span class="zh">我是学生。你是老师。他是医生。</span><button class="speak-btn" data-text="Yo soy estudiante. Tú eres profesor. Él es médico.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>省略主语是常态,但为强调或避免歧义时可保留:<em>Ella trabaja aquí.</em></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="objeto-directo">
|
||||||
|
<h2>2. 宾格代词(直接宾语)</h2>
|
||||||
|
<div class="article-title-en">Los pronombres de objeto directo</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>人称</th><th>单数</th><th>复数</th></tr>
|
||||||
|
<tr><td>第一人称</td><td>me(我)</td><td>nos(我们)</td></tr>
|
||||||
|
<tr><td>第二人称</td><td>te(你)</td><td>os(你们)</td></tr>
|
||||||
|
<tr><td>第三人称</td><td>lo / la(他/她/它)</td><td>los / las(他们/她们/它们)</td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>位置:</strong>变位动词前,或紧跟在原形动词后:<em>Lo veo.</em> / <em>Voy a verlo.</em></p>
|
||||||
|
<div class="example"><span>Veo a María. → La veo. Leo libros. → Los leo.</span><span class="zh">我看见玛丽亚 → 我看见她;我读书 → 我读它们</span><button class="speak-btn" data-text="La veo. Los leo.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>指人直接宾语前要加 a(Veo a María);宾格代词 lo/la 要随性数变化。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="objeto-indirecto">
|
||||||
|
<h2>3. 与格代词(间接宾语)</h2>
|
||||||
|
<div class="article-title-en">Los pronombres de objeto indirecto</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>人称</th><th>单数</th><th>复数</th></tr>
|
||||||
|
<tr><td>第一人称</td><td>me(给我)</td><td>nos(给我们)</td></tr>
|
||||||
|
<tr><td>第二人称</td><td>te(给你)</td><td>os(给你们)</td></tr>
|
||||||
|
<tr><td>第三人称</td><td>le(给他/她/您)</td><td>les(给他们/她们/您们)</td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>与格 + 宾格同现:</strong>le/les 变 se:<em>Se lo doy.</em>(我把它给他)</p>
|
||||||
|
<div class="example"><span>Me das el libro. → Me lo das. Te escribo una carta. → Te la escribo.</span><span class="zh">你给我书 → 你把它给我;我给你写信 → 我把它写给你</span><button class="speak-btn" data-text="Me lo das. Te la escribo.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>le/les + lo/la/los/las 时,le/les 必须改为 se,不能写 le lo。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="demostrativos-pron">
|
||||||
|
<h2>4. 指示代词</h2>
|
||||||
|
<div class="article-title-en">Los pronombres demostrativos</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>距离</th><th>阳性</th><th>阴性</th><th>中性</th></tr>
|
||||||
|
<tr><td>近</td><td>éste / éstos</td><td>ésta / éstas</td><td>esto</td></tr>
|
||||||
|
<tr><td>中</td><td>ése / ésos</td><td>ésa / ésas</td><td>eso</td></tr>
|
||||||
|
<tr><td>远</td><td>aquél / aquéllos</td><td>aquélla / aquéllas</td><td>aquello</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>中性形式 esto/eso/aquello 指代事物或概念,无复数、无性变化。</p>
|
||||||
|
<div class="example"><span>¿Qué es esto? Eso es importante. Aquello no me gusta.</span><span class="zh">这是什么?那很重要。那个我不喜欢。</span><button class="speak-btn" data-text="¿Qué es esto? Eso es importante. Aquello no me gusta.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>现代西班牙语皇家学院已允许指示代词不加重音,但教材中 éste/ése 通常仍带重音以区别形容词。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="interrogativos">
|
||||||
|
<h2>5. 疑问代词</h2>
|
||||||
|
<div class="article-title-en">Los pronombres interrogativos</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>疑问词</th><th>含义</th><th>示例</th></tr>
|
||||||
|
<tr><td>qué</td><td>什么</td><td>¿Qué estudias?</td></tr>
|
||||||
|
<tr><td>quién / quiénes</td><td>谁</td><td>¿Quién es ella?</td></tr>
|
||||||
|
<tr><td>cuál / cuáles</td><td>哪一个</td><td>¿Cuál prefieres?</td></tr>
|
||||||
|
<tr><td>cuánto/a/os/as</td><td>多少</td><td>¿Cuánto cuesta?</td></tr>
|
||||||
|
<tr><td>dónde</td><td>哪里</td><td>¿Dónde vives?</td></tr>
|
||||||
|
<tr><td>cuándo</td><td>什么时候</td><td>¿Cuándo llegas?</td></tr>
|
||||||
|
<tr><td>cómo</td><td>怎样</td><td>¿Cómo estás?</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="example"><span>¿Qué haces? ¿Quién es tu amigo? ¿Dónde está la biblioteca?</span><span class="zh">你在做什么?谁是你的朋友?图书馆在哪里?</span><button class="speak-btn" data-text="¿Qué haces? ¿Quién es tu amigo? ¿Dónde está la biblioteca?">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>直接/间接疑问句中的疑问词都必须带重音:<em>No sé qué hacer.</em>(我不知道做什么)</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../speech.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
var btn = e.target.closest('.speak-btn');
|
||||||
|
if (btn) {
|
||||||
|
var lang = btn.dataset.lang || 'es';
|
||||||
|
var text = btn.dataset.text;
|
||||||
|
if (text) speakText(text, lang);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #f8f9fa;
|
||||||
|
--panel-bg: #ffffff;
|
||||||
|
--border: #dee2e6;
|
||||||
|
--text: #212529;
|
||||||
|
--text-muted: #6c757d;
|
||||||
|
--accent: #0d6efd;
|
||||||
|
--accent-hover: #0b5ed7;
|
||||||
|
--danger: #dc3545;
|
||||||
|
--success: #198754;
|
||||||
|
--warning-bg: #fff3cd;
|
||||||
|
--warning-border: #ffc107;
|
||||||
|
--code-bg: #f1f3f5;
|
||||||
|
--radius: 8px;
|
||||||
|
--font-mono: 'Consolas', 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg: #1a1d23;
|
||||||
|
--panel-bg: #212529;
|
||||||
|
--border: #373b3e;
|
||||||
|
--text: #e9ecef;
|
||||||
|
--text-muted: #9ca3af;
|
||||||
|
--accent: #3b82f6;
|
||||||
|
--accent-hover: #60a5fa;
|
||||||
|
--danger: #ef4444;
|
||||||
|
--success: #22c55e;
|
||||||
|
--warning-bg: #3d3520;
|
||||||
|
--warning-border: #b8860b;
|
||||||
|
--code-bg: #2d2d2f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 1.5rem;
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
a { color: var(--accent); text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
.top-nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.top-nav .back { font-size: 0.9rem; }
|
||||||
|
.top-nav .home { font-weight: 600; }
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
.page-subtitle {
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
.category-card {
|
||||||
|
display: block;
|
||||||
|
background: var(--panel-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 1.25rem;
|
||||||
|
color: var(--text);
|
||||||
|
transition: border-color 0.15s, box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
.category-card:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.category-card h2 { font-size: 1.1rem; margin-bottom: 0.3rem; }
|
||||||
|
.category-card p { color: var(--text-muted); font-size: 0.85rem; }
|
||||||
|
.category-card .count {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--accent);
|
||||||
|
background: var(--code-bg);
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Article sections */
|
||||||
|
.article {
|
||||||
|
background: var(--panel-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.article h2 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.article .article-title-en {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.article h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 1rem 0 0.4rem;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.article p { margin-bottom: 0.5rem; }
|
||||||
|
.article ul, .article ol { margin: 0.4rem 0 0.75rem 1.4rem; }
|
||||||
|
.article li { margin-bottom: 0.25rem; }
|
||||||
|
|
||||||
|
.example {
|
||||||
|
background: var(--code-bg);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
margin: 0.5rem 0 0.75rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
.example .zh {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.speak-btn {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.25rem 0.6rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-muted);
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.speak-btn:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
background: var(--warning-bg);
|
||||||
|
border-left: 4px solid var(--warning-border);
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
padding: 0.6rem 0.9rem;
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.tip strong { color: var(--warning-border); }
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 0.6rem 0 0.9rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 0.45rem 0.6rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background: var(--code-bg);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
padding: 0.1rem 0.45rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--code-bg);
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-left: 0.4rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
body { padding: 0.75rem; }
|
||||||
|
.example { flex-direction: column; align-items: flex-start; }
|
||||||
|
.category-grid { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>名词与冠词 — 西班牙语知识库</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a class="back" href="../knowledge.html">← 知识库</a>
|
||||||
|
<span class="home">📦 名词与冠词</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1>名词与冠词</h1>
|
||||||
|
<p class="page-subtitle">《现代西班牙语1》基础词法,共 4 个知识点。</p>
|
||||||
|
|
||||||
|
<div class="article" id="genero">
|
||||||
|
<h2>1. 名词的性</h2>
|
||||||
|
<div class="article-title-en">El género del sustantivo</div>
|
||||||
|
<p>西班牙语名词分<strong>阳性</strong>和<strong>阴性</strong>,且名词的性影响冠词和形容词的形式。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>规则</th><th>阳性</th><th>阴性</th></tr>
|
||||||
|
<tr><td>以 -o 结尾多为阳性</td><td>libro(书), perro(狗)</td><td>—</td></tr>
|
||||||
|
<tr><td>以 -a 结尾多为阴性</td><td>—</td><td>mesa(桌子), casa(房子)</td></tr>
|
||||||
|
<tr><td>以 -or、-ón 结尾多为阳性</td><td>profesor(男老师)</td><td>—</td></tr>
|
||||||
|
<tr><td>以 -ción、-sión、-dad 结尾多为阴性</td><td>—</td><td>canción(歌曲), ciudad(城市)</td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>例外:</strong>el día(天)、la mano(手)、el mapa(地图)、el problema(问题)、el programa(节目)。</p>
|
||||||
|
<div class="example"><span>el libro, la mesa, el problema</span><span class="zh">书,桌子,问题</span><button class="speak-btn" data-text="el libro la mesa el problema">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>以 -a 结尾不一定是阴性,如 el día、el mapa、el programa。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="numero">
|
||||||
|
<h2>2. 名词的数</h2>
|
||||||
|
<div class="article-title-en">El número del sustantivo</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>规则</th><th>单数</th><th>复数</th></tr>
|
||||||
|
<tr><td>以元音结尾加 -s</td><td>libro</td><td>libros</td></tr>
|
||||||
|
<tr><td>以辅音结尾加 -es</td><td>ciudad</td><td>ciudades</td></tr>
|
||||||
|
<tr><td>以 z 结尾变 z → c 再加 -es</td><td>lápiz</td><td>lápices</td></tr>
|
||||||
|
<tr><td>以 -s 结尾且末音节不重读,单复数同形</td><td>lunes</td><td>lunes</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="example"><span>un libro, dos libros; una ciudad, tres ciudades</span><span class="zh">一本书、两本书;一座城、三座城</span><button class="speak-btn" data-text="un libro dos libros una ciudad tres ciudades">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>z 结尾变复数先变 c(lápiz → lápices),不是直接加 es。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="articulo-def">
|
||||||
|
<h2>3. 定冠词</h2>
|
||||||
|
<div class="article-title-en">El artículo determinado</div>
|
||||||
|
<p>定冠词表示特指,相当于"这个/那个"。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th></th><th>阳性</th><th>阴性</th></tr>
|
||||||
|
<tr><td>单数</td><td>el</td><td>la</td></tr>
|
||||||
|
<tr><td>复数</td><td>los</td><td>las</td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>用法:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>特指已知的人或物:<em>el libro</em>(那本书)</li>
|
||||||
|
<li>表示类别:<em>El español es difícil.</em></li>
|
||||||
|
<li>星期几前常用:<em>el lunes</em></li>
|
||||||
|
<li>阴性单数名词以重读 a/ha 开头时用 el:<em>el agua, el aula</em>(但复数仍用 las)</li>
|
||||||
|
</ul>
|
||||||
|
<div class="example"><span>el libro, la casa, los amigos, las mujeres</span><span class="zh">书,房子,朋友们,女人们</span><button class="speak-btn" data-text="el libro la casa los amigos las mujeres">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>el agua 是"阴性名词 + 阳性冠词",形容阴性词仍用阴性形式:el agua fría。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="articulo-indef">
|
||||||
|
<h2>4. 不定冠词</h2>
|
||||||
|
<div class="article-title-en">El artículo indeterminado</div>
|
||||||
|
<p>不定冠词表示泛指,相当于"一个/一些"。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th></th><th>阳性</th><th>阴性</th></tr>
|
||||||
|
<tr><td>单数</td><td>un</td><td>una</td></tr>
|
||||||
|
<tr><td>复数</td><td>unos</td><td>unas</td></tr>
|
||||||
|
</table>
|
||||||
|
<ul>
|
||||||
|
<li>首次提到:<em>Tengo un amigo.</em>(我有一个朋友)</li>
|
||||||
|
<li>不定量:<em>Necesito unos libros.</em>(我需要一些书)</li>
|
||||||
|
<li>职业、国籍前的 un/una:<em>Es una profesora.</em>(她是一位老师)</li>
|
||||||
|
<li>无冠词:<em>No tengo dinero.</em>(我没钱)</li>
|
||||||
|
</ul>
|
||||||
|
<div class="example"><span>un amigo, una amiga, unos amigos, unas amigas</span><span class="zh">一个男/女朋友,一些男/女朋友</span><button class="speak-btn" data-text="un amigo una amiga unos amigos unas amigas">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>表示职业的句子常用 un/una(Es médico),否定存在常省略冠词(No hay agua)。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../speech.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
var btn = e.target.closest('.speak-btn');
|
||||||
|
if (btn) {
|
||||||
|
var lang = btn.dataset.lang || 'es';
|
||||||
|
var text = btn.dataset.text;
|
||||||
|
if (text) speakText(text, lang);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>动词与时态 — 西班牙语知识库</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a class="back" href="../knowledge.html">← 知识库</a>
|
||||||
|
<span class="home">⚡ 动词与时态</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1>动词与时态</h1>
|
||||||
|
<p class="page-subtitle">《现代西班牙语1》核心动词体系,共 7 个知识点。</p>
|
||||||
|
|
||||||
|
<div class="article" id="conjugacion">
|
||||||
|
<h2>1. 规则动词变位</h2>
|
||||||
|
<div class="article-title-en">La conjugación regular</div>
|
||||||
|
<p>西班牙语动词按词尾分为 <strong>-ar、-er、-ir</strong> 三类。规则动词变位时去掉词尾,加对应人称词尾。</p>
|
||||||
|
<h3>-ar 动词(hablar 说)</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>人称</th><th>词尾</th><th>变位</th></tr>
|
||||||
|
<tr><td>yo</td><td>-o</td><td>hablo</td></tr>
|
||||||
|
<tr><td>tú</td><td>-as</td><td>hablas</td></tr>
|
||||||
|
<tr><td>él/ella/usted</td><td>-a</td><td>habla</td></tr>
|
||||||
|
<tr><td>nosotros</td><td>-amos</td><td>hablamos</td></tr>
|
||||||
|
<tr><td>vosotros</td><td>-áis</td><td>habláis</td></tr>
|
||||||
|
<tr><td>ellos/ustedes</td><td>-an</td><td>hablan</td></tr>
|
||||||
|
</table>
|
||||||
|
<h3>-er 动词(comer 吃)</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>人称</th><th>词尾</th><th>变位</th></tr>
|
||||||
|
<tr><td>yo</td><td>-o</td><td>como</td></tr>
|
||||||
|
<tr><td>tú</td><td>-es</td><td>comes</td></tr>
|
||||||
|
<tr><td>él/ella/usted</td><td>-e</td><td>come</td></tr>
|
||||||
|
<tr><td>nosotros</td><td>-emos</td><td>comemos</td></tr>
|
||||||
|
<tr><td>vosotros</td><td>-éis</td><td>coméis</td></tr>
|
||||||
|
<tr><td>ellos/ustedes</td><td>-en</td><td>comen</td></tr>
|
||||||
|
</table>
|
||||||
|
<h3>-ir 动词(vivir 居住)</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>人称</th><th>词尾</th><th>变位</th></tr>
|
||||||
|
<tr><td>yo</td><td>-o</td><td>vivo</td></tr>
|
||||||
|
<tr><td>tú</td><td>-es</td><td>vives</td></tr>
|
||||||
|
<tr><td>él/ella/usted</td><td>-e</td><td>vive</td></tr>
|
||||||
|
<tr><td>nosotros</td><td>-imos</td><td>vivimos</td></tr>
|
||||||
|
<tr><td>vosotros</td><td>-ís</td><td>vivís</td></tr>
|
||||||
|
<tr><td>ellos/ustedes</td><td>-en</td><td>viven</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="example"><span>Hablo español. Comemos juntos. Viven en Madrid.</span><span class="zh">我说西班牙语。我们一起吃饭。他们住在马德里。</span><button class="speak-btn" data-text="Hablo español. Comemos juntos. Viven en Madrid.">🔊</button></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="presente">
|
||||||
|
<h2>2. 陈述式现在时</h2>
|
||||||
|
<div class="article-title-en">El presente de indicativo</div>
|
||||||
|
<p><strong>用法:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>表示经常性动作、习惯:<em>Estudio todos los días.</em>(我每天学习)</li>
|
||||||
|
<li>表示此刻正在发生:<em>Ahora trabajo.</em>(我现在在工作)</li>
|
||||||
|
<li>表示普遍真理:<em>El sol sale por el este.</em>(太阳从东边升起)</li>
|
||||||
|
<li>表示将来计划(常带时间状语):<em>Mañana voy a Madrid.</em>(明天我去马德里)</li>
|
||||||
|
</ul>
|
||||||
|
<div class="example"><span>Estudio español todos los días. Ahora leo un libro.</span><span class="zh">我每天学习西班牙语。现在我正在读一本书。</span><button class="speak-btn" data-text="Estudio español todos los días. Ahora leo un libro.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>现在时也可表达很近的将来,尤其与 ir a + inf 同义但更口语化。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="irregulares">
|
||||||
|
<h2>3. 不规则动词</h2>
|
||||||
|
<div class="article-title-en">Los verbos irregulares</div>
|
||||||
|
<h3>ser(是)</h3>
|
||||||
|
<div class="example"><span>soy, eres, es, somos, sois, son</span><button class="speak-btn" data-text="soy eres es somos sois son">🔊</button></div>
|
||||||
|
<h3>estar(在/处于)</h3>
|
||||||
|
<div class="example"><span>estoy, estás, está, estamos, estáis, están</span><button class="speak-btn" data-text="estoy estás está estamos estáis están">🔊</button></div>
|
||||||
|
<h3>tener(有)</h3>
|
||||||
|
<div class="example"><span>tengo, tienes, tiene, tenemos, tenéis, tienen</span><button class="speak-btn" data-text="tengo tienes tiene tenemos tenéis tienen">🔊</button></div>
|
||||||
|
<h3>ir(去)</h3>
|
||||||
|
<div class="example"><span>voy, vas, va, vamos, vais, van</span><button class="speak-btn" data-text="voy vas va vamos vais van">🔊</button></div>
|
||||||
|
<h3>hacer(做)</h3>
|
||||||
|
<div class="example"><span>hago, haces, hace, hacemos, hacéis, hacen</span><button class="speak-btn" data-text="hago haces hace hacemos hacéis hacen">🔊</button></div>
|
||||||
|
<p><strong>其他常见不规则:</strong>venir(来)、poder(能够)、querer(想要)、decir(说)、ver(看见)。</p>
|
||||||
|
<div class="example"><span>Soy estudiante. Estoy cansado. Tengo un libro. Voy a casa. Hago la tarea.</span><span class="zh">我是学生。我累了。我有一本书。我回家。我做作业。</span><button class="speak-btn" data-text="Soy estudiante. Estoy cansado. Tengo un libro. Voy a casa. Hago la tarea.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>tener 第一人称单数特殊(tengo);ir 与 ser 的变位有相似点,注意区分。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="preterito-perfecto">
|
||||||
|
<h2>4. 现在完成时</h2>
|
||||||
|
<div class="article-title-en">El pretérito perfecto compuesto</div>
|
||||||
|
<p><strong>构成:</strong>haber 的现在时 + 过去分词(-ado / -ido)。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>人称</th><th>haber</th><th>示例(hablar)</th></tr>
|
||||||
|
<tr><td>yo</td><td>he</td><td>he hablado</td></tr>
|
||||||
|
<tr><td>tú</td><td>has</td><td>has hablado</td></tr>
|
||||||
|
<tr><td>él/ella</td><td>ha</td><td>ha hablado</td></tr>
|
||||||
|
<tr><td>nosotros</td><td>hemos</td><td>hemos hablado</td></tr>
|
||||||
|
<tr><td>vosotros</td><td>habéis</td><td>habéis hablado</td></tr>
|
||||||
|
<tr><td>ellos</td><td>han</td><td>han hablado</td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>用法:</strong>表示刚刚完成的、与现在有关的动作:<em>Hoy he estudiado.</em>(今天我学了)</p>
|
||||||
|
<div class="example"><span>Hoy he comido en casa. Ya he hecho la tarea.</span><span class="zh">今天我吃了饭。我已经做完作业。</span><button class="speak-btn" data-text="Hoy he comido en casa. Ya he hecho la tarea.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>不规则过去分词:hacer→hecho、decir→dicho、escribir→escrito、ver→visto、abrir→abierto。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="preterito-indefinido">
|
||||||
|
<h2>5. 简单过去时</h2>
|
||||||
|
<div class="article-title-en">El pretérito indefinido</div>
|
||||||
|
<p><strong>规则变位:</strong></p>
|
||||||
|
<table>
|
||||||
|
<tr><th>人称</th><th>hablar</th><th>comer</th><th>vivir</th></tr>
|
||||||
|
<tr><td>yo</td><td>hablé</td><td>comí</td><td>viví</td></tr>
|
||||||
|
<tr><td>tú</td><td>hablaste</td><td>comiste</td><td>viviste</td></tr>
|
||||||
|
<tr><td>él/ella</td><td>habló</td><td>comió</td><td>vivió</td></tr>
|
||||||
|
<tr><td>nosotros</td><td>hablamos</td><td>comimos</td><td>vivimos</td></tr>
|
||||||
|
<tr><td>vosotros</td><td>hablasteis</td><td>comisteis</td><td>vivisteis</td></tr>
|
||||||
|
<tr><td>ellos</td><td>hablaron</td><td>comieron</td><td>vivieron</td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>用法:</strong>表示过去已结束的动作:<em>Ayer estudié dos horas.</em>(昨天我学了两个小时)</p>
|
||||||
|
<div class="example"><span>Ayer fui al parque. Compré un libro. Vi una película.</span><span class="zh">昨天我去了公园。我买了一本书。我看了一部电影。</span><button class="speak-btn" data-text="Ayer fui al parque. Compré un libro. Vi una película.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>fui 既是 ir 也是 ser 的简单过去时第一/三人称单数,靠上下文判断。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="frases-verbales">
|
||||||
|
<h2>6. 动词短语</h2>
|
||||||
|
<div class="article-title-en">Las perífrasis verbales</div>
|
||||||
|
<table>
|
||||||
|
<tr><th>结构</th><th>含义</th><th>示例</th></tr>
|
||||||
|
<tr><td>ir a + inf</td><td>将要去做</td><td>Voy a estudiar.(我要学习)</td></tr>
|
||||||
|
<tr><td>tener que + inf</td><td>必须做</td><td>Tengo que trabajar.(我必须工作)</td></tr>
|
||||||
|
<tr><td>estar + gerundio</td><td>正在做</td><td>Estoy leyendo.(我正在读)</td></tr>
|
||||||
|
<tr><td>acabar de + inf</td><td>刚做完</td><td>Acabo de llegar.(我刚到)</td></tr>
|
||||||
|
<tr><td>hay que + inf</td><td>必须(无人称)</td><td>Hay que estudiar.(必须学习)</td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>现在分词构成:</strong>-ar → -ando(hablar → hablando);-er/-ir → -iendo(comer → comiendo, vivir → viviendo)。</p>
|
||||||
|
<div class="example"><span>Voy a estudiar. Tengo que trabajar. Estoy leyendo un libro.</span><span class="zh">我要学习。我必须工作。我正在读一本书。</span><button class="speak-btn" data-text="Voy a estudiar. Tengo que trabajar. Estoy leyendo un libro.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>estar + gerundio 中 estar 随主语变位,gerundio 不变。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="gustar">
|
||||||
|
<h2>7. gustar 类动词</h2>
|
||||||
|
<div class="article-title-en">El verbo gustar</div>
|
||||||
|
<p>gustar 字面义"使喜欢",因此<strong>受事作主语</strong>,喜欢的人用间接宾语代词。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>受事</th><th>结构</th><th>示例</th></tr>
|
||||||
|
<tr><td>单数名词/动词</td><td>me/te/le/nos/os/les + gusta</td><td>Me gusta el café.</td></tr>
|
||||||
|
<tr><td>复数名词</td><td>me/te/le/nos/os/les + gustan</td><td>Me gustan los libros.</td></tr>
|
||||||
|
</table>
|
||||||
|
<p><strong>同类动词:</strong>encantar(非常喜欢)、interesar(感兴趣)、faltar(缺少)、importar(重要)。</p>
|
||||||
|
<div class="example"><span>Me gusta el café. Me gustan los libros. Te interesa el español.</span><span class="zh">我喜欢咖啡。我喜欢书。你对西班牙语感兴趣。</span><button class="speak-btn" data-text="Me gusta el café. Me gustan los libros. Te interesa el español.">🔊</button></div>
|
||||||
|
<div class="tip"><strong>易错提醒:</strong>Me gusta los libros 是错的;受事为复数时 gustar 必须用 gustan。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../speech.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
var btn = e.target.closest('.speak-btn');
|
||||||
|
if (btn) {
|
||||||
|
var lang = btn.dataset.lang || 'es';
|
||||||
|
var text = btn.dataset.text;
|
||||||
|
if (text) speakText(text, lang);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
function initSpeech() {
|
||||||
|
if (!window.speechSynthesis) return;
|
||||||
|
var voices = window.speechSynthesis.getVoices();
|
||||||
|
if (voices.length > 0) return;
|
||||||
|
window.speechSynthesis.onvoiceschanged = function() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function speakText(text, lang) {
|
||||||
|
if (!text) return;
|
||||||
|
if (!window.speechSynthesis) { return; }
|
||||||
|
if (window.speechSynthesis.getVoices().length === 0) {
|
||||||
|
window.speechSynthesis.onvoiceschanged = function() {
|
||||||
|
doSpeak(text, lang);
|
||||||
|
window.speechSynthesis.onvoiceschanged = null;
|
||||||
|
};
|
||||||
|
window.speechSynthesis.getVoices();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
doSpeak(text, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSpeak(text, lang) {
|
||||||
|
window.speechSynthesis.cancel();
|
||||||
|
var utterance = new SpeechSynthesisUtterance(text);
|
||||||
|
var langMap = { 'es': 'es-ES', 'en': 'en-US', 'zh': 'zh-CN' };
|
||||||
|
utterance.lang = langMap[lang] || lang;
|
||||||
|
utterance.rate = 0.9;
|
||||||
|
utterance.pitch = 1;
|
||||||
|
window.speechSynthesis.speak(utterance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'complete') initSpeech();
|
||||||
|
else document.addEventListener('DOMContentLoaded', initSpeech);
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #f8f9fa;
|
||||||
|
--panel-bg: #ffffff;
|
||||||
|
--border: #dee2e6;
|
||||||
|
--text: #212529;
|
||||||
|
--text-muted: #6c757d;
|
||||||
|
--accent: #0d6efd;
|
||||||
|
--accent-hover: #0b5ed7;
|
||||||
|
--danger: #dc3545;
|
||||||
|
--success: #198754;
|
||||||
|
--highlight: #fff3cd;
|
||||||
|
--shadow: 0 1px 3px rgba(0,0,0,0.08);
|
||||||
|
--radius: 8px;
|
||||||
|
--font-mono: 'Consolas', 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg: #1a1d23;
|
||||||
|
--panel-bg: #212529;
|
||||||
|
--border: #373b3e;
|
||||||
|
--text: #e9ecef;
|
||||||
|
--text-muted: #9ca3af;
|
||||||
|
--accent: #3b82f6;
|
||||||
|
--accent-hover: #60a5fa;
|
||||||
|
--danger: #ef4444;
|
||||||
|
--success: #22c55e;
|
||||||
|
--highlight: #3d3520;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body { height: 100%; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 240px 1fr 280px;
|
||||||
|
gap: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app > * {
|
||||||
|
background: var(--panel-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header h2 { font-size: 0.95rem; font-weight: 600; }
|
||||||
|
|
||||||
|
/* Left panel — entry list */
|
||||||
|
#panel-list { overflow-y: auto; }
|
||||||
|
#entry-list {
|
||||||
|
list-style: none;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
#entry-list li {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
transition: background 0.1s;
|
||||||
|
}
|
||||||
|
#entry-list li:hover { background: var(--highlight); }
|
||||||
|
#entry-list li.active { background: var(--highlight); border-left: 3px solid var(--accent); }
|
||||||
|
#entry-list .entry-date { font-size: 0.75rem; color: var(--text-muted); }
|
||||||
|
#entry-list .entry-preview { font-size: 0.85rem; margin-top: 0.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.entry-actions { display: none; gap: 0.3rem; margin-top: 0.3rem; }
|
||||||
|
#entry-list li:hover .entry-actions { display: flex; }
|
||||||
|
.entry-actions button {
|
||||||
|
background: none; border: 1px solid var(--border); border-radius: 4px;
|
||||||
|
padding: 0.15rem 0.4rem; font-size: 0.7rem; cursor: pointer; color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.entry-actions .btn-delete { color: var(--danger); border-color: var(--danger); }
|
||||||
|
|
||||||
|
#btn-new {
|
||||||
|
background: var(--accent); color: #fff; border: none;
|
||||||
|
width: 28px; height: 28px; border-radius: 6px;
|
||||||
|
font-size: 1.2rem; cursor: pointer; line-height: 1;
|
||||||
|
}
|
||||||
|
#btn-new:hover { background: var(--accent-hover); }
|
||||||
|
|
||||||
|
/* Grammar quick access */
|
||||||
|
#grammar-quick {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.grammar-header {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.more-link {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.more-link:hover { text-decoration: underline; }
|
||||||
|
#grammar-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
max-height: 220px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
#grammar-list li {
|
||||||
|
padding: 0.35rem 1rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
#grammar-list a {
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#grammar-list a:hover { color: var(--accent); }
|
||||||
|
|
||||||
|
/* Center panel — editor */
|
||||||
|
#panel-editor { display: flex; flex-direction: column; }
|
||||||
|
#entry-date {
|
||||||
|
border: 1px solid var(--border); border-radius: 4px;
|
||||||
|
padding: 0.3rem 0.5rem; font-size: 0.85rem;
|
||||||
|
background: var(--panel-bg); color: var(--text);
|
||||||
|
}
|
||||||
|
#btn-save {
|
||||||
|
background: var(--accent); color: #fff; border: none;
|
||||||
|
padding: 0.35rem 0.75rem; border-radius: 6px;
|
||||||
|
font-size: 0.85rem; cursor: pointer; white-space: nowrap;
|
||||||
|
}
|
||||||
|
#btn-save:hover { background: var(--accent-hover); }
|
||||||
|
#entry-text {
|
||||||
|
flex: 1; resize: none; border: none; padding: 1rem;
|
||||||
|
font-size: 1.05rem; line-height: 1.7;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
background: transparent; color: var(--text);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#entry-hint { padding: 0.5rem 1rem; font-size: 0.75rem; color: var(--text-muted); border-top: 1px solid var(--border); }
|
||||||
|
|
||||||
|
/* Editor panel - live translation section */
|
||||||
|
#editor-translate { border-top: 1px solid var(--border); padding: 0.5rem 1rem; }
|
||||||
|
.editor-section-title { font-size: 0.75rem; font-weight: 600; color: var(--text-muted); margin-bottom: 0.4rem; }
|
||||||
|
.editor-translate-line { display: flex; gap: 0.4rem; margin-bottom: 0.25rem; font-size: 0.85rem; line-height: 1.5; }
|
||||||
|
.et-label { font-weight: 600; color: var(--text-muted); white-space: nowrap; min-width: 2.5rem; }
|
||||||
|
.et-text { color: var(--text); flex: 1; }
|
||||||
|
#selection-translate { margin-top: 0.3rem; padding: 0.3rem 0.5rem; background: var(--highlight); border-radius: 4px; }
|
||||||
|
|
||||||
|
/* Editor panel - vocabulary section */
|
||||||
|
#editor-vocab { border-top: 1px solid var(--border); padding: 0.5rem 1rem; max-height: 180px; overflow-y: auto; }
|
||||||
|
.vocab-item { display: flex; align-items: center; gap: 0.4rem; padding: 0.25rem 0; font-size: 0.85rem; border-bottom: 1px solid var(--border); }
|
||||||
|
.vocab-item:last-child { border-bottom: none; }
|
||||||
|
.vocab-word { font-weight: 600; font-family: var(--font-mono); color: var(--accent); min-width: 6rem; }
|
||||||
|
.vocab-en { color: var(--text-muted); flex: 1; }
|
||||||
|
.vocab-zh { color: var(--text); min-width: 3rem; }
|
||||||
|
.vocab-speech { background: none; border: 1px solid var(--border); border-radius: 4px; padding: 0.15rem 0.4rem; font-size: 0.7rem; cursor: pointer; color: var(--text-muted); line-height: 1; }
|
||||||
|
.vocab-speech:hover { border-color: var(--accent); color: var(--accent); }
|
||||||
|
|
||||||
|
/* Right panel — translation */
|
||||||
|
#panel-translate { overflow-y: auto; }
|
||||||
|
.translate-section {
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.translate-label { font-size: 0.8rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-muted); }
|
||||||
|
.translate-text {
|
||||||
|
font-size: 0.95rem; line-height: 1.6;
|
||||||
|
min-height: 3rem; padding: 0.5rem;
|
||||||
|
background: var(--bg); border-radius: 4px;
|
||||||
|
}
|
||||||
|
.btn-speech {
|
||||||
|
background: none; border: 1px solid var(--border); border-radius: 4px;
|
||||||
|
padding: 0.25rem 0.6rem; font-size: 0.75rem; cursor: pointer;
|
||||||
|
margin-top: 0.5rem; color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.btn-speech:hover { border-color: var(--accent); color: var(--accent); }
|
||||||
|
|
||||||
|
/* Modal overlay for delete confirmation */
|
||||||
|
#modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed; inset: 0;
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
align-items: center; justify-content: center;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
#modal-overlay.show { display: flex; }
|
||||||
|
#modal-box {
|
||||||
|
background: var(--panel-bg); border-radius: var(--radius);
|
||||||
|
padding: 1.5rem; max-width: 360px; width: 90%;
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
#modal-box p { margin-bottom: 1rem; font-size: 0.9rem; }
|
||||||
|
#modal-box .modal-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }
|
||||||
|
#modal-box button {
|
||||||
|
padding: 0.4rem 1rem; border-radius: 6px; border: 1px solid var(--border);
|
||||||
|
cursor: pointer; font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
#modal-box .btn-cancel { background: var(--panel-bg); color: var(--text); }
|
||||||
|
#modal-box .btn-confirm { background: var(--danger); color: #fff; border-color: var(--danger); }
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body { padding: 0.5rem; }
|
||||||
|
#app { grid-template-columns: 1fr; }
|
||||||
|
#panel-list { max-height: 30vh; }
|
||||||
|
#panel-editor { min-height: 40vh; }
|
||||||
|
#panel-translate { max-height: 30vh; }
|
||||||
|
}
|
||||||
+199
@@ -0,0 +1,199 @@
|
|||||||
|
const MOCK_TRANSLATIONS = {
|
||||||
|
'hola': { en: 'Hello', zh: '你好' },
|
||||||
|
'buenos días': { en: 'Good morning', zh: '早上好' },
|
||||||
|
'buenas tardes': { en: 'Good afternoon', zh: '下午好' },
|
||||||
|
'buenas noches': { en: 'Good night', zh: '晚安' },
|
||||||
|
'hoy': { en: 'today', zh: '今天' },
|
||||||
|
'ayer': { en: 'yesterday', zh: '昨天' },
|
||||||
|
'mañana': { en: 'tomorrow', zh: '明天' },
|
||||||
|
'fui': { en: 'I went', zh: '我去了' },
|
||||||
|
'fui a': { en: 'I went to', zh: '我去了' },
|
||||||
|
'voy': { en: 'I go', zh: '我去' },
|
||||||
|
'voy a': { en: 'I am going to', zh: '我要去' },
|
||||||
|
'estoy': { en: 'I am', zh: '我是' },
|
||||||
|
'estoy muy': { en: 'I am very', zh: '我非常' },
|
||||||
|
'tengo': { en: 'I have', zh: '我有' },
|
||||||
|
'quiero': { en: 'I want', zh: '我想要' },
|
||||||
|
'me gusta': { en: 'I like', zh: '我喜欢' },
|
||||||
|
'me gustó': { en: 'I liked', zh: '我喜欢了' },
|
||||||
|
'aprendí': { en: 'I learned', zh: '我学了' },
|
||||||
|
'estudié': { en: 'I studied', zh: '我学习了' },
|
||||||
|
'comí': { en: 'I ate', zh: '我吃了' },
|
||||||
|
'bebí': { en: 'I drank', zh: '我喝了' },
|
||||||
|
'leí': { en: 'I read', zh: '我读了' },
|
||||||
|
'escribí': { en: 'I wrote', zh: '我写了' },
|
||||||
|
'trabajé': { en: 'I worked', zh: '我工作了' },
|
||||||
|
'descansé': { en: 'I rested', zh: '我休息了' },
|
||||||
|
'caminé': { en: 'I walked', zh: '我散步了' },
|
||||||
|
'corrí': { en: 'I ran', zh: '我跑步了' },
|
||||||
|
'compré': { en: 'I bought', zh: '我买了' },
|
||||||
|
'vi': { en: 'I saw', zh: '我看了' },
|
||||||
|
'el': { en: 'the', zh: '' },
|
||||||
|
'la': { en: 'the', zh: '' },
|
||||||
|
'los': { en: 'the', zh: '' },
|
||||||
|
'las': { en: 'the', zh: '' },
|
||||||
|
'un': { en: 'a', zh: '' },
|
||||||
|
'una': { en: 'a', zh: '' },
|
||||||
|
'unos': { en: 'some', zh: '一些' },
|
||||||
|
'unas': { en: 'some', zh: '一些' },
|
||||||
|
'al': { en: 'to the', zh: '' },
|
||||||
|
'del': { en: 'of the', zh: '' },
|
||||||
|
'a': { en: 'to', zh: '' },
|
||||||
|
'de': { en: 'of', zh: '' },
|
||||||
|
'en': { en: 'in', zh: '在' },
|
||||||
|
'para': { en: 'for', zh: '为了' },
|
||||||
|
'por': { en: 'for / by', zh: '' },
|
||||||
|
'con': { en: 'with', zh: '和' },
|
||||||
|
'sin': { en: 'without', zh: '没有' },
|
||||||
|
'sobre': { en: 'about', zh: '关于' },
|
||||||
|
'mi': { en: 'my', zh: '我的' },
|
||||||
|
'tu': { en: 'your', zh: '你的' },
|
||||||
|
'su': { en: 'his / her / their', zh: '' },
|
||||||
|
'nuestro': { en: 'our', zh: '我们的' },
|
||||||
|
'amigo': { en: 'friend', zh: '朋友' },
|
||||||
|
'amiga': { en: 'friend', zh: '朋友' },
|
||||||
|
'familia': { en: 'family', zh: '家人' },
|
||||||
|
'casa': { en: 'home', zh: '家' },
|
||||||
|
'trabajo': { en: 'work', zh: '工作' },
|
||||||
|
'escuela': { en: 'school', zh: '学校' },
|
||||||
|
'parque': { en: 'park', zh: '公园' },
|
||||||
|
'restaurante': { en: 'restaurant', zh: '餐厅' },
|
||||||
|
'café': { en: 'cafe', zh: '咖啡馆' },
|
||||||
|
'libro': { en: 'book', zh: '书' },
|
||||||
|
'película': { en: 'movie', zh: '电影' },
|
||||||
|
'música': { en: 'music', zh: '音乐' },
|
||||||
|
'día': { en: 'day', zh: '天' },
|
||||||
|
'noche': { en: 'night', zh: '夜晚' },
|
||||||
|
'comida': { en: 'food', zh: '食物' },
|
||||||
|
'agua': { en: 'water', zh: '水' },
|
||||||
|
'tiempo': { en: 'time / weather', zh: '时间' },
|
||||||
|
'feliz': { en: 'happy', zh: '开心的' },
|
||||||
|
'cansado': { en: 'tired', zh: '累的' },
|
||||||
|
'enfermo': { en: 'sick', zh: '生病的' },
|
||||||
|
'bien': { en: 'well', zh: '好' },
|
||||||
|
'mal': { en: 'bad', zh: '不好' },
|
||||||
|
'muy': { en: 'very', zh: '非常' },
|
||||||
|
'un poco': { en: 'a little', zh: '一点' },
|
||||||
|
'mucho': { en: 'a lot', zh: '很多' },
|
||||||
|
'y': { en: 'and', zh: '和' },
|
||||||
|
'pero': { en: 'but', zh: '但是' },
|
||||||
|
'porque': { en: 'because', zh: '因为' },
|
||||||
|
'también': { en: 'also', zh: '也' },
|
||||||
|
'no': { en: 'not', zh: '不' },
|
||||||
|
'sí': { en: 'yes', zh: '是的' },
|
||||||
|
'gracias': { en: 'thank you', zh: '谢谢' },
|
||||||
|
'por favor': { en: 'please', zh: '请' },
|
||||||
|
'lo siento': { en: 'sorry', zh: '对不起' },
|
||||||
|
'nuevo': { en: 'new', zh: '新的' },
|
||||||
|
'viejo': { en: 'old', zh: '旧的' },
|
||||||
|
'grande': { en: 'big', zh: '大' },
|
||||||
|
'pequeño': { en: 'small', zh: '小' },
|
||||||
|
'bueno': { en: 'good', zh: '好的' },
|
||||||
|
'malo': { en: 'bad', zh: '坏的' },
|
||||||
|
'bonito': { en: 'pretty', zh: '漂亮的' },
|
||||||
|
'rápido': { en: 'fast', zh: '快的' },
|
||||||
|
'lento': { en: 'slow', zh: '慢的' },
|
||||||
|
'aquí': { en: 'here', zh: '这里' },
|
||||||
|
'allí': { en: 'there', zh: '那里' },
|
||||||
|
'ahora': { en: 'now', zh: '现在' },
|
||||||
|
'siempre': { en: 'always', zh: '总是' },
|
||||||
|
'nunca': { en: 'never', zh: '从不' },
|
||||||
|
'ya': { en: 'already', zh: '已经' },
|
||||||
|
'todavía': { en: 'still', zh: '仍然' },
|
||||||
|
'después': { en: 'after', zh: '之后' },
|
||||||
|
'antes': { en: 'before', zh: '之前' },
|
||||||
|
'más': { en: 'more', zh: '更多' },
|
||||||
|
'menos': { en: 'less', zh: '更少' },
|
||||||
|
'tan': { en: 'so', zh: '如此' },
|
||||||
|
'como': { en: 'as / like', zh: '像' },
|
||||||
|
'que': { en: 'that / than', zh: '比' },
|
||||||
|
'este': { en: 'this', zh: '这个' },
|
||||||
|
'ese': { en: 'that', zh: '那个' },
|
||||||
|
'qué': { en: 'what', zh: '什么' },
|
||||||
|
'quién': { en: 'who', zh: '谁' },
|
||||||
|
'cuándo': { en: 'when', zh: '什么时候' },
|
||||||
|
'dónde': { en: 'where', zh: '哪里' },
|
||||||
|
'por qué': { en: 'why', zh: '为什么' },
|
||||||
|
'cómo': { en: 'how', zh: '如何' },
|
||||||
|
'mucho gusto': { en: 'nice to meet you', zh: '很高兴认识你' },
|
||||||
|
'buena suerte': { en: 'good luck', zh: '好运' },
|
||||||
|
'tener que': { en: 'have to', zh: '必须' },
|
||||||
|
'tener hambre': { en: 'be hungry', zh: '饿了' },
|
||||||
|
'tener sed': { en: 'be thirsty', zh: '渴了' },
|
||||||
|
'tener sueño': { en: 'be sleepy', zh: '困了' },
|
||||||
|
'tener razón': { en: 'be right', zh: '是对的' },
|
||||||
|
'hacer calor': { en: 'be hot (weather)', zh: '热' },
|
||||||
|
'hacer frío': { en: 'be cold (weather)', zh: '冷' },
|
||||||
|
'estar bien': { en: 'be fine', zh: '没问题' }
|
||||||
|
};
|
||||||
|
|
||||||
|
const SORTED_KEYS = Object.keys(MOCK_TRANSLATIONS).sort(function(a, b) {
|
||||||
|
return b.length - a.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
function translateText(text) {
|
||||||
|
if (!text || !text.trim()) return { english: '', chinese: '' };
|
||||||
|
var lower = text.toLowerCase().trim();
|
||||||
|
var enParts = [];
|
||||||
|
var zhParts = [];
|
||||||
|
var i = 0;
|
||||||
|
while (i < lower.length) {
|
||||||
|
var matched = false;
|
||||||
|
for (var k = 0; k < SORTED_KEYS.length; k++) {
|
||||||
|
var key = SORTED_KEYS[k];
|
||||||
|
if (lower.indexOf(key, i) === i) {
|
||||||
|
var entry = MOCK_TRANSLATIONS[key];
|
||||||
|
if (entry.en) enParts.push(entry.en);
|
||||||
|
if (entry.zh) zhParts.push(entry.zh);
|
||||||
|
i += key.length;
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matched) {
|
||||||
|
var ch = lower[i];
|
||||||
|
if (/[a-zà-ÿñ]/.test(ch)) { enParts.push(ch); }
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var enStr = enParts.join(' ').replace(/\s+/g, ' ').trim();
|
||||||
|
var zhStr = zhParts.join('').trim();
|
||||||
|
enStr = enStr.charAt(0).toUpperCase() + enStr.slice(1);
|
||||||
|
return { english: enStr || text, chinese: zhStr || text };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVocabulary(text) {
|
||||||
|
if (!text || !text.trim()) return [];
|
||||||
|
var lower = text.toLowerCase().trim();
|
||||||
|
var seen = {};
|
||||||
|
var result = [];
|
||||||
|
var i = 0;
|
||||||
|
while (i < lower.length) {
|
||||||
|
for (var k = 0; k < SORTED_KEYS.length; k++) {
|
||||||
|
var key = SORTED_KEYS[k];
|
||||||
|
if (lower.indexOf(key, i) === i) {
|
||||||
|
var entry = MOCK_TRANSLATIONS[key];
|
||||||
|
if (!seen[key] && entry.zh && key.indexOf(' ') === -1) {
|
||||||
|
seen[key] = true;
|
||||||
|
result.push({ word: key, english: entry.en, chinese: entry.zh });
|
||||||
|
}
|
||||||
|
i += key.length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
result.sort(function(a, b) { return b.word.length - a.word.length; });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function translateSelection(text) {
|
||||||
|
if (!text || !text.trim()) return { english: '', chinese: '' };
|
||||||
|
return translateText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVocabForWord(word) {
|
||||||
|
var w = word.toLowerCase().trim();
|
||||||
|
var entry = MOCK_TRANSLATIONS[w];
|
||||||
|
return entry || null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user