refactor: 優化例句口說功能設計與用戶體驗

## VoiceRecorder 組件改進
- 添加自定義說明文字 prop (instructionText)
- 調整布局順序:圖片 → 說明 → 例句
- 統一圖片容器樣式與其他模式一致

## 例句口說頁面優化
- 移除重複的例句和圖片顯示
- 簡化錄音完成回饋區域
- 移除不必要的目標單字和發音顯示
- 調整回饋訊息為分行顯示,提升可讀性
- 實現滿版寬度布局

## 用戶體驗改進
- 消除重複內容,介面更簡潔
- 統一設計語言,視覺一致性更好
- 優化訊息層次,重點更突出

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-09-20 04:12:52 +08:00
parent 7203346134
commit d1c5f2e31c
2 changed files with 29 additions and 55 deletions

View File

@ -937,42 +937,12 @@ export default function LearnPage() {
</span>
</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>
<div className="space-y-4 mb-8">
{/* Example Sentence */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
<div className="relative">
<p className="text-gray-700 italic mb-2 text-left pr-12">"{currentCard.example}"</p>
<div className="absolute bottom-0 right-0">
<AudioPlayer text={currentCard.example} />
</div>
</div>
<p className="text-gray-600 text-sm text-left">"{currentCard.exampleTranslation}"</p>
</div>
</div>
<div className="max-w-md mx-auto">
<div className="w-full">
<VoiceRecorder
targetText={currentCard.example}
targetTranslation={currentCard.exampleTranslation}
exampleImage={currentCard.exampleImage}
instructionText="請看例句圖片並大聲說出完整的例句:"
onRecordingComplete={() => {
// 簡化處理:直接顯示結果
handleSpeakingAnswer(currentCard.example)
@ -981,21 +951,13 @@ export default function LearnPage() {
</div>
{showResult && (
<div className="mt-6 p-4 rounded-lg bg-blue-50 border border-blue-200 max-w-md mx-auto">
<p className="text-blue-700 text-center">
...
<div className="mt-6 p-6 rounded-lg bg-blue-50 border border-blue-200 w-full">
<p className="text-blue-700 text-left text-xl font-semibold mb-2">
</p>
<p className="text-gray-600 text-left">
...
</p>
<div className="mt-4 text-center">
<p className="text-gray-700">
<strong>{currentCard.word}</strong>
</p>
<p className="text-gray-600 mt-1">
{currentCard.pronunciation}
</p>
<div className="mt-2">
<AudioPlayer text={currentCard.word} />
</div>
</div>
</div>
)}
</div>

View File

@ -24,6 +24,7 @@ export interface VoiceRecorderProps {
targetText: string;
targetTranslation?: string;
exampleImage?: string;
instructionText?: string;
onScoreReceived?: (score: PronunciationScore) => void;
onRecordingComplete?: (audioBlob: Blob) => void;
maxDuration?: number;
@ -35,6 +36,7 @@ export default function VoiceRecorder({
targetText,
targetTranslation,
exampleImage,
instructionText,
onScoreReceived,
onRecordingComplete,
maxDuration = 30, // 30 seconds default
@ -245,13 +247,23 @@ export default function VoiceRecorder({
{/* Example Image */}
{exampleImage && (
<div className="mb-4">
<img
src={exampleImage}
alt="Example context"
className="w-full rounded-lg shadow-md cursor-pointer hover:shadow-lg transition-shadow"
style={{ maxHeight: '400px', objectFit: 'contain' }}
/>
<div className="text-xs text-gray-500 mt-2 text-center"></div>
<div className="bg-gray-50 rounded-lg p-4">
<img
src={exampleImage}
alt="Example context"
className="w-full max-w-md mx-auto rounded-lg cursor-pointer"
style={{ maxHeight: '400px', objectFit: 'contain' }}
/>
</div>
</div>
)}
{/* Instruction Text */}
{instructionText && (
<div className="mb-6">
<p className="text-lg text-gray-700 text-left">
{instructionText}
</p>
</div>
)}