dramaling-vocab-learning/note/複習系統/技術實作規格.md

8.1 KiB
Raw Blame History

複習系統技術實作規格書

版本: 1.0 對應PRD: 產品需求規格.md 目標讀者: 開發者、技術主管 最後更新: 2025-10-03


📊 核心算法和公式

進度條計算公式

進度百分比 = (今日完成) / (今日完成 + 今日到期) * 100

實作範例:

const progressPercentage = completedToday / (completedToday + dueToday) * 100

複習間隔算法

下一次複習時間 = 當前時間 + (2^成功複習次數) 

實作範例:

const nextReviewDate = new Date()
nextReviewDate.setDate(nextReviewDate.getDate() + Math.pow(2, successCount))

🏷️ 卡片狀態管理機制

延遲註記系統

新增延遲註記的情況:

  1. 用戶點選 "跳過" → 卡片標記 isDelayed: true
  2. 用戶答錯題目 → 卡片標記 isDelayed: true

延遲註記的影響:

// 撈取下一個複習卡片時排除延遲卡片
const nextCards = allCards.filter(card => !card.isDelayed)

消除延遲註記:

// 用戶答對題目時
if (isCorrect) {
  card.isDelayed = false
  card.successCount++
  card.nextReviewDate = calculateNextReviewDate(card.successCount)
}

🎯 測驗模式實作

階段1: 翻卡記憶 (已實作)

數據結構:

interface FlashcardData {
  id: string
  word: string
  definition: string
  example: string
  exampleTranslation: string
  pronunciation: string
  synonyms: string[]
  cefr: string
}

狀態管理:

const [isFlipped, setIsFlipped] = useState(false)
const [selectedConfidence, setSelectedConfidence] = useState<number | null>(null)
const [cardHeight, setCardHeight] = useState<number>(400)

信心度評分映射:

const confidenceToScore = {
  1: 0,    // 完全不懂 → 答錯
  2: 0,    // 模糊 → 答錯
  3: 1,    // 一般 → 答對
  4: 1,    // 熟悉 → 答對
  5: 1     // 非常熟悉 → 答對
}

階段2: 詞彙選擇題 (計劃中)

選項生成算法:

// 暫時使用固定選項 (MVP階段)
const mockOptions = ['apple', 'orange', 'banana', correctAnswer]
const shuffledOptions = shuffle(mockOptions)

未來升級方案:

// 基於詞性和CEFR等級生成干擾項
const generateDistractors = (correctWord, allWords) => {
  const samePOS = allWords.filter(w => w.partOfSpeech === correctWord.partOfSpeech)
  const sameCEFR = allWords.filter(w => w.cefr === correctWord.cefr)
  return selectRandom([...samePOS, ...sameCEFR], 3)
}

🎨 UI/UX實作規格

翻卡動畫參數

/* 3D翻卡動畫 - 已調校的完美參數 */
.flip-card {
  perspective: 1000px;
  transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}

.flip-card.flipped {
  transform: rotateY(180deg);
}

響應式高度計算

const calculateCardHeight = () => {
  const backHeight = backRef.current?.scrollHeight || 0
  const minHeight = window.innerWidth <= 480 ? 300 :
                   window.innerWidth <= 768 ? 350 : 400
  return Math.max(minHeight, backHeight)
}

信心度按鈕配色

const confidenceColors = {
  1: 'bg-red-100 text-red-700 border-red-200',      // 完全不懂
  2: 'bg-orange-100 text-orange-700 border-orange-200', // 模糊
  3: 'bg-yellow-100 text-yellow-700 border-yellow-200', // 一般
  4: 'bg-blue-100 text-blue-700 border-blue-200',   // 熟悉
  5: 'bg-green-100 text-green-700 border-green-200'  // 非常熟悉
}

📱 API設計規格 (階段3實作)

獲取複習卡片API

GET /api/flashcards/due?limit=10

Response:
{
  "success": true,
  "data": {
    "flashcards": FlashcardData[],
    "count": number
  },
  "timestamp": string
}

記錄複習結果API

POST /api/flashcards/{id}/review

