forked from 77media/video-flow
使用 provider 使用 deviceType
This commit is contained in:
parent
f680e7c002
commit
236051a91c
@ -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 {
|
||||
|
||||
@ -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 (
|
||||
<Provider store={store}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="dark"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<AuthGuard>
|
||||
{children}
|
||||
</AuthGuard>
|
||||
<Toaster
|
||||
position="bottom-left"
|
||||
theme="dark"
|
||||
richColors
|
||||
toastOptions={{
|
||||
// 统一配置所有 toast 的样式
|
||||
classNames: {
|
||||
toast: "dark:bg-zinc-900 dark:text-zinc-100 dark:border-zinc-800",
|
||||
title: "dark:text-zinc-100 font-medium",
|
||||
description: "dark:text-zinc-400",
|
||||
actionButton: "dark:bg-zinc-700 dark:text-zinc-100",
|
||||
cancelButton: "dark:bg-zinc-600 dark:text-zinc-100",
|
||||
closeButton: "dark:text-zinc-300 hover:text-zinc-100"
|
||||
},
|
||||
duration: 3000,
|
||||
}}
|
||||
/>
|
||||
{process.env.NODE_ENV === 'development' && <DevHelper />}
|
||||
</ThemeProvider>
|
||||
<DeviceTypeProvider>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="dark"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<AuthGuard>
|
||||
{children}
|
||||
</AuthGuard>
|
||||
<Toaster
|
||||
position="bottom-left"
|
||||
theme="dark"
|
||||
richColors
|
||||
toastOptions={{
|
||||
// 统一配置所有 toast 的样式
|
||||
classNames: {
|
||||
toast: "dark:bg-zinc-900 dark:text-zinc-100 dark:border-zinc-800",
|
||||
title: "dark:text-zinc-100 font-medium",
|
||||
description: "dark:text-zinc-400",
|
||||
actionButton: "dark:bg-zinc-700 dark:text-zinc-100",
|
||||
cancelButton: "dark:bg-zinc-600 dark:text-zinc-100",
|
||||
closeButton: "dark:text-zinc-300 hover:text-zinc-100"
|
||||
},
|
||||
duration: 3000,
|
||||
}}
|
||||
/>
|
||||
{process.env.NODE_ENV === 'development' && <DevHelper />}
|
||||
</ThemeProvider>
|
||||
</DeviceTypeProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
@ -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>(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
|
||||
};
|
||||
}
|
||||
160
hooks/useDeviceType.tsx
Normal file
160
hooks/useDeviceType.tsx
Normal file
@ -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<DeviceTypeContextValue | null>(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<DeviceType>(() => {
|
||||
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<DeviceTypeContextValue>(() => ({
|
||||
deviceType,
|
||||
windowSize,
|
||||
isMobile: deviceType === DeviceType.MOBILE,
|
||||
isTablet: deviceType === DeviceType.TABLET,
|
||||
isDesktop: deviceType === DeviceType.DESKTOP,
|
||||
isMobileDevice: deviceType === DeviceType.MOBILE || deviceType === DeviceType.TABLET
|
||||
}), [deviceType, windowSize]);
|
||||
|
||||
return (
|
||||
<DeviceTypeContext.Provider value={value}>
|
||||
{children}
|
||||
</DeviceTypeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user