From 6c8c656dc3f0c13a40bbc4c9160a2d0a9f8e1ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=84=AD=E6=B2=9B=E8=BB=92?= Date: Fri, 26 Sep 2025 13:37:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=87=8D=E6=A7=8B?= =?UTF-8?q?=E8=A8=88=E5=8A=83=E6=96=87=E6=AA=94=E5=92=8C=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E6=9C=83=E8=A9=B1=E7=8B=80=E6=85=8B=E5=84=AA=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增複習系統重構計劃文檔,詳細規劃後端驅動架構 - 優化前端學習頁面,添加詞卡複習會話狀態管理 - 實現測驗項目進度追蹤和任務清單彈出功能 - 清理過期文檔檔案 - 為後續重構奠定基礎 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CEFR系統更新完成報告.md | 173 --------- frontend/app/learn/page.tsx | 706 ++++++++++++++++++++++++++---------- 冗餘數值欄位移除完成報告.md | 179 --------- 移除冗餘數值欄位計劃.md | 272 -------------- 複習系統重構計劃.md | 317 ++++++++++++++++ 5 files changed, 829 insertions(+), 818 deletions(-) delete mode 100644 CEFR系統更新完成報告.md delete mode 100644 冗餘數值欄位移除完成報告.md delete mode 100644 移除冗餘數值欄位計劃.md create mode 100644 複習系統重構計劃.md diff --git a/CEFR系統更新完成報告.md b/CEFR系統更新完成報告.md deleted file mode 100644 index aab495e..0000000 --- a/CEFR系統更新完成報告.md +++ /dev/null @@ -1,173 +0,0 @@ -# 智能複習系統CEFR架構更新完成報告 - -## 📋 更新總結 -**執行時間**: 2025-09-25 -**狀態**: ✅ **成功完成** -**架構**: 從雙欄位架構改為純CEFR字符串 + 即時轉換 -**前端地址**: http://localhost:3002/learn -**後端地址**: http://localhost:5008 - -## 🎯 更新目標達成 - -### ✅ 移除資料冗余問題 -- **原架構**: CEFR字符串 + 數值欄位 (資料重複) -- **新架構**: CEFR字符串 + 即時轉換 (消除冗余) -- **效果**: 簡化資料庫結構,減少維護負擔 - -### ✅ 符合CEFR國際標準 -- **用戶程度**: 基於User.EnglishLevel (A1-C2) -- **詞彙難度**: 基於Flashcard.DifficultyLevel (A1-C2) -- **顯示邏輯**: 完全使用標準CEFR術語 - -## 🔧 具體實施成果 - -### **後端架構更新** ✅ **完成** - -#### **1. API接口改為CEFR字符串** -```csharp -// OptimalModeRequest.cs - 新的請求格式 -public class OptimalModeRequest -{ - public string UserCEFRLevel { get; set; } = "B1"; // A1-C2字符串 - public string WordCEFRLevel { get; set; } = "B1"; // A1-C2字符串 - public bool IncludeHistory { get; set; } = true; -} -``` - -#### **2. ReviewTypeSelectorService即時轉換** -```csharp -// 接收CEFR字符串,內部即時轉換為數值計算 -public async Task SelectOptimalReviewModeAsync( - Guid flashcardId, string userCEFRLevel, string wordCEFRLevel) -{ - var userLevel = CEFRMappingService.GetWordLevel(userCEFRLevel); // A2→35 - var wordLevel = CEFRMappingService.GetWordLevel(wordCEFRLevel); // A2→35 - // 使用數值進行算法計算... -} -``` - -### **前端架構更新** ✅ **完成** - -#### **1. API服務層使用CEFR字符串** -```typescript -// flashcards.ts - 新的API呼叫方式 -async getOptimalReviewMode(cardId: string, userCEFRLevel: string, wordCEFRLevel: string) { - return await this.makeRequest(`/flashcards/${cardId}/optimal-review-mode`, { - method: 'POST', - body: JSON.stringify({ - userCEFRLevel, // "A2" - wordCEFRLevel, // "B1" - includeHistory: true - }), - }); -} -``` - -#### **2. 學習頁面使用CEFR字符串** -```typescript -// learn/page.tsx - 智能選擇使用CEFR -const selectOptimalReviewMode = async (card: ExtendedFlashcard) => { - const userCEFRLevel = localStorage.getItem('userEnglishLevel') || 'A2'; - const wordCEFRLevel = card.difficultyLevel || 'A2'; - - console.log(`CEFR智能選擇: 用戶${userCEFRLevel} vs 詞彙${wordCEFRLevel}`); - - const apiResult = await flashcardsService.getOptimalReviewMode( - card.id, userCEFRLevel, wordCEFRLevel - ); -} -``` - -#### **3. 前端組件CEFR顯示** -```typescript -// ReviewTypeIndicator.tsx - 顯示標準CEFR等級 - - -// 顯示: "學習者等級: A2 | 詞彙等級: B1" -``` - -## 🧪 驗證測試結果 - -### **API測試成功** ✅ -```bash -✅ CEFR智能選擇成功: - 用戶等級: A2 - 詞彙等級: A2 - 選擇題型: sentence-speaking - 適配情境: 適中詞彙 - 選擇理由: 適中詞彙進行全方位練習 - -✅ 復習結果提交成功: - 新的熟悉度: 23 - 下次復習日期: 2025-09-26T00:00:00+08:00 -``` - -### **後端日誌驗證** ✅ -``` -Selecting optimal review mode for flashcard ..., userCEFR: A2, wordCEFR: A2 -CEFR converted to levels: A2→35, A2→35 -``` - -### **前端顯示更新** ✅ -- 學習者等級: 顯示 "A2" (而非數值35) -- 詞彙等級: 顯示 "A2" (而非數值35) -- 情境判斷: "適中詞彙" (基於CEFR等級差異) - -## 🚀 架構優化成果 - -### **技術優勢** ✅ **實現** -- ✅ **消除資料冗余**: 不再需要維護數值和CEFR兩套欄位 -- ✅ **符合國際標準**: 完全使用標準CEFR等級術語 -- ✅ **提升可讀性**: API和UI都使用CEFR,更直觀 -- ✅ **簡化維護**: 只需維護一套CEFR字符串欄位 - -### **性能表現** ✅ **優異** -- ✅ **即時轉換**: CEFRMappingService轉換極快 (< 1ms) -- ✅ **API響應**: 整體響應時間無影響 -- ✅ **算法準確**: 四情境判斷100%正確 -- ✅ **用戶體驗**: 顯示更加直觀和標準 - -### **系統穩定性** ✅ **優良** -- ✅ **向後相容**: 保留數值計算邏輯作為內部實現 -- ✅ **錯誤處理**: 完善的CEFR驗證和預設值 -- ✅ **測試通過**: API整合測試100%成功 - -## 📊 更新前後對比 - -### **更新前 (雙欄位架構)** -``` -❌ 複雜: User.EnglishLevel + Flashcard.UserLevel -❌ 冗余: Flashcard.DifficultyLevel + Flashcard.WordLevel -❌ 維護: 需要同步兩套欄位 -❌ 混亂: API使用數值,顯示使用CEFR -``` - -### **更新後 (純CEFR架構)** ✅ -``` -✅ 簡潔: User.EnglishLevel (CEFR字符串) -✅ 標準: Flashcard.DifficultyLevel (CEFR字符串) -✅ 一致: API和顯示都使用CEFR -✅ 高效: 即時轉換,無性能問題 -``` - -## 🎉 最終成果 - -**智能複習系統CEFR架構更新圓滿完成!** 🚀 - -### **✅ 達成效果** -1. **消除資料冗余**: 系統更簡潔,維護更容易 -2. **標準化實現**: 完全符合CEFR國際標準 -3. **用戶體驗提升**: 顯示更直觀,專業感更強 -4. **技術債務清理**: 移除不必要的複雜性 - -### **🔧 系統現狀** -- **後端**: 純CEFR字符串API,即時轉換計算 -- **前端**: 標準CEFR顯示,智能適配正常 -- **資料庫**: 待移除冗余數值欄位 (UserLevel, WordLevel) -- **性能**: 優異,轉換開銷微乎其微 - -**系統已準備投入生產使用,CEFR架構更加專業和標準!** 📚✅ \ No newline at end of file diff --git a/frontend/app/learn/page.tsx b/frontend/app/learn/page.tsx index aa5e96d..f636912 100644 --- a/frontend/app/learn/page.tsx +++ b/frontend/app/learn/page.tsx @@ -11,6 +11,26 @@ import MasteryIndicator from '@/components/review/MasteryIndicator' import { flashcardsService, type Flashcard } from '@/lib/services/flashcards' import { calculateCurrentMastery, getReviewTypesByDifficulty } from '@/lib/utils/masteryCalculator' +// 測驗項目接口 +interface TestItem { + id: string; // 唯一ID: cardId + testType + cardId: string; // 所屬詞卡ID + word: string; // 詞卡單字 + testType: string; // 測驗類型 (flip-memory, vocab-choice, etc.) + testName: string; // 測驗中文名稱 + isCompleted: boolean; // 是否已完成 + isCurrent: boolean; // 是否為當前測驗 + order: number; // 執行順序 (1-8) +} + +// 詞卡測驗分組接口 +interface CardTestGroup { + cardId: string; + word: string; + context: string; + tests: TestItem[]; +} + // 擴展的Flashcard接口,包含智能複習需要的欄位 interface ExtendedFlashcard extends Omit { nextReviewDate?: string; // 下次復習日期 (可選) @@ -24,6 +44,26 @@ interface ExtendedFlashcard extends Omit { // 注意:userLevel和wordLevel已移除,改用即時CEFR轉換 } +// 單個測驗結果接口 +interface TestResult { + testType: string; // 測驗類型 + isCorrect: boolean; // 是否正確 + userAnswer?: string; // 用戶答案 + confidenceLevel?: number; // 信心等級 (1-5, 用於flip-memory) + responseTimeMs: number; // 答題時間 + completedAt: Date; // 完成時間 +} + +// 詞卡複習會話接口 +interface CardReviewSession { + cardId: string; // 詞卡ID + word: string; // 詞卡單字 + plannedTests: string[]; // 預定的測驗類型列表 + completedTests: TestResult[]; // 已完成的測驗結果 + startedAt: Date; // 開始時間 + isCompleted: boolean; // 是否完成所有測驗 +} + export default function LearnPage() { const router = useRouter() const [mounted, setMounted] = useState(false) @@ -49,6 +89,13 @@ export default function LearnPage() { // 測驗進度狀態 const [totalTests, setTotalTests] = useState(0) // 所有測驗總數 const [completedTests, setCompletedTests] = useState(0) // 已完成測驗數 + const [testItems, setTestItems] = useState([]) // 測驗項目列表 + const [currentTestItemIndex, setCurrentTestItemIndex] = useState(0) // 當前測驗項目索引 + + // 詞卡複習會話狀態 + const [cardReviewSessions, setCardReviewSessions] = useState>(new Map()) + const [currentCardSession, setCurrentCardSession] = useState(null) + const [completedCards, setCompletedCards] = useState(0) // 已完成復習的詞卡數 // UI狀態 const [modalImage, setModalImage] = useState(null) @@ -57,6 +104,7 @@ export default function LearnPage() { const [reportingCard, setReportingCard] = useState(null) const [showComplete, setShowComplete] = useState(false) const [showNoDueCards, setShowNoDueCards] = useState(false) + const [showTaskListModal, setShowTaskListModal] = useState(false) const [cardHeight, setCardHeight] = useState(400) // 題型特定狀態 @@ -144,16 +192,40 @@ export default function LearnPage() { setCompletedTests(0); setDueCards(cardsToUse); + // 生成測驗項目列表 + const testItemsList = generateTestItems(cardsToUse, userCEFR); + setTestItems(testItemsList); + setCurrentTestItemIndex(0); + + console.log('📝 測驗項目列表生成:', testItemsList.length, '個項目'); + console.log('🎯 測驗項目詳情:', testItemsList.map(item => + `${item.order}. ${item.word} - ${item.testName}` + )); + // 設置第一張卡片 const firstCard = cardsToUse[0]; setCurrentCard(firstCard); setCurrentCardIndex(0); + // 開始第一張詞卡的複習會話 + startCardReviewSession(firstCard); + // 系統自動選擇模式 const selectedMode = await selectOptimalReviewMode(firstCard); setMode(selectedMode); setIsAutoSelecting(false); + // 標記第一個測驗項目為當前狀態 + if (testItemsList.length > 0) { + setTestItems(prev => + prev.map((item, index) => + index === 0 + ? { ...item, isCurrent: true } + : item + ) + ); + } + console.log(`🎯 初始載入: ${firstCard.word}, 選擇模式: ${selectedMode}`); } else { // 沒有到期詞卡 @@ -396,6 +468,105 @@ export default function LearnPage() { return labels[mode] || mode; } + // 生成測驗項目列表 + const generateTestItems = (cards: ExtendedFlashcard[], userCEFR: string): TestItem[] => { + const items: TestItem[] = []; + let order = 1; + + cards.forEach(card => { + const wordCEFR = card.difficultyLevel || 'A2'; + const testTypes = getReviewTypesByCEFR(userCEFR, wordCEFR); + + testTypes.forEach(testType => { + items.push({ + id: `${card.id}-${testType}`, + cardId: card.id, + word: card.word, + testType, + testName: getModeLabel(testType), + isCompleted: false, + isCurrent: false, + order + }); + order++; + }); + }); + + return items; + } + + // 按詞卡分組測驗項目 + const groupTestItemsByCard = (items: TestItem[]): CardTestGroup[] => { + const grouped = items.reduce((acc, item) => { + const cardId = item.cardId; + if (!acc[cardId]) { + const card = dueCards.find(c => c.id === cardId); + const userCEFR = localStorage.getItem('userEnglishLevel') || 'A2'; + const wordCEFR = card?.difficultyLevel || 'A2'; + + acc[cardId] = { + cardId, + word: item.word, + context: getCurrentContext(userCEFR, wordCEFR), + tests: [] + }; + } + acc[cardId].tests.push(item); + return acc; + }, {} as Record); + + return Object.values(grouped); + } + + // 初始化詞卡複習會話 + const initializeCardReviewSession = (card: ExtendedFlashcard): CardReviewSession => { + const userCEFR = localStorage.getItem('userEnglishLevel') || 'A2'; + const wordCEFR = card.difficultyLevel || 'A2'; + const plannedTests = getReviewTypesByCEFR(userCEFR, wordCEFR); + + return { + cardId: card.id, + word: card.word, + plannedTests, + completedTests: [], + startedAt: new Date(), + isCompleted: false + }; + } + + // 開始詞卡複習會話 + const startCardReviewSession = (card: ExtendedFlashcard) => { + const session = initializeCardReviewSession(card); + setCurrentCardSession(session); + + // 更新會話映射 + setCardReviewSessions(prev => new Map(prev.set(card.id, session))); + + console.log(`🎯 開始詞卡複習會話: ${card.word}`, { + plannedTests: session.plannedTests, + totalTests: session.plannedTests.length + }); + } + + // 檢查詞卡是否已完成所有測驗 + const isCardReviewCompleted = (cardId: string): boolean => { + const session = cardReviewSessions.get(cardId); + return session?.isCompleted || false; + } + + // 獲取詞卡的下一個測驗類型 + const getNextTestTypeForCard = (cardId: string): string | null => { + const session = cardReviewSessions.get(cardId); + if (!session) return null; + + const completedTestTypes = session.completedTests.map(t => t.testType); + const nextTestType = session.plannedTests.find(testType => + !completedTestTypes.includes(testType) + ); + + return nextTestType || null; + } + // 重置所有答題狀態 const resetAllStates = () => { setIsFlipped(false); @@ -538,8 +709,8 @@ export default function LearnPage() { total: prev.total + 1 })) - // 提交復習結果 - await submitReviewResult(isCorrect, userSentence); + // 記錄測驗結果 + recordTestResult(isCorrect, userSentence); } const handleResetReorder = () => { @@ -556,15 +727,60 @@ export default function LearnPage() { setIsFlipped(!isFlipped) } + // 移動到下一個測驗或下一張詞卡 const handleNext = async () => { - if (currentCardIndex < dueCards.length - 1) { - await loadNextCardWithAutoMode(currentCardIndex + 1); + if (!currentCard || !currentCardSession) return; + + // 檢查當前詞卡是否還有未完成的測驗 + const nextTestType = getNextTestTypeForCard(currentCard.id); + + if (nextTestType) { + // 當前詞卡還有測驗未完成,切換到下一個測驗類型 + const modeMapping: { [key: string]: typeof mode } = { + 'flip-memory': 'flip-memory', + 'vocab-choice': 'vocab-choice', + 'vocab-listening': 'vocab-listening', + 'sentence-fill': 'sentence-fill', + 'sentence-reorder': 'sentence-reorder', + 'sentence-speaking': 'sentence-speaking', + 'sentence-listening': 'sentence-listening' + }; + + const nextMode = modeMapping[nextTestType] || 'flip-memory'; + setMode(nextMode); + resetAllStates(); + + // 更新測驗項目的當前狀態 + setTestItems(prev => + prev.map(item => + item.cardId === currentCard.id && item.testType === nextTestType + ? { ...item, isCurrent: true } + : { ...item, isCurrent: false } + ) + ); + + console.log(`🔄 切換到下一個測驗: ${nextTestType} for ${currentCard.word}`); } else { - setShowComplete(true); + // 當前詞卡的所有測驗都已完成,移動到下一張詞卡 + if (currentCardIndex < dueCards.length - 1) { + const nextCardIndex = currentCardIndex + 1; + const nextCard = dueCards[nextCardIndex]; + + // 開始新詞卡的複習會話 + startCardReviewSession(nextCard); + + await loadNextCardWithAutoMode(nextCardIndex); + console.log(`➡️ 移動到下一張詞卡: ${nextCard.word}`); + } else { + // 所有詞卡都已完成 + setShowComplete(true); + console.log(`🎉 所有詞卡復習完成!`); + } } } const handlePrevious = async () => { + // 暫時保持簡單的向前導航 if (currentCardIndex > 0) { await loadNextCardWithAutoMode(currentCardIndex - 1); } @@ -582,46 +798,135 @@ export default function LearnPage() { total: prev.total + 1 })) - // 提交復習結果到後端 - await submitReviewResult(isCorrect, answer); + // 記錄測驗結果到本地會話 + recordTestResult(isCorrect, answer); } - // 提交復習結果並更新測驗進度 - const submitReviewResult = async (isCorrect: boolean, userAnswer?: string, confidenceLevel?: number) => { - if (!currentCard) return; + // 記錄測驗結果到本地會話(不提交到後端) + const recordTestResult = (isCorrect: boolean, userAnswer?: string, confidenceLevel?: number) => { + if (!currentCard || !currentCardSession) return; + const testResult: TestResult = { + testType: mode, + isCorrect, + userAnswer, + confidenceLevel, + responseTimeMs: 2000, // 簡化時間計算,稍後可改進 + completedAt: new Date() + }; + + // 更新當前會話的測驗結果 + const updatedSession = { + ...currentCardSession, + completedTests: [...currentCardSession.completedTests, testResult] + }; + + // 檢查是否完成所有預定測驗 + const isAllTestsCompleted = updatedSession.completedTests.length >= updatedSession.plannedTests.length; + if (isAllTestsCompleted) { + updatedSession.isCompleted = true; + } + + setCurrentCardSession(updatedSession); + + // 更新會話映射 + setCardReviewSessions(prev => new Map(prev.set(currentCard.id, updatedSession))); + + // 更新測驗進度 + setCompletedTests(prev => { + const newCompleted = prev + 1; + console.log(`📈 測驗進度更新: ${newCompleted}/${totalTests} (${Math.round((newCompleted/totalTests)*100)}%)`); + return newCompleted; + }); + + // 標記當前測驗項目為完成 + setTestItems(prev => + prev.map((item, index) => + index === currentTestItemIndex + ? { ...item, isCompleted: true, isCurrent: false } + : item + ) + ); + + // 移到下一個測驗項目 + setCurrentTestItemIndex(prev => prev + 1); + + console.log(`🔍 記錄測驗結果:`, { + word: currentCard.word, + testType: mode, + isCorrect, + completedTests: updatedSession.completedTests.length, + plannedTests: updatedSession.plannedTests.length, + isCardCompleted: updatedSession.isCompleted + }); + + // 如果詞卡的所有測驗都完成了,觸發完整復習邏輯 + if (updatedSession.isCompleted) { + console.log(`✅ 詞卡 ${currentCard.word} 的所有測驗已完成,準備提交復習結果`); + completeCardReview(updatedSession); + } + } + + // 完成詞卡複習並提交到後端 + const completeCardReview = async (session: CardReviewSession) => { try { - const result = await flashcardsService.submitReview(currentCard.id, { - isCorrect, - confidenceLevel, - questionType: mode, - userAnswer, - timeTaken: Date.now() - Date.now() // 簡化時間計算 + // 計算綜合表現指標 + const correctCount = session.completedTests.filter(t => t.isCorrect).length; + const totalTests = session.completedTests.length; + const accuracy = totalTests > 0 ? correctCount / totalTests : 0; + + // 計算平均信心等級(用於翻卡記憶測驗) + const confidenceTests = session.completedTests.filter(t => t.confidenceLevel !== undefined); + const avgConfidence = confidenceTests.length > 0 + ? confidenceTests.reduce((sum, t) => sum + (t.confidenceLevel || 3), 0) / confidenceTests.length + : 3; + + // 計算平均答題時間 + const avgResponseTime = session.completedTests.reduce((sum, t) => sum + t.responseTimeMs, 0) / totalTests; + + // 確定主要測驗類型(用於後端SM2算法) + const primaryTestType = session.completedTests[0]?.testType || 'flip-memory'; + + console.log(`🔥 提交詞卡完整復習結果:`, { + word: session.word, + accuracy: `${Math.round(accuracy * 100)}%`, + avgConfidence, + avgResponseTime: `${avgResponseTime}ms`, + primaryTestType, + completedTests: session.completedTests.length + }); + + // 提交到後端 + const result = await flashcardsService.submitReview(session.cardId, { + isCorrect: accuracy >= 0.7, // 70%以上正確率視為通過 + confidenceLevel: Math.round(avgConfidence), + questionType: primaryTestType, + userAnswer: `綜合${totalTests}個測驗,正確率${Math.round(accuracy * 100)}%`, + timeTaken: Math.round(avgResponseTime) }); if (result.success && result.data) { - console.log('復習結果提交成功:', result.data); - // 更新卡片的熟悉度等資訊,但不觸發卡片重新載入 - setCurrentCard(prev => prev ? { - ...prev, - masteryLevel: result.data!.masteryLevel, - nextReviewDate: result.data!.nextReviewDate - } : null); + console.log('✅ 詞卡復習結果提交成功:', result.data); + + // 更新詞卡的熟悉度等資訊 + if (currentCard && currentCard.id === session.cardId) { + setCurrentCard(prev => prev ? { + ...prev, + masteryLevel: result.data!.masteryLevel, + nextReviewDate: result.data!.nextReviewDate + } : null); + } + + // 增加已完成詞卡數量 + setCompletedCards(prev => prev + 1); + + console.log(`🎉 詞卡 ${session.word} 復習完成!新熟悉度: ${result.data.masteryLevel}%, 下次復習: ${result.data.nextReviewDate}`); } else { - console.log('復習結果提交失敗,繼續運行'); + console.error('詞卡復習結果提交失敗:', result.error); } - // 更新測驗進度(無論提交成功或失敗) - setCompletedTests(prev => { - const newCompleted = prev + 1; - console.log(`📈 測驗進度更新: ${newCompleted}/${totalTests} (${Math.round((newCompleted/totalTests)*100)}%)`); - return newCompleted; - }); - } catch (error) { - console.error('提交復習結果失敗:', error); - // 即使出錯也更新進度,避免卡住 - setCompletedTests(prev => prev + 1); + console.error('完成詞卡復習時發生錯誤:', error); } } @@ -636,8 +941,8 @@ export default function LearnPage() { total: prev.total + 1 })) - // 提交復習結果 - await submitReviewResult(isCorrect, fillAnswer); + // 記錄測驗結果 + recordTestResult(isCorrect, fillAnswer); } const handleListeningAnswer = async (answer: string) => { @@ -652,8 +957,8 @@ export default function LearnPage() { total: prev.total + 1 })) - // 提交復習結果 - await submitReviewResult(isCorrect, answer); + // 記錄測驗結果 + recordTestResult(isCorrect, answer); } const handleSpeakingAnswer = async (transcript: string) => { @@ -667,8 +972,8 @@ export default function LearnPage() { total: prev.total + 1 })) - // 提交復習結果 - await submitReviewResult(isCorrect, transcript); + // 記錄測驗結果 + recordTestResult(isCorrect, transcript); } const handleSentenceListeningAnswer = async (answer: string) => { @@ -683,8 +988,8 @@ export default function LearnPage() { total: prev.total + 1 })) - // 提交復習結果 - await submitReviewResult(isCorrect, answer); + // 記錄測驗結果 + recordTestResult(isCorrect, answer); } const handleReportSubmit = () => { @@ -701,6 +1006,8 @@ export default function LearnPage() { setScore({ correct: 0, total: 0 }) setCompletedTests(0) setTotalTests(0) + setTestItems([]) + setCurrentTestItemIndex(0) setShowComplete(false) setShowNoDueCards(false) await loadDueCards(); // 重新載入到期詞卡 @@ -787,173 +1094,78 @@ export default function LearnPage() {
{/* Progress Bar */}
-
- 進度 -
- - {completedTests} / {totalTests} 測驗 - - - 詞卡 {currentCardIndex + 1}/{dueCards.length} - -
- {score.correct} +
+ 學習進度 +
+ {/* 詞卡進度 */} +
+ 詞卡: + {completedCards} / - {score.total} - {score.total > 0 && ( - - ({Math.round((score.correct / score.total) * 100)}%) + {dueCards.length} + {dueCards.length > 0 && ( + + ({Math.round((completedCards / dueCards.length) * 100)}%) )}
-
-
-
-
0 ? (completedTests / totalTests) * 100 : 0}%` }} - >
-
-
- {/* Smart Review Information Panel */} -
-

🧠 CEFR智能複習系統

- -
-
-
當前情境
-
- {currentCard && (() => { - const userCEFRLevel = localStorage.getItem('userEnglishLevel') || 'A2'; - const wordCEFRLevel = currentCard.difficultyLevel || 'A2'; - const userLevel = getCEFRToLevel(userCEFRLevel); - const wordLevel = getCEFRToLevel(wordCEFRLevel); - const difficulty = wordLevel - userLevel; - - if (userCEFRLevel === 'A1') return 'A1學習者'; - if (difficulty < -10) return '簡單詞彙'; - if (difficulty >= -10 && difficulty <= 10) return '適中詞彙'; - return '困難詞彙'; - })()} -
-
-
-
學習者等級
-
{localStorage.getItem('userEnglishLevel') || 'A2'}
-
-
-
詞彙等級
-
{currentCard?.difficultyLevel || 'A2'}
-
-
-
等級差異
-
- {currentCard ? (() => { - const userCEFR = localStorage.getItem('userEnglishLevel') || 'A2'; - const wordCEFR = currentCard.difficultyLevel || 'A2'; - const diff = getCEFRToLevel(wordCEFR) - getCEFRToLevel(userCEFR); - return diff > 0 ? `+${diff}` : diff.toString(); - })() : '--'} -
+ {/* 測驗進度 */} +
- {/* 當前選擇突出顯示 */} -
-
-
- {currentCard && (() => { - const userCEFR = localStorage.getItem('userEnglishLevel') || 'A2'; - const wordCEFR = currentCard.difficultyLevel || 'A2'; - const context = getCurrentContext(userCEFR, wordCEFR); - const contextData = generateContextTable(userCEFR, wordCEFR).find(c => c.isCurrent); - - return ( - <> -
- - 當前情境: {contextData?.icon} {context} - -
- 可用題型: {contextData?.reviewTypes.join(' | ')} -
-
- - ); - })()} -
-
-
系統已選擇
-
- {getModeIcon(mode)} - {getModeLabel(mode)} -
+ {/* 雙層進度條 */} +
+ {/* 詞卡進度條 */} +
+ 詞卡 +
+
0 ? (completedCards / dueCards.length) * 100 : 0}%` }} + >
+ + {dueCards.length > 0 ? Math.round((completedCards / dueCards.length) * 100) : 0}% +
-
- {/* 完整四情境對照表 */} -
-
📚 智能複習四情境對照表
- -
- - - - - - - - - - - - {currentCard && (() => { - const userCEFR = localStorage.getItem('userEnglishLevel') || 'A2'; - const wordCEFR = currentCard.difficultyLevel || 'A2'; - const tableData = generateContextTable(userCEFR, wordCEFR); - - return tableData.map((row, index) => ( - - - - - - - - )); - })()} - -
情境類型建議複習方式學習目的判斷條件狀態
- - {row.icon} {row.type} - - -
- {row.reviewTypes.map((type, idx) => ( - {type} - ))} -
-
{row.purpose}{row.condition} - {row.isCurrent && ← 目前} -
+ {/* 測驗進度條 */} +
+ 測驗 +
setShowTaskListModal(true)} + title="點擊查看詳細任務清單" + > +
0 ? (completedTests / totalTests) * 100 : 0}%` }} + >
+
+ + {totalTests > 0 ? Math.round((completedTests / totalTests) * 100) : 0}% +
- {/* Current Card Mastery Level */} - {currentCard?.baseMasteryLevel && currentCard?.lastReviewDate && ( -
- -
- )} - {mode === 'flip-memory' ? ( /* Flip Card Mode */
@@ -1885,6 +2097,111 @@ export default function LearnPage() {
)} + {/* Task List Modal */} + {showTaskListModal && ( +
+
+ {/* Header */} +
+

+ 📚 學習任務清單 +

+ +
+ + {/* Content */} +
+ {/* 進度統計 */} +
+
+ + 測驗進度: {completedTests} / {totalTests} ({totalTests > 0 ? Math.round((completedTests / totalTests) * 100) : 0}%) + +
+ ✅ 已完成: {testItems.filter(item => item.isCompleted).length} + ⏳ 進行中: {testItems.filter(item => item.isCurrent).length} + ⚪ 待完成: {testItems.filter(item => !item.isCompleted && !item.isCurrent).length} +
+
+
+
0 ? (completedTests / totalTests) * 100 : 0}%` }} + >
+
+
+ + {/* 任務清單 */} +
+ {groupTestItemsByCard(testItems).map((cardGroup, cardIndex) => ( +
+ {/* 詞卡標題 */} +
+ + 詞卡{cardIndex + 1}: {cardGroup.word} + + + {cardGroup.context} + + + {cardGroup.tests.length}個測驗 + +
+ + {/* 測驗項目 */} +
+ {cardGroup.tests.map(test => ( +
+ {/* 狀態圖標 */} + + {test.isCompleted ? '✅' : test.isCurrent ? '⏳' : '⚪'} + + + {/* 測驗資訊 */} +
+
+ {test.order}. {test.testName} +
+
+ {test.isCompleted ? '已完成' : + test.isCurrent ? '進行中' : '待完成'} +
+
+
+ ))} +
+
+ ))} +
+ + {testItems.length === 0 && ( +
+
📚
+

還沒有生成任務清單

+
+ )} +
+
+
+ )} + {/* Complete Modal */} {showComplete && (
)} +