从需求到上线:我用 AI 辅助开发了一个在线倒计时器

11 阅读6分钟

最近在处理一个项目排期的时候,发现一个很实际的问题:团队里经常需要知道"距离某个截止日期还有多少天",但每次都要打开日历数日子,或者用命令行算时间差,实在不够直观。

问题背景

作为独立开发者,我经常需要跟踪各种截止日期——项目交付、域名续费、服务器证书到期等等。市面上确实有不少倒计时工具,但大多存在几个问题:

  1. 要么功能太复杂,打开页面就被各种设置项淹没
  2. 要么需要安装应用,杀鸡用牛刀
  3. 有的还带广告,体验很糟糕

我就是想要一个打开即用、设置目标时间就能看剩余时间的轻量工具。而且作为开发者,我希望能完全掌控代码,方便后续按需定制。

技术选型思考

一开始考虑过几个方案:

  • 用现成的 npm 包:比如 countdown 库,但为了一个倒计时引入依赖,感觉有点重
  • 写个命令行脚本:能用,但不方便给团队其他人用
  • 做个网页工具:跨平台、无需安装、分享方便,但需要处理前端的一堆细节

最后决定做个单文件 HTML,纯 vanilla JS 实现。理由很简单:不需要构建工具,不需要服务器,双击就能打开,这符合"工具就该轻量"的原则。

AI 辅助开发过程

说实话,一开始我连 datetime-local 输入框的兼容性问题都懒得查,直接打开了 Claude 开始对话。

第一轮:需求描述

我给的 prompt 大概是这样的:

帮我写一个单文件 HTML 倒计时器,支持设置目标日期和时间、自定义事件名称、实时显示剩余天时分秒、每秒刷新,支持深色模式,中英文切换。

AI 很快就生成了一版,整体结构不错,但我发现几个问题:

问题 1:时间计算有 bug

AI 第一次用的是 Date.now() 和 new Date(targetStr) 直接相减,这在跨时区时会出问题。比如用户在东八区设置了一个 UTC 时间,显示的剩余时间会差 8 小时。

// AI 第一次的写法(有 bug)
var ms = new Date(targetStr) - new Date();

我的修正方案:用 datetime-local 输入的值是本地时间字符串,直接 new Date(targetStr) 解析即可,但要注意 new Date('2024-01-01T00:00') 在不同浏览器解析行为可能不同,最好手动解析:

function parseLocalDateTime(str) {
  var parts = str.split('T');
  var dateParts = parts[0].split('-');
  var timeParts = parts[1].split(':');
  return new Date(
    +dateParts[0], +dateParts[1] - 1, +dateParts[2],
    +timeParts[0], +timeParts[1]
  );
}

问题 2:i18n 实现太笨重

AI 第一次的国际化方案是每个元素都写 if (lang === 'zh') ... else ...,代码重复不说,还容易漏。我让它改成 key-value 映射 + t() 函数的方式,清爽多了。

问题 3:进度条逻辑没考虑边界

AI 写的进度条在"起始日期晚于目标日期"时会算出负百分比,需要 clamp 到 0-100。

第二轮:交互细节打磨

我发现 AI 生成的版本有几个交互问题:

  1. 页面标题不会更新:我让它把倒计时同步到 document.title,这样浏览器标签页上就能直接看到剩余时间
  2. 快捷预设按钮是写死的:比如"新年"预设,如果写死 2025-01-01,明年就失效了。我让 AI 改成动态计算:
// 动态计算下一个新年
function getNextNewYear() {
  var now = new Date();
  return new Date(now.getFullYear() + 1, 0, 1); // 明年1月1日
}
  1. 到期后的表现:我要求到期后切换为正计时(显示已过去多久),并变红提示。AI 一开始只是显示"已过期",不够直观。

第三轮:视觉和细节

设计上我提了几个要求:

  • 数字用等宽字体(monospace),避免每秒跳动时宽度变化导致布局抖动
  • 数字切换时有简单的动画效果(用 CSS transition)
  • 深色模式下所有颜色都要重新适配

AI 在这轮表现不错,CSS 变量切换深色模式的方式很干净:

:root {
  --bg: #fff;
  --text: #111827;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1a2e;
    --text: #e2e8f0;
  }
}

核心实现细节

时间差计算

这是倒计时的核心,要注意:

  • 不能用 Math.abs() 直接取绝对值,因为天、时、分、秒需要分别计算
  • 用 Math.floor() 而不是 Math.round(),否则会出现"59秒显示为60秒"的问题
function calcDiff(from, to) {
  var ms = to - from;
  var isPast = ms < 0;
  ms = Math.abs(ms);
  var d = Math.floor(ms / 86400000);
  ms -= d * 86400000;
  var h = Math.floor(ms / 3600000);
  ms -= h * 3600000;
  var m = Math.floor(ms / 60000);
  ms -= m * 60000;
  var s = Math.floor(ms / 1000);
  return { d, h, m, s, isPast };
}

每秒刷新

用 setInterval(tick, 1000),但要注意:如果用户切换到其他标签页,浏览器会节流 setInterval,导致时间不准。不过对于倒计时场景,1秒的误差完全可以接受,不需要用 requestAnimationFrame 或 Web Worker 来保证精度。

快捷预设

这里有个细节:周末的判定。如果今天是周六,那"本周末"应该是明天(周日),而不是今天。我用了个小技巧:

var daysUntilSat = (6 - day + 7) % 7;
if (daysUntilSat === 0) daysUntilSat = 7; // 如果是周六,跳到下周六

踩坑记录

  1. datetime-local 的兼容性:Safari 15 以下不支持,需要 fallback。不过我做了渐进增强——不支持的浏览器会降级为文本输入框,用户手动输入日期格式。

  2. XSS 风险:事件名称是用户输入,直接插到 innerHTML 里会有 XSS 风险。必须用 escapeHtml() 转义:

function escapeHtml(s) {
  var d = document.createElement('div');
  d.textContent = s;
  return d.innerHTML;
}
  1. 移动端布局:4 个数字盒子在 320px 宽度下会挤在一起。用 flex-wrap 和 min-width 解决,同时在小屏下减小字号。

对 AI 辅助编程的感受

这次开发过程最大的感受是:AI 是很好的结对编程伙伴,但你不能完全放手。

  • AI 擅长:快速搭框架、写重复性代码、查 API 用法
  • AI 不擅长:理解业务边界条件、处理浏览器兼容性、做交互细节的取舍

比如 AI 第一次生成的代码完全没有考虑"用户没设置日期就点击开始"的情况,是我在测试时发现的。这种边界情况,需要开发者自己把关。

另外,给 AI 的 prompt 越具体,输出质量越高。比如"进度条需要 clamp 到 0-100"、"到期后切换为正计时并显示红色",比"加个进度条"要好得多。

适用场景

这个工具适合:

  • 团队项目截止日期追踪
  • 个人目标倒计时(考试、旅行、健身计划)
  • 产品上线倒计时(配合全屏模式投屏)
  • 日常提醒(比如服务器证书到期)

如果你也有类似需求,可以直接在线使用:倒计时器 - 在线日期倒计时计算工具

代码已经完整放在页面上,纯前端实现,所有计算都在浏览器本地完成,不会上传任何数据。你也可以直接右键查看源码,或者 F12 拷贝下来自己改。