696 lines
20 KiB
Markdown
696 lines
20 KiB
Markdown
# 複習系統設計工具重構規格
|
||
|
||
## 📋 現況問題分析
|
||
|
||
### 當前 review-design 頁面的問題
|
||
|
||
**檔案**: `/frontend/app/review-design/page.tsx`
|
||
|
||
#### ❌ **問題 1: 靜態組件展示**
|
||
```typescript
|
||
// 當前實作:只是靜態展示不同測驗組件
|
||
const [activeTab, setActiveTab] = useState('FlipMemoryTest')
|
||
|
||
// 問題:無法模擬真實的複習流程
|
||
return <FlipMemoryTest cardData={staticData} onAnswer={logOnly} />
|
||
```
|
||
|
||
**後果**:
|
||
- 無法測試真實的 Store 協作
|
||
- 無法驗證答題→進度更新→下一題的完整流程
|
||
- 不能測試錯誤處理和邊界情況
|
||
|
||
#### ❌ **問題 2: 測試資料寫死**
|
||
```typescript
|
||
// 當前實作:直接 import 靜態 JSON
|
||
import exampleData from './example-data.json'
|
||
|
||
// 問題:無法動態切換或重置測試場景
|
||
const cardData = exampleData[currentCardIndex]
|
||
```
|
||
|
||
**後果**:
|
||
- 無法測試空資料狀態
|
||
- 無法測試資料載入失敗情況
|
||
- 無法快速切換不同測試場景
|
||
|
||
#### ❌ **問題 3: 缺乏真實性**
|
||
```typescript
|
||
// 當前實作:簡化的回調函數
|
||
const handleAnswer = (answer: string) => {
|
||
addLog(`答案: ${answer}`) // 只是記錄,不做真實處理
|
||
}
|
||
|
||
// 問題:無法測試真實的 Store 狀態更新
|
||
```
|
||
|
||
**後果**:
|
||
- 進度條不會真實更新
|
||
- Store 狀態不會改變
|
||
- 無法發現狀態同步問題
|
||
|
||
## 🎯 重構設計規格
|
||
|
||
### 目標:打造**專業的複習系統開發工具**
|
||
|
||
#### 核心需求:
|
||
1. **真實模擬**: 完整模擬生產環境的複習流程
|
||
2. **動態資料**: 可動態匯入、重置、切換測試資料
|
||
3. **狀態監控**: 即時顯示所有 Store 狀態
|
||
4. **調試功能**: 提供開發者需要的調試工具
|
||
|
||
## 🏗️ 新架構設計
|
||
|
||
### 整體頁面架構
|
||
|
||
```
|
||
複習系統設計工具
|
||
├── 控制面板 (ControlPanel)
|
||
│ ├── 資料管理區
|
||
│ │ ├── 匯入測試資料按鈕
|
||
│ │ ├── 重置 Store 狀態按鈕
|
||
│ │ ├── 切換測試資料集下拉選單
|
||
│ │ └── Store 狀態重置按鈕
|
||
│ ├── 模擬控制區
|
||
│ │ ├── 開始複習模擬按鈕
|
||
│ │ ├── 暫停/繼續按鈕
|
||
│ │ └── 結束模擬按鈕
|
||
│ └── 快速測試區
|
||
│ ├── 單一組件測試模式
|
||
│ └── 完整流程測試模式
|
||
├── 複習模擬器 (ReviewSimulator)
|
||
│ ├── 真實的 ReviewRunner 組件
|
||
│ ├── 真實的進度條和導航
|
||
│ └── 真實的狀態管理
|
||
└── 調試面板 (DebugPanel)
|
||
├── Store 狀態監控 (即時)
|
||
├── 答題歷史記錄
|
||
├── 性能指標顯示
|
||
└── 錯誤日誌
|
||
```
|
||
|
||
### 1. 資料管理系統設計
|
||
|
||
#### 動態資料匯入機制
|
||
```typescript
|
||
interface TestDataManager {
|
||
// 測試資料集管理
|
||
availableDatasets: TestDataset[]
|
||
currentDataset: TestDataset | null
|
||
|
||
// 動態操作
|
||
importDataset(dataset: TestDataset): void
|
||
resetStores(): void
|
||
switchDataset(datasetId: string): void
|
||
|
||
// 預定義場景
|
||
loadScenario(scenario: TestScenario): void
|
||
}
|
||
|
||
// 測試場景定義
|
||
type TestScenario =
|
||
| 'empty' // 空資料測試
|
||
| 'single-card' // 單詞卡測試
|
||
| 'full-session' // 完整會話測試
|
||
| 'error-cases' // 錯誤情況測試
|
||
| 'performance' // 性能測試
|
||
```
|
||
|
||
#### 測試資料結構
|
||
```typescript
|
||
interface TestDataset {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
flashcards: MockFlashcard[]
|
||
scenarios: {
|
||
completedTests?: CompletedTest[]
|
||
userProfile?: UserProfile
|
||
errorConditions?: ErrorCondition[]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 真實複習模擬器設計
|
||
|
||
#### 完整Store整合
|
||
```typescript
|
||
const ReviewSimulator = () => {
|
||
// 使用真實的 Store,不是模擬
|
||
const reviewSession = useReviewSessionStore()
|
||
const testQueue = useTestQueueStore()
|
||
const testResult = useTestResultStore()
|
||
const reviewData = useReviewDataStore()
|
||
const reviewUI = useReviewUIStore()
|
||
|
||
// 真實的初始化流程
|
||
const initializeSimulation = async (dataset: TestDataset) => {
|
||
// 1. 重置所有 Store
|
||
reviewSession.resetSession()
|
||
testQueue.resetQueue()
|
||
testResult.resetScore()
|
||
reviewData.resetData()
|
||
|
||
// 2. 載入測試資料到 ReviewDataStore
|
||
reviewData.setDueCards(dataset.flashcards)
|
||
|
||
// 3. 觸發真實的佇列初始化
|
||
testQueue.initializeTestQueue(dataset.flashcards, dataset.scenarios.completedTests || [])
|
||
|
||
// 4. 設置第一張卡片
|
||
if (dataset.flashcards.length > 0) {
|
||
reviewSession.setCurrentCard(dataset.flashcards[0])
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="review-simulator">
|
||
{/* 使用真實的 ReviewRunner,不是模擬組件 */}
|
||
<ReviewRunner />
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
#### 真實進度追蹤
|
||
```typescript
|
||
// 真實的進度條,連接到真實 Store
|
||
const RealProgressTracker = () => {
|
||
const { testItems, currentTestIndex, completedTests } = useTestQueueStore()
|
||
const { score } = useTestResultStore()
|
||
|
||
// 真實計算,不是模擬
|
||
const progress = testItems.length > 0 ? (completedTests / testItems.length) * 100 : 0
|
||
const accuracy = score.total > 0 ? (score.correct / score.total) * 100 : 0
|
||
|
||
return (
|
||
<div className="real-progress-tracker">
|
||
<div className="progress-stats">
|
||
<span>進度: {completedTests}/{testItems.length} ({progress.toFixed(1)}%)</span>
|
||
<span>正確率: {score.correct}/{score.total} ({accuracy.toFixed(1)}%)</span>
|
||
</div>
|
||
<div className="progress-bar">
|
||
<div
|
||
className="progress-fill bg-blue-500 h-2 rounded transition-all duration-300"
|
||
style={{ width: `${progress}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 3. 調試面板設計
|
||
|
||
#### Store 狀態即時監控
|
||
```typescript
|
||
const StoreMonitor = () => {
|
||
// 即時監控所有 Store 狀態
|
||
const sessionState = useReviewSessionStore()
|
||
const queueState = useTestQueueStore()
|
||
const resultState = useTestResultStore()
|
||
const dataState = useReviewDataStore()
|
||
const uiState = useReviewUIStore()
|
||
|
||
return (
|
||
<div className="store-monitor">
|
||
<div className="store-section">
|
||
<h4>Session Store</h4>
|
||
<pre className="store-state">
|
||
{JSON.stringify({
|
||
mounted: sessionState.mounted,
|
||
currentCard: sessionState.currentCard?.id,
|
||
mode: sessionState.mode,
|
||
isLoading: sessionState.isLoading,
|
||
error: sessionState.error
|
||
}, null, 2)}
|
||
</pre>
|
||
</div>
|
||
|
||
<div className="store-section">
|
||
<h4>Queue Store</h4>
|
||
<pre className="store-state">
|
||
{JSON.stringify({
|
||
totalTests: queueState.testItems.length,
|
||
currentIndex: queueState.currentTestIndex,
|
||
currentMode: queueState.currentMode,
|
||
completedCount: queueState.testItems.filter(t => t.isCompleted).length,
|
||
skippedCount: queueState.testItems.filter(t => t.isSkipped).length
|
||
}, null, 2)}
|
||
</pre>
|
||
</div>
|
||
|
||
<div className="store-section">
|
||
<h4>Result Store</h4>
|
||
<pre className="store-state">
|
||
{JSON.stringify(resultState.score, null, 2)}
|
||
</pre>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 4. 操作控制面板設計
|
||
|
||
#### 資料管理控制
|
||
```typescript
|
||
const DataControlPanel = () => {
|
||
const [selectedDataset, setSelectedDataset] = useState<string>('')
|
||
|
||
const predefinedDatasets = [
|
||
{
|
||
id: 'basic',
|
||
name: '基礎詞彙 (5張卡)',
|
||
description: 'A1-A2 等級詞彙,適合基礎測試'
|
||
},
|
||
{
|
||
id: 'advanced',
|
||
name: '進階詞彙 (10張卡)',
|
||
description: 'B2-C1 等級詞彙,測試複雜邏輯'
|
||
},
|
||
{
|
||
id: 'mixed',
|
||
name: '混合難度 (15張卡)',
|
||
description: '各等級混合,測試自適應算法'
|
||
},
|
||
{
|
||
id: 'error-test',
|
||
name: '錯誤情況測試',
|
||
description: '包含異常資料,測試錯誤處理'
|
||
}
|
||
]
|
||
|
||
return (
|
||
<div className="data-control-panel">
|
||
<div className="control-section">
|
||
<h3>測試資料管理</h3>
|
||
<select
|
||
value={selectedDataset}
|
||
onChange={(e) => setSelectedDataset(e.target.value)}
|
||
>
|
||
<option value="">選擇測試資料集...</option>
|
||
{predefinedDatasets.map(dataset => (
|
||
<option key={dataset.id} value={dataset.id}>
|
||
{dataset.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
|
||
<div className="action-buttons">
|
||
<button onClick={() => importDataset(selectedDataset)}>
|
||
匯入資料
|
||
</button>
|
||
<button onClick={resetAllStores}>
|
||
重置 Store
|
||
</button>
|
||
<button onClick={clearAllData}>
|
||
清空資料
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="current-dataset-info">
|
||
{currentDataset && (
|
||
<div className="dataset-info">
|
||
<h4>當前資料集: {currentDataset.name}</h4>
|
||
<p>{currentDataset.description}</p>
|
||
<p>詞卡數量: {currentDataset.flashcards.length}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 5. 模擬控制器設計
|
||
|
||
#### 複習流程控制
|
||
```typescript
|
||
const SimulationController = () => {
|
||
const [isSimulating, setIsSimulating] = useState(false)
|
||
const [simulationSpeed, setSimulationSpeed] = useState(1)
|
||
|
||
const simulationControls = {
|
||
// 開始完整複習模擬
|
||
startFullSimulation: async () => {
|
||
setIsSimulating(true)
|
||
await initializeRealReviewSession()
|
||
},
|
||
|
||
// 暫停模擬
|
||
pauseSimulation: () => {
|
||
setIsSimulating(false)
|
||
},
|
||
|
||
// 單步執行 (逐題測試)
|
||
stepThrough: async () => {
|
||
await processNextTest()
|
||
updateDebugInfo()
|
||
},
|
||
|
||
// 快速完成 (自動答題)
|
||
autoComplete: async () => {
|
||
while (!isAllTestsCompleted()) {
|
||
await autoAnswerCurrentTest()
|
||
await waitFor(1000 / simulationSpeed)
|
||
}
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="simulation-controller">
|
||
<div className="simulation-status">
|
||
<span className={`status-indicator ${isSimulating ? 'active' : 'inactive'}`}>
|
||
{isSimulating ? '模擬進行中' : '模擬暫停'}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="control-buttons">
|
||
<button onClick={simulationControls.startFullSimulation}>
|
||
開始完整模擬
|
||
</button>
|
||
<button onClick={simulationControls.stepThrough}>
|
||
單步執行
|
||
</button>
|
||
<button onClick={simulationControls.autoComplete}>
|
||
自動完成
|
||
</button>
|
||
<button onClick={simulationControls.pauseSimulation}>
|
||
暫停模擬
|
||
</button>
|
||
</div>
|
||
|
||
<div className="simulation-settings">
|
||
<label>
|
||
模擬速度:
|
||
<input
|
||
type="range"
|
||
min="0.1"
|
||
max="3"
|
||
step="0.1"
|
||
value={simulationSpeed}
|
||
onChange={(e) => setSimulationSpeed(Number(e.target.value))}
|
||
/>
|
||
{simulationSpeed}x
|
||
</label>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 6. 調試工具增強
|
||
|
||
#### 測驗佇列視覺化
|
||
```typescript
|
||
const QueueVisualizer = () => {
|
||
const { testItems, currentTestIndex } = useTestQueueStore()
|
||
|
||
return (
|
||
<div className="queue-visualizer">
|
||
<h4>測驗佇列狀態</h4>
|
||
<div className="queue-timeline">
|
||
{testItems.map((test, index) => (
|
||
<div
|
||
key={test.id}
|
||
className={`queue-item ${
|
||
index === currentTestIndex ? 'current' :
|
||
test.isCompleted ? 'completed' :
|
||
test.isSkipped ? 'skipped' :
|
||
test.isIncorrect ? 'incorrect' : 'pending'
|
||
}`}
|
||
>
|
||
<div className="test-info">
|
||
<span className="test-type">{test.testType}</span>
|
||
<span className="word">{test.word}</span>
|
||
<span className="priority">P{test.priority}</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
#### 答題歷史追蹤
|
||
```typescript
|
||
const AnswerHistoryTracker = () => {
|
||
const [answerHistory, setAnswerHistory] = useState<AnswerRecord[]>([])
|
||
|
||
const trackAnswer = useCallback((answer: AnswerRecord) => {
|
||
setAnswerHistory(prev => [...prev, {
|
||
...answer,
|
||
timestamp: new Date(),
|
||
responseTime: answer.responseTime,
|
||
isCorrect: answer.isCorrect
|
||
}])
|
||
}, [])
|
||
|
||
return (
|
||
<div className="answer-history">
|
||
<h4>答題歷史</h4>
|
||
<div className="history-list">
|
||
{answerHistory.map((record, index) => (
|
||
<div key={index} className="history-item">
|
||
<span className="timestamp">{record.timestamp.toLocaleTimeString()}</span>
|
||
<span className="test-type">{record.testType}</span>
|
||
<span className="word">{record.word}</span>
|
||
<span className={`result ${record.isCorrect ? 'correct' : 'incorrect'}`}>
|
||
{record.isCorrect ? '✓' : '✗'}
|
||
</span>
|
||
<span className="response-time">{record.responseTime}ms</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
## 🎛️ 操作流程設計
|
||
|
||
### 理想的使用流程
|
||
|
||
#### 1. 初始狀態 (空白畫面)
|
||
```
|
||
┌─────────────────────────────────┐
|
||
│ 複習系統設計工具 │
|
||
├─────────────────────────────────┤
|
||
│ │
|
||
│ 🔧 控制面板 │
|
||
│ ┌───────────────────────────┐ │
|
||
│ │ 📁 測試資料管理 │ │
|
||
│ │ ○ 選擇資料集... │ │
|
||
│ │ [ 匯入資料 ] [ 重置 ] │ │
|
||
│ └───────────────────────────┘ │
|
||
│ │
|
||
│ 💭 當前狀態: 無資料 │
|
||
│ 📊 Store 狀態: 空 │
|
||
└─────────────────────────────────┘
|
||
```
|
||
|
||
#### 2. 匯入資料後
|
||
```
|
||
┌─────────────────────────────────┐
|
||
│ 🎯 模擬控制 │
|
||
│ [ 開始完整模擬 ] │
|
||
│ [ 單步執行 ] [ 自動完成 ] │
|
||
├─────────────────────────────────┤
|
||
│ 📊 即時狀態監控 │
|
||
│ TestQueue: 15 items loaded │
|
||
│ Current: flip-memory (0/15) │
|
||
│ Score: 0/0 (0%) │
|
||
└─────────────────────────────────┘
|
||
```
|
||
|
||
#### 3. 模擬進行中
|
||
```
|
||
┌─────────────────────────────────┐
|
||
│ 🎮 複習模擬器 │
|
||
│ ┌─ 真實 ReviewRunner ─────┐ │
|
||
│ │ [Progress: 3/15 ████▒▒] │ │
|
||
│ │ │ │
|
||
│ │ 當前測驗: 翻卡記憶 │ │
|
||
│ │ 詞卡: "elaborate" │ │
|
||
│ │ [ 信心度選擇: 1-5 ] │ │
|
||
│ │ │ │
|
||
│ │ [ 跳過 ] [ 繼續 ] │ │
|
||
│ └─────────────────────────┘ │
|
||
├─────────────────────────────────┤
|
||
│ 🐛 調試面板 (即時更新) │
|
||
│ TestQueue: item 3→4 │
|
||
│ Score: 2✓ 1✗ (66.7%) │
|
||
│ LastAnswer: "elaborate" ✓ │
|
||
└─────────────────────────────────┘
|
||
```
|
||
|
||
### 3. 測試資料集設計
|
||
|
||
#### 預定義資料集範例
|
||
```json
|
||
{
|
||
"datasets": [
|
||
{
|
||
"id": "basic-flow",
|
||
"name": "基礎流程測試",
|
||
"description": "測試完整的答題→進度更新→下一題流程",
|
||
"flashcards": [
|
||
{
|
||
"id": "test-1",
|
||
"word": "hello",
|
||
"definition": "A greeting",
|
||
"cefr": "A1"
|
||
}
|
||
],
|
||
"scenarios": {
|
||
"completedTests": [],
|
||
"userProfile": {
|
||
"level": "A2",
|
||
"preferences": {}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "error-handling",
|
||
"name": "錯誤處理測試",
|
||
"description": "測試各種錯誤情況的處理",
|
||
"flashcards": [],
|
||
"scenarios": {
|
||
"errorConditions": [
|
||
"api_failure",
|
||
"invalid_answer",
|
||
"network_timeout"
|
||
]
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 4. 開發者工作流程
|
||
|
||
#### 典型調試場景
|
||
```typescript
|
||
// 場景 1: 測試新測驗類型
|
||
1. 選擇「單一組件測試」模式
|
||
2. 匯入「基礎詞彙」資料集
|
||
3. 選擇特定測驗類型 (如 sentence-fill)
|
||
4. 逐步測試答題邏輯
|
||
5. 檢查 Store 狀態變化
|
||
6. 驗證UI反饋正確性
|
||
|
||
// 場景 2: 測試完整流程
|
||
1. 選擇「完整流程測試」模式
|
||
2. 匯入「混合難度」資料集
|
||
3. 開始自動模擬
|
||
4. 監控進度條更新
|
||
5. 檢查佇列重排邏輯
|
||
6. 驗證會話完成處理
|
||
|
||
// 場景 3: 測試錯誤處理
|
||
1. 選擇「錯誤情況測試」模式
|
||
2. 觸發各種錯誤條件
|
||
3. 驗證錯誤邊界處理
|
||
4. 檢查錯誤恢復機制
|
||
```
|
||
|
||
## 🔧 技術實施細節
|
||
|
||
### Store 重置機制
|
||
```typescript
|
||
const resetAllStores = () => {
|
||
// 重置所有複習相關 Store
|
||
const { resetSession } = useReviewSessionStore.getState()
|
||
const { resetQueue } = useTestQueueStore.getState()
|
||
const { resetScore } = useTestResultStore.getState()
|
||
const { resetData } = useReviewDataStore.getState()
|
||
|
||
resetSession()
|
||
resetQueue()
|
||
resetScore()
|
||
resetData()
|
||
|
||
console.log('✅ 所有 Store 已重置')
|
||
}
|
||
```
|
||
|
||
### 真實資料注入
|
||
```typescript
|
||
const injectTestData = async (dataset: TestDataset) => {
|
||
// 模擬真實的資料載入流程
|
||
const { setDueCards, setLoadingCards } = useReviewDataStore.getState()
|
||
|
||
setLoadingCards(true)
|
||
|
||
// 模擬API延遲
|
||
await new Promise(resolve => setTimeout(resolve, 500))
|
||
|
||
setDueCards(dataset.flashcards)
|
||
setLoadingCards(false)
|
||
|
||
console.log(`✅ 已匯入 ${dataset.flashcards.length} 張測試詞卡`)
|
||
}
|
||
```
|
||
|
||
### 自動答題機器人
|
||
```typescript
|
||
const createAnswerBot = () => {
|
||
return {
|
||
// 自動產生正確答案
|
||
generateCorrectAnswer: (cardData: any, testType: string): string => {
|
||
switch (testType) {
|
||
case 'vocab-choice':
|
||
case 'vocab-listening':
|
||
return cardData.word
|
||
case 'sentence-fill':
|
||
return cardData.word
|
||
case 'sentence-reorder':
|
||
case 'sentence-listening':
|
||
return cardData.example
|
||
default:
|
||
return ''
|
||
}
|
||
},
|
||
|
||
// 自動產生錯誤答案 (用於測試錯誤處理)
|
||
generateIncorrectAnswer: (cardData: any, testType: string): string => {
|
||
// 故意產生錯誤答案來測試錯誤處理邏輯
|
||
return 'incorrect_answer_' + Math.random().toString(36).substr(2, 9)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 📊 成功指標
|
||
|
||
### 工具效能指標
|
||
- **資料匯入時間**: < 1秒
|
||
- **Store 重置時間**: < 0.5秒
|
||
- **狀態監控更新**: 即時 (< 100ms)
|
||
- **模擬流程完成**: 15張卡片 < 30秒
|
||
|
||
### 開發者體驗指標
|
||
- **問題發現時間**: 從數小時縮短到數分鐘
|
||
- **UI設計驗證**: 即時預覽和調整
|
||
- **邏輯調試效率**: 提升 80%+
|
||
- **回歸測試**: 自動化場景測試
|
||
|
||
## 🎯 總結
|
||
|
||
這個重構將把 `review-design` 從**靜態組件展示**升級為**專業的複習系統開發工具**,提供:
|
||
|
||
1. **真實性**: 完全模擬生產環境行為
|
||
2. **靈活性**: 動態資料管理和場景切換
|
||
3. **可觀測性**: 完整的狀態監控和調試信息
|
||
4. **自動化**: 自動測試和驗證功能
|
||
|
||
**這樣的工具將大幅提升複習功能的開發和調試效率!** 🛠️✨
|
||
|
||
---
|
||
|
||
*規格版本: v1.0*
|
||
*設計目標: 專業複習系統開發工具*
|
||
*預計實施時間: 2-3 天* |