fix: 修复账号管理移动端更多操作菜单溢出

This commit is contained in:
NellPoi
2026-07-20 17:18:42 +08:00
parent e625ce3b3b
commit 53c8ab5b00
4 changed files with 221 additions and 78 deletions
@@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest'
import { getFloatingPanelPosition } from '@/utils/floatingPanel'
describe('getFloatingPanelPosition', () => {
it('移动端使用视口安全边距,不再从靠左按钮向屏幕外展开', () => {
const position = getFloatingPanelPosition(
{ top: 160, right: 148, bottom: 200 },
393,
844
)
expect(position).toMatchObject({
top: 208,
bottom: null,
left: 16,
width: 320
})
expect(position.left + position.width).toBeLessThanOrEqual(393 - 16)
})
it('桌面端与按钮右侧对齐', () => {
const position = getFloatingPanelPosition(
{ top: 100, right: 1000, bottom: 140 },
1280,
900
)
expect(position.left).toBe(680)
expect(position.width).toBe(320)
})
it('按钮下方空间不足时改为向上展开', () => {
const position = getFloatingPanelPosition(
{ top: 700, right: 1000, bottom: 740 },
1280,
800
)
expect(position.top).toBeNull()
expect(position.bottom).toBe(108)
expect(position.maxHeight).toBe(560)
})
})
+56
View File
@@ -0,0 +1,56 @@
export interface FloatingPanelPosition {
top: number | null
bottom: number | null
left: number
width: number
maxHeight: number
}
export interface FloatingPanelOptions {
viewportPadding?: number
gap?: number
maxWidth?: number
maxHeightRatio?: number
mobileBreakpoint?: number
minComfortableHeight?: number
}
/**
* 计算挂载到 body 的浮层位置,避免触发按钮靠近视口边缘时浮层被挤到屏幕外。
*/
export const getFloatingPanelPosition = (
triggerRect: Pick<DOMRect, 'top' | 'right' | 'bottom'>,
viewportWidth: number,
viewportHeight: number,
options: FloatingPanelOptions = {}
): FloatingPanelPosition => {
const viewportPadding = options.viewportPadding ?? 16
const gap = options.gap ?? 8
const maxWidth = options.maxWidth ?? 320
const maxHeightRatio = options.maxHeightRatio ?? 0.7
const mobileBreakpoint = options.mobileBreakpoint ?? 768
const minComfortableHeight = options.minComfortableHeight ?? 240
const availableWidth = Math.max(0, viewportWidth - viewportPadding * 2)
const width = Math.min(maxWidth, availableWidth)
const left = viewportWidth < mobileBreakpoint
? viewportPadding
: Math.max(
viewportPadding,
Math.min(triggerRect.right - width, viewportWidth - width - viewportPadding)
)
const preferredMaxHeight = Math.max(0, Math.floor(viewportHeight * maxHeightRatio))
const spaceBelow = Math.max(0, viewportHeight - triggerRect.bottom - gap - viewportPadding)
const spaceAbove = Math.max(0, triggerRect.top - gap - viewportPadding)
const openAbove = spaceBelow < Math.min(minComfortableHeight, preferredMaxHeight) && spaceAbove > spaceBelow
const maxHeight = Math.min(preferredMaxHeight, openAbove ? spaceAbove : spaceBelow)
return {
top: openAbove ? null : triggerRect.bottom + gap,
bottom: openAbove ? viewportHeight - triggerRect.top + gap : null,
left,
width,
maxHeight
}
}