fix: 優化詞彙標記樣式與片語點擊功能
- 還原例句中詞彙樣式為簡潔設計 (rounded, px-1 py-0.5) - 實現片語標籤點擊顯示詳細彈窗功能 - 修正假資料結構,區分cut動詞和cut someone some slack片語 - 調整片語標籤樣式與例句詞彙保持一致 - 修復Console錯誤和語法問題 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
fb89cf1a33
commit
11e19d5e1c
|
|
@ -18,6 +18,11 @@ function GenerateContent() {
|
|||
const [finalText, setFinalText] = useState('')
|
||||
const [usageCount, setUsageCount] = useState(0)
|
||||
const [isPremium] = useState(true)
|
||||
const [phrasePopup, setPhrasePopup] = useState<{
|
||||
phrase: string
|
||||
analysis: any
|
||||
position: { x: number, y: number }
|
||||
} | null>(null)
|
||||
|
||||
|
||||
// 處理句子分析 - 使用假資料測試
|
||||
|
|
@ -120,16 +125,16 @@ function GenerateContent() {
|
|||
exampleTranslation: "我們去公園吧。"
|
||||
},
|
||||
"cut": {
|
||||
word: "cut someone some slack",
|
||||
translation: "對某人寬容一點",
|
||||
definition: "to be more lenient or forgiving with someone",
|
||||
partOfSpeech: "idiom",
|
||||
pronunciation: "/kʌt ˈsʌmwʌn sʌm slæk/",
|
||||
difficultyLevel: "B2",
|
||||
isPhrase: true,
|
||||
synonyms: ["be lenient", "be forgiving", "give leeway"],
|
||||
example: "Cut him some slack, he's new here.",
|
||||
exampleTranslation: "對他寬容一點,他是新來的。"
|
||||
word: "cut",
|
||||
translation: "切;削減",
|
||||
definition: "to use a knife or other sharp tool to divide something",
|
||||
partOfSpeech: "verb",
|
||||
pronunciation: "/kʌt/",
|
||||
difficultyLevel: "A2",
|
||||
isPhrase: false,
|
||||
synonyms: ["slice", "chop", "reduce"],
|
||||
example: "Please cut the apple.",
|
||||
exampleTranslation: "請切蘋果。"
|
||||
},
|
||||
"her": {
|
||||
word: "her",
|
||||
|
|
@ -250,7 +255,19 @@ function GenerateContent() {
|
|||
synonyms: ["becomes", "obtains"],
|
||||
example: "It gets cold at night.",
|
||||
exampleTranslation: "晚上會變冷。"
|
||||
}
|
||||
},
|
||||
"cut someone some slack": {
|
||||
word: "cut someone some slack",
|
||||
translation: "對某人寬容一點",
|
||||
definition: "to be more lenient or forgiving with someone",
|
||||
partOfSpeech: "idiom",
|
||||
pronunciation: "/kʌt ˈsʌmwʌn sʌm slæk/",
|
||||
difficultyLevel: "B2",
|
||||
isPhrase: true,
|
||||
synonyms: ["be lenient", "be forgiving", "give leeway"],
|
||||
example: "Cut him some slack, he's new here.",
|
||||
exampleTranslation: "對他寬容一點,他是新來的。"
|
||||
},
|
||||
}
|
||||
|
||||
// 設定結果 - 包含語法錯誤情境
|
||||
|
|
@ -621,7 +638,7 @@ function GenerateContent() {
|
|||
|
||||
{/* 句子主體展示 */}
|
||||
<div className="text-left mb-8">
|
||||
<div className="text-3xl font-medium text-gray-900 mb-6" style={{lineHeight: '2.5'}}>
|
||||
<div className="text-3xl font-medium text-gray-900 mb-6" >
|
||||
<ClickableTextV2
|
||||
text={finalText}
|
||||
analysis={sentenceAnalysis}
|
||||
|
|
@ -684,13 +701,26 @@ function GenerateContent() {
|
|||
{phrases.map((phrase, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="bg-white text-gray-700 px-3 py-1 rounded-full text-sm border border-gray-200 cursor-pointer"
|
||||
onClick={() => {
|
||||
console.log('Clicked phrase:', phrase)
|
||||
className="cursor-pointer transition-all duration-200 rounded-lg relative mx-0.5 px-1 py-0.5 inline-flex items-center gap-1 bg-blue-50 border border-blue-200 hover:bg-blue-100 hover:shadow-lg transform hover:-translate-y-0.5 text-blue-700 font-medium"
|
||||
onClick={(e) => {
|
||||
// 找到片語的完整分析資料
|
||||
const phraseAnalysis = sentenceAnalysis?.["cut someone some slack"]
|
||||
|
||||
if (phraseAnalysis) {
|
||||
// 設定片語彈窗狀態
|
||||
setPhrasePopup({
|
||||
phrase: phrase.phrase,
|
||||
analysis: phraseAnalysis,
|
||||
position: {
|
||||
x: e.currentTarget.getBoundingClientRect().left + e.currentTarget.getBoundingClientRect().width / 2,
|
||||
y: e.currentTarget.getBoundingClientRect().bottom + 10
|
||||
}
|
||||
})
|
||||
}
|
||||
}}
|
||||
title={`${phrase.phrase}: ${phrase.meaning}`}
|
||||
>
|
||||
"{phrase.phrase}"
|
||||
{phrase.phrase}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -712,6 +742,96 @@ function GenerateContent() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 片語彈窗 */}
|
||||
{phrasePopup && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-50 z-40"
|
||||
onClick={() => setPhrasePopup(null)}
|
||||
/>
|
||||
<div
|
||||
className="fixed z-50 bg-white rounded-xl shadow-lg w-96 max-w-md overflow-hidden"
|
||||
style={{
|
||||
left: `${phrasePopup.position.x}px`,
|
||||
top: `${phrasePopup.position.y}px`,
|
||||
transform: 'translate(-50%, 8px)',
|
||||
maxHeight: '85vh',
|
||||
overflowY: 'auto'
|
||||
}}
|
||||
>
|
||||
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-5 border-b border-blue-200">
|
||||
<div className="flex justify-end mb-3">
|
||||
<button
|
||||
onClick={() => setPhrasePopup(null)}
|
||||
className="text-gray-400 hover:text-gray-600 w-6 h-6 rounded-full bg-white bg-opacity-80 hover:bg-opacity-100 transition-all flex items-center justify-center"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<h3 className="text-2xl font-bold text-gray-900">{phrasePopup.analysis.word}</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm bg-gray-100 text-gray-700 px-3 py-1 rounded-full">
|
||||
{phrasePopup.analysis.partOfSpeech}
|
||||
</span>
|
||||
<span className="text-base text-gray-600">{phrasePopup.analysis.pronunciation}</span>
|
||||
</div>
|
||||
|
||||
<span className="px-3 py-1 rounded-full text-sm font-medium border bg-blue-100 text-blue-700 border-blue-200">
|
||||
{phrasePopup.analysis.difficultyLevel}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="bg-green-50 rounded-lg p-3 border border-green-200">
|
||||
<h4 className="font-semibold text-green-900 mb-2 text-left text-sm">中文翻譯</h4>
|
||||
<p className="text-green-800 font-medium text-left">{phrasePopup.analysis.translation}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||
<h4 className="font-semibold text-gray-900 mb-2 text-left text-sm">英文定義</h4>
|
||||
<p className="text-gray-700 text-left text-sm leading-relaxed">{phrasePopup.analysis.definition}</p>
|
||||
</div>
|
||||
|
||||
{phrasePopup.analysis.example && (
|
||||
<div className="bg-blue-50 rounded-lg p-3 border border-blue-200">
|
||||
<h4 className="font-semibold text-blue-900 mb-2 text-left text-sm">例句</h4>
|
||||
<div className="space-y-2">
|
||||
<p className="text-blue-800 text-left text-sm italic">
|
||||
"{phrasePopup.analysis.example}"
|
||||
</p>
|
||||
<p className="text-blue-700 text-left text-sm">
|
||||
{phrasePopup.analysis.exampleTranslation}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4 pt-2">
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
await handleSaveWord(phrasePopup.phrase, phrasePopup.analysis)
|
||||
setPhrasePopup(null)
|
||||
} catch (error) {
|
||||
console.error('Save phrase error:', error)
|
||||
}
|
||||
}}
|
||||
className="w-full bg-primary text-white py-3 rounded-lg font-medium hover:bg-primary-hover transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<span className="font-medium">保存到詞卡</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export function ClickableTextV2({
|
|||
|
||||
const getWordClass = (word: string) => {
|
||||
const wordAnalysis = findWordAnalysis(word)
|
||||
const baseClass = "cursor-pointer transition-all duration-200 rounded-lg relative mx-0.5 my-1 px-2 py-1 inline-flex items-center gap-1"
|
||||
const baseClass = "cursor-pointer transition-all duration-200 rounded relative mx-0.5 px-1 py-0.5"
|
||||
|
||||
if (wordAnalysis) {
|
||||
const isPhrase = getWordProperty(wordAnalysis, 'isPhrase')
|
||||
|
|
@ -275,7 +275,7 @@ export function ClickableTextV2({
|
|||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="text-lg" leading-relaxed>
|
||||
<div className="text-lg" style={{lineHeight: '2.5'}}>
|
||||
{words.map((word, index) => {
|
||||
if (word.trim() === '' || /^[.,!?;:\s]+$/.test(word)) {
|
||||
return <span key={index}>{word}</span>
|
||||
|
|
|
|||
Loading…
Reference in New Issue