feat: 完成詞卡儲存功能整合並移除假資料

🎯 主要功能:
- 統一前後端API欄位名稱(word/translation/definition)
- 移除所有假資料生成函數,改用真實API調用
- 修正FlashcardForm和相關組件的欄位映射

🔧 技術修正:
- 前端handleSaveWord函數使用正確的API欄位
- flashcardsService interface與後端API完全匹配
- handleAnalyzeSentence改為調用真實的analyze-sentence API

📝 代碼清理:
- 移除generateMockAnalysis函數(~150行代碼)
- 移除getRandomTranslation、getRandomPartOfSpeech等工具函數
- 清理所有mock相關的註釋和變數

 功能驗證:
- 後端API正常運行(localhost:5000)
- 前端Portal彈窗樣式完美
- 詞卡儲存功能完整可用

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-09-21 01:36:22 +08:00
parent aca9ec2f7a
commit b348780eaa
4 changed files with 47 additions and 178 deletions

View File

@ -876,8 +876,9 @@ function FlashcardsContent() {
initialData={editingCard ? {
id: editingCard.id,
cardSetId: editingCard.cardSet ? cardSets.find(cs => cs.name === editingCard.cardSet.name)?.id || cardSets[0]?.id : cardSets[0]?.id,
english: editingCard.word,
chinese: editingCard.translation,
word: editingCard.word,
translation: editingCard.translation,
definition: editingCard.definition,
pronunciation: editingCard.pronunciation,
partOfSpeech: editingCard.partOfSpeech,
example: editingCard.example,

View File

@ -20,9 +20,9 @@ function GenerateContent() {
const [isPremium] = useState(true)
// 處理句子分析 - 使用假數據進行快速測試
// 處理句子分析 - 使用真實API
const handleAnalyzeSentence = async () => {
console.log('🚀 handleAnalyzeSentence 被調用 (假數據模式)')
console.log('🚀 handleAnalyzeSentence 被調用 (真實API模式)')
console.log('📝 輸入文本:', textInput)
if (!textInput.trim()) {
@ -33,181 +33,46 @@ function GenerateContent() {
setIsAnalyzing(true)
try {
// 模擬API延遲
await new Promise(resolve => setTimeout(resolve, 1500))
const response = await fetch('http://localhost:5000/api/ai/analyze-sentence', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
},
body: JSON.stringify({
text: textInput
})
})
// 生成假的分析數據
const mockAnalysis = generateMockAnalysis(textInput)
if (response.ok) {
const result = await response.json()
console.log('✅ API分析完成:', result)
setSentenceAnalysis(mockAnalysis.wordAnalysis)
setSentenceMeaning(mockAnalysis.sentenceTranslation)
setGrammarCorrection(mockAnalysis.grammarCorrection)
setFinalText(mockAnalysis.finalText)
setShowAnalysisView(true)
setUsageCount(prev => prev + 1)
console.log('✅ 假數據分析完成:', mockAnalysis)
if (result.success) {
setSentenceAnalysis(result.data.wordAnalysis || {})
setSentenceMeaning(result.data.sentenceTranslation || '')
setGrammarCorrection(result.data.grammarCorrection || null)
setFinalText(result.data.finalText || textInput)
setShowAnalysisView(true)
setUsageCount(prev => prev + 1)
} else {
throw new Error(result.error || '分析失敗')
}
} else {
throw new Error(`API錯誤: ${response.status}`)
}
} catch (error) {
console.error('Error in mock analysis:', error)
console.error('Error in real API analysis:', error)
alert(`分析句子時發生錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`)
} finally {
setIsAnalyzing(false)
}
}
// 生成假的分析數據
const generateMockAnalysis = (inputText: string) => {
const words = inputText.toLowerCase().split(/\s+/).filter(word =>
word.length > 2 && /^[a-z]+$/.test(word.replace(/[.,!?;:]/g, ''))
)
const wordAnalysis: any = {}
words.forEach((word, index) => {
const cleanWord = word.replace(/[.,!?;:]/g, '')
const isHighValue = index % 3 === 0 // 每3個詞中有1個高價值
const isPhrase = cleanWord.length > 6 // 長詞視為片語
wordAnalysis[cleanWord] = {
word: cleanWord,
translation: getRandomTranslation(cleanWord),
definition: `Definition of ${cleanWord} - a common English word`,
partOfSpeech: getRandomPartOfSpeech(),
pronunciation: `/${cleanWord}/`,
isHighValue: isHighValue,
isPhrase: isPhrase,
difficultyLevel: getRandomDifficulty(),
synonyms: [getRandomSynonym(cleanWord), getRandomSynonym(cleanWord)],
learningPriority: isHighValue ? 'high' : 'medium'
}
})
return {
wordAnalysis,
sentenceTranslation: `這是「${inputText}」的中文翻譯。`,
grammarCorrection: {
hasErrors: Math.random() > 0.7, // 30%機率有語法錯誤
correctedText: inputText,
originalText: inputText
},
finalText: inputText
}
}
// 輔助函數 - 改進翻譯生成
const getRandomTranslation = (word: string) => {
// 常見英文單字的實際翻譯
const commonTranslations: {[key: string]: string} = {
'hello': '你好',
'how': '如何',
'are': '是',
'you': '你',
'today': '今天',
'good': '好的',
'morning': '早晨',
'evening': '晚上',
'thank': '謝謝',
'please': '請',
'sorry': '抱歉',
'love': '愛',
'like': '喜歡',
'want': '想要',
'need': '需要',
'think': '思考',
'know': '知道',
'see': '看見',
'go': '去',
'come': '來',
'get': '得到',
'make': '製作',
'take': '拿取',
'give': '給予',
'find': '找到',
'work': '工作',
'feel': '感覺',
'become': '成為',
'leave': '離開',
'put': '放置',
'mean': '意思',
'keep': '保持',
'let': '讓',
'begin': '開始',
'seem': '似乎',
'help': '幫助',
'talk': '談話',
'turn': '轉向',
'start': '開始',
'show': '顯示',
'hear': '聽見',
'play': '玩耍',
'run': '跑步',
'move': '移動',
'live': '生活',
'believe': '相信',
'bring': '帶來',
'happen': '發生',
'write': '寫作',
'provide': '提供',
'sit': '坐下',
'stand': '站立',
'lose': '失去',
'pay': '付費',
'meet': '遇見',
'include': '包含',
'continue': '繼續',
'set': '設置',
'learn': '學習',
'change': '改變',
'lead': '領導',
'understand': '理解',
'watch': '觀看',
'follow': '跟隨',
'stop': '停止',
'create': '創造',
'speak': '說話',
'read': '閱讀',
'allow': '允許',
'add': '添加',
'spend': '花費',
'grow': '成長',
'open': '打開',
'walk': '走路',
'win': '獲勝',
'offer': '提供',
'remember': '記住',
'consider': '考慮',
'appear': '出現',
'buy': '購買',
'wait': '等待',
'serve': '服務',
'die': '死亡',
'send': '發送',
'expect': '期待',
'build': '建造',
'stay': '停留',
'fall': '跌倒',
'cut': '切割',
'reach': '到達',
'kill': '殺死',
'remain': '保持'
}
return commonTranslations[word.toLowerCase()] || `${word}的翻譯`
}
const getRandomPartOfSpeech = () => {
const parts = ['noun', 'verb', 'adjective', 'adverb', 'preposition']
return parts[Math.floor(Math.random() * parts.length)]
}
const getRandomDifficulty = () => {
const levels = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2']
return levels[Math.floor(Math.random() * levels.length)]
}
const getRandomSynonym = (word: string) => {
return `synonym_${word}_${Math.floor(Math.random() * 10)}`
}
const handleAcceptCorrection = () => {
if (grammarCorrection?.correctedText) {
@ -225,8 +90,9 @@ function GenerateContent() {
const handleSaveWord = async (word: string, analysis: any) => {
try {
const cardData = {
english: word, // 修正API欄位名稱
chinese: analysis.translation || analysis.Translation || '',
word: word,
translation: analysis.translation || analysis.Translation || '',
definition: analysis.definition || analysis.Definition || '',
pronunciation: analysis.pronunciation || analysis.Pronunciation || `/${word}/`,
partOfSpeech: analysis.partOfSpeech || analysis.PartOfSpeech || 'unknown',
example: `Example sentence with ${word}.` // 提供預設例句

View File

@ -30,8 +30,9 @@ export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess
const [formData, setFormData] = useState<CreateFlashcardRequest>({
cardSetId: getDefaultCardSetId(),
english: initialData?.english || '',
chinese: initialData?.chinese || '',
word: initialData?.word || '',
translation: initialData?.translation || '',
definition: initialData?.definition || '',
pronunciation: initialData?.pronunciation || '',
partOfSpeech: initialData?.partOfSpeech || '名詞',
example: initialData?.example || '',
@ -158,16 +159,16 @@ export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess
<div className="flex gap-2">
<input
type="text"
value={formData.english}
onChange={(e) => handleChange('english', e.target.value)}
value={formData.word}
onChange={(e) => handleChange('word', e.target.value)}
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
placeholder="例如negotiate"
required
/>
{formData.english && (
{formData.word && (
<div className="flex-shrink-0">
<AudioPlayer
text={formData.english}
text={formData.word}
className="w-auto"
/>
</div>
@ -182,8 +183,8 @@ export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess
</label>
<input
type="text"
value={formData.chinese}
onChange={(e) => handleChange('chinese', e.target.value)}
value={formData.translation}
onChange={(e) => handleChange('translation', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
placeholder="例如:談判,協商"
required

View File

@ -42,8 +42,9 @@ export interface CreateCardSetRequest {
export interface CreateFlashcardRequest {
cardSetId?: string;
english: string;
chinese: string;
word: string;
translation: string;
definition: string;
pronunciation: string;
partOfSpeech: string;
example: string;