import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { VocabularyReviewData, ReviewSession, ReviewResponse, WeaknessPattern } from '@/utils/spacedRepetition' import { SpacedRepetitionAlgorithm, createDefaultVocabularyReviewData } from '@/utils/spacedRepetition' export interface LearningPlan { date: string vocabulary: VocabularyReviewData[] totalCount: number estimatedTime: number // 分鐘 } export interface ReviewStats { todayCompleted: number todayTotal: number weeklyStreak: number totalMastered: number averageAccuracy: number improvementTrend: number nextReviewTime: Date | null } export const useReviewStore = defineStore('review', () => { // 狀態 const vocabularyReviewData = ref>(new Map()) const reviewHistory = ref([]) const currentReviewSession = ref(null) const learningPlan = ref>(new Map()) const isLoading = ref(false) const algorithm = new SpacedRepetitionAlgorithm() // 計算屬性 const todaysReviewVocabulary = computed(() => { const allVocabulary = Array.from(vocabularyReviewData.value.values()) return SpacedRepetitionAlgorithm.getTodaysReviewVocabulary(allVocabulary) }) const reviewStats = computed((): ReviewStats => { const todayTotal = todaysReviewVocabulary.value.length const todayCompleted = reviewHistory.value.filter(session => { const today = new Date() today.setHours(0, 0, 0, 0) const sessionDate = new Date(session.startTime) sessionDate.setHours(0, 0, 0, 0) return sessionDate.getTime() === today.getTime() }).length const allVocabulary = Array.from(vocabularyReviewData.value.values()) const totalMastered = allVocabulary.filter(v => v.masteryLevel >= 80).length const efficiency = SpacedRepetitionAlgorithm.analyzeLearningEfficiency(reviewHistory.value) // 計算連續學習天數 const weeklyStreak = calculateWeeklyStreak() // 下次複習時間 const nextReviewTime = getNextReviewTime() return { todayCompleted, todayTotal, weeklyStreak, totalMastered, averageAccuracy: efficiency.averageAccuracy, improvementTrend: efficiency.improvementTrend, nextReviewTime } }) const urgentReviewVocabulary = computed(() => { const today = new Date() return todaysReviewVocabulary.value.filter(vocab => { const overdueDays = Math.floor((today.getTime() - vocab.nextReviewDate.getTime()) / (24 * 60 * 60 * 1000)) return overdueDays > 2 // 過期超過2天視為緊急 }) }) const weaknessAnalysis = computed(() => { const allPatterns = new Map() Array.from(vocabularyReviewData.value.values()).forEach(vocab => { vocab.weaknessPatterns.forEach(pattern => { const existing = allPatterns.get(pattern.type) || { severity: 0, frequency: 0 } allPatterns.set(pattern.type, { severity: Math.max(existing.severity, pattern.severity), frequency: existing.frequency + pattern.frequency }) }) }) return Array.from(allPatterns.entries()) .map(([type, data]) => ({ type, severity: data.severity, frequency: data.frequency, score: data.severity * Math.log(data.frequency + 1) })) .sort((a, b) => b.score - a.score) .slice(0, 5) // 前5個最嚴重的薄弱點 }) // 方法 const initializeVocabularyReviewData = (vocabularyIds: string[]) => { vocabularyIds.forEach(id => { if (!vocabularyReviewData.value.has(id)) { vocabularyReviewData.value.set(id, createDefaultVocabularyReviewData(id)) } }) } const startReviewSession = (vocabularyIds: string[]): string => { const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` currentReviewSession.value = { vocabularyId: vocabularyIds[0], // 如果是批量複習,這裡需要調整 startTime: new Date(), responses: [], overallAccuracy: 0, averageResponseTime: 0 } return sessionId } const addReviewResponse = (response: Omit) => { if (!currentReviewSession.value) { throw new Error('沒有活躍的複習會話') } const fullResponse: ReviewResponse = { ...response, timestamp: new Date() } currentReviewSession.value.responses.push(fullResponse) // 更新會話統計 updateSessionStats() } const completeReviewSession = () => { if (!currentReviewSession.value) { throw new Error('沒有活躍的複習會話') } currentReviewSession.value.endTime = new Date() // 更新複習數據 const reviewData = vocabularyReviewData.value.get(currentReviewSession.value.vocabularyId) if (reviewData) { const updatedData = algorithm.calculateNextReview(reviewData, currentReviewSession.value) vocabularyReviewData.value.set(reviewData.id, updatedData) } // 保存到歷史記錄 reviewHistory.value.push({ ...currentReviewSession.value }) // 清空當前會話 currentReviewSession.value = null // 重新生成學習計劃 generateLearningPlan() } const updateSessionStats = () => { if (!currentReviewSession.value) return const responses = currentReviewSession.value.responses const correctCount = responses.filter(r => r.isCorrect).length const totalResponseTime = responses.reduce((sum, r) => sum + r.responseTime, 0) currentReviewSession.value.overallAccuracy = responses.length > 0 ? correctCount / responses.length : 0 currentReviewSession.value.averageResponseTime = responses.length > 0 ? totalResponseTime / responses.length : 0 } const generateLearningPlan = (daysAhead: number = 7) => { const allVocabulary = Array.from(vocabularyReviewData.value.values()) const planMap = SpacedRepetitionAlgorithm.generateLearningPlan(allVocabulary, daysAhead) learningPlan.value.clear() planMap.forEach((vocabularyList, date) => { const estimatedTime = vocabularyList.length * 2 // 每個詞彙平均2分鐘 learningPlan.value.set(date, { date, vocabulary: vocabularyList, totalCount: vocabularyList.length, estimatedTime }) }) } const calculateWeeklyStreak = (): number => { if (reviewHistory.value.length === 0) return 0 const today = new Date() let streak = 0 // 從今天開始往前檢查 for (let i = 0; i < 7; i++) { const checkDate = new Date(today) checkDate.setDate(today.getDate() - i) checkDate.setHours(0, 0, 0, 0) const nextDay = new Date(checkDate) nextDay.setDate(checkDate.getDate() + 1) const hasReviewOnDate = reviewHistory.value.some(session => { const sessionDate = new Date(session.startTime) return sessionDate >= checkDate && sessionDate < nextDay }) if (hasReviewOnDate) { streak++ } else if (i > 0) { // 今天沒複習不算打斷,其他天沒複習就算打斷 break } } return streak } const getNextReviewTime = (): Date | null => { const allVocabulary = Array.from(vocabularyReviewData.value.values()) if (allVocabulary.length === 0) return null const nextReviews = allVocabulary .filter(v => v.nextReviewDate > new Date()) .sort((a, b) => a.nextReviewDate.getTime() - b.nextReviewDate.getTime()) return nextReviews.length > 0 ? nextReviews[0].nextReviewDate : null } const getVocabularyReviewData = (vocabularyId: string): VocabularyReviewData | null => { return vocabularyReviewData.value.get(vocabularyId) || null } const updateVocabularyReviewData = (data: VocabularyReviewData) => { vocabularyReviewData.value.set(data.id, data) } const resetVocabularyProgress = (vocabularyId: string) => { const defaultData = createDefaultVocabularyReviewData(vocabularyId) vocabularyReviewData.value.set(vocabularyId, defaultData) } const getPersonalizedRecommendations = (): string[] => { const recommendations: string[] = [] const stats = reviewStats.value // 基於統計數據生成建議 if (stats.averageAccuracy < 0.7) { recommendations.push('建議放慢學習節奏,專注於理解而不是數量') } if (stats.weeklyStreak === 0) { recommendations.push('建立每日複習習慣,即使只複習5個詞彙也有幫助') } if (urgentReviewVocabulary.value.length > 10) { recommendations.push('有較多詞彙需要緊急複習,建議優先處理過期詞彙') } if (stats.improvementTrend < 0) { recommendations.push('學習效果有下降趨勢,建議調整學習策略或休息一下') } // 基於薄弱點生成建議 const topWeakness = weaknessAnalysis.value[0] if (topWeakness) { const weaknessRecommendations = { spelling: '建議加強拼寫練習,可以嘗試手寫練習', meaning: '建議多做詞義辨析練習,建立詞彙語義網絡', pronunciation: '建議多聽音頻,模仿正確發音', usage: '建議多閱讀例句,理解詞彙在不同語境中的用法', grammar: '建議複習相關語法規則,理解詞彙的語法功能' } recommendations.push(weaknessRecommendations[topWeakness.type as keyof typeof weaknessRecommendations]) } return recommendations.slice(0, 3) // 最多返回3個建議 } const exportReviewData = () => { return { vocabularyReviewData: Object.fromEntries(vocabularyReviewData.value), reviewHistory: reviewHistory.value, exportDate: new Date().toISOString() } } const importReviewData = (data: any) => { if (data.vocabularyReviewData) { vocabularyReviewData.value = new Map(Object.entries(data.vocabularyReviewData)) } if (data.reviewHistory) { reviewHistory.value = data.reviewHistory.map((session: any) => ({ ...session, startTime: new Date(session.startTime), endTime: session.endTime ? new Date(session.endTime) : undefined, responses: session.responses.map((response: any) => ({ ...response, timestamp: new Date(response.timestamp) })) })) } generateLearningPlan() } // 初始化 generateLearningPlan() return { // 狀態 vocabularyReviewData, reviewHistory, currentReviewSession, learningPlan, isLoading, // 計算屬性 todaysReviewVocabulary, reviewStats, urgentReviewVocabulary, weaknessAnalysis, // 方法 initializeVocabularyReviewData, startReviewSession, addReviewResponse, completeReviewSession, generateLearningPlan, getVocabularyReviewData, updateVocabularyReviewData, resetVocabularyProgress, getPersonalizedRecommendations, exportReviewData, importReviewData } })