refactor: 重構 Generate 頁面移除過度抽象 + 統一按鈕樣式
主要改動: - 移除 ClickableTextV2 組件 (115行) → 內聯為35行邏輯 - 新增 selectedWord 狀態管理與統一 WordPopup 組件 - 移除慣用語區塊複雜星星判斷邏輯 (17行 → 0行) - 調整句子主體字體大小 text-xl→lg 更適中 - 重構單字樣式: 下劃線 → 按鈕樣式 (邊框+圓角+hover) - 根據 CEFR 等級設置顏色主題 (A1/A2綠、B1/B2藍、C1/C2紅) 效果: - 淨減少 ~80行代碼複雜度 - 統一視覺風格 (慣用語 + 單字按鈕一致) - 提升用戶體驗 (清晰可點擊按鈕) - 簡化維護成本 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6a5831bb16
commit
3783be0fcd
|
|
@ -3,11 +3,11 @@
|
|||
import { useState, useMemo, useCallback } from 'react'
|
||||
import { ProtectedRoute } from '@/components/shared/ProtectedRoute'
|
||||
import { Navigation } from '@/components/shared/Navigation'
|
||||
import { ClickableTextV2 } from '@/components/generate/ClickableTextV2'
|
||||
import { WordPopup } from '@/components/word/WordPopup'
|
||||
import { useToast } from '@/components/shared/Toast'
|
||||
import { flashcardsService } from '@/lib/services/flashcards'
|
||||
import { compareCEFRLevels, getLevelIndex, getTargetLearningRange } from '@/lib/utils/cefrUtils'
|
||||
import { useWordAnalysis } from '@/hooks/word/useWordAnalysis'
|
||||
import { API_CONFIG } from '@/lib/config/api'
|
||||
import Link from 'next/link'
|
||||
|
||||
|
|
@ -34,6 +34,7 @@ interface GrammarCorrection {
|
|||
|
||||
function GenerateContent() {
|
||||
const toast = useToast()
|
||||
const { findWordAnalysis, getWordClass, shouldShowStar } = useWordAnalysis()
|
||||
const [textInput, setTextInput] = useState('')
|
||||
const [isAnalyzing, setIsAnalyzing] = useState(false)
|
||||
const [showAnalysisView, setShowAnalysisView] = useState(false)
|
||||
|
|
@ -41,6 +42,7 @@ function GenerateContent() {
|
|||
const [sentenceMeaning, setSentenceMeaning] = useState('')
|
||||
const [grammarCorrection, setGrammarCorrection] = useState<GrammarCorrection | null>(null)
|
||||
const [selectedIdiom, setSelectedIdiom] = useState<string | null>(null)
|
||||
const [selectedWord, setSelectedWord] = useState<string | null>(null)
|
||||
|
||||
// 處理句子分析 - 使用真實API
|
||||
const handleAnalyzeSentence = async () => {
|
||||
|
|
@ -393,16 +395,50 @@ function GenerateContent() {
|
|||
|
||||
{/* 句子主體展示 */}
|
||||
<div className="text-left mb-8">
|
||||
<div className="text-xl sm:text-2xl lg:text-3xl font-medium text-gray-900 mb-6" >
|
||||
<ClickableTextV2
|
||||
text={textInput}
|
||||
analysis={sentenceAnalysis?.vocabularyAnalysis || undefined}
|
||||
showIdiomsInline={false}
|
||||
onWordClick={(word, analysis) => {
|
||||
console.log('Clicked word:', word, analysis)
|
||||
}}
|
||||
onSaveWord={handleSaveWord}
|
||||
/>
|
||||
<div className="text-lg font-medium text-gray-900 mb-6 select-text leading-relaxed">
|
||||
{textInput.split(/(\s+)/).map((token, index) => {
|
||||
const cleanToken = token.replace(/[^\w']/g, '')
|
||||
if (!cleanToken || /^\s+$/.test(token)) {
|
||||
return (
|
||||
<span key={index} className="whitespace-pre">
|
||||
{token}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const analysis = sentenceAnalysis?.vocabularyAnalysis || {}
|
||||
const wordAnalysis = findWordAnalysis(cleanToken, analysis)
|
||||
if (!wordAnalysis) {
|
||||
return (
|
||||
<span key={index} className="text-gray-900">
|
||||
{token}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<span key={index} className="relative">
|
||||
<span
|
||||
className={getWordClass(cleanToken, analysis)}
|
||||
onClick={() => setSelectedWord(cleanToken)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
setSelectedWord(cleanToken)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{token}
|
||||
</span>
|
||||
{/* {shouldShowStar(wordAnalysis) && (
|
||||
<span className="absolute -top-1 -right-1 text-xs text-yellow-500">
|
||||
⭐
|
||||
</span>
|
||||
)} */}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 翻譯 - 參考翻卡背面設計 */}
|
||||
|
|
@ -434,23 +470,6 @@ function GenerateContent() {
|
|||
title={`${idiom.idiom}: ${idiom.translation}`}
|
||||
>
|
||||
{idiom.idiom}
|
||||
{(() => {
|
||||
// 只有當慣用語為常用且不是簡單慣用語時才顯示星星
|
||||
// 簡單慣用語定義:學習者CEFR > 慣用語CEFR
|
||||
const userLevel = localStorage.getItem('userEnglishLevel') || 'A2'
|
||||
const isHighFrequency = idiom?.frequency === 'high'
|
||||
const idiomCefr = idiom?.cefrLevel || 'A1'
|
||||
const isNotSimpleIdiom = !compareCEFRLevels(userLevel, idiomCefr, '>')
|
||||
|
||||
return isHighFrequency && isNotSimpleIdiom ? (
|
||||
<span
|
||||
className="absolute -top-1 -right-1 text-xs pointer-events-none z-10"
|
||||
style={{ fontSize: '8px', lineHeight: 1 }}
|
||||
>
|
||||
⭐
|
||||
</span>
|
||||
) : null
|
||||
})()}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -485,6 +504,18 @@ function GenerateContent() {
|
|||
}}
|
||||
/>
|
||||
|
||||
{/* 單詞彈窗 - 使用統一的 WordPopup */}
|
||||
<WordPopup
|
||||
selectedWord={selectedWord}
|
||||
analysis={sentenceAnalysis?.vocabularyAnalysis || {}}
|
||||
isOpen={!!selectedWord}
|
||||
onClose={() => setSelectedWord(null)}
|
||||
onSaveWord={async (word, analysis) => {
|
||||
const result = await handleSaveWord(word, analysis)
|
||||
return result
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Toast 通知系統 */}
|
||||
<toast.ToastContainer />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,13 +36,30 @@ export function useWordAnalysis() {
|
|||
const wordAnalysis = findWordAnalysis(word, analysis)
|
||||
if (!wordAnalysis) return 'cursor-default text-gray-900'
|
||||
|
||||
let classes = 'cursor-pointer transition-all duration-200 '
|
||||
// 基礎按鈕樣式 - 類似慣用語區塊
|
||||
let classes = 'cursor-pointer transition-all duration-200 rounded px-1 py-0.5 border font-medium hover:shadow-md transform hover:-translate-y-0.5 '
|
||||
|
||||
if (wordAnalysis.isIdiom) {
|
||||
classes += 'bg-purple-100 text-purple-800 border-b-2 border-purple-300 hover:bg-purple-200 font-medium'
|
||||
classes += 'bg-purple-50 text-purple-700 border-purple-200 hover:bg-purple-100'
|
||||
} else {
|
||||
const cefrColor = getCEFRColor(wordAnalysis.cefr)
|
||||
classes += `underline decoration-2 hover:bg-opacity-20 ${cefrColor.replace('border-', 'decoration-').replace('text-', 'hover:bg-')}`
|
||||
// 根據 CEFR 等級設置顏色主題
|
||||
const cefr = wordAnalysis.cefr || 'A1'
|
||||
switch (cefr) {
|
||||
case 'A1':
|
||||
case 'A2':
|
||||
classes += 'bg-green-50 text-green-700 border-green-200 hover:bg-green-100'
|
||||
break
|
||||
case 'B1':
|
||||
case 'B2':
|
||||
classes += 'bg-blue-50 text-blue-700 border-blue-200 hover:bg-blue-100'
|
||||
break
|
||||
case 'C1':
|
||||
case 'C2':
|
||||
classes += 'bg-red-50 text-red-700 border-red-200 hover:bg-red-100'
|
||||
break
|
||||
default:
|
||||
classes += 'bg-gray-50 text-gray-700 border-gray-200 hover:bg-gray-100'
|
||||
}
|
||||
}
|
||||
|
||||
return classes
|
||||
|
|
|
|||
Loading…
Reference in New Issue