111 lines
3.4 KiB
TypeScript
111 lines
3.4 KiB
TypeScript
import React, { memo, useState, useCallback } from 'react'
|
||
import { BluePlayButton } from '@/components/shared/BluePlayButton'
|
||
|
||
interface TestResultDisplayProps {
|
||
isCorrect: boolean
|
||
correctAnswer: string
|
||
userAnswer?: string
|
||
word: string
|
||
pronunciation?: string
|
||
example: string
|
||
exampleTranslation: string
|
||
showResult: boolean
|
||
}
|
||
|
||
export const TestResultDisplay = memo<TestResultDisplayProps>(({
|
||
isCorrect,
|
||
correctAnswer,
|
||
userAnswer,
|
||
word,
|
||
pronunciation,
|
||
example,
|
||
exampleTranslation,
|
||
showResult
|
||
}) => {
|
||
const [isPlayingAnswer, setIsPlayingAnswer] = useState(false)
|
||
const [isPlayingExample, setIsPlayingExample] = useState(false)
|
||
|
||
// TTS 播放邏輯
|
||
const handleToggleTTS = useCallback((text: string, type: 'answer' | 'example', lang?: string) => {
|
||
const isCurrentlyPlaying = type === 'answer' ? isPlayingAnswer : isPlayingExample
|
||
const setPlaying = type === 'answer' ? setIsPlayingAnswer : setIsPlayingExample
|
||
const stopOther = type === 'answer' ? setIsPlayingExample : setIsPlayingAnswer
|
||
|
||
if (isCurrentlyPlaying) {
|
||
speechSynthesis.cancel()
|
||
setPlaying(false)
|
||
return
|
||
}
|
||
|
||
// 停止另一個播放
|
||
stopOther(false)
|
||
speechSynthesis.cancel()
|
||
|
||
const utterance = new SpeechSynthesisUtterance(text)
|
||
utterance.lang = lang || 'en-US'
|
||
utterance.rate = 0.8
|
||
|
||
utterance.onstart = () => setPlaying(true)
|
||
utterance.onend = () => setPlaying(false)
|
||
utterance.onerror = () => setPlaying(false)
|
||
|
||
speechSynthesis.speak(utterance)
|
||
}, [isPlayingAnswer, isPlayingExample])
|
||
if (!showResult) return null
|
||
|
||
return (
|
||
<div className={`mt-6 p-6 rounded-lg w-full ${
|
||
isCorrect
|
||
? 'bg-green-50 border border-green-200'
|
||
: 'bg-red-50 border border-red-200'
|
||
}`}>
|
||
<p className={`font-semibold text-left text-xl mb-4 ${
|
||
isCorrect ? 'text-green-700' : 'text-red-700'
|
||
}`}>
|
||
{isCorrect ? '正確!' : '錯誤!'}
|
||
</p>
|
||
|
||
{!isCorrect && userAnswer && (
|
||
<div className="mb-4">
|
||
<p className="text-gray-700 text-left">
|
||
正確答案是:<strong className="text-lg">{correctAnswer}</strong>
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
<div className="space-y-3">
|
||
<div className="text-left">
|
||
<p className="text-gray-600">
|
||
{word && <span className="font-semibold text-left text-xl">{word}</span>}
|
||
{pronunciation && <span className="mx-2">{pronunciation}</span>}
|
||
<BluePlayButton
|
||
text={correctAnswer}
|
||
isPlaying={isPlayingAnswer}
|
||
onToggle={(text, lang) => handleToggleTTS(text, 'answer', lang)}
|
||
size="sm"
|
||
title="播放答案"
|
||
/>
|
||
</p>
|
||
</div>
|
||
|
||
<div className="text-left">
|
||
<p className="text-gray-600">
|
||
{example}
|
||
<BluePlayButton
|
||
text={example}
|
||
isPlaying={isPlayingExample}
|
||
onToggle={(text, lang) => handleToggleTTS(text, 'example', lang)}
|
||
size="sm"
|
||
title="播放例句"
|
||
/>
|
||
</p>
|
||
<p className="text-gray-500 text-sm">
|
||
{exampleTranslation}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
})
|
||
|
||
TestResultDisplay.displayName = 'TestResultDisplay' |