From 236051a91c50dc07c3183b4fb2c36802e1e47023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?= <7854742+wang_rumeng@user.noreply.gitee.com> Date: Fri, 17 Oct 2025 19:56:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=BF=E7=94=A8=20provider=20=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20deviceType?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ChatInputBox/ChatInputBox.tsx | 3 +- components/providers.tsx | 59 +++++---- hooks/useDeviceType.ts | 70 ---------- hooks/useDeviceType.tsx | 160 +++++++++++++++++++++++ 4 files changed, 193 insertions(+), 99 deletions(-) delete mode 100644 hooks/useDeviceType.ts create mode 100644 hooks/useDeviceType.tsx diff --git a/components/ChatInputBox/ChatInputBox.tsx b/components/ChatInputBox/ChatInputBox.tsx index f393f36..5d7e80a 100644 --- a/components/ChatInputBox/ChatInputBox.tsx +++ b/components/ChatInputBox/ChatInputBox.tsx @@ -1,6 +1,7 @@ "use client"; import { useState, useEffect, useRef, useCallback } from "react"; +import { useUpdateEffect } from '@/app/hooks/useUpdateEffect'; import { ChevronDown, ChevronUp, @@ -182,7 +183,7 @@ export function ChatInputBox({ noData }: { noData: boolean }) { }); // 从 localStorage 初始化配置 - useEffect(() => { + useUpdateEffect(() => { const savedConfig = localStorage.getItem('videoFlowConfig'); if (savedConfig) { try { diff --git a/components/providers.tsx b/components/providers.tsx index fc89d3f..71fe678 100644 --- a/components/providers.tsx +++ b/components/providers.tsx @@ -8,6 +8,7 @@ import AuthGuard from './auth/auth-guard'; import dynamic from 'next/dynamic'; import { registerGlobalMessage } from '@/components/common/GlobalMessage'; import { useEffect } from 'react'; +import { DeviceTypeProvider } from '@/hooks/useDeviceType'; const DevHelper = dynamic( () => import('@/utils/dev-helper').then(mod => (mod as any).default), @@ -22,34 +23,36 @@ export function Providers({ children }: { children: React.ReactNode }) { return ( - - - {children} - - - {process.env.NODE_ENV === 'development' && } - + + + + {children} + + + {process.env.NODE_ENV === 'development' && } + + ); } \ No newline at end of file diff --git a/hooks/useDeviceType.ts b/hooks/useDeviceType.ts deleted file mode 100644 index 30bb631..0000000 --- a/hooks/useDeviceType.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { useState, useEffect } from 'react'; - -// 定义设备类型枚举 -export enum DeviceType { - MOBILE = 'mobile', - TABLET = 'tablet', - DESKTOP = 'desktop' -} - -// 定义屏幕断点 -const BREAKPOINTS = { - MOBILE: 480, - TABLET: 1024, - DESKTOP: 1025 -}; - -export function useDeviceType() { - const [deviceType, setDeviceType] = useState(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; - }; - - const handleResize = () => { - const width = window.innerWidth; - const height = window.innerHeight; - - setWindowSize({ width, height }); - setDeviceType(getDeviceType(width)); - - // 移动端动态视口高度处理 - if (width <= BREAKPOINTS.TABLET) { - const vh = height * 0.01; - document.documentElement.style.setProperty('--vh', `${vh}px`); - } - }; - - // 初始化 - handleResize(); - - // 添加事件监听 - window.addEventListener('resize', handleResize); - window.addEventListener('orientationchange', () => { - // 延迟处理以确保获取正确的视口尺寸 - setTimeout(handleResize, 100); - }); - - return () => { - window.removeEventListener('resize', handleResize); - window.removeEventListener('orientationchange', handleResize); - }; - }, []); - - return { - deviceType, - windowSize, - isMobile: deviceType === DeviceType.MOBILE, - isTablet: deviceType === DeviceType.TABLET, - isDesktop: deviceType === DeviceType.DESKTOP, - /** 是否为移动端设备(包括平板) */ - isMobileDevice: deviceType === DeviceType.MOBILE || deviceType === DeviceType.TABLET - }; -} \ No newline at end of file diff --git a/hooks/useDeviceType.tsx b/hooks/useDeviceType.tsx new file mode 100644 index 0000000..122223c --- /dev/null +++ b/hooks/useDeviceType.tsx @@ -0,0 +1,160 @@ +'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; +} +