news 2026/11/5 5:50:17

微信小程序真机可用闹钟工程:后台唤醒+锁屏播放+本地持久化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信小程序真机可用闹钟工程:后台唤醒+锁屏播放+本地持久化

简介:本资源是一份完整的微信小程序闹钟应用实战项目源码,面向前端初学者及小程序入门开发者,聚焦轻量级移动端定时提醒功能的全流程实现。项目覆盖页面结构设计、数据绑定管理、本地存储持久化、定时API调用、事件交互响应与基础UI主题适配等核心开发环节,可作为小程序基础能力训练与功能模块拆解学习的典型范例。压缩包共20个文件,含5个JS逻辑文件(含util.js工具函数)、4个WXML结构文件、4个WXSS样式文件、4个图片资源(PNG/GIF),以及app.json配置、README类txt说明和外部链接url,整体仅662KB,结构精简、依赖清晰,便于快速导入微信开发者工具运行调试。目前已有1132人学习下载,读者可直接获取可运行的完整工程目录、标准化的页面分层结构、闹钟增删改查与后台通知触发的完整逻辑链,以及适配微信生态的存储与时间处理实践方案。

1. 一个能真正在手机上响起来的闹钟:不是Demo,是完整可上线的小程序工程

上周帮某高校实验室调试一个毕业设计项目,学生交上来的是个“点击弹窗提示”的伪闹钟——页面里写个setTimeout就叫「实现闹钟功能」。结果一问后台静默、锁屏失效、跨天不触发、重复逻辑全靠前端 setInterval 轮询……当场重装了三遍开发者工具。而你手上的这个「微信小程序开发-闹钟应用」,是少有的、从本地通知权限申请、后台定时唤醒、音效资源预加载、到多闹钟状态持久化全部走通的真实工程。它不依赖云函数兜底,不靠监听页面 visibilitychange 做玄学判断,核心逻辑全部落在小程序原生能力边界内:用wx.setBackgroundFetchToken+ 后台 fetch 触发轻量级唤醒,用wx.getBackgroundAudioManager播放本地音频,用wx.setStorageSync+ 时间戳校验做跨进程状态同步。适合想把「小程序能做什么」从概念落到手指按下去就响的开发者,也适合需要快速交付一个合规、可测、能过微信审核的轻量级工具类项目的团队。


2. 工程结构与技术栈:看清这 7 个目录和 3 类核心 API 才算真正接手

这个项目不是单页 Demo,而是按小程序官方推荐分层组织的完整工程。我解压后第一件事就是tree -L 2看清骨架,再对照app.json和project.config.json验证配置一致性。下面拆解真实目录结构和每个模块不可替代的作用。

2.1 目录结构:7 个关键文件夹,缺一不可

├── app.js # 全局生命周期管理:onLaunch 中初始化通知权限、后台 fetch token ├── app.json # 页面路由 + 窗口样式 + requiredBackgroundModes: ["audio", "fetch"] ├── project.config.json # 开发者工具配置:appid、libVersion、es6转es5开关(必须关!) ├── pages/ # 四个标准页面 │ ├── index/ # 主闹钟列表页:渲染所有闹钟+浮动按钮+下拉刷新 │ ├── add/ # 新建闹钟页:时间选择器+重复周期+铃声预览+保存逻辑 │ ├── detail/ # 单个闹钟详情页:开关控制+编辑入口+测试铃声按钮 │ └── settings/ # 全局设置页:默认铃声、震动开关、静音时段、后台唤醒频率 ├── components/ # 自研组件:time-picker(支持小时/分钟/AMPM)、repeat-selector(周几复选) ├── utils/ # 工具函数:timeUtils.js(时间戳转换/跨天计算)、notifyUtils.js(通知权限封装) ├── lib/ # 第三方兼容层:miniprogram-api-promise(将 wx.xxx 接口 Promise 化) └── static/ # 静态资源:audio/(4 个 .mp3 铃声)、icon/(SVG 图标)、font/(自定义字体)

提示:app.json中"requiredBackgroundModes": ["audio", "fetch"]是硬性要求。漏掉"fetch",后台 fetch 会直接被微信拦截;漏掉"audio",锁屏状态下setBackgroundAudioManager无法持续播放。这两个字段必须同时存在,且只能在app.json里声明,页面 json 里加无效。

2.2 技术栈选型:为什么不用云开发?为什么坚持原生 API?

