Django环境下如何用JS/jQuery实现同标签页跨页面后台运行计时器
问题根源
你的计时器依赖当前页面的setInterval定时器实现秒数累加,页面跳转后原有页面的JS执行上下文会被完全销毁,定时器自动终止;同时你当前的状态存储key绑定了页面路径window.location.pathname,跳转后路径变化也无法读取到之前的计时状态。
最优实现方案(改造成本极低)
放弃秒数累加的计时逻辑,改用「时间戳差值计算」的方式实现计时,完全不受页面跳转影响:
- 调整localStorage存储字段,全部和任务ID绑定,不要关联页面路径:
${object.id}/is_running:布尔值,标记计时器是否处于运行状态${object.id}/last_start_ts:最近一次启动/恢复的秒级时间戳${object.id}/total_paused_sec:暂停时累计的总运行秒数,恢复计时后直接叠加差值即可
- 页面加载时先读取对应任务的存储字段,如果是运行状态,直接用
当前时间戳 - last_start_ts + total_paused_sec计算得到实时总运行秒数,渲染到页面后启动定时器每秒刷新一次显示即可,不需要做累加操作 - 操作逻辑调整:
- 点击启动:设置is_running为true,写入当前时间戳到last_start_ts
- 点击暂停:计算当前累计秒数存到total_paused_sec,设置is_running为false,清除定时器
- 点击完成:计算总时长存库,清除对应任务的所有localStorage字段
调整后的核心JS代码示例
const taskId = {{ object.id }}; const hrspan = document.getElementById("hours"); const minspan = document.getElementById("mins"); const secspan = document.getElementById("secs"); const startBtn = document.getElementById("startPause"); const pauseBtn = document.getElementById("Pause"); const completeBtn = document.getElementById("complete"); let timer = null; // 格式化时间并渲染到页面 function renderTime(totalSec) { const hour = Math.floor(totalSec / 3600); const min = Math.floor((totalSec % 3600) / 60); const sec = totalSec % 60; hrspan.innerHTML = String(hour).padStart(2, '0'); minspan.innerHTML = String(min).padStart(2, '0'); secspan.innerHTML = String(sec).padStart(2, '0'); } // 页面加载初始化 window.addEventListener('load', () => { const isRunning = localStorage.getItem(`${taskId}/is_running`) === 'true'; const totalPausedSec = Number(localStorage.getItem(`${taskId}/total_paused_sec`) || 0); pauseBtn.disabled =!isRunning; startBtn.disabled = isRunning; if (isRunning) { const lastStartTs = Number(localStorage.getItem(`${taskId}/last_start_ts`)); // 先计算跳转期间的时长,直接渲染 const now = Math.ceil(Date.now() / 1000); const totalSec = now - lastStartTs + totalPausedSec; renderTime(totalSec); // 启动定时器每秒刷新 timer = setInterval(() => { const now = Math.ceil(Date.now() / 1000); const currentTotal = now - lastStartTs + totalPausedSec; renderTime(currentTotal); }, 1000); } else { renderTime(totalPausedSec); } }); // 暂停逻辑 function Pause() { clearInterval(timer); const lastStartTs = Number(localStorage.getItem(`${taskId}/last_start_ts`)); const totalPausedSec = Number(localStorage.getItem(`${taskId}/total_paused_sec`) || 0); const now = Math.ceil(Date.now() / 1000); const newTotal = now - lastStartTs + totalPausedSec; // 更新存储 localStorage.setItem(`${taskId}/is_running`, 'false'); localStorage.setItem(`${taskId}/total_paused_sec`, newTotal); // 更新按钮状态 pauseBtn.disabled = true; startBtn.disabled = false; // 同步暂停时间到后端 ajax_call(0, now, 0, 0, 'OG'); } // 启动/恢复逻辑 function startPause() { const now = Math.ceil(Date.now() / 1000); localStorage.setItem(`${taskId}/is_running`, 'true'); localStorage.setItem(`${taskId}/last_start_ts`, now); // 更新按钮状态 startBtn.disabled = true; pauseBtn.disabled = false; // 同步恢复/启动时间到后端 const totalPausedSec = Number(localStorage.getItem(`${taskId}/total_paused_sec`) || 0); if (totalPausedSec > 0) { ajax_call(0, 0, now, 0, 'OG'); } else { ajax_call(now, 0, 0, 0, 'OG'); } // 启动定时器刷新 timer = setInterval(() => { const currentNow = Math.ceil(Date.now() / 1000); const currentTotal = currentNow - now + totalPausedSec; renderTime(currentTotal); }, 1000); } // 完成逻辑 function complete() { const isRunning = localStorage.getItem(`${taskId}/is_running`) === 'true'; let totalSec = Number(localStorage.getItem(`${taskId}/total_paused_sec`) || 0); if (isRunning) { const lastStartTs = Number(localStorage.getItem(`${taskId}/last_start_ts`)); const now = Math.ceil(Date.now() / 1000); totalSec = now - lastStartTs + totalSec; clearInterval(timer); } const result = confirm("Are you sure?"); if(result){ const now = Math.ceil(Date.now() / 1000); completeBtn.disabled = true; document.getElementById('work_done').style.display = 'block'; // 渲染总时长 const hour = Math.floor(totalSec / 3600); const min = Math.floor((totalSec % 3600) / 60); const sec = totalSec % 60; if(hour == 0){ document.getElementById('work_done_time').innerHTML = min+' minutes '+sec+' seconds'; } else{ document.getElementById('work_done_time').innerHTML = hour+' hours '+min+' minutes '+sec+' seconds'; } // 同步到后端 ajax_call(0,0,0,now, 'CM'); // 清除当前任务的存储数据 localStorage.removeItem(`${taskId}/is_running`); localStorage.removeItem(`${taskId}/last_start_ts`); localStorage.removeItem(`${taskId}/total_paused_sec`); } } // 原有ajax逻辑保持不变即可 function ajax_call(S_time, P_time, R_time, ST_time, stage = 'NE'){ $.ajax({ url: '/update_counter/', data: { 'taskID': taskId, 'S_Time': S_time, 'P_Time': P_time, 'R_Time': R_time, 'ST_Time': ST_time, 'Stage': stage, 'csrfmiddlewaretoken': '{{ csrf_token }}', }, type: 'POST' }).done(function(response){ console.log(response); }); }
可选优化方案
如果需要完全避免页面跳转后的定时器重新初始化,可以将项目调整为单页应用模式,使用pjax或者前端框架的客户端路由实现页面切换,页面不会整页刷新,计时器可以一直挂载在全局上下文持续运行,不过改造成本相对更高。
内容的提问来源于stack exchange,提问作者varisha15
相关产品推荐
相关产品推荐

