feat: 重構例句填空與例句重組功能

## 例句填空重大改進
- 實現真正的例句挖空功能,支援點擊輸入
- 添加例句圖片顯示,提供視覺化學習輔助
- 重新設計答案回饋為滿版左對齊布局
- 優化提示功能顯示詞彙定義而非字母提示
- 移除例句中文翻譯,專注於英文理解

## 例句重組功能增強
- 添加例句圖片顯示功能
- 保持與例句填空一致的視覺設計

## 其他優化
- 修復翻卡記憶標題置中問題
- 優化詞彙聽力模式,移除定義和翻譯干擾
- 統一所有測驗模式的標題和布局格式

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-09-20 03:41:44 +08:00
parent a39ef4ba6f
commit a20fa9004d
1 changed files with 155 additions and 95 deletions

View File

@ -391,11 +391,27 @@ export default function LearnPage() {
<div className="card-front"> <div className="card-front">
<div <div
ref={cardFrontRef} ref={cardFrontRef}
className="bg-white rounded-xl shadow-lg text-center cursor-pointer hover:shadow-xl transition-shadow" className="bg-white rounded-xl shadow-lg cursor-pointer hover:shadow-xl transition-shadow p-8"
> >
<h2 className="text-4xl font-bold text-gray-900 mb-6"> {/* Title and Instructions */}
{currentCard.word} <div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900">
</h2> </h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{currentCard.difficulty}
</span>
</div>
<p className="text-lg text-gray-700 mb-2 text-left">
</p>
{/* Word Display */}
<div className="flex-1 flex items-center justify-center mt-6">
<div className="bg-gray-50 rounded-lg p-8 w-full text-center">
<h3 className="text-4xl font-bold text-gray-900 mb-6">
{currentCard.word}
</h3>
<div className="flex items-center justify-center gap-3"> <div className="flex items-center justify-center gap-3">
<span className="text-lg text-gray-500"> <span className="text-lg text-gray-500">
{currentCard.pronunciation} {currentCard.pronunciation}
@ -404,6 +420,8 @@ export default function LearnPage() {
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
{/* Back */} {/* Back */}
<div className="card-back"> <div className="card-back">
@ -599,25 +617,31 @@ export default function LearnPage() {
{currentCard.difficulty} {currentCard.difficulty}
</span> </span>
</div> </div>
{/* Example Image */}
{currentCard.exampleImage && (
<div className="mb-6">
<div className="bg-gray-50 rounded-lg p-4">
<img
src={currentCard.exampleImage}
alt="Example illustration"
className="w-full max-w-md mx-auto rounded-lg cursor-pointer"
onClick={() => setModalImage(currentCard.exampleImage)}
/>
</div>
</div>
)}
<p className="text-lg text-gray-700 mb-2 text-left"> <p className="text-lg text-gray-700 mb-2 text-left">
</p> </p>
{/* Example Sentence with Blanks */}
<div className="text-center mb-8"> <div className="mb-6">
<div className="bg-gray-50 rounded-lg p-6 mb-6"> <div className="bg-gray-50 rounded-lg p-6">
<p className="text-lg text-gray-700 mb-4"> <div className="text-lg text-gray-700 leading-relaxed">
<strong></strong>{currentCard.definition} {currentCard.example.split(new RegExp(`(${currentCard.word})`, 'gi')).map((part, index) => {
</p> const isTargetWord = part.toLowerCase() === currentCard.word.toLowerCase();
<p className="text-lg text-gray-700"> return isTargetWord ? (
<strong></strong>{currentCard.translation} <span key={index} className="relative inline-block mx-1">
</p>
</div>
<p className="text-lg text-gray-700 mb-4">
</p>
</div>
<div className="max-w-md mx-auto">
<input <input
type="text" type="text"
value={fillAnswer} value={fillAnswer}
@ -627,59 +651,90 @@ export default function LearnPage() {
handleFillAnswer() handleFillAnswer()
} }
}} }}
placeholder="輸入英文單字..." placeholder=""
disabled={showResult} disabled={showResult}
className="w-full p-4 text-center text-xl border-2 border-gray-200 rounded-lg focus:border-blue-500 focus:outline-none disabled:bg-gray-100" className={`inline-block px-2 py-1 text-center bg-transparent focus:outline-none disabled:bg-gray-100 ${
fillAnswer
? 'border-b-2 border-blue-500'
: 'border-b-2 border-dashed border-gray-400 focus:border-blue-400 focus:border-solid'
}`}
style={{ width: `${Math.max(100, Math.max(currentCard.word.length * 12, fillAnswer.length * 12 + 20))}px` }}
/> />
{!fillAnswer && (
<span
className="absolute inset-0 flex items-center justify-center text-gray-400 pointer-events-none"
style={{ paddingBottom: '8px' }}
>
____
</span>
)}
</span>
) : (
<span key={index}>{part}</span>
);
})}
</div>
</div>
</div>
<div className="flex gap-2 mt-4"> {/* Action Buttons */}
<div className="flex gap-3 mb-4">
{!showResult && fillAnswer.trim() && ( {!showResult && fillAnswer.trim() && (
<button <button
onClick={handleFillAnswer} onClick={handleFillAnswer}
className="flex-1 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors" className="px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
> >
</button> </button>
)} )}
<button <button
onClick={() => setShowHint(!showHint)} onClick={() => setShowHint(!showHint)}
className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors" className="px-6 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors"
> >
{showHint ? '隱藏提示' : '顯示提示'} {showHint ? '隱藏提示' : '顯示提示'}
</button> </button>
</div> </div>
{/* Hint Section */}
{showHint && ( {showHint && (
<div className="mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg"> <div className="mb-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<p className="text-yellow-800"> <h4 className="font-semibold text-yellow-800 mb-2"></h4>
<strong></strong> {currentCard.word.length} <p className="text-yellow-800">{currentCard.definition}</p>
"{currentCard.word[0].toUpperCase()}"
</p>
</div> </div>
)} )}
</div>
{showResult && ( {showResult && (
<div className={`mt-6 p-4 rounded-lg max-w-md mx-auto ${ <div className={`mt-6 p-6 rounded-lg w-full ${
fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase() fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase()
? 'bg-green-50 border border-green-200' ? 'bg-green-50 border border-green-200'
: 'bg-red-50 border border-red-200' : 'bg-red-50 border border-red-200'
}`}> }`}>
<p className={`font-semibold text-center ${ <p className={`font-semibold text-left text-xl mb-4 ${
fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase() fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase()
? 'text-green-700' ? 'text-green-700'
: 'text-red-700' : 'text-red-700'
}`}> }`}>
{fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase() ? '正確!' : '錯誤!'} {fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase() ? '正確!' : '錯誤!'}
</p> </p>
{fillAnswer.toLowerCase().trim() !== currentCard.word.toLowerCase() && ( {fillAnswer.toLowerCase().trim() !== currentCard.word.toLowerCase() && (
<p className="text-gray-700 mt-2 text-center"> <div className="mb-4">
<strong>{currentCard.word}</strong> <p className="text-gray-700 text-left">
<strong className="text-lg">{currentCard.word}</strong>
</p> </p>
</div>
)} )}
<div className="mt-4 text-center">
<p className="text-gray-600 mb-2">{currentCard.pronunciation}</p> <div className="space-y-3">
<div className="text-left">
<p className="text-gray-600">
<span className="mx-2">{currentCard.pronunciation}</span>
<AudioPlayer text={currentCard.word} /> <AudioPlayer text={currentCard.word} />
</p>
</div>
<div className="text-left">
</div>
</div> </div>
</div> </div>
)} )}
@ -732,23 +787,15 @@ export default function LearnPage() {
</p> </p>
<div className="text-center mb-8"> {/* Content Sections */}
<div className="space-y-4 mb-8">
<div className="bg-gray-50 rounded-lg p-6 mb-6"> {/* Audio */}
<p className="text-lg text-gray-700 mb-4"> <div className="bg-gray-50 rounded-lg p-4">
<strong></strong>{currentCard.translation} <h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
</p> <div className="flex items-center gap-3">
<p className="text-lg text-gray-700"> <span className="text-gray-700">{currentCard.pronunciation}</span>
<strong></strong>{currentCard.definition} <AudioPlayer text={currentCard.word} />
</p>
</div> </div>
<p className="text-lg text-gray-700 mb-6">
</p>
<div className="mb-8">
<AudioPlayer
text={currentCard.word}
/>
</div> </div>
</div> </div>
@ -1009,12 +1056,26 @@ export default function LearnPage() {
{currentCard.difficulty} {currentCard.difficulty}
</span> </span>
</div> </div>
{/* Example Image */}
{currentCard.exampleImage && (
<div className="mb-6">
<div className="bg-gray-50 rounded-lg p-4">
<img
src={currentCard.exampleImage}
alt="Example illustration"
className="w-full max-w-md mx-auto rounded-lg cursor-pointer"
onClick={() => setModalImage(currentCard.exampleImage)}
/>
</div>
</div>
)}
<p className="text-lg text-gray-700 mb-2 text-left"> <p className="text-lg text-gray-700 mb-2 text-left">
</p> </p>
<div className="text-center mb-8"> <div className="text-center mb-8">
<div className="bg-gray-50 rounded-lg p-6 mb-6"> <div className="bg-gray-50 rounded-lg p-6 mb-6">
<p className="text-lg text-gray-700 mb-4"> <p className="text-lg text-gray-700 mb-4">
<strong></strong>{currentCard.word} <strong></strong>{currentCard.word}
@ -1023,7 +1084,6 @@ export default function LearnPage() {
<strong></strong>{currentCard.exampleTranslation} <strong></strong>{currentCard.exampleTranslation}
</p> </p>
</div> </div>
</div> </div>
<div className="mb-6"> <div className="mb-6">
@ -1174,8 +1234,8 @@ export default function LearnPage() {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: flex-start;
align-items: center; align-items: stretch;
padding: 2rem; padding: 2rem;
box-sizing: border-box; box-sizing: border-box;
} }