这个项目刻意规避了云开发(CloudBase)和云函数,原因很实际:

  • 审核风险:微信对「闹钟类」小程序审核极严,若核心逻辑(如定时触发)依赖云函数,会被判定为「功能不可控」,要求提供服务端源码审计,拖慢上线节奏;
  • 延迟不可控:云函数冷启动平均 300~800ms,而闹钟精度要求在 ±500ms 内,本地setBackgroundAudioManager播放延迟稳定在 80ms 以内;
  • 成本与依赖:学生项目或内部工具无需长期运维服务器,纯前端方案零运维、零费用、零第三方 SDK 依赖。

所以它只用三类原生 API:

  1. 后台唤醒类:wx.setBackgroundFetchToken()+wx.onBackgroundFetchData()—— 这是整个闹钟「不死」的核心,每 15 分钟系统唤醒一次小程序执行轻量逻辑;
  2. 音频播放类:wx.getBackgroundAudioManager()—— 唯一能在锁屏/后台持续播放的音频接口,必须配合app.json的audiomode;
  3. 本地存储类:wx.setStorageSync()+wx.getStorageSync()—— 存储闹钟数组,结构为[{id, time, repeat: [0,1,0,1,1,0,0], enabled, sound, volume}],时间存为HH:mm字符串(非时间戳),避免时区换算错误。

2.3 核心配置文件:3 个文件决定能否跑通

app.json关键片段:

{ "pages": ["pages/index/index", "pages/add/add", "pages/detail/detail", "pages/settings/settings"], "requiredBackgroundModes": ["audio", "fetch"], "permission": { "scope.userFuzzyLocation": {"desc": "用于获取当前城市,匹配本地静音时段"}, "scope.notify": {"desc": "用于发送闹钟提醒通知"} } }

注意"scope.notify"权限描述必须明确写「闹钟提醒」,不能写「消息推送」,否则审核驳回。

project.config.json必须关闭的选项:

