dramaling-vocab-learning/note/智能複習/詞彙學習-UI設計規範.md

1096 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 詞彙學習 - UI設計規範
**文件版本**: 1.0
**建立日期**: 2025-09-27
**目標讀者**: 前端實作工程師、UI設計師
**用途**: HTML/CSS實作、視覺設計、組件規範
**配合文檔**: [智能複習系統-開發指南.md](/note/智能複習/智能複習系統-開發指南.md) - 系統架構和業務邏輯
**來源**: 從備份檔案 `page-v1-original.tsx` 提取的完整UI設計規格
---
## 📋 總體設計架構
### 核心測驗類型
該系統包含6種主要測驗類型
1. **翻卡記憶** (flip-memory)
2. **詞彙選擇** (vocab-choice)
3. **例句填空** (sentence-fill)
4. **例句重組** (sentence-reorder)
5. **聽力測驗** (listening - vocab-listening, sentence-listening)
6. **口說測驗** (speaking - sentence-speaking)
### 共同設計元素
#### 通用容器結構
```jsx
<div className="relative">
{/* 錯誤回報按鈕 */}
<div className="flex justify-end mb-2">
<button className="px-3 py-2 rounded-md transition-colors text-gray-600 hover:text-gray-900">
🚩 回報錯誤
</button>
</div>
{/* 主要內容區 */}
<div className="bg-white rounded-xl shadow-lg p-8">
{/* 測驗內容 */}
</div>
{/* 導航按鈕 */}
<div className="flex gap-4 mt-6">
<button 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 className="flex-1 py-3 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors font-medium">
下一張/完成
</button>
</div>
</div>
```
#### 通用標題區
```jsx
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900">
{測驗名稱}
</h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{currentCard.difficultyLevel}
</span>
</div>
```
#### 配色方案
- **主色調**: `bg-primary`, `text-white`, `hover:bg-primary-dark`
- **次要色調**: `bg-gray-500`, `hover:bg-gray-600`
- **成功狀態**: `bg-green-50`, `border-green-200`, `text-green-700`
- **錯誤狀態**: `bg-red-50`, `border-red-200`, `text-red-700`
- **提示狀態**: `bg-yellow-50`, `border-yellow-200`, `text-yellow-800`
- **信息狀態**: `bg-blue-50`, `border-blue-200`, `text-blue-700`
---
## 🔄 1. 翻卡記憶 (flip-memory)
### 核心特色
- 3D翻卡動畫效果
- 正面顯示單字,背面顯示詳細資訊
- 信心等級評估系統
### HTML結構
```jsx
<div
className="card-container"
onClick={handleFlip}
style={{ height: `${cardHeight}px` }}
>
<div className={`card ${isFlipped ? 'flipped' : ''}`}>
{/* 正面 */}
<div className="card-front">
<div className="bg-white rounded-xl shadow-lg cursor-pointer hover:shadow-xl transition-shadow p-8">
{/* 標題區 */}
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900">翻卡記憶</h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{difficultyLevel}
</span>
</div>
{/* 指示文字 */}
<p className="text-lg text-gray-700 mb-6 text-left">
點擊卡片翻面根據你對單字的熟悉程度進行自我評估
</p>
{/* 單字顯示區 */}
<div className="flex-1 flex items-center justify-center mt-6">
<div className="bg-gray-50 rounded-lg p-8 w-full text-center">
<h3 className="text-4xl font-bold text-gray-900 mb-6">{word}</h3>
<div className="flex items-center justify-center gap-3">
<span className="text-lg text-gray-500">{pronunciation}</span>
<AudioPlayer text={word} />
</div>
</div>
</div>
</div>
</div>
{/* 背面 */}
<div className="card-back">
<div className="bg-white rounded-xl shadow-lg cursor-pointer hover:shadow-xl transition-shadow">
<div className="space-y-4">
{/* 定義區塊 */}
<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 text-left">{definition}</p>
</div>
{/* 例句區塊 */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left">例句</h3>
<div className="relative">
<p className="text-gray-700 italic mb-2 text-left pr-12">{example}</p>
<div className="absolute bottom-0 right-0">
<AudioPlayer text={example} />
</div>
</div>
<p className="text-gray-600 text-sm text-left">{exampleTranslation}</p>
</div>
{/* 同義詞區塊 */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left">同義詞</h3>
<div className="flex flex-wrap gap-2">
{synonyms.map(synonym => (
<span className="bg-white text-gray-700 px-3 py-1 rounded-full text-sm border border-gray-200">
{synonym}
</span>
))}
</div>
</div>
{/* 信心等級評估區 */}
<div className="bg-blue-50 rounded-lg p-4">
<h3 className="font-semibold text-blue-900 mb-3 text-left">你對這個單字的熟悉程度</h3>
<div className="grid grid-cols-5 gap-2">
{[1, 2, 3, 4, 5].map(level => (
<button
key={level}
onClick={() => handleConfidenceLevel(level)}
className="py-3 px-4 bg-white border border-blue-200 text-blue-700 rounded-lg hover:bg-blue-100 hover:border-blue-300 transition-all text-center"
>
<div className="font-bold text-lg">{level}</div>
<div className="text-xs">
{level === 1 ? '完全不懂' :
level === 2 ? '模糊' :
level === 3 ? '一般' :
level === 4 ? '熟悉' : '非常熟悉'}
</div>
</button>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
```
### CSS動畫樣式
```css
.card-container {
perspective: 1000px;
transition: height 0.3s ease;
overflow: visible;
position: relative;
}
.card {
position: relative;
width: 100%;
height: 100%;
text-align: center;
transition: transform 0.6s ease;
transform-style: preserve-3d;
}
.card.flipped {
transform: rotateY(180deg);
}
.card-front, .card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
display: flex;
align-items: stretch;
justify-content: center;
top: 0;
left: 0;
}
.card-back {
transform: rotateY(180deg);
}
```
---
## 🎯 2. 詞彙選擇 (vocab-choice)
### 核心特色
- 四選一選擇題
- 顯示詞彙定義,選擇對應單字
- 即時反饋系統
### HTML結構
```jsx
<div className="bg-white rounded-xl shadow-lg p-8">
{/* 標題區 */}
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900">詞彙選擇</h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{difficultyLevel}
</span>
</div>
{/* 指示文字 */}
<p className="text-lg text-gray-700 mb-6 text-left">
請選擇符合上述定義的英文詞彙
</p>
{/* 定義顯示區 */}
<div className="text-center mb-8">
<div className="bg-gray-50 rounded-lg p-4 mb-6">
<h3 className="font-semibold text-gray-900 mb-2 text-left">定義</h3>
<p className="text-gray-700 text-left">{definition}</p>
</div>
</div>
{/* 選項區域 */}
<div className="space-y-3 mb-6">
{quizOptions.map((option, idx) => (
<button
key={idx}
onClick={() => !showResult && handleQuizAnswer(option)}
disabled={showResult}
className={`w-full p-4 text-left rounded-lg border-2 transition-all ${
showResult
? option === correctAnswer
? 'border-green-500 bg-green-50 text-green-700'
: option === selectedAnswer
? 'border-red-500 bg-red-50 text-red-700'
: 'border-gray-200 bg-gray-50 text-gray-500'
: 'border-gray-200 hover:border-blue-300 hover:bg-blue-50'
}`}
>
{option}
</button>
))}
</div>
{/* 結果反饋區 */}
{showResult && (
<div className={`p-6 rounded-lg w-full mb-6 ${
selectedAnswer === correctAnswer
? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200'
}`}>
<p className={`font-semibold text-left text-xl mb-4 ${
selectedAnswer === correctAnswer ? 'text-green-700' : 'text-red-700'
}`}>
{selectedAnswer === correctAnswer ? '正確!' : '錯誤!'}
</p>
{selectedAnswer !== correctAnswer && (
<div className="mb-4">
<p className="text-gray-700 text-left">
正確答案是<strong className="text-lg">{correctAnswer}</strong>
</p>
</div>
)}
<div className="space-y-3">
<div className="text-left">
<div className="flex items-center text-gray-600">
<span className="mx-2">{pronunciation}</span>
<AudioPlayer text={correctAnswer} />
</div>
</div>
<div className="text-left">
<p className="text-gray-600">
<strong>例句</strong>{example}
</p>
<p className="text-gray-500 text-sm">
<strong>翻譯</strong>{exampleTranslation}
</p>
</div>
</div>
</div>
)}
</div>
```
---
## ✏️ 3. 例句填空 (sentence-fill)
### 核心特色
- 內嵌式輸入框設計
- 動態寬度調整
- 提示系統
- 支援圖片顯示
### HTML結構
```jsx
<div className="bg-white rounded-xl shadow-lg p-8">
{/* 標題區 */}
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900">例句填空</h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{difficultyLevel}
</span>
</div>
{/* 圖片區(如果有) */}
{exampleImage && (
<div className="mb-6">
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left">圖片提示</h3>
<img
src={exampleImage}
alt="Example illustration"
className="w-full max-w-md mx-auto rounded-lg cursor-pointer"
onClick={() => setModalImage(exampleImage)}
/>
</div>
</div>
)}
{/* 指示文字 */}
<p className="text-lg text-gray-700 mb-6 text-left">
請點擊例句中的空白處輸入正確的單字
</p>
{/* 填空句子區域 */}
<div className="mb-6">
<div className="bg-gray-50 rounded-lg p-6">
<div className="text-lg text-gray-700 leading-relaxed">
{/* 動態生成填空句子 */}
{sentence.split(targetWord).map((part, index) => {
const isTargetWord = index > 0;
return (
<React.Fragment key={index}>
<span>{part}</span>
{isTargetWord && (
<span className="relative inline-block mx-1">
<input
type="text"
value={fillAnswer}
onChange={(e) => setFillAnswer(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !showResult && fillAnswer.trim()) {
handleFillAnswer()
}
}}
placeholder=""
disabled={showResult}
className={`inline-block px-2 py-1 text-center bg-transparent focus:outline-none disabled:bg-gray-100 ${
fillAnswer
? 'border-b-2 border-blue-500'
: 'border-b-2 border-dashed border-gray-400 focus:border-blue-400 focus:border-solid'
}`}
style={{
width: `${Math.max(100, Math.max(targetWord.length * 12, fillAnswer.length * 12 + 20))}px`
}}
/>
{!fillAnswer && (
<span
className="absolute inset-0 flex items-center justify-center text-gray-400 pointer-events-none"
style={{ paddingBottom: '8px' }}
>
____
</span>
)}
</span>
)}
</React.Fragment>
)
})}
</div>
</div>
</div>
{/* 操作按鈕 */}
<div className="flex gap-3 mb-4">
{!showResult && fillAnswer.trim() && (
<button
onClick={handleFillAnswer}
className="px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
確認答案
</button>
)}
<button
onClick={() => setShowHint(!showHint)}
className="px-6 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors"
>
{showHint ? '隱藏提示' : '顯示提示'}
</button>
</div>
{/* 提示區域 */}
{showHint && (
<div className="mb-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<h4 className="font-semibold text-yellow-800 mb-2">詞彙定義</h4>
<p className="text-yellow-800">{definition}</p>
</div>
)}
{/* 結果反饋區 */}
{showResult && (
<div className={`mt-6 p-6 rounded-lg w-full ${
fillAnswer.toLowerCase().trim() === targetWord.toLowerCase()
? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200'
}`}>
<p className={`font-semibold text-left text-xl mb-4 ${
fillAnswer.toLowerCase().trim() === targetWord.toLowerCase()
? 'text-green-700'
: 'text-red-700'
}`}>
{fillAnswer.toLowerCase().trim() === targetWord.toLowerCase() ? '正確!' : '錯誤!'}
</p>
{fillAnswer.toLowerCase().trim() !== targetWord.toLowerCase() && (
<div className="mb-4">
<p className="text-gray-700 text-left">
正確答案是<strong className="text-lg">{targetWord}</strong>
</p>
</div>
)}
<div className="space-y-3">
<div className="text-left">
<p className="text-gray-600">
<strong>發音</strong>
<span className="mx-2">{pronunciation}</span>
<AudioPlayer text={targetWord} />
</p>
</div>
<div className="text-left">
<p className="text-gray-600">
<strong>完整例句</strong>"{completeExample}"
</p>
<p className="text-gray-500 text-sm">
<strong>翻譯</strong>"{exampleTranslation}"
</p>
</div>
</div>
</div>
)}
</div>
```
---
## 🧩 4. 例句重組 (sentence-reorder)
### 核心特色
- 拖拽式單字重組
- 雙區域設計(可用單字區 + 重組區域)
- 即時狀態反饋
### HTML結構
```jsx
<div className="bg-white rounded-xl shadow-lg p-8">
{/* 標題區 */}
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900">例句重組</h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{difficultyLevel}
</span>
</div>
{/* 圖片區(如果有) */}
{exampleImage && (
<div className="mb-6">
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left">圖片提示</h3>
<img
src={exampleImage}
alt="Example illustration"
className="w-full max-w-md mx-auto rounded-lg cursor-pointer"
onClick={() => setModalImage(exampleImage)}
/>
</div>
</div>
)}
{/* 重組區域 */}
<div className="mb-6">
<h3 className="text-lg font-semibold text-gray-900 mb-3 text-left">重組區域</h3>
<div className="relative min-h-[120px] bg-gray-50 rounded-lg p-4 border-2 border-dashed border-gray-300">
{arrangedWords.length === 0 ? (
<div className="absolute inset-0 flex items-center justify-center text-gray-400 text-lg">
將下方單字拖拽到這裡組成完整句子
</div>
) : (
<div className="flex flex-wrap gap-2">
{arrangedWords.map((word, index) => (
<div
key={`arranged-${index}`}
className="inline-flex items-center bg-blue-100 text-blue-800 px-3 py-2 rounded-full text-lg font-medium cursor-pointer hover:bg-blue-200 transition-colors"
onClick={() => handleRemoveFromArranged(word)}
>
{word}
<span className="ml-2 text-blue-600 hover:text-blue-800">×</span>
</div>
))}
</div>
)}
</div>
</div>
{/* 指示文字 */}
<p className="text-lg text-gray-700 mb-6 text-left">
點擊下方單字依序重組成正確的句子
</p>
{/* 可用單字區域 */}
<div className="mb-6">
<h3 className="text-lg font-semibold text-gray-900 mb-3 text-left">可用單字</h3>
<div className="bg-white border border-gray-200 rounded-lg p-4 min-h-[80px]">
{shuffledWords.length === 0 ? (
<div className="text-center text-gray-400">
所有單字都已使用
</div>
) : (
<div className="flex flex-wrap gap-2">
{shuffledWords.map((word, index) => (
<button
key={`shuffled-${index}`}
onClick={() => handleWordClick(word)}
className="bg-gray-100 text-gray-800 px-3 py-2 rounded-full text-lg font-medium cursor-pointer hover:bg-gray-200 active:bg-gray-300 transition-colors select-none"
>
{word}
</button>
))}
</div>
)}
</div>
</div>
{/* 控制按鈕 */}
<div className="flex gap-3 mb-6">
{arrangedWords.length > 0 && (
<button
onClick={handleCheckReorderAnswer}
className="px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
檢查答案
</button>
)}
<button
onClick={handleResetReorder}
className="px-6 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors"
>
重新開始
</button>
</div>
{/* 結果反饋區 */}
{reorderResult !== null && (
<div className={`p-6 rounded-lg w-full mb-6 ${
reorderResult
? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200'
}`}>
<p className={`font-semibold text-left text-xl mb-4 ${
reorderResult ? 'text-green-700' : 'text-red-700'
}`}>
{reorderResult ? '正確!' : '錯誤!'}
</p>
{!reorderResult && (
<div className="mb-4">
<p className="text-gray-700 text-left">
正確答案是<strong className="text-lg">{correctSentence}</strong>
</p>
</div>
)}
<div className="space-y-3">
<div className="text-left">
<div className="flex items-center text-gray-600">
<AudioPlayer text={correctSentence} />
</div>
</div>
<div className="text-left">
<p className="text-gray-600">
{translation}
</p>
</div>
</div>
</div>
)}
</div>
```
---
## 🎧 5. 聽力測驗 (listening)
### 5.1 詞彙聽力 (vocab-listening)
```jsx
<div className="bg-white rounded-xl shadow-lg p-8">
{/* 標題區 */}
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900">詞彙聽力</h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{difficultyLevel}
</span>
</div>
{/* 指示文字 */}
<p className="text-lg text-gray-700 mb-6 text-left">
請聽發音並選擇正確的英文單字
</p>
{/* 音頻播放區 */}
<div className="space-y-4 mb-8">
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left">發音</h3>
<div className="flex items-center gap-3">
<span className="text-gray-700">{pronunciation}</span>
<AudioPlayer text={word} />
</div>
</div>
</div>
{/* 選項區域 - 2x2網格布局 */}
<div className="grid grid-cols-2 gap-3 mb-6">
{options.map((word) => (
<button
key={word}
onClick={() => !showResult && handleListeningAnswer(word)}
disabled={showResult}
className={`p-4 text-center rounded-lg border-2 transition-all ${
showResult
? word === correctAnswer
? 'border-green-500 bg-green-50 text-green-700'
: word === selectedAnswer
? 'border-red-500 bg-red-50 text-red-700'
: 'border-gray-200 bg-gray-50 text-gray-500'
: 'border-gray-200 hover:border-blue-300 hover:bg-blue-50'
}`}
>
<div className="text-lg font-medium">{word}</div>
<div className="text-sm text-gray-500 mt-1">{pronunciation}</div>
</button>
))}
</div>
{/* 結果反饋區 */}
{showResult && (
<div className={`p-6 rounded-lg w-full mb-6 ${
selectedAnswer === correctAnswer
? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200'
}`}>
<p className={`font-semibold text-left text-xl mb-4 ${
selectedAnswer === correctAnswer ? 'text-green-700' : 'text-red-700'
}`}>
{selectedAnswer === correctAnswer ? '正確!' : '錯誤!'}
</p>
<div className="space-y-3">
<div className="text-left">
<p className="text-gray-700">
<strong>正確單字</strong>{correctAnswer}
</p>
<p className="text-gray-600">
<strong>定義</strong>{definition}
</p>
</div>
</div>
</div>
)}
</div>
```
### 5.2 例句聽力 (sentence-listening)
```jsx
<div className="bg-white rounded-xl shadow-lg p-8">
{/* 標題區 */}
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900">例句聽力</h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{difficultyLevel}
</span>
</div>
{/* 指示文字 */}
<p className="text-lg text-gray-700 mb-6 text-left">
請聽例句並選擇正確的選項
</p>
{/* 音頻播放區 */}
<div className="text-center mb-8">
<div className="mb-6">
<AudioPlayer text={example} />
<p className="text-sm text-gray-500 mt-2">
點擊播放聽例句
</p>
</div>
</div>
{/* 選項區域 - 垂直列表布局 */}
<div className="grid grid-cols-1 gap-3 mb-6">
{sentenceOptions.map((sentence, idx) => (
<button
key={idx}
onClick={() => !showResult && handleSentenceListeningAnswer(sentence)}
disabled={showResult}
className={`w-full p-4 text-left rounded-lg border-2 transition-all ${
showResult
? sentence === correctSentence
? 'border-green-500 bg-green-50 text-green-700'
: sentence === selectedAnswer
? 'border-red-500 bg-red-50 text-red-700'
: 'border-gray-200 bg-gray-50 text-gray-500'
: 'border-gray-200 hover:border-blue-300 hover:bg-blue-50'
}`}
>
<div className="text-sm text-gray-600 mb-1">選項 {String.fromCharCode(65 + idx)}:</div>
<div className="text-base">{sentence}</div>
</button>
))}
</div>
{/* 結果反饋區 */}
{showResult && (
<div className={`p-6 rounded-lg w-full mb-6 ${
selectedAnswer === correctSentence
? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200'
}`}>
<p className={`font-semibold text-left text-xl mb-4 ${
selectedAnswer === correctSentence ? 'text-green-700' : 'text-red-700'
}`}>
{selectedAnswer === correctSentence ? '正確!' : '錯誤!'}
</p>
<div className="space-y-3">
<div className="text-left">
<p className="text-gray-700">
{correctSentence}
</p>
<p className="text-gray-600">
{translation}
</p>
</div>
</div>
</div>
)}
</div>
```
---
## 🎤 6. 口說測驗 (sentence-speaking)
### 核心特色
- 整合 VoiceRecorder 組件
- 語音識別評估
- 即時反饋系統
### HTML結構
```jsx
<div className="bg-white rounded-xl shadow-lg p-8">
{/* 標題區 */}
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900">例句口說</h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{difficultyLevel}
</span>
</div>
{/* VoiceRecorder 組件區域 */}
<div className="w-full">
<VoiceRecorder
targetText={example}
targetTranslation={exampleTranslation}
exampleImage={exampleImage}
instructionText="請看例句圖片並大聲說出完整的例句:"
onRecordingComplete={() => {
handleSpeakingAnswer(example)
}}
/>
</div>
{/* 結果反饋區 */}
{showResult && (
<div className="mt-6 p-6 rounded-lg bg-blue-50 border border-blue-200 w-full">
<p className="text-blue-700 text-left text-xl font-semibold mb-2">
錄音完成
</p>
<p className="text-gray-600 text-left">
系統正在評估你的發音...
</p>
</div>
)}
</div>
```
---
## 📊 進度條系統設計
### 雙層進度條
```jsx
<div className="mb-8">
<div className="flex justify-between items-center mb-3">
<span className="text-sm font-medium text-gray-900">學習進度</span>
<div className="flex items-center gap-6">
{/* 詞卡進度 */}
<div className="flex items-center gap-2 text-sm text-gray-600">
<span>詞卡: {completedCards}/{totalCards}</span>
</div>
{/* 測驗進度 */}
<button
onClick={() => setShowTaskListModal(true)}
className="text-sm text-gray-600 hover:text-blue-600 transition-colors cursor-pointer flex items-center gap-1"
title="點擊查看詳細進度"
>
測驗: {completedTests}/{totalTests}
<span className="text-xs ml-1">📋</span>
</button>
</div>
</div>
{/* 詞卡進度條 */}
<div className="flex items-center gap-2 mb-2">
<span className="text-xs text-gray-500 w-12">詞卡</span>
<div className="flex-1 bg-gray-200 rounded-full h-2">
<div
className="bg-green-500 h-2 rounded-full transition-all"
style={{ width: `${(completedCards / totalCards) * 100}%` }}
/>
</div>
<span className="text-xs text-gray-500 w-8">{completedCards}/{totalCards}</span>
</div>
{/* 測驗進度條 */}
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500 w-12">測驗</span>
<div
className="flex-1 bg-gray-200 rounded-full h-2 cursor-pointer hover:bg-gray-300 transition-colors"
onClick={() => setShowTaskListModal(true)}
title="點擊查看詳細進度"
>
<div
className="bg-blue-500 h-2 rounded-full transition-all hover:bg-blue-600"
style={{ width: `${(completedTests / totalTests) * 100}%` }}
/>
</div>
<span className="text-xs text-gray-500 w-8">{completedTests}/{totalTests}</span>
</div>
</div>
```
---
## 🎨 通用Modal設計
### 圖片放大Modal
```jsx
<div className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50" onClick={() => setModalImage(null)}>
<div className="relative max-w-4xl max-h-[90vh] mx-4">
<img
src={modalImage}
alt="放大圖片"
className="max-w-full max-h-full rounded-lg"
/>
<button
onClick={() => setModalImage(null)}
className="absolute top-4 right-4 bg-black bg-opacity-50 text-white p-2 rounded-full hover:bg-opacity-75"
>
</button>
</div>
</div>
```
### 任務清單Modal
```jsx
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-xl shadow-2xl max-w-4xl w-full mx-4 max-h-[80vh] overflow-hidden">
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-gray-200">
<h2 className="text-2xl font-bold text-gray-900 flex items-center gap-2">
📚 學習任務清單
</h2>
<button className="text-gray-400 hover:text-gray-600 text-2xl"></button>
</div>
{/* Content */}
<div className="p-6 overflow-y-auto max-h-[60vh]">
{/* 進度統計 */}
<div className="mb-6 bg-blue-50 rounded-lg p-4">
<div className="flex items-center justify-between text-sm">
<span className="text-blue-900 font-medium">
測驗進度: {completedTests} / {totalTests} ({progressPercentage}%)
</span>
<div className="flex items-center gap-4 text-blue-800">
<span> 已完成: {completedCount}</span>
<span> 進行中: {currentCount}</span>
<span> 待完成: {pendingCount}</span>
</div>
</div>
<div className="mt-3 w-full bg-blue-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all"
style={{ width: `${progressPercentage}%` }}
/>
</div>
</div>
{/* 測驗清單 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{testItems.map((item) => (
<div
key={item.id}
className={`flex items-center gap-3 p-3 rounded-lg transition-all ${
item.isCompleted
? 'bg-green-50 border border-green-200'
: item.isCurrent
? 'bg-blue-50 border border-blue-300 shadow-sm'
: 'bg-gray-50 border border-gray-200'
}`}
>
<span className="text-lg">
{item.isCompleted ? '✅' : item.isCurrent ? '⏳' : '⚪'}
</span>
<div className="flex-1">
<div className="font-medium text-sm">
{item.order}. {item.word} - {item.testName}
</div>
<div className={`text-xs ${
item.isCompleted ? 'text-green-600' :
item.isCurrent ? 'text-blue-600' : 'text-gray-500'
}`}>
{item.isCompleted ? '已完成' :
item.isCurrent ? '進行中' : '待完成'}
</div>
</div>
</div>
))}
</div>
</div>
{/* Footer */}
<div className="px-6 py-4 border-t border-gray-200 bg-gray-50">
<button className="w-full py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors">
關閉
</button>
</div>
</div>
</div>
```
---
## 🎮 導航控制設計
### 智能導航按鈕
```jsx
<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 < totalCards - 1 ? '下一張' : '完成學習'}
</button>
</div>
```
### 跳過功能按鈕
```jsx
{/* 跳過按鈕(答題前狀態) */}
{!showResult && (
<div className="flex justify-center mb-4">
<button
onClick={handleSkip}
className="px-6 py-2 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600 transition-colors"
>
⏭️ 暫時跳過
</button>
</div>
)}
{/* 繼續按鈕(答題後狀態) */}
{showResult && (
<div className="flex justify-center mt-6">
<button
onClick={handleNext}
className="px-8 py-3 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors font-medium"
>
繼續下一個
</button>
</div>
)}
```
---
## 🎨 設計特色總結
### 1. **一致性設計**
- 所有測驗類型都採用相同的容器結構 (`bg-white rounded-xl shadow-lg p-8`)
- 統一的配色方案和視覺層次
- 標準化的按鈕樣式和hover效果
### 2. **響應式布局**
- 使用 Grid 和 Flexbox 布局
- 適配不同螢幕尺寸 (`md:grid-cols-2 lg:grid-cols-3`)
- 移動端友好的觸控設計
### 3. **互動動畫**
- 翻卡3D效果 (`transform: rotateY(180deg)`)
- Hover狀態轉換 (`transition-all`, `hover:bg-blue-50`)
- 按鈕點擊反饋 (`active:bg-gray-300`)
### 4. **無障礙設計**
- 鍵盤導航支援 (`onKeyDown` Enter 鍵提交)
- 清晰的狀態反饋 (`disabled` 狀態顯示)
- 適當的對比度和字體大小
### 5. **視覺層次**
- 合理的間距系統 (`mb-6`, `p-4`, `gap-3`)
- 字體大小層次 (`text-4xl`, `text-2xl`, `text-lg`, `text-sm`)
- 顏色對比設計 (`text-gray-900`, `text-gray-700`, `text-gray-500`)
### 6. **即時反饋**
- 答題後的即時視覺反饋(綠色成功,紅色錯誤)
- 動態進度條更新
- 狀態圖標顯示 (`✅`, `❌`, `⏳`, `⚪`)
---
## 📝 實作注意事項
### 必需依賴組件
1. **AudioPlayer** - 音頻播放功能
2. **VoiceRecorder** - 語音錄製功能
3. **MasteryIndicator** - 熟悉度顯示
4. **ReviewTypeIndicator** - 測驗類型指示器
### 關鍵狀態管理
- `isFlipped` - 翻卡狀態
- `showResult` - 顯示答題結果
- `selectedAnswer` - 用戶選擇的答案
- `fillAnswer` - 填空題答案
- `arrangedWords` / `shuffledWords` - 重組題狀態
### 動畫效果實作
- 使用 CSS `transition-all` 實現平滑過渡
- 翻卡動畫需要 `transform-style: preserve-3d`
- 進度條動畫使用 `transition-all` 配合動態 `width` 樣式
這份文件保留了原始設計的所有精華和用戶體驗細節,可作為重新實作的完整參考。