From 8776ce4181da9cb359af676e2316707ac55136c3 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Tue, 21 Apr 2026 20:18:33 +0800 Subject: [PATCH] fix(label): inactivityTimer --- components/label/LabelNossr.tsx | 98 +++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/components/label/LabelNossr.tsx b/components/label/LabelNossr.tsx index 1b34bc8..eb0044f 100644 --- a/components/label/LabelNossr.tsx +++ b/components/label/LabelNossr.tsx @@ -49,11 +49,7 @@ import { useDescToolsStore } from "./useDescToolsStore" import { useKeyboardStore } from "./useKeyBoardStore" import { usePaperStore } from "./usePaperStore" import { useRightToolsStore } from "./useRightToolsStore" -import { - useIntervalStore, - useLabelTimeStore, - useTimerStore, -} from "./useTimerStore" +import { useIntervalStore, useLabelTimeStore } from "./useTimerStore" import { useTopToolsStore } from "./useTopToolsStore" import { findGroupKey, isEditableKeyboardTarget } from "./util" import { safeClone } from "./utils/clone" @@ -89,6 +85,8 @@ const RIGHT_PANEL_SEPARATOR = const RIGHT_PANEL_SOFT_MAIN_MIN_WIDTH = 420 const RIGHT_PANEL_HARD_MAIN_MIN_WIDTH = 260 const RIGHT_PANEL_TARGET_WIDTH = 350 +const INACTIVITY_VIEW_DELAY_MS = 5 * 60 * 1000 +const INACTIVITY_ACTIVITY_THROTTLE_MS = 1000 // utils const getRectPoints = ([sx, sy, w, h]: number[]) => { @@ -280,6 +278,8 @@ const LabelPage = ({ const topRef = useRef(null) const autoSaveTimerRef = useRef | null>(null) const autoSaveInProgressRef = useRef(false) + const inactivityTimerRef = useRef | null>(null) + const lastInactivityResetAtRef = useRef(0) const paperContainerRef = useRef(null) const qaToolContainerRef = useRef(null) const [isConfirmSave, setIsConfirmSave] = useState(false) @@ -2472,22 +2472,86 @@ const LabelPage = ({ } }, [updateSplitterSizes]) + const clearInactivityTimer = useCallback(() => { + if (inactivityTimerRef.current !== null) { + clearTimeout(inactivityTimerRef.current) + inactivityTimerRef.current = null + } + }, []) + + const scheduleInactivityViewMode = useCallback(() => { + clearInactivityTimer() + inactivityTimerRef.current = setTimeout(() => { + const topToolsState = useTopToolsStore.getState() + if (!topToolsState.isView) { + topToolsState.setIsView(true) + } + }, INACTIVITY_VIEW_DELAY_MS) + }, [clearInactivityTimer]) + + const reportUserActivity = useCallback( + (force = false) => { + if (useTopToolsStore.getState().isView) return + + const now = Date.now() + if ( + !force && + now - lastInactivityResetAtRef.current < INACTIVITY_ACTIVITY_THROTTLE_MS + ) { + return + } + + lastInactivityResetAtRef.current = now + scheduleInactivityViewMode() + }, + [scheduleInactivityViewMode] + ) + useEffect(() => { - if (!isView) { - const delay = 5 * 60000 - // 编辑模式下,修改labelData时启动或更新定时器 - useTimerStore.getState().startTimer(() => { - useTopToolsStore.getState().setIsView(true) - }, delay) - } else { - useTimerStore.getState().clearTimer() + if (isView) { + lastInactivityResetAtRef.current = 0 + clearInactivityTimer() + return } - // 组件卸载时清理定时器 - return () => { - useTimerStore.getState().clearTimer() + // 使用真实输入事件追踪活跃度,避免进行中的标注因 labelData 未变化被误判为空闲。 + reportUserActivity(true) + + const handlePointerDown = () => { + reportUserActivity(true) } - }, [isView, labelData]) + const handlePointerMove = (event: PointerEvent) => { + if (event.buttons !== 0) { + reportUserActivity() + } + } + const handleWheel = () => { + reportUserActivity() + } + const handleKeyDown = () => { + reportUserActivity(true) + } + const handleVisibilityChange = () => { + if (document.visibilityState === "visible") { + reportUserActivity(true) + } + } + + window.addEventListener("pointerdown", handlePointerDown, true) + window.addEventListener("pointermove", handlePointerMove, true) + window.addEventListener("wheel", handleWheel, true) + window.addEventListener("keydown", handleKeyDown, true) + document.addEventListener("visibilitychange", handleVisibilityChange) + + return () => { + window.removeEventListener("pointerdown", handlePointerDown, true) + window.removeEventListener("pointermove", handlePointerMove, true) + window.removeEventListener("wheel", handleWheel, true) + window.removeEventListener("keydown", handleKeyDown, true) + document.removeEventListener("visibilitychange", handleVisibilityChange) + clearInactivityTimer() + } + }, [clearInactivityTimer, isView, reportUserActivity]) // auto save useEffect(() => {