'use client'; import { createContext, useContext, useState, useEffect, useMemo, ReactNode } from 'react'; /** 设备类型枚举 */ export enum DeviceType { MOBILE = 'mobile', TABLET = 'tablet', DESKTOP = 'desktop' } /** 屏幕断点配置 */ const BREAKPOINTS = { MOBILE: 480, TABLET: 1024, DESKTOP: 1025 }; /** 设备类型上下文数据接口 */ interface DeviceTypeContextValue { deviceType: DeviceType; windowSize: { width: number; height: number; }; isMobile: boolean; isTablet: boolean; isDesktop: boolean; /** 是否为移动端设备(包括平板) */ isMobileDevice: boolean; } const DeviceTypeContext = createContext(null); /** * Device type provider component * Centralized device type detection and window size management * @param {ReactNode} children - Child components */ export function DeviceTypeProvider({ children }: { children: ReactNode }) { const [deviceType, setDeviceType] = useState(() => { if (typeof window === 'undefined') return DeviceType.DESKTOP; const width = window.innerWidth; if (width <= BREAKPOINTS.MOBILE) return DeviceType.MOBILE; if (width <= BREAKPOINTS.TABLET) return DeviceType.TABLET; return DeviceType.DESKTOP; }); const [windowSize, setWindowSize] = useState({ width: typeof window !== 'undefined' ? window.innerWidth : 0, height: typeof window !== 'undefined' ? window.innerHeight : 0 }); useEffect(() => { const getDeviceType = (width: number): DeviceType => { if (width <= BREAKPOINTS.MOBILE) return DeviceType.MOBILE; if (width <= BREAKPOINTS.TABLET) return DeviceType.TABLET; return DeviceType.DESKTOP; }; let timeoutId: NodeJS.Timeout | null = null; let isInitialized = false; const handleResize = () => { const width = window.innerWidth; const height = window.innerHeight; const newDeviceType = getDeviceType(width); // Only update if values actually changed setWindowSize(prev => { const changed = prev.width !== width || prev.height !== height; if (changed) { return { width, height }; } return prev; }); setDeviceType(prev => { const changed = prev !== newDeviceType; if (changed) { return newDeviceType; } return prev; }); // 移动端动态视口高度处理 if (width <= BREAKPOINTS.TABLET) { const vh = height * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); } if (!isInitialized) { isInitialized = true; } }; const debouncedHandleResize = () => { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(handleResize, 100); }; // 初始化 - 因为 state 已经在初始化时设置了正确的值,这里只需要设置 vh 变量 const width = window.innerWidth; const height = window.innerHeight; if (width <= BREAKPOINTS.TABLET) { const vh = height * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); } isInitialized = true; // 添加事件监听 window.addEventListener('resize', debouncedHandleResize); window.addEventListener('orientationchange', () => { // 延迟处理以确保获取正确的视口尺寸 setTimeout(handleResize, 100); }); return () => { if (timeoutId) { clearTimeout(timeoutId); } window.removeEventListener('resize', debouncedHandleResize); window.removeEventListener('orientationchange', handleResize); }; }, []); const value = useMemo(() => ({ deviceType, windowSize, isMobile: deviceType === DeviceType.MOBILE, isTablet: deviceType === DeviceType.TABLET, isDesktop: deviceType === DeviceType.DESKTOP, isMobileDevice: deviceType === DeviceType.MOBILE || deviceType === DeviceType.TABLET }), [deviceType, windowSize]); return ( {children} ); } /** * Hook to access device type context * @returns {DeviceTypeContextValue} Device type information * @throws {Error} If used outside DeviceTypeProvider */ export function useDeviceType(): DeviceTypeContextValue { const context = useContext(DeviceTypeContext); if (!context) { throw new Error('useDeviceType must be used within DeviceTypeProvider'); } return context; }