Creation chat
ClarkZhang
帮我做一个背单词的应用。
对单词分三类:
我懂的/我不懂的/我完全掌握
根据艾宾浩斯遗忘曲线,定时提醒我复习单词。
可以随机输入100个单词,作为单词库
03-12 08:25
暂无回复
No comments yet
登录 后可以评论
Assistant
(HTML code has been generated and updated; see above for the current version.)
▼ Code
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>艾宾浩斯背单词</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config = { darkMode: 'class' }</script>
<style>
.flip-card-inner {
transition: transform 0.6s;
transform-style: preserve-3d;
}
.flip-card.flipped .flip-card-inner {
transform: rotateY(180deg);
}
.flip-card-front, .flip-card-back {
backface-visibility: hidden;
}
.flip-card-back {
transform: rotateY(180deg);
}
</style>
</head>
<body class="min-h-screen bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-slate-100">
<!-- 主题切换按钮 -->
<button id="theme-toggle-btn" class="fixed top-4 right-4 z-10 p-2 rounded-md hover:bg-gray-200 dark:hover:bg-grayly-700 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors flex items-center justify-center" aria-label="Toggle theme" onclick="toggleTheme()">
<svg id="icon-moon" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998z"/></svg>
<svg id="icon-sun" class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true" style="display:none"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"></path></svg>
</button>
<script>
function toggleTheme(){ var el=document.documentElement; var isDark=!el.classList.contains('dark'); el.classList.toggle('dark',isDark); localStorage.setItem('theme',isDark?'dark':'light'); applyTheme(isDark); }
function applyTheme(isDark){ document.getElementById('icon-moon').style.display=isDark?'none':'block'; document.getElementById('icon-sun').style.display=isDark?'block':'none'; }
document.addEventListener('DOMContentLoaded',function(){ var saved=localStorage.getItem('theme'); var isDark=saved==='dark'||(!saved&&window.matchMedia('(prefers-color-scheme: dark)').matches); document.documentElement.classList.toggle('dark',isDark); applyTheme(isDark); });
</script>
<!-- 自定义弹窗 -->
<div id="custom-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 hidden" aria-hidden="true">
<div class="rounded-xl bg-white dark:bg-slate-800 p-6 shadow-xl max-w-sm w-full border border-slate-200 dark:border-slate-700">
<p id="custom-modal-msg" class="text-slate-700 dark:text-slate-200 mb-4"></p>
<button type="button" onclick="hideModal()" class="w-full rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">确定</button>
</div>
</div>
<script>
function showModal(msg){ var el=document.getElementById('custom-modal'); document.getElementById('custom-modal-msg').textContent=msg||''; el.classList.remove('hidden'); }
function hideModal(){ document.getElementById('custom-modal').classList.add('hidden'); }
</script>
<header class="bg-white dark:bg-slate-800 shadow-sm py-4 px-6">
<div class="container mx-auto flex justify-between items-center">
<h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">艾宾浩斯背单词</h1>
<div class="flex items-center space-x-4">
<button id="stats-btn" class="px-3 py-1 bg-slate-100 dark:bg-slate-700 rounded-lg text-sm font-medium hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors">
统计
</button>
<button id="reset-btn" class="px-3 py-1 bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-300 rounded-lg text-sm font-medium hover:bg-amber-200 dark:hover:bg-amber-800 transition-colors">
重置
</button>
</div>
</div>
</header>
<main class="container mx-auto px-4 py-8">
<!-- 单词输入区域 -->
<section id="input-section" class="mb-12">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6 max-w-3xl mx-auto">
<h2 class="text-xl font-semibold mb-4 text-slate-800 dark:text-slate-100">导入单词库</h2>
<p class="text-slate-600 dark:text-slate-300 mb-4">请输入100个单词(每行一个单词)</p>
<form id="word-input-form">
<textarea
id="word-input"
class="w-full h-40 p-3 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-slate-100 mb-4"
placeholder="例如: apple banana computer ..."></textarea>
<div class="flex justify-between items-center">
<div class="text-sm text-slate-500 dark:text-slate-400">
已输入 <span id="word-count">0</span> 个单词
</div>
<button
type="submit"
class="px-6 py-2 bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white rounded-lg font-medium transition-colors"
>
开始学习
</button>
</div>
</form>
</div>
</section>
<!-- 学习区域 -->
<section id="learning-section" class="hidden mb-12">
<div class="max-w-2xl mx-auto">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6 mb-6">
<div class="flex justify-between items-center mb-4">
<div class="text-sm font-medium text-slate-500 dark:text-slate-400">
<span id="current-index">1</span> / <span id="total-words">0</span>
</div>
<div class="text-sm font-medium text-slate-500 dark:text-slate-400">
待复习: <span id="review-count" class="text-amber-600 dark:text-amber-400">0</span>
</div>
</div>
<!-- 单词卡片 -->
<div id="word-card" class="flip-card cursor-pointer mb-6 min-h-[200px] flex items-center justify-center">
<div class="flip-card-inner w-full h-full">
<div class="flip-card-front absolute inset-0 flex items-center justify-center">
<div class="text-center p-4">
<div class="text-3xl font-bold mb-2 text-slate-800 dark:text-slate-100" id="current-word">单词</div>
<div class="text-slate-500 dark:text-slate-400 text-sm">点击翻转查看释义</div>
</div>
</div>
<div class="flip-card-back absolute inset-0 flex items-center justify-center">
<div class="text-center p-4">
<div class="text-xl mb-4 text-slate-700 dark:text-slate-200" id="current-meaning">单词释义</div>
<div class="text-slate-500 dark:text-slate-400 text-sm">点击翻转查看单词</div>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="grid grid-cols-3 gap-3">
<button
id="known-btn"
class="py-3 px-4 bg-green-100 hover:bg-green-200 dark:bg-green-900/30 dark:hover:bg-green-800/50 text-green-700 dark:text-green-300 rounded-lg font-medium transition-colors flex flex-col items-center"
>
<span class="text-lg">✓</span>
<span class="text-sm">认识</span>
</button>
<button
id="unknown-btn"
class="py-3 px-4 bg-red-100 hover:bg-red-200 dark:bg-red-900/30 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded-lg font-medium transition-colors flex flex-col items-center"
>
<span class="text-lg">✗</span>
<span class="text-sm">不认识</span>
</button>
<button
id="mastered-btn"
class="py-3 px-4 bg-blue-100 hover:bg-blue-200 dark:bg-blue-900/30 dark:hover:bg-blue-800/50 text-blue-700 dark:text-blue-300 rounded-lg font-medium transition-colors flex flex-col items-center"
>
<span class="text-lg">★</span>
<span class="text-sm">掌握</span>
</button>
</div>
</div>
<!-- 进度条 -->
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6">
<h3 class="font-medium mb-3 text-slate-800 dark:text-slate-100">学习进度</h3>
<div class="space-y-3">
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-slate-600 dark:text-slate-300">认识</span>
<span class="font-medium text-green-600 dark:text-green-400" id="known-count">0</span>
</div>
<div class="h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
<div id="known-progress" class="h-full bg-green-500 rounded-full" style="width: 0%"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-slate-600 dark:text-slate-300">不认识</span>
<span class="font-medium text-red-600 dark:text-red-400" id="unknown-count">0</span>
</div>
<div class="h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
<div id="unknown-progress" class="h-full bg-red-500 rounded-full" style="width: 0%"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-slate-600 dark:text-slate-300">掌握</span>
<span class="font-medium text-blue-600 dark:text-blue-400" id="mastered-count">0</span>
</div>
<div class="h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
<div id="mastered-progress" class="h-full bg-blue-500 rounded-full" style="width: 0%"></div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 统计区域 -->
<section id="stats-section" class="hidden">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6 max-w-3xl mx-auto">
<h2 class="text-xl font-semibold mb-6 text-slate-800 dark:text-slate-100">学习统计</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-5 text-center">
<div class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-2" id="total-words-stats">0</div>
<div class="text-slate-600 dark:text-slate-300">总单词数</div>
</div>
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-5 text-center">
<div class="text-3xl font-bold text-green-600 dark:text-green-400 mb-2" id="mastered-words-stats">0</div>
<div class="text-slate-600 dark:text-slate-300">已掌握</div>
</div>
<div class="bg-amber-50 dark:bg-amber-900/20 rounded-lg p-5 text-center">
<div class="text-3xl font-bold text-amber-600 dark:text-amber-400 mb-2" id="review-words-stats">0</div>
<div class="text-slate-600 dark:text-slate-300">待复习</div>
</div>
</div>
<div class="mb-6">
<h3 class="font-medium mb-3 text-slate-800 dark:text-slate-100">复习计划</h3>
<div class="space-y-3">
<div class="flex items-center justify-between p-3 bg-slate-50 dark:bg-slate-700/50 rounded-lg">
<div>
<div class="font-medium text-slate-800 dark:text-slate-100">5分钟后</div>
<div class="text-sm text-slate-500 dark:text-slate-400" id="review-5min">0 个单词</div>
</div>
<div class="text-sm bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300 px-2 py-1 rounded">
即将复习
</div>
</div>
<div class="flex items-center justify-between p-3 bg-slate-50 dark:bg-slate-700/50 rounded-lg">
<div>
<div class="font-medium text-slate-800 dark:text-slate-100">30分钟后</div>
<div class="text-sm text-slate-500 dark:text-slate-400" id="review-30min">0 个单词</div>
</div>
<div class="text-sm bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 px-2 py-1 rounded">
即将复习
</div>
</div>
<div class="flex items-center justify-between p-3 bg-slate-50 dark:bg-slate-700/50 rounded-lg">
<div>
<div class="font-medium text-slate-800 dark:text-slate-100">12小时后</div>
<div class="text-sm text-slate-500 dark:text-slate-400" id="review-12h">0 个单词</div>
</div>
<div class="text-sm bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 px-2 py-1 rounded">
即将复习
</div>
</div>
<div class="flex items-center justify-between p-3 bg-slate-50 dark:bg-slate-700/50 rounded-lg">
<div>
<div class="font-medium text-slate-800 dark:text-slate-100">1天后</div>
<div class="text-sm text-slate-500 dark:text-slate-400" id="review-1d">0 个单词</div>
</div>
<div class="text-sm bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300 px-2 py-1 rounded">
即将复习
</div>
</div>
</div>
</div>
<button
id="back-to-learning"
class="w-full py-3 bg-slate-100 hover:bg-slate-200 dark:bg-slate-700 dark:hover:bg-slate-600 rounded-lg font-medium transition-colors"
>
返回学习
</button>
</div>
</section>
</main>
<script>
// 数据结构
let words = [];
let currentIndex = 0;
let reviewSchedule = {};
// DOM 元素
const inputSection = document.getElementById('input-section');
const learningSection = document.getElementById('learning-section');
const statsSection = document.getElementById('stats-section');
const wordInput = document.getElementById('word-input');
const wordCount = document.getElementById('word-count');
const wordCard = document.getElementById('word-card');
const currentWord = document.getElementById('current-word');
const currentMeaning = document.getElementById('current-meaning');
const currentIndexEl = document.getElementById('current-index');
const totalWordsEl = document.getElementById('total-words');
const reviewCountEl = document.getElementById('review-count');
// 进度统计元素
const knownCountEl = document.getElementById('known-count');
const unknownCountEl = document.getElementById('unknown-count');
const masteredCountEl = document.getElementById('mastered-count');
const knownProgressEl = document.getElementById('known-progress');
const unknownProgressEl = document.getElementById('unknown-progress');
const masteredProgressEl = document.getElementById('mastered-progress');
// 统计页面元素
const totalWordsStatsEl = document.getElementById('total-words-stats');
const masteredWordsStatsEl = document.getElementById('mastered-words-stats');
const reviewWordsStatsEl = document.getElementById('review-words-stats');
const review5minEl = document.getElementById('review-5min');
const review30minEl = document.getElementById('review-30min');
const review12hEl = document.getElementById('review-12h');
const review1dEl = document.getElementById('review-1d');
// 按钮
const statsBtn = document.getElementById('stats-btn');
const resetBtn = document.getElementById('reset-btn');
const backToLearningBtn = document.getElementById('back-to-learning');
// 初始化
document.addEventListener('DOMContentLoaded', function() {
loadWords();
updateWordCount();
setupEventListeners();
// 如果已有单词数据,直接进入学习界面
if (words.length > 0) {
showLearningSection();
updateLearningUI();
}
});
// 事件监听器
function setupEventListeners() {
// 单词输入表单
document.getElementById('word-input-form').addEventListener('submit', function(e) {
e.preventDefault();
processWords();
});
// 单词输入区域字符统计
wordInput.addEventListener('input', updateWordCount);
// 单词卡片翻转
wordCard.addEventListener('click', function() {
this.classList.toggle('flipped');
});
// 分类按钮
document.getElementById('known-btn').addEventListener('click', function() {
classifyWord('known');
});
document.getElementById('unknown-btn').addEventListener('click', function() {
classifyWord('unknown');
});
document.getElementById('mastered-btn').addEventListener('click', function() {
classifyWord('mastered');
});
// 统计按钮
statsBtn.addEventListener('click', showStatsSection);
// 重置按钮
resetBtn.addEventListener('click', function() {
if (confirm('确定要重置所有学习数据吗?')) {
localStorage.removeItem('ebbinghaus_words');
words = [];
reviewSchedule = {};
showInputSection();
}
});
// 返回学习按钮
backToLearningBtn.addEventListener('click', showLearningSection);
}
// 更新单词计数
function updateWordCount() {
const text = wordInput.value.trim();
const count = text ? text.split('\n').filter(word => word.trim() !== '').length : 0;
wordCount.textContent = count;
}
// 处理输入的单词
function processWords() {
const text = wordInput.value.trim();
if (!text) {
showModal('请输入单词');
return;
}
const wordArray = text.split('\n')
.map(word => word.trim())
.filter(word => word !== '')
.slice(0, 100); // 限制最多100个单词
if (wordArray.length === 0) {
showModal('请输入有效的单词');
return;
}
// 初始化单词数据
words = wordArray.map((word, index) => ({
id: index,
word: word,
meaning: `【${word}】的释义`, // 实际应用中可以从API获取
status: 'new', // new, known, unknown, mastered
nextReview: null,
reviewCount: 0
}));
saveWords();
showLearningSection();
updateLearningUI();
}
// 显示输入区域
function showInputSection() {
inputSection.classList.remove('hidden');
learningSection.classList.add('hidden');
statsSection.classList.add('hidden');
}
// 显示学习区域
function showLearningSection() {
inputSection.classList.add('hidden');
learningSection.classList.remove('hidden');
statsSection.classList.add('hidden');
// 重置卡片状态
wordCard.classList.remove('flipped');
}
// 显示统计区域
function showStatsSection() {
inputSection.classList.add('hidden');
learningSection.classList.add('hidden');
statsSection.classList.remove('hidden');
updateStatsUI();
}
// 更新学习界面
function updateLearningUI() {
if (words.length === 0) return;
// 确保索引在有效范围内
if (currentIndex >= words.length) {
currentIndex = 0;
}
const word = words[currentIndex];
currentWord.textContent = word.word;
currentMeaning.textContent = word.meaning;
currentIndexEl.textContent = currentIndex + 1;
totalWordsEl.textContent = words.length;
// 更新复习计数
const reviewCount = words.filter(w => w.status === 'unknown' || w.status === 'known').length;
reviewCountEl.textContent = reviewCount;
// 更新进度统计
updateProgressStats();
}
// 更新进度统计
function updateProgressStats() {
const knownCount = words.filter(w => w.status === 'known').length;
const unknownCount = words.filter(w => w.status === 'unknown').length;
const masteredCount = words.filter(w => w.status === 'mastered').length;
const totalCount = words.length;
knownCountEl.textContent = knownCount;
unknownCountEl.textContent = unknownCount;
masteredCountEl.textContent = masteredCount;
knownProgressEl.style.width = totalCount ? `${(knownCount / totalCount) * 100}%` : '0%';
unknownProgressEl.style.width = totalCount ? `${(unknownCount / totalCount) * 100}%` : '0%';
masteredProgressEl.style.width = totalCount ? `${(masteredCount / totalCount) * 100}%` : '0%';
}
// 更新统计页面
function updateStatsUI() {
const totalCount = words.length;
const masteredCount = words.filter(w => w.status === 'mastered').length;
const reviewCount = words.filter(w => w.status === 'unknown' || w.status === 'known').length;
totalWordsStatsEl.textContent = totalCount;
masteredWordsStatsEl.textContent = masteredCount;
reviewWordsStatsEl.textContent = reviewCount;
// 计算各时间段待复习单词数
const now = Date.now();
let count5min = 0;
let count30min = 0;
let count12h = 0;
let count1d = 0;
words.forEach(word => {
if (word.nextReview) {
const nextReview = new Date(word.nextReview).getTime();
const diffMinutes = (nextReview - now) / (1000 * 60);
if (diffMinutes <= 5) count5min++;
if (diffMinutes <= 30) count30min++;
if (diffMinutes <= 720) count12h++; // 12小时
if (diffMinutes <= 1440) count1d++; // 24小时
}
});
review5minEl.textContent = `${count5min} 个单词`;
review30minEl.textContent = `${count30min} 个单词`;
review12hEl.textContent = `${count12h} 个单词`;
review1dEl.textContent = `${count1d} 个单词`;
}
// 分类单词
function classifyWord(status) {
if (words.length === 0) return;
const word = words[currentIndex];
word.status = status;
word.reviewCount++;
// 根据艾宾浩斯遗忘曲线设置下次复习时间
const intervals = [5, 30, 720, 1440]; // 5分钟, 30分钟, 12小时, 24小时 (分钟)
const reviewIndex = Math.min(word.reviewCount, intervals.length - 1);
const nextReviewTime = new Date(Date.now() + intervals[reviewIndex] * 60000);
word.nextReview = nextReviewTime.toISOString();
saveWords();
moveToNextWord();
}
// 移动到下一个单词
function moveToNextWord() {
currentIndex++;
// 如果到达末尾,回到开头
if (currentIndex >= words.length) {
currentIndex = 0;
}
// 重置卡片状态
wordCard.classList.remove('flipped');
updateLearningUI();
}
// 保存单词数据到本地存储
function saveWords() {
try {
localStorage.setItem('ebbinghaus_words', JSON.stringify(words));
} catch (e) {
console.error('保存数据失败:', e);
}
}
// 从本地存储加载单词数据
function loadWords() {
try {
const savedWords = localStorage.getItem('ebbinghaus_words');
if (savedWords) {
words = JSON.parse(savedWords);
}
} catch (e) {
console.error('加载数据失败:', e);
words = [];
}
}
// 检查是否需要复习
function checkReviewSchedule() {
const now = Date.now();
let needsReview = false;
words.forEach(word => {
if (word.nextReview && new Date(word.nextReview).getTime() <= now) {
needsReview = true;
}
});
if (needsReview) {
// 在实际应用中,这里可以触发通知
console.log('有单词需要复习了!');
}
}
// 每分钟检查一次复习计划
setInterval(checkReviewSchedule, 60000);
</script>
</body>
</html>
03-12 08:28
暂无回复
No comments yet
登录 后可以评论
Comments
暂无回复
No comments yet
登录 后可以评论