135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
import { flashcardsService } from '@/lib/services/flashcards'
|
|
import { ExtendedFlashcard, TestItem } from '@/store/useReviewStore'
|
|
|
|
// 複習會話服務
|
|
export class ReviewService {
|
|
// 載入到期詞卡
|
|
static async loadDueCards(limit = 50): Promise<ExtendedFlashcard[]> {
|
|
try {
|
|
const result = await flashcardsService.getDueFlashcards(limit)
|
|
|
|
if (result.success && result.data) {
|
|
return result.data
|
|
} else {
|
|
throw new Error(result.error || '載入詞卡失敗')
|
|
}
|
|
} catch (error) {
|
|
console.error('載入到期詞卡失敗:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// 載入已完成的測驗
|
|
static async loadCompletedTests(cardIds: string[]): Promise<any[]> {
|
|
try {
|
|
const result = await flashcardsService.getCompletedTests(cardIds)
|
|
|
|
if (result.success && result.data) {
|
|
return result.data
|
|
} else {
|
|
console.warn('載入已完成測驗失敗:', result.error)
|
|
return []
|
|
}
|
|
} catch (error) {
|
|
console.error('載入已完成測驗異常:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
// 記錄測驗結果
|
|
static async recordTestResult(params: {
|
|
flashcardId: string
|
|
testType: string
|
|
isCorrect: boolean
|
|
userAnswer?: string
|
|
confidenceLevel?: number
|
|
responseTimeMs?: number
|
|
}): Promise<boolean> {
|
|
try {
|
|
const result = await flashcardsService.recordTestCompletion({
|
|
...params,
|
|
responseTimeMs: params.responseTimeMs || 2000
|
|
})
|
|
|
|
if (result.success) {
|
|
return true
|
|
} else {
|
|
console.error('記錄測驗結果失敗:', result.error)
|
|
return false
|
|
}
|
|
} catch (error) {
|
|
console.error('記錄測驗結果異常:', error)
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 生成測驗選項
|
|
static async generateTestOptions(
|
|
cardId: string,
|
|
testType: string,
|
|
count = 4
|
|
): Promise<string[]> {
|
|
try {
|
|
// 這裡可以呼叫後端API生成選項
|
|
// 或者使用本地邏輯生成
|
|
|
|
// 暫時使用簡單的佔位符邏輯
|
|
return Array.from({ length: count }, (_, i) => `選項 ${i + 1}`)
|
|
} catch (error) {
|
|
console.error('生成測驗選項失敗:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
// 驗證學習會話完整性
|
|
static validateSession(
|
|
cards: ExtendedFlashcard[],
|
|
testItems: TestItem[]
|
|
): { isValid: boolean; errors: string[] } {
|
|
const errors: string[] = []
|
|
|
|
// 檢查詞卡是否存在
|
|
if (!cards || cards.length === 0) {
|
|
errors.push('沒有可用的詞卡')
|
|
}
|
|
|
|
// 檢查測驗項目
|
|
if (!testItems || testItems.length === 0) {
|
|
errors.push('沒有可用的測驗項目')
|
|
}
|
|
|
|
// 檢查測驗項目和詞卡的一致性
|
|
if (cards && testItems) {
|
|
const cardIds = new Set(cards.map(c => c.id))
|
|
const testCardIds = new Set(testItems.map(t => t.cardId))
|
|
|
|
for (const testCardId of testCardIds) {
|
|
if (!cardIds.has(testCardId)) {
|
|
errors.push(`測驗項目引用了不存在的詞卡: ${testCardId}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
isValid: errors.length === 0,
|
|
errors
|
|
}
|
|
}
|
|
|
|
// 計算學習統計
|
|
static calculateStats(testItems: TestItem[], score: { correct: number; total: number }) {
|
|
const completed = testItems.filter(item => item.isCompleted).length
|
|
const total = testItems.length
|
|
const progressPercentage = total > 0 ? (completed / total) * 100 : 0
|
|
const accuracyPercentage = score.total > 0 ? (score.correct / score.total) * 100 : 0
|
|
|
|
return {
|
|
completed,
|
|
total,
|
|
remaining: total - completed,
|
|
progressPercentage: Math.round(progressPercentage),
|
|
accuracyPercentage: Math.round(accuracyPercentage),
|
|
estimatedTimeRemaining: Math.max(0, (total - completed) * 30) // 假設每個測驗30秒
|
|
}
|
|
}
|
|
} |