简介:微信步数主题的微信小程序页面源码,适用于入门小程序开发的初学者,或需要快速搭建运动健康类页面的开发者,能够直接参考其页面划分、数据绑定与基础交互实现。压缩包共32个文件,类型涵盖json配置、js逻辑、wxml页面结构、wxss样式、png和jpg图片素材以及md说明文档,整体仅445KB,体量小巧且目录层次清楚。目前已有81人学习下载。源码包含pages下多个功能页面、工具函数与全局配置,并配套sitemap及项目配置文件,可帮助读者理解小程序页面注册、公共样式引用、图片资源调用等环节;同时通过日志、个人中心等模块,能够掌握常用组件、事件绑定和页面间跳转的组织方式。这份源码还可作为二次开发或课程设计的基础模板,适合边读边改,快速积累小程序前端实战经验。
1. 微信步数小程序页面源码的资源拆解
拿到微信步数的微信小程序页面源码.zip,别急着点开微信开发者工具导入,先把它当成一个页面级工程来拆。它解决的不是“怎么注册小程序”,而是给你一套已经排好页面、tabBar、图片资源和工具函数的前端骨架,适合做微信小程序项目实例、课程设计或毕业设计底子。
这里有个反直觉结论:微信步数不是页面里自己累计算出来的。微信小程序中拿到步数只有一条常规路径——调用wx.getWeRunData,而且它返回的是加密数据,不能在前端直接解出步数。后面所有代码和报错都围绕这句话展开。
解压后能看到四页结构:pages/index是步数主页,pages/orgpk和pages/myorg是业务扩展页,pages/logs是日志页。这个包没有附带后端解密服务,就算wx.getWeRunData调到成功,页面也拿不到明文步数,这是整套源码里最需要先认清的边界。
2. app.json 页面注册与底盘配置:先让小程序骨架跑起来
2.1 从文件清单反推页面模块
解压后的目录结构大致是这样:
├── app.js ├── app.json ├── app.wxss ├── project.config.json ├── sitemap.json ├── pages/ │ ├── index/ │ ├── orgpk/ │ ├── logs/ │ └── myorg/ ├── src/ │ ├── avatar_1.png │ ├── liked.png │ ├── cover_pic.jpg │ └── ... └── utils/ └── util.js看目录就能猜出分工:pages是四个页面模块,src里的图片全部本地化,utils放纯函数,app.wxss是全局样式。这个结构和常见的 uniapp微信小程序工程不一样,uniapp 生成的小程序包会有pages.json和底层运行时文件,而这个包里只有原生小程序配置,说明它是纯原生写法,别用 hbuilderx 那套构建逻辑去套。
2.2 页面注册顺序决定启动页和 tabBar
app.json是原生小程序的全局配置,步数主页能跑起来,先看这段配置:
{ "pages": [ "pages/index/index", "pages/orgpk/orgpk", "pages/logs/logs", "pages/myorg/myorg" ], "window": { "navigationBarTitleText": "微信步数", "navigationBarBackgroundColor": "#2e5e4e", "navigationBarTextStyle": "white", "backgroundColor": "#f6f6f6" }, "tabBar": { "color": "#7f8487", "selectedColor": "#2e5e4e", "list": [ { "pagePath": "pages/index/index", "text": "步数" }, { "pagePath": "pages/myorg/myorg", "text": "我的" } ] }, "sitemapLocation": "sitemap.json" }这段配置里有两个容易忽略的点。第一,pages数组的第一个元素决定启动页,这里把pages/index/index放在最前面,进入小程序第一眼看到的就是步数主页;orgpk、logs即使页面文件存在,只要不出现在pages数组里,就不会作为独立页面编译。第二,tabBar.list里的pagePath必须同时存在于pages数组,否则编译直接报错,并且tabBar只能配置 2 到 5 个页面。这个包的orgpk没有进 tabBar,说明它只能通过wx.navigateTo跳转进入。
下面这张表是调试时最常用的几项:
| 配置项 | 示例值 | 作用 |
|---|---|---|
pages | ["pages/index/index"] | 声明页面路径,第一项为启动页 |
window.navigationBarTitleText | 微信步数 | 顶部导航栏标题 |
tabBar.selectedColor | #2e5e4e | 底部导航选中态颜色 |
sitemapLocation | sitemap.json | 指定站点地图文件位置 |
2.3 sitemap.json 与 project.config.json 决定编译行为
sitemap.json的rules规则控制页面是否被微信搜一搜建立索引。开发期保持放行即可:
{ "desc": "步数页面索引规则", "rules": [ { "action": "allow", "page": "*" } ] }project.config.json则是微信开发者工具读的工程配置,核心是 AppID 和编译选项:
{ "compileType": "miniprogram", "appid": "touristappid", "projectname": "wechat-steps", "setting": { "es6": true, "postcss": true, "minified": true, "urlCheck": false } }urlCheck设为 false 只能让开发者工具跳过 request 合法域名校验,真机预览时仍然会校验,后面走云函数就不需要担心这个。touristappid是游客模式,不能调用wx.getWeRunData,测试步数功能前必须换成真实 AppID,这是最多人卡住的第一步。
3. 微信步数核心链路:wx.getWeRunData 授权、加密数据与云函数解密
3.1 调用前先查 scope.werun 授权状态
微信小程序的步数接口不是随便调的,它属于隐私接口,绑定scope.werun授权。用户拒绝一次之后,开发者不能再次主动触发授权弹窗,只能引导用户去设置页手动打开。这个行为和scope.userInfo类似,所以第一步应该先查授权状态,而不是直接写一个wx.getWeRunData完事。
查授权状态的函数我一般写成 Promise 形式,后面配合async/await调用,避免回调嵌套:
function checkWeRunAuth() { return new Promise((resolve) => { wx.getSetting({ success(res) { const auth = res.authSetting['scope.werun']; if (auth === true) { resolve('authorized'); } else if (auth === false) { resolve('denied'); } else { resolve('notDetermined'); } }, fail() { resolve('unknown'); } }); }); }这段代码的关键点是authSetting对象里scope.werun的三种取值:true表示已授权,false表示用户明确拒绝过,undefined表示还没决定。很多新手把undefined也当成拒绝,结果用户第一次还被弹了两次授权框。fail回调也不能简单忽略,基础库版本过低时getSetting也可能失败,返回unknown状态之后走默认调用链路会更稳妥。
3.2 三种授权状态下的调用分支
拿到状态后,按authorized、notDetermined、denied三个分支处理:
async function loadSteps() { const status = await checkWeRunAuth(); if (status === 'authorized' || status === 'notDetermined') { wx.getWeRunData({ success: (res) => { handleEncryptedData(res); }, fail: (err) => { console.error('getWeRunData fail', err); } }); } else { wx.showModal({ title: '需要微信运动权限', content: '请在设置中打开微信运动授权', confirmText: '去设置', success: (r) => { if (r.confirm) { wx.openSetting(); } } }); } }这里最容易踩的坑是denied后直接再调一次wx.getWeRunData,实际上微信不会再弹授权框,而是直接走fail回调,写好的fail处理就会反复执行。所以denied分支要用wx.showModal解释原因,再通过wx.openSetting让用户手动打开。wx.openSetting必须在用户点击回调里调用,不能在onLoad生命周期直接调,否则开发者工具会提示openSetting:fail can only be invoked by user TAP gesture。notDetermined状态则什么都不用做,直接调接口就会触发授权弹窗。
3.3 加密数据怎么变成步数列表
授权通过后,wx.getWeRunData的 success 回调拿到的不是 JSON 步数列表,而是这样两段字符串:
{ "errMsg": "getWeRunData:ok", "encryptedData": "base64字符串", "iv": "base64字符串" }encryptedData是 AES-128-CBC 加密后的用户信息和步数列表,iv是初始向量。真正解密的session_key只有后端从wx.login的 code 换出来,所以页面源码里看不到解密逻辑是正常的。之前有人在前端引入 crypto-js 去解,解出来也是乱码,因为拿不到正确的session_key。常见做法是把这个解密放到小程序云函数里,前端只把encryptedData和iv传过去。
下面是一个最常用的云函数解密模板,Node.js 环境自带 crypto,不需要额外装包:
// 云函数 getWeRunData const crypto = require('crypto'); exports.main = async (event) => { const { encryptedData, iv, sessionKey } = event; const decipher = crypto.createDecipheriv( 'aes-128-cbc', Buffer.from(sessionKey, 'base64'), Buffer.from(iv, 'base64') ); let decoded = decipher.update(encryptedData, 'base64', 'utf8'); decoded += decipher.final('utf8'); return JSON.parse(decoded); };这段代码里aes-128-cbc对应微信官方加密算法,sessionKey理论上不应由前端传,生产环境应由云函数通过用户身份去换取或直接存到数据库;上面这样写在本地开发联调时比较方便,部署前要收紧。解密结果是一个 JSON 字符串,JSON.parse后就能拿到stepInfoList。
3.4 数据为空和跨天问题的处理
解密后的stepInfoList是数组,每一项里的timestamp是当天零点的时间戳,单位是秒;step是该天总步数。注意step为 0 是合法值,不能写成if (!step)来过滤。日期换算时给timestamp乘 1000 再传给Date,否则会出现 1970。接口一般返回最近 30 天数据,但开发者工具里经常是模拟数据,真机上如果用户没开微信运动,返回的数组可能只有一条空数据。
提示:不要尝试在前端引入 crypto-js 硬解,既拿不到
session_key,还会把小程序包体积撑大;解密这一步放在云函数或自有后端而不是页面源码里。
4. 步数页面的数据渲染:index 卡片、资源路径与 util.js 封装
4.1 本地图片资源为什么不走网络
src目录里放着 avatar_1.png、avatar_2.png、liked.png、unlike.png、cover_pic.jpg、hr.jpg 这些资源。页面引用本地图片的好处是列表渲染时不依赖网络,也不受 downloadFile 合法域名限制;如果包是从线上摘出来的,还要先确认这些图片的版权和大小。这个包已经把所有图片放在本地,写cover_pic.jpg这种封面图时直接填/src/cover_pic.jpg即可,不需要再去找微信小程序图片提取工具从 wxpkg 缓存里翻。本地图片路径一旦写错,控制台会直接报fail,所以路径里的/src/前缀不要省略。
4.2 用 wx:for 渲染步数卡片
步数主页的卡片列表一般就是wx:for渲染,核心模板如下:
<view class="step-card" wx:for="{{stepList}}" wx:key="date"> <view class="step-date">{{item.date}}</view> <view class="step-number">{{item.step}}</view> <image class="step-like" src="{{item.liked ? '/src/liked.png' : '/src/unlike.png'}}" /> </view>这段模板里wx:for默认把当前项叫作item,wx:key="date"告诉框架用date字段做列表唯一标识;这里不能写wx:key="item.date",直接写字段名。item.liked是布尔值,如果后端返回的是 0/1,模板里true条件会不成立,所以最好在数据层把 0/1 转成布尔。image的src用/src/liked.png这种绝对路径最稳,相对路径在分包场景容易算错,尤其是orgpk这类子页面引用根目录资源时。
4.3 util.js 里的日期格式化与排序
util.js里最常用的是把秒级时间戳变成日期字符串,以及给步数排序:
function formatDate(timestamp) { const d = new Date(timestamp * 1000); const month = d.getMonth() + 1; const day = d.getDate(); return ( d.getFullYear() + '-' + (month < 10 ? '0' + month : month) + '-' + (day < 10 ? '0' + day : day) ); } function compareStep(a, b) { return b.step - a.step; } module.exports = { formatDate, compareStep };timestamp * 1000是为了把秒转成毫秒,getMonth()从 0 开始所以要加 1。compareStep里b.step - a.step返回降序,如果orgpk页面要显示团队排行前三名,直接对这个排序结果取slice(0, 3)。排序会修改原数组,调用前先const sorted = list.slice().sort(compareStep),否则页面后面想按时间顺序展示时,原数组已经被改掉了。
4.4 orgpk 和 myorg 页面在工程里的定位
orgpk从命名看更像组织 PK 或团队排行,myorg是个人组织信息页,这类页面如果要显示当前用户头像,需要自己接wx.getUserProfile或云开发用户表,源码里没有现成接口时不要硬编码一个固定头像。logs页面放调试日志,上线前最好把wx.navigateTo的入口隐藏。如果myorg要自定义顶部导航,微信小程序顶部导航栏高度不能写死 64px,要用wx.getMenuButtonBoundingClientRect拿到胶囊按钮位置,再计算状态栏高度,否则不同机型的返回按钮会错位。
5. 真机预览前检查清单与步数异常排查技巧
5.1 让代码在开发者工具里先跑起来
第一步不写新业务代码,先跑通编译。把project.config.json里的appid换成自己的 AppID,使用测试号也能编译,但wx.getWeRunData在测试号上经常拿不到真实数据。导入项目时选择包含app.json的那个目录,若选了外层 zip 解压目录,开发者工具会报找不到app.json。编译通过后,在模拟器里先点开index页,看控制台有没有getWeRunData:fail can only be invoked by user TAP gesture,这类错误说明页面初始化阶段不该直接调隐私接口。
5.2 常见异常和对应处理
真机调试时,步数相关的报错有规律,下面是我遇到最多的几类:
| 现象 | 原因 | 处理 |
|---|---|---|
fail:auth deny | 用户拒绝过授权 | 用wx.openSetting引导打开 |
encryptedData无法解析 | 前端直接尝试解密 | 把解密放到云函数或后端 |
| 开发者工具有数据,真机没有 | 测试 AppID 或基础库版本不一致 | 换成正式 AppID,切换基础库版本 |
| 日期全是 1970 | timestamp没有乘 1000 | 检查formatDate入参 |
除了表格里的原因,还有一个延迟问题:微信运动的数据不是实时写入的,用户刚走完步数立刻调接口,stepInfoList里可能还是旧值。比较稳妥的做法是在页面onShow时拉一次,下拉刷新再拉一次,而不是用onLoad只拉一次。
5.3 用 useMock 开关让页面脱离授权调试
对页面渲染来说,反复授权很浪费时间,我习惯在app.globalData里放一个useMock开关:
const useMock = true; if (useMock) { const mockList = [ { date: '2024-01-01', step: 10234, liked: false }, { date: '2024-01-02', step: 8721, liked: true } ]; this.setData({ stepList: mockList }); } else { loadSteps(); }useMock为 true 时直接喂假数据,页面所有wx:for、图片切换和排序逻辑都能正常走一遍;联调后端时改成 false 即可。setData传整个数组在几十条数据时没问题,如果步数列表规划超过 20 条,前端可以做分页或改用recycle-view,避免一次setData传递大数组导致滚动掉帧。把useMock的切换和真实请求放到同一个入口函数里,后面接云函数时只需要替换else分支,页面模板保持不动。
本文还有配套的精品资源,点击获取