/** * useIdleTimer Hook * * 检测用户空闲状态(默认 5 分钟无操作)。 * 监听鼠标移动、键盘按下、触摸等事件,空闲时触发 onIdle 回调。 * * 用法: * const { isIdle, resetTimer } = useIdleTimer({ * timeout: 5 * 60 * 1000, // 5 分钟 * onIdle: () => console.log('用户空闲'), * onActive: () => console.log('用户恢复活动'), * }) */ import { useState, useEffect, useCallback, useRef } from 'react' interface UseIdleTimerOptions { /** 空闲超时时间(ms),默认 5 分钟 */ timeout?: number /** 空闲时触发 */ onIdle?: () => void /** 从空闲恢复活动时触发 */ onActive?: () => void /** 是否启用,默认 true */ enabled?: boolean } interface UseIdleTimerReturn { /** 当前是否空闲 */ isIdle: boolean /** 手动重置空闲计时器 */ resetTimer: () => void } const EVENTS = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll', 'wheel'] as const export function useIdleTimer(options: UseIdleTimerOptions = {}): UseIdleTimerReturn { const { timeout = 5 * 60 * 1000, onIdle, onActive, enabled = true } = options const [isIdle, setIsIdle] = useState(false) const timerRef = useRef | null>(null) const isIdleRef = useRef(false) const onIdleRef = useRef(onIdle) const onActiveRef = useRef(onActive) onIdleRef.current = onIdle onActiveRef.current = onActive const clearTimer = useCallback(() => { if (timerRef.current) { clearTimeout(timerRef.current) timerRef.current = null } }, []) const startTimer = useCallback(() => { clearTimer() if (!enabled) return timerRef.current = setTimeout(() => { isIdleRef.current = true setIsIdle(true) onIdleRef.current?.() }, timeout) }, [timeout, enabled, clearTimer]) const resetTimer = useCallback(() => { if (!enabled) return if (isIdleRef.current) { isIdleRef.current = false setIsIdle(false) onActiveRef.current?.() } startTimer() }, [enabled, startTimer]) useEffect(() => { if (!enabled) { clearTimer() return } startTimer() const handleActivity = () => { resetTimer() } EVENTS.forEach((event) => { window.addEventListener(event, handleActivity, { passive: true }) }) return () => { clearTimer() EVENTS.forEach((event) => { window.removeEventListener(event, handleActivity) }) } }, [enabled, startTimer, resetTimer, clearTimer]) return { isIdle, resetTimer } }