| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- import { useState, useEffect, useRef } from 'react';
- import { Navbar } from '../components/layout/Navbar';
- import type { TabId } from '../components/layout/Navbar';
- interface MainLayoutProps {
- children: (activeTab: TabId) => React.ReactNode;
- }
- // 这里的顺序决定了滑动的顺序,必须是 6 个!
- const TAB_ORDER: TabId[] = ['dashboard', 'relations', 'requirements', 'capabilities', 'tools', 'knowledge'];
- const MIN_SWITCH_INTERVAL = 1000;
- export function MainLayout({ children }: MainLayoutProps) {
- const [activeTab, setActiveTab] = useState<TabId>('dashboard');
- const lastSwitchTime = useRef(0);
- const accumX = useRef(0);
- const touchStartX = useRef(0);
- const wheelTimeout = useRef<NodeJS.Timeout>();
- useEffect(() => {
- const handleTabSwitch = (direction: 1 | -1) => {
- const now = Date.now();
- if (now - lastSwitchTime.current < MIN_SWITCH_INTERVAL) return;
- setActiveTab(prev => {
- const currentIndex = TAB_ORDER.indexOf(prev);
- const nextIndex = currentIndex + direction;
- if (nextIndex >= 0 && nextIndex < TAB_ORDER.length) {
- lastSwitchTime.current = now;
- return TAB_ORDER[nextIndex];
- }
- return prev;
- });
- };
- const isInsideHorizontallyScrollable = (targetNode: EventTarget | null) => {
- let target = targetNode as HTMLElement | null;
- while (target && target !== document.body) {
- if (target.scrollWidth > target.clientWidth) {
- const style = window.getComputedStyle(target);
- if (style.overflowX === 'auto' || style.overflowX === 'scroll' || style.overflowX === 'overlay') {
- return true;
- }
- }
- target = target.parentElement;
- }
- return false;
- };
- const handleWheel = (e: WheelEvent) => {
- if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) return;
- if (isInsideHorizontallyScrollable(e.target)) return;
- if (e.cancelable) e.preventDefault();
- const now = Date.now();
- if (now - lastSwitchTime.current < MIN_SWITCH_INTERVAL) {
- accumX.current = 0;
- return;
- }
- accumX.current += e.deltaX;
- clearTimeout(wheelTimeout.current);
- wheelTimeout.current = setTimeout(() => { accumX.current = 0; }, 150);
- if (accumX.current > 120) {
- handleTabSwitch(1);
- accumX.current = 0;
- } else if (accumX.current < -120) {
- handleTabSwitch(-1);
- accumX.current = 0;
- }
- };
- const handleTouchStart = (e: TouchEvent) => {
- if (isInsideHorizontallyScrollable(e.target)) return;
- touchStartX.current = e.touches[0].clientX;
- };
- const handleTouchEnd = (e: TouchEvent) => {
- if (isInsideHorizontallyScrollable(e.target)) return;
- const now = Date.now();
- if (now - lastSwitchTime.current < MIN_SWITCH_INTERVAL) return;
- const diffX = touchStartX.current - e.changedTouches[0].clientX;
- if (diffX > 60) handleTabSwitch(1);
- else if (diffX < -60) handleTabSwitch(-1);
- };
- window.addEventListener('wheel', handleWheel, { passive: false });
- window.addEventListener('touchstart', handleTouchStart, { passive: true });
- window.addEventListener('touchend', handleTouchEnd, { passive: true });
- return () => {
- window.removeEventListener('wheel', handleWheel);
- window.removeEventListener('touchstart', handleTouchStart);
- window.removeEventListener('touchend', handleTouchEnd);
- clearTimeout(wheelTimeout.current);
- };
- }, []);
- const currentIndex = TAB_ORDER.indexOf(activeTab);
- const totalTabs = TAB_ORDER.length;
- return (
- <div className="min-h-screen bg-slate-50 flex flex-col overflow-x-hidden" style={{ overscrollBehaviorX: 'none' }}>
- <Navbar activeTab={activeTab} onTabChange={setActiveTab} />
- <main className="flex-1 w-full overflow-x-hidden relative">
- <div
- className="flex h-full will-change-transform"
- style={{
- // 轨道总宽度由标签页数量动态决定,6个页面就是 600%
- width: `${totalTabs * 100}%`,
- // 偏移量
- transform: `translateX(-${(currentIndex / totalTabs) * 100}%)`,
- transition: 'transform 0.7s cubic-bezier(0.34, 1.3, 0.64, 1)'
- }}
- >
- {TAB_ORDER.map((tab) => (
- <div
- key={tab}
- className="shrink-0 flex justify-center pb-12"
- // 每个页面的宽度是总宽度的 1/6
- style={{ width: `${100 / totalTabs}%` }}
- >
- <div className="w-full max-w-[1600px] px-6 py-6">
- {children(tab)}
- </div>
- </div>
- ))}
- </div>
- </main>
- </div>
- );
- }
|