ux: 重構學習模式設計與導航體驗

- 改進選擇題模式:顯示定義讓用戶選擇對應英文詞彙
- 優化選項生成邏輯:動態從卡片組生成選項並隨機排序
- 新增翻卡背面例句播放功能,提升學習效果
- 統一所有學習模式導航按鈕位置和樣式
- 實現全版導航按鈕設計,改善觸控體驗
- 修正結果顯示邏輯和音頻回饋功能

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-09-19 23:23:02 +08:00
parent c1e296c860
commit 5bd823ee91
1 changed files with 99 additions and 103 deletions

View File

@ -112,14 +112,14 @@ export default function LearnPage() {
setMounted(true)
const currentWord = cards[currentCardIndex].word;
// Fixed options based on current card index
const optionSets = [
[currentWord, 'determine', 'achieve', 'consider'], // for index 0
[currentWord, 'brought', 'achieve', 'negotiate'], // for index 1
[currentWord, 'brought', 'instincts', 'determine'] // for index 2
];
// Generate quiz options with current word and other words from the deck
const otherWords = cards
.filter((_, idx) => idx !== currentCardIndex)
.map(card => card.word)
.slice(0, 3); // Take 3 other words
const options = optionSets[currentCardIndex] || [currentWord, 'determine', 'achieve', 'consider'];
// Add the current word and shuffle
const options = [currentWord, ...otherWords].sort(() => Math.random() - 0.5);
setQuizOptions(options);
// Reset quiz state when card changes
@ -163,7 +163,7 @@ export default function LearnPage() {
setSelectedAnswer(answer)
setShowResult(true)
const isCorrect = answer === currentCard.translation
const isCorrect = answer === currentCard.word
setScore(prev => ({
correct: isCorrect ? prev.correct + 1 : prev.correct,
total: prev.total + 1
@ -386,7 +386,12 @@ export default function LearnPage() {
{/* Example */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
<p className="text-gray-700 italic mb-2 text-left">"{currentCard.example}"</p>
<div className="relative">
<p className="text-gray-700 italic mb-2 text-left pr-12">"{currentCard.example}"</p>
<div className="absolute bottom-0 right-0">
<AudioPlayer text={currentCard.example} />
</div>
</div>
<p className="text-gray-600 text-sm text-left">"{currentCard.exampleTranslation}"</p>
</div>
@ -411,17 +416,17 @@ export default function LearnPage() {
</div>
{/* Navigation */}
<div className="flex justify-between mt-6">
<div className="flex gap-4 mt-6">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="px-4 py-2 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors"
className="flex-1 py-3 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors font-medium"
>
</button>
<button
onClick={handleNext}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
className="flex-1 py-3 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors font-medium"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
@ -444,28 +449,13 @@ export default function LearnPage() {
</div>
<div className="bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8">
<div className="mb-4">
<span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{currentCard.difficulty}
</span>
</div>
<h2 className="text-3xl font-bold text-gray-900 mb-4">
{currentCard.word}
</h2>
<p className="text-gray-600 mb-4">
{currentCard.partOfSpeech} {currentCard.pronunciation}
</p>
<div className="mb-6">
<AudioPlayer text={currentCard.word} />
</div>
<div className="mb-8">
<div className="bg-gray-50 rounded-lg p-4 mb-6">
<p className="text-gray-700 text-lg">
{currentCard.definition}
</p>
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
<p className="text-gray-700 text-left">{currentCard.definition}</p>
</div>
<p className="text-lg text-gray-700 mb-2">
<p className="text-lg text-gray-700 mb-2 text-center">
</p>
</div>
@ -475,9 +465,9 @@ export default function LearnPage() {
key={idx}
onClick={() => !showResult && handleQuizAnswer(option)}
disabled={showResult}
className={`w-full p-4 text-left rounded-lg border-2 transition-all ${
className={`w-full p-4 text-center rounded-lg border-2 transition-all ${
showResult
? option === currentCard.translation
? option === currentCard.word
? 'border-green-500 bg-green-50 text-green-700'
: option === selectedAnswer
? 'border-red-500 bg-red-50 text-red-700'
@ -492,41 +482,47 @@ export default function LearnPage() {
{showResult && (
<div className={`mt-6 p-4 rounded-lg ${
selectedAnswer === currentCard.translation
selectedAnswer === currentCard.word
? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200'
}`}>
<p className={`font-semibold ${
selectedAnswer === currentCard.translation
selectedAnswer === currentCard.word
? 'text-green-700'
: 'text-red-700'
}`}>
{selectedAnswer === currentCard.translation ? '正確!' : '錯誤!'}
{selectedAnswer === currentCard.word ? '正確!' : '錯誤!'}
</p>
{selectedAnswer !== currentCard.translation && (
{selectedAnswer !== currentCard.word && (
<p className="text-gray-700 mt-2">
{currentCard.translation}
<strong>{currentCard.word}</strong>
</p>
)}
<div className="mt-3 text-center">
<p className="text-gray-600 text-sm mb-2">
{currentCard.translation}
</p>
<AudioPlayer text={currentCard.word} />
</div>
</div>
)}
</div>
{/* Navigation */}
<div className="flex justify-between mt-8">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="px-4 py-2 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors"
>
</button>
<button
onClick={handleNext}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
{/* Navigation */}
<div className="flex gap-4 mt-6">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="flex-1 py-3 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors font-medium"
>
</button>
<button
onClick={handleNext}
className="flex-1 py-3 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors font-medium"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
</div>
) : mode === 'fill' ? (
@ -634,23 +630,23 @@ export default function LearnPage() {
</div>
</div>
)}
</div>
{/* Navigation */}
<div className="flex justify-between mt-8">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="px-4 py-2 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors"
>
</button>
<button
onClick={handleNext}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
{/* Navigation */}
<div className="flex gap-4 mt-6">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="flex-1 py-3 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors font-medium"
>
</button>
<button
onClick={handleNext}
className="flex-1 py-3 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors font-medium"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
</div>
) : mode === 'listening' ? (
@ -744,23 +740,23 @@ export default function LearnPage() {
)}
</div>
)}
</div>
{/* Navigation */}
<div className="flex justify-between mt-8">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="px-4 py-2 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors"
>
</button>
<button
onClick={handleNext}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
{/* Navigation */}
<div className="flex gap-4 mt-6">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="flex-1 py-3 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors font-medium"
>
</button>
<button
onClick={handleNext}
className="flex-1 py-3 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors font-medium"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
</div>
) : mode === 'speaking' ? (
@ -839,23 +835,23 @@ export default function LearnPage() {
</div>
</div>
)}
</div>
{/* Navigation */}
<div className="flex justify-between mt-8">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="px-4 py-2 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors"
>
</button>
<button
onClick={handleNext}
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
{/* Navigation */}
<div className="flex gap-4 mt-6">
<button
onClick={handlePrevious}
disabled={currentCardIndex === 0}
className="flex-1 py-3 bg-gray-500 text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-600 transition-colors font-medium"
>
</button>
<button
onClick={handleNext}
className="flex-1 py-3 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors font-medium"
>
{currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button>
</div>
</div>
) : null}