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;
+}
+