feat: 建立開發/正式版本分離的進度組件架構
## 組件重組 - 創建 QuizProgressDev.tsx:給 /review-dev 使用,保留完整開發功能 - 修改 QuizProgress.tsx:給 /review 使用,移除開發測試資訊 - 頁面獨立:兩個頁面使用不同組件,互不影響 ## TypeScript 修復 - 完善 CardState interface 類型兼容性 - 修復 primaryImageUrl, updatedAt, exampleTranslation 類型匹配 - 確保所有必需屬性都有預設值 ## 頁面功能 - /review: 使用簡化版 QuizProgress,適合正式使用 - /review-dev: 使用完整版 QuizProgressDev,保留所有調試功能 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
473ecf4508
commit
d0b6e9e757
|
|
@ -3,7 +3,7 @@
|
|||
import { Navigation } from '@/components/shared/Navigation'
|
||||
import { FlipMemory } from '@/components/review/quiz/FlipMemory'
|
||||
import { VocabChoiceQuiz } from '@/components/review/quiz/VocabChoiceQuiz'
|
||||
import { QuizProgress } from '@/components/review/ui/QuizProgress'
|
||||
import { QuizProgressDev } from '@/components/review/ui/QuizProgressDev'
|
||||
import { QuizResult } from '@/components/review/quiz/QuizResult'
|
||||
import { useReviewSession } from '@/hooks/review/useReviewSession'
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ export default function ReviewDevPage() {
|
|||
<div className="py-8">
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
{/* 使用修改後的 SimpleProgress 組件 */}
|
||||
<QuizProgress
|
||||
<QuizProgressDev
|
||||
currentQuizItem={currentQuizItem}
|
||||
totalQuizItems={totalQuizItems}
|
||||
completedQuizItems={completedQuizItems}
|
||||
|
|
|
|||
|
|
@ -114,13 +114,12 @@ export default function ReviewPage() {
|
|||
|
||||
<div className="py-8">
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
{/* 使用修改後的 SimpleProgress 組件 */}
|
||||
{/* 使用簡化的 ReviewProgress 組件 */}
|
||||
<QuizProgress
|
||||
currentQuizItem={currentQuizItem}
|
||||
totalQuizItems={totalQuizItems}
|
||||
completedQuizItems={completedQuizItems}
|
||||
score={score}
|
||||
quizItems={quizItems}
|
||||
/>
|
||||
|
||||
{/* 根據當前測驗項目類型渲染對應組件 */}
|
||||
|
|
|
|||
|
|
@ -22,23 +22,7 @@ export function QuizProgress({ currentQuizItem, totalQuizItems, completedQuizIte
|
|||
return (
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-600">線性複習進度</span>
|
||||
{currentQuizItem && (
|
||||
<div className="flex items-center mt-1">
|
||||
<span className="text-lg mr-2">
|
||||
{currentQuizItem.quizType === 'flip-card' ? '🔄' : '🎯'}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
{currentQuizItem.quizType === 'flip-card' ? '翻卡記憶' : '詞彙選擇'} • {currentQuizItem.cardData.word}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm text-right">
|
||||
<span className="text-gray-600">
|
||||
{completedQuizItems}/{totalQuizItems} 項目
|
||||
</span>
|
||||
{score.total > 0 && (
|
||||
<span className="text-green-600 font-medium">
|
||||
準確率 {accuracy}%
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import { QuizItem } from '@/lib/data/reviewSimpleData'
|
||||
|
||||
interface ReviewProgressProps {
|
||||
currentQuizItem?: QuizItem
|
||||
totalQuizItems: number
|
||||
completedQuizItems: number
|
||||
score: { correct: number; total: number }
|
||||
}
|
||||
|
||||
export function QuizProgressDev({ currentQuizItem, totalQuizItems, completedQuizItems, score }: ReviewProgressProps) {
|
||||
const progress = (completedQuizItems / totalQuizItems) * 100
|
||||
const accuracy = score.total > 0 ? Math.round((score.correct / score.total) * 100) : 0
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-600">複習進度</span>
|
||||
{currentQuizItem && (
|
||||
<div className="flex items-center mt-1">
|
||||
<span className="text-lg mr-2">
|
||||
{currentQuizItem.quizType === 'flip-card' ? '🔄' : '🎯'}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
{currentQuizItem.quizType === 'flip-card' ? '翻卡記憶' : '詞彙選擇'} • {currentQuizItem.cardData.word}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm text-right">
|
||||
<span className="text-gray-600">
|
||||
{completedQuizItems}/{totalQuizItems} 項目
|
||||
</span>
|
||||
{score.total > 0 && (
|
||||
<span className="text-green-600 font-medium">
|
||||
準確率 {accuracy}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 進度條 */}
|
||||
<div className="w-full bg-gray-200 rounded-full h-3">
|
||||
<div
|
||||
className="bg-blue-500 h-3 rounded-full transition-all duration-300"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 基本統計 */}
|
||||
<div className="flex justify-center gap-4 mt-3 text-sm">
|
||||
{score.total > 0 && (
|
||||
<>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full"></span>
|
||||
<span className="text-green-700">答對 {score.correct}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="w-2 h-2 bg-red-500 rounded-full"></span>
|
||||
<span className="text-red-700">答錯 {score.total - score.correct}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
import { useReducer, useEffect, useMemo, useState } from 'react'
|
||||
import { flashcardsService, Flashcard } from '@/lib/services/flashcards'
|
||||
|
||||
// 重新定義所需的介面
|
||||
// 重新定義所需的介面,確保與 reviewSimpleData.CardState 完全兼容
|
||||
interface CardState extends Flashcard {
|
||||
skipCount: number
|
||||
wrongCount: number
|
||||
isCompleted: boolean
|
||||
originalOrder: number
|
||||
synonyms?: string[]
|
||||
difficultyLevelNumeric: number // 添加缺少的屬性
|
||||
synonyms: string[] // 改為必需的數組
|
||||
difficultyLevelNumeric: number // 必需屬性
|
||||
exampleTranslation: string // 覆蓋 Flashcard 的可選定義,改為必需
|
||||
updatedAt: string // 覆蓋 Flashcard 的可選定義,改為必需
|
||||
primaryImageUrl: string | null // 確保類型匹配(null 而非 undefined)
|
||||
}
|
||||
|
||||
interface QuizItem {
|
||||
|
|
@ -89,16 +92,18 @@ const generateQuizItemsFromFlashcards = (flashcards: Flashcard[]): QuizItem[] =>
|
|||
let order = 0
|
||||
|
||||
flashcards.forEach((card) => {
|
||||
// 轉換 Flashcard 為 CardState 格式
|
||||
// 轉換 Flashcard 為 CardState 格式,確保所有屬性都有值
|
||||
const cardState: CardState = {
|
||||
...card,
|
||||
exampleTranslation: card.exampleTranslation || '', // 確保 exampleTranslation 不為 undefined
|
||||
exampleTranslation: card.exampleTranslation || '', // 確保為 string,不是 undefined
|
||||
updatedAt: card.updatedAt || card.createdAt, // 確保 updatedAt 為 string
|
||||
primaryImageUrl: card.primaryImageUrl || null, // 確保為 null 而非 undefined
|
||||
skipCount: 0,
|
||||
wrongCount: 0,
|
||||
isCompleted: false,
|
||||
originalOrder: order / 2, // 原始詞卡的順序
|
||||
synonyms: [],
|
||||
difficultyLevelNumeric: card.masteryLevel || 1 // 添加缺少的屬性
|
||||
synonyms: [], // 確保為空數組而非 undefined
|
||||
difficultyLevelNumeric: card.masteryLevel || 1 // 使用 masteryLevel 或預設值 1
|
||||
}
|
||||
|
||||
// 為每張詞卡生成兩種測驗模式
|
||||
|
|
|
|||
Loading…
Reference in New Issue