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,16 +391,34 @@ export default function LearnPage() {
<div className="card-front">
<div
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">
{currentCard.word}
</h2>
<div className="flex items-center justify-center gap-3">
<span className="text-lg text-gray-500">
{currentCard.pronunciation}
{/* Title and Instructions */}
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900">
</h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{currentCard.difficulty}
</span>
<AudioPlayer text={currentCard.word} />
</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">
<span className="text-lg text-gray-500">
{currentCard.pronunciation}
</span>
<AudioPlayer text={currentCard.word} />
</div>
</div>
</div>
</div>
</div>
@ -599,87 +617,124 @@ export default function LearnPage() {
{currentCard.difficulty}
</span>
</div>
<p className="text-lg text-gray-700 mb-2 text-left">
</p>
<div className="text-center mb-8">
<div className="bg-gray-50 rounded-lg p-6 mb-6">
<p className="text-lg text-gray-700 mb-4">
<strong></strong>{currentCard.definition}
</p>
<p className="text-lg text-gray-700">
<strong></strong>{currentCard.translation}
</p>
</div>
<p className="text-lg text-gray-700 mb-4">
</p>
</div>
<div className="max-w-md mx-auto">
<input
type="text"
value={fillAnswer}
onChange={(e) => setFillAnswer(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !showResult && fillAnswer.trim()) {
handleFillAnswer()
}
}}
placeholder="輸入英文單字..."
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"
/>
<div className="flex gap-2 mt-4">
{!showResult && fillAnswer.trim() && (
<button
onClick={handleFillAnswer}
className="flex-1 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
</button>
)}
<button
onClick={() => setShowHint(!showHint)}
className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors"
>
{showHint ? '隱藏提示' : '顯示提示'}
</button>
</div>
{showHint && (
<div className="mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<p className="text-yellow-800">
<strong></strong> {currentCard.word.length}
"{currentCard.word[0].toUpperCase()}"
</p>
{/* 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>
{/* Example Sentence with Blanks */}
<div className="mb-6">
<div className="bg-gray-50 rounded-lg p-6">
<div className="text-lg text-gray-700 leading-relaxed">
{currentCard.example.split(new RegExp(`(${currentCard.word})`, 'gi')).map((part, index) => {
const isTargetWord = part.toLowerCase() === currentCard.word.toLowerCase();
return isTargetWord ? (
<span key={index} className="relative inline-block mx-1">
<input
type="text"
value={fillAnswer}
onChange={(e) => setFillAnswer(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !showResult && fillAnswer.trim()) {
handleFillAnswer()
}
}}
placeholder=""
disabled={showResult}
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>
{/* Action Buttons */}
<div className="flex gap-3 mb-4">
{!showResult && fillAnswer.trim() && (
<button
onClick={handleFillAnswer}
className="px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors"
>
</button>
)}
<button
onClick={() => setShowHint(!showHint)}
className="px-6 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors"
>
{showHint ? '隱藏提示' : '顯示提示'}
</button>
</div>
{/* Hint Section */}
{showHint && (
<div className="mb-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<h4 className="font-semibold text-yellow-800 mb-2"></h4>
<p className="text-yellow-800">{currentCard.definition}</p>
</div>
)}
{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()
? 'bg-green-50 border border-green-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()
? 'text-green-700'
: 'text-red-700'
}`}>
{fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase() ? '正確!' : '錯誤!'}
</p>
{fillAnswer.toLowerCase().trim() !== currentCard.word.toLowerCase() && (
<p className="text-gray-700 mt-2 text-center">
<strong>{currentCard.word}</strong>
</p>
<div className="mb-4">
<p className="text-gray-700 text-left">
<strong className="text-lg">{currentCard.word}</strong>
</p>
</div>
)}
<div className="mt-4 text-center">
<p className="text-gray-600 mb-2">{currentCard.pronunciation}</p>
<AudioPlayer text={currentCard.word} />
<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} />
</p>
</div>
<div className="text-left">
</div>
</div>
</div>
)}
@ -732,23 +787,15 @@ export default function LearnPage() {
</p>
<div className="text-center mb-8">
<div className="bg-gray-50 rounded-lg p-6 mb-6">
<p className="text-lg text-gray-700 mb-4">
<strong></strong>{currentCard.translation}
</p>
<p className="text-lg text-gray-700">
<strong></strong>{currentCard.definition}
</p>
</div>
<p className="text-lg text-gray-700 mb-6">
</p>
<div className="mb-8">
<AudioPlayer
text={currentCard.word}
/>
{/* Content Sections */}
<div className="space-y-4 mb-8">
{/* Audio */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
<div className="flex items-center gap-3">
<span className="text-gray-700">{currentCard.pronunciation}</span>
<AudioPlayer text={currentCard.word} />
</div>
</div>
</div>
@ -1009,12 +1056,26 @@ export default function LearnPage() {
{currentCard.difficulty}
</span>
</div>
<p className="text-lg text-gray-700 mb-2 text-left">
{/* 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>
<div className="text-center mb-8">
<div className="bg-gray-50 rounded-lg p-6 mb-6">
<p className="text-lg text-gray-700 mb-4">
<strong></strong>{currentCard.word}
@ -1023,7 +1084,6 @@ export default function LearnPage() {
<strong></strong>{currentCard.exampleTranslation}
</p>
</div>
</div>
<div className="mb-6">
@ -1174,8 +1234,8 @@ export default function LearnPage() {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
justify-content: flex-start;
align-items: stretch;
padding: 2rem;
box-sizing: border-box;
}