☰
用 AI 做网页版番茄钟:自定义时长、暂停继续、结束提醒|AI 编程实战第 2 课
2026/10/3 7:29:04 网站建设 项目流程

上一课,我们做出了个人效率工具箱首页。这一课,把其中的“番茄钟”做成真正能用的小工具。

完成后,你可以设置 1~120 分钟的专注时间,开始倒计时,中途暂停,再继续计时。时间结束后,页面和浏览器标签页都会显示提醒。

这次学习的重点是:怎样让 AI 在已有代码上增加功能,以及如何检查新增功能有没有按要求工作。

先把上一课的完整代码发给 AI,再附上下面这段需求:

请在现有工具箱首页中实现番茄钟功能。

默认时长为 25 分钟,允许输入 1~120 分钟的整数。
提供开始、暂停、继续和重置操作。
暂停后要保留剩余时间,继续时从剩余时间开始。
重复点击开始按钮时,只能有一个计时器运行。
计时开始后锁定时长输入框,重置或结束后允许调整。
计时结束时,在页面上显示提醒,并修改浏览器标签页标题。
使用结束时间减去当前时间来计算剩余时间。
保留待办清单和便签卡片,输出完整 HTML 文件。

这里的“暂停后保留剩余时间”“重复点击只能启动一个计时器”,就是容易被遗漏的细节。把这些行为提前写进需求,后面就有明确的检查标准。

下面是一份完整参考代码。用它替换上一课index.html中的内容,保存后用浏览器打开。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>个人效率工具箱</title> <style> * { box-sizing: border-box; } body { margin: 0; color: #1f2937; background: #f5f7fb; font-family: "Microsoft YaHei", Arial, sans-serif; } header, main, #message { max-width: 960px; margin-left: auto; margin-right: auto; } header { padding: 40px 20px 24px; } header h1 { margin: 0 0 12px; } header p, .card p { color: #6b7280; line-height: 1.6; } main { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; padding: 0 20px; } .card { padding: 24px; background: white; border-radius: 16px; box-shadow: 0 8px 24px #1f293712; } .card h2 { margin: 0 0 16px; font-size: 21px; } input { width: 80px; padding: 8px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 16px; } #clock { margin: 24px 0; font-size: 52px; font-weight: bold; text-align: center; font-variant-numeric: tabular-nums; } .controls { display: flex; flex-wrap: wrap; gap: 8px; } button { padding: 10px 14px; border: 0; border-radius: 8px; color: white; background: #4f46e5; cursor: pointer; } button:hover { background: #4338ca; } #status { min-height: 48px; } #message { padding: 16px 20px; color: #3730a3; } </style> </head> <body> <header> <h1>我的效率工具箱</h1> <p>先选一件事,专注完成它。</p> </header> <main> <article class="card"> <h2>🍅 番茄钟</h2> <label for="duration">专注时长:</label> <input id="duration" type="number" min="1" max="120" step="1" value="25"> <span>分钟</span> <div id="clock">25:00</div> <div class="controls"> <button id="start" type="button">开始 / 继续</button> <button id="pause" type="button">暂停</button> <button id="reset" type="button">重置</button> </div> <p id="status" role="status">准备开始</p> </article> <article class="card"> <h2>✅ 待办清单</h2> <p>记录今天要完成的事情。</p> <button type="button" data-module="待办清单">查看功能</button> </article> <article class="card"> <h2>📝 便签</h2> <p>随手记下想法和提醒。</p> <button type="button" data-module="便签">查看功能</button> </article> </main> <p id="message" role="status">工具箱正在逐步完善。</p> <script> const durationInput = document.querySelector("#duration"); const clock = document.querySelector("#clock"); const status = document.querySelector("#status"); let remainingMs = 25 * 60 * 1000; let endAt = 0; let timerId = null; let started = false; // 检查输入,并将分钟转换成毫秒 function readDuration() { const minutes = Number(durationInput.value); if (!Number.isInteger(minutes) || minutes < 1 || minutes > 120) { status.textContent = "请输入 1~120 分钟的整数。"; return null; } return minutes * 60 * 1000; } // 将剩余时间显示为 分钟:秒 function render() { const totalSeconds = Math.ceil(remainingMs / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; clock.textContent = String(minutes).padStart(2, "0") + ":" + String(seconds).padStart(2, "0"); } function stopTimer() { clearInterval(timerId); timerId = null; } function finish() { stopTimer(); remainingMs = 0; started = false; durationInput.disabled = false; render(); status.textContent = "时间到!完成一轮专注,休息一下吧。"; document.title = "时间到!|效率工具箱"; } function update() { remainingMs = Math.max(0, endAt - Date.now()); render(); if (remainingMs === 0) { finish(); } else { document.title = clock.textContent + "|专注中"; } } document.querySelector("#start").addEventListener("click", () => { // 防止重复启动计时器 if (timerId !== null) return; // 新的一轮读取时长,暂停后继续则沿用剩余时间 if (!started) { const duration = readDuration(); if (duration === null) return; remainingMs = duration; started = true; } endAt = Date.now() + remainingMs; durationInput.disabled = true; status.textContent = "专注中……"; timerId = setInterval(update, 250); update(); }); document.querySelector("#pause").addEventListener("click", () => { if (timerId === null) return; update(); if (timerId === null) return; stopTimer(); status.textContent = "已暂停,点击开始可以继续。"; document.title = "已暂停|效率工具箱"; }); function reset() { const duration = readDuration(); if (duration === null) return; stopTimer(); started = false; durationInput.disabled = false; remainingMs = duration; render(); status.textContent = "已重置,可以调整时长。"; document.title = "个人效率工具箱"; } document.querySelector("#reset").addEventListener("click", reset); durationInput.addEventListener("change", () => { if (!started) reset(); }); // 返回页面时立即更新剩余时间 document.addEventListener("visibilitychange", () => { if (!document.hidden && timerId !== null) update(); }); const message = document.querySelector("#message"); document.querySelectorAll("[data-module]").forEach((button) => { button.addEventListener("click", () => { message.textContent = button.dataset.module + "将在后续课程逐步完成。"; }); }); </script> </body> </html>

代码里最值得理解的是这一句:

remainingMs = Math.max(0, endAt - Date.now());

endAt是预计结束的时间,Date.now()是当前时间,两者相减就是剩余时间。

这样处理,是因为setInterval()的实际执行间隔可能比设置值更长。如果每次回调都简单减一秒,延迟可能逐渐累积。这里让计时器负责更新显示,每次更新都重新计算剩余时间。参考:MDN setInterval 文档。

打开网页后,先设置为 1 分钟,按下面的顺序检查:

  1. 点击开始,再连续点击几次,倒计时应保持正常速度。
  2. 运行几秒后点击暂停,等待一会儿,显示应保持不变。
  3. 点击继续,倒计时应从暂停的位置接着走。
  4. 点击重置,时间应回到设置值,输入框可以重新调整。
  5. 输入 0 后点击开始,页面应提示输入范围。
  6. 完成一轮计时,页面和标签页标题应出现结束提示。

如果某一步失败,把操作过程、预期结果和实际结果一起告诉 AI。例如:

我设置了 1 分钟,运行 10 秒后暂停,再点击继续,时间重新变成了 1 分钟。请检查剩余时间的保存逻辑,修复后保留其他功能。

本课的结束提醒显示在页面和标签页标题中。浏览器可能降低后台页面的更新频率,返回页面时,程序会根据结束时间重新计算显示。刷新页面会恢复初始设置。参考:MDN 页面可见性文档。

下一课,我们实现待办清单,让工具箱可以新增任务、标记完成和删除任务。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询