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) setMounted(true)
const currentWord = cards[currentCardIndex].word; const currentWord = cards[currentCardIndex].word;
// Fixed options based on current card index // Generate quiz options with current word and other words from the deck
const optionSets = [ const otherWords = cards
[currentWord, 'determine', 'achieve', 'consider'], // for index 0 .filter((_, idx) => idx !== currentCardIndex)
[currentWord, 'brought', 'achieve', 'negotiate'], // for index 1 .map(card => card.word)
[currentWord, 'brought', 'instincts', 'determine'] // for index 2 .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); setQuizOptions(options);
// Reset quiz state when card changes // Reset quiz state when card changes
@ -163,7 +163,7 @@ export default function LearnPage() {
setSelectedAnswer(answer) setSelectedAnswer(answer)
setShowResult(true) setShowResult(true)
const isCorrect = answer === currentCard.translation const isCorrect = answer === currentCard.word
setScore(prev => ({ setScore(prev => ({
correct: isCorrect ? prev.correct + 1 : prev.correct, correct: isCorrect ? prev.correct + 1 : prev.correct,
total: prev.total + 1 total: prev.total + 1
@ -386,7 +386,12 @@ export default function LearnPage() {
{/* Example */} {/* Example */}
<div className="bg-gray-50 rounded-lg p-4"> <div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3> <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> <p className="text-gray-600 text-sm text-left">"{currentCard.exampleTranslation}"</p>
</div> </div>
@ -411,17 +416,17 @@ export default function LearnPage() {
</div> </div>
{/* Navigation */} {/* Navigation */}
<div className="flex justify-between mt-6"> <div className="flex gap-4 mt-6">
<button <button
onClick={handlePrevious} onClick={handlePrevious}
disabled={currentCardIndex === 0} 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>
<button <button
onClick={handleNext} 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 ? '完成' : '下一張'} {currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button> </button>
@ -444,28 +449,13 @@ export default function LearnPage() {
</div> </div>
<div className="bg-white rounded-xl shadow-lg p-8"> <div className="bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8"> <div className="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="bg-gray-50 rounded-lg p-4 mb-6"> <div className="bg-gray-50 rounded-lg p-4 mb-6">
<p className="text-gray-700 text-lg"> <h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
{currentCard.definition} <p className="text-gray-700 text-left">{currentCard.definition}</p>
</p>
</div> </div>
<p className="text-lg text-gray-700 mb-2"> <p className="text-lg text-gray-700 mb-2 text-center">
</p> </p>
</div> </div>
@ -475,9 +465,9 @@ export default function LearnPage() {
key={idx} key={idx}
onClick={() => !showResult && handleQuizAnswer(option)} onClick={() => !showResult && handleQuizAnswer(option)}
disabled={showResult} 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 showResult
? option === currentCard.translation ? option === currentCard.word
? 'border-green-500 bg-green-50 text-green-700' ? 'border-green-500 bg-green-50 text-green-700'
: option === selectedAnswer : option === selectedAnswer
? 'border-red-500 bg-red-50 text-red-700' ? 'border-red-500 bg-red-50 text-red-700'
@ -492,43 +482,49 @@ export default function LearnPage() {
{showResult && ( {showResult && (
<div className={`mt-6 p-4 rounded-lg ${ <div className={`mt-6 p-4 rounded-lg ${
selectedAnswer === currentCard.translation selectedAnswer === currentCard.word
? 'bg-green-50 border border-green-200' ? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200' : 'bg-red-50 border border-red-200'
}`}> }`}>
<p className={`font-semibold ${ <p className={`font-semibold ${
selectedAnswer === currentCard.translation selectedAnswer === currentCard.word
? 'text-green-700' ? 'text-green-700'
: 'text-red-700' : 'text-red-700'
}`}> }`}>
{selectedAnswer === currentCard.translation ? '正確!' : '錯誤!'} {selectedAnswer === currentCard.word ? '正確!' : '錯誤!'}
</p> </p>
{selectedAnswer !== currentCard.translation && ( {selectedAnswer !== currentCard.word && (
<p className="text-gray-700 mt-2"> <p className="text-gray-700 mt-2">
{currentCard.translation} <strong>{currentCard.word}</strong>
</p> </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>
)} )}
</div>
{/* Navigation */} {/* Navigation */}
<div className="flex justify-between mt-8"> <div className="flex gap-4 mt-6">
<button <button
onClick={handlePrevious} onClick={handlePrevious}
disabled={currentCardIndex === 0} 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>
<button <button
onClick={handleNext} 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 ? '完成' : '下一張'} {currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button> </button>
</div> </div>
</div> </div>
</div>
) : mode === 'fill' ? ( ) : mode === 'fill' ? (
/* Fill in the Blank Mode - 填空題 */ /* Fill in the Blank Mode - 填空題 */
<div className="relative"> <div className="relative">
@ -634,25 +630,25 @@ export default function LearnPage() {
</div> </div>
</div> </div>
)} )}
</div>
{/* Navigation */} {/* Navigation */}
<div className="flex justify-between mt-8"> <div className="flex gap-4 mt-6">
<button <button
onClick={handlePrevious} onClick={handlePrevious}
disabled={currentCardIndex === 0} 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>
<button <button
onClick={handleNext} 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 ? '完成' : '下一張'} {currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button> </button>
</div> </div>
</div> </div>
</div>
) : mode === 'listening' ? ( ) : mode === 'listening' ? (
/* Listening Test Mode - 聽力測試 */ /* Listening Test Mode - 聽力測試 */
<div className="relative"> <div className="relative">
@ -744,25 +740,25 @@ export default function LearnPage() {
)} )}
</div> </div>
)} )}
</div>
{/* Navigation */} {/* Navigation */}
<div className="flex justify-between mt-8"> <div className="flex gap-4 mt-6">
<button <button
onClick={handlePrevious} onClick={handlePrevious}
disabled={currentCardIndex === 0} 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>
<button <button
onClick={handleNext} 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 ? '完成' : '下一張'} {currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button> </button>
</div> </div>
</div> </div>
</div>
) : mode === 'speaking' ? ( ) : mode === 'speaking' ? (
/* Speaking Test Mode - 口說測試 */ /* Speaking Test Mode - 口說測試 */
<div className="relative"> <div className="relative">
@ -839,25 +835,25 @@ export default function LearnPage() {
</div> </div>
</div> </div>
)} )}
</div>
{/* Navigation */} {/* Navigation */}
<div className="flex justify-between mt-8"> <div className="flex gap-4 mt-6">
<button <button
onClick={handlePrevious} onClick={handlePrevious}
disabled={currentCardIndex === 0} 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>
<button <button
onClick={handleNext} 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 ? '完成' : '下一張'} {currentCardIndex === cards.length - 1 ? '完成' : '下一張'}
</button> </button>
</div> </div>
</div> </div>
</div>
) : null} ) : null}
{/* Report Modal */} {/* Report Modal */}