You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django环境下如何用JS/jQuery实现同标签页跨页面后台运行计时器

问题根源

你的计时器依赖当前页面的setInterval定时器实现秒数累加,页面跳转后原有页面的JS执行上下文会被完全销毁,定时器自动终止;同时你当前的状态存储key绑定了页面路径window.location.pathname,跳转后路径变化也无法读取到之前的计时状态。

最优实现方案(改造成本极低)

放弃秒数累加的计时逻辑,改用「时间戳差值计算」的方式实现计时,完全不受页面跳转影响:

  1. 调整localStorage存储字段,全部和任务ID绑定,不要关联页面路径:
    • ${object.id}/is_running:布尔值,标记计时器是否处于运行状态
    • ${object.id}/last_start_ts:最近一次启动/恢复的秒级时间戳
    • ${object.id}/total_paused_sec:暂停时累计的总运行秒数,恢复计时后直接叠加差值即可
  2. 页面加载时先读取对应任务的存储字段,如果是运行状态,直接用当前时间戳 - last_start_ts + total_paused_sec计算得到实时总运行秒数,渲染到页面后启动定时器每秒刷新一次显示即可,不需要做累加操作
  3. 操作逻辑调整:
    • 点击启动:设置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 22:27:04