抢购按钮倒计时必须以服务端 endtime 为准,前端通过 servertime 与 date.now() 计算 offset 校准时间,用 settimeout 递归更新(间隔 200ms),绑定 iscounting 和 remaining 状态防重复点击,多商品需独立倒计时,且服务端时间须毫秒精度并监控时钟误差。

抢购按钮倒计时必须以服务端 endTime 为准
本地自增/自减毫秒数会错——锁屏、切后台、低端机节流都会让 setInterval 失效,用户回来时按钮还显示“剩余 12s”,实际活动早已结束。正确做法是:后端返回两个字段:serverTime(当前服务器毫秒时间戳)和 endTime(活动截止毫秒时间戳),前端只做一次偏移校准:offset = serverTime - Date.now(),后续所有计算都用 Date.now() + offset 对齐服务端时间。
用 setTimeout 递归更新,别用 setInterval
setInterval 容易堆积、无法感知生命周期、切后台后恢复不准。抢购按钮的倒计时必须用 setTimeout 递归控制节奏:
- 每次更新前重新算剩余时间:
remaining = Math.max(0, props.endTime - (Date.now() + offset)) - 渲染间隔设为 200ms(非 1000ms),既保证视觉流畅,又避免频繁触发响应式更新
- 剩余 > 0 才继续调用:
if (remaining > 0) setTimeout(tick, 200),天然无残留定时器 - 不要在
onHide中clearTimeout——它本就没在跑,只是没触发;切回来时直接继续算即可
按钮状态与点击逻辑必须强绑定
抢购按钮不是“显示数字”那么简单,关键在防重复点击、状态同步、UI 及时反馈:
- 定义两个响应式字段:
isCounting(控制:disabled)和remaining(显示值) - 点击前先判断:
if (isCounting.value) return,避免连点启多个定时器 - 成功请求后才启动倒计时:
isCounting.value = true,并立即执行首次tick() - 倒计时归零时,必须显式重置:
isCounting.value = false,否则按钮永远禁用 - 按钮文字要动态切换:
{{ isCounting ? `抢购中(${formatTime(remaining)})` : '立即抢购' }}
多商品列表里每个按钮倒计时独立运行
一个页面多个抢购入口?不能共用一个定时器,也不能靠数组索引改值:
- 每个商品数据对象上挂载独立的
remaining和isCounting(用reactive或ref包裹) - 全局只维护一份
offset,所有按钮共享同一套服务端时间体系 - 新订单加载进来时,对每一项调用初始化函数:
initCountdown(item),内部仍走setTimeout+offset逻辑 - 倒计时归零后,用
orders.splice(index, 1)删除对应项,而不是仅隐藏 —— 抢购页需真实移除已失效入口
真正容易被忽略的是:服务端时间戳必须带毫秒精度,且前后端时钟误差超过 500ms 就该告警;另外,iOS 微信里 Date.now() 在切后台再回来时可能滞后,所以 onShow 钩子中要检查距上次更新是否超 500ms,若是则立刻重算一次 remaining 值。











