diff --git a/frontend/app/generate/page.tsx b/frontend/app/generate/page.tsx index 5450004..dce6c6f 100644 --- a/frontend/app/generate/page.tsx +++ b/frontend/app/generate/page.tsx @@ -9,6 +9,7 @@ import { flashcardsService } from '@/lib/services/flashcards' import { compareCEFRLevels, getLevelIndex, getTargetLearningRange } from '@/lib/utils/cefrUtils' import { BluePlayButton } from '@/components/shared/BluePlayButton' import { API_CONFIG } from '@/lib/config/api' +import { calculateSmartPopupPosition, isMobileDevice, getElementPosition } from '@/lib/utils/popupPositioning' import Link from 'next/link' // 常數定義 @@ -34,6 +35,7 @@ interface IdiomPopup { idiom: string; analysis: any; position: { x: number; y: number }; + placement?: 'top' | 'bottom' | 'center'; } function GenerateContent() { @@ -442,15 +444,36 @@ function GenerateContent() { key={index} 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) => { - // 使用新的API格式,直接使用idiom物件 - setIdiomPopup({ - idiom: idiom.idiom, - analysis: idiom, - position: { - x: e.currentTarget.getBoundingClientRect().left + e.currentTarget.getBoundingClientRect().width / 2, - y: e.currentTarget.getBoundingClientRect().bottom + 10 - } - }) + // 檢測移動設備 + const isMobile = isMobileDevice() + + if (isMobile) { + // 移動設備:使用底部居中 modal + setIdiomPopup({ + idiom: idiom.idiom, + analysis: idiom, + position: { x: 0, y: 0 }, // 移動版不使用計算位置 + placement: 'center' + }) + } else { + // 桌面設備:使用智能定位系統 + const elementPosition = getElementPosition(e.currentTarget) + const smartPosition = calculateSmartPopupPosition( + elementPosition, + 384, // w-96 = 384px + 400 // 預估高度 + ) + + setIdiomPopup({ + idiom: idiom.idiom, + analysis: idiom, + position: { + x: smartPosition.x, + y: smartPosition.y + }, + placement: smartPosition.placement + }) + } }} title={`${idiom.idiom}: ${idiom.translation}`} > @@ -494,7 +517,7 @@ function GenerateContent() { )} - {/* 片語彈窗 */} + {/* 片語彈窗 - 智能定位系統 */} {idiomPopup && ( <>
setIdiomPopup(null)} />
diff --git a/frontend/components/word/WordPopup.tsx b/frontend/components/word/WordPopup.tsx index 3c994c2..0f9284a 100644 --- a/frontend/components/word/WordPopup.tsx +++ b/frontend/components/word/WordPopup.tsx @@ -1,6 +1,5 @@ -import React, { useState } from 'react' +import React from 'react' import { Modal } from '@/components/ui/Modal' -import { ContentBlock } from '@/components/shared/ContentBlock' import { getCEFRColor } from '@/lib/utils/flashcardUtils' import { BluePlayButton } from '@/components/shared/BluePlayButton' import { useWordAnalysis } from '@/hooks/word/useWordAnalysis' @@ -39,20 +38,18 @@ export const WordPopup: React.FC = ({ return ( {/* Header */} -
+
-

- {getWordProperty(wordAnalysis, 'word')} -

+

{getWordProperty(wordAnalysis, 'word')}

-
- +
+ {getWordProperty(wordAnalysis, 'partOfSpeech')}
- + {getWordProperty(wordAnalysis, 'pronunciation')} = ({
{/* Content */} -
+
{/* Translation */} - +
+

中文翻譯

{getWordProperty(wordAnalysis, 'translation')}

- +
{/* Definition */} - -

+

+

英文定義

+

{getWordProperty(wordAnalysis, 'definition')}

- +
{/* Example */} - {(() => { - const example = getWordProperty(wordAnalysis, 'example') - return example && example !== 'null' && example !== 'undefined' - })() && ( - + {getWordProperty(wordAnalysis, 'example') && ( +
+

例句

-

+

"{getWordProperty(wordAnalysis, 'example')}"

-

+

{getWordProperty(wordAnalysis, 'exampleTranslation')}

- +
)} {/* Synonyms */} - {(() => { - const synonyms = getWordProperty(wordAnalysis, 'synonyms') - return synonyms && Array.isArray(synonyms) && synonyms.length > 0 - })() && ( - + {getWordProperty(wordAnalysis, 'synonyms')?.length > 0 && ( +
+

同義詞

{getWordProperty(wordAnalysis, 'synonyms')?.map((synonym: string, index: number) => ( = ({ ))}
- +
)}
{/* Save Button */} {onSaveWord && ( -
+