Request:
{
  "confidence": number,  // 1-5
  "isCorrect": boolean,  // 基於confidence >= 3判斷
  "reviewType": "flip-memory",
  "responseTimeMs": number
}

Response:
{
  "success": boolean,
  "nextReviewDate": string,
  "newSuccessCount": number
}

🗄️ 數據存儲設計

階段1: 內存狀態 (當前)

// 純React狀態會話結束即消失
const [currentIndex, setCurrentIndex] = useState(0)
const [score, setScore] = useState({ correct: 0, total: 0 })

階段2: 本地存儲 (計劃)

// localStorage持久化
const saveProgress = (progress: ReviewProgress) => {
  localStorage.setItem('review-progress', JSON.stringify({
    currentIndex,
    score,
    timestamp: Date.now()
  }))
}

階段3: 後端同步 (遠期)

// 與後端API同步
const syncProgress = async (progress: ReviewProgress) => {
  await api.post('/api/user/review-progress', progress)
}

🔒 業務邏輯約束

狀態轉換規則

// 卡片狀態轉換
enum CardState {
  PENDING = 'pending',     // 未開始
  COMPLETED = 'completed', // 已完成
  DELAYED = 'delayed'      // 延遲 (跳過或答錯)
}

// 狀態轉換邏輯
const handleAnswer = (confidence: number) => {
  if (confidence >= 3) {
    // 答對: 移除延遲標記,標記完成
    card.state = CardState.COMPLETED
    card.isDelayed = false
    card.successCount++
  } else {
    // 答錯: 添加延遲標記
    card.isDelayed = true
    card.state = CardState.DELAYED
  }
}

隊列管理邏輯

// 下一張卡片選擇邏輯
const getNextCard = (cards: FlashCard[]) => {
  // 1. 優先選擇未延遲的卡片
  const normalCards = cards.filter(c => !c.isDelayed && c.state === CardState.PENDING)
  if (normalCards.length > 0) {
    return normalCards[0]
  }

  // 2. 如果沒有正常卡片,選擇延遲卡片
  const delayedCards = cards.filter(c => c.isDelayed)
  return delayedCards[0] || null
}

性能和優化規格

載入性能要求

// 性能指標
const PERFORMANCE_TARGETS = {
  INITIAL_LOAD: 2000,      // 初始載入 < 2秒
  CARD_FLIP: 500,          // 翻卡響應 < 500ms
  CONFIDENCE_SELECT: 200,  // 按鈕響應 < 200ms
  NAVIGATION: 300          // 頁面切換 < 300ms
}

記憶體管理

// 避免記憶體洩漏
useEffect(() => {
  const timer = setTimeout(updateHeight, 100)
  const resizeHandler = () => updateHeight()

  window.addEventListener('resize', resizeHandler)

  return () => {
    clearTimeout(timer)
    window.removeEventListener('resize', resizeHandler)
  }
}, [dependencies])

🧪 測試規格 (TDD Implementation)

測試覆蓋要求

  • 核心邏輯: 100% 測試覆蓋
  • UI組件: 關鍵交互測試
  • API調用: Mock測試
  • 狀態管理: 狀態轉換測試

測試案例範例

// 翻卡邏輯測試
describe('FlipCard Component', () => {
  test('應該在點擊時切換翻轉狀態', () => {
    const { result } = renderHook(() => useFlipCard())

    act(() => result.current.flip())
    expect(result.current.isFlipped).toBe(true)
  })

  test('應該在選擇信心度時觸發提交', () => {
    const onSubmit = jest.fn()
    const card = render(<FlipCard onSubmit={onSubmit} />)

    card.selectConfidence(4)
    expect(onSubmit).toHaveBeenCalledWith(4)
  })
})

📋 實作檢查清單

每個功能完成時檢查

  • 功能符合產品需求規格
  • 遵循技術約束和算法
  • 通過所有相關測試
  • 性能指標達標
  • 無記憶體洩漏
  • 錯誤處理完善

代碼品質標準

  • TypeScript 無錯誤
  • ESLint 無警告
  • 組件 < 200行
  • 函數 < 20行
  • 嵌套層次 < 3層

技術實作規格維護者: 開發團隊 版本控制: 與產品需求規格同步更新 目的: 確保實作準確性,避免開發失控