diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 0543a08..dc263dd 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -241,6 +241,37 @@ export function MainLayout() { }; }, []); + // 将主内容区的中心点写入 CSS 变量,供底部浮层(如配置面板操作栏)对齐到内容区而非整窗 + useLayoutEffect(() => { + const updateContentCenter = () => { + const el = contentRef.current; + if (!el) return; + const rect = el.getBoundingClientRect(); + const centerX = rect.left + rect.width / 2; + document.documentElement.style.setProperty('--content-center-x', `${centerX}px`); + }; + + updateContentCenter(); + + const resizeObserver = + typeof ResizeObserver !== 'undefined' && contentRef.current + ? new ResizeObserver(updateContentCenter) + : null; + + if (resizeObserver && contentRef.current) { + resizeObserver.observe(contentRef.current); + } + + window.addEventListener('resize', updateContentCenter); + + return () => { + if (resizeObserver) { + resizeObserver.disconnect(); + } + window.removeEventListener('resize', updateContentCenter); + }; + }, []); + // 5秒后自动收起品牌名称 useEffect(() => { brandCollapseTimer.current = setTimeout(() => { diff --git a/src/components/providers/ProviderNav/ProviderNav.module.scss b/src/components/providers/ProviderNav/ProviderNav.module.scss index 310a857..7c169d7 100644 --- a/src/components/providers/ProviderNav/ProviderNav.module.scss +++ b/src/components/providers/ProviderNav/ProviderNav.module.scss @@ -2,25 +2,34 @@ .navContainer { position: fixed; - right: 24px; - top: 50%; - transform: translateY(-50%); + left: var(--content-center-x, 50%); + bottom: calc(12px + env(safe-area-inset-bottom)); + transform: translateX(-50%); z-index: 50; pointer-events: auto; + width: fit-content; + max-width: calc(100vw - 24px); } .navList { position: relative; - display: flex; - flex-direction: column; - gap: 8px; - padding: 12px 8px; + display: inline-flex; + flex-direction: row; + gap: 6px; + padding: 10px 12px; background: rgba(255, 255, 255, 0.7); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 16px; + border-radius: 999px; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1); + overflow-x: auto; + scrollbar-width: none; + max-width: inherit; + + &::-webkit-scrollbar { + display: none; + } } .indicator { @@ -29,7 +38,7 @@ left: 0; pointer-events: none; opacity: 0; - border-radius: 10px; + border-radius: 999px; background: rgba(59, 130, 246, 0.15); box-shadow: inset 0 0 0 2px var(--primary-color); transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1), @@ -58,9 +67,10 @@ padding: 0; border: none; background: transparent; - border-radius: 10px; + border-radius: 999px; cursor: pointer; transition: background-color 0.2s ease, transform 0.15s ease; + flex: 0 0 auto; &:hover { background: rgba(0, 0, 0, 0.06); @@ -80,8 +90,8 @@ } .icon { - width: 28px; - height: 28px; + width: 24px; + height: 24px; object-fit: contain; } @@ -110,42 +120,20 @@ } } -// 小屏幕改为底部横向浮层 +// 小屏幕进一步收紧尺寸 @media (max-width: 1200px) { .navContainer { - top: auto; - right: auto; - left: 50%; - bottom: calc(12px + env(safe-area-inset-bottom)); - transform: translateX(-50%); - width: fit-content; - max-width: calc(100vw - 24px); + max-width: calc(100vw - 16px); } .navList { - display: inline-flex; - flex-direction: row; gap: 6px; padding: 8px 10px; - border-radius: 999px; - overflow-x: auto; - scrollbar-width: none; - max-width: inherit; - - &::-webkit-scrollbar { - display: none; - } - } - - .indicator { - border-radius: 999px; } .navItem { width: 36px; height: 36px; - border-radius: 999px; - flex: 0 0 auto; } .icon { diff --git a/src/components/providers/ProviderNav/ProviderNav.tsx b/src/components/providers/ProviderNav/ProviderNav.tsx index c58c6db..1dd7960 100644 --- a/src/components/providers/ProviderNav/ProviderNav.tsx +++ b/src/components/providers/ProviderNav/ProviderNav.tsx @@ -41,6 +41,7 @@ export function ProviderNav() { const [activeProvider, setActiveProvider] = useState(null); const contentScrollerRef = useRef(null); const navListRef = useRef(null); + const navContainerRef = useRef(null); const itemRefs = useRef>({ gemini: null, codex: null, @@ -170,6 +171,31 @@ export function ProviderNav() { updateIndicator(activeProvider); }, [activeProvider, shouldShow, updateIndicator]); + // Expose overlay height to the page, so it can reserve bottom padding and avoid being covered. + useLayoutEffect(() => { + if (!shouldShow) return; + + const el = navContainerRef.current; + if (!el) return; + + const updateHeight = () => { + const height = el.getBoundingClientRect().height; + document.documentElement.style.setProperty('--provider-nav-height', `${height}px`); + }; + + updateHeight(); + window.addEventListener('resize', updateHeight); + + const ro = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(updateHeight); + ro?.observe(el); + + return () => { + ro?.disconnect(); + window.removeEventListener('resize', updateHeight); + document.documentElement.style.removeProperty('--provider-nav-height'); + }; + }, [shouldShow]); + const scrollToProvider = (providerId: ProviderId) => { const container = getScrollContainer(); const element = document.getElementById(`provider-${providerId}`); @@ -204,7 +230,7 @@ export function ProviderNav() { }, [activeProvider, shouldShow, updateIndicator]); const navContent = ( -
+
- +