ux: 重構學習模式設計與導航體驗
- 改進選擇題模式:顯示定義讓用戶選擇對應英文詞彙 - 優化選項生成邏輯:動態從卡片組生成選項並隨機排序 - 新增翻卡背面例句播放功能,提升學習效果 - 統一所有學習模式導航按鈕位置和樣式 - 實現全版導航按鈕設計,改善觸控體驗 - 修正結果顯示邏輯和音頻回饋功能 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c1e296c860
commit
5bd823ee91
|
|
@ -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 */}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue