| 78 | // Reset state and determine alignment when popover opens/closes |
| 79 | useEffect(() => { |
| 80 | const updatePosition = () => { |
| 81 | if (!isOpen || !containerRef.current || isMobileDevice) return; |
| 82 | |
| 83 | // 桌面端定位逻辑 |
| 84 | const rect = containerRef.current.getBoundingClientRect(); |
| 85 | const vWidth = window.innerWidth; |
| 86 | const vHeight = window.innerHeight; |
| 87 | |
| 88 | const popoverWidth = 320; |
| 89 | const estimatedPopoverHeight = 480; |
| 90 | |
| 91 | // 1. 水平定位 |
| 92 | const wouldOverflowRight = rect.left + popoverWidth > vWidth - 20; |
| 93 | const wouldOverflowLeftIfRightAligned = rect.right - popoverWidth < 20; |
| 94 | const isInRightHalf = rect.left > vWidth / 2; |
| 95 | const shouldAlignRight = (wouldOverflowRight || isInRightHalf) && !wouldOverflowLeftIfRightAligned; |
| 96 | setAlignRight(shouldAlignRight); |
| 97 | |
| 98 | // 2. 垂直定位 |
| 99 | const spaceBelow = vHeight - rect.bottom; |
| 100 | const spaceAbove = rect.top; |
| 101 | const shouldAlignTop = spaceBelow < estimatedPopoverHeight && spaceAbove > spaceBelow; |
| 102 | setAlignTop(shouldAlignTop); |
| 103 | |
| 104 | // 3. 计算 fixed 定位的像素坐标 |
| 105 | // 向上展开时用 bottom(距视口底部距离),确保弹窗底边贴近触发词条顶部而不遮挡 |
| 106 | const left = shouldAlignRight ? rect.right - popoverWidth : rect.left; |
| 107 | if (shouldAlignTop) { |
| 108 | const bottom = vHeight - rect.top + 8; |
| 109 | const maxH = Math.min(rect.top - 16, 520); // 上方可用空间 |
| 110 | setPopoverPos({ bottom, top: 'auto', left, maxHeight: maxH }); |
| 111 | } else { |
| 112 | const maxH = Math.min(vHeight - rect.bottom - 16, 520); // 下方可用空间 |
| 113 | setPopoverPos({ top: rect.bottom + 8, bottom: 'auto', left, maxHeight: maxH }); |
| 114 | } |
| 115 | |
| 116 | setMaxPopoverWidth(`${Math.min(vWidth - 32, 320)}px`); |
| 117 | }; |
| 118 | |
| 119 | if (!isOpen) { |
| 120 | setIsAdding(false); |