简介:微信跑步小程序源码是一份面向微信小程序初学者的完整实战项目,以跑步追踪与数据记录为场景,解决从零搭建运动健康类应用时的页面组织、数据同步和设备能力接入问题。资源共20个文件,包体仅12KB,核心包含WXML页面结构、WXSS样式、JavaScript业务逻辑、JSON项目配置以及图标与说明文档,整体目录结构简洁紧凑,适合逐文件拆解学习。源码覆盖Page路由、setData响应式更新、本地存储、wx.getLocation定位、加速度计计步、事件绑定、UI组件组合等关键知识点,并展示了公共工具模块、自定义图标素材与说明文档的组织方式,也涉及开发者工具调试、用户授权等实操环节;由于体积小巧,可快速上手修改,适合作为课程设计或原型开发的基础。已有1972人学习下载,尤其适合正在准备毕业设计、小程序竞赛或想转前端开发的读者,以此为起点进一步扩展健身打卡、户外运动等场景。 最近不少人来问“微信跑步小程序源码”,有拿去做毕业设计的,有想在公司项目里加一个运动记录模块的,还有打算直接做成跑步社区产品的。我前前后后做过几个运动类小程序,从最基础的轨迹记录、数据统计,到后来的付费课程报名、跑友排行榜,基本把微信小程序里跟定位、地图、支付相关的坑都踩过一遍。这篇就把这些经验整理出来,尽量给你一套看得懂、能上手的思路和代码,而不是丢一个光秃秃的源码包让你自己猜。
如果你正在找源码参考,或者打算自己从零写一个跑步类小程序,这篇文章基本就是按“需求拆解 → 技术选型 → 核心实现 → 问题排查”的顺序来聊,不管你是前端新手还是写过几个小项目的开发者,都能在里面找到能直接用的东西。
1. 起步之前:先想清楚你要做什么样的跑步小程序
1.1 需求拆解:跑步小程序的核心功能边界
跑步类小程序和商城、工具类小程序不太一样,它的核心是“记录一段运动过程,并把数据可视化地还给用户”。用户打开小程序,开始跑步,结束时看到一条轨迹、一组数据,这个体验闭环就算成立了。
如果把一个跑步小程序比作一套完整产品,最基础的功能边界大致是这几块:
- 运动记录:开始、暂停、继续、结束,这是整个小程序的骨架。
- 定位与轨迹:拿到用户的经纬度,在地图上画出跑步路线。
- 数据统计:距离、时长、配速、卡路里,这是用户最关心的数字。
- 历史记录:保存每一次运动,用户可以回看。
- 个人中心:微信授权登录,展示头像昵称和累计运动数据。
进阶一点的功能,比如跑步计划、排行榜、社区打卡、付费课程报名,都属于在上面这个底座上加东西。我见过不少初次做这个项目的朋友,一上来就想把排行榜、动态、社交全塞进去,结果光定位和轨迹就折腾了两周。我的建议是:第一个版本先把“开始跑步 → 记录轨迹 → 结束出数据 → 存进历史”这条主链路跑通,其他都是后话。
1.2 技术选型:原生小程序、uni-app 还是 Taro
这个选择题几乎每个想做跑步小程序的人都会遇到。直接从我的经验说结论:如果这个项目是你一个人的毕设或者练手项目,建议用原生微信小程序;如果你是想跨端复用,而且你本身熟悉 Vue,可以用 uni-app;如果你的团队是 React 技术栈,Taro 更顺一些。
原生小程序的好处是,微信提供的能力包装得最直接,定位、地图、支付这些 API 都是第一手对接,遇到问题搜资料也最好搜。uni-app 和 Taro 确实能跨端,但运动类小程序对定位和地图渲染的要求比较高,跨端框架在这两块偶尔会有 API 透传不完整的情况,出了问题反而更难排查。
另外提醒一句,如果你想基于别人的源码改,先看清楚它是原生还是 uni-app,两者目录结构和构建方式差别很大,硬改很容易把自己绕晕。HBuilder 打开原生小程序项目时会提示“不是开发者”之类的问题,多半也是工程类型不匹配导致的。
1.3 后端方案:云开发还是自建服务
后端选型决定你后面省不省心。微信云开发(云函数 + 云数据库)对个人项目和毕设非常友好,不需要自己买服务器、配域名、搞 HTTPS,云函数里可以直接调用微信上下文,登录态也帮你处理好了。轨迹数据量不大的情况下,云数据库完全扛得住。
如果你的项目要上生产,或者以后还想做 App、Web 端,后端建议用 Node.js 或 Java 单独拆服务。原因很简单:云开发虽然方便,但数据库查询能力相对基础,报表统计、复杂权限控制、和其他系统对接时,自建服务的灵活度会高很多。
我个人的做法是:原型阶段用云开发,等核心功能稳定了,再把接口迁移到独立后端。前端代码可以做到无感切换,只要把请求层封装好就行。
2. 核心功能的技术拆解:轨迹、数据、权限
2.1 定位能力:getLocation 与后台持续定位
跑步小程序最核心的能力就是定位。微信小程序里和定位相关的接口有三个,很多人分不清:
- wx.getLocation:单次定位,适合获取当前坐标。
- wx.startLocationUpdate:持续监听位置变化,适合跑步过程中实时获取坐标。
- wx.startLocationUpdateBackground:后台持续定位,适合小程序切入后台后依然记录轨迹。
跑步场景下,通常流程是:用户点击开始 → 调用 wx.getLocation 拿到起点 → 调 wx.startLocationUpdate 持续监听位置变化 → 结束时 wx.stopLocationUpdate 停止监听。
这里有个大坑,也是很多源码跑不起来的根本原因:光在代码里调用接口是不够的,必须在 app.json 里声明权限和隐私接口。低版本基础库和真机上,缺少声明会直接导致定位失败或者拿不到经纬度。app.json 里至少要这样配置:
{ "permission": { "scope.userLocation": { "desc": "您的位置信息将用于记录跑步轨迹" } }, "requiredPrivateInfos": ["getLocation", "startLocationUpdate"] }如果你需要后台定位,还要在 app.json 的 requiredBackgroundModes 里声明 location:
{ "requiredBackgroundModes": ["location"] }注意:这个配置一旦声明,微信审核时会重点检查你的定位用途是否合理。跑步类小程序声明后台定位可以解释为“熄屏或切后台时持续记录运动轨迹”,这是说得通的。但如果你的小程序和运动八竿子打不着,却申请了后台定位,基本会被打回。
2.2 轨迹生成与地图渲染
轨迹的呈现方式是用 map 组件加上 polyline 属性。基础思路是:把定位监听回调里拿到的经纬度点一个个 push 到数组里,然后通过 setData 更新给 map 组件,polyline 就会把这些点连成一条线。
代码层面大致是这个结构:
<map id="runMap" latitude="{{latitude}}" longitude="{{longitude}}" polyline="{{polyline}}" show-location ></map>this.setData({ polyline: [{ points: this.data.trackPoints, color: "#FF6B35", width: 5, borderColor: "#FFFFFF", borderWidth: 1 }] });但 GPS 数据有一个很头疼的问题:漂移。原地不动时,GPS 点也会小幅跳动;跑到高楼或树荫下,一个点可能瞬间“飞”出去几十米。如果不做处理,轨迹画出来就是一团乱麻。常见的处理办法是加一个距离阈值,比如相邻两个点之间的距离小于 5 米就忽略,大于某个不合理的值(比如 100 米)也忽略,因为正常跑步几百毫秒内不可能位移这么远。
2.3 运动数据计算:距离、配速、卡路里
距离不推荐直接读取定位接口返回的 speed 字段累加,那个值在不同手机上差异很大。最稳的做法是用 Haversine 公式,根据两个经纬度点算球面距离,再把所有相邻点之间的距离累加起来。
Haversine 公式的实现不难,跑起来大概是这样的:
function getDistance(lat1, lng1, lat2, lng2) { const R = 6371000; const rad = Math.PI / 180; const dLat = (lat2 - lat1) * rad; const dLng = (lng2 - lng1) * rad; const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * rad) * Math.cos(lat2 * rad) * Math.sin(dLng / 2) * Math.sin(dLng / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; }配速的计算方式是运动时间除以距离,单位通常写成“分/公里”,比如跑 5 公里用了 30 分钟,配速就是 6 分 00 秒。卡路里一般用 MET 值估算,跑步的 MET 值大概在 9.8 左右,公式是 卡路里 = MET × 体重(kg) × 时间(小时),这个数值是估算值,够用但别当成医学参考。
还有个细节:暂停运动的时候,不能继续往轨迹数组里 push 点,否则暂停期间的位置晃动会变成一条难看的短横线。暂停时要停止监听或者打个暂停标记,恢复时再继续记录。
3. 实战实现:从零搭建跑步记录核心模块
3.1 项目结构与权限配置
假设我们用原生微信小程序来搭,目录结构可以分成几个清晰的部分:
├── app.js ├── app.json ├── pages/ │ ├── index/ // 首页,展示快捷入口 │ ├── run/ // 跑步记录页,核心页面 │ └── history/ // 历史记录页 ├── utils/ │ ├── gps.js // 距离计算、轨迹去噪 │ ├── format.js // 时间、配速格式化 │ └── storage.js // 本地存储封装 └── components/ └── stat-card/ // 数据展示卡片app.json 里除了权限声明,还要配置页面路径和 window 样式。这里有一个容易被忽略的点:运动页面的导航栏最好设置成自定义,因为跑步过程中需要大数字展示实时配速和距离,默认导航栏会占掉一部分屏幕空间,自定义导航栏能让界面更沉浸。
3.2 核心代码:运动状态机与轨迹记录
跑步页面的核心逻辑本质上是一个状态机:空闲、跑步中、暂停中、已结束。每一次状态切换都有对应的入口和出口。我习惯把状态和核心参数放在 data 里统一管理。
Page({ data: { status: "idle", // idle | running | paused | finished distance: 0, duration: 0, pace: "0'00\"", trackPoints: [], latitude: 0, longitude: 0 }, startRun() { wx.getLocation({ type: "gcj02", success: (res) => { this.setData({ status: "running", latitude: res.latitude, longitude: res.longitude, trackPoints: [{ latitude: res.latitude, longitude: res.longitude }] }); this._startWatch(); this._startTimer(); } }); }, _startWatch() { this._watchListener = (res) => { const point = { latitude: res.latitude, longitude: res.longitude }; // 去噪:距离过近或异常跳变的点直接忽略 const last = this.data.trackPoints[this.data.trackPoints.length - 1]; const d = getDistance(last.latitude, last.longitude, point.latitude, point.longitude); if (d < 5 || d > 100) return; const distance = this.data.distance + d; const duration = this.data.duration; const pace = duration / 60 / (distance / 1000); this.setData({ trackPoints: [...this.data.trackPoints, point], distance, pace: formatPace(pace) }); // 更新地图视野 this.setData({ latitude: point.latitude, longitude: point.longitude }); }; wx.startLocationUpdate({ success: () => { wx.onLocationChange(this._watchListener); } }); }, pauseRun() { this.setData({ status: "paused" }); wx.stopLocationUpdate(); clearInterval(this._timer); }, resumeRun() { this.setData({ status: "running" }); this._startWatch(); this._startTimer(); }, endRun() { wx.stopLocationUpdate(); clearInterval(this._timer); // 保存记录到本地,再异步同步到云数据库 const record = { distance: this.data.distance, duration: this.data.duration, pace: this.data.pace, track: this.data.trackPoints, date: Date.now() }; saveRecord(record); this.setData({ status: "finished" }); } });几个关键点再强调一下:
- 暂停时一定要 stopLocationUpdate,因为微信的持续定位是比较耗电的操作,长时间挂着对用户手机不友好,而且也会影响状态判断。
- 计时器用 setInterval 每秒更新一次 duration,但 UI 里的距离和配速不要每秒都 setData。setData 太重,频繁更新会导致页面卡顿,尤其是轨迹点数组越来越长之后。我习惯把轨迹绘制和数据展示拆开,轨迹每 3-5 秒更新一次,数据和配速每秒更新一次。
- 跑完的轨迹数据不要无脑存全量点,一小时跑下来可能有三四千个点,直接存数据库压力不小。建议做抽稀处理,比如每 10 个点保留 1 个,或者用简单的距离抽稀——相邻点距离小于 8 米就丢弃。这样轨迹形状基本不变,数据量能减少一大半。
3.3 延伸需求:微信支付v3对接要点
如果你做的是带付费功能的跑步小程序,比如赛事报名、付费训练课程,就绕不开微信支付。这里简单聊一下 v3 对接的核心流程,这部分在源码项目里也是经常被问到的。
微信支付 v3 的流程分两步:第一步是后端向微信支付统一下单接口发起请求,拿到预支付交易会话标识;第二步是后端把参数返回给小程序前端,前端调 wx.requestPayment 拉起支付。
后端统一下单时,签名串的格式是:
HTTP方法\n 请求路径\n 请求时间戳\n 请求随机串\n 请求报文主体\n这个签名串很容易拼错,每一项后面都有一个换行符,而且最后一项报文主体后面也要有换行。用商户私钥做 SHA256withRSA 签名后,放到 Authorization 头里。
小程序端拿到参数后这样调用:
wx.requestPayment({ timeStamp: data.timeStamp, nonceStr: data.nonceStr, package: data.package, // 格式为 prepay_id=xxx signType: "RSA", paySign: data.paySign, success: () => { // 支付成功 }, fail: () => { // 支付失败或取消 } });支付回调是最容易出问题的地方。支付结果通知会通过 POST 请求发到你配置的回调地址,报文里的 resource 字段是 AES-256-GCM 加密的,需要用 APIv3 密钥解密,解密后才能拿到订单号、支付状态这些信息。很多同学在回调里卡了好几天,基本都是这里。
如果遇到“支付功能暂时无法使用”的提示,先别急着改代码。先排查三件事:商户号主体是否和小程序主体一致、小程序类目是否包含需要支付能力的类目、账户有没有被平台限制。确认是限制问题的话,只能按平台要求整改后申诉,不要试图绕过去,那只会把问题越搞越大。
4. 常见问题与排查实录
4.1 定位与轨迹相关的坑
定位这块我遇到的坑可以列一个小表格,方便你对照排查:
| 现象 | 常见原因 | 解决办法 |
|---|---|---|
| 真机上 getLocation 直接走 fail | app.json 未声明 permission 或 requiredPrivateInfos | 补全配置,重新编译 |
| 模拟器定位不准,轨迹乱画 | 模拟器 GPS 模拟能力有限 | 尽量用真机调试 |
| 用户拒绝授权后无法恢复 | 未提供引导重新授权入口 | 调 wx.openSetting 引导用户打开位置权限 |
| 轨迹出现“飞点” | GPS 漂移或遮挡导致异常跳变 | 加距离阈值过滤异常点 |
| 切后台后轨迹中断 | 系统省电策略杀掉定位进程 | 使用后台定位模式,并引导用户在系统设置中允许后台运行 |
这里想多说一句“飞点”的问题。我最早做原型的时候没有做任何过滤,测试时跑了一圈,地图上两条轨迹叠加,直接画出一个“回形针”。后来在定位回调里加了距离阈值和速度判断,情况才好转。速度判断的思路是:两点之间的距离除以时间差,如果瞬时速度超过 10 米/秒,那这个点大概率是漂移点,直接丢弃。
4.2 审核与隐私合规
跑步小程序涉及地理位置信息,微信审核对隐私的要求很严格。上线前必须在 mp 后台配置“用户隐私保护指引”,把位置信息、微信昵称头像等用途写清楚。这里有一个很容易踩的坑:如果你用的是 getPhoneNumber 这类能力,也需要在隐私保护指引里声明,不然调用时会直接被拦截。
另外,如果你的小程序里嵌了 H5 页面,要注意 H5 顶部的导航栏表现。很多跑步应用会在小程序里内嵌活动页、报名页,但 H5 页面的工具栏返回箭头有时会消失,这种情况通常和 web-view 的页面栈有关,需要在 H5 页面里检查自己是否调用了隐藏导航栏的接口,或者通过 url 参数控制导航栏的显示状态。
4.3 真机调试与环境问题
真机调试时最常遇到的一个问题就是:代码在开发者工具里跑得好好的,一发到手机上就白屏或者接口报错。如果是云开发项目,先检查云环境 ID 是否配置正确,本地调试和线上环境的云环境 ID 很可能是两个不同的值。
另外一个常见场景是:你拿别人的源码在 HBuilder 里打开,然后想在微信开发者工具里运行,结果提示“不是开发者”或者“无法识别项目”。这个大概率是因为你打开的目录不是微信小程序的根目录,或者项目本身是 uni-app 项目,需要先在 HBuilder 里运行到微信开发者工具,而不是直接拿源码目录去微信开发者工具里打开。
运动类小程序还有一个细节:不同手机的后台策略差异很大,尤其是安卓阵营,很多手机默认会杀掉长时间后台定位的进程。应对方案是在代码里监听切后台事件,提示用户到系统设置里允许应用后台运行。虽然体验上有点打扰,但这是目前最有效的兜底方案。
5. 写在最后的一点个人体会
做跑步类小程序这几年,我最大的感受是:定位和轨迹记录的代码框架并不复杂,真正花时间的全在细节处理上。GPS 数据怎么去噪、后台定位怎么保证续航和轨迹完整、数据量大了之后怎么存储和绘制不卡顿,这些才是让一个源码从“能跑”变成“能上线”的关键。
如果你现在正在参考某个跑步小程序源码,建议拿到手之后先不改功能,原样在真机上跑一遍。跑的过程中细细观察定位的准确度、暂停和恢复的响应速度、跑完后的数据展示,然后再考虑怎么优化。源码是很好的起点,但只有把它拆开、弄懂、改过,它才能真正变成你自己的东西。
最后再分享一个小技巧:运动结束后的轨迹保存,别忘了把时间戳和时区一起存下来,不要只存日期字符串。等你要做周报、月报这类累计统计的时候,时区问题会省掉你很多返工的麻烦。
本文还有配套的精品资源,点击获取