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 && (
-
+