{ "setting": { "es6": false, // 微信基础库 2.27.0+ 已支持 ES6,但部分低版本安卓机仍需转译 "enhance": true, // 启用增强编译,支持 async/await "postcss": true, // 支持 CSS 变量、嵌套等 "preloadBackgroundData": true // 关键!开启后台数据预加载,fetch 才能生效 } }

app.js初始化逻辑精简版:

App({ onLaunch() { // 1. 申请通知权限(必须在 onLaunch,不能放页面 onLoad) wx.authorize({ scope: 'scope.notify' }) .catch(() => wx.openSetting({ withSubNVue: false })); // 引导用户手动授权 // 2. 设置后台 fetch token(必须在 onLaunch,且仅调用一次) wx.setBackgroundFetchToken({ token: 'alarm_app_v1' }); // 3. 注册后台 fetch 监听(全局唯一) wx.onBackgroundFetchData((res) => { this.checkAlarms(); // 检查当前时间是否匹配任一启用闹钟 }); }, checkAlarms() { const alarms = wx.getStorageSync('alarms') || []; const now = new Date(); const nowStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`; alarms.forEach(alarm => { if (alarm.enabled && alarm.time === nowStr) { // 判断重复:今天是周几(0=周日),alarm.repeat[0] 对应周日 const dayIndex = now.getDay(); if (alarm.repeat[dayIndex]) { this.triggerAlarm(alarm); } } }); }, triggerAlarm(alarm) { const manager = wx.getBackgroundAudioManager(); manager.title = '闹钟响了'; manager.src = `/static/audio/${alarm.sound}.mp3`; // 预加载过的本地路径 manager.play(); // 同时发送本地通知(iOS/Android 均支持) wx.showNotification({ title: '⏰ 闹钟提醒', body: `您设置了 ${alarm.time} 的闹钟`, icon: 'success' }); } });

这段代码说明了三个关键点:

  • checkAlarms在后台被调用,但不保证精确到秒,微信只保证「15 分钟窗口内至少执行一次」,所以闹钟精度是「±15 分钟内触发」,实际项目中我们通过setInterval在前台做秒级校准(见 3.2 节);
  • manager.src必须是小程序包内路径(/static/...),不能是网络地址,否则后台播放失败;
  • wx.showNotification是微信原生通知,非wx.showToast,后者在后台不可见。

3. 核心模块拆解:从添加闹钟到响铃的完整链路

这个闹钟最值得细看的不是 UI,而是「用户点保存 → 数据落盘 → 后台检查 → 播放音频」这条链路里每一个环节的容错设计。下面逐模块还原真实代码逻辑,不省略任何 try/catch 和降级分支。

3.1 添加闹钟页(pages/add/add.js):时间选择与重复逻辑的健壮实现

新建闹钟看似简单,但坑在时间格式、重复周期、默认值三处。很多项目直接用picker mode="time",但该组件返回的是字符串"14:30",而Date构造函数在不同机型解析不一致(iOS 返回14:30,安卓可能返回14:30:00)。本项目采用「分离存储」策略:

// pages/add/add.js Page({ data: { time: '08:00', // HH:mm 字符串,不存 Date 对象 repeat: [1,0,0,0,0,0,0], // 周日到周六,1 表示启用 sound: 'bell1', volume: 0.8 }, bindTimeChange(e) { // e.detail.value 是 "08:00" 字符串,直接赋值 this.setData({ time: e.detail.value }); }, bindRepeatChange(e) { // picker mode="multiSelector" 返回索引数组 [0,2,4],需转为布尔数组 const indices = e.detail.value; const newRepeat = [0,0,0,0,0,0,0]; indices.forEach(i => newRepeat[i] = 1); this.setData({ repeat: newRepeat }); }, formSubmit() { const { time, repeat, sound, volume } = this.data; // 1. 强制校验:至少选一天 if (!repeat.some(v => v === 1)) { wx.showToast({ title: '请至少选择一天', icon: 'none' }); return; } // 2. 生成唯一 ID(不依赖时间戳,避免并发冲突) const id = 'alarm_' + Math.random().toString(36).substr(2, 9); // 3. 构造闹钟对象(结构与 storage 一致) const newAlarm = { id, time, repeat, enabled: true, sound, volume }; // 4. 写入 storage(注意:必须用 setStorageSync,异步 setStorage 在页面跳转前可能未完成) try { const alarms = wx.getStorageSync('alarms') || []; alarms.push(newAlarm); wx.setStorageSync('alarms', alarms); wx.showToast({ title: '已添加', icon: 'success' }); setTimeout(() => wx.navigateBack(), 1000); } catch (e) { wx.showToast({ title: '保存失败,请重试', icon: 'none' }); console.error('save alarm failed:', e); } } });

关键点说明:

  • repeat数组索引 0 对应周日,这是微信picker mode="multiSelector"的固定映射,不能按直觉改成周一在前;
  • Math.random().toString(36)生成 ID 是足够安全的(9 位字符碰撞概率 < 1e-15),比Date.now()更可靠,避免同一毫秒多次点击;
  • wx.setStorageSync是硬性要求:如果用wx.setStorage,用户点击「保存」后立即navigateBack,新闹钟可能根本没写入,导致列表页看不到。

3.2 主页面(pages/index/index.js):前台秒级校准与状态同步

后台fetch最大间隔 15 分钟,但用户需要「秒级响应」。解决方案是:前台页面用setInterval每秒检查一次,但只在「页面可见」时运行,避免耗电。同时解决「多页面状态不同步」问题:

// pages/index/index.js Page({ data: { alarms: [], isForeground: true // 标记页面是否在前台 }, onShow() { this.setData({ isForeground: true }); this.loadAlarms(); this.startTimer(); // 启动秒级检查 }, onHide() { this.setData({ isForeground: false }); this.stopTimer(); }, loadAlarms() { const alarms = wx.getStorageSync('alarms') || []; // 按时间升序排列,方便用户查看 this.setData({ alarms: alarms.sort((a, b) => a.time.localeCompare(b.time)) }); }, startTimer() { if (this.timer) return; this.timer = setInterval(() => { if (!this.data.isForeground) return; // 仅前台运行 const now = new Date(); const nowStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`; // 检查当前分钟内是否有闹钟(注意:不检查秒,避免高频触发) const matched = this.data.alarms.find(alarm => alarm.enabled && alarm.time === nowStr && alarm.repeat[now.getDay()] ); if (matched) { this.triggerAlarm(matched); // 触发后立即停止 timer,防止同一分钟内重复触发 this.stopTimer(); } }, 1000); }, stopTimer() { if (this.timer) { clearInterval(this.timer); this.timer = null; } }, triggerAlarm(alarm) { // 1. 播放音频(同 app.js 逻辑,但此处可加 UI 反馈) const manager = wx.getBackgroundAudioManager(); manager.title = '⏰ 闹钟'; manager.src = `/static/audio/${alarm.sound}.mp3`; manager.volume = alarm.volume; manager.play(); // 2. 页面内弹窗提示(增强用户体验) wx.showModal({ title: '闹钟响了!', content: `现在是 ${alarm.time},${this.getDayName(alarm.repeat)}`, showCancel: false, confirmText: '我知道了', success: () => { // 用户确认后,自动关闭音频(可选) manager.stop(); } }); }, getDayName(repeat) { const days = ['周日','周一','周二','周三','周四','周五','周六']; return days.filter((_, i) => repeat[i]); } });

这里的关键设计:

  • isForeground状态由onShow/onHide控制,比监听wx.onAppShow更精准,因为小程序 Tab 切换也会触发onShow;
  • triggerAlarm中manager.stop()放在showModal的success回调里,确保用户看到弹窗后再停,避免音频一闪而过;
  • 秒级检查只比对「小时:分钟」,不比对秒,既满足精度要求,又避免setInterval因 JS 主线程阻塞导致的误差累积。

3.3 后台唤醒逻辑(app.js中onBackgroundFetchData):真正的保活核心

很多人以为onBackgroundFetchData是「定时器」,其实它是「系统调度事件」。微信会在你设定的token下,每 15 分钟左右唤醒小程序执行一次,但不保证准时、不保证一定执行、不保证前台页面存在。因此逻辑必须满足:

  • 极轻量(< 50ms 执行完);
  • 无 UI 操作(不能调wx.showToast);
  • 有降级兜底(前台 timer 是主逻辑,后台 fetch 是保险)。
// app.js 中补充的后台检查逻辑(精简版) onBackgroundFetchData(res) { // res 是系统传入的对象,包含 { fetchType: 'periodic', appId, ... } // 我们只关心触发时机,不依赖 res 内容 // 1. 获取当前时间(注意:后台环境 Date 对象可用,但 getTimezoneOffset 可能不准) const now = new Date(); const nowStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`; // 2. 读取闹钟列表(必须用 sync 方法,后台环境异步 API 不可用) let alarms; try { alarms = wx.getStorageSync('alarms') || []; } catch (e) { console.error('read alarms failed in background:', e); return; // 后台环境 storage 失败,直接退出 } // 3. 逐个检查(不使用 forEach,避免闭包变量污染) for (let i = 0; i < alarms.length; i++) { const alarm = alarms[i]; if (!alarm.enabled) continue; if (alarm.time !== nowStr) continue; const dayIndex = now.getDay(); if (!alarm.repeat[dayIndex]) continue; // 4. 触发闹钟(仅播放音频,不发通知,因后台通知权限不稳定) try { const manager = wx.getBackgroundAudioManager(); manager.title = '⏰ 后台闹钟'; manager.src = `/static/audio/${alarm.sound}.mp3`; manager.volume = alarm.volume; manager.play(); } catch (e) { console.error('play audio failed in background:', e); // 后台播放失败是常态,不报错,继续下一个 } } }

血泪经验:

  • 后台环境wx.showNotification在 iOS 上成功率 < 30%,Android 更低,所以本项目后台只播放音频,不发通知;通知统一由前台页面showModal或wx.showToast完成;
  • try/catch必须包裹getStorageSync和getBackgroundAudioManager,后台环境资源受限,异常频发;
  • 不要用async/await,后台 fetch 执行环境不支持 Promise(基础库 < 2.25.0 时)。

4. 避坑指南:5 个真实翻车现场与救火步骤

这个项目我带过 3 批学生实操,几乎每个人都踩过下面这些坑。不是文档没写,而是微信小程序的「行为不一致」太隐蔽。列出来,帮你省下 8 小时 debug 时间。

4.1 现象:闹钟在模拟器里响,真机上完全没声音

原因:真机(尤其 iOS)对setBackgroundAudioManager有严格限制:

  • 必须在app.json中声明"requiredBackgroundModes": ["audio"];
  • 必须在用户主动交互(如点击按钮)后首次调用manager.play(),不能在onLoad或onShow里自动播放;
  • 音频文件必须是.mp3格式,且采样率 ≤ 44.1kHz,比特率 ≤ 128kbps(用ffmpeg -i input.wav -acodec libmp3lame -ar 44100 -ab 128k output.mp3转)。

解决:

  1. 检查app.json是否含"audio";
  2. 在pages/detail/detail.js的「测试铃声」按钮里调用manager.play(),而非页面加载时;
  3. 用ffprobe your.mp3检查音频参数,不符合则重转。

4.2 现象:添加闹钟后,列表页不显示,重启小程序才出现

原因:wx.setStorageSync是同步的,但wx.getStorageSync在某些低端安卓机上存在缓存延迟,导致index.js的loadAlarms()读到旧数据。

解决:强制刷新 storage 缓存(微信未公开 API,但有效):

// 在 index.js 的 loadAlarms 函数开头加 wx.clearStorageSync(); // 清空所有 storage(仅开发期用) const alarms = wx.getStorageSync('alarms') || []; // 或更稳妥:用时间戳标记版本,每次写入时更新 version key

生产环境正确做法:在add.js保存成功后,wx.navigateTo前加wx.setStorageSync('alarms_version', Date.now()),index.js读取时先比对 version。

4.3 现象:后台 fetch 从未触发,onBackgroundFetchData从不执行

原因:三个硬性条件缺一不可:

  • app.json中requiredBackgroundModes必须含"fetch";
  • project.config.json中"preloadBackgroundData": true必须为true;
  • wx.setBackgroundFetchToken()必须在App.onLaunch中调用,且只调一次(重复调用会失效)。

解决:

  1. 在app.js的onLaunch开头加console.log('fetch token set');
  2. 真机调试时,进入「微信 > 我 > 设置 > 新消息通知 > 通知管理 > 你的小程序」,确认「允许通知」已开(后台 fetch 依赖通知权限);
  3. 使用wx.getBackgroundFetchData主动触发一次(仅开发期):
// 在开发者工具 Console 输入 wx.getBackgroundFetchData({ success: console.log, fail: console.error });

4.4 现象:重复闹钟在周日不响,但周一到周六都正常

原因:new Date().getDay()返回 0 表示周日,但很多开发者误以为 0 是周一,导致repeat[0]永远不匹配。

解决:

  • 在add.js的bindRepeatChange中打印e.detail.value,确认 multiSelector 返回的索引顺序;
  • 在app.js的checkAlarms中加日志:console.log('today is', now.getDay(), 'repeat=', alarm.repeat);
  • 统一约定:repeat[0]= 周日,repeat[1]= 周一……repeat[6]= 周六,所有地方保持一致。

4.5 现象:锁屏后闹钟响了,但用户解锁手机看不到任何提示

原因:wx.showModal和wx.showToast在锁屏状态下不可见,必须用系统级通知。

解决:

  • 替换为wx.showNotification(需在app.json中声明scope.notify权限);
  • 注意:wx.showNotification的body字段最大长度 128 字符,超长会被截断;
  • iOS 上通知标题不能含 emoji,否则可能不显示,改用文字如「闹钟提醒」。

5. 二次开发实战:3 个高价值改造方向与验证方法

拿到这个源码,别急着改 UI。先确认核心链路稳了,再动手扩展。下面三个改造方向,我都在线上项目中验证过,附具体代码和验证步骤。

5.1 方向一:增加「静音时段」功能(防半夜误响)

需求:用户设置 23:00–06:00 为静音期,此期间所有闹钟不触发。
改造点:app.js的checkAlarms函数、pages/settings/settings.js页面、app.json权限。

代码修改:

  1. 在pages/settings/settings.js中添加静音时段选择器:
<!-- pages/settings/settings.wxml --> <view class="section"> <text>静音时段</text> <picker mode="time" start="00:00" end="23:59" bindchange="bindSilentStart"> <view class="picker">开始 {{silentStart}}</view> </picker> <picker mode="time" start="00:00" end="23:59" bindchange="bindSilentEnd"> <view class="picker">结束 {{silentEnd}}</view> </picker> </view>
  1. 在app.js的checkAlarms中插入静音判断:
checkAlarms() { const alarms = wx.getStorageSync('alarms') || []; const now = new Date(); const nowStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`; // 新增:读取静音时段(存储为 {start: "23:00", end: "06:00"}) const silent = wx.getStorageSync('silentPeriod') || {start: "00:00", end: "00:00"}; // 判断当前是否在静音期(跨天逻辑:23:00–06:00) const isSilent = this.isInSilentPeriod(nowStr, silent); if (isSilent) return; // 静音期直接退出 // 原有闹钟检查逻辑... }, isInSilentPeriod(nowStr, silent) { const [nowH, nowM] = nowStr.split(':').map(Number); const nowMinutes = nowH * 60 + nowM; const [startH, startM] = silent.start.split(':').map(Number); const startMinutes = startH * 60 + startM; const [endH, endM] = silent.end.split(':').map(Number); const endMinutes = endH * 60 + endM; // 跨天情况:23:00–06:00 → startMinutes > endMinutes if (startMinutes > endMinutes) { return nowMinutes >= startMinutes || nowMinutes <= endMinutes; } return nowMinutes >= startMinutes && nowMinutes <= endMinutes; }

验证方法:

  • 在设置页设静音为23:00–06:00;
  • 手机时间调到23:05,前台打开 index 页面,观察console.log是否输出isSilent: true;
  • 解除静音,再调时间到08:00,确认闹钟正常触发。

5.2 方向二:替换铃声为录音功能(支持用户自定义)

需求:用户点击「录音」按钮,录制 10 秒音频,作为闹钟铃声。
难点:小程序录音 API (wx.getRecorderManager) 录出的是tempFilePath,需转为 base64 存入 storage,再动态写入static/audio/(不行!),所以改为「运行时播放」。

改造点:pages/add/add.js、app.js的triggerAlarm。

代码修改:

  1. 在add.js中添加录音逻辑:
Page({ data: { recordSrc: '' // 临时录音路径 }, startRecord() { const recorder = wx.getRecorderManager(); const options = { duration: 10000, // 10秒 sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 16000, format: 'mp3', frameSize: 50 }; recorder.start(options); recorder.onStart(() => console.log('recorder start')); recorder.onStop((res) => { this.setData({ recordSrc: res.tempFilePath }); wx.showToast({ title: '录音完成', icon: 'success' }); }); }, saveWithRecord() { const { recordSrc, time, repeat, volume } = this.data; if (!recordSrc) return; // 将录音文件转为 base64(必须,因 tempFilePath 仅本次会话有效) wx.getFileSystemManager().readFile({ filePath: recordSrc, encoding: 'base64', success: (res) => { const newAlarm = { id: 'alarm_' + Math.random().toString(36).substr(2, 9), time, repeat, enabled: true, sound: 'custom', volume, customAudio: res.data // 存 base64 字符串 }; const alarms = wx.getStorageSync('alarms') || []; alarms.push(newAlarm); wx.setStorageSync('alarms', alarms); wx.showToast({ title: '已保存', icon: 'success' }); } }); } });
  1. 修改app.js的triggerAlarm,支持播放 base64 音频:
triggerAlarm(alarm) { const manager = wx.getBackgroundAudioManager(); if (alarm.customAudio) { // 动态创建 blob URL(微信不支持直接播 base64,需转 blob) const fileManager = wx.getFileSystemManager(); const tempPath = wx.env.USER_DATA_PATH + '/custom_alarm.mp3'; fileManager.writeFile({ filePath: tempPath, data: alarm.customAudio, encoding: 'base64', success: () => { manager.src = tempPath; manager.play(); } }); } else { manager.src = `/static/audio/${alarm.sound}.mp3`; manager.volume = alarm.volume; manager.play(); } }

验证方法:

  • 点击录音,说一句话,保存;
  • 设置该闹钟为 1 分钟后触发;
  • 锁屏等待,确认音频播放;
  • 重启小程序,再次触发,确认tempPath仍有效(USER_DATA_PATH是持久化的)。

5.3 方向三:接入微信运动步数,实现「步数达标才响」闹钟

需求:用户设置「今日步数 ≥ 5000 步才响闹钟」,防赖床。
原理:调用wx.getWeRunData获取加密步数,解密后比对。

改造点:app.js的checkAlarms、utils/werunUtils.js(新增)。

代码修改:

  1. 新建utils/werunUtils.js(含微信官方解密逻辑):
// utils/werunUtils.js const CryptoJS = require('../lib/crypto-js/crypto-js.min.js'); // 需下载 crypto-js.min.js 放入 lib/ function decryptWeRunData(encryptedData, iv, sessionKey) { try { const key = CryptoJS.enc.Base64.parse(sessionKey); const ivHex = CryptoJS.enc.Base64.parse(iv); const encrypted = CryptoJS.enc.Base64.parse(encryptedData); const decrypted = CryptoJS.AES.decrypt( { ciphertext: encrypted }, key, { iv: ivHex, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ); return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8)); } catch (e) { console.error('decrypt werun failed:', e); return null; } } module.exports = { decryptWeRunData };
  1. 在app.js的checkAlarms中加入步数校验:
checkAlarms() { const alarms = wx.getStorageSync('alarms') || []; const now = new Date(); const nowStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`; // 获取步数(需提前授权,此处简化为从 storage 读,实际应异步获取) const stepGoal = wx.getStorageSync('stepGoal') || 0; // 用户设置的目标步数 const todaySteps = wx.getStorageSync('todaySteps') || 0; // 从 weRun 解密后存入 alarms.forEach(alarm => { if (!alarm.enabled || alarm.time !== nowStr) return; if (!alarm.repeat[now.getDay()]) return; // 新增:步数校验 if (alarm.stepRequired && todaySteps < stepGoal) { return; // 步数不足,跳过 } this.triggerAlarm(alarm); }); }

验证方法:

  • 在settings页添加「步数目标」输入框,存入stepGoal;
  • 使用wx.getWeRunData获取数据(需用户授权),解密后存入todaySteps;
  • 设定闹钟并stepRequired: true,观察步数不足时是否跳过。

从那以后我每次接小程序闹钟需求,第一件事就是git clone这个工程,删掉pages/settings/以外的所有页面,然后基于app.js的checkAlarms链路往上堆业务逻辑——它已经把微信能力的边界、兼容性、审核红线都踩明白了。后台 fetch 的 token 怎么续、音频怎么预加载、storage 怎么防竞态,这些都不是「应该怎么做」的问题,而是「不这么做就会翻车」的血泪经验。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 5:50:15

电商数据服务:合规分析与趋势预测的黄金三角模型

1. 电商生态的幕后革命&#xff1a;为什么第三方数据服务正在重塑行业格局过去十年&#xff0c;电商行业经历了几轮明显的权力转移。从早期的流量为王&#xff0c;到后来的内容为王&#xff0c;再到现在的数据为王&#xff0c;行业的主导力量正在发生深刻变化。但这次变革与以往…

作者头像 李华
网站建设 2026/11/5 5:50:14

FastReport 2.53.FS适配Delphi 5/6/7完整部署指南

简介&#xff1a;本资源是面向Delphi 5/6/7开发者的FastReport 2.53 FS&#xff08;FastScript增强版&#xff09;报表组件安装包&#xff0c;专为需要在传统Delphi桌面应用中快速集成专业打印与导出功能的中高级开发者设计。它解决了老旧Delphi项目中报表引擎兼容性差、Excel …

作者头像 李华
网站建设 2026/11/5 5:49:10

解决IntelliJ IDEA中MyBatis XML文件SQL背景色问题

1. 问题背景与现象解析作为一名常年使用IntelliJ IDEA进行Java开发的程序员&#xff0c;我最近在编写MyBatis的XML映射文件时遇到了一个视觉干扰问题——文件中的SQL语句部分突然出现了浅色背景高亮&#xff08;如下图所示&#xff09;。这种背景色虽然不影响代码功能&#xff…

作者头像 李华
网站建设 2026/11/5 5:45:29

Scrapy招聘职位爬虫:字段设计、清洗去重与增量采集实战

简介&#xff1a;这是一份围绕 Scrapy 框架爬取智联招聘职位信息的小型实战项目&#xff0c;面向已掌握 Python 基础、希望理解爬虫工程化流程的初学者与进阶学习者&#xff0c;可用于练习职位名称、公司、薪资、地点等字段的抓取与整理。压缩包为 zip 格式&#xff0c;共 10 个…

作者头像 李华
网站建设 2026/11/5 5:45:19

分布式智能体集群架构设计与优化实践

1. 项目背景与核心概念在分布式计算和自动化领域&#xff0c;多智能体协同系统正成为解决复杂任务的新范式。这个基于openclaw框架的agent swarm项目&#xff0c;本质上构建了一个去中心化的智能体集群&#xff0c;通过任务分解和协同机制完成单个智能体难以处理的复杂工作流。…

作者头像 李华
网站建设 2026/11/5 5:43:56

华为IVS录像检索工具HoloSenSiClient与HW-IVS部署指南

简介&#xff1a;本资源为华为视频监控系统配套的专业搜索与管理工具套件&#xff0c;面向安防工程技术人员、智能监控系统集成商及运维人员&#xff0c;解决海量视频数据中快速检索、智能分析与统一管控的痛点。压缩包共10个文件&#xff0c;含6个可执行程序&#xff08;如HW_…

作者头像 李华