每天定时唤醒
在页面存活期间,按用户设定的「每天 HH:mm」触发一次轻量唤醒:短提示音 + 可选 Notification。标签页关闭则停止(需向用户说明限制)。
何时使用
- 用户要「每天定点提醒」「浏览器闹钟」「上班唤醒」「跟进打卡提醒」
- 不要求原生系统级闹钟权限时的轻量方案
硬约束
- 时间格式
HH:mm(24 小时);非法则拒绝。 - 同一自然日同一时刻只响一次(用本地日期键去重)。
- 提示须可见或可闻至少一种;提供停止/取消定时。
- 申请通知权限失败时降级为页面内提示,不得静默失败。
- 明确告知:依赖页面/标签保持打开(或未来扩展 Service Worker 时另开 Skill)。
核心实现
export function parseHm(hm: string): { h: number; m: number } | null {
const m = /^([01]?\d|2[0-3]):([0-5]\d)$/.exec(String(hm || '').trim());
if (!m) return null;
return { h: +m[1], m: +m[2] };
}
export function msUntilNext(hm: string, now = new Date()): number | null {
const t = parseHm(hm);
if (!t) return null;
const next = new Date(now);
next.setHours(t.h, t.m, 0, 0);
if (next.getTime() <= now.getTime()) next.setDate(next.getDate() + 1);
return next.getTime() - now.getTime();
}
export function startDailyWake(opts: {
hm: string;
onWake: () => void;
storageKey?: string;
}): { ok: boolean; message: string; stop?: () => void } {
const wait = msUntilNext(opts.hm);
if (wait == null) return { ok: false, message: '时间格式应为 HH:mm' };
const key = opts.storageKey || `daily-wake:${opts.hm}`;
let timer = window.setTimeout(function tick() {
const day = new Date().toISOString().slice(0, 10);
const firedKey = `${key}:${day}`;
if (localStorage.getItem(firedKey) !== '1') {
localStorage.setItem(firedKey, '1');
opts.onWake();
}
const again = msUntilNext(opts.hm);
if (again != null) timer = window.setTimeout(tick, again);
}, wait);
return {
ok: true,
message: `已设置每天 ${opts.hm} 唤醒(请保持页面打开)`,
stop: () => clearTimeout(timer),
};
}
onWake 内可:new Audio(url).play() 或 Notification(先 Notification.requestPermission())。
验收
09:30合法;25:00拒绝。- 触发后同一天不重复;跨天可再响。
stop()后不再